RadioLib
Universal wireless communication library for Arduino
Loading...
Searching...
No Matches
EspHal.h
1// EspHal.h
2// ESP-IDF HAL for RadioLib using spi_master driver
3// This avoids conflicts with other SPI devices on the same bus
4
5#ifndef RADIOLIB_ESP_IDF_HAL
6#define RADIOLIB_ESP_IDF_HAL
7
8#if defined(ESP_PLATFORM)
9#include "RadioLib.h"
10#include "driver/gpio.h"
11#include "driver/ledc.h"
12#include "driver/spi_master.h"
13#include "esp_log.h"
14#include "esp_rom_sys.h" // For esp_rom_delay_us
15#include "esp_timer.h"
16#include "freertos/FreeRTOS.h"
17#include "freertos/task.h"
18#include "sdkconfig.h"
19#include <cinttypes> // For PRIu32
20
21// Arduino-style macros for RadioLib compatibility
22#ifndef LOW
23#define LOW (0x0)
24#endif
25#ifndef HIGH
26#define HIGH (0x1)
27#endif
28#define INPUT (GPIO_MODE_INPUT)
29#define OUTPUT (GPIO_MODE_OUTPUT)
30#define RISING (GPIO_INTR_POSEDGE)
31#define FALLING (GPIO_INTR_NEGEDGE)
32
33// These are left undefined by the generic build; define them for the ESP-IDF HAL
34#if !defined(RADIOLIB_ESP32)
35#define RADIOLIB_ESP32
36#endif
37#if !defined(RADIOLIB_TONE_ESP32_CHANNEL)
38#define RADIOLIB_TONE_ESP32_CHANNEL (LEDC_CHANNEL_0)
39#endif
40
46class EspHal : public RadioLibHal {
47public:
58 EspHal(int8_t sck, int8_t miso, int8_t mosi,
59 spi_host_device_t host = SPI2_HOST, uint32_t clockHz = 2000000)
60 : RadioLibHal(INPUT, OUTPUT, LOW, HIGH, RISING, FALLING), spiSCK(sck),
61 spiMISO(miso), spiMOSI(mosi), spiHost(host), spiClockHz(clockHz),
62 spiDevice(nullptr), busInitialized(false), deviceAdded(false),
63 halInitialized(false), tonePrevFreq(-1) {}
64
65 virtual ~EspHal() { term(); }
66
67 void init() override {
68 if (!halInitialized) {
69 spiBegin();
70 // Install the application-wide GPIO ISR service once.
71 // Loosely accept duplicate installations (ESP_ERR_INVALID_STATE)
72 esp_err_t ret = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
73 if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
74 ESP_LOGE("EspHal", "Failed to install GPIO ISR service: %s",
75 esp_err_to_name(ret));
76 }
77 halInitialized = true;
78 }
79 }
80
81 void term() override {
82 if (halInitialized) {
83 spiEnd();
84 halInitialized = false;
85 }
86 }
87
88 // GPIO operations
89 void pinMode(uint32_t pin, uint32_t mode) override {
90 if (pin == RADIOLIB_NC) {
91 return;
92 }
93
94 gpio_config_t conf = {
95 .pin_bit_mask = (1ULL << pin),
96 .mode = (mode == OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT,
97 .pull_up_en = GPIO_PULLUP_DISABLE,
98 .pull_down_en = GPIO_PULLDOWN_DISABLE,
99 .intr_type = GPIO_INTR_DISABLE,
100 };
101 gpio_config(&conf);
102 }
103
104 void digitalWrite(uint32_t pin, uint32_t value) override {
105 if (pin == RADIOLIB_NC) {
106 return;
107 }
108 gpio_set_level((gpio_num_t)pin, value);
109 }
110
111 uint32_t digitalRead(uint32_t pin) override {
112 if (pin == RADIOLIB_NC) {
113 return 0;
114 }
115 return gpio_get_level((gpio_num_t)pin);
116 }
117
118 void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void),
119 uint32_t mode) override {
120 if (interruptNum == RADIOLIB_NC) {
121 return;
122 }
123
124 gpio_set_intr_type((gpio_num_t)interruptNum, (gpio_int_type_t)mode);
125 gpio_isr_handler_add((gpio_num_t)interruptNum, (gpio_isr_t)interruptCb,
126 nullptr);
127 }
128
129 void detachInterrupt(uint32_t interruptNum) override {
130 if (interruptNum == RADIOLIB_NC) {
131 return;
132 }
133 gpio_isr_handler_remove((gpio_num_t)interruptNum);
134 }
135
136 // Timing functions
137 void delay(unsigned long ms) override { vTaskDelay(pdMS_TO_TICKS(ms)); }
138
139 void delayMicroseconds(unsigned long us) override { esp_rom_delay_us(us); }
140
141 unsigned long millis() override {
142 return (unsigned long)(esp_timer_get_time() / 1000ULL);
143 }
144
145 unsigned long micros() override {
146 return (unsigned long)esp_timer_get_time();
147 }
148
149 long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override {
150 if (pin == RADIOLIB_NC) {
151 return 0;
152 }
153
154 unsigned long startMicros = micros();
155 unsigned long currentMicros;
156
157 // Wait for pulse to start
158 while (this->digitalRead(pin) != state) {
159 currentMicros = micros();
160 if (currentMicros - startMicros >= timeout) {
161 return 0;
162 }
163 }
164
165 // Measure pulse duration
166 unsigned long pulseStart = micros();
167 while (this->digitalRead(pin) == state) {
168 currentMicros = micros();
169 if (currentMicros - pulseStart >= timeout) {
170 return 0;
171 }
172 }
173
174 return micros() - pulseStart;
175 }
176
177 // GPIO pull-up/pull-down configuration
178 void pullUpDown(uint32_t pin, bool enable, bool up) override {
179 if (pin == RADIOLIB_NC) {
180 return;
181 }
182
183 if (enable) {
184 gpio_set_pull_mode((gpio_num_t)pin,
185 up ? GPIO_PULLUP_ONLY : GPIO_PULLDOWN_ONLY);
186 } else {
187 gpio_set_pull_mode((gpio_num_t)pin, GPIO_FLOATING);
188 }
189 }
190
191 // Tone generation using the LEDC peripheral (lazily initialised on first use)
192 void tone(uint32_t pin, unsigned int frequency,
193 RadioLibTime_t duration = 0) override {
194 if (pin == RADIOLIB_NC) {
195 return;
196 }
197 (void)duration;
198
199 // Configure the LEDC timer + channel only once
200 if (tonePrevFreq == -1) {
201 ledc_timer_config_t timer_config = {};
202 timer_config.speed_mode = LEDC_LOW_SPEED_MODE;
203 timer_config.duty_resolution = LEDC_TIMER_10_BIT;
204 timer_config.timer_num = LEDC_TIMER_0;
205 timer_config.freq_hz = (frequency > 0) ? frequency : 1000;
206 timer_config.clk_cfg = LEDC_AUTO_CLK;
207 ledc_timer_config(&timer_config);
208
209 ledc_channel_config_t channel_config = {};
210 channel_config.gpio_num = (int)pin;
211 channel_config.speed_mode = LEDC_LOW_SPEED_MODE;
212 channel_config.channel = (ledc_channel_t)RADIOLIB_TONE_ESP32_CHANNEL;
213 channel_config.timer_sel = LEDC_TIMER_0;
214 channel_config.duty = 512; // 50% duty at 10-bit resolution
215 channel_config.hpoint = 0;
216 ledc_channel_config(&channel_config);
217 }
218
219 // Update the frequency only when it actually changes
220 if ((int32_t)frequency != tonePrevFreq) {
221 ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, frequency);
222 ledc_set_duty(LEDC_LOW_SPEED_MODE,
223 (ledc_channel_t)RADIOLIB_TONE_ESP32_CHANNEL, 512);
224 ledc_update_duty(LEDC_LOW_SPEED_MODE,
225 (ledc_channel_t)RADIOLIB_TONE_ESP32_CHANNEL);
226 tonePrevFreq = (int32_t)frequency;
227 }
228 }
229
230 void noTone(uint32_t pin) override {
231 if (pin == RADIOLIB_NC) {
232 return;
233 }
234 if (tonePrevFreq == -1) {
235 return; // never started
236 }
237 ledc_stop(LEDC_LOW_SPEED_MODE,
238 (ledc_channel_t)RADIOLIB_TONE_ESP32_CHANNEL, 0);
239 tonePrevFreq = -1;
240 }
241
242 // SPI operations using spi_master driver
243 void spiBegin() {
244 ESP_LOGD("EspHal", "Initializing SPI on host %d (SCK=%d, MISO=%d, MOSI=%d)",
245 spiHost, spiSCK, spiMISO, spiMOSI);
246
247 // Try to initialize the bus - it may already be initialized by another
248 // component (shared bus). DMA is disabled because RadioLib does not
249 // allocate DMA-capable buffers and the transfers here are small.
250 spi_bus_config_t bus_config = {};
251 bus_config.mosi_io_num = spiMOSI;
252 bus_config.miso_io_num = spiMISO;
253 bus_config.sclk_io_num = spiSCK;
254 bus_config.quadwp_io_num = -1;
255 bus_config.quadhd_io_num = -1;
256 bus_config.data4_io_num = -1;
257 bus_config.data5_io_num = -1;
258 bus_config.data6_io_num = -1;
259 bus_config.data7_io_num = -1;
260 bus_config.max_transfer_sz = SOC_SPI_MAXIMUM_BUFFER_SIZE;
261 bus_config.flags = 0;
262 bus_config.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO;
263 bus_config.intr_flags = 0;
264
265 esp_err_t ret = spi_bus_initialize(spiHost, &bus_config, SPI_DMA_DISABLED);
266 if (ret == ESP_OK) {
267 busInitialized = true;
268 ESP_LOGD("EspHal", "SPI bus initialized successfully");
269 } else if (ret == ESP_ERR_INVALID_STATE) {
270 // Bus already initialized - this is OK for shared bus
271 busInitialized = false; // We didn't init it, so we won't free it
272 ESP_LOGD("EspHal", "SPI bus already initialized (shared bus)");
273 } else {
274 ESP_LOGE("EspHal", "Failed to initialize SPI bus: %s",
275 esp_err_to_name(ret));
276 return;
277 }
278
279 // Add the device. CS is left to RadioLib (software-driven via
280 // digitalWrite on the Module's csPin), so spics_io_num is -1.
281 spi_device_interface_config_t dev_config = {};
282 dev_config.command_bits = 0;
283 dev_config.address_bits = 0;
284 dev_config.dummy_bits = 0;
285 dev_config.mode = 0; // SPI mode 0 (CPOL=0, CPHA=0)
286 dev_config.clock_source = SPI_CLK_SRC_DEFAULT;
287 dev_config.duty_cycle_pos = 128; // 50% duty cycle
288 dev_config.cs_ena_pretrans = 0;
289 dev_config.cs_ena_posttrans = 0;
290 dev_config.clock_speed_hz = (int)spiClockHz;
291 dev_config.input_delay_ns = 0;
292 dev_config.spics_io_num = -1; // RadioLib drives CS in software
293 dev_config.flags = 0;
294 dev_config.queue_size = 1;
295 dev_config.pre_cb = nullptr;
296 dev_config.post_cb = nullptr;
297
298 ret = spi_bus_add_device(spiHost, &dev_config, &spiDevice);
299 if (ret != ESP_OK) {
300 ESP_LOGE("EspHal", "Failed to add SPI device: %s", esp_err_to_name(ret));
301 return;
302 }
303 deviceAdded = true;
304 ESP_LOGD("EspHal", "SPI device added (clock=%" PRIu32 " Hz, software CS)",
305 spiClockHz);
306 }
307
308 void spiBeginTransaction() {
309 // Acquire the SPI bus for this device
310 if (spiDevice != nullptr) {
311 spi_device_acquire_bus(spiDevice, portMAX_DELAY);
312 }
313 }
314
315 void spiTransfer(uint8_t *out, size_t len, uint8_t *in) {
316 if (spiDevice == nullptr) {
317 ESP_LOGE("EspHal", "SPI device not initialized!");
318 return;
319 }
320
321 if (len == 0) {
322 return;
323 }
324
325 spi_transaction_t trans = {};
326 trans.length = len * 8; // length in bits
327 trans.tx_buffer = out;
328 trans.rx_buffer = in;
329
330 esp_err_t ret = spi_device_polling_transmit(spiDevice, &trans);
331 if (ret != ESP_OK) {
332 ESP_LOGE("EspHal", "SPI transfer failed: %s", esp_err_to_name(ret));
333 }
334 }
335
336 void spiEndTransaction() {
337 // Release the SPI bus
338 if (spiDevice != nullptr) {
339 spi_device_release_bus(spiDevice);
340 }
341 }
342
343 void spiEnd() {
344 // Remove device from bus
345 if (deviceAdded && spiDevice != nullptr) {
346 spi_bus_remove_device(spiDevice);
347 spiDevice = nullptr;
348 deviceAdded = false;
349 ESP_LOGD("EspHal", "SPI device removed");
350 }
351
352 // Free the bus only if we initialized it
353 if (busInitialized) {
354 spi_bus_free(spiHost);
355 busInitialized = false;
356 ESP_LOGD("EspHal", "SPI bus freed");
357 }
358 }
359
360private:
361 int8_t spiSCK;
362 int8_t spiMISO;
363 int8_t spiMOSI;
364 spi_host_device_t spiHost;
365 uint32_t spiClockHz;
366 spi_device_handle_t spiDevice;
367 bool busInitialized;
368 bool deviceAdded;
369 bool halInitialized;
370 int32_t tonePrevFreq;
371};
372#endif
373#endif // RADIOLIB_ESP_IDF_HAL
Hardware abstraction library base interface.
Definition Hal.h:18
virtual uint32_t digitalRead(uint32_t pin)=0
Digital read method. Must be implemented by the platform-specific hardware abstraction!
virtual void detachInterrupt(uint32_t interruptNum)=0
Method to detach function from an external interrupt. Must be implemented by the platform-specific ha...
virtual long pulseIn(uint32_t pin, uint32_t state, RadioLibTime_t timeout)=0
Measure the length of incoming digital pulse in microseconds. Must be implemented by the platform-spe...
virtual void spiEnd()=0
SPI termination method.
virtual void init()
Module initialization method. This will be called by all radio modules at the beginning of startup....
Definition Hal.cpp:17
virtual RadioLibTime_t millis()=0
Get number of milliseconds since start. Must be implemented by the platform-specific hardware abstrac...
virtual void digitalWrite(uint32_t pin, uint32_t value)=0
Digital write method. Must be implemented by the platform-specific hardware abstraction!
virtual void tone(uint32_t pin, unsigned int frequency, RadioLibTime_t duration=0)
Method to produce a square-wave with 50% duty cycle ("tone") of a given frequency at some pin.
Definition Hal.cpp:25
virtual RadioLibTime_t micros()=0
Get number of microseconds since start. Must be implemented by the platform-specific hardware abstrac...
virtual void spiEndTransaction()=0
Method to end SPI transaction.
virtual void noTone(uint32_t pin)
Method to stop producing a tone.
Definition Hal.cpp:31
virtual void spiBegin()=0
SPI initialization method.
virtual void delay(RadioLibTime_t ms)=0
Blocking wait function. Must be implemented by the platform-specific hardware abstraction!
virtual void term()
Module termination method. This will be called by all radio modules when the destructor is called....
Definition Hal.cpp:21
virtual void delayMicroseconds(RadioLibTime_t us)=0
Blocking microsecond wait function. Must be implemented by the platform-specific hardware abstraction...
virtual void spiBeginTransaction()=0
Method to start SPI transaction.
virtual void pullUpDown(uint32_t pin, bool enable, bool up)
Enable or disable pull up or pull down for a specific pin.
Definition Hal.cpp:43
virtual void spiTransfer(uint8_t *out, size_t len, uint8_t *in)=0
Method to transfer buffer over SPI.
virtual void pinMode(uint32_t pin, uint32_t mode)=0
GPIO pin mode (input/output/...) configuration method. Must be implemented by the platform-specific h...
virtual void attachInterrupt(uint32_t interruptNum, void(*interruptCb)(void), uint32_t mode)=0
Method to attach function to an external interrupt. Must be implemented by the platform-specific hard...
unsigned long RadioLibTime_t
Type used for durations in RadioLib.
Definition TypeDef.h:679