Arduino PulseWire Transceiver Library
Loading...
Searching...
No Matches
MillerCodec.h
1#pragma once
2#include "Codec.h"
3namespace pulsewire {
4
17class MillerCodec : public Codec {
18 public:
19 MillerCodec() = default;
21
22 CodecEnum getCodecType() const override { return CodecEnum::Miller; }
23
24 size_t getEdgeCount() const override { return 16; }
25
26 int getEndOfFrameDelayUs() override { return 4 * _bitPeriodUs; }
27
28 bool getIdleLevel() const override { return false; }
29
30 void reset() override {
32 _encLevel = true;
33 _rxByte = 0;
34 _rxBitPos = 0;
35 _rxExpectingMidTransition = false;
36 }
37
50 size_t encode(uint8_t byte, Vector<OutputEdge>& output) override {
51 size_t count = 0;
52 uint32_t halfT = _bitPeriodUs / 2;
54 0; // Accumulated time at current level before next transition
55 bool prevBit =
56 false; // Track previous bit for Miller context (local to byte)
57
58 Logger::debug("Miller encode: byte=0x%02X, startLevel=%d", byte, _encLevel);
59
60 // Process each bit from MSB to LSB
61 for (int i = 7; i >= 0; --i) {
62 bool bit = (byte >> i) & 0x01;
63
64 if (bit) {
65 // '1' bit: Transition at middle of bit period
66 // Add first half to pending, output edge, then start second half
67 pending += halfT;
69 edge.level = _encLevel;
70 edge.pulseUs = pending;
71 output.push_back(edge);
72 count++;
73 _encLevel = !_encLevel; // Toggle level after transition
74 pending = halfT; // Second half of '1' at new level
75 } else {
76 // '0' bit: Behavior depends on previous bit
77 if (prevBit) {
78 // '0' after '1': NO transition, just accumulate full bit period
79 pending += _bitPeriodUs;
80 } else {
81 // '0' after '0' (or first bit): Transition at START of bit
82 // Output any accumulated pending time, then start new accumulation
83 if (pending > 0) {
85 edge.level = _encLevel;
86 edge.pulseUs = pending;
87 output.push_back(edge);
88 count++;
89 _encLevel = !_encLevel;
90 pending = 0;
91 }
92 pending = _bitPeriodUs; // This '0' bit's full period
93 }
94 }
95 prevBit = bit;
96 }
97
98 // Flush any remaining pending time at end of byte
99 if (pending > 0) {
101 edge.level = _encLevel;
102 edge.pulseUs = pending;
103 output.push_back(edge);
104 count++;
105 _encLevel = !_encLevel;
106 }
107
108 Logger::debug("Miller encode: produced %zu edges, endLevel=%d", count,
109 _encLevel);
110 return count;
111 }
112
119 size_t flushEncoder(Vector<OutputEdge>& output) override {
120 size_t count = 0;
121
122 // Add a 2T termination pulse to mark end of frame
124 edge.level = _encLevel;
125 edge.pulseUs = 2 * _bitPeriodUs; // 2T gives receiver time to process
126 output.push_back(edge);
127 count++;
128 _encLevel = !_encLevel;
129
130 Logger::debug("Miller flushEncoder: added termination edge (2T)");
131 return count;
132 }
133
146 bool decodeEdge(uint32_t durationUs, bool level, uint8_t& result) override {
147 // Handle idle gap (end of frame)
148 if (handleIdleGap(durationUs, level, result)) {
149 return true;
150 }
151
152 // Handle preamble detection
153 if (!handlePreamble(durationUs, level)) {
154 return false;
155 }
156
157 // Decode the Miller-encoded edge
159 decodeMiller(hp, level);
160
161 // Check if we've collected a complete byte
163 }
164
165 protected:
171 if (level == getIdleLevel() && durationUs > getEndOfFrameDelayUs()) {
172 Logger::debug("Miller: Idle gap %lu us, resetting",
173 (unsigned long)durationUs);
174 // If we were mid-'1', complete it before resetting
175 if (_rxExpectingMidTransition && _rxBitPos > 0) {
176 Logger::debug(" Completing pending '1' at end of frame");
177 pushBit(true);
178 if (_rxBitPos >= 8) {
179 result = _rxByte;
180 Logger::debug(" Byte complete at frame end: 0x%02X", result);
181 reset();
182 return true;
183 }
184 }
185 reset();
186 }
187 return false;
188 }
189
196 assert(_preamble != nullptr);
197 if (!_inFrame) {
198 if (_preamble->preambleLength() == 0) {
199 // No preamble configured - first edge starts the frame
200 // Don't process this edge as data - its duration is the idle gap
201 _inFrame = true;
202 Logger::debug("Miller: No preamble, starting new frame");
203 return false; // Skip this edge, wait for next
204 } else if (_preamble->detect(newEdge)) {
205 // Preamble detected - start new frame
206 _inFrame = true;
207 Logger::debug("Miller: Preamble detected, starting new frame");
208 // Don't process the preamble edge as data, wait for next edge
209 return false;
210 } else {
211 // Still waiting for preamble
212 return false;
213 }
214 }
215 return true;
216 }
217
222 uint32_t halfT = _bitPeriodUs / 2;
223 int hp = (durationUs + halfT / 2) / halfT;
224 if (hp < 1) hp = 1;
225 if (hp > 4) hp = 4; // Cap at 2T (4 half-periods)
226 return hp;
227 }
228
232 void decodeMiller(int hp, bool level) {
233 Logger::debug("Miller decode: hp=%d, bitPos=%d, expectMid=%d", hp,
234 _rxBitPos, _rxExpectingMidTransition);
235
236 if (_rxExpectingMidTransition) {
238 } else {
240 }
241 }
242
247 if (hp == 1) {
248 // 0.5T: Second half of '1' - complete the bit
249 Logger::debug(" '1' complete");
250 pushBit(true);
251 _rxExpectingMidTransition = false;
252 } else if (hp == 2) {
253 // 1T = 0.5T + 0.5T: Complete '1', then start another '1'
254 Logger::debug(" '1', start '1'");
255 pushBit(true);
256 // Stay in expectMid state for next '1'
257 } else if (hp == 3) {
258 // 1.5T = 0.5T + 1T: Complete '1', then full '0' bit
259 Logger::debug(" '1', '0'");
260 pushBit(true);
261 pushBit(false);
262 _rxExpectingMidTransition = false;
263 } else if (hp == 4) {
264 // 2T = 0.5T + 1T + 0.5T: Complete '1', '0', start '1'
265 Logger::debug(" '1', '0', start '1'");
266 pushBit(true);
267 pushBit(false);
268 // Stay in expectMid state for next '1'
269 }
270 }
271
276 if (hp == 1) {
277 // 0.5T: First half of '1' bit
278 Logger::debug(" start '1'");
279 _rxExpectingMidTransition = true;
280 } else if (hp == 2) {
281 // 1T: One '0' bit (with transition at boundary)
282 Logger::debug(" '0'");
283 pushBit(false);
284 } else if (hp == 3) {
285 // 1.5T = 1T + 0.5T: '0' bit, then start of '1'
286 Logger::debug(" '0', start '1'");
287 pushBit(false);
288 _rxExpectingMidTransition = true;
289 } else if (hp == 4) {
290 // 2T: Two '0' bits
291 Logger::debug(" '0', '0'");
292 pushBit(false);
293 pushBit(false);
294 }
295 }
296
302 if (_rxBitPos >= 8) {
303 result = _rxByte;
304 Logger::debug(" Byte complete: 0x%02X", result);
305 _rxByte = 0;
306 _rxBitPos = 0;
307 return true;
308 }
309 return false;
310 }
311
318 void pushBit(bool bit) {
319 if (_rxBitPos >= 8) {
320 Logger::debug(" WARNING: bit overflow, ignoring bit %d", bit);
321 return;
322 }
323 _rxByte = (_rxByte << 1) | (bit ? 1 : 0);
324 _rxBitPos++;
325 Logger::debug(" Pushed bit %d, pos=%d, byte=0x%02X", bit, _rxBitPos,
326 _rxByte);
327 }
328
329 // Encoder state
330 bool _encLevel = true; // Current output level (starts HIGH for visibility)
331
332 // Decoder state
333 uint8_t _rxByte = 0; // Byte being assembled from received bits
334 uint8_t _rxBitPos = 0; // Number of bits received (0-8)
335 bool _rxExpectingMidTransition = false; // True if in first half of '1' bit
336};
337
338} // namespace pulsewire
Abstract base class for IR protocol encoding and decoding.
Definition Codec.h:53
virtual void reset()
Reset the internal state of the codec.
Definition Codec.h:84
static void debug(const char *format,...)
Log a debug message with formatting.
Definition Logger.h:82
Miller (Delay Modulation) encoding/decoding utility class.
Definition MillerCodec.h:17
size_t encode(uint8_t byte, Vector< OutputEdge > &output) override
Encode a byte using Miller encoding (byte-aligned).
Definition MillerCodec.h:50
size_t flushEncoder(Vector< OutputEdge > &output) override
Add termination edge at end of frame.
CodecEnum getCodecType() const override
Definition MillerCodec.h:22
void decodeAtBoundary(int hp)
Decode when at bit boundary (not expecting mid-transition).
int getEndOfFrameDelayUs() override
Definition MillerCodec.h:26
void pushBit(bool bit)
Push a decoded bit into the receive buffer.
bool handlePreamble(uint32_t durationUs, bool level)
Handle preamble detection.
bool handleIdleGap(uint32_t durationUs, bool level, uint8_t &result)
Handle idle gap detection and end-of-frame processing.
bool decodeEdge(uint32_t durationUs, bool level, uint8_t &result) override
Decode Miller encoding edge by edge.
void decodeWhileExpectingMid(int hp)
Decode when expecting mid-transition of a '1' bit.
void reset() override
Reset the internal state of the codec.
Definition MillerCodec.h:30
void decodeMiller(int hp, bool level)
Decode Miller half-periods into bits.
int durationToHalfPeriods(uint32_t durationUs)
Convert duration to half-periods (rounded to nearest).
size_t getEdgeCount() const override
Get the number of protocol symbols (bits, pulses, etc.) per encoded byte.
Definition MillerCodec.h:24
bool getIdleLevel() const override
Provides the initial ldle state (low or hith)
Definition MillerCodec.h:28
bool checkByteComplete(uint8_t &result)
Check if a complete byte has been assembled.
Abstract base class for preamble detection and generation.
Definition Preamble.h:40
virtual bool detect(const OutputEdge &edge)
Detects if the incoming edge matches the expected preamble pattern.
Definition Preamble.h:52
Small, header-only vector replacement for non-STL environments.
Definition Vector.h:29
void push_back(const T &value)
Add element to end of vector.
Definition Vector.h:92
Specifies a single IR signal segment for protocol-agnostic transmission.
Definition Preamble.h:23