Arduino PulseWire Transceiver Library
Loading...
Searching...
No Matches
RxDriverESP32.h
1
2#pragma once
3#include <driver/rmt_rx.h>
4#include <driver/rmt_tx.h>
5#include <freertos/FreeRTOS.h>
6#include <freertos/queue.h>
7#include <freertos/task.h>
8
9#include <stddef.h>
10#include <stdint.h>
11
12#include "Config.h"
13#include "pulse/Vector.h"
14#include "pulse/Logger.h"
15#include "pulse/RxDriver.h"
16#include "pulse/TxDriver.h"
17#include "pulse/Codec.h"
18#include "pulse/RingBuffer.h"
19
20namespace pulsewire {
21
43class RxDriverESP32 : public RxDriver {
44public:
53 uint32_t freqHz = DEFAULT_BIT_FREQ_HZ, bool useChecksum = false, uint32_t timeoutUs = 5000)
54 : _codec(codec), _rxPin(pin), _freqHz(freqHz), _useChecksum(useChecksum), _timeoutUs(timeoutUs) {
55 _lastSymbolTime = 0;
56 }
57
58 void setFrameSize(uint8_t size) override { _frameSize = size; }
59
60 void setRxBufferSize(size_t size) override { _rxBufferSize = size; }
61
62 bool begin(uint8_t frameSize) override {
63 setFrameSize(frameSize);
64 return begin();
65 }
66
67 bool begin() override {
68 if (!_codec.begin()){
69 return false;
70 }
71 manchesterBits.reserve(_codec.getBitCount());
72 // Enable DMA for large frames
73 uint8_t dma = (_frameSize > 64) ? 1 : 0;
74 rmt_rx_channel_config_t rx_config = {.gpio_num = (gpio_num_t)_rxPin,
75 .clk_src = RMT_CLK_SRC_DEFAULT,
76 .resolution_hz = 1000000,
77 .mem_block_symbols = 64,
78 .flags = {
79 .invert_in = 0,
80 .with_dma = dma,
81 }};
82
83 // Setup channel
84 rmt_channel_handle_t rx_channel = nullptr;
85 esp_err_t rmt_result = rmt_new_rx_channel(&rx_config, &rx_channel);
86 if (rmt_result != ESP_OK || rx_channel == nullptr) {
87 Logger::error("RMT RX channel initialization failed: %d", rmt_result);
88 return false;
89 }
90 _rxChannel = rx_channel;
91
92 // Setup FreeRTOS queue for received frames
93 int count = _rxBufferSize / _frameSize;
94 if (_rxBufferSize % _frameSize != 0) count++;
95
96 // Create Queue
97 _frameQueue = xQueueCreate(count, _frameSize); // n frames max in queue
98 if (_frameQueue == nullptr) {
99 Logger::error("Failed to create frame queue");
100 return false;
101 }
102 rxOverflow.resize(_rxBufferSize);
103 _stopTask = false;
104
105 // Create task
106 BaseType_t taskResult = xTaskCreatePinnedToCore(rxTask, "rmt_rx", 4096,
107 this, 1, &_taskHandle, 1);
108 if (taskResult != pdPASS || _taskHandle == nullptr) {
109 Logger::error("Failed to create RX task: %d", taskResult);
110 return false;
111 }
112 return true;
113 }
114
115 void end() override {
116 _stopTask = true;
117 if (_taskHandle) {
118 // Wait for task to exit
119 while (eTaskGetState(_taskHandle) != eDeleted) {
120 delay(1);
121 }
122 _taskHandle = nullptr;
123 }
124 if (_rxChannel) {
125 rmt_del_channel(_rxChannel);
126 _rxChannel = nullptr;
127 }
128 }
129
130 size_t readBytes(uint8_t* buffer, size_t length) override {
131 size_t totalRead = 0;
132 // First, serve from rxOverflow if any
133 while (length > 0 && rxOverflow.available() > 0) {
134 int b = rxOverflow.read();
135 if (b < 0) break;
136 *buffer++ = (uint8_t)b;
137 --length;
138 ++totalRead;
139 }
140 // Then, read full frames from the queue as needed
141 uint8_t frame[_frameSize]{};
142 while (length > 0 && _frameQueue &&
143 uxQueueMessagesWaiting(_frameQueue) > 0) {
144 if (xQueueReceive(_frameQueue, frame, 0) == pdTRUE) {
145 size_t toCopy = (length < _frameSize) ? length : _frameSize;
147 totalRead += toCopy;
148 buffer += toCopy;
149 length -= toCopy;
150 // If we didn't consume the whole frame, store the rest in rxOverflow
151 if (toCopy < _frameSize) {
152 rxOverflow.writeArray(frame + toCopy, _frameSize - toCopy);
153 }
154 }
155 }
156 return totalRead;
157 }
158
159 int available() override {
160 if (!_frameQueue) return 0;
161 return uxQueueMessagesWaiting(_frameQueue) * _frameSize;
162 }
163
164 protected:
165 size_t _rxBufferSize = 256;
166 uint8_t _rxPin;
167 uint16_t _frameSize = DEFAULT_FRAME_SIZE;
168 uint32_t _freqHz;
169 rmt_channel_handle_t _rxChannel = nullptr;
170 TaskHandle_t _taskHandle = nullptr;
171 volatile bool _stopTask = false;
172 uint16_t _preambleBits = 0;
173 bool _inFrame = false;
174 QueueHandle_t _frameQueue = nullptr;
175 RingBuffer<uint8_t> rxOverflow;
176 ManchesterCodec& _codec;
177 Vector<uint8_t> manchesterBits;
178 bool _useChecksum = false;
179 uint32_t _timeoutUs = 5000;
180 uint32_t _lastSymbolTime = 0;
181
192 static void rxTask(void* arg) {
193 auto* self = static_cast<RxDriverESP32*>(arg);
194 size_t symbolCount = self->_frameSize * self->_codec.getBitCount();
196 uint8_t data[self->_frameSize]{};
197 self->_lastSymbolTime = micros();
198 while (!self->_stopTask) {
199 size_t rx_size = 0;
201 esp_err_t err =
202 rmt_receive(self->_rxChannel, symbols.data(),
203 symbols.size() * sizeof(rmt_symbol_word_t), &rx_cfg);
204 bool gotSymbol = false;
205 if (err == ESP_OK && rx_size > 0) {
206 size_t num_bits = rx_size / sizeof(rmt_symbol_word_t);
207 // Use decodeEdge for each symbol
208 uint32_t bitPeriodUs = 1000000UL / self->_freqHz;
211 size_t frameLen = 0;
212 for (size_t i = 0; i < num_bits; ++i) {
213 uint32_t duration = symbols[i].duration0 + symbols[i].duration1;
214 bool isOne = (symbols[i].level0 == 1 && symbols[i].level1 == 0);
215 bool isZero = (symbols[i].level0 == 0 && symbols[i].level1 == 1);
216 if (isOne || isZero) {
217 bool level = isOne ? 1 : 0;
218 self->_lastSymbolTime = micros();
219 gotSymbol = true;
220 if (self->_codec.decodeEdge(duration, level, minUs, maxUs, data,
221 self->_frameSize, frameLen)) {
222 if (self->_useChecksum) {
223 // Validate checksum
224 if (frameLen < 2) continue;
225 uint8_t sum = 0;
226 for (size_t j = 0; j < frameLen - 1; ++j) sum += data[j];
227 if (sum == data[frameLen - 1]) {
228 xQueueSend(self->_frameQueue, data, 0);
229 }
230 } else {
231 xQueueSend(self->_frameQueue, data, 0);
232 }
233 }
234 }
235 }
236 }
237 // Timeout logic: if no symbol received for timeoutUs, flush buffer
238 uint32_t now = micros();
239 if (!gotSymbol && (now - self->_lastSymbolTime > self->_timeoutUs)) {
240 // Try to decode with a zero-duration edge to flush the buffer
241 size_t frameLen = 0;
242 if (self->_codec.decodeEdge(0, 0, 0, 0, data, self->_frameSize, frameLen)) {
243 if (self->_useChecksum) {
244 if (frameLen >= 2) {
245 uint8_t sum = 0;
246 for (size_t j = 0; j < frameLen - 1; ++j) sum += data[j];
247 if (sum == data[frameLen - 1]) {
248 xQueueSend(self->_frameQueue, data, 0);
249 }
250 }
251 } else {
252 xQueueSend(self->_frameQueue, data, 0);
253 }
254 }
255 self->_lastSymbolTime = now;
256 }
257 delay(1);
258 }
259 vTaskDelete(nullptr);
260 }
261
262 // processSymbols is no longer needed; decoding is handled by decodeEdge
263};
264
265} // namespace pulsewire
static void error(const char *format,...)
Log an error message with formatting.
Definition Logger.h:37
Manchester encoding/decoding utility class for IR communication.
bool begin(uint32_t bitFrequencyHz) override
initialization method for codecs that require setup before use (e.g., loading PIO programs,...
bool read(T &out)
Read and remove the next element. Returns true if successful.
Definition RingBuffer.h:75
High-performance ESP32 IR RX driver using the RMT peripheral and Manchester decoding.
RxDriverESP32(ManchesterCodec &codec, uint8_t pin, uint32_t freqHz=DEFAULT_BIT_FREQ_HZ, bool useChecksum=false, uint32_t timeoutUs=5000)
void end() override
Stop 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
static void rxTask(void *arg)
FreeRTOS task for receiving and decoding IR frames using ESP32 RMT.
void setRxBufferSize(size_t size) override
Set the size of the internal RX buffer.
Abstract base class for IR receivers.
Definition RxDriver.h:10
Small, header-only vector replacement for non-STL environments.
Definition Vector.h:29
void reserve(size_t cap)
Reserve space for at least cap elements.
Definition Vector.h:157
T * data()
Pointer to underlying data.
Definition Vector.h:106
size_t size() const
Number of elements in vector.
Definition Vector.h:116