FastLED 3.10.8
Loading...
Searching...
No Matches
ws2812_stream_decoder.cpp.hpp
Go to the documentation of this file.
1
5
6// IWYU pragma: private
7
10
11namespace fl {
12namespace channels {
13namespace rx {
14
18 fl::span<u8> bytes_out) FL_NO_EXCEPT {
19 if (edges.size() == 0 || bytes_out.size() == 0) {
21 }
22
23 // Pre-pass: the adaptive midpoint from the observed HIGH widths.
24 u32 high_min = 0xFFFFFFFFu;
25 u32 high_max = 0u;
26 u32 high_samples = 0;
27 for (size_t k = 0; k + 1 < edges.size(); k += 2) {
28 if (!edges[k].high) continue;
29 const u32 h = edges[k].ns;
30 if (!flexPwmIsMidpointSample(h)) continue;
31 if (h < high_min) high_min = h;
32 if (h > high_max) high_max = h;
33 ++high_samples;
34 }
35 const u32 midpoint =
36 flexPwmAdaptiveMidpointNs(timing, high_min, high_max, high_samples);
37
38 u32 byte_index = 0;
39 u8 current_byte = 0;
40 u32 bit_count = 0;
41 u32 error_count = 0;
42 u32 total_bits = 0;
43
44 size_t i = 0;
45 while (i < edges.size()) {
46 if (!edges[i].high) {
47 ++i; // polarity error: resync on the next HIGH
48 continue;
49 }
50 const u32 high_ns = edges[i].ns;
51 int bit = -1;
52 if (i + 1 == edges.size()) {
53 // The frame's last HIGH: its LOW is the reset, so classify by
54 // HIGH width alone, like a mid-frame bit. (Stopping at the last
55 // edge dropped the frame's final bit: LSB 1 decoded as 0.)
56 bit = flexPwmBitFromMidpoint(high_ns, midpoint);
57 i += 1;
58 } else if (!edges[i + 1].high) {
59 bit = flexPwmBitFromMidpoint(high_ns, midpoint);
60 i += 2;
61 } else {
62 bit = flexPwmBitFromHigh(high_ns, timing);
63 i += 1;
64 }
65 ++total_bits;
66 if (bit < 0) {
67 ++error_count;
68 continue;
69 }
70 current_byte = static_cast<u8>((current_byte << 1) | static_cast<u8>(bit));
71 if (++bit_count == 8) {
72 if (byte_index >= bytes_out.size()) {
75 }
76 bytes_out[byte_index++] = current_byte;
77 current_byte = 0;
78 bit_count = 0;
79 }
80 }
81
82 // Partial final byte, left-aligned.
83 if (bit_count > 0 && byte_index < bytes_out.size()) {
84 bytes_out[byte_index++] = static_cast<u8>(current_byte << (8 - bit_count));
85 }
86 if (total_bits > 0 && (error_count * 10) > total_bits) {
88 }
90}
91
92// ---------------------------------------------------------------------------
93// Ws2812StreamDecoder
94// ---------------------------------------------------------------------------
95
97 u32 ns_per_tick_q16, fl::span<u8> out,
99 mTiming = stream_timing;
100 mNsPerTickQ16 = ns_per_tick_q16;
101 mOut = out;
102 mDiag = diag_edges;
103 mDiagCount = 0;
104 mHavePrev = false;
105 mStarted = false;
106 mPrevRise = 0;
107 mPrevFall = 0;
108 mEdgeIndex = 0;
109 mHighMin = 0xFFFFFFFFu;
110 mHighMax = 0;
111 mHighSamples = 0;
112 mCalibrated = false;
113 mMidpoint = 0;
114 mPendingCount = 0;
115 mExceptionCount = 0;
116 mCurByte = 0;
117 mBitCount = 0;
118 mBitPos = 0;
119 mFlushed = false;
120 mPartialWritten = false;
121 mStats = Stats();
122}
123
124void Ws2812StreamDecoder::record(u32 high_ns, Mode mode, bool stream_valid) FL_NO_EXCEPT {
127 e.bit_pos = mBitPos;
128 e.high_ns = high_ns;
129 e.mode = mode;
130 e.stream_valid = stream_valid;
131 ++mStats.exceptions;
132 } else {
133 ++mStats.exceptionsDropped;
134 }
135}
136
138 mCurByte = static_cast<u8>((mCurByte << 1) | static_cast<u8>(b));
139 ++mBitPos;
140 if (++mBitCount == 8) {
141 if (mStats.fullBytes < mOut.size()) {
142 mOut[mStats.fullBytes] = mCurByte;
143 }
144 ++mStats.fullBytes;
145 mCurByte = 0;
146 mBitCount = 0;
147 }
148}
149
151 ++mStats.bits;
152 if (mode == Mode::FromHigh) {
153 const int b = flexPwmBitFromHigh(high_ns, mTiming);
154 record(high_ns, mode, b >= 0);
155 if (b < 0) {
156 ++mStats.errors;
157 return;
158 }
159 appendBit(b);
160 return;
161 }
162 const u32 dist = (high_ns > mMidpoint) ? high_ns - mMidpoint : mMidpoint - high_ns;
163 if (dist <= kExceptionBandNs) {
164 record(high_ns, mode, true);
165 }
167}
168
171 mStats.calibratedMidpointNs = mMidpoint;
172 mCalibrated = true;
173 for (u32 k = 0; k < mPendingCount; ++k) {
174 classify(mPending[k].high_ns, mPending[k].mode);
175 }
176 mPendingCount = 0;
177}
178
180 if (mCalibrated) {
181 classify(high_ns, mode);
182 return;
183 }
184 mPending[mPendingCount].high_ns = high_ns;
185 mPending[mPendingCount].mode = mode;
187 calibrate();
188 }
189}
190
192 if (mDiagCount < mDiag.size()) {
193 mDiag[mDiagCount++] = EdgeTime(high, ns);
194 }
195 ++mEdgeIndex;
196}
197
198FASTLED_FORCE_INLINE void Ws2812StreamDecoder::emitPair(u16 rise, u16 fall, bool has_low,
199 u32 low_ns) FL_NO_EXCEPT {
200 const u32 high_ns = flexPwmTickDeltaNs(rise, fall, mNsPerTickQ16);
201 // The batch pre-pass samples HIGHs at even edge indices that are not
202 // the last edge (the final pair, emitted by flush(), never is).
203 if ((mEdgeIndex & 1u) == 0 && flexPwmIsMidpointSample(high_ns)) {
204 if (high_ns < mHighMin) mHighMin = high_ns;
205 if (high_ns > mHighMax) mHighMax = high_ns;
206 ++mHighSamples;
207 }
208 emitEdge(true, high_ns);
209 if (has_low) {
210 emitEdge(false, low_ns);
211 pushBit(high_ns, Mode::Midpoint);
212 } else {
213 pushBit(high_ns, Mode::FromHigh);
214 }
215}
216
217// Hot path: runs in the capture DMA ISR, once per ring half.
219 const size_t pair_count = captures.size() / 2u;
220 for (size_t p = 0; p < pair_count; ++p) {
221 const u16 rise = captures[2 * p];
222 const u16 fall = captures[2 * p + 1];
223 ++mStats.pairs;
224 if (mHavePrev) {
225 const u32 low_ns = flexPwmTickDeltaNs(mPrevFall, rise, mNsPerTickQ16);
226 const bool has_low = low_ns <= kFlexPwmGapNs;
227 // Leading phantom pairs (#3219, #3409): the arm sequence can
228 // latch a stray pair followed by an idle gap. Drop any leading
229 // pair whose LOW is a gap.
230 if (mStarted || has_low) {
231 mStarted = true;
232 emitPair(mPrevRise, mPrevFall, has_low, low_ns);
233 }
234 }
235 mPrevRise = rise;
236 mPrevFall = fall;
237 mHavePrev = true;
238 }
239}
240
242 if (mFlushed) {
243 return;
244 }
245 mFlushed = true;
246 if (mHavePrev) {
247 // The final pair's HIGH is the last edge: not a pre-pass sample, and
248 // classified by the midpoint like the batch decoder's last edge.
250 emitEdge(true, high_ns);
251 pushBit(high_ns, Mode::Midpoint);
252 mHavePrev = false;
253 }
254 if (!mCalibrated) {
255 calibrate();
256 }
257}
258
260 flush();
261 mPartialWritten = false;
262 mStats.gapBitShift = false;
263 if (mBitCount > 0 && mStats.fullBytes < mOut.size()) {
264 mOut[mStats.fullBytes] = static_cast<u8>(mCurByte << (8 - mBitCount));
265 mPartialWritten = true;
266 }
267
268 const u32 frame_mid =
270 mStats.frameMidpointNs = frame_mid;
271 const u32 drift = (frame_mid > mMidpoint) ? frame_mid - mMidpoint : mMidpoint - frame_mid;
272 bool inexact = drift > kExceptionBandNs || mStats.exceptionsDropped > 0;
273 for (u32 k = 0; k < mExceptionCount; ++k) {
274 const Exception& e = mExceptions[k];
275 int b;
276 if (e.mode == Mode::FromHigh) {
277 b = flexPwmBitFromHigh(e.high_ns, timing);
278 if ((b >= 0) != e.stream_valid) {
279 inexact = true; // the bit would be dropped / kept instead
280 mStats.gapBitShift = true;
281 continue;
282 }
283 if (b < 0) {
284 continue;
285 }
286 } else {
287 b = flexPwmBitFromMidpoint(e.high_ns, frame_mid);
288 }
289 const u32 byte = e.bit_pos / 8u;
290 if (byte >= mOut.size()) {
291 continue;
292 }
293 const u8 mask = static_cast<u8>(0x80u >> (e.bit_pos % 8u));
294 mOut[byte] = b ? static_cast<u8>(mOut[byte] | mask)
295 : static_cast<u8>(mOut[byte] & ~mask);
296 }
297 mStats.inexact = inexact;
298}
299
301 if (mStats.overrun) {
303 }
304 if (mStats.gapBitShift) {
305 // The capture-time timing kept or dropped a gap bit that the decode
306 // timing would not: every later bit is shifted. Pass the decode
307 // timing as RxConfig::stream_timing.
309 }
310 if (mStats.pairs == 0 || out.size() == 0) {
312 }
313 const u32 full = mStats.fullBytes;
314 if (full > out.size() || full > mOut.size()) {
316 }
317 u32 n = full;
318 if (mPartialWritten && n < out.size()) {
319 ++n;
320 }
321 for (u32 k = 0; k < n; ++k) {
322 out[k] = mOut[k];
323 }
324 if (mStats.bits > 0 && (mStats.errors * 10) > mStats.bits) {
326 }
328}
329
330} // namespace rx
331} // namespace channels
332} // namespace fl
void finish(const ChipsetTiming4Phase &timing) FL_NO_EXCEPT
Re-classify the recorded bits with the decode-time timing and write the partial final byte.
void classify(u32 high_ns, Mode mode) FL_NO_EXCEPT
void flush() FL_NO_EXCEPT
End of capture: decode the final pair (HIGH only; its LOW is the reset) and fix the calibration.
void pushBit(u32 high_ns, Mode mode) FL_NO_EXCEPT
void emitEdge(bool high, u32 ns) FL_NO_EXCEPT
void push(fl::span< const u16 > captures) FL_NO_EXCEPT
Consume capture pairs laid out as [rise0, fall0, rise1, fall1...].
void emitPair(u16 rise, u16 fall, bool has_low, u32 low_ns) FL_NO_EXCEPT
void record(u32 high_ns, Mode mode, bool stream_valid) FL_NO_EXCEPT
static constexpr u32 kExceptionBandNs
Bits within this distance of the calibrated threshold are recorded for re-classification against the ...
static constexpr u32 kCalibrationBits
Bits classified before the threshold is fixed. 64 bits = 8 bytes.
void reset(const ChipsetTiming4Phase &stream_timing, u32 ns_per_tick_q16, fl::span< u8 > out, fl::span< EdgeTime > diag_edges) FL_NO_EXCEPT
Start a frame.
fl::result< u32, DecodeError > copyTo(fl::span< u8 > out) const FL_NO_EXCEPT
Copy the decoded frame into out with the batch decoder's result rules (overflow, >10% error rate,...
static expected failure(E err, const char *msg=nullptr) FL_NO_EXCEPT
Create error result.
Definition expected.h:115
static expected success(T value) FL_NO_EXCEPT
Create successful result.
Definition expected.h:108
#define FASTLED_FORCE_INLINE
#define FL_OPTIMIZE_FUNCTION
uint8_t byte
Definition midi_Defs.h:36
fl::result< u32, DecodeError > decodeFlexPwmEdges(const ChipsetTiming4Phase &timing, fl::span< const EdgeTime > edges, fl::span< u8 > bytes_out) FL_NO_EXCEPT
Batch decoder over an edge list (HIGH, LOW, HIGH, ... with a LOW left out after a gap).
constexpr u32 kFlexPwmGapNs
A LOW longer than this ends the bit without a LOW edge (gap or reset).
bool flexPwmIsMidpointSample(u32 high_ns) FL_NO_EXCEPT
Whether a HIGH width contributes to the adaptive-midpoint pre-pass (idle and glitch widths are exclud...
int flexPwmBitFromHigh(u32 high_ns, const ChipsetTiming4Phase &timing) FL_NO_EXCEPT
Bit from a HIGH whose LOW was a gap/reset or was not captured: range check against the T0H / T1H wind...
u32 flexPwmTickDeltaNs(u16 t0, u16 t1, u32 ns_per_tick_q16) FL_NO_EXCEPT
Convert a 16-bit capture-counter delta to ns.
int flexPwmBitFromMidpoint(u32 high_ns, u32 midpoint_ns) FL_NO_EXCEPT
Bit from a HIGH followed by a normal LOW: HIGH-only midpoint split.
u32 flexPwmAdaptiveMidpointNs(const ChipsetTiming4Phase &timing, u32 high_min, u32 high_max, u32 samples) FL_NO_EXCEPT
#3416 adaptive midpoint: (min + max) / 2 of the observed HIGH widths once there are enough samples sp...
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
bool flush(u32 timeoutMs) FL_NO_EXCEPT
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
@ CAPTURE_OVERRUN
Streaming capture lost data (ring overwritten before decode)
Definition rx.h:73
@ HIGH_ERROR_RATE
Symbol decode error rate too high (>10%)
Definition rx.h:70
@ BUFFER_OVERFLOW
Output buffer overflow.
Definition rx.h:71
@ INVALID_ARGUMENT
Invalid input arguments.
Definition rx.h:72
Base definition for an LED controller.
Definition crgb.hpp:179
4-phase RX timing thresholds for chipset detection
Definition rx.h:88
Universal edge timing representation (platform-agnostic)
Definition rx.h:34
WS2812 decoder for paired rise/fall capture timestamps, in batch and streaming form.