FastLED 3.10.6
Loading...
Searching...
No Matches
uart_wave_encoder.cpp.hpp
Go to the documentation of this file.
1#pragma once
2
3// IWYU pragma: private
4
11
13#include "fl/stl/noexcept.h"
14
16
17namespace fl {
18
19namespace {
20
22u32 absDiff(u32 a, u32 b) FL_NO_EXCEPT {
23 return (a > b) ? (a - b) : (b - a);
24}
25
36u8 computePulseCount(u32 timing_ns, u32 pulse_width_ns) FL_NO_EXCEPT {
37 if (pulse_width_ns == 0) return 0;
38 u8 lo = static_cast<u8>(timing_ns / pulse_width_ns);
39 u8 hi = lo + 1;
40 u32 err_lo = absDiff(static_cast<u32>(lo) * pulse_width_ns, timing_ns);
41 u32 err_hi = absDiff(static_cast<u32>(hi) * pulse_width_ns, timing_ns);
42 // Prefer lo on tie (shorter HIGH time is safer)
43 return (err_hi < err_lo) ? hi : lo;
44}
45
66u8 buildUartByte(u8 pulses_a, u8 pulses_b, u8 pulses_per_bit = 5) FL_NO_EXCEPT {
67 // Build the desired 2P-bit wire pattern (with TX inversion), where
68 // P = pulses_per_bit. Wire positions:
69 // [0=START] [1=~D0] ... [2P-2=~D(2P-3)] [2P-1=STOP]
70 //
71 // LED bit A (pos 0..P-1): pulses_a HIGH bits, then LOW
72 // LED bit B (pos P..2P-1): pulses_b HIGH bits, then LOW (STOP=L)
73
74 const int P = static_cast<int>(pulses_per_bit);
75 int wire[10]; // max frame = 10 (P=5)
76
77 // LED bit A: pulses_a HIGH bits starting from position 0
78 for (int i = 0; i < P; i++) {
79 wire[i] = (i < static_cast<int>(pulses_a)) ? 1 : 0;
80 }
81
82 // LED bit B: pulses_b HIGH bits starting from position P
83 for (int i = 0; i < P; i++) {
84 wire[P + i] = (i < static_cast<int>(pulses_b)) ? 1 : 0;
85 }
86
87 // Convert wire pattern back to UART data byte
88 // Wire[0] = START (inverted) = always H -> no control needed
89 // Wire[1..2P-2] = ~D0..~D(2P-3) (inverted data, LSB first)
90 // Wire[2P-1] = STOP (inverted) = always L -> no control needed
91 //
92 // So: wire[1+i] = ~D_i -> D_i = ~wire[1+i] = wire[1+i] ? 0 : 1
93 u8 byte_val = 0;
94 const int data_bits = 2 * P - 2;
95 for (int i = 0; i < data_bits; i++) {
96 int data_bit = wire[1 + i] ? 0 : 1; // Invert: wire HIGH -> data 0
97 byte_val |= static_cast<u8>(data_bit << i); // LSB first
98 }
99
100 return byte_val;
101}
102
121
127 u8 pulses_per_bit,
128 u32 max_baud_rate) FL_NO_EXCEPT {
129 UartWaveFit fit = {};
130 const u32 period_ns = timing.total_period_ns();
131 if (period_ns == 0) return fit;
132
133 // Baud = one UART bit per pulse = P / period. Must fit the ESP32
134 // UART ceiling.
135 const u64 baud =
136 static_cast<u64>(pulses_per_bit) * 1000000000ULL / period_ns;
137 if (baud == 0 || baud > max_baud_rate) return fit;
138
139 const u32 pulse_width_ns = period_ns / pulses_per_bit;
140 if (pulse_width_ns == 0) return fit;
141
142 const u32 t0h_ns = timing.t1_ns;
143 const u32 t1h_ns = timing.t1_ns + timing.t2_ns;
144 const u8 max_pulses = static_cast<u8>(pulses_per_bit - 1);
145
146 u8 pulses_0 = computePulseCount(t0h_ns, pulse_width_ns);
147 u8 pulses_1 = computePulseCount(t1h_ns, pulse_width_ns);
148 if (pulses_0 < 1 || pulses_0 > max_pulses) return fit;
149 if (pulses_1 < 1 || pulses_1 > max_pulses) return fit;
150 if (pulses_0 == pulses_1) return fit;
151
152 // FastLED#3569/#3572 -- enforce a minimum ABSOLUTE wire separation
153 // between the 0 and 1 symbols, widening T1H upward (a longer HIGH
154 // still reads as 1). Best-fit rounding alone can land them one
155 // pulse apart, and at high baud one pulse is too little: WS2812B-V5
156 // at P=5 rounds to 245 vs 490 ns -- inside real WS281x parts'
157 // sampling-threshold band (~550-625 ns); it flapped the WROOM bench
158 // decoder. Slow chipsets (WS2811-400: 500+ ns pulses) already
159 // exceed the floor with single-pulse separation.
160 constexpr u32 kMinSymbolSeparationNs = 400;
161 const u8 pulses_1_raw = pulses_1;
162 while (pulses_1 < max_pulses &&
163 static_cast<u32>(pulses_1 - pulses_0) * pulse_width_ns <
164 kMinSymbolSeparationNs) {
165 ++pulses_1;
166 }
167
168 // Quantized timing must be within half a pulse of nominal -- the
169 // natural error bound for best-fit rounding. When the separation
170 // bump deliberately widened T1H, allow exactly the pulses it added
171 // on top of that bound.
172 const u32 tolerance_ns = pulse_width_ns / 2;
173 const u32 actual_t0h = static_cast<u32>(pulses_0) * pulse_width_ns;
174 const u32 actual_t1h = static_cast<u32>(pulses_1) * pulse_width_ns;
175 const u32 t1h_tolerance_ns =
176 tolerance_ns +
177 static_cast<u32>(pulses_1 - pulses_1_raw) * pulse_width_ns;
178 const u32 err_t0h = absDiff(actual_t0h, t0h_ns);
179 const u32 err_t1h = absDiff(actual_t1h, t1h_ns);
180 if (err_t0h > tolerance_ns) return fit;
181 if (err_t1h > t1h_tolerance_ns) return fit;
182
183 const u32 raw_t1h = static_cast<u32>(pulses_1_raw) * pulse_width_ns;
184 const u32 err_t1h_raw = absDiff(raw_t1h, t1h_ns);
185
186 fit.ok = true;
187 fit.pulses_0 = pulses_0;
188 fit.pulses_1 = pulses_1;
189 fit.total_err_ns = err_t0h + err_t1h;
190 fit.max_err_ns = err_t0h > err_t1h_raw ? err_t0h : err_t1h_raw;
191 return fit;
192}
193
194} // anonymous namespace
195
197 u32 max_baud_rate) FL_NO_EXCEPT {
198 const Wave10Lut lut = buildWave10LutForMaxBaud(timing, max_baud_rate);
199 if (lut.pulses_per_bit == 0) {
200 return ChipsetTiming{0, 0, 0, timing.reset_us, "uart_infeasible"};
201 }
202 // Re-run the fit at the geometry the LUT settled on. With P fixed the
203 // result is independent of max_baud_rate (that argument only gates
204 // feasibility), so this reproduces exactly the pulse counts encoded.
205 const UartWaveFit fit = fitUartWave(timing, lut.pulses_per_bit, max_baud_rate);
206 if (!fit.ok) {
207 return ChipsetTiming{0, 0, 0, timing.reset_us, "uart_infeasible"};
208 }
209 const u32 period_ns = timing.total_period_ns();
210 const u32 pulse_width_ns = period_ns / lut.pulses_per_bit;
211 const u32 t0h = fit.pulses_0 * pulse_width_ns;
212 const u32 t1h = fit.pulses_1 * pulse_width_ns;
213 return ChipsetTiming{t0h, t1h - t0h, period_ns - t1h, timing.reset_us,
214 "uart_wire"};
215}
216
220
222 u32 max_baud_rate) FL_NO_EXCEPT {
223 Wave10Lut result = {};
224
225 // Evaluate both frame geometries (FastLED#3572 follow-up):
226 // P=5 -- wave10: 8 data bits, baud = 5/period (the classic shape)
227 // P=4 -- wave8-frame: 6 data bits, baud = 4/period (lower baud ->
228 // reaches faster chipsets under the 5 Mbps cap; different
229 // quantization grid -- e.g. SK6812 is exact at period/4)
230 // Pick the feasible geometry with the smaller total quantization
231 // error. P=5 wins ties and near-ties (50 ns hysteresis) so the
232 // long-proven wave10 shapes stay stable for existing chipsets.
233 const UartWaveFit fit5 = fitUartWave(timing, 5, max_baud_rate);
234 const UartWaveFit fit4 = fitUartWave(timing, 4, max_baud_rate);
235
236 u8 P = 0;
237 UartWaveFit fit = {};
238 constexpr u32 kHysteresisNs = 50;
239 // A symbol further than this from nominal is one a receiver may not
240 // classify at all. WS281x parts quote about +/-150 ns, and AutoResearch
241 // decodes at +/-170; `fitUartWave` on its own admits half a pulse width,
242 // which at a 2500 ns period is 250 ns -- wider than either, which is how a
243 // geometry emitting an unclassifiable symbol got selected. See #4379.
244 constexpr u32 kMaxSymbolErrorNs = 150;
245 if (fit5.ok && fit4.ok) {
246 const bool fit5_classifiable = fit5.max_err_ns <= kMaxSymbolErrorNs;
247 const bool fit4_classifiable = fit4.max_err_ns <= kMaxSymbolErrorNs;
248 if (fit5_classifiable != fit4_classifiable) {
249 // Exactly one geometry keeps every symbol inside a receiver's
250 // tolerance. Take it, whatever the totals say -- a smaller sum
251 // bought by putting one symbol out of range is not a better fit.
252 // WS2811 at 400 kHz is the case: P=5 nails T0H and misses T1H by
253 // 200 ns, P=4 spends 125 ns on T0H to bring T1H within 50 ns.
254 P = fit5_classifiable ? 5 : 4;
255 fit = fit5_classifiable ? fit5 : fit4;
256 } else if (fit4.total_err_ns + kHysteresisNs < fit5.total_err_ns) {
257 P = 4;
258 fit = fit4;
259 } else {
260 P = 5;
261 fit = fit5;
262 }
263 } else if (fit5.ok) {
264 P = 5;
265 fit = fit5;
266 } else if (fit4.ok) {
267 P = 4;
268 fit = fit4;
269 } else {
270 return result; // infeasible -- pulses_per_bit stays 0
271 }
272
273 result.pulses_per_bit = P;
274 result.lut[0] = buildUartByte(fit.pulses_0, fit.pulses_0, P); // "00"
275 result.lut[1] = buildUartByte(fit.pulses_0, fit.pulses_1, P); // "01"
276 result.lut[2] = buildUartByte(fit.pulses_1, fit.pulses_0, P); // "10"
277 result.lut[3] = buildUartByte(fit.pulses_1, fit.pulses_1, P); // "11"
278
279 return result;
280}
281
285
287 u32 max_baud_rate) FL_NO_EXCEPT {
288 // Feasible if EITHER frame geometry fits -- same helper as
289 // buildWave10Lut(), so admission and construction can't drift.
290 return fitUartWave(timing, 5, max_baud_rate).ok ||
291 fitUartWave(timing, 4, max_baud_rate).ok;
292}
293
295size_t encodeLedsToUart(const u8* input,
296 size_t input_size,
297 u8* output,
298 size_t output_capacity,
299 const Wave10Lut& lut) FL_NO_EXCEPT {
300 size_t required_size = input_size * 4;
301
302 if (required_size > output_capacity) {
303 return 0;
304 }
305
306 for (size_t i = 0; i < input_size; ++i) {
307 detail::encodeUartByte(input[i], &output[i * 4], lut);
308 }
309
310 return required_size;
311}
312
314size_t encodeLedsToUart(const u8* input,
315 size_t input_size,
316 u8* output,
317 size_t output_capacity) FL_NO_EXCEPT {
318 size_t required_size = input_size * 4;
319
320 if (required_size > output_capacity) {
321 return 0;
322 }
323
324 for (size_t i = 0; i < input_size; ++i) {
325 detail::encodeUartByte(input[i], &output[i * 4]);
326 }
327
328 return required_size;
329}
330
331} // namespace fl
332
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN
#define FL_OPTIMIZATION_LEVEL_O3_END
#define FL_OPTIMIZE_FUNCTION
#define FL_IRAM
u8 computePulseCount(u32 timing_ns, u32 pulse_width_ns) FL_NO_EXCEPT
Compute HIGH pulse count for a given timing value.
u8 buildUartByte(u8 pulses_a, u8 pulses_b, u8 pulses_per_bit=5) FL_NO_EXCEPT
Build a UART data byte from two LED bit pulse counts.
u32 absDiff(u32 a, u32 b) FL_NO_EXCEPT
Absolute value of difference (unsigned-safe)
UartWaveFit fitUartWave(const ChipsetTimingConfig &timing, u8 pulses_per_bit, u32 max_baud_rate) FL_NO_EXCEPT
Evaluate whether P pulses/LED-bit can represent the timing.
u32 max_err_ns
Worst single-symbol error, measured pre-bump.
Fit result for one UART wave geometry (P pulses per LED bit)
FASTLED_FORCE_INLINE FL_IRAM FL_OPTIMIZE_FUNCTION void encodeUartByte(u8 led_byte, u8 *output, const Wave10Lut &lut) FL_NO_EXCEPT
Encode 1 LED byte to 4 UART bytes using a Wave10 LUT.
bool canRepresentTiming(const ChipsetTimingConfig &timing) FL_NO_EXCEPT
Check if a chipset timing can be accurately represented by UART.
unsigned char u8
Definition stdint.h:131
FL_IRAM FL_OPTIMIZE_FUNCTION size_t encodeLedsToUart(const u8 *input, size_t input_size, u8 *output, size_t output_capacity, const Wave10Lut &lut) FL_NO_EXCEPT
Encode LED pixel data to UART bytes using a Wave10 LUT.
constexpr u32 kMaxUartBaudRate
Maximum UART baud rate supported by ESP32 variants.
FASTLED_FORCE_INLINE fl::u8 P(fl::u8 x)
Wave10Lut buildWave10LutForMaxBaud(const ChipsetTimingConfig &timing, u32 max_baud_rate) FL_NO_EXCEPT
Build a UART waveform LUT with a backend-specific baud ceiling.
Wave10Lut buildWave10Lut(const ChipsetTimingConfig &timing) FL_NO_EXCEPT
Build a Wave10 LUT from chipset timing parameters.
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
InputGamut g FL_NO_EXCEPT
Definition rgbw.h:121
ChipsetTiming uartWireTiming(const ChipsetTimingConfig &timing, u32 max_baud_rate) FL_NO_EXCEPT
The wire timing the UART encoder actually produces for a chipset.
bool canRepresentTimingForMaxBaud(const ChipsetTimingConfig &timing, u32 max_baud_rate) FL_NO_EXCEPT
Timing feasibility with a backend-specific baud ceiling.
Base definition for an LED controller.
Definition crgb.hpp:179
Generic chipset timing entry Provides T1, T2, T3 timing parameters in nanoseconds for any LED protoco...
Definition led_timing.h:86
fl::u64 u64
Definition stdint.h:220
Runtime bit-period timing for a clockless chipset.
u8 pulses_per_bit
Frame geometry: 5 (wave10) or 4 (wave8-frame); 0 = infeasible.
Wave lookup table for UART LED encoding (waveN family)
Platform-neutral UART wave4/wave5 encoder API.