Arduino PulseWire Transceiver Library
Loading...
Searching...
No Matches
TxDriverESP32.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#include <stddef.h>
9#include <stdint.h>
10
11#include "TransceiverConfig.h"
12#include "pulse/RxDriver.h"
13#include "pulse/TxDriver.h"
14#include "pulse/TxDriverCommon.h"
15#include "pulse/TxProtocol.h"
16#include "pulse/codecs/Codec.h"
17#include "pulse/tools/RingBuffer.h"
18#include "pulse/tools/Vector.h"
19
20namespace pulsewire {
21
27 public:
28 TxProtocolESP32() = default;
29
30 void setFrameSize(uint16_t size) {
31 _frameSize = size;
32 _itemsBuffer.resize(
33 size * 2); // Each byte can produce up to 2 edges (for Manchester)
34 }
35
36 void setCarrierHz(uint32_t carrierHz) { _carrierHz = carrierHz; }
37
38 bool begin(uint32_t bitFrequencyHz, Codec* p_codec, uint8_t pin) {
39 Logger::info("begin TxProtocolESP32 with bitFrequencyHz=%d, pin=%d",
40 bitFrequencyHz, pin);
41 this->_codec = p_codec;
42 this->_txPin = pin;
43 if (!initCodec(bitFrequencyHz)) return false;
44
46 if (!createTxChannel(tx_channel)) return false;
47 if (!applyCarrierConfig(tx_channel)) return false;
48 if (!createCopyEncoder()) return false;
49 if (!enableTxChannel(tx_channel)) return false;
50
51 _txChannel = tx_channel;
52 _txTransmitConfig = {.loop_count = 0, .flags = {}};
53 return true;
54 }
55
56 void sendPreamble() override {
57 if (is_frame_closed) {
58 sum = 0;
59 output.clear();
60 // Insert preamble edges at the start
61 _codec->getPreamble().getEdges(output);
62 is_frame_closed = false;
63 for (const auto& edge : output) {
64 Logger::debug("Preamble edge: level=%d, duration=%d us", edge.level,
65 edge.pulseUs);
66 }
67 sum = 0;
68 }
69 }
70
71 void sendData(const uint8_t* data, uint8_t len) override {
72 if (len == 0) return;
73 // process bytes
74 for (int byteIdx = 0; byteIdx < len; ++byteIdx) {
75 uint8_t b = data[byteIdx];
76 sum += b;
77 _codec->encode(b, output);
78 }
79
80 // Convert OutputSpec to RMT symbols (step by 2)
81 // Ensure even number of edges - pad with minimal edge if needed
82 if (output.size() % 2 != 0) {
83 Logger::debug("Odd edge count %zu, padding with 1us edge", output.size());
84 if (output.size() > 0) {
85 output.push_back(OutputEdge(!output.back().level, 1));
86 }
87 }
88 size_t symbolCount = output.size() / 2;
89
90 if (_itemsBuffer.size() < symbolCount) _itemsBuffer.resize(symbolCount);
91 for (size_t i = 0, j = 0; i + 1 < output.size(); i += 2, ++j) {
92 _itemsBuffer[j].duration0 = output[i].pulseUs;
93 _itemsBuffer[j].level0 = output[i].level ? 1 : 0;
94 _itemsBuffer[j].duration1 = output[i + 1].pulseUs;
95 _itemsBuffer[j].level1 = output[i + 1].level ? 1 : 0;
97 "TX edge %zu: level0=%d, duration0=%d us, level1=%d, duration1=%d "
98 "us",
99 j, output[i].level, output[i].pulseUs, output[i + 1].level,
100 output[i + 1].pulseUs);
101 }
102 if (_txChannel && _txEncoder && symbolCount > 0) {
103 esp_err_t err = rmt_transmit(_txChannel, _txEncoder, _itemsBuffer.data(),
105 &_txTransmitConfig);
106 if (err != ESP_OK) {
107 Logger::error("RMT tx failed: %d", err);
108 } else {
110 }
111 }
112 output.clear();
113 _itemsBuffer.clear();
114 }
115
116 void sendEnd(bool& useChecksum) {
117 // Optionally append checksum
118 if (is_frame_closed) return;
119 if (useChecksum) {
120 _codec->encode(sum, output);
121 sum = 0;
122 }
123
124 // add delay: create idle period after frame
125 uint32_t delayUs = _codec->getEndOfFrameDelayUs();
126 bool idleLevel = _codec->getIdleLevel();
127 // Ensure the frame ends in idle state with proper delay
128 output.push_back(OutputEdge(idleLevel, delayUs));
129 output.push_back(OutputEdge(idleLevel, 1));
130
131 // Convert OutputSpec to RMT symbols
132 // Ensure even number of edges - pad with minimal edge if needed
133 if (output.size() % 2 != 0) {
134 Logger::error("Odd edge count %zu in sendEnd, padding with 1us edge",
135 output.size());
136 if (output.size() > 0) {
137 output.push_back(OutputEdge(!output.back().level, 1));
138 }
139 }
140 size_t symbolCount = output.size() / 2;
141 if (_itemsBuffer.size() < symbolCount) _itemsBuffer.resize(symbolCount);
142 for (size_t i = 0, j = 0; i + 1 < output.size(); i += 2, ++j) {
143 _itemsBuffer[j].duration0 = output[i].pulseUs;
144 _itemsBuffer[j].level0 = output[i].level ? 1 : 0;
145 _itemsBuffer[j].duration1 = output[i + 1].pulseUs;
146 _itemsBuffer[j].level1 = output[i + 1].level ? 1 : 0;
147
149 "TX End frame edge %zu: level0=%d, duration0=%d us, level1=%d, "
150 "duration1=%d us",
151 j, output[i].level, output[i].pulseUs, output[i + 1].level,
152 output[i + 1].pulseUs);
153 }
154 if (_txChannel && _txEncoder && symbolCount > 0) {
155 esp_err_t err = rmt_transmit(_txChannel, _txEncoder, _itemsBuffer.data(),
157 &_txTransmitConfig);
158 if (err != ESP_OK) {
159 Logger::error("RMT tx failed: %d", err);
160 } else {
162 }
163 }
164 is_frame_closed = true;
165 }
166
167 bool isFrameClosed() const override { return is_frame_closed; }
168
169 protected:
170 Codec* _codec = nullptr;
171 uint8_t _txPin;
172 uint16_t _frameSize = 64;
173 rmt_channel_handle_t _txChannel = nullptr;
174 Vector<rmt_symbol_word_t> _itemsBuffer;
175 rmt_encoder_handle_t _txEncoder = nullptr;
176 rmt_transmit_config_t _txTransmitConfig = {};
177 Vector<OutputEdge> output;
178 uint8_t sum = 0;
179 bool is_frame_closed = true;
180 uint32_t _carrierHz = 0;
181
182 private:
183 static constexpr uint32_t kRmtResolutionHz = 1000000;
184
185 bool initCodec(uint32_t bitFrequencyHz) {
186 if (!_codec->begin(bitFrequencyHz)) {
187 Logger::error("Codec initialization failed");
188 return false;
189 }
190 return true;
191 }
192
193 bool createTxChannel(rmt_channel_handle_t& tx_channel) {
194 uint8_t dma = (_frameSize > 64) ? 1 : 0;
195 uint16_t memBlockSymbols = 64; // ESP-IDF requires even and >= 64
196 if (memBlockSymbols & 0x1) ++memBlockSymbols;
197
198 rmt_tx_channel_config_t tx_config = {.gpio_num = (gpio_num_t)_txPin,
200 .resolution_hz = kRmtResolutionHz,
203 .flags = {
204 .invert_out = 0,
205 .with_dma = dma,
206 }};
207
209 if (tx_result != ESP_OK && dma) {
210 Logger::error("RMT TX DMA unsupported, retrying without DMA");
211 tx_config.flags.with_dma = 0;
213 }
214 if (tx_result != ESP_OK || tx_channel == nullptr) {
215 Logger::error("RMT TX channel initialization failed: %d", tx_result);
216 return false;
217 }
218 return true;
219 }
220
221 bool applyCarrierConfig(rmt_channel_handle_t tx_channel) {
222 if (_carrierHz == 0) return true;
223
224 uint32_t carrierHz = _carrierHz;
225 uint32_t periodTicks = kRmtResolutionHz / carrierHz;
226 if (periodTicks < 2) {
227 carrierHz = kRmtResolutionHz / 2;
229 "Carrier frequency too high for RMT resolution, clamped to %u Hz",
230 carrierHz);
231 }
232
234 .frequency_hz = carrierHz, // e.g. 38000 for 38kHz
235 .duty_cycle = 0.33f, // range is 0.0 .. 1.0
236 };
238 if (carrier_result != ESP_OK) {
239 Logger::error("RMT carrier config failed: %d", carrier_result);
240 return false;
241 }
242 return true;
243 }
244
245 bool createCopyEncoder() {
249 if (enc_result != ESP_OK || encoder == nullptr) {
250 Logger::error("RMT copy encoder init failed: %d", enc_result);
251 return false;
252 }
253 _txEncoder = encoder;
254 return true;
255 }
256
257 bool enableTxChannel(rmt_channel_handle_t tx_channel) {
259 if (enable_result != ESP_OK) {
260 Logger::error("RMT TX channel enable failed: %d", enable_result);
261 return false;
262 }
263 return true;
264 }
265};
266
272 public:
281 TxDriverESP32(Codec& codec, uint8_t pin, uint32_t freq = CARRIER_HZ,
282 bool useChecksum = false) {
283 _carrierHz = freq; // Store carrier frequency before init
284 init(codec, pin, useChecksum);
285 }
286
287 void init(Codec& codec, uint8_t pin, uint8_t duty = 33,
288 bool useChecksum = false) {
289 TxDriverCommon::init(protocol, codec, pin, useChecksum);
290 protocol.setCarrierHz(_carrierHz);
291 }
292
293 protected:
294 TxProtocolESP32 protocol;
295 uint32_t _carrierHz = CARRIER_HZ;
296
297 void sendPreamble() { protocol.sendPreamble(); }
298
299 void sendData(const uint8_t* data, uint8_t len) {
300 protocol.sendData(data, len);
301 }
302
303 void sendEnd() { protocol.sendEnd(_useChecksum); }
304};
305
306} // namespace pulsewire
Abstract base class for IR protocol encoding and decoding.
Definition Codec.h:53
virtual size_t encode(uint8_t byte, Vector< OutputEdge > &output)=0
Fill output vector with protocol-specific OutputSpec(s) for a byte.
virtual int getEndOfFrameDelayUs()=0
virtual bool getIdleLevel() const
Provides the initial ldle state (low or hith)
Definition Codec.h:127
Preamble & getPreamble()
Get the preamble detector associated with this codec.
Definition Codec.h:100
virtual bool begin(uint32_t bitFrequencyHz)
initialization method for codecs that require setup before use (e.g., loading PIO programs,...
Definition Codec.h:70
static void debug(const char *format,...)
Log a debug message with formatting.
Definition Logger.h:82
static void error(const char *format,...)
Log an error message with formatting.
Definition Logger.h:37
static void info(const char *format,...)
Log an informational message with formatting.
Definition Logger.h:67
virtual int getEdges(pulsewire::Vector< pulsewire::OutputEdge > &output) const =0
Returns the expected preamble edges for this protocol.
Provides common logic for transmitting signals using various framing modes.
ESP32-specific TxDriver implementation that uses TxProtocolESP32 for transmission.
TxDriverESP32(Codec &codec, uint8_t pin, uint32_t freq=CARRIER_HZ, bool useChecksum=false)
ESP32-specific TxProtocol implementation using RMT for precise timing and DMA support.
Abstract base class for defining transmission protocols.
Definition TxProtocol.h:125
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
void clear()
Remove all elements.
Definition Vector.h:120
void push_back(const T &value)
Add element to end of vector.
Definition Vector.h:92
T * data()
Pointer to underlying data.
Definition Vector.h:106
T & back()
Access last element.
Definition Vector.h:123
size_t size() const
Number of elements in vector.
Definition Vector.h:116
Specifies a single IR signal segment for protocol-agnostic transmission.
Definition Preamble.h:23