Skip to content

Commit 5e19163

Browse files
committed
Refactor DALI using library with read buffer
1 parent 4145c5f commit 5e19163

File tree

7 files changed

+556
-371
lines changed

7 files changed

+556
-371
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#######################################
2+
# Syntax Coloring Map for TasmotaDali
3+
# (esp8266 and esp32)
4+
#######################################
5+
6+
#######################################
7+
# Datatypes (KEYWORD1)
8+
#######################################
9+
10+
TasmotaDali KEYWORD1
11+
12+
#######################################
13+
# Methods and Functions (KEYWORD2)
14+
#######################################
15+
16+
begin KEYWORD2
17+
end KEYWORD2
18+
write KEYWORD2
19+
read KEYWORD2
20+
available KEYWORD2
21+
flush KEYWORD2
22+
23+
#######################################
24+
# Constants (LITERAL1)
25+
#######################################
26+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "TasmotaDali",
3+
"version": "1.0.0",
4+
"keywords": [
5+
"serial", "io", "TasmotaDali"
6+
],
7+
"description": "Implementation of DALI bitbang for ESP8266 and ESP32.",
8+
"repository":
9+
{
10+
"type": "git",
11+
"url": "https://github.com/arendst/Tasmota/lib/TasmotaDali"
12+
},
13+
"frameworks": "arduino",
14+
"platforms": [
15+
"espressif8266", "espressif32"
16+
]
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name=TasmotaDali
2+
version=1.0.0
3+
author=Theo Arends
4+
maintainer=Theo Arends <[email protected]>
5+
sentence=Implementation of DALI bitbang for ESP8266 and ESP32.
6+
paragraph=
7+
category=Signal Input/Output
8+
url=
9+
architectures=esp8266,esp32
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
/*
2-
xdrv_75_dali.h - DALI support for Tasmota
2+
Dali.h - DALI support for Tasmota
33
44
SPDX-FileCopyrightText: 2025 Theo Arends
55
66
SPDX-License-Identifier: GPL-3.0-only
77
*/
88

9-
#ifdef USE_DALI
10-
11-
#ifndef _XDRV75_DALI_H_
12-
#define _XDRV75_DALI_H_
9+
#ifndef _DALI_H_
10+
#define _DALI_H_
1311

1412
/*-------------------------------------------------------------------------------------------*\
1513
* DALI Address types - Send as first byte
@@ -973,5 +971,4 @@
973971
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
974972
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
975973

976-
#endif // _XDRV75_DALI_H_
977-
#endif // USE_DALI
974+
#endif // _DALI_H_
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/*
2+
TasmotaDali.cpp - DALI support for Tasmota
3+
4+
SPDX-FileCopyrightText: 2025 Theo Arends
5+
6+
SPDX-License-Identifier: GPL-3.0-only
7+
*/
8+
9+
#include <TasmotaDali.h>
10+
11+
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
12+
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
13+
14+
/*-------------------------------------------------------------------------------------------*/
15+
16+
bool TasmotaDali::IsValidGPIOpin(int pin) {
17+
return (pin >= -1 && pin <= 5) || (pin >= 12 && pin <= 15);
18+
}
19+
20+
/*-------------------------------------------------------------------------------------------*/
21+
22+
TasmotaDali::TasmotaDali(int receive_pin, int transmit_pin, bool receive_invert, bool transmit_invert, int buffer_size) {
23+
m_valid = false;
24+
if ((receive_pin < 0) || (transmit_pin < 0)) { return; }
25+
#ifdef ESP8266
26+
if (!((IsValidGPIOpin(receive_pin)) && (IsValidGPIOpin(transmit_pin) || transmit_pin == 16))) {
27+
return;
28+
}
29+
#endif // ESP8266
30+
#ifdef ESP32
31+
if (!GPIO_IS_VALID_GPIO(receive_pin)) { return; }
32+
if (!GPIO_IS_VALID_OUTPUT_GPIO(transmit_pin)) { return; }
33+
#endif // ESP32
34+
m_buffer_size = buffer_size;
35+
m_buffer = (DaliFrame*)malloc(m_buffer_size * sizeof(DaliFrame));
36+
if (m_buffer == NULL) { return; }
37+
38+
m_rx_pin = receive_pin;
39+
m_tx_pin = transmit_pin;
40+
m_rx_invert = receive_invert;
41+
m_tx_invert = transmit_invert;
42+
43+
// Use getCycleCount() loop to get as exact timing as possible
44+
// Manchester twice 1200 bps = 2400 bps = 417 (protocol 416.76 +/- 10%) us = 1Te
45+
m_bit_time = ESP.getCpuFreqMHz() * 1000000 / 2400;
46+
m_last_activity = 0;
47+
48+
pinMode(m_tx_pin, OUTPUT);
49+
digitalWrite(m_tx_pin, (m_tx_invert) ? LOW : HIGH); // Idle
50+
pinMode(m_rx_pin, INPUT);
51+
EnableRxInterrupt();
52+
53+
m_in_pos = 0;
54+
m_out_pos = 0;
55+
56+
m_valid = true;
57+
}
58+
59+
void TasmotaDali::end(void) {
60+
DisableRxInterrupt();
61+
if (m_buffer) {
62+
free(m_buffer);
63+
}
64+
}
65+
66+
TasmotaDali::~TasmotaDali(void) {
67+
if (m_valid) {
68+
end();
69+
}
70+
}
71+
72+
bool TasmotaDali::begin(void) {
73+
return m_valid;
74+
}
75+
76+
void TasmotaDali::flush(void) {
77+
m_in_pos = 0;
78+
m_out_pos = 0;
79+
}
80+
81+
int TasmotaDali::available(void) {
82+
int avail = m_in_pos - m_out_pos;
83+
if (avail < 0) {
84+
avail += m_buffer_size;
85+
}
86+
return avail;
87+
}
88+
89+
void TasmotaDali::write(DaliFrame frame) {
90+
DisableRxInterrupt();
91+
SendData(frame); // Takes 14.7 ms
92+
if (frame.meta & TM_DALI_SEND_TWICE) {
93+
SendData(frame); // Takes 14.7 ms
94+
}
95+
delay(2); // Block response
96+
EnableRxInterrupt();
97+
}
98+
99+
DaliFrame TasmotaDali::read(void) {
100+
DaliFrame frame;
101+
frame.meta = 0;
102+
if (m_in_pos == m_out_pos) {
103+
return frame;
104+
}
105+
frame = m_buffer[m_out_pos];
106+
m_out_pos = (m_out_pos +1) % m_buffer_size;
107+
return frame;
108+
}
109+
110+
/*-------------------------------------------------------------------------------------------*\
111+
* DALI send
112+
\*-------------------------------------------------------------------------------------------*/
113+
114+
void TasmotaDali::SendData(DaliFrame frame) {
115+
/*
116+
DALI-2 protocol forward frame
117+
DALI data 0xFE6432 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 0 0 1 1 0 0 1 0
118+
Start and Stop bits 1 1 1
119+
Manchester data 01010101010101011010010110100110101010010110100110
120+
Stop bits 1111
121+
122+
DALI protocol forward frame
123+
DALI data 0xFE64 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0
124+
Start and Stop bits 1 1 1
125+
Manchester data 0101010101010101101001011010011010
126+
Stop bits 1111
127+
128+
Bit number 012345678901234567890123456789012345678901234567890123
129+
1 2 3 4 5
130+
*/
131+
bool bit_value;
132+
bool pin_value;
133+
bool dali_read;
134+
bool collision;
135+
uint32_t retry = 2;
136+
do {
137+
collision = false;
138+
uint32_t send_data = frame.data;
139+
uint32_t bit_pos = (frame.meta & TM_DALI_BIT_COUNT_MASK) -1;
140+
uint32_t max_bit_number = (bit_pos * 2) + 4;
141+
uint32_t bit_number = 0;
142+
143+
m_last_activity += 14; // As suggested by DALI protocol (>22Te = 9.17 ms) - We need to add 1.1 ms due to not waiting for stop bits
144+
while (((int) (millis() - m_last_activity)) < 0) {
145+
delay(1); // Wait for bus to be free if needed
146+
}
147+
148+
#ifdef ESP32
149+
{portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
150+
portENTER_CRITICAL(&mux);
151+
#endif
152+
153+
uint32_t wait = ESP.getCycleCount();
154+
while (bit_number <= max_bit_number) { // 417 * 35 = 35Te = 14.7 ms
155+
if (!collision) {
156+
if (0 == (bit_number &1)) { // Even bit
157+
// Start bit, Stop bit, Data bits
158+
bit_value = (0 == bit_number) ? 1 : (max_bit_number == bit_number) ? 0 : (bool)((send_data >> bit_pos--) &1); // MSB first
159+
} else { // Odd bit
160+
bit_value = !bit_value; // Complement bit
161+
}
162+
pin_value = bit_value ? LOW : HIGH; // Invert bit
163+
} else {
164+
if (max_bit_number == bit_number) {
165+
pin_value = HIGH; // Set to idle
166+
}
167+
}
168+
169+
digitalWrite(m_tx_pin, (m_tx_invert) ? !pin_value : pin_value);
170+
wait += m_bit_time; // Auto roll-over
171+
while (ESP.getCycleCount() < wait);
172+
173+
if (!collision) {
174+
dali_read = (digitalRead(m_rx_pin) != m_rx_invert);
175+
if ((HIGH == pin_value) && (LOW == dali_read)) { // Collision if write is 1 and bus is 0
176+
collision = true;
177+
pin_value = LOW;
178+
bit_number = max_bit_number -5; // Keep bus low for 4 bits - break sequence
179+
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DLI: Tx collision"));
180+
}
181+
}
182+
183+
bit_number++;
184+
}
185+
186+
#ifdef ESP32
187+
portEXIT_CRITICAL(&mux);}
188+
#endif
189+
190+
// delayMicroseconds(1100); // Wait 3Te as sending stop bits - adds to total 15.8 ms
191+
m_last_activity = millis(); // Start Forward Frame delay time (>22Te)
192+
} while (retry-- && collision);
193+
}
194+
195+
/*-------------------------------------------------------------------------------------------*\
196+
* DALI receive
197+
\*-------------------------------------------------------------------------------------------*/
198+
199+
void TasmotaDali::ReceiveData(void) {
200+
/*
201+
Unsupported Forward frame (1 Start bit + 32 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 70 bits
202+
DALI data 0xFE643278 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 0 0 1 1 0 0 1 0 0 1 1 1 1 0 0 0 Forward frame - 30.2 ms
203+
Start and Stop bits 1 1 1
204+
Manchester data 010101010101010110100101101001101010100101101001101001010101101010
205+
Stop bits 1111
206+
207+
DALI-2 Forward frame (1 Start bit + 24 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 54 bits
208+
DALI data 0xFE6432 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 0 0 1 1 0 0 1 0 Forward frame - 23.2 ms
209+
Start and Stop bits 1 1 1
210+
Manchester data 01010101010101011010010110100110101010010110100110
211+
Stop bits 1111
212+
213+
Forward frame (1 Start bit + 16 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 38 bits
214+
DALI data 0xFE64 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 Forward frame - 16.2 ms
215+
Start and Stop bits 1 1 1
216+
Manchester data 0101010101010101101001011010011010
217+
Stop bits 1111
218+
219+
Backward frame (1 Start bit + 8 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 22 bits
220+
DALI data 0x64 0 1 1 0 0 1 0 0 Backward frame - 10 ms
221+
Start and Stop bits 1 1 1
222+
Manchester data 011001011010011010
223+
Stop bits 1111
224+
225+
Bit number 01234567890123456789012345678901234567890123456789012345678901234567890
226+
1 2 3 4 5 6 7
227+
*/
228+
uint32_t wait = ESP.getCycleCount() + (m_bit_time / 2);
229+
int bit_state = 0;
230+
bool dali_read;
231+
uint32_t bit_count = 0;
232+
uint32_t received_dali_data = 0;
233+
uint32_t bit_number = 0;
234+
while (bit_number < 72) {
235+
while (ESP.getCycleCount() < wait);
236+
wait += m_bit_time; // Auto roll-over +1Te
237+
dali_read = (digitalRead(m_rx_pin) != m_rx_invert);
238+
if (bit_number < 68) { // 66 manchester encoded bits
239+
bit_state += (dali_read) ? 1 : -1;
240+
if (0 == bit_state) { // Manchester encoding total 2 bits is always 0
241+
if (bit_number > 2) { // Skip start bit
242+
received_dali_data <<= 1;
243+
received_dali_data |= dali_read;
244+
}
245+
}
246+
else if (2 == bit_state) { // Invalid manchester data (might be stop bit)
247+
// bn 19 -> 8, 35 -> 16, 51 -> 24, 67 -> 32
248+
bit_count = (bit_number - 3) / 2; // 0..32 bit
249+
bit_state = 0;
250+
bit_number = 69; // Continue receiving stop bits
251+
}
252+
else if (abs(bit_state) > 1) { // Invalid manchester data (too many 0 or 1)
253+
break;
254+
}
255+
} else { // 4 high Stop bits
256+
if (bit_state != 0) { // Invalid manchester data
257+
break;
258+
}
259+
else if (dali_read != 1) { // Invalid level of stop bit
260+
bit_state = 1;
261+
break;
262+
}
263+
}
264+
bit_number++;
265+
}
266+
m_last_activity = millis(); // Start Forward Frame delay time (>22Te)
267+
268+
if ((0 == bit_state) && // Valid Manchester encoding including start and stop bits
269+
(bit_count >= 8) && // Minimum 8-bits (backward frame)
270+
(bit_count <= 32)) { // Maximum 32-bits (forward and event frame)
271+
DaliFrame frame;
272+
frame.meta = bit_count; // 8..32 bit
273+
frame.data = received_dali_data;
274+
uint32_t prev = (0 == m_in_pos) ? m_buffer_size -1 : m_in_pos -1;
275+
if ((m_buffer[prev].data != frame.data) ||
276+
(m_buffer[prev].meta != frame.meta)) { // Skip duplicates
277+
uint32_t next = (m_in_pos + 1) % m_buffer_size;
278+
m_buffer[m_in_pos] = frame;
279+
m_in_pos = next;
280+
}
281+
}
282+
}
283+
284+
void IRAM_ATTR ReceiveDataIrq(void *self) {
285+
((TasmotaDali*)self)->ReceiveData();
286+
};
287+
288+
void TasmotaDali::EnableRxInterrupt(void) {
289+
attachInterruptArg(m_rx_pin, ReceiveDataIrq, this, (m_rx_invert) ? RISING : FALLING);
290+
}
291+
292+
void TasmotaDali::DisableRxInterrupt(void) {
293+
detachInterrupt(m_rx_pin);
294+
}

0 commit comments

Comments
 (0)