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

  1. Include headers:

    #include "dali.h"
    #include "dali_command.h"
    
  2. 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));
    
  3. 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));
    
  4. 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 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.command is arc power value.

  • Normal command/query: config.is_cmd = true, config.command from dali_command.h.

  • Commands requiring double transmission: set config.send_twice = true.

  • For queries, pass result != NULL and check DALI_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_gpio and the RMT RX channel on config->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; *handle is valid

  • ESP_ERR_INVALID_ARG if config or handle is NULL, or GPIO numbers are invalid

  • ESP_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

1111111S

DALI_ADDR_SPECIAL

config->addr passed through

The S (selector) bit is set to 1 when config->is_cmd is true (indirect command), or 0 when false (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 true and the driver will automatically re-transmit the frame after a 40 ms delay.

Result semantics:

  • Pass NULL for result if no backward frame is expected. The driver will wait one backward-frame window before returning.

  • Pass a pointer for result to 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 NULL if no reply is expected.

Returns

  • ESP_OK on success (including DALI_RESULT_NO_REPLY case)

  • ESP_ERR_INVALID_ARG if handle or config is NULL, or address is out of range

  • Other 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.

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_config argument 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).

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

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

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.