Chapter 206: DALI Ballast and LED Driver Control

Chapter Objectives

Upon completing this chapter, students will be able to:

  • Understand the role and common characteristics of DALI ballasts and LED drivers (control gear).
  • Identify and use key DALI commands for configuring control gear parameters like MIN/MAX levels, power-on level, and fade times.
  • Implement sequences for querying status information from ballasts and LED drivers, such as lamp status and actual power levels.
  • Describe how scenes are stored and recalled within DALI control gear.
  • Understand the basic DALI device types relevant to lighting control (e.g., fluorescent, LED).
  • Write ESP32 code to interact with DALI ballasts/drivers for advanced control beyond simple on/off.
  • Recognize common issues when working with specific DALI control gear commands.

Introduction

In the preceding chapters, we established the fundamentals of the DALI protocol and learned how to implement a DALI master controller using the ESP32. Now, we turn our attention to the devices that actually produce light: the DALI ballasts (for fluorescent lamps, though becoming less common) and, more predominantly today, DALI LED drivers. These are collectively referred to as “control gear” in DALI terminology.

Understanding how to communicate effectively with DALI control gear is crucial for unlocking the full potential of a DALI lighting system. It’s not just about turning lights on and off or setting brightness levels. DALI control gear often has a rich set of internal parameters that can be configured and status information that can be queried. This allows for fine-tuned lighting behavior, energy optimization, and basic diagnostic capabilities.

This chapter will explore the specific DALI commands used to configure and query these lighting devices. We will examine how to set operational parameters such as minimum and maximum brightness levels, power-on behavior, fade characteristics, and how to retrieve information like lamp status or current light output. While we will continue to use the ESP32 as our DALI master, the focus here is on the interaction with the end devices, transforming our ESP32 into a truly intelligent lighting controller.

Theory

DALI Control Gear: Ballasts and LED Drivers

DALI control gear are the electronic devices that interface directly with the light sources.

  • Electronic Ballasts: Traditionally used for fluorescent lamps, gas-discharge lamps. They regulate the current and voltage supplied to the lamp. While still found in existing installations, their use in new systems is declining in favor of LED technology.
  • LED Drivers: These are specialized power supplies designed to drive LEDs or LED modules. They convert the input AC or DC power to the specific DC current/voltage required by the LEDs and often incorporate dimming capabilities. Most modern DALI installations use LED drivers.

From a DALI master’s perspective, both ballasts and LED drivers are “control gear” and respond to a common set of DALI commands defined in IEC 62386-102 (Control Gear General Requirements) and further specified by device-type (DT) numbers (e.g., IEC 62386-201 for fluorescent lamps – DT0, IEC 62386-207 for LED modules – DT6).

Key Control Gear Parameters and Configuration

DALI control gear stores several operational parameters that can be configured by a DALI master. These parameters define how the light behaves. Accessing these often requires sending “special commands” (where the S-bit in the address byte is 1) or specific sequences. Many configuration commands require sending the command twice within 100ms to confirm.

sequenceDiagram
    participant ESP32_Master as ESP32 Master
    participant DALI_Gear as DALI Control Gear

    autonumber

    ESP32_Master->>DALI_Gear: Set DTR0 (Address: SA_S0, Data: value)
    Note over DALI_Gear: DTR0 register is now loaded with value.

    ESP32_Master->>DALI_Gear: SET MIN/MAX/etc. (Address: SA_S1, Data: config_opcode)
    Note over DALI_Gear: Receives 1st command.<br>Waits for confirmation.

    ESP32_Master->>DALI_Gear: SET MIN/MAX/etc. (Address: SA_S1, Data: config_opcode)
    Note over DALI_Gear: Receives 2nd command within 100ms.<br>Confirms and applies the new setting<br>using the value stored in DTR0.

   

  1. MIN LEVEL (Minimum Arc Power Level):
    • Defines the lowest light output the control gear will produce when dimmed down (excluding OFF).
    • Value: 1 to 254 (or MASK (255) to leave unchanged). Value 0 is not allowed as MIN LEVEL.
    • Command: SET MIN LEVEL (DTR0) (Data: 0x2B, DTR0 contains the value). DTR0 (Data Transfer Register 0) is a temporary register in the control gear. You first load DTR0, then issue the configuration command.
    • Example Sequence:
      1. SEND DATA TO DTR0 (value) (Command: 0xA3, Data: min_level_value)
      2. SET MIN LEVEL (DTR0) (Address: YAAAAAA1, Data: 0x2B) – Send twice.
  2. MAX LEVEL (Maximum Arc Power Level):
    • Defines the highest light output. Often set to less than physical maximum for energy saving or to match other luminaires.
    • Value: MIN LEVEL to 254 (or MASK).
    • Command: SET MAX LEVEL (DTR0) (Data: 0x2A). Sequence similar to MIN LEVEL.
  3. POWER ON LEVEL:
    • Defines the light level the control gear will go to when AC power is first applied (or restored after a power outage).
    • Value: 1 to 254, or a special value 0xFF (MASK, no change) or 0xFE (go to MAX LEVEL).
    • Command: SET POWER ON LEVEL (DTR0) (Data: 0x2C). Sequence similar to MIN LEVEL.
  4. SYSTEM FAILURE LEVEL:
    • Defines the light level the control gear will adopt if DALI communication is lost for a specified timeout (typically if no valid DALI frame received for >600ms).
    • Value: 0 (OFF) to 254, or 0xFF (MASK), 0xFE (go to MAX LEVEL).
    • Command: SET SYSTEM FAILURE LEVEL (DTR0) (Data: 0x2D). Sequence similar to MIN LEVEL.
  5. FADE TIME:
    • Defines the duration it takes for the light level to change from its current value to a new target value (e.g., when a DAPC command is received).
    • Value: Encoded 4-bit value (0-15) representing times from <0.7s to 90.5s.
      • 0: As fast as possible (no added fade).
      • 1: 0.7 seconds
      • 15: 90.5 seconds
    • Command: SET FADE TIME (DTR0) (Data: 0x2E). Sequence similar to MIN LEVEL. DTR0 holds the 4-bit fade time value.
  6. FADE RATE:
    • Defines the number of dimming steps per second. An alternative to FADE TIME.
    • Value: Encoded 4-bit value (0-15) representing rates from 358 steps/s to 0.1 steps/s.
    • Command: SET FADE RATE (DTR0) (Data: 0x2F). Sequence similar to MIN LEVEL.
Encoded Value Fade Time (seconds) Fade Rate (steps/sec)
0 As fast as possible Not applicable
1 0.7 357.8
2 1.0 253.0
3 1.4 178.9
… (values increase) … (values decrease)
10 11.3 5.6
13 45.3 1.4
14 64.0 1.0
15 90.5 0.7

Tip: When sending configuration commands that require two transmissions (e.g., SET MIN LEVEL), the two identical forward frames must be sent consecutively, typically within 100ms of each other. The control gear only applies the setting after receiving the second identical command correctly.

Storing and Recalling Scenes

DALI control gear can store up to 16 lighting scenes (Scene 0 to Scene 15). Each scene defines a specific light output level for that device.

  • Storing a Scene Level:
    1. Set the desired light level using DIRECT ARC POWER (DAPC).
    2. Send the STORE ACTUAL DIM LEVEL IN DTR0 command (Address: YAAAAAA1, Data: 0x21). This copies the current arc power level into the control gear’s DTR0.
    3. Send the SET SCENE (DTR0, X) command (Address: YAAAAAA1, Data: 0x40 to 0x4F for scenes 0-15 respectively). Send this command twice.
      • Example: For scene 0, Data is 0x40. For scene 15, Data is 0x4F.
  • Recalling a Scene:
    • Command: GO TO SCENE X (Address: YAAAAAA0, Data: 0x10 to 0x1F for scenes 0-15).
      • Example: To recall scene 0, Data is 0x10. To recall scene 15, Data is 0x1F.
    • When a GO TO SCENE X command is received, the control gear transitions to its stored light level for that scene, using its configured FADE TIME/RATE.
graph TD
    subgraph "Scene Storage Sequence"
        A[Start: Set Desired Light Level] --> B{Send DAPC Command};
        B --> C[Wait for light to adjust];
        C --> D{Send <b>STORE ACTUAL<br>DIM LEVEL IN DTR0</b>};
        D --> E[Control Gear copies current<br>level to its DTR0];
        E --> F{"Send <b>SET SCENE (DTR0, X)</b>"};
        F --> G{"Send <b>SET SCENE (DTR0, X)</b><br>again within 100ms"};
        G --> H[End: Scene X is now stored<br>with the desired light level];
    end

    classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;
    classDef checkNode fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B;
    classDef endNode fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;

    class A,B startNode;
    class C,D,E,F,G processNode;
    class H endNode;

Querying Control Gear Status

DALI allows the master to query various status aspects of the control gear. Query commands usually have S=1 in the address byte and expect a backward frame response.

  • QUERY STATUS (Command 0x90):
    • Returns an 8-bit status byte. Each bit has a specific meaning:
      • Bit 0: Control gear failure (e.g., internal fault).
      • Bit 1: Lamp failure (lamp not operating or open circuit).
      • Bit 2: Lamp arc power on.
      • Bit 3: Limit error (e.g., requested level is outside MIN/MAX).
      • Bit 4: Fade running.
      • Bit 5: Reset state.
      • Bit 6: Missing short address.
      • Bit 7: Power failure (indicates power cycle since last query).
    • Address: YAAAAAA1, Data: 0x90.
  • QUERY CONTROL GEAR (Command 0x99):
    • Used to check if a control gear at the addressed short address is present and communicating.
    • If present, it responds with a backward frame (YES, 0xFF). If not, no response.
    • Address: YAAAAAA1, Data: 0x99.
  • QUERY ACTUAL LEVEL (Command 0xA0):
    • Returns the current actual arc power level (0-254) of the lamp.
    • Address: YAAAAAA1, Data: 0xA0.
  • QUERY MAX LEVEL (Command 0xA1), QUERY MIN LEVEL (Command 0xA2):
    • Returns the configured MAX or MIN arc power level.
    • Address: YAAAAAA1, Data: 0xA1 or 0xA2.
  • QUERY POWER ON LEVEL (Command 0xA3): (Note: This is also used to send data to DTR0. The standard uses 0xA3 for multiple purposes depending on context. For querying, it’s QUERY DTR0).
    • The command QUERY CONTENT DTR0 (opcode 0x9C) can be used to read back the value last written to DTR0. If DTR0 was loaded with the POWER ON LEVEL setting, this can retrieve it. More directly, some devices might respond to a specific query for Power On Level. The QUERY POWER ON LEVEL specific opcode is 0xA4.
    • Address: YAAAAAA1, Data: 0xA4 for QUERY POWER ON LEVEL.
  • QUERY LAMP FAILURE (Command 0x92):
    • A more specific query for lamp failure status. Returns YES (0xFF) if lamp failure.
    • Address: YAAAAAA1, Data: 0x92.

Warning: When sending a query command, the DALI master must wait for the response window (e.g., ~22ms) and be prepared to receive a backward frame. Sending another command too soon will disrupt the response.

DALI Device Types (DT Numbers)

IEC 62386 defines different device types (DTs) within separate parts of the standard. Each DT number specifies functionalities and commands relevant to that particular type of lighting equipment.

  • IEC 62386-201 (DT0): Control gear for fluorescent lamps.
  • IEC 62386-202 (DT1): Control gear for self-contained emergency lighting.
  • IEC 62386-207 (DT6): Control gear for LED modules. This is one of the most common types in modern systems.
  • IEC 62386-209 (DT8): Control gear for color control (e.g., tunable white, RGBWAF). This is more advanced.
Device Type (DT) IEC 62386 Part Description & Primary Use Case Relevance
DT0 -201 Control gear for fluorescent lamps. This was the original and most common type for many years. Legacy systems; declining in new installations.
DT1 -202 Control gear for self-contained emergency lighting. Manages battery charging, monitoring, and test sequences. Specialized; crucial for safety compliance.
DT6 -207 Control gear for LED modules. This is the most common device type in modern DALI installations. High. The primary focus for modern development.
DT8 -209 Control gear for color control. Manages color temperature (tunable white) or full color (RGBWAF). Advanced applications; growing in popularity.

While many basic commands (like DAPC, ON/OFF, scene recall, query actual level) are common across these types (as defined in Part 102), device-specific commands and parameters might differ. For this chapter, we primarily focus on commands applicable to DT6 (LED drivers) and broadly to Part 102.

DALI-2 and Control Gear

DALI-2 certification for control gear ensures stricter conformance to the standard, leading to better interoperability. DALI-2 also standardizes more detailed diagnostic information. The commands discussed are generally compatible, but DALI-2 devices might offer more robust feedback or extended features.

Practical Examples

We will extend the dali_master_utils from Chapter 205 to include functions for configuring and querying DALI control gear. Remember, full RX for queries is complex; the examples will send queries and respect the response window, but actual response data processing will remain conceptual unless explicitly stated.

The project structure (esp32_dali_cg_control) remains similar to esp32_dali_controller, with updates to dali_master_utils.h and .c, and a new dali_cg_control_main.c.

main/dali_master_utils.h (Additions/Modifications):

C
// ... (previous definitions from Chapter 205) ...

// DALI Special Command Opcodes (used with S=1 in address byte)
#define DALI_SCMD_STORE_DTR0_AS_MAX_LEVEL       0x2A // SET MAX LEVEL (DTR0)
#define DALI_SCMD_STORE_DTR0_AS_MIN_LEVEL       0x2B // SET MIN LEVEL (DTR0)
#define DALI_SCMD_STORE_DTR0_AS_POWER_ON_LEVEL  0x2C // SET POWER ON LEVEL (DTR0)
#define DALI_SCMD_STORE_DTR0_AS_SYS_FAIL_LEVEL  0x2D // SET SYSTEM FAILURE LEVEL (DTR0)
#define DALI_SCMD_STORE_DTR0_AS_FADE_TIME       0x2E // SET FADE TIME (DTR0)
#define DALI_SCMD_STORE_DTR0_AS_FADE_RATE       0x2F // SET FADE RATE (DTR0)
#define DALI_SCMD_STORE_ACTUAL_TO_DTR0          0x21 // STORE ACTUAL DIM LEVEL IN DTR0

// DALI Query Command Opcodes (used with S=1 in address byte)
#define DALI_QCMD_QUERY_STATUS                  0x90
#define DALI_QCMD_QUERY_CONTROL_GEAR_PRESENT    0x99
#define DALI_QCMD_QUERY_ACTUAL_LEVEL            0xA0
#define DALI_QCMD_QUERY_MAX_LEVEL               0xA1
#define DALI_QCMD_QUERY_MIN_LEVEL               0xA2
#define DALI_QCMD_QUERY_POWER_ON_LEVEL          0xA4
#define DALI_QCMD_QUERY_LAMP_FAILURE            0x92

// DTRx commands
#define DALI_CMD_SET_DTR0                       0xA3 // Data is value for DTR0

// Scene commands (S=0 for GO_TO_SCENE, S=1 for SET_SCENE)
// Data for GO_TO_SCENE X: 0x10 + X
// Data for SET_SCENE (DTR0), X: 0x40 + X

// Configuration functions
esp_err_t dali_master_set_dtr0(dali_master_handle_t *handle, uint8_t short_address, uint8_t value);
esp_err_t dali_master_configure_parameter(dali_master_handle_t *handle, uint8_t short_address, uint8_t config_cmd_opcode, uint8_t value);
esp_err_t dali_master_set_fade_time(dali_master_handle_t *handle, uint8_t short_address, uint8_t fade_time_value); // fade_time_value 0-15

// Scene functions
esp_err_t dali_master_store_scene(dali_master_handle_t *handle, uint8_t short_address, uint8_t scene_number, uint8_t level);
esp_err_t dali_master_recall_scene(dali_master_handle_t *handle, uint8_t short_address_or_group, bool is_group, uint8_t scene_number);

// Query functions (conceptual response handling)
esp_err_t dali_master_query_status(dali_master_handle_t *handle, uint8_t short_address, uint8_t *status_response);
esp_err_t dali_master_query_lamp_failure(dali_master_handle_t *handle, uint8_t short_address, bool *is_failure);

// ... (dali_master_deinit and other existing function declarations) ...

main/dali_master_utils.c (Additions/Modifications):

C
// ... (includes and existing functions from Chapter 205) ...

// Helper to send a command that typically requires being sent twice for confirmation
static esp_err_t send_confirmed_special_command(dali_master_handle_t *handle, uint8_t short_address_selector_bit_set, uint8_t command_opcode) {
    // Address byte (YAAAAAA1 format, short_address already shifted and S=1 applied by caller)
    esp_err_t ret;
    ESP_LOGD(TAG_DALI_UTILS, "Sending confirmed cmd: AddrByte=0x%02X, Opcode=0x%02X (1st time)", short_address_selector_bit_set, command_opcode);
    ret = dali_master_send_raw_frame(handle, short_address_selector_bit_set, command_opcode, false); // No response expected from these
    if (ret != ESP_OK) return ret;
    vTaskDelay(pdMS_TO_TICKS(50)); // Small delay between confirmed commands, DALI spec says <= 100ms

    ESP_LOGD(TAG_DALI_UTILS, "Sending confirmed cmd: AddrByte=0x%02X, Opcode=0x%02X (2nd time)", short_address_selector_bit_set, command_opcode);
    ret = dali_master_send_raw_frame(handle, short_address_selector_bit_set, command_opcode, false);
    return ret;
}

esp_err_t dali_master_set_dtr0(dali_master_handle_t *handle, uint8_t short_address, uint8_t value) {
    if (short_address > 63) return ESP_ERR_INVALID_ARG;
    // Address for SET_DTR0 is special: it's a direct command (S=0 usually) or special (S=1)
    // The DALI standard (IEC 62386-102:2014, Table 16) lists "WRITE MEMORY LOCATION (DTR0, value)" as 0xA3, Address: Y0000001
    // However, sending to a specific SA is more common like: ADDR: short_address_S1, DATA: 0xA3 (DTR0 load), then DTR0 value? This is tricky.
    // A more direct way to set DTR0 for a specific device (SA) often involves the direct "DTR0" command:
    // Cmd: DTR0, Data: value. Address is ShortAddress_S0: `(short_address << 1)`
    // Let's use this common interpretation for setting DTR0 on a specific device:
    uint8_t address_byte = (short_address << 1); // YAAAAAA0 format
    ESP_LOGI(TAG_DALI_UTILS, "SET DTR0: SA=%d, Value=0x%02X (via DTR0 command to SA_S0)", short_address, value);
    // The command for direct DTR0 write is 0xA3 but its data byte is the value itself.
    // This function is intended to set the DTR0 register with 'value'.
    // Standard way: CMD: SET DTR0 (0xA3), DATA: value
    // Address should be SA_S0 (YAAAAAA0)
    return dali_master_send_raw_frame(handle, address_byte, value, false); // Assuming DTR0 value is passed directly as data with 0xA3 (needs verification across ballasts)
    // Alternative: Address Y0000001, Data 0xA3. Then Address YAAAAAA0, Data DTR0_value (less common sequence)
    // The typical use of 0xA3 opcode is to set DTR0 directly to the data_byte value when S=0.
    // Let's re-evaluate: Send command "SET DTR0" with value:
    // Address: (short_address << 1) | 0x01  (YAAAAAA1)
    // Data: DALI_CMD_SET_DTR0 (0xA3) - *This is likely wrong, 0xA3 is the command itself*
    // The command to directly set DTR0 for a device is YAAAAAA0, DATA = value_for_dtr0. This is not an explicit "SET DTR0" command from the list.
    // Let's stick to the "load DTR0" pattern for config:
    // Command YAAAAAA1, Data: 0xA3, DTR1: <value> (This loads DTR1. We need DTR0)
    // To set DTR0, you actually send a command where the *data_byte* is the value for DTR0.
    // The command is 'DTR0'. So, Address: YAAAAAA0, Data: value_for_dtr0.
    // Re-implementing based on general DALI use for configuration:
    // Master sends (YAAAAAA0, value) to set DTR0 to 'value'
    // Then Master sends (YAAAAAA1, config_opcode) which uses DTR0
    ESP_LOGI(TAG_DALI_UTILS, "Setting DTR0 for SA %d to %d", short_address, value);
    uint8_t addr_s0 = (short_address << 1); // YAAAAAA0
    return dali_master_send_raw_frame(handle, addr_s0, value, false);
}


esp_err_t dali_master_configure_parameter(dali_master_handle_t *handle, uint8_t short_address, uint8_t config_cmd_opcode, uint8_t value) {
    if (short_address > 63) return ESP_ERR_INVALID_ARG;

    ESP_LOGI(TAG_DALI_UTILS, "Configuring SA %d: Param Opcode=0x%02X, Value=0x%02X", short_address, config_cmd_opcode, value);
    // Step 1: Set DTR0 to the desired value
    esp_err_t ret = dali_master_set_dtr0(handle, short_address, value);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG_DALI_UTILS, "Failed to set DTR0 for SA %d to %d", short_address, value);
        return ret;
    }
    vTaskDelay(pdMS_TO_TICKS(DALI_INTER_COMMAND_DELAY_MS)); // Ensure DTR0 write is processed

    // Step 2: Send the configuration command (which uses DTR0) twice
    uint8_t addr_s1 = (short_address << 1) | 0x01; // YAAAAAA1
    return send_confirmed_special_command(handle, addr_s1, config_cmd_opcode);
}

esp_err_t dali_master_set_fade_time(dali_master_handle_t *handle, uint8_t short_address, uint8_t fade_time_value) {
    if (fade_time_value > 15) return ESP_ERR_INVALID_ARG; // Fade time is 0-15
    ESP_LOGI(TAG_DALI_UTILS, "Setting Fade Time for SA %d to %d", short_address, fade_time_value);
    return dali_master_configure_parameter(handle, short_address, DALI_SCMD_STORE_DTR0_AS_FADE_TIME, fade_time_value);
}

esp_err_t dali_master_store_scene(dali_master_handle_t *handle, uint8_t short_address, uint8_t scene_number, uint8_t level) {
    if (short_address > 63 || scene_number > 15) return ESP_ERR_INVALID_ARG;

    ESP_LOGI(TAG_DALI_UTILS, "Storing Scene: SA=%d, Scene=%d, Level=%d", short_address, scene_number, level);
    // Step 1: Set the desired light level (DAPC)
    esp_err_t ret = dali_master_send_dapc(handle, short_address, level);
    if (ret != ESP_OK) return ret;
    vTaskDelay(pdMS_TO_TICKS(DALI_INTER_COMMAND_DELAY_MS)); // Wait for DAPC to take effect

    // Step 2: Send STORE ACTUAL DIM LEVEL IN DTR0 (Special Command)
    uint8_t addr_s1 = (short_address << 1) | 0x01;
    ESP_LOGD(TAG_DALI_UTILS, "Sending STORE_ACTUAL_TO_DTR0 to SA %d", short_address);
    ret = dali_master_send_raw_frame(handle, addr_s1, DALI_SCMD_STORE_ACTUAL_TO_DTR0, false);
    if (ret != ESP_OK) return ret;
    vTaskDelay(pdMS_TO_TICKS(DALI_INTER_COMMAND_DELAY_MS));

    // Step 3: Send SET SCENE (DTR0, X) command twice
    uint8_t set_scene_opcode = 0x40 + scene_number;
    return send_confirmed_special_command(handle, addr_s1, set_scene_opcode);
}

esp_err_t dali_master_recall_scene(dali_master_handle_t *handle, uint8_t short_address_or_group, bool is_group, uint8_t scene_number) {
    if (scene_number > 15) return ESP_ERR_INVALID_ARG;
    if (!is_group && short_address_or_group > 63) return ESP_ERR_INVALID_ARG;
    if (is_group && short_address_or_group > 15) return ESP_ERR_INVALID_ARG;

    uint8_t address_byte;
    if (is_group) {
        address_byte = 0x80 | (short_address_or_group << 1); // 10GGGG0
        ESP_LOGI(TAG_DALI_UTILS, "Recalling Scene %d for Group %d", scene_number, short_address_or_group);
    } else {
        address_byte = (short_address_or_group << 1); // 0AAAAAA0
        ESP_LOGI(TAG_DALI_UTILS, "Recalling Scene %d for SA %d", scene_number, short_address_or_group);
    }
    uint8_t recall_scene_data = 0x10 + scene_number;
    return dali_master_send_raw_frame(handle, address_byte, recall_scene_data, false);
}

esp_err_t dali_master_query_status(dali_master_handle_t *handle, uint8_t short_address, uint8_t *status_response) {
    if (short_address > 63) return ESP_ERR_INVALID_ARG;
    uint8_t addr_s1 = (short_address << 1) | 0x01;
    ESP_LOGI(TAG_DALI_UTILS, "Querying Status for SA %d", short_address);
    esp_err_t ret = dali_master_send_raw_frame(handle, addr_s1, DALI_QCMD_QUERY_STATUS, true); // Expect response
    if (ret == ESP_OK) {
        ESP_LOGI(TAG_DALI_UTILS, "Query Status sent. Conceptual response window passed.");
        if (status_response) { /* *status_response = ... (from actual RX data); */ ESP_LOGW(TAG_DALI_UTILS, "Actual RX not implemented."); }
    }
    return ret;
}

esp_err_t dali_master_query_lamp_failure(dali_master_handle_t *handle, uint8_t short_address, bool *is_failure) {
     if (short_address > 63) return ESP_ERR_INVALID_ARG;
    uint8_t addr_s1 = (short_address << 1) | 0x01;
    ESP_LOGI(TAG_DALI_UTILS, "Querying Lamp Failure for SA %d", short_address);
    esp_err_t ret = dali_master_send_raw_frame(handle, addr_s1, DALI_QCMD_QUERY_LAMP_FAILURE, true); // Expect response
    if (ret == ESP_OK) {
        ESP_LOGI(TAG_DALI_UTILS, "Query Lamp Failure sent. Conceptual response window passed.");
        if (is_failure) { /* *is_failure = ... (from actual RX data, response is YES (0xFF) or NO (0x00)); */ ESP_LOGW(TAG_DALI_UTILS, "Actual RX not implemented."); }
    }
    return ret;
}

// Ensure all other functions from Chapter 205 dali_master_utils.c are present here,
// such as dali_master_init, dali_master_send_raw_frame, dali_master_send_dapc,
// dali_master_send_cmd, dali_master_broadcast_cmd, dali_master_send_group_cmd,
// dali_master_query_actual_level, dali_master_deinit.
// The rmt_encode_dali_forward_frame helper is also needed.

main/dali_cg_control_main.c:

C
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "dali_master_utils.h" // Our DALI utilities

static const char *TAG_CG_MAIN = "DALI_CG_CTRL";
static dali_master_handle_t dali_handle;

// DALI Standard Commands from Chapter 205
#define DALI_CMD_OFF                    0x00
#define DALI_CMD_RECALL_MAX_LEVEL       0x05

void control_gear_config_task(void *pvParameters) {
    ESP_LOGI(TAG_CG_MAIN, "Control Gear Configuration Task Started");
    uint8_t target_sa = 0; // Target Short Address for configuration

    vTaskDelay(pdMS_TO_TICKS(2000)); // Initial delay for system to settle

    // 1. Configure MIN LEVEL to 50 for SA 0
    ESP_LOGI(TAG_CG_MAIN, "Configuring MIN LEVEL for SA %d to 50...", target_sa);
    if (dali_master_configure_parameter(&dali_handle, target_sa, DALI_SCMD_STORE_DTR0_AS_MIN_LEVEL, 50) == ESP_OK) {
        ESP_LOGI(TAG_CG_MAIN, "MIN LEVEL for SA %d configured to 50.", target_sa);
    } else {
        ESP_LOGE(TAG_CG_MAIN, "Failed to configure MIN LEVEL for SA %d.", target_sa);
    }
    vTaskDelay(pdMS_TO_TICKS(1000));

    // 2. Configure FADE TIME to ~2.8 seconds (value 3) for SA 0
    // Fade Time Values: 0 (<0.7s), 1 (0.7s), 2 (1.0s), 3 (1.4s), 4 (2.0s), 5 (2.8s)...
    uint8_t fade_time_setting = 5; // Corresponds to 2.8 seconds
    ESP_LOGI(TAG_CG_MAIN, "Configuring FADE TIME for SA %d to setting %d...", target_sa, fade_time_setting);
    if (dali_master_set_fade_time(&dali_handle, target_sa, fade_time_setting) == ESP_OK) {
        ESP_LOGI(TAG_CG_MAIN, "FADE TIME for SA %d configured to setting %d.", target_sa, fade_time_setting);
    } else {
        ESP_LOGE(TAG_CG_MAIN, "Failed to configure FADE TIME for SA %d.", target_sa);
    }
    vTaskDelay(pdMS_TO_TICKS(1000));

    // 3. Store Scene 0 for SA 0 at level 100
    ESP_LOGI(TAG_CG_MAIN, "Storing Scene 0 for SA %d at level 100...", target_sa);
    if (dali_master_store_scene(&dali_handle, target_sa, 0, 100) == ESP_OK) {
        ESP_LOGI(TAG_CG_MAIN, "Scene 0 for SA %d stored at level 100.", target_sa);
    } else {
        ESP_LOGE(TAG_CG_MAIN, "Failed to store Scene 0 for SA %d.", target_sa);
    }
    vTaskDelay(pdMS_TO_TICKS(1000));

    // 4. Recall Scene 0 for SA 0
    ESP_LOGI(TAG_CG_MAIN, "Recalling Scene 0 for SA %d...", target_sa);
    if (dali_master_recall_scene(&dali_handle, target_sa, false, 0) == ESP_OK) {
        ESP_LOGI(TAG_CG_MAIN, "Scene 0 recalled for SA %d.", target_sa);
    } else {
        ESP_LOGE(TAG_CG_MAIN, "Failed to recall Scene 0 for SA %d.", target_sa);
    }
    vTaskDelay(pdMS_TO_TICKS(3000)); // Observe the fade

    // 5. Query Status for SA 0 (conceptual response)
    uint8_t status_resp;
    ESP_LOGI(TAG_CG_MAIN, "Querying status for SA %d...", target_sa);
    if (dali_master_query_status(&dali_handle, target_sa, &status_resp) == ESP_OK) {
        ESP_LOGI(TAG_CG_MAIN, "Status query sent for SA %d. (Conceptual status: 0x%02X)", target_sa, status_resp);
        // Here you would parse status_resp if RX was implemented
    } else {
        ESP_LOGE(TAG_CG_MAIN, "Failed to query status for SA %d.", target_sa);
    }
    vTaskDelay(pdMS_TO_TICKS(1000));

    // 6. Set light to a different level then recall scene again
    ESP_LOGI(TAG_CG_MAIN, "Setting SA %d to DAPC 200", target_sa);
    dali_master_send_dapc(&dali_handle, target_sa, 200);
    vTaskDelay(pdMS_TO_TICKS(3000));

    ESP_LOGI(TAG_CG_MAIN, "Recalling Scene 0 for SA %d again...", target_sa);
    dali_master_recall_scene(&dali_handle, target_sa, false, 0);
    vTaskDelay(pdMS_TO_TICKS(3000));


    ESP_LOGI(TAG_CG_MAIN, "Configuration and scene demo finished. Task will delete itself.");
    vTaskDelete(NULL);
}

void app_main(void) {
    ESP_LOGI(TAG_CG_MAIN, "Application Start: DALI Control Gear Control");

    esp_err_t ret = dali_master_init(&dali_handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG_CG_MAIN, "Failed to initialize DALI Master: %s", esp_err_to_name(ret));
        return;
    }
    ESP_LOGI(TAG_CG_MAIN, "DALI Master Initialized Successfully.");

    xTaskCreate(control_gear_config_task, "cg_config_task", 4096, NULL, 5, NULL);
    ESP_LOGI(TAG_CG_MAIN, "app_main finished. Control gear config task running.");
}

Explanation:

  • dali_master_utils.h/.c Additions:
    • New DALI command opcodes for configuration and queries are defined.
    • dali_master_set_dtr0(): This function is crucial. Many configuration commands in DALI require loading a value into the control gear’s Data Transfer Register 0 (DTR0) first. This implementation sends the value as the data byte of a command to the short address with S=0. This is a common interpretation for directly setting DTR0 value in many ballasts.
    • dali_master_configure_parameter(): A generic function to set a parameter. It first calls dali_master_set_dtr0() to load the value, then sends the specific configuration command (which uses DTR0) twice for confirmation using the send_confirmed_special_command helper.
    • dali_master_set_fade_time(): Example using dali_master_configure_parameter.
    • dali_master_store_scene(): Implements the sequence to set a light level, store it in DTR0, and then assign DTR0’s value to a scene slot (requires two transmissions of the SET SCENE command).
    • dali_master_recall_scene(): Sends the GO TO SCENE X command.
    • Query functions (dali_master_query_status, dali_master_query_lamp_failure): Send query commands and include the expect_response = true flag and appropriate delay for the conceptual response window.
  • dali_cg_control_main.c:
    • The control_gear_config_task demonstrates:
      • Configuring MIN LEVEL and FADE TIME for a specific short address.
      • Storing a light level into a scene slot for that short address.
      • Recalling the stored scene.
      • Querying status (conceptually).

Build, Flash, and Observe

  1. Hardware Setup: Same as Chapter 205: ESP32 with DALI interface, DALI PSU, and at least one DALI control gear (e.g., LED driver) set to Short Address 0.
  2. Software:
    • Create a new project esp32_dali_cg_control.
    • Copy dali_master_utils.h and dali_master_utils.c from Chapter 205, then apply the additions/modifications described above.
    • Create dali_cg_control_main.c as shown.
    • Update CMakeLists.txt files.
    • Verify DALI_TX_GPIO and RMT settings.
    • Build, flash, and monitor.
  3. Observe:
    • The serial monitor will show logs for each configuration step.
    • The DALI lamp at SA0 should:
      • Have its MIN LEVEL and FADE TIME configured (behavioral change might be subtle without specific tests).
      • Go to level 100 when Scene 0 is stored.
      • Transition to level 100 (with the configured fade time) when Scene 0 is recalled.
      • Change to DAPC 200, then fade back to scene 0 level when recalled again.
    • Status queries will show as “sent,” but actual response data is not processed.

Variant Notes

The applicability of this chapter across ESP32 variants remains consistent with previous chapters, as it relies on the RMT peripheral for DALI TX.

  • ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2: All support the RMT operations required.
    • Ensure chosen DALI_TX_GPIO is valid.
    • RMT channel availability and API usage are per ESP-IDF v5.x.
  • Performance Impact: Sending configuration commands, which might involve multiple DALI frames and delays, is still well within the capabilities of all ESP32 variants. The DALI bus speed (1200 bps) is the main limiting factor for command throughput, not the ESP32.

No significant differences are expected across variants for the DALI master TX logic presented.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
Incorrect DTR0 Handling Configuration commands (e.g., setting MIN/MAX level) have no effect or behave unexpectedly. The light level does not change as configured. Verify Sequence: Ensure you first load the DTR0 register before sending the configuration command. The correct sequence is:
1. Send command to set DTR0.
2. Send the configuration command that uses DTR0 (twice).
Forgetting Double Transmission A configuration or scene store command is sent, but the control gear does not save or apply the new setting. The parameter reverts to its old value on the next query. Send Command Twice: Commands like SET MIN LEVEL or SET SCENE must be sent twice consecutively (within 100ms). Use a helper function to ensure this.
Misinterpreting Fade Values Fades are either instant or much longer/shorter than intended. E.g., sending SET FADE TIME with data 5 doesn’t result in a 5-second fade. Use Encoded Values: Fade Time and Rate use 4-bit encoded values (0-15). Refer to the DALI standard (IEC 62386-102) to map your desired time/rate to the correct encoded value.
Querying a Group Address Sending a query like QUERY ACTUAL LEVEL to a group or broadcast address results in no clear response or DALI bus errors/collisions. Query Short Addresses Only: Queries that expect a single data byte in response (e.g., level, status) must only be sent to individual short addresses to avoid multiple devices responding at once.
No Delay Between Commands Commands sent in rapid succession seem to be ignored by the control gear. Queries fail immediately after a configuration command. Respect Processing Time: Add small delays (vTaskDelay) of 20-50ms between commands, especially after a configuration command or before a query, to give the control gear time to process.

Exercises

  1. Power-On Level Configurator:
    • Write a function dali_master_set_power_on_level(dali_master_handle_t *handle, uint8_t short_address, uint8_t level_or_code) where level_or_code can be 0-254, or 0xFE for MAX_LEVEL, or 0xFF for MASK.
    • In your main task, configure the power-on level for SA0 to 75. (To test this, you’d need to power cycle the DALI ballast itself, not just the ESP32).
  2. System Failure Level Setter:
    • Implement dali_master_set_system_failure_level(dali_master_handle_t *handle, uint8_t short_address, uint8_t level_or_code).
    • Configure the system failure level for SA0 to go to MAX_LEVEL (0xFE). (Testing this requires simulating a DALI bus failure, e.g., by stopping the ESP32 from sending commands for over 600ms).
  3. Comprehensive Status Display (Conceptual):
    • Create a function that queries SA0 for: QUERY_STATUS, QUERY_ACTUAL_LEVEL, QUERY_MAX_LEVEL, QUERY_MIN_LEVEL.
    • Log the conceptual responses. If QUERY_STATUS indicates a lamp failure (Bit 1 set), log a warning.
  4. Fade Time Experiment:
    • Set the fade time for SA0 to the fastest (value 0). Send DAPC 50, then DAPC 200. Observe the speed.
    • Then, set the fade time for SA0 to a slow setting (e.g., value 10 for 11.3 seconds). Send DAPC 50, then DAPC 200. Observe the much slower transition.

Summary

  • DALI ballasts and LED drivers (control gear) are the end devices that control light output based on commands from a DALI master.
  • Control gear stores configurable parameters like MIN/MAX levels, power-on level, system failure level, fade time, and fade rate.
  • Configuration often involves loading a value into DTR0 and then sending a special command (usually twice) that uses DTR0’s content.
  • Up to 16 scenes can be stored per device, defining specific light levels that can be recalled with a single command.
  • DALI provides numerous query commands to retrieve status (lamp failure, power state) and parameter values from control gear.
  • Effective DALI control involves understanding these specific commands and their sequences, particularly for configuration.
  • The ESP32, acting as a DALI master, can send these commands using its RMT peripheral, enabling sophisticated control of lighting devices.

Further Reading

  1. IEC 62386-102: Digital addressable lighting interface – Part 102: General requirements – Control gear. This is the primary standard for control gear commands.
  2. IEC 62386-201 (DT0): Part 201: Particular requirements for control gear – Fluorescent lamps (device type 0).
  3. IEC 62386-207 (DT6): Part 207: Particular requirements for control gear – LED modules (device type 6).
  4. Datasheets for DALI Control Gear: Specific ballast or LED driver datasheets often provide details on supported DALI commands and any manufacturer-specific behavior.
  5. DALI Alliance (DiiA) Technical Guides: The DiiA website (https://www.dali-alliance.org/) offers numerous resources explaining DALI features and commands.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top