Initial commit

This commit is contained in:
Tony
2026-04-29 18:53:26 +08:00
commit f4756ce816
45 changed files with 14318 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
set(dali_srcs
"src/dali.c"
"src/dali_hal_idf5.c"
)
set(dali_include_dirs "src/include")
idf_component_register(
SRCS ${dali_srcs}
INCLUDE_DIRS ${dali_include_dirs}
REQUIRES driver esp_timer
)
+68
View File
@@ -0,0 +1,68 @@
menu "DALI Component"
config DALI_PHY_COUNT
int "Maximum DALI buses"
range 1 16
default 16
help
Maximum number of DALI PHY buses managed by this component.
config DALI_DEFAULT_BAUDRATE
int "Default DALI baudrate"
range 400 2400
default 1200
help
Default baudrate used during initialization.
config DALI_API_QUEUE_LEN
int "Global API queue length"
range 1 64
default 10
config DALI_TX_QUEUE_LEN
int "Per-bus TX queue length"
range 1 16
default 1
config DALI_TX_REPLY_QUEUE_LEN
int "Per-bus TX reply queue length"
range 1 32
default 4
config DALI_RX_QUEUE_LEN
int "Per-bus RX queue length"
range 1 128
default 50
config DALI_DEBUG_QUEUE_LEN
int "Debug queue length"
range 1 256
default 100
config DALI_ENABLE_DEBUG_TASK
bool "Enable debug task"
default n
help
When enabled, starts a low-priority task that prints RX timing traces.
config DALI_DALI_TASK_STACK_SIZE
int "DALI task stack size"
range 1024 8192
default 2048
config DALI_DALI_TASK_PRIORITY
int "DALI task priority"
range 1 24
default 2
config DALI_DEBUG_TASK_STACK_SIZE
int "Debug task stack size"
range 1024 8192
default 2048
config DALI_DEBUG_TASK_PRIORITY
int "Debug task priority"
range 1 24
default 1
endmenu
+18
View File
@@ -0,0 +1,18 @@
# DALI Component Migration Notes
## 0.1.0
### Breaking API cleanup
- Renamed global queue symbol:
- `dali_send_replay_queue` -> `dali_send_reply_queue`
### Build/config changes
- Added `Kconfig` options for queue sizes and task tuning.
- `components/dali/CMakeLists.txt` now uses a fixed explicit source list for the current ESP-IDF HAL path.
### Required action
- Replace all external uses of `dali_send_replay_queue` with `dali_send_reply_queue`.
- Run `idf.py reconfigure` after updating to load new Kconfig defaults.
+60
View File
@@ -0,0 +1,60 @@
# DALI ESP-IDF Component
This component provides a DALI (IEC 62386) protocol stack and ESP-IDF HAL implementation that can be used directly in native FreeRTOS/ESP-IDF projects.
## Features
- DALI forward and backward frame support.
- Multi-bus support (`DALI_PHY_COUNT`, default 16).
- Runtime baudrate control (`400..2400`, default `1200`).
- ESP-IDF GPTimer + GPIO interrupt driven HAL.
## Public Headers
- `dali.h`: high-level DALI helpers.
- `dali_hal.h`: HAL API and queue access.
- `dali_define.h`: DALI command definitions.
## Quick Start
1. Add this component to your project.
2. Configure TX/RX pins and initialize at least one bus.
3. Send a DALI frame.
```c
#include "dali.h"
#include "dali_define.h"
#include "dali_hal.h"
void app_main(void) {
ESP_ERROR_CHECK(dali_hal_set_baudrate(1200));
ESP_ERROR_CHECK(dali_hal_init(0, 4, 5));
Dali_msg_t msg = dali_msg_new(0xFF, DALI_CMD_ON); // broadcast ON command
msg.id = 0;
dali_send(&msg);
}
```
## Kconfig Options
Use `menuconfig` under `DALI Component` to configure:
- Bus count and default baudrate.
- Queue sizes.
- Task stack sizes and priorities.
- Optional debug task.
## API Note
The global TX response queue symbol was renamed:
- old: `dali_send_replay_queue`
- new: `dali_send_reply_queue`
See `MIGRATION.md` for compatibility notes.
## Examples
- `examples/dali_basic`
- `examples/dali_multi_bus`
+11
View File
@@ -0,0 +1,11 @@
version: "0.1.0"
description: "DALI (IEC 62386) protocol and HAL component for ESP-IDF"
url: "https://github.com/openLuat/luatos-soc-idf5"
repository: "https://github.com/openLuat/luatos-soc-idf5.git"
dependencies:
idf:
version: ">=5.0"
tags:
- dali
- lighting
- esp-idf
BIN
View File
Binary file not shown.
+507
View File
@@ -0,0 +1,507 @@
#include "dali.h"
#include "dali_hal.h"
#include "dali_define.h"
#include "freertos/FreeRTOS.h"
#include <memory.h> // for memset
#include "freertos/semphr.h"
static SemaphoreHandle_t s_dali_core_lock;
static SemaphoreHandle_t dali_core_mutex(void)
{
if (s_dali_core_lock == NULL) {
s_dali_core_lock = xSemaphoreCreateRecursiveMutex();
}
return s_dali_core_lock;
}
static inline void dali_core_lock(void)
{
SemaphoreHandle_t mtx = dali_core_mutex();
if (mtx) {
xSemaphoreTakeRecursive(mtx, portMAX_DELAY);
}
}
static inline void dali_core_unlock(void)
{
if (s_dali_core_lock) {
xSemaphoreGiveRecursive(s_dali_core_lock);
}
}
Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3) {
Dali_msg_t dali_msg;
dali_msg.id = 0;
dali_msg.type = DALI_MSG_FORWARD;
dali_msg.status = 0;
dali_msg.length = bit_length; // bit count 1-32
dali_msg.data[0] = address;
dali_msg.data[1] = cmd1;
dali_msg.data[2] = cmd2;
dali_msg.data[3] = cmd3;
return dali_msg;
}
Dali_msg_t dali_msg_new(uint8_t address, uint8_t cmd1) {
return dali_msg_new_generic(16, address, cmd1, 0, 0);
}
Dali_msg_t dali_msg_new_1B(uint8_t data) {
return dali_msg_new_generic(8, data, 0, 0, 0);
}
Dali_msg_t dali_msg_new_2B(uint8_t address, uint8_t cmd1) {
return dali_msg_new_generic(16, address, cmd1, 0, 0);
}
Dali_msg_t dali_msg_new_3B(uint8_t address, uint8_t cmd1, uint8_t cmd2) {
return dali_msg_new_generic(24, address, cmd1, cmd2, 0);
}
Dali_msg_t dali_msg_new_4B(uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3) {
return dali_msg_new_generic(32, address, cmd1, cmd2, cmd3);
}
// ----------------------------------------------------------------------------------------------
// send message to DALI task - not expecting reply payload
// TODO also restrict delay - to allow recovery
static void dali_send_locked(Dali_msg_t *tx_msg) {
xQueueSendToBack(dali_send_queue, tx_msg, portMAX_DELAY); // send message to DALI task
xQueueReceive(dali_send_reply_queue, tx_msg, portMAX_DELAY); // always wait for reply that message was sent
// TODO check status
if(rx_debug_enabled & 0x01) {
printf("** tm=%d[ms] st=%d len=%d d0=%d [0x%02X] d1=%d [0x%02X]\n",
tx_msg->type, tx_msg->status, tx_msg->length, tx_msg->data[0], tx_msg->data[0], tx_msg->data[1], tx_msg->data[1]);
}
}
void dali_send(Dali_msg_t *tx_msg) {
dali_core_lock();
dali_send_locked(tx_msg);
dali_core_unlock();
}
// send double message
void dali_send_double(Dali_msg_t *dali_msg) {
dali_core_lock();
dali_send_locked(dali_msg);
// TODO check status
dali_msg->id++; // increment message ID
dali_delay_ms(10); // delay 13ms 101.8.1.2: 13.5 - 75ms
dali_send_locked(dali_msg);
// TODO check status
dali_core_unlock();
}
int dali_query(Dali_msg_t *tx_msg, Dali_msg_t *rx_msg) {
BaseType_t ret;
uint8_t bus_id = tx_msg ? tx_msg->id : 0;
if (bus_id >= DALI_PHY_COUNT || dali_receive_queues[bus_id] == NULL) {
bus_id = 0;
}
QueueHandle_t rx_q = dali_receive_queues[bus_id] ? dali_receive_queues[bus_id] : dali_receive_queue;
if (rx_q == NULL) {
printf("dali_query: rx queue not ready\n");
return -1;
}
// TODO check empty queue
if(xQueueReceive(rx_q, rx_msg, 0) == pdTRUE) {
printf("Queue not empty\n");
return -1;
}
// printf("check A tx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, tx_msg->type, tx_msg->status, tx_msg->length, tx_msg->data[0]);
dali_core_lock();
dali_send_locked(tx_msg);
// receive message from DALI task
ret = xQueueReceive(rx_q, rx_msg, pdMS_TO_TICKS(50));
dali_core_unlock();
// printf("B rx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, rx_msg->type, rx_msg->status, rx_msg->length, rx_msg->data[0]);
return ret;
}
// ----------------------------------------------------------------------------------------------
// addr: 0-63
uint32_t dali_cmd_query_rand_addr24(uint8_t addr)
{
Dali_msg_t tx, rx;
uint32_t addr24 = 0;
uint8_t device = dali_short_address(addr);
tx = dali_msg_new(device, DALI_CMD_QUERY_RAND_ADDR_H);
if(dali_query(&tx, &rx) == pdTRUE) {
addr24 |= rx.data[0] << 16;
}
else {
printf("Cannot query long address H\n");
return -1;
}
tx = dali_msg_new(device, DALI_CMD_QUERY_RAND_ADDR_M);
if(dali_query(&tx, &rx) == pdTRUE) {
addr24 |= rx.data[0] << 8;
}
else {
printf("Cannot query long address M\n");
return -1;
}
tx = dali_msg_new(device, DALI_CMD_QUERY_RAND_ADDR_L);
if(dali_query(&tx, &rx) == pdTRUE) {
addr24 |= rx.data[0];
}
else {
printf("Cannot query long address L\n");
return -1;
}
return addr24;
}
// commands
void dali_cmd(uint8_t device, uint8_t cmd)
{
Dali_msg_t tx;
tx = dali_msg_new(device, cmd);
dali_send(&tx);
}
void dali_cmd_double(uint8_t device, uint8_t cmd)
{
Dali_msg_t tx;
tx = dali_msg_new(device, cmd);
dali_send_double(&tx);
}
// device: 102.7.2.1 table 1
void dali_cmd_off(uint8_t device)
{
dali_cmd(device, DALI_CMD_OFF);
}
void dali_cmd_on(uint8_t device)
{
dali_cmd(device, DALI_CMD_ON);
}
// this is broadcast command - all devices set DTR0
void dali_set_dtr0(uint8_t value)
{
dali_cmd(DALI_CMD_DTR0, value);
}
void dali_set_dtr1(uint8_t value)
{
dali_cmd(DALI_CMD_DTR1, value);
}
void dali_set_dtr2(uint8_t value)
{
dali_cmd(DALI_CMD_DTR2, value);
}
// LEVELS
void dali_set_level(uint8_t device, uint8_t cmd, uint8_t level)
{
Dali_msg_t tx;
dali_set_dtr0(level);
tx = dali_msg_new(device, cmd);
dali_send_double(&tx);
}
// GROUP add or remove
// group: 0-15
void dali_group(uint8_t device1, uint8_t cmd, uint8_t group)
{
Dali_msg_t tx;
cmd += group & 0x0F;
tx = dali_msg_new(device1, cmd);
dali_send_double(&tx);
}
// ----------------------------------------------------------------------------------------------
// device address: 0-63
#define _QQ(cmd, val) \
tx = dali_msg_new(device, cmd); \
if(dali_query(&tx, &rx) == pdTRUE) { \
val = rx.data[0]; \
} \
else printf("Cannot query: %s\n", #cmd); \
void dali_device_info(uint8_t addr)
{
Dali_msg_t tx, rx;
// int ret;
uint8_t device = dali_short_address(addr);
uint32_t addr24 = 0;
uint8_t op=0, status=0, dtr0=0, dtr1=0, dtr2=0;
uint8_t levelActual=0, levelMax=0, levelMin=0, levelPowerOn=0, levelSystemFail=0, levelPhysMin=0;
uint8_t fadeTime=0, extFadeTime=0;
addr24 = dali_cmd_query_rand_addr24(addr);
_QQ(DALI_CMD_QUERY_OPERATING_MODE, op);
_QQ(DALI_CMD_QUERY_STATUS, status);
_QQ(DALI_CMD_QUERY_DTR0, dtr0);
_QQ(DALI_CMD_QUERY_DTR1, dtr1);
_QQ(DALI_CMD_QUERY_DTR2, dtr2);
printf("Device %2d [0x%zX]: MODE=0x%02X STAT=0x%02X DTR0=%3d [0x%02X] DTR1=%3d [0x%02X] DTR2=%3d [0x%02X]\n",
addr, (size_t)addr24, op, status, dtr0, dtr0, dtr1, dtr1, dtr2, dtr2);
_QQ(DALI_CMD_QUERY_ACTUAL_LEVEL, levelActual);
_QQ(DALI_CMD_QUERY_MAX_LEVEL, levelMax);
_QQ(DALI_CMD_QUERY_MIN_LEVEL, levelMin);
_QQ(DALI_CMD_QUERY_POWER_ON_LEVEL, levelPowerOn);
_QQ(DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL, levelSystemFail);
_QQ(DALI_CMD_QUERY_PHYSICAL_MIN, levelPhysMin);
_QQ(DALI_CMD_QUERY_FADE_TIME, fadeTime);
_QQ(DALI_CMD_QUERY_EXT_FADE_TIME, extFadeTime);
printf("* LEVELS ACT=%3d MAX=%3d MIN=%3d PMIN=%3d PWON=%3d SYSF=%3d FTM=0x%02X EFTM=0x%02X\n",
levelActual, levelMax, levelMin, levelPhysMin, levelPowerOn, levelSystemFail, fadeTime, extFadeTime);
// groups
uint8_t group0=0, group1=0;
_QQ(DALI_CMD_QUERY_GROUPS_0_7, group0);
_QQ(DALI_CMD_QUERY_GROUPS_8_15, group1);
printf("* GROUPS 0-7=0x%02X 8-15=0x%02X\n", group0, group1);
uint8_t scenes[16];
for(int i=0; i<16; i++) {
_QQ(DALI_CMD_QUERY_SCENE_LEVEL + i, scenes[i]);
}
printf("* SCENES: ");
for(int i=0; i<16; i++) {
printf("%3d ", scenes[i]);
if(i % 8 == 7) printf(" ");
}
printf("\n");
}
#undef _QQ // undefine macro
void dali_device_identify(uint8_t device)
{
Dali_msg_t tx;
tx = dali_msg_new(device, DALI_CMD_IDENTIFY);
dali_send_double(&tx);
}
// reset
// - random 24-bit address to 0xFFFFFF
// - levels to default values, clear groups and scenes
void dali_device_reset(uint8_t device)
{
Dali_msg_t tx;
tx = dali_msg_new(device, DALI_CMD_RESET);
dali_send_double(&tx);
dali_delay_ms(300); // wait for reset
}
// try identify all devices by asking for operating mode
// - check from 0 to 63
// - uint8 dev[64]
void dali_find_all_short(uint8_t *dev, int with_print)
{
printf("Identifying all devices\n");
Dali_msg_t tx, rx;
int ret;
memset(dev, 0, 64);
int cnt = 0;
for(int i=0; i<64; i++)
{
tx = dali_msg_new(dali_short_address(i), DALI_CMD_QUERY_OPERATING_MODE);
ret = dali_query(&tx, &rx);
if(ret == pdTRUE) {
dev[i] = 1;
cnt++;
// printf("Device %2d: operating mode: 0x%X\n", i, rx.data[0]);
if(with_print) printf("%d", i % 10);
}
else {
if(with_print) printf(".");
}
if(with_print) {
fflush(stdout);
dali_delay_ms(100);
}
}
if(with_print) printf(" \n");
printf("Found %d devices:", cnt);
for(int i=0; i<64; i++) if(dev[i]) printf(" %d", i);
printf("\n");
}
// ----------------------------------------------------------------------------------------------
// RANDOMIZE
// device: 0AAA AAA1 - only device with short address
// 1111 1111 - all devices without short address
// 0000 0000 - all devices
void dali_cmd_randomize(uint8_t device)
{
Dali_msg_t tx;
dali_cmd_initialise(device); // 0x00=ALL 0xFF=without short address 0b0AAA AAA1=with short address
// randomize 24-bit address
tx = dali_msg_new(DALI_CMD_RANDOMISE, 0x00);
dali_send_double(&tx);
dali_delay_ms(100); // wait for randomize
dali_cmd_terminate();
printf("*** Randomize end\n");
}
// - requires INITIALISE state
void dali_set_search_addr24(uint32_t addr24)
{
Dali_msg_t tx;
tx = dali_msg_new(DALI_CMD_SEARCHADDRH, addr24 >> 16);
dali_send(&tx);
tx = dali_msg_new(DALI_CMD_SEARCHADDRM, addr24 >> 8);
dali_send(&tx);
tx = dali_msg_new(DALI_CMD_SEARCHADDRL, addr24);
dali_send(&tx);
}
// only device in INITIALISE state and not in WITHDRAW state
// return: 24-bit address
// 0xFFFFFF - not found
uint32_t dali_binary_search()
{
Dali_msg_t tx, rx;
uint32_t high = 0xFFFFFF; // we must start from 0xFFFFFF but max address is 0xFFFFFE, it simplifies search
uint32_t low = 0x000000;
uint32_t mid;
int ret;
for(int i=0; i<24; i++)
{
mid = (high + low) / 2;
dali_set_search_addr24(mid);
// compare
tx = dali_msg_new(DALI_CMD_COMPARE, 0x00);
ret = dali_query(&tx, &rx);
if(ret) high = mid; // found address <= mid and high is possible address
else low = mid;
// printf("CMP i=%2d rx=%d tm=%d[ms] st=%d len=%d d0=0x%X mid=0x%06X high=0x%06X low=0x%06X\n",
// i, ret, rx.type, rx.status, rx.length, rx.data[0], mid, high, low);
if(high - low < 2) {
if(low == 0 && mid != 0) { // special case for 0
mid = 0;
continue;
}
break;
}
dali_delay_ms(10);
}
uint32_t addr24 = high; // found address is always in high
printf("* Address: 0x%zX\n", (size_t)addr24);
return addr24;
}
// discard address in dali search command COMPARE
// - requires INITIALISE state
void dali_cmd_withdraw(uint32_t addr24)
{
Dali_msg_t tx;
dali_set_search_addr24(addr24);
tx = dali_msg_new(DALI_CMD_WITHDRAW, 0x00);
dali_send(&tx);
printf("Withdraw 0x%06zX\n", (size_t)addr24);
}
// initialise all devices selected by device:
// device: 0AAA AAA1 - only device with this short address
// 1111 1111 - all devices without short address
// 0000 0000 - all devices
void dali_cmd_initialise(uint8_t device)
{
Dali_msg_t tx;
tx = dali_msg_new(DALI_CMD_INITIALISE, device); // 0x00=ALL 0xFF=without short address 0b0AAA AAA1=with short address
dali_send_double(&tx);
}
void dali_cmd_terminate()
{
Dali_msg_t tx;
tx = dali_msg_new(DALI_CMD_TERMINATE, 0x00);
dali_send(&tx);
}
// Reassign short addresses to all devices selected by device:
// - see dali_cmd_initialise(device)
void dali_reindex_addresses(uint8_t device)
{
Dali_msg_t tx;
uint32_t addr24 = 0;
printf("Start searching\n");
dali_cmd_initialise(device); // 0x00=ALL 0xFF=without short address 0b0AAA AAA1=with short address
for(uint8_t i=0; i<63; i++)
{
addr24 = dali_binary_search();
if(addr24 == 0xFFFFFF) break;
uint8_t device = dali_short_address(i);
dali_set_search_addr24(addr24);
tx = dali_msg_new(DALI_CMD_PROGRAM_SHORT, device);
dali_send(&tx);
printf("Assign address: 0x%06zX --> %d\n", (size_t)addr24, i);
dali_cmd_withdraw(addr24);
}
dali_cmd_terminate();
printf("End searching\n");
}
void dali_change_short_address(int addr1, int addr2)
{
Dali_msg_t tx;
uint8_t device1, device2;
device1 = dali_short_address(addr1 & 0x3F);
if(addr2 == -1) device2 = DALI_ADR_BROADCAST; // delete short address
else if(addr2 < 0 || addr2 > 63) {
printf("Invalid address: %d\n", addr2);
return;
}
else {
device2 = dali_short_address(addr2 & 0x3F);
}
int32_t addr24 = dali_cmd_query_rand_addr24(addr1);
if(addr24 == -1) {
printf("Cannot read address 24 for %d\n", addr1);
return;
}
dali_cmd_initialise(device1);
dali_set_search_addr24(addr24);
tx = dali_msg_new(DALI_CMD_PROGRAM_SHORT, device2);
dali_send(&tx);
dali_cmd_terminate();
printf("Address changed: %d --> %d\n", addr1, addr2);
}
+934
View File
@@ -0,0 +1,934 @@
#include <stdio.h>
#include <memory.h>
//ESP-IDF HAL
#ifdef CONFIG_IDF_TARGET
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/portmacro.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "driver/gptimer.h"
#include "dali_hal.h"
#ifndef CONFIG_DALI_API_QUEUE_LEN
#define CONFIG_DALI_API_QUEUE_LEN 10
#endif
#ifndef CONFIG_DALI_TX_QUEUE_LEN
#define CONFIG_DALI_TX_QUEUE_LEN 1
#endif
#ifndef CONFIG_DALI_TX_REPLY_QUEUE_LEN
#define CONFIG_DALI_TX_REPLY_QUEUE_LEN 4
#endif
#ifndef CONFIG_DALI_RX_QUEUE_LEN
#define CONFIG_DALI_RX_QUEUE_LEN 50
#endif
#ifndef CONFIG_DALI_DEBUG_QUEUE_LEN
#define CONFIG_DALI_DEBUG_QUEUE_LEN 100
#endif
#ifndef CONFIG_DALI_DALI_TASK_STACK_SIZE
#define CONFIG_DALI_DALI_TASK_STACK_SIZE 2048
#endif
#ifndef CONFIG_DALI_DALI_TASK_PRIORITY
#define CONFIG_DALI_DALI_TASK_PRIORITY 2
#endif
#ifndef CONFIG_DALI_DEBUG_TASK_STACK_SIZE
#define CONFIG_DALI_DEBUG_TASK_STACK_SIZE 2048
#endif
#ifndef CONFIG_DALI_DEBUG_TASK_PRIORITY
#define CONFIG_DALI_DEBUG_TASK_PRIORITY 1
#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
#define DALI_BAUDRATE_MIN 400U
#define DALI_BAUDRATE_MAX 2400U
typedef struct {
uint32_t hb;
uint32_t rx_hb_min;
uint32_t rx_hb_max;
uint32_t rx_2hb_min;
uint32_t rx_2hb_max;
uint32_t rx_stop_cond;
uint32_t time_bus_down;
uint32_t time_break_min;
uint32_t time_recovery_min;
uint32_t tx_stop_cond;
uint32_t collision_txrx_delta;
} dali_timing_t;
static const char *TAG = "dali_hal";
typedef struct {
uint8_t bus_id;
uint8_t tx_pin;
uint8_t rx_pin;
dali_tx_state_t tx_state;
dali_rx_state_t rx_state;
dali_bus_state_t bus_state;
uint64_t rx_last_edge_time;
uint64_t tx_last_edge_time;
uint32_t rx_tx_delta;
uint32_t rx_pulse_width;
uint8_t rx_level;
Dali_msg_t tx_data;
Dali_msg_t rx_data;
uint8_t tx_half_bit_counter;
uint8_t tx_data_bit_counter;
uint8_t rx_half_bit_counter;
uint8_t rx_data_bit_counter;
QueueHandle_t tx_queue;
QueueHandle_t tx_reply_queue;
QueueHandle_t rx_queue;
bool inited;
} dali_bus_ctx_t;
static volatile dali_timing_t s_timing;
static uint32_t s_baudrate = DALI_DEFAULT_BAUDRATE;
static bool s_timing_ready = false;
static portMUX_TYPE s_timing_spinlock = portMUX_INITIALIZER_UNLOCKED;
static SemaphoreHandle_t s_hal_mutex;
static gptimer_handle_t gptimer = NULL;
static SemaphoreHandle_t dali_hal_mutex(void)
{
if (s_hal_mutex == NULL) {
s_hal_mutex = xSemaphoreCreateRecursiveMutex();
}
return s_hal_mutex;
}
static inline void dali_hal_lock(void)
{
SemaphoreHandle_t mtx = dali_hal_mutex();
if (mtx) {
xSemaphoreTakeRecursive(mtx, portMAX_DELAY);
}
}
static inline void dali_hal_unlock(void)
{
if (s_hal_mutex) {
xSemaphoreGiveRecursive(s_hal_mutex);
}
}
static uint32_t dali_half_bit_from_baud(uint32_t baudrate)
{
if (baudrate < DALI_BAUDRATE_MIN || baudrate > DALI_BAUDRATE_MAX) {
return 0;
}
uint64_t hb = 500000ULL + (baudrate / 2U); // round to nearest
hb /= baudrate;
if (hb == 0 || hb > 2000000ULL) { // should never happen with checked bounds
return 0;
}
return (uint32_t)hb;
}
static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us)
{
return (uint32_t)(((uint64_t)base_us * hb_us + (DALI_TIME_HB / 2U)) / DALI_TIME_HB);
}
static esp_err_t update_timing_locked(uint32_t baudrate)
{
uint32_t hb = dali_half_bit_from_baud(baudrate);
if (hb == 0) {
ESP_LOGE(TAG, "invalid baudrate: %u", baudrate);
return ESP_ERR_INVALID_ARG;
}
dali_timing_t new_timing = {
.hb = hb,
.rx_hb_min = scale_time_by_hb(DALI_RX_HB_MIN, hb),
.rx_hb_max = scale_time_by_hb(DALI_RX_HB_MAX, hb),
.rx_2hb_min = scale_time_by_hb(DALI_RX_2HB_MIN, hb),
.rx_2hb_max = scale_time_by_hb(DALI_RX_2HB_MAX, hb),
.rx_stop_cond = scale_time_by_hb(DALI_RX_STOP_COND, hb),
.time_bus_down = scale_time_by_hb(DALI_TIME_BUS_DOWN, hb),
.time_break_min = scale_time_by_hb(DALI_TIME_BREAK_MIN, hb),
.time_recovery_min = scale_time_by_hb(DALI_TIME_RECOVERY_MIN, hb),
.tx_stop_cond = scale_time_by_hb(DALI_TX_STOP_COND, hb),
.collision_txrx_delta = scale_time_by_hb(DALI_COLLISION_TXRX_DELTA, hb),
};
portENTER_CRITICAL(&s_timing_spinlock);
s_timing = new_timing;
s_baudrate = baudrate;
s_timing_ready = true;
portEXIT_CRITICAL(&s_timing_spinlock);
return ESP_OK;
}
static esp_err_t ensure_timing_ready_locked(void)
{
if (s_timing_ready) {
return ESP_OK;
}
return update_timing_locked(s_baudrate);
}
static esp_err_t apply_timer_alarm_locked(void)
{
if (!gptimer) {
return ESP_OK;
}
gptimer_alarm_config_t timer_alarm_config = {
.alarm_count = s_timing.hb,
.reload_count = 0,
.flags = {
.auto_reload_on_alarm = true,
},
};
return gptimer_set_alarm_action(gptimer, &timer_alarm_config);
}
// public queue for sending and receiving data
QueueHandle_t dali_send_queue; // [Dali_msg_t] from other tasks to dali task
QueueHandle_t dali_send_reply_queue; // [Dali_msg_t] from dali task to other tasks
QueueHandle_t dali_receive_queue; // alias to bus 0 for compatibility
QueueHandle_t dali_receive_queues[DALI_PHY_COUNT];
// internal queues for debug data
struct Dali_rx_dbg_data {
uint32_t rx_pulse_width;
uint32_t rx_tx_delta;
uint8_t level;
uint8_t bus_id;
};
typedef struct Dali_rx_dbg_data Dali_rx_dbg_data_t;
static QueueHandle_t rx_dbg_queue; // [Dali_rx_dbg_data_t] from GPIO ISR to debug task, data:
uint8_t rx_debug_enabled = 0; // enable debug data - set to 1 in main.c
static dali_bus_ctx_t s_bus[DALI_PHY_COUNT];
static bool s_timer_started = false;
static bool s_timer_configured = false;
static bool s_timer_enabled = false;
#if CONFIG_DALI_ENABLE_DEBUG_TASK
static bool s_debug_task_created = false;
#endif
static bool s_dali_task_created = false;
#define DALI_SET_BUS_HIGH(bus) gpio_set_level((bus)->tx_pin, DALI_TX_HIGH) // set bus level
#define DALI_SET_BUS_LOW(bus) gpio_set_level((bus)->tx_pin, DALI_TX_LOW) // set bus level
#define DALI_SET_BUS_LEVEL(bus, x) gpio_set_level((bus)->tx_pin, ((x)==DALI_TX_HIGH)) // set bus level
// !!! read from RX pin, we need real bus level, not logic level of TX pin
// return: 0 - bus level low, active state
// 1 - bus level high, idle state
#define DALI_GET_BUS_LEVEL(bus) (gpio_get_level((bus)->rx_pin) == (DALI_RX_HIGH)) // get bus level
// return: 0 - tx pin drive bus low, active state
// 1 - tx pin drive bus high, idle state
#define DALI_GET_TX_LEVEL(bus) (gpio_get_level((bus)->tx_pin) == (DALI_TX_HIGH)) // get TX pin level
static inline bool bus_valid(uint8_t bus_id) {
return bus_id < DALI_PHY_COUNT && s_bus[bus_id].inited;
}
// GPIO ISR handler
// define rx_gpio_isr_handler on any edge
static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
{
dali_bus_ctx_t *bus = (dali_bus_ctx_t *)arg;
if (bus == NULL || !bus->inited) {
return;
}
uint64_t rx_current_edge_time = esp_timer_get_time(); // get time in us
uint8_t rx_previous_level = bus->rx_level;
Dali_rx_dbg_data_t dbg;
// 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->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
// always save time of last edge
bus->rx_last_edge_time = rx_current_edge_time; // get time in us
if(bus->bus_state == DALI_BUS_READY && bus->rx_level == 0) // found start bit
{
// within range for backward frame
uint32_t time_ms = bus->rx_pulse_width / 1000; // 1ms = 1000us
if(time_ms>255) bus->rx_data.type = 255;
else bus->rx_data.type = time_ms; // pulse width in ms for later identification of type msg
bus->rx_data.id = bus->bus_id; // store bus id
bus->rx_data.length = 0;
bus->rx_data.status = DALI_FRAME_UNKNOWN; // on start in unknown state
memset(bus->rx_data.data, 0, DALI_MAX_BYTES); // clear data
bus->rx_state = RX_STATE_START; // start receiving
bus->bus_state = DALI_BUS_RECEIVING; // bus is receiving
bus->rx_half_bit_counter = 0;
bus->rx_data_bit_counter = 0; // actually received bits count
}
else if(bus->bus_state == DALI_BUS_RECEIVING)
{
if(bus->rx_state == RX_STATE_START) {
// start bit always has width HB and level 1
if(bus->rx_level == 1 && WITHIN_RANGE(bus->rx_pulse_width, s_timing.rx_hb_min, s_timing.rx_hb_max)) {
bus->rx_state = RX_STATE_DATA; // start receiving data
bus->rx_half_bit_counter++;
}
else {
bus->bus_state = DALI_BUS_ERROR; // not in range
bus->rx_data.status = DALI_FRAME_TIME_VIOLATION;
goto end_rx_isr;
}
}
else if(bus->rx_state == RX_STATE_DATA) {
// for long gap there are two valid cases:
// 1. previus_level == 1 and current_level == 0 then result is 0 bit
// 2. previus_level == 0 and current_level == 1 then result is 1 bit
// if for whatever reason we get the same level as previous we are in error, eg we missed edge
if(rx_previous_level == bus->rx_level) {
bus->rx_state = RX_STATE_ERROR; // invalid bit
bus->rx_data.status = DALI_FRAME_SEQUENCE_ERROR;
goto end_rx_isr;
}
if(WITHIN_RANGE(bus->rx_pulse_width, s_timing.rx_2hb_min, s_timing.rx_2hb_max))
{
if((bus->rx_half_bit_counter & 0x01) == 0) { // even half bit
bus->rx_state = RX_STATE_ERROR; // invalid bit
goto end_rx_isr;
}
if(rx_previous_level == 1) { // edge:1->0 value 0, skip because data[] is already set to 0
}
else { // edge:0->1 value 1
if(bus->rx_level == 1) { // result to: 1
bus->rx_data.data[bus->rx_data_bit_counter/8] |= (1 << (7 - (bus->rx_data_bit_counter % 8))); // set bit
}
}
bus->rx_half_bit_counter+=2; // 3,5,7...
bus->rx_data_bit_counter++; // increment bit counter
}
else if(WITHIN_RANGE(bus->rx_pulse_width, s_timing.rx_hb_min, s_timing.rx_hb_max))
{
if(bus->rx_half_bit_counter & 0x01) { // first half of bit
bus->rx_half_bit_counter++;
}
else { // second half of bit
bus->rx_half_bit_counter++;
if(rx_previous_level == 1) { // edge:1->0 value 0 - skip because data[] is already set to 0
}
else { // edge:0->1 value 1
if(bus->rx_level == 1) { // 0->1 is 1
bus->rx_data.data[bus->rx_data_bit_counter/8] |= (1 << (7 - (bus->rx_data_bit_counter % 8))); // set bit
}
}
bus->rx_data_bit_counter++; // increment bit counter - only on second half
}
if(bus->rx_data_bit_counter >= DALI_MAX_BITS) {
bus->rx_state = RX_STATE_STOP; // stop receiving - we cannot receive more bits
}
}
else {
bus->rx_data.status = DALI_FRAME_TIME_VIOLATION;
bus->rx_state = RX_STATE_ERROR; // invalid bit
goto end_rx_isr;
}
}
}
// if collision detected: we are too late after bit was transmitted
else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->rx_tx_delta > s_timing.collision_txrx_delta)
{
// we need now to start collision recovery with time break: 101.9.2.4
DALI_SET_BUS_LOW(bus); // force TX low - active state, inform about collision, this also generate new GPIO ISR
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
bus->bus_state = DALI_BUS_TIME_BREAK; // we are in time break state
bus->tx_data.status = DALI_FRAME_COLLISION; // collision detected
}
else if (bus->bus_state == DALI_BUS_RECOVERY)
{ // we should not receive data during recover
bus->bus_state = DALI_BUS_ERROR; // not idle wait for idle state or cancel transmission and start receiving
}
end_rx_isr:
if(rx_debug_enabled & 0x02) {
dbg.level = bus->rx_level;
dbg.rx_pulse_width = bus->rx_pulse_width;
dbg.rx_tx_delta = bus->rx_tx_delta;
dbg.bus_id = bus->bus_id;
xQueueSendToBackFromISR(rx_dbg_queue, &dbg, NULL); // send data to queue
}
}
// ---------------------------------------------------
// HW TIMER ISR
// is triggered at interval of 416us (half bit)
static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
{
BaseType_t yield = false;
uint32_t rx_delta = time_now - bus->rx_last_edge_time; // time from last edge
uint32_t tx_delta = time_now - bus->tx_last_edge_time; // time from last edge
if(rx_delta > MAX_DELTA_RELOAD_TIME)
{
bus->rx_last_edge_time = time_now - MAX_DELTA_RELOAD_TIME/2; // half of max time
}
if(tx_delta > MAX_DELTA_RELOAD_TIME)
{
bus->tx_last_edge_time = time_now - MAX_DELTA_RELOAD_TIME/2; // half of max time
}
// recovery from different error states: UNKNOWN, ERROR, POWER_DOWN
if(bus->bus_state <= DALI_BUS_ERROR)
{ // 101.8.2.4 - startup BUS after 2.4ms
if(bus->rx_level==1 && rx_delta > s_timing.rx_stop_cond)
{
bus->bus_state = DALI_BUS_READY; // bus is ready
}
}
// if bus power down - if bus is low for more then 45ms
if(bus->rx_level==0 && rx_delta > s_timing.time_bus_down)
{ // power lost
bus->bus_state = DALI_BUS_POWER_DOWN; // bus is power down - recovery see previous if
DALI_SET_BUS_HIGH(bus); // make sure TX is high
}
// recovery from collision detection
// if end of TIME BREAK BUS is LOW - collision recovery: 101.9.2.4 tab 25
// BUS: IDLE, HIGH - we are last device who keep bus low, if we want to restart transmission wait for TIME RECOVERY
// BUS: ACTIVE, LOW - BUS is busy, let caller to restart transmission
if(bus->bus_state == DALI_BUS_TIME_BREAK && rx_delta > s_timing.time_break_min)
{
DALI_SET_BUS_HIGH(bus); // TX high - idle state - generate ISR on RX pin
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
// read bus state
if(DALI_GET_BUS_LEVEL(bus) == 0) // other device is keeping bus low
{
// wait for bus to be idle before starting recovery - caller will restart transmission
bus->bus_state = DALI_BUS_ERROR; // bus is in multi master state: 101.9.2.4 Fig 17
}
else
{
// bus is free - we are the last one who keeps bus low, we can start recovery
bus->bus_state = DALI_BUS_RECOVERY; // start recovery for 4ms: 101.9.2.4 tab 25
// TODO: add check if we want to restart transmission !!!
}
}
else if(bus->bus_state == DALI_BUS_RECOVERY && rx_delta > s_timing.time_recovery_min)
{
bus->bus_state = DALI_BUS_READY; // bus is ready
// immediately start transmitting if we have data
}
// 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)
{
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
DALI_SET_BUS_LOW(bus); // start bit first half
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
}
else if(bus->bus_state == DALI_BUS_TRANSMITTING)
{
// transmit data
if(bus->tx_state == TX_STATE_START) {
bus->tx_state = TX_STATE_DATA; // start transmitting data
bus->tx_half_bit_counter++;
DALI_SET_BUS_HIGH(bus); // start bit second half
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
}
else if(bus->tx_state == TX_STATE_DATA) {
bool value = (bus->tx_data.data[bus->tx_data_bit_counter/8] >> ( 7 - (bus->tx_data_bit_counter % 8) )) & 0x01;
value ^= bus->tx_half_bit_counter & 0x01; // xor=invert value for odd half bit 1:0->1 and 0:1->0
DALI_SET_BUS_LEVEL(bus, value);
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
bus->tx_half_bit_counter++; // increment half bit counter before next test
if(bus->tx_half_bit_counter & 0x01) { // next bit
bus->tx_data_bit_counter++;
if(bus->tx_data_bit_counter >= bus->tx_data.length) {
bus->tx_state = TX_STATE_STOP;
}
}
}
else if(bus->tx_state == TX_STATE_STOP) {
// here we check TX (NOT RX) bit state
if(DALI_GET_TX_LEVEL(bus) == 0) // really ok - otherwise we will keep bus low forever
{
DALI_SET_BUS_HIGH(bus);
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
}
else if(tx_delta > s_timing.tx_stop_cond) {
bus->tx_data.status = DALI_FRAME_OK; // frame is OK
if (bus->tx_reply_queue) {
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
}
bus->tx_state = TX_STATE_IDLE; // final state with transmitted data
bus->bus_state = DALI_BUS_READY; // bus is ready
}
}
}
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) {
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
}
bus->tx_state = TX_STATE_IDLE; // clear state
}
// recover receiving
if(bus->bus_state == DALI_BUS_RECEIVING) {
if(bus->rx_state == RX_STATE_ERROR) {
// wait until bus is idle
if(rx_delta > s_timing.rx_stop_cond) { // minimum time 2400us
bus->rx_state = RX_STATE_IDLE;
bus->bus_state = DALI_BUS_READY;
// 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
if (bus->rx_queue) {
xQueueSendToBackFromISR(bus->rx_queue, &bus->rx_data, &yield); // send data to queue
}
}
}
else if(bus->rx_state == RX_STATE_DATA || bus->rx_state == RX_STATE_STOP) {
// wait until bus is idle
if(rx_delta > s_timing.rx_stop_cond) { // minimum time 2400us
bus->rx_state = RX_STATE_IDLE;
bus->bus_state = DALI_BUS_READY;
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
if (bus->rx_queue) {
xQueueSendToBackFromISR(bus->rx_queue, &bus->rx_data, &yield); // send data to queue
}
}
}
}
return yield;
}
// HW TIMER ISR wrapper
bool IRAM_ATTR hw_timer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *arg)
{
bool yield = false;
uint64_t time_now = esp_timer_get_time();
for (int i = 0; i < DALI_PHY_COUNT; i++) {
if (s_bus[i].inited) {
yield |= handle_bus_timer(&s_bus[i], time_now);
}
}
return yield;
}
// ---------------------------------------------------
// should be at lowest priority
#if CONFIG_DALI_ENABLE_DEBUG_TASK
static void debug_task(void *pvParameters)
{
Dali_rx_dbg_data_t dbg;
static uint8_t i=0;
static uint8_t HB=0; // half bit counter
char v = '*'; // interpret value
while (1)
{
if(xQueueReceive(rx_dbg_queue, &dbg, portMAX_DELAY) == pdTRUE) // wait forever
{
if(dbg.rx_pulse_width > 1000) { i=0; HB=0; v='*';}
else if ((HB & 0x01) == 1) {
if(HB == 2) v = 'S';
else v = '0' + dbg.level;
}
else v=' ';
printf("bus[%u] rx: [%2d] pw=%lu v=%u rtd=%lu [%c]",
dbg.bus_id, i++, dbg.rx_pulse_width, dbg.level, dbg.rx_tx_delta, v);
if(dbg.rx_pulse_width < 1000) {
if(dbg.rx_pulse_width > 550) HB+=2;
else HB++;
}
}
}
}
#endif
static esp_err_t ensure_isr_service(void)
{
static bool installed = false;
if (installed) {
return ESP_OK;
}
esp_err_t err = gpio_install_isr_service(0);
if (err == ESP_ERR_INVALID_STATE) {
installed = true;
return ESP_OK;
}
if (err == ESP_OK) {
installed = true;
}
return err;
}
static void ensure_common_queues(void)
{
if (!dali_send_queue) {
dali_send_queue = xQueueCreate(CONFIG_DALI_API_QUEUE_LEN, sizeof(Dali_msg_t));
}
if (!dali_send_reply_queue) {
dali_send_reply_queue = xQueueCreate(CONFIG_DALI_API_QUEUE_LEN, sizeof(Dali_msg_t));
}
if (!rx_dbg_queue) {
rx_dbg_queue = xQueueCreate(CONFIG_DALI_DEBUG_QUEUE_LEN, sizeof(Dali_rx_dbg_data_t));
}
#if CONFIG_DALI_ENABLE_DEBUG_TASK
if (!s_debug_task_created) {
xTaskCreate(debug_task, "debug_task", CONFIG_DALI_DEBUG_TASK_STACK_SIZE, NULL, CONFIG_DALI_DEBUG_TASK_PRIORITY, NULL); // at low priority !!!
s_debug_task_created = true;
}
#endif
}
static esp_err_t ensure_timer_started(void)
{
dali_hal_lock();
if (s_timer_started) {
dali_hal_unlock();
return ESP_OK;
}
esp_err_t err = ensure_timing_ready_locked();
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
if (gptimer == NULL) {
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.intr_priority = 0,
.flags = {
.intr_shared = true,
},
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
};
err = gptimer_new_timer(&timer_config, &gptimer);
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
}
if (!s_timer_configured) {
gptimer_event_callbacks_t cbs = {
.on_alarm = hw_timer_callback,
};
err = gptimer_register_event_callbacks(gptimer, &cbs, NULL);
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
s_timer_configured = true;
}
err = apply_timer_alarm_locked();
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
if (!s_timer_enabled) {
err = gptimer_enable(gptimer);
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
s_timer_enabled = true;
}
err = gptimer_start(gptimer);
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
s_timer_started = true;
dali_hal_unlock();
return ESP_OK;
}
static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
{
if (bus_id >= DALI_PHY_COUNT) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = ESP_OK;
dali_bus_ctx_t *bus = &s_bus[bus_id];
if (bus->inited) {
if (bus->tx_pin == tx_pin && bus->rx_pin == rx_pin) {
return ESP_OK;
}
return ESP_ERR_INVALID_STATE;
}
ensure_common_queues();
bus->bus_id = bus_id;
bus->tx_pin = tx_pin;
bus->rx_pin = rx_pin;
gpio_config_t io_conf;
// DALI TX
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = BIT(bus->tx_pin);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
err = gpio_config(&io_conf);
if (err != ESP_OK) {
return err;
}
// DALI RX
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = BIT(bus->rx_pin);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0; // no pull-up or pull-down - bus has default pull-down
err = gpio_config(&io_conf);
if (err != ESP_OK) {
return err;
}
// set initial state
DALI_SET_BUS_HIGH(bus); // TX high - idle state
if (!bus->tx_queue) {
bus->tx_queue = xQueueCreate(CONFIG_DALI_TX_QUEUE_LEN, sizeof(Dali_msg_t));
}
if (!bus->tx_reply_queue) {
bus->tx_reply_queue = xQueueCreate(CONFIG_DALI_TX_REPLY_QUEUE_LEN, sizeof(Dali_msg_t));
}
if (!bus->rx_queue) {
bus->rx_queue = xQueueCreate(CONFIG_DALI_RX_QUEUE_LEN, sizeof(Dali_msg_t));
dali_receive_queues[bus_id] = bus->rx_queue;
if (bus_id == 0) {
dali_receive_queue = bus->rx_queue; // backward compatibility
}
}
bus->rx_last_edge_time = esp_timer_get_time(); // get time in us - startup time
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin
bus->tx_last_edge_time = bus->rx_last_edge_time;
err = ensure_isr_service();
if (err != ESP_OK) {
return err;
}
err = gpio_set_intr_type(bus->rx_pin, GPIO_INTR_ANYEDGE);
if (err != ESP_OK) {
return err;
}
err = gpio_isr_handler_add(bus->rx_pin, rx_gpio_isr_handler, bus); // add isr handler for specific gpio pin
if (err != ESP_OK) {
return err;
}
bus->bus_state = DALI_BUS_READY;
bus->tx_state = TX_STATE_IDLE;
bus->rx_state = RX_STATE_IDLE;
bus->inited = true;
return ESP_OK;
}
esp_err_t dali_init_ports(uint8_t _dali_tx_pin, uint8_t _dali_rx_pin)
{
return dali_hal_init(0, _dali_tx_pin, _dali_rx_pin);
}
// communication with ISR - send data to queue and wait for reply
static int dali_tx_bus(dali_bus_ctx_t *bus, Dali_msg_t *dali_msg)
{
if (bus == NULL || !bus->inited) {
return ESP_FAIL;
}
if(xQueueSendToBack(bus->tx_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
xQueueReset(bus->tx_queue); // clear queue
printf("dali_tx: Queue full\n");
return ESP_FAIL;
}
if(xQueueReceive(bus->tx_reply_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
xQueueReset(bus->tx_reply_queue); // clear queue
printf("dali_tx: No reply\n");
return ESP_FAIL;
}
return ESP_OK;
}
// dali_task - should run at highest priority
void dali_task(void *pvParameters)
{
Dali_msg_t dali_msg;
while (1)
{
// data from queue -> copy to local variable
if(xQueueReceive(dali_send_queue, &dali_msg, portMAX_DELAY) == pdTRUE) {
uint8_t bus_id = dali_msg.id;
if (!bus_valid(bus_id)) {
bus_id = 0;
}
dali_msg.id = bus_id;
dali_bus_ctx_t *bus = &s_bus[bus_id];
dali_tx_bus(bus, &dali_msg);
// send data to queue
xQueueSendToBack(dali_send_reply_queue, &dali_msg, 0);
}
}
}
esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin)
{
dali_hal_lock();
esp_err_t err = ensure_timing_ready_locked();
if (err == ESP_OK) {
ensure_common_queues();
if (!s_dali_task_created) {
BaseType_t created = xTaskCreate(dali_task, "dali_task", CONFIG_DALI_DALI_TASK_STACK_SIZE, NULL, CONFIG_DALI_DALI_TASK_PRIORITY, NULL); // at high priority
if (created != pdPASS) {
err = ESP_FAIL;
} else {
s_dali_task_created = true;
}
}
}
if (err == ESP_OK) {
err = init_bus(dali_id, tx_pin, rx_pin);
}
if (err == ESP_OK) {
err = ensure_timer_started();
}
dali_hal_unlock();
return err;
}
esp_err_t dali_hal_set_baudrate(uint32_t baudrate)
{
dali_hal_lock();
bool resume_timer = s_timer_started && gptimer;
if (resume_timer) {
esp_err_t stop_ret = gptimer_stop(gptimer);
if (stop_ret != ESP_OK) {
dali_hal_unlock();
return stop_ret;
}
s_timer_started = false;
}
esp_err_t err = update_timing_locked(baudrate);
if (err == ESP_OK && gptimer) {
err = apply_timer_alarm_locked();
}
if (resume_timer && gptimer) {
esp_err_t restart_ret = gptimer_start(gptimer);
if (restart_ret == ESP_OK) {
s_timer_started = true;
} else if (err == ESP_OK) {
err = restart_ret;
}
}
dali_hal_unlock();
return err;
}
uint32_t dali_hal_get_baudrate(void)
{
dali_hal_lock();
uint32_t baud = s_baudrate;
dali_hal_unlock();
return baud;
}
size_t dali_hal_get_inited_buses(uint8_t *ids, size_t max_ids)
{
dali_hal_lock();
size_t count = 0;
for (uint8_t i = 0; i < DALI_PHY_COUNT; i++) {
if (s_bus[i].inited) {
if (ids && count < max_ids) {
ids[count] = i;
}
count++;
}
}
dali_hal_unlock();
return count;
}
esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info)
{
if (info == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (bus_id >= DALI_PHY_COUNT) {
return ESP_ERR_INVALID_ARG;
}
dali_hal_lock();
info->bus_id = bus_id;
info->tx_pin = s_bus[bus_id].tx_pin;
info->rx_pin = s_bus[bus_id].rx_pin;
info->inited = s_bus[bus_id].inited;
dali_hal_unlock();
return ESP_OK;
}
#endif // CONFIG_IDF_TARGET
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include <stdint.h>
#include "dali_hal.h"
/*
Addressing: 102.7.2.1
0AAA AAAx - short address AAAAAA 0-63
100G GGGx - group address GGG 0-15
1111 1101 - broadcast unaddressed
1111 1111 - broadcast
x - 0 - direct arc power control (DAPC), 1 - standard command
*/
// create generic DALI message - for any bit length
Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3);
// create standard DALI message: 16,24,32 bits
Dali_msg_t dali_msg_new(uint8_t address, uint8_t cmd1);
Dali_msg_t dali_msg_new_3B(uint8_t address, uint8_t cmd1, uint8_t cmd2);
Dali_msg_t dali_msg_new_4B(uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3);
// short address 0-63 - 6 bits mask 0b00111111=0x3F
// - create device address from short address
// - device address: 0b 0AAA AAA1
inline uint8_t dali_short_address(uint8_t address) {
return ((address & 0x3F) << 1) | 0x01;
}
// group address 0-15 - 4 bits mask 0b00001111=0x0F
// - create group address from short address
// - group address: 0b100G GGG1
inline uint8_t dali_group_address(uint8_t grp) {
return ((grp & 0x0F) << 1) | 0x81;
}
void dali_send(Dali_msg_t *tx_msg);
void dali_send_double(Dali_msg_t *dali_msg);
int dali_query(Dali_msg_t *tx_msg, Dali_msg_t *rx_msg);
// DALI queries
uint32_t dali_cmd_query_rand_addr24(uint8_t addr);
void dali_find_all_short(uint8_t *dev, int with_print);
void dali_device_info(uint8_t addr);
void dali_device_identify(uint8_t device);
void dali_device_reset(uint8_t device);
// DALI commands
void dali_cmd(uint8_t device, uint8_t cmd);
void dali_cmd_double(uint8_t device, uint8_t cmd);
void dali_cmd_off(uint8_t device);
void dali_cmd_on(uint8_t device);
void dali_set_level(uint8_t device, uint8_t cmd, uint8_t level);
void dali_group(uint8_t device1, uint8_t cmd, uint8_t group);
void dali_set_dtr0(uint8_t value);
void dali_set_dtr1(uint8_t value);
void dali_set_dtr2(uint8_t value);
void dali_cmd_initialise(uint8_t device);
void dali_cmd_terminate();
void dali_cmd_randomize(uint8_t device);
void dali_cmd_withdraw(uint32_t addr24);
// DALI special commands
void dali_reindex_addresses(uint8_t device);
void dali_set_search_addr24(uint32_t addr24);
uint32_t dali_binary_search();
void dali_change_short_address(int addr1, int addr2);
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#define DALI_ADR_BROADCAST 0b11111111
#define DALI_CMD_OFF 0x00 // 102.9.7.2 11.3.2: tagetLevel=0 & OFF
#define DALI_CMD_UP 0x01 // 102.11.3.3: for 200 ms up: tagetLevel calculated from actualLevel & fadeRate
#define DALI_CMD_DOWN 0x02 // 102.11.3.4: for 200 ms down:
#define DALI_CMD_STEP_UP 0x03 // 102.11.3.5: immediate one step up
#define DALI_CMD_STEP_DOWN 0x04 // 102.11.3.6: immediate one step down
#define DALI_CMD_RECALL_MAX 0x05 // 102.11.3.7: tagetLevel=actualLevel=maxLevel
// INIT: moreover & output 100%, see RECALL MIN to identify light
#define DALI_CMD_ON DALI_CMD_RECALL_MAX
#define DALI_CMD_RECALL_MIN 0x06 // 102.11.3.7: tagetLevel=actualLevel=minLevel
// INIT: moreover & output 0% or OFF, see RECALL MAX to identify light
#define DALI_CMD_STEP_DOWN_OFF 0x07 //
#define DALI_CMD_ON_STEP_UP 0x08 //
#define DALI_CMD_DAPC_SEQ 0x09 //
#define DALI_CMD_LAST_ACTIVE 0x0A //
#define DALI_CMD_CONTIN_UP 0x0B //
#define DALI_CMD_CONTIN_DOWN 0x0C //
#define DALI_CMD_GOTO_SCENE 0x10 // + scene number 0-15
#define DALI_CMD_RESET 0x20 // 102.9.11.1 11.4.2
#define DALI_CMD_ACTUAL_IN_DTR0 0x21 //
#define DALI_CMD_SET_OPERATING_MODE 0x23 //
#define DALI_CMD_IDENTIFY 0x25 // 102.9.14.3 11.4.6
// SCENE commands
#define DALI_CMD_GO_TO_SCENE 0x10 //
#define DALI_CMD_SET_SCENE 0x40 //
#define DALI_CMD_REMOVE_FROM_SCENE 0x50 //
// GROUP commands
#define DALI_CMD_ADD_TO_GROUP 0x60 //
#define DALI_CMD_REMOVE_FROM_GROUP 0x70 //
// SET commands
#define DALI_CMD_SET_MAX_LEVEL 0x2A // 102.9.6 11.4.7
#define DALI_CMD_SET_MIN_LEVEL 0x2B // 102.9.6 11.4.8
#define DALI_CMD_SET_SYS_FAIL_LEVEL 0x2C // 102.9.12 11.4.9
#define DALI_CMD_SET_PW_ON_LEVEL 0x2D // 102.9.13 11.4.10
#define DALI_CMD_SET_FADE_TIME 0x2E // 102.9.5.2 11.4.11
#define DALI_CMD_SET_FADE_RATE 0x2F // 102.9.5.3 11.4.12
#define DALI_CMD_SET_EXT_FADE_TIME 0x30 // 102.9.5.4 11.4.13
// QUERY commands
#define DALI_CMD_QUERY_STATUS 0x90 // 102.9.16 102.11.5.2
#define DALI_CMD_QUERY_DTR0 0x98 // 102.11.5.11
#define DALI_CMD_QUERY_DTR1 0x9C // 102.11.5.16
#define DALI_CMD_QUERY_DTR2 0x9D // 102.11.5.17
#define DALI_CMD_QUERY_OPERATING_MODE 0x9E // 102.9.9 102.11.5.18
#define DALI_CMD_QUERY_PHYSICAL_MIN 0x9A // 102.11.5.14
#define DALI_CMD_QUERY_ACTUAL_LEVEL 0xA0 // 102.11.5.20
#define DALI_CMD_QUERY_MAX_LEVEL 0xA1 // 102.11.5.21
#define DALI_CMD_QUERY_MIN_LEVEL 0xA2 // 102.11.5.22
#define DALI_CMD_QUERY_POWER_ON_LEVEL 0xA3 // 102.11.5.23
#define DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL 0xA4 // 102.11.5.24
#define DALI_CMD_QUERY_FADE_TIME 0xA5 // 102.11.5.25
#define DALI_CMD_QUERY_EXT_FADE_TIME 0xA8 // 102.11.5.65
#define DALI_CMD_QUERY_SCENE_LEVEL 0xB0 //
#define DALI_CMD_QUERY_GROUPS_0_7 0xC0 //
#define DALI_CMD_QUERY_GROUPS_8_15 0xC1 //
#define DALI_CMD_QUERY_RAND_ADDR_H 0xC2 // 102.11.4.31
#define DALI_CMD_QUERY_RAND_ADDR_M 0xC3 // 102.11.4.32
#define DALI_CMD_QUERY_RAND_ADDR_L 0xC4 // 102.11.4.33
// Special commands
#define DALI_CMD_TERMINATE 0xA1 // Releases the INITIALISE state.
#define DALI_CMD_DTR0 0xA3 // Data Transfer Register 0
#define DALI_CMD_INITIALISE 0xA5 // 2x Initialise 0x00=ALL 0xFF=without short address 0b0AAA AAA1=with short address
#define DALI_CMD_RANDOMISE 0xA7 // 2x Randomises the short address of the control gear.
#define DALI_CMD_COMPARE 0xA9 // Compares the stored short address with the received short address.
#define DALI_CMD_WITHDRAW 0xAB // Withdraws the control gear from the system.
#define DALI_CMD_PING 0xAD //
#define DALI_CMD_SEARCHADDRH 0xB1 // Search Address H
#define DALI_CMD_SEARCHADDRM 0xB3 // Search Address M
#define DALI_CMD_SEARCHADDRL 0xB5 // Search Address L
#define DALI_CMD_PROGRAM_SHORT 0xB7 // Program Short Address
#define DALI_CMD_VERIFY_SHORT 0xB9 // Verify Short Address
#define DALI_CMD_QUERY_SHORT 0xBB // Query Short Address
#define DALI_CMD_ENABLE_DEVICE 0xC1 // Enable Device Type
#define DALI_CMD_DTR1 0xC3 // Data Transfer Register 1
#define DALI_CMD_DTR2 0xC5 // Data Transfer Register 2
#define DALI_CMD_WRITE_MEM_LOC 0xC7 // Write Memory Location
#define DALI_CMD_WRITE_MEM_NR 0xC9 // Write Memory no reply
+228
View File
@@ -0,0 +1,228 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#ifdef CONFIG_DALI_PHY_COUNT
#define DALI_PHY_COUNT CONFIG_DALI_PHY_COUNT
#else
#define DALI_PHY_COUNT 16
#endif
#define DALI_MAX_BYTES 4
#define DALI_MAX_BITS (DALI_MAX_BYTES * 8) // total bits
#ifdef CONFIG_DALI_DEFAULT_BAUDRATE
#define DALI_DEFAULT_BAUDRATE CONFIG_DALI_DEFAULT_BAUDRATE
#else
#define DALI_DEFAULT_BAUDRATE 1200
#endif
//
// DALI speed: 1200 bps = 833us/bit half period = 416us
// DALI coding 1 = low to high, 0 = high to low
//
// Check if hardware is capable of multi master, very restricted timing
// #define DALI_MULTIMASTER
// -------------------------------------------------------------------------------------
// Common Timing: 101.8.1.1 tab 16
#define DALI_TIME_HB 416 // us = HB - half bit
#define DALI_TIME_2HB 833 // us = 2*HB - full bit or double half bit
#define DALI_TIME_BUS_DOWN 45000 // us = 45ms = 108*HB bus power down time
// we use hardware timer for timing and crystal so values are very accurate and we assume 416us pulse width
#ifndef DALI_MULTIMASTER
// Transmit Timing: 101.8.1.1 tab 16
#define DALI_TX_HB_MIN 366 // us = 0.88*HB
#define DALI_TX_HB_MAX 467 // us = 1.12*HB
#define DALI_TX_2HB_MIN 733 // us = 0.88*2*HB
#define DALI_TX_2HB_MAX 934 // us = 1.12*2*HB
#else
// Multimaster transmission: 101.8.3.1 tab 21
#define DALI_TX_HB_MIN 400 // us = 0.96*HB
#define DALI_TX_HB_MAX 433 // us = 1.04*HB
#define DALI_TX_2HB_MIN 800 // us = 0.96*2*HB
#define DALI_TX_2HB_MAX 867 // us = 1.04*2*HB
#endif // DALI_MULTIMASTER
#define DALI_TX_STOP_COND 2450 // us = 2450/416 = 5.89 = 6*HB from last edge
// Transmit Timing: 101.8.1.2 tab 17
// backward frame are sent independently of bus state, even if bus is busy
#define DALI_TX_FF_BF_MIN 5500 // us = 5.5ms = 13*HB forward to backward frame time interval
#define DALI_TX_FF_BF_MAX 10500 // us = 10.5ms = 25*HB
#define DALI_TX_FF_FF_MIN 13500 // us = 13.5ms = 32*HB forward to forward frame time interval
#define DALI_TX_FF_FF_MAX 75500 // us = 75.5ms = 182*HB twice forward frame 101.9.4
// -------------------------------------------------------------------------------------
// Receiving Timing:
#ifndef DALI_MULTIMASTER
// Receiving Timing: 101.8.2.1 tab 18 and 19
#define DALI_RX_HB_MIN 333 // us = 0.8*HB
#define DALI_RX_HB_MAX 500 // us = 1.2*HB
#define DALI_RX_2HB_MIN 666 // us = 0.8*2*HB
#define DALI_RX_2HB_MAX 1000 // us = 1.2*2*HB
#else
// Multimaster reception: 101.9.2.3 tab 23 and 24
#define DALI_RX_HB_MIN 400 // us = 0.96*HB
#define DALI_RX_HB_MAX 433 // us = 1.04*HB
#define DALI_RX_2HB_MIN 800 // us = 0.96*2*HB
#define DALI_RX_2HB_MAX 867 // us = 1.04*2*HB
#endif // DALI_MULTIMASTER
#define DALI_RX_STOP_COND 2400 // us = 2400/416 = 5.77 = 6*HB from last edge
// Timing violation may inform about not identical backward frame: 101.9.6.2
// Timing violation for start bit
#define DALI_RX_START_TIMING_MIN 750 // us = 750/416 = 1.8 = 2*HB
#define DALI_RX_START_TIMING_MAX 1400 // us = 1400/416 = 3.4 = 4*HB
// Timing violation for other bits, see break time in collision detection
#define DALI_RX_BIT_TIMING_MIN 1200 // us = 1200/416 = 2.9 = 3*HB
#define DALI_RX_BIT_TIMING_MAX 1400 // us = 1400/416 = 3.4 = 4*HB
// collision recovery: 101.9.2.4 tab 25
#define DALI_TIME_BREAK_MIN 1200 // us = 1200/416 = 2.9 = 3*HB
#define DALI_TIME_RECOVERY_MIN 4000 // us = 4000/416 = 9.6 = 10*HB
// Receiver setting times: 101.8.2.4 tab 20
#define DALI_RX_FF_BF_MIN 2400 // us = 2400/416 = 5.77 = 6*HB forward to backward frame time interval
#define DALI_RX_FF_BF_MAX 12400 // us = 12400/416 = 29.8 = 30*HB
#define DALI_RX_BF_FF_MIN 2400 // us = 2400/416 = 5.77 = 6*HB backward to forward frame time interval
// -------------------------------------------------------------------------------------
// Collision detection:
// if we are transmitting we also listen to the bus in GPIO ISR, delay between TX and RX should be less then 84us
// - value is hardware specific but in worst case it should be 500-416=84us or 416-333=83us - we use 100us
// - multimaster is more restrictive 416-400=16us or 433-416=17us - we use 50us
#ifndef DALI_MULTIMASTER
#define DALI_COLLISION_TXRX_DELTA 100 // us = 100/416 = 0.24 = 1/4*HB
#else
#define DALI_COLLISION_TXRX_DELTA 50 // us = 50/416 = 0.12 = 1/8*HB
#endif // DALI_MULTIMASTER
typedef enum {
DALI_BUS_UNKNOWN = 0, // after startup
DALI_BUS_POWER_DOWN, // bus power down
DALI_BUS_ERROR, // states <= this state are all errors
DALI_BUS_READY, // bus powered - ready to transmit or receive
DALI_BUS_TRANSMITTING, // bus is transmitting
DALI_BUS_RECEIVING, // bus is receiving
DALI_BUS_TIME_BREAK, // bus is in time break after collision 101.9.2.4 tab 25, now is LOW=ACTIVE
DALI_BUS_RECOVERY, // bus is in recovery after collision, wait for bus to be free, now is HIGH=IDLE
} dali_bus_state_t;
typedef enum {
TX_STATE_ERROR = 0,
TX_STATE_COLLISION, // collision detected
TX_STATE_IDLE, // we are not transmitting
TX_STATE_START, // sending start bit
TX_STATE_DATA, // sending data bits
TX_STATE_STOP, // sending stop state
} dali_tx_state_t;
typedef enum {
RX_STATE_ERROR = 0,
RX_STATE_IDLE, // we are not receiving
RX_STATE_START,
RX_STATE_DATA,
RX_STATE_STOP,
RX_STATE_END, // final state with received data
} dali_rx_state_t;
// Frame types: 101.7.4
typedef enum {
DALI_MSG_FORWARD = 0, // 16,24,32 bits
DALI_MSG_BACKWARD, // 8 bits
DALI_MSG_RESERVED, // 20 bits - should not be used
DALI_MSG_PROPRIETARY, // other, 8-to-15 bits may conflict with backward frames, priority only 5 101.8.3.2 tab 22
} dali_msg_type_t;
// Message status
typedef enum {
DALI_FRAME_UNKNOWN = 0, // status not defined
DALI_FRAME_ERROR, // error in frame
DALI_FRAME_SEQUENCE_ERROR, // such frame should be ignored
DALI_FRAME_COLLISION, // such frame should be ignored
DALI_FRAME_SIZE_VIOLATION, // such frame should be ignored
DALI_FRAME_TIME_VIOLATION, // such frame should be ignored
DALI_FRAME_GRAY_AREA, // allow multiple interpretations - such frame should be ignored
DALI_FRAME_OK, // frame is OK
} dali_msg_status_t;
// size is 8 bytes
struct Dali_msg {
uint8_t id; // message ID (0-255)
uint8_t type; // [dali_msg_type_t] type of message (forward, backward, reserved, proprietary)
// receiving data contains time of reception from last edge change in [ms]
uint8_t status; // [dali_msg_status_t] status of message frame (0-255)
uint8_t length; // [1-32] length of data in bits: 8,16,24,32 bits or nonstandard eg 20 bits reserved
uint8_t data[DALI_MAX_BYTES];
} __attribute__((packed));
typedef struct Dali_msg Dali_msg_t;
// DEFINE in main.c and then stored in dali.c in global variables
// #define DALI_TX_PIN ?
// #define DALI_RX_PIN ?
// ----------------------------------------------
// define HW DALI for gpio functions
#ifndef DALI_HW_PINS
// define HW DALI for gpio functions
// - what we should write to pin to get HIGH/LOW
#define DALI_TX_HIGH 1 // idle state
#define DALI_TX_LOW 0 // active state
// - what we should read from pin to get HIGH/LOW
#define DALI_RX_HIGH 1 // idle state
#define DALI_RX_LOW 0 // active state
#endif
// LED onboard - debug
#define DALI_LED_PIN 2 // output D4 = GPIO2 esp8266 and esp32
// onboard LED uses inverted logic - debug
#define DALI_LED_ON 0
#define DALI_LED_OFF 1
// ----------------------------------------------
extern QueueHandle_t dali_send_queue;
extern QueueHandle_t dali_send_reply_queue;
extern QueueHandle_t dali_receive_queue;
extern QueueHandle_t dali_receive_queues[DALI_PHY_COUNT];
extern uint8_t rx_debug_enabled; // 1 - enable debug for received messages, timing, etc
// preferred way: pdMS_TO_TICKS(ms) == ms * (configTICK_RATE_HZ ) / 1000
// old way: ms / portTICK_PERIOD_MS == ms / ( 1000 / configTICK_RATE_HZ )
inline void dali_delay_ms(TickType_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }
typedef struct {
uint8_t bus_id;
uint8_t tx_pin;
uint8_t rx_pin;
bool inited;
} dali_hal_bus_info_t;
esp_err_t dali_init_ports(uint8_t _dali_tx_pin, uint8_t _dali_rx_pin);
esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin);
esp_err_t dali_hal_set_baudrate(uint32_t baudrate);
uint32_t dali_hal_get_baudrate(void);
size_t dali_hal_get_inited_buses(uint8_t *ids, size_t max_ids);
esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info);
void dali_task(void *pvParameters);