From 70ae1ae6cfea97b1c3c65e051c18d6d170bb20b2 Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 15 May 2026 03:17:35 +0800 Subject: [PATCH] feat(dali): enhance DALI timing configurations and add new timeout parameters Signed-off-by: Tony --- apps/gateway/sdkconfig | 6 +- apps/gateway/sdkconfig.old | 8 +- components/dali/Kconfig | 34 +++++ components/dali/README.md | 10 +- components/dali/src/dali_hal_idf5.c | 217 ++++++++++++++++++++++++++-- 5 files changed, 259 insertions(+), 16 deletions(-) diff --git a/apps/gateway/sdkconfig b/apps/gateway/sdkconfig index 5c697d6..1467d43 100644 --- a/apps/gateway/sdkconfig +++ b/apps/gateway/sdkconfig @@ -2775,8 +2775,12 @@ CONFIG_DALI_TIMER_RESOLUTION_HZ=3000000 CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US=0 CONFIG_DALI_TX_STOP_CONDITION_US=0 CONFIG_DALI_RX_STOP_CONDITION_US=0 -CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=25 +CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=20 CONFIG_DALI_DOUBLE_SEND_DELAY_MS=12 +CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=25 +CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=5 +CONFIG_DALI_FORWARD_MAX_WAIT_MS=50 +CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=9 CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500 CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS=3000 # CONFIG_DALI_LOG_LEVEL_NONE is not set diff --git a/apps/gateway/sdkconfig.old b/apps/gateway/sdkconfig.old index a631725..4309966 100644 --- a/apps/gateway/sdkconfig.old +++ b/apps/gateway/sdkconfig.old @@ -2775,8 +2775,12 @@ CONFIG_DALI_TIMER_RESOLUTION_HZ=3000000 CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US=0 CONFIG_DALI_TX_STOP_CONDITION_US=0 CONFIG_DALI_RX_STOP_CONDITION_US=0 -CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=25 +CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=12 CONFIG_DALI_DOUBLE_SEND_DELAY_MS=12 +CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=25 +CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=5 +CONFIG_DALI_FORWARD_MAX_WAIT_MS=50 +CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=9 CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500 CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS=3000 # CONFIG_DALI_LOG_LEVEL_NONE is not set @@ -2795,7 +2799,7 @@ CONFIG_DALI_TX_QUEUE_LEN=4 CONFIG_DALI_TX_REPLY_QUEUE_LEN=4 CONFIG_DALI_RX_QUEUE_LEN=50 CONFIG_DALI_DEBUG_QUEUE_LEN=100 -# CONFIG_DALI_ENABLE_DEBUG_TASK is not set +CONFIG_DALI_ENABLE_DEBUG_TASK=y CONFIG_DALI_DALI_TASK_STACK_SIZE=8192 CONFIG_DALI_DALI_TASK_PRIORITY=2 CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048 diff --git a/components/dali/Kconfig b/components/dali/Kconfig index 5c44244..6cf38d7 100644 --- a/components/dali/Kconfig +++ b/components/dali/Kconfig @@ -66,6 +66,40 @@ config DALI_DOUBLE_SEND_DELAY_MS Delay between the two frames sent by dali_send_double(), measured after the first frame has completed. Exposed for development tuning. +config DALI_FORWARD_ACTIVITY_WAIT_MS + int "Forward-frame wait after bus activity ms" + range 0 1000 + default 25 + help + Minimum delay before sending a 2- to 4-byte forward frame after normal + bus activity. This is the native queue anti-collision wait. + +config DALI_FORWARD_AFTER_BACKWARD_WAIT_MS + int "Forward-frame wait after backward frame ms" + range 0 1000 + default 5 + help + Minimum delay before sending a 2- to 4-byte forward frame after the last + valid 1-byte backward frame received by the native bus. + +config DALI_FORWARD_MAX_WAIT_MS + int "Forward-frame maximum queue wait ms" + range 0 5000 + default 50 + help + Maximum time the native queue waits for a forward-frame send window. If + no valid send window is reached before this timeout, the frame is + dropped with an error status. + +config DALI_BACKWARD_IDLE_TIMEOUT_MS + int "Backward-frame idle wait timeout ms" + range 0 1000 + default 9 + help + Time a 1-byte backward frame waits for an idle bus before being sent + anyway. Backward frame sends are not echo-verified because collisions + during addressing responses are valid DALI behavior. + config DALI_BUS_POWER_CHECK_INTERVAL_MS int "Bus power-down check interval ms" range 10 5000 diff --git a/components/dali/README.md b/components/dali/README.md index 76abd61..1a7d61a 100644 --- a/components/dali/README.md +++ b/components/dali/README.md @@ -42,7 +42,8 @@ Use `menuconfig` under `DALI Component` to configure: - Bus count and default baudrate. - Native timing values for development, including timer resolution, half-bit period, - TX/RX stop conditions, query response timeout, and double-send delay. + TX/RX stop conditions, query response timeout, double-send delay, and TX queue + arbitration waits. - TX/RX active polarity. The native gateway default is TX active low and RX active high. - Native bus power-down polling and legacy `FF FD` bus-abnormal raw-frame reporting intervals. - Native DALI HAL log level for the `dali_hal` ESP-IDF log tag. @@ -61,6 +62,13 @@ can override the half-bit period for development; keep it at 0 for baudrate-deri Query no-response timeout defaults to 25 ms, which covers the 5.5-10.5 ms backward-frame start window plus the approximately 9.95 ms backward frame duration. +Native TX queue arbitration uses frame length as the frame type signal. Two- to four-byte +forward frames wait up to `CONFIG_DALI_FORWARD_MAX_WAIT_MS` for a valid send window, +using `CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS` after normal bus activity and +`CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS` after a valid backward frame. One-byte +backward frames wait up to `CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS` for idle, then are +sent without echo verification because addressing-phase collisions are valid. + ## API Note The global TX response queue symbol was renamed: diff --git a/components/dali/src/dali_hal_idf5.c b/components/dali/src/dali_hal_idf5.c index 239ebce..9de6586 100644 --- a/components/dali/src/dali_hal_idf5.c +++ b/components/dali/src/dali_hal_idf5.c @@ -87,6 +87,22 @@ #define CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS 1000 #endif +#ifndef CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS +#define CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS 25 +#endif + +#ifndef CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS +#define CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS 5 +#endif + +#ifndef CONFIG_DALI_FORWARD_MAX_WAIT_MS +#define CONFIG_DALI_FORWARD_MAX_WAIT_MS 50 +#endif + +#ifndef CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS +#define CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS 9 +#endif + #define WITHIN_RANGE(x, min, max) ((x) > (min) && (x) < (max)) #define MAX_DELTA_RELOAD_TIME 600000000 // 600s - max u32: 4,294,967,295~4,294s @@ -138,6 +154,11 @@ typedef struct { uint64_t bus_abnormal_report_time; Dali_msg_t tx_data; + bool bus_activity_seen; + uint64_t rx_last_frame_time; + uint8_t rx_last_frame_length; + uint8_t rx_last_frame_status; + volatile bool force_backward_tx; Dali_msg_t rx_data; uint8_t tx_half_bit_counter; @@ -391,6 +412,115 @@ static uint32_t tx_completion_timeout_ms(const Dali_msg_t *msg) return timeout_ms; } +static inline bool IRAM_ATTR is_backward_tx_msg(const Dali_msg_t *msg) +{ + return msg != NULL && msg->length == 8; +} + +static bool is_forward_tx_msg(const Dali_msg_t *msg) +{ + return msg != NULL && msg->length >= 16 && msg->length <= 32 && (msg->length % 8) == 0; +} + +static UBaseType_t queue_waiting(QueueHandle_t queue); + +static uint64_t bus_last_activity_time_us(const dali_bus_ctx_t *bus) +{ + if (bus == NULL || !bus->bus_activity_seen) { + return 0; + } + return bus->rx_last_edge_time > bus->tx_last_edge_time ? bus->rx_last_edge_time + : bus->tx_last_edge_time; +} + +static bool bus_idle_for_tx(const dali_bus_ctx_t *bus) +{ + return bus != NULL && bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE && + bus->rx_state == RX_STATE_IDLE && bus->rx_level == 1 && queue_waiting(bus->tx_queue) == 0; +} + +static bool last_activity_was_backward_frame(const dali_bus_ctx_t *bus, uint64_t last_activity) +{ + return bus != NULL && bus->rx_last_frame_status == DALI_FRAME_OK && + bus->rx_last_frame_length == 8 && bus->rx_last_frame_time != 0 && + bus->rx_last_frame_time >= last_activity; +} + +static TickType_t tx_wait_poll_ticks(void) +{ + TickType_t ticks = pdMS_TO_TICKS(1); + return ticks == 0 ? 1 : ticks; +} + +static bool wait_for_forward_tx_window(dali_bus_ctx_t *bus, const Dali_msg_t *msg) +{ + if (bus == NULL || !is_forward_tx_msg(msg)) { + return true; + } + + const uint64_t started = esp_timer_get_time(); + const uint64_t max_wait_us = (uint64_t)CONFIG_DALI_FORWARD_MAX_WAIT_MS * 1000ULL; + const TickType_t poll_ticks = tx_wait_poll_ticks(); + + while (true) { + if (bus->bus_state == DALI_BUS_POWER_DOWN) { + return false; + } + + const uint64_t now = esp_timer_get_time(); + if (bus_idle_for_tx(bus)) { + const uint64_t last_activity = bus_last_activity_time_us(bus); + if (last_activity == 0) { + return true; + } + const uint32_t wait_ms = last_activity_was_backward_frame(bus, last_activity) + ? CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS + : CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS; + if (wait_ms == 0 || (now - last_activity) >= ((uint64_t)wait_ms * 1000ULL)) { + return true; + } + } + + if (max_wait_us == 0 || (now - started) >= max_wait_us) { + return false; + } + vTaskDelay(poll_ticks); + } +} + +static bool wait_for_backward_tx_window(dali_bus_ctx_t *bus, const Dali_msg_t *msg, + bool *force_start) +{ + if (force_start != NULL) { + *force_start = false; + } + if (bus == NULL || !is_backward_tx_msg(msg)) { + return true; + } + + const uint64_t started = esp_timer_get_time(); + const uint64_t timeout_us = (uint64_t)CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS * 1000ULL; + const TickType_t poll_ticks = tx_wait_poll_ticks(); + + while (true) { + if (bus->bus_state == DALI_BUS_POWER_DOWN) { + return false; + } + if (bus_idle_for_tx(bus)) { + return true; + } + + const uint64_t now = esp_timer_get_time(); + if (timeout_us == 0 || (now - started) >= timeout_us) { + if (force_start != NULL) { + *force_start = true; + } + return true; + } + vTaskDelay(poll_ticks); + } +} + static UBaseType_t queue_waiting(QueueHandle_t queue) { return queue ? uxQueueMessagesWaiting(queue) : 0; @@ -537,6 +667,16 @@ static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queu } } +static inline void IRAM_ATTR note_rx_frame_from_isr(dali_bus_ctx_t *bus) +{ + if (bus == NULL) { + return; + } + bus->rx_last_frame_time = bus->rx_last_edge_time; + bus->rx_last_frame_length = bus->rx_data.length; + bus->rx_last_frame_status = bus->rx_data.status; +} + static void IRAM_ATTR complete_tx_from_isr(dali_bus_ctx_t *bus, BaseType_t *yield) { if (bus == NULL) { @@ -544,7 +684,7 @@ static void IRAM_ATTR complete_tx_from_isr(dali_bus_ctx_t *bus, BaseType_t *yiel } bus->tx_data.status = DALI_FRAME_OK; - if (bus->tx_reply_queue) { + if (!is_backward_tx_msg(&bus->tx_data) && bus->tx_reply_queue) { if (xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, yield) != pdTRUE) { Dali_msg_t dropped = {0}; xQueueReceiveFromISR(bus->tx_reply_queue, &dropped, yield); @@ -586,6 +726,7 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg) // rx_level = 1 if and only if DALI bus is really high, idle bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin - not depend on hw: 0 - low, 1 - high + bus->bus_activity_seen = true; bus->rx_pulse_width = rx_current_edge_time - bus->rx_last_edge_time; // time from last edge bus->rx_tx_delta = rx_current_edge_time - bus->tx_last_edge_time; // time from last edge @@ -693,6 +834,7 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg) // if collision detected: we are too late after bit was transmitted else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->tx_state != TX_STATE_STOP && + !is_backward_tx_msg(&bus->tx_data) && bus->rx_tx_delta > s_timing.collision_txrx_delta) { // we need now to start collision recovery with time break: 101.9.2.4 @@ -803,20 +945,40 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now) } // start transmitting - else if(bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE && bus->tx_queue && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, NULL) == pdTRUE) + else if(bus->tx_state == TX_STATE_IDLE && bus->tx_queue) { - bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success - bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting - bus->tx_state = TX_STATE_START; // start transmitting - bus->tx_half_bit_counter = 0; - bus->tx_data_bit_counter = 0; // actually sent bits count - bus->tx_last_edge_time = esp_timer_get_time(); // get time in us - DALI_SET_BUS_LOW(bus); // start bit first half + Dali_msg_t pending = {0}; + bool can_start = bus->bus_state == DALI_BUS_READY; + bool force_backward = false; + if (!can_start && bus->force_backward_tx && + xQueuePeekFromISR(bus->tx_queue, &pending) == pdTRUE) { + if (is_backward_tx_msg(&pending) && bus->bus_state != DALI_BUS_POWER_DOWN) { + can_start = true; + force_backward = true; + } else { + bus->force_backward_tx = false; + } + } + if (can_start && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, &yield) == pdTRUE) { + bus->force_backward_tx = false; + if (force_backward) { + bus->rx_state = RX_STATE_IDLE; + } + bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success + bus->bus_activity_seen = true; + bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting + bus->tx_state = TX_STATE_START; // start transmitting + bus->tx_half_bit_counter = 0; + bus->tx_data_bit_counter = 0; // actually sent bits count + bus->tx_last_edge_time = esp_timer_get_time(); // get time in us + DALI_SET_BUS_LOW(bus); // start bit first half + } } else if(bus->bus_state == DALI_BUS_TRANSMITTING) { uint8_t bus_level = DALI_GET_BUS_LEVEL(bus); - if(bus->tx_state != TX_STATE_STOP && tx_delta > s_timing.collision_txrx_delta && + if(bus->tx_state != TX_STATE_STOP && !is_backward_tx_msg(&bus->tx_data) && + tx_delta > s_timing.collision_txrx_delta && bus_level != DALI_GET_TX_LEVEL(bus)) { bus->rx_level = bus_level; bus->rx_tx_delta = tx_delta; @@ -859,7 +1021,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now) else if(bus->bus_state == DALI_BUS_READY && bus->tx_state > TX_STATE_IDLE) { // we are not transmitting but we have data - reply to queue and let error state in tx_data.status - if (bus->tx_reply_queue) { + if (bus->tx_reply_queue && !is_backward_tx_msg(&bus->tx_data)) { xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue } bus->tx_state = TX_STATE_IDLE; // clear state @@ -875,6 +1037,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now) // rx_data.status = DALI_FRAME_ERROR; // should be set inside ISR bus->rx_data.length = bus->rx_data_bit_counter; // set length of data // rx_data.data[0] = 0xAA; // debug + note_rx_frame_from_isr(bus); publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue } } @@ -886,6 +1049,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now) bus->rx_data.status = DALI_FRAME_OK; // frame is OK bus->rx_data.length = bus->rx_data_bit_counter; // set length of data // rx_data.data[0] = 0xBB; // debug + note_rx_frame_from_isr(bus); publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue } } @@ -1117,6 +1281,11 @@ static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin) bus->tx_last_edge_time = bus->rx_last_edge_time; bus->bus_level_check_time = bus->rx_last_edge_time; bus->bus_abnormal_report_time = 0; + bus->bus_activity_seen = false; + bus->rx_last_frame_time = 0; + bus->rx_last_frame_length = 0; + bus->rx_last_frame_status = DALI_FRAME_UNKNOWN; + bus->force_backward_tx = false; err = ensure_isr_service(); if (err != ESP_OK) { @@ -1169,13 +1338,37 @@ static int dali_tx_bus(dali_bus_ctx_t *bus, Dali_msg_t *dali_msg) bus->bus_id, dali_msg->length, dali_msg->data[0], dali_msg->data[1], dali_msg->data[2], dali_msg->data[3], (unsigned long)timeout_ms); - if(xQueueSendToBack(bus->tx_queue, dali_msg, timeout_ticks) == pdFALSE) { + if (is_forward_tx_msg(dali_msg) && !wait_for_forward_tx_window(bus, dali_msg)) { + dali_msg->status = DALI_FRAME_ERROR; + log_tx_message("native TX wait timeout", bus, dali_msg, + CONFIG_DALI_FORWARD_MAX_WAIT_MS); + return ESP_FAIL; + } + + bool force_backward = false; + if (is_backward_tx_msg(dali_msg) && + !wait_for_backward_tx_window(bus, dali_msg, &force_backward)) { + dali_msg->status = DALI_FRAME_ERROR; + log_tx_message("native backward TX wait timeout", bus, dali_msg, + CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS); + return ESP_FAIL; + } + + bus->force_backward_tx = force_backward; + if(xQueueSendToBack(bus->tx_queue, dali_msg, 0) == pdFALSE) { + bus->force_backward_tx = false; log_tx_message("native TX queue full", bus, dali_msg, timeout_ms); if (bus->tx_state == TX_STATE_IDLE) { xQueueReset(bus->tx_queue); } return ESP_FAIL; } + + if (is_backward_tx_msg(dali_msg)) { + dali_msg->status = DALI_FRAME_OK; + return ESP_OK; + } + if(xQueueReceive(bus->tx_reply_queue, dali_msg, timeout_ticks) == pdFALSE) { if (bus->tx_state == TX_STATE_IDLE && queue_waiting(bus->tx_queue) == 0) { *dali_msg = bus->tx_data;