Digital Addressable Lighting Interface (DALI) Bus Driver
The DALI component provides an ESP-IDF based DALI (IEC 62386) master driver. It uses the ESP RMT peripheral to generate forward frames and decode backward frames, so applications can control and query DALI control gear directly.
Features
RMT-based Manchester TX/RX implementation for DALI physical layer timing.
Supports short address, group address, broadcast, and special command modes.
Supports direct arc power control (DAPC), control/configuration commands, and query commands.
Built-in send-twice support for commands that must be issued twice within 100 ms.
Configurable TX/RX polarity adaptation for different hardware interface circuits.
Includes a test app and a runnable lighting example.
Supported Targets
ESP32 series chips supported by the component metadata:
ESP32
ESP32-S2
ESP32-S3
ESP32-C3
ESP32-C6
ESP32-P4
ESP32-H2
Quick Start
Include headers:
#include "dali.h" #include "dali_command.h"
Initialize the driver:
dali_master_handle_t dali; dali_master_config_t cfg = { .rx_gpio = GPIO_NUM_4, .tx_gpio = GPIO_NUM_5, .invert_tx = false, .invert_rx = false, }; dali_master_rmt_config_t rmt_cfg = { .mem_block_symbols = 64, }; ESP_ERROR_CHECK(dali_new_master_rmt(&cfg, &rmt_cfg, &dali));
Send a command (no backward frame expected):
/* The driver automatically inserts the minimum inter-frame gap (> 22 Te) after every transaction — no explicit delay needed. */ dali_master_transaction_config_t tx_cfg = { .addr_type = DALI_ADDR_SHORT, .addr = 0, .is_cmd = true, .command = DALI_CMD_RECALL_MAX_LEVEL, .send_twice = false, .tx_timeout_ms = DALI_TX_TIMEOUT_MS, }; ESP_ERROR_CHECK(dali_master_do_transaction(dali, &tx_cfg, NULL));
Send a query (backward frame expected):
int reply = DALI_RESULT_NO_REPLY; dali_master_transaction_config_t tx_cfg = { .addr_type = DALI_ADDR_SHORT, .addr = 0, .is_cmd = true, .command = DALI_CMD_QUERY_STATUS, .send_twice = false, .tx_timeout_ms = DALI_TX_TIMEOUT_MS, }; ESP_ERROR_CHECK(dali_master_do_transaction(dali, &tx_cfg, &reply)); if (DALI_RESULT_IS_VALID(reply)) { ESP_LOGI("dali", "QUERY_STATUS = 0x%02X", (unsigned)reply); }
Configuration
All DALI driver configuration is done through the two configuration structures at runtime — no Kconfig options are required:
dali_master_config_t— GPIO pin assignment (rx_gpio,tx_gpio) and polarity inversion (invert_tx,invert_rx).dali_master_rmt_config_t— RMT-specific settings such as memory block size.
dali_master_config_t cfg = {
.rx_gpio = GPIO_NUM_4,
.tx_gpio = GPIO_NUM_5,
.invert_tx = false, /* By default, TX polarity is not inverted */
.invert_rx = false, /* By default, RX polarity is not inverted */
};
dali_master_rmt_config_t rmt_cfg = {
.mem_block_symbols = 0, /* 0 = auto-detect per SOC capability */
};
ESP_ERROR_CHECK(dali_new_master_rmt(&cfg, &rmt_cfg, &dali));
Command Model
Use dali_master_do_transaction() as the single entry for all transaction types.
Pass a dali_master_transaction_config_t to describe the transaction:
DAPC value write:
config.is_cmd = false,config.commandis arc power value.Normal command/query:
config.is_cmd = true,config.commandfromdali_command.h.Commands requiring double transmission: set
config.send_twice = true.For queries, pass
result != NULLand checkDALI_RESULT_IS_VALID(*result).dali_master_do_transaction()automatically inserts the required inter-frame gap (> 22 Te) after every transaction, so no manual delay is needed between consecutive calls.
Timing Notes
DALI defines strict frame spacing and backward-frame response windows.
The driver automatically inserts the minimum inter-frame gap (> 22 Te) after every
dali_master_do_transaction()call, satisfying the IEC 62386 requirement.dali_master_do_transaction()is blocking — do not call it from an ISR or a time-critical task.
Example and Test
Example application: lighting/dali_basic
Component test app:
components/dali/test_apps/main/dali_test.c
The example demonstrates:
DAPC dimming sequence
Normal command transmission
Query transactions with reply parsing
Send-twice configuration command flow
API Reference
Header File
Functions
-
esp_err_t dali_new_master_rmt(const dali_master_config_t *config, const dali_master_rmt_config_t *rmt_config, dali_master_handle_t *handle)
Create and initialize a new DALI driver instance backed by RMT.
Allocates a driver context, configures the RMT TX channel on
config->tx_gpioand the RMT RX channel onconfig->rx_gpio, and returns an opaque handle. Multiple independent instances can be created on different GPIO pairs.The naming convention follows the pattern
dali_new_bus_<backend>so that future backends (e.g.dali_new_bus_uart) can coexist without naming conflicts.- Parameters
config – [in] Pointer to bus configuration (GPIO numbers).
rmt_config – [in] Pointer to RMT-specific configuration. Pass NULL to use built-in defaults (mem_block_symbols=64).
handle – [out] Pointer to store the created handle on success.
- Returns
ESP_OK on success;
*handleis validESP_ERR_INVALID_ARG if
configorhandleis NULL, or GPIO numbers are invalidESP_ERR_NO_MEM if heap allocation fails
Other ESP_ERR codes propagated from RMT driver
-
esp_err_t dali_del_master(dali_master_handle_t handle)
De-initialize a DALI driver instance and release all resources.
Disables and deletes the RMT channels, encoder, and RX queue associated with
handle, then frees the context memory. The handle is invalid after this call.Safe to call even if dali_new_master_rmt only partially succeeded (e.g. during error recovery).
- Parameters
handle – [in] Handle returned by dali_new_master_rmt.
- Returns
ESP_OK always.
-
esp_err_t dali_master_do_transaction(dali_master_handle_t handle, const dali_master_transaction_config_t *config, int *result)
Execute a DALI transaction (forward frame, optional backward frame).
Transmits a 16-bit DALI forward frame composed of an address byte and a command/data byte, then optionally listens for an 8-bit backward frame. After the transaction completes the driver automatically inserts the minimum inter-frame gap required by IEC 62386 (> 22 Te ≈ 9.2 ms), so callers do not need to add an explicit delay between consecutive calls.
Address byte construction:
config->addr_type
First byte format
DALI_ADDR_SHORT
0AAAAAAS(A = 0–63)DALI_ADDR_GROUP
100AAAAS(A = 0–15)DALI_ADDR_BROADCAST
1111111SDALI_ADDR_SPECIAL
config->addr passed through
The S (selector) bit is set to 1 when config->is_cmd is
true(indirect command), or 0 whenfalse(direct arc-power control, DAPC).Send-twice commands: Certain configuration commands (e.g., RESET, STORE_ACTUAL_LEVEL) only take effect when sent twice within 100 ms. Set config->send_twice to
trueand the driver will automatically re-transmit the frame after a 40 ms delay.Result semantics:
Pass
NULLforresultif no backward frame is expected. The driver will wait one backward-frame window before returning.Pass a pointer for
resultto enable RX. On return:*result >= 0: valid 8-bit backward-frame value (0x00–0xFF)*result == DALI_RESULT_NO_REPLY: timeout — no reply received
Note
This function is blocking. It occupies the calling task for the duration of the TX transmission plus the backward-frame reception window (up to config->tx_timeout_ms + 50 ms). Do not call this function from an ISR or from a high-priority real-time task where blocking is not acceptable.
- Parameters
handle – [in] Handle returned by dali_new_master_rmt.
config – [in] Pointer to transaction configuration.
result – [out] Pointer to store the backward-frame result, or
NULLif no reply is expected.
- Returns
ESP_OK on success (including DALI_RESULT_NO_REPLY case)
ESP_ERR_INVALID_ARG if
handleorconfigis NULL, or address is out of rangeOther ESP_ERR codes propagated from RMT driver
Structures
-
struct dali_master_config_t
Configuration structure for creating a DALI driver instance.
Contains bus-agnostic parameters shared across all backend implementations (RMT, UART, etc.). Backend-specific parameters are passed separately via the corresponding config struct (e.g. dali_master_rmt_config_t).
Public Members
-
gpio_num_t rx_gpio
GPIO number for the DALI bus receive line
-
gpio_num_t tx_gpio
GPIO number for the DALI bus transmit line
-
bool invert_tx
Invert TX signal polarity. Enable when the hardware path between the MCU TX GPIO and the DALI bus performs an odd number of signal inversions.
-
bool invert_rx
Invert RX input signal polarity. Enable when the hardware path between the DALI bus and the MCU RX GPIO performs an odd number of signal inversions.
-
gpio_num_t rx_gpio
-
struct dali_master_rmt_config_t
RMT-backend specific configuration for a DALI master driver instance.
Pass a pointer to this struct as the
rmt_configargument of dali_new_master_rmt. Initialize with designated initializers to override the built-in defaults:dali_master_rmt_config_t rmt_cfg = { .mem_block_symbols = 48, }; dali_new_master_rmt(&cfg, &rmt_cfg, &handle);
Public Members
-
uint32_t mem_block_symbols
RMT memory block size in symbols for both TX and RX channels. Set to 0 to let the driver auto-detect the optimal value based on SOC_RMT_MEM_WORDS_PER_CHANNEL (48 on ESP32-C3/S3, 64 on ESP32/S2).
-
uint32_t mem_block_symbols
-
struct dali_master_transaction_config_t
Transaction configuration for dali_master_do_transaction().
Bundles all parameters that describe a single DALI transaction (forward frame addressing, command/data byte, and transmission options).
Public Members
-
dali_addr_type_t addr_type
Address type (dali_addr_type_t)
-
uint8_t addr
Device address (0–63 for short, 0–15 for group, ignored for broadcast, raw byte for special)
-
bool is_cmd
true → indirect command (S-bit = 1) false → direct DAPC value (S-bit = 0)
-
uint8_t command
Command code or arc-power value (second byte)
-
bool send_twice
true to repeat the frame within 100 ms
-
int tx_timeout_ms
Timeout in ms for the RMT TX completion wait
-
dali_addr_type_t addr_type
Macros
-
DALI_TX_TIMEOUT_MS
Default TX transmission timeout in milliseconds.
-
DALI_RESULT_NO_REPLY
Sentinel value returned in *result when no backward frame was received.
A genuine DALI backward frame carries an 8-bit value (0x00–0xFF). This negative sentinel is outside that range and is safe to use as a “no reply” indicator.
-
DALI_RESULT_IS_VALID(r)
Test whether a query result contains a valid backward-frame byte.
Usage:
int reply; dali_master_do_transaction(handle, &config, &reply); if (DALI_RESULT_IS_VALID(reply)) { ... }
Type Definitions
-
typedef struct dali_master_t *dali_master_handle_t
Opaque handle for a DALI driver instance.
Obtained from dali_new_master_rmt and passed to all subsequent API calls. Multiple independent instances can coexist on different GPIO pairs.
Enumerations
-
enum dali_addr_type_t
DALI address types.
Selects the addressing mode used when constructing the first byte of a forward frame.
Values:
-
enumerator DALI_ADDR_SHORT
Short address (0–63, format: 0AAAAAAS)
-
enumerator DALI_ADDR_GROUP
Group address (0–15, format: 100AAAAS)
-
enumerator DALI_ADDR_BROADCAST
Broadcast (format: 1111111S)
-
enumerator DALI_ADDR_SPECIAL
Special command byte passed through as-is
-
enumerator DALI_ADDR_SHORT
Glossary
- Te
The DALI half-period unit. Nominal value: 416.67 µs (±10 % tolerance allowed by IEC 62386). All DALI timing is expressed as multiples of Te.
- Forward Frame (FF)
A 16-bit frame transmitted by the DALI master to control gear. It consists of 1 start bit + 16 data bits + 2 stop bits = 38 Te total. The first byte encodes the address; the second byte carries the command or arc-power value.
- Backward Frame (BF)
An 8-bit reply frame sent by a DALI slave in response to a query command. It consists of 1 start bit + 8 data bits + 2 stop bits = 22 Te total. The slave must respond within 7 Te-22 Te after the forward frame ends.
- Short Address
A unique address assigned to a single DALI control gear, in the range 0-63. Encoded in the forward frame as
0AAAAAAS(A = address bits, S = selector bit).- Group Address
An address shared by up to 16 control gear units, in the range 0-15. Encoded as
100AAAAS. Allows simultaneous control of multiple fixtures without individual addressing.