Arduino PulseWire Transceiver Library
Loading...
Searching...
No Matches
RxDriverArduino.h
1#pragma once
2#include <Arduino.h>
3
4#include "TransceiverConfig.h"
5#include "pulse/RxDriver.h"
6#include "pulse/codecs/Codec.h"
7#include "pulse/codecs/ManchesterCodec.h"
8#include "pulse/tools/Logger.h"
9#include "pulse/tools/RingBuffer.h"
10#include "pulse/tools/Vector.h"
11
12namespace pulsewire {
13
23class RxDriverInt : public RxDriver {
24 public:
25 virtual void handleInterrupt() = 0;
26};
27
28#if !defined(HAS_INTERRUPT_ARG)
29
35 public:
36 static bool attach(uint8_t pin, RxDriverInt* instance) {
37 initISR();
38 for (int i = 0; i < 10; i++) {
39 if (!_isrData[i].active || _isrData[i].pin == pin) {
40 _isrData[i].instance = instance;
41 _isrData[i].active = true;
42 _isrData[i].pin = pin;
43 attachInterrupt(digitalPinToInterrupt(pin), _isrData[i].isr, CHANGE);
44 return true;
45 }
46 }
47 return false;
48 }
49
50 static bool isAttached(uint8_t pin) {
51 for (int i = 0; i < 10; i++) {
52 if (_isrData[i].active && _isrData[i].pin == pin) {
53 return true;
54 }
55 }
56 return false;
57 }
58
59 static void detach(uint8_t pin) {
60 for (int i = 0; i < 10; i++) {
61 if (_isrData[i].active && _isrData[i].pin == pin) {
63 _isrData[i].instance = nullptr;
64 _isrData[i].active = false;
65 _isrData[i].pin = -1;
66 break;
67 }
68 }
69 }
70
71 private:
72 struct InstanceData {
73 RxDriverInt* instance;
74 void (*isr)(void);
75 int pin;
76 bool active;
77 };
78
79 static InstanceData _isrData[10];
80
81 static void isr0() { dispatch(0); }
82 static void isr1() { dispatch(1); }
83 static void isr2() { dispatch(2); }
84 static void isr3() { dispatch(3); }
85 static void isr4() { dispatch(4); }
86 static void isr5() { dispatch(5); }
87 static void isr6() { dispatch(6); }
88 static void isr7() { dispatch(7); }
89 static void isr8() { dispatch(8); }
90 static void isr9() { dispatch(9); }
91
92 static void initISR() {
93 static bool initialized = false;
94 if (!initialized) {
95 void (*isrTable[10])() = {isr0, isr1, isr2, isr3, isr4,
96 isr5, isr6, isr7, isr8, isr9};
97 for (int i = 0; i < 10; ++i) {
98 _isrData[i].instance = nullptr;
99 _isrData[i].isr = isrTable[i];
100 _isrData[i].pin = -1;
101 _isrData[i].active = false;
102 }
103 initialized = true;
104 }
105 }
106
107 static void dispatch(int index) {
108 if (index >= 0 && index < 10 && _isrData[index].active) {
109 _isrData[index].instance->handleInterrupt();
110 }
111 }
112};
113
114#endif
115
142 public:
151 uint32_t freqHz = DEFAULT_BIT_FREQ_HZ,
152 bool useChecksum = false, uint32_t timeoutUs = 0)
153 : _pin(pin),
154 _codec(codec),
155 _freqHz(freqHz),
156 _useChecksum(useChecksum),
157 _timeoutUs(timeoutUs) {
158 reset();
159 }
160
161 void init(Codec& codec, uint8_t pin, uint32_t freqHz = DEFAULT_BIT_FREQ_HZ,
162 bool useChecksum = false, uint32_t timeoutUs = 0) {
163 TRACE();
164 _pin = pin;
165 _codec = codec;
166 _freqHz = freqHz;
167 _useChecksum = useChecksum;
168 _timeoutUs = timeoutUs;
169 reset();
170 }
171
172 //  destructor
173 ~RxDriverArduino() { end(); }
174
175 void setFrameSize(uint16_t size) override {
176 TRACE();
177 _codec.setFrameSize(size);
178 _frameSize = size;
179 _rxBuffer.resize(size * 2);
180 _edgeBuffer.resize(size * _codec.getEdgeCount() *
181 2); // enough for edges of a full frame
182 }
183
184 void setRxBufferSize(size_t size) { _rxBuffer.resize(size); }
185
186 bool begin(uint16_t bitFrequencyHz = DEFAULT_BIT_FREQ_HZ) override {
187 TRACE();
188 _freqHz = bitFrequencyHz;
189
190 // Calculate bit timing window from freqHz
191 _bitPeriodUs = 1000000UL / _freqHz;
192 _minUs = _bitPeriodUs / 2;
193 _maxUs = _bitPeriodUs * 2;
195 "Bit frequency: %d Hz, Bit period: %d us, Timing window: [%d, %d] us",
196 _freqHz, _bitPeriodUs, _minUs, _maxUs);
197
198 if (_timeoutUs == 0) {
199 _timeoutUs = 0.95 * _codec.getEndOfFrameDelayUs();
200 }
201
202 // Ensure byte buffer is sized for frame
203 setFrameSize(_frameSize);
204
205 // Initialize codec once
206 bool rc = _codec.begin(bitFrequencyHz);
207
208 if (!_is_active) {
209 // Reset state BEFORE attaching interrupt
210 reset();
211 // Read initial pin level before first interrupt
212 pinMode(_pin, INPUT);
213 _lastLevel = digitalRead(_pin);
214 _lastEdge = micros();
215
216#ifdef HAS_INTERRUPT_ARG
217 attachInterruptArg(_pin, interruptHandler, this, CHANGE);
218#else
219 if (!ISRManager::attach(_pin, this)) {
220 return false;
221 }
222#endif
223 _is_active = true;
224 }
225
226 Logger::info("RX driver started on pin %d with codec %s with frame size %d",
227 _pin, _codec.name(), _frameSize);
228 return rc;
229 }
230
231 size_t readBytes(uint8_t* buffer, size_t length) override {
232 processEdges();
233 checkTimeout();
234 if (_rxBuffer.size() == 0) {
235 return 0;
236 }
237 size_t count = 0;
238 count = _rxBuffer.readArray((uint8_t*)buffer, length);
239 return count;
240 }
241
242 int available() override {
243 processEdges();
244 checkTimeout();
245 int result = _rxBuffer.available();
246 Logger::debug("available(): %d", result);
247 return result;
248 }
249
250 void end() {
251 TRACE();
252 if (_is_active) {
253#ifdef HAS_INTERRUPT_ARG
254 detachInterrupt(_pin);
255#else
256 ISRManager::detach(_pin);
257#endif
258 _is_active = false;
259 }
260 }
261
262 protected:
263 volatile uint32_t _lastEdge;
264 volatile bool _lastLevel;
265 uint8_t _pin;
266 uint32_t _freqHz;
267 uint16_t _frameSize = DEFAULT_FRAME_SIZE;
268 Codec& _codec;
269 RingBuffer<uint8_t> _rxBuffer;
270 RingBuffer<OutputEdge> _edgeBuffer;
271
272 uint32_t _bitPeriodUs = 0;
273 uint32_t _minUs = 0;
274 uint32_t _maxUs = 0;
275 bool _useChecksum = false;
276 uint32_t _timeoutUs = 0;
277 volatile bool _is_active = false;
278 volatile bool _is_open = false;
279
280#ifdef HAS_INTERRUPT_ARG
281 static IRAM_ATTR void interruptHandler(void* arg) {
282 static_cast<RxDriverArduino*>(arg)->handleInterrupt();
283 }
284#endif
285
286 void IRAM_ATTR handleInterrupt() override {
287 bool newLevel = digitalRead(_pin);
288 uint32_t now = micros();
289 uint32_t duration = now - _lastEdge;
290 _lastEdge = now;
291
292 // Pass the duration and the previous level (_lastLevel)
293 OutputEdge edge{_lastLevel, duration};
294 _edgeBuffer.write(edge);
295 _lastLevel = newLevel;
296 }
297
298 // Called from loop() context — safe for heap, virtual calls, etc.
299 void processEdges() {
300 Logger::debug("processEdges");
301 bool ok = true;
302 OutputEdge edge;
303 while (!_rxBuffer.isFull() && ok) {
304 noInterrupts();
305 ok = _edgeBuffer.read(edge);
306 interrupts();
307 uint8_t byte_data = 0;
308 if (ok && _codec.decodeEdge(edge.pulseUs, edge.level, byte_data)) {
309 _rxBuffer.write(byte_data);
310 _is_open = true;
311 }
312 }
313 }
314
315 void checkTimeout() {
316 Logger::debug("checkTimeout");
317 uint32_t now = micros();
318 uint32_t duration = now - _lastEdge;
319 uint8_t byte_data = 0;
320
321 // Process pending final edge (e.g., last stop bit)
322 if (_is_open) {
323 // Protect codec state from ISR preemption
324 bool has_data = _codec.decodeEdge(_bitPeriodUs, _lastLevel, byte_data);
325 if (has_data) {
326 _rxBuffer.write(byte_data);
327 }
328 _is_open = false;
329 }
330
331 // Process timeout — reset state atomically
332 if (duration > _timeoutUs) {
333 // noInterrupts();
334 _codec.reset();
335 // interrupts();
336 // Don't touch _lastEdge/_lastLevel here — ISR owns them
337 }
338 }
339
340 void reset() {
341 _lastEdge = micros();
342 _lastLevel = 0;
343 _is_open = false;
344 }
345};
346
347} // namespace pulsewire
Abstract base class for IR protocol encoding and decoding.
Definition Codec.h:41
virtual bool decodeEdge(uint32_t durationUs, bool level, uint8_t &result)
Edge-based decoding for protocol-agnostic RX drivers.
Definition Codec.h:106
virtual bool begin(uint32_t bitFrequencyHz)
initialization method for codecs that require setup before use (e.g., loading PIO programs,...
Definition Codec.h:56
virtual int getEndOfFrameDelayUs()=0
Provide the end of frame delay in microseconds for this protocol, used by RX driver to.
const char * name() const
Get the name of the codec type as a string (e.g., "PulseDistance", "Manchester").
Definition Codec.h:218
virtual size_t getEdgeCount() const =0
Get the number of protocol symbols (bits, pulses, etc.) per encoded byte.
Manager for handling multiple ISR instances on platforms without attachInterruptArg.
static void debug(const char *format,...)
Log a debug message with formatting.
Definition Logger.h:82
static void info(const char *format,...)
Log an informational message with formatting.
Definition Logger.h:67
int readArray(T *dest, size_t len)
Read up to len elements into dest, returns number of elements read.
Definition RingBuffer.h:94
Interrupt-driven Arduino RX driver for pulse-based protocols.
void end()
Stop the receiver.
void setRxBufferSize(size_t size)
Set the size of the internal RX buffer.
bool begin(uint32_t bitFrequencyHz=DEFAULT_BIT_FREQ_HZ) override
Start the receiver.
int available() override
Get the number of bytes available in the internal buffer.
size_t readBytes(uint8_t *buffer, size_t length) override
RxDriverArduino(Codec &codec, uint8_t pin, uint32_t freqHz=DEFAULT_BIT_FREQ_HZ, bool useChecksum=false, uint32_t timeoutUs=0)
void setFrameSize(uint16_t size) override
Set the expected frame size for dynamic data reception.
Interrupt-capable IR RX driver interface for platforms without attachInterruptArg.
Abstract base class for IR receivers.
Definition RxDriver.h:10
Small, header-only vector replacement for non-STL environments.
Definition Vector.h:29
void resize(size_t n)
Resize vector to n elements.
Definition Vector.h:150