RadioLib
Universal wireless communication library for Arduino
EspHal.h
1 #ifndef ESP_HAL_H
2 #define ESP_HAL_H
3 
4 // include RadioLib
5 #include <RadioLib.h>
6 
7 // this example only works on ESP32 and is unlikely to work on ESP32S2/S3 etc.
8 // if you need high portability, you should probably use Arduino anyway ...
9 #if CONFIG_IDF_TARGET_ESP32 == 0
10  #error Target is not ESP32!
11 #endif
12 
13 // include all the dependencies
14 #include "freertos/FreeRTOS.h"
15 #include "freertos/task.h"
16 #include "esp32/rom/gpio.h"
17 #include "soc/rtc.h"
18 #include "soc/dport_reg.h"
19 #include "soc/spi_reg.h"
20 #include "soc/spi_struct.h"
21 #include "driver/gpio.h"
22 #include "hal/gpio_hal.h"
23 #include "esp_timer.h"
24 #include "esp_log.h"
25 
26 // define Arduino-style macros
27 #define LOW (0x0)
28 #define HIGH (0x1)
29 #define INPUT (0x01)
30 #define OUTPUT (0x03)
31 #define RISING (0x01)
32 #define FALLING (0x02)
33 #define NOP() asm volatile ("nop")
34 
35 #define MATRIX_DETACH_OUT_SIG (0x100)
36 #define MATRIX_DETACH_IN_LOW_PIN (0x30)
37 
38 // all of the following is needed to calculate SPI clock divider
39 #define ClkRegToFreq(reg) (apb_freq / (((reg)->clkdiv_pre + 1) * ((reg)->clkcnt_n + 1)))
40 
41 typedef union {
42  uint32_t value;
43  struct {
44  uint32_t clkcnt_l: 6;
45  uint32_t clkcnt_h: 6;
46  uint32_t clkcnt_n: 6;
47  uint32_t clkdiv_pre: 13;
48  uint32_t clk_equ_sysclk: 1;
49  };
50 } spiClk_t;
51 
52 uint32_t getApbFrequency() {
53  rtc_cpu_freq_config_t conf;
54  rtc_clk_cpu_freq_get_config(&conf);
55 
56  if(conf.freq_mhz >= 80) {
57  return(80 * MHZ);
58  }
59 
60  return((conf.source_freq_mhz * MHZ) / conf.div);
61 }
62 
63 uint32_t spiFrequencyToClockDiv(uint32_t freq) {
64  uint32_t apb_freq = getApbFrequency();
65  if(freq >= apb_freq) {
66  return SPI_CLK_EQU_SYSCLK;
67  }
68 
69  const spiClk_t minFreqReg = { 0x7FFFF000 };
70  uint32_t minFreq = ClkRegToFreq((spiClk_t*) &minFreqReg);
71  if(freq < minFreq) {
72  return minFreqReg.value;
73  }
74 
75  uint8_t calN = 1;
76  spiClk_t bestReg = { 0 };
77  int32_t bestFreq = 0;
78  while(calN <= 0x3F) {
79  spiClk_t reg = { 0 };
80  int32_t calFreq;
81  int32_t calPre;
82  int8_t calPreVari = -2;
83 
84  reg.clkcnt_n = calN;
85 
86  while(calPreVari++ <= 1) {
87  calPre = (((apb_freq / (reg.clkcnt_n + 1)) / freq) - 1) + calPreVari;
88  if(calPre > 0x1FFF) {
89  reg.clkdiv_pre = 0x1FFF;
90  } else if(calPre <= 0) {
91  reg.clkdiv_pre = 0;
92  } else {
93  reg.clkdiv_pre = calPre;
94  }
95  reg.clkcnt_l = ((reg.clkcnt_n + 1) / 2);
96  calFreq = ClkRegToFreq(&reg);
97  if(calFreq == (int32_t) freq) {
98  memcpy(&bestReg, &reg, sizeof(bestReg));
99  break;
100  } else if(calFreq < (int32_t) freq) {
101  if(RADIOLIB_ABS(freq - calFreq) < RADIOLIB_ABS(freq - bestFreq)) {
102  bestFreq = calFreq;
103  memcpy(&bestReg, &reg, sizeof(bestReg));
104  }
105  }
106  }
107  if(calFreq == (int32_t) freq) {
108  break;
109  }
110  calN++;
111  }
112  return(bestReg.value);
113 }
114 
115 // create a new ESP-IDF hardware abstraction layer
116 // the HAL must inherit from the base RadioLibHal class
117 // and implement all of its virtual methods
118 // this is pretty much just copied from Arduino ESP32 core
119 class EspHal : public RadioLibHal {
120  public:
121  // default constructor - initializes the base HAL and any needed private members
122  EspHal(int8_t sck, int8_t miso, int8_t mosi)
123  : RadioLibHal(INPUT, OUTPUT, LOW, HIGH, RISING, FALLING),
124  spiSCK(sck), spiMISO(miso), spiMOSI(mosi) {
125  }
126 
127  void init() override {
128  // we only need to init the SPI here
129  spiBegin();
130  }
131 
132  void term() override {
133  // we only need to stop the SPI here
134  spiEnd();
135  }
136 
137  // GPIO-related methods (pinMode, digitalWrite etc.) should check
138  // RADIOLIB_NC as an alias for non-connected pins
139  void pinMode(uint32_t pin, uint32_t mode) override {
140  if(pin == RADIOLIB_NC) {
141  return;
142  }
143 
144  gpio_hal_context_t gpiohal;
145  gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
146 
147  gpio_config_t conf = {
148  .pin_bit_mask = (1ULL<<pin),
149  .mode = (gpio_mode_t)mode,
150  .pull_up_en = GPIO_PULLUP_DISABLE,
151  .pull_down_en = GPIO_PULLDOWN_DISABLE,
152  .intr_type = (gpio_int_type_t)gpiohal.dev->pin[pin].int_type,
153  };
154  gpio_config(&conf);
155  }
156 
157  void digitalWrite(uint32_t pin, uint32_t value) override {
158  if(pin == RADIOLIB_NC) {
159  return;
160  }
161 
162  gpio_set_level((gpio_num_t)pin, value);
163  }
164 
165  uint32_t digitalRead(uint32_t pin) override {
166  if(pin == RADIOLIB_NC) {
167  return(0);
168  }
169 
170  return(gpio_get_level((gpio_num_t)pin));
171  }
172 
173  void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override {
174  if(interruptNum == RADIOLIB_NC) {
175  return;
176  }
177 
178  gpio_install_isr_service((int)ESP_INTR_FLAG_IRAM);
179  gpio_set_intr_type((gpio_num_t)interruptNum, (gpio_int_type_t)(mode & 0x7));
180 
181  // this uses function typecasting, which is not defined when the functions have different signatures
182  // untested and might not work
183  gpio_isr_handler_add((gpio_num_t)interruptNum, (void (*)(void*))interruptCb, NULL);
184  }
185 
186  void detachInterrupt(uint32_t interruptNum) override {
187  if(interruptNum == RADIOLIB_NC) {
188  return;
189  }
190 
191  gpio_isr_handler_remove((gpio_num_t)interruptNum);
192  gpio_wakeup_disable((gpio_num_t)interruptNum);
193  gpio_set_intr_type((gpio_num_t)interruptNum, GPIO_INTR_DISABLE);
194  }
195 
196  void delay(unsigned long ms) override {
197  vTaskDelay(ms / portTICK_PERIOD_MS);
198  }
199 
200  void delayMicroseconds(unsigned long us) override {
201  uint64_t m = (uint64_t)esp_timer_get_time();
202  if(us) {
203  uint64_t e = (m + us);
204  if(m > e) { // overflow
205  while((uint64_t)esp_timer_get_time() > e) {
206  NOP();
207  }
208  }
209  while((uint64_t)esp_timer_get_time() < e) {
210  NOP();
211  }
212  }
213  }
214 
215  unsigned long millis() override {
216  return((unsigned long)(esp_timer_get_time() / 1000ULL));
217  }
218 
219  unsigned long micros() override {
220  return((unsigned long)(esp_timer_get_time()));
221  }
222 
223  long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override {
224  if(pin == RADIOLIB_NC) {
225  return(0);
226  }
227 
228  this->pinMode(pin, INPUT);
229  uint32_t start = this->micros();
230  uint32_t curtick = this->micros();
231 
232  while(this->digitalRead(pin) == state) {
233  if((this->micros() - curtick) > timeout) {
234  return(0);
235  }
236  }
237 
238  return(this->micros() - start);
239  }
240 
241  void spiBegin() {
242  // enable peripheral
243  DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN);
244  DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST);
245 
246  // reset the control struct
247  this->spi->slave.trans_done = 0;
248  this->spi->slave.val = 0;
249  this->spi->pin.val = 0;
250  this->spi->user.val = 0;
251  this->spi->user1.val = 0;
252  this->spi->ctrl.val = 0;
253  this->spi->ctrl1.val = 0;
254  this->spi->ctrl2.val = 0;
255  this->spi->clock.val = 0;
256  this->spi->user.usr_mosi = 1;
257  this->spi->user.usr_miso = 1;
258  this->spi->user.doutdin = 1;
259  for(uint8_t i = 0; i < 16; i++) {
260  this->spi->data_buf[i] = 0x00000000;
261  }
262 
263  // set SPI mode 0
264  this->spi->pin.ck_idle_edge = 0;
265  this->spi->user.ck_out_edge = 0;
266 
267  // set bit order to MSB first
268  this->spi->ctrl.wr_bit_order = 0;
269  this->spi->ctrl.rd_bit_order = 0;
270 
271  // set the clock
272  this->spi->clock.val = spiFrequencyToClockDiv(2000000);
273 
274  // initialize pins
275  this->pinMode(this->spiSCK, OUTPUT);
276  this->pinMode(this->spiMISO, INPUT);
277  this->pinMode(this->spiMOSI, OUTPUT);
278  gpio_matrix_out(this->spiSCK, HSPICLK_OUT_IDX, false, false);
279  gpio_matrix_in(this->spiMISO, HSPIQ_OUT_IDX, false);
280  gpio_matrix_out(this->spiMOSI, HSPID_IN_IDX, false, false);
281  }
282 
284  // not needed - in ESP32 Arduino core, this function
285  // repeats clock div, mode and bit order configuration
286  }
287 
288  uint8_t spiTransferByte(uint8_t b) {
289  this->spi->mosi_dlen.usr_mosi_dbitlen = 7;
290  this->spi->miso_dlen.usr_miso_dbitlen = 7;
291  this->spi->data_buf[0] = b;
292  this->spi->cmd.usr = 1;
293  while(this->spi->cmd.usr);
294  return(this->spi->data_buf[0] & 0xFF);
295  }
296 
297  void spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
298  for(size_t i = 0; i < len; i++) {
299  in[i] = this->spiTransferByte(out[i]);
300  }
301  }
302 
304  // nothing needs to be done here
305  }
306 
307  void spiEnd() {
308  // detach pins
309  gpio_matrix_out(this->spiSCK, MATRIX_DETACH_OUT_SIG, false, false);
310  gpio_matrix_in(this->spiMISO, MATRIX_DETACH_IN_LOW_PIN, false);
311  gpio_matrix_out(this->spiMOSI, MATRIX_DETACH_OUT_SIG, false, false);
312  }
313 
314  private:
315  // the HAL can contain any additional private members
316  int8_t spiSCK;
317  int8_t spiMISO;
318  int8_t spiMOSI;
319  spi_dev_t * spi = (volatile spi_dev_t *)(DR_REG_SPI2_BASE);
320 };
321 
322 #endif
Definition: EspHal.h:119
long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override
Measure the length of incoming digital pulse in microseconds. Must be implemented by the platform-spe...
Definition: EspHal.h:223
void attachInterrupt(uint32_t interruptNum, void(*interruptCb)(void), uint32_t mode) override
Method to attach function to an external interrupt. Must be implemented by the platform-specific hard...
Definition: EspHal.h:173
unsigned long micros() override
Get number of microseconds since start. Must be implemented by the platform-specific hardware abstrac...
Definition: EspHal.h:219
uint32_t digitalRead(uint32_t pin) override
Digital read method. Must be implemented by the platform-specific hardware abstraction!
Definition: EspHal.h:165
void spiBeginTransaction()
Method to start SPI transaction.
Definition: EspHal.h:283
void spiEndTransaction()
Method to end SPI transaction.
Definition: EspHal.h:303
unsigned long millis() override
Get number of milliseconds since start. Must be implemented by the platform-specific hardware abstrac...
Definition: EspHal.h:215
void pinMode(uint32_t pin, uint32_t mode) override
GPIO pin mode (input/output/...) configuration method. Must be implemented by the platform-specific h...
Definition: EspHal.h:139
void detachInterrupt(uint32_t interruptNum) override
Method to detach function from an external interrupt. Must be implemented by the platform-specific ha...
Definition: EspHal.h:186
void delay(unsigned long ms) override
Blocking wait function. Must be implemented by the platform-specific hardware abstraction!
Definition: EspHal.h:196
void term() override
Module termination method. This will be called by all radio modules when the destructor is called....
Definition: EspHal.h:132
void delayMicroseconds(unsigned long us) override
Blocking microsecond wait function. Must be implemented by the platform-specific hardware abstraction...
Definition: EspHal.h:200
void digitalWrite(uint32_t pin, uint32_t value) override
Digital write method. Must be implemented by the platform-specific hardware abstraction!
Definition: EspHal.h:157
void init() override
Module initialization method. This will be called by all radio modules at the beginning of startup....
Definition: EspHal.h:127
void spiBegin()
SPI initialization method.
Definition: EspHal.h:241
void spiEnd()
SPI termination method.
Definition: EspHal.h:307
void spiTransfer(uint8_t *out, size_t len, uint8_t *in)
Method to transfer buffer over SPI.
Definition: EspHal.h:297
Hardware abstraction library base interface.
Definition: Hal.h:13
RadioLibHal(const uint32_t input, const uint32_t output, const uint32_t low, const uint32_t high, const uint32_t rising, const uint32_t falling)
Default constructor.
Definition: Hal.cpp:3
Definition: EspHal.h:41