forked from things-nyc/arduino-lmic
-
Notifications
You must be signed in to change notification settings - Fork 208
/
hal.cpp
578 lines (485 loc) · 18.1 KB
/
hal.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
/*******************************************************************************
* Copyright (c) 2015 Matthijs Kooijman
* Copyright (c) 2018-2019 MCCI Corporation
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This the HAL to run LMIC on top of the Arduino environment.
*******************************************************************************/
#include <Arduino.h>
#include <SPI.h>
// include all the lmic header files, including ../lmic/hal.h
#include "../lmic.h"
// include the C++ hal.h
#include "hal.h"
// we may need some things from stdio.
#include <stdio.h>
// -----------------------------------------------------------------------------
// I/O
static const Arduino_LMIC::HalPinmap_t *plmic_pins;
static Arduino_LMIC::HalConfiguration_t *pHalConfig;
static Arduino_LMIC::HalConfiguration_t nullHalConig;
static hal_failure_handler_t* custom_hal_failure_handler = NULL;
static void hal_interrupt_init(); // Fwd declaration
static void hal_io_init () {
// NSS and DIO0 are required, DIO1 is required for LoRa, DIO2 for FSK
ASSERT(plmic_pins->nss != LMIC_UNUSED_PIN);
ASSERT(plmic_pins->dio[0] != LMIC_UNUSED_PIN);
// SX126x family can operate with a single DIO
#if (defined(CFG_sx1276_radio) || defined(CFG_sx1272_radio))
ASSERT(plmic_pins->dio[1] != LMIC_UNUSED_PIN || plmic_pins->dio[2] != LMIC_UNUSED_PIN);
#endif
// Serial.print("nss: "); Serial.println(plmic_pins->nss);
// Serial.print("rst: "); Serial.println(plmic_pins->rst);
// Serial.print("dio[0]: "); Serial.println(plmic_pins->dio[0]);
// Serial.print("dio[1]: "); Serial.println(plmic_pins->dio[1]);
// Serial.print("dio[2]: "); Serial.println(plmic_pins->dio[2]);
// initialize SPI chip select to high (it's active low)
digitalWrite(plmic_pins->nss, HIGH);
pinMode(plmic_pins->nss, OUTPUT);
if (plmic_pins->rxtx != LMIC_UNUSED_PIN) {
// initialize to RX
digitalWrite(plmic_pins->rxtx, LOW != plmic_pins->rxtx_rx_active);
pinMode(plmic_pins->rxtx, OUTPUT);
}
if (plmic_pins->rst != LMIC_UNUSED_PIN) {
// initialize RST to floating
pinMode(plmic_pins->rst, INPUT);
}
if (pHalConfig->queryBusyPin() != LMIC_UNUSED_PIN) {
pinMode(pHalConfig->queryBusyPin(), INPUT);
}
hal_interrupt_init();
}
// val == 1 => tx
void hal_pin_rxtx (u1_t val) {
if (plmic_pins->rxtx != LMIC_UNUSED_PIN)
digitalWrite(plmic_pins->rxtx, val != plmic_pins->rxtx_rx_active);
}
// set radio RST pin to given value (or keep floating!)
void hal_pin_rst (u1_t val) {
if (plmic_pins->rst == LMIC_UNUSED_PIN)
return;
if(val == 0 || val == 1) { // drive pin
digitalWrite(plmic_pins->rst, val);
pinMode(plmic_pins->rst, OUTPUT);
} else { // keep pin floating
pinMode(plmic_pins->rst, INPUT);
}
}
s1_t hal_getRssiCal (void) {
return plmic_pins->rssi_cal;
}
//--------------------
// Interrupt handling
//--------------------
static constexpr unsigned NUM_DIO_INTERRUPT = 3;
static_assert(NUM_DIO_INTERRUPT <= NUM_DIO, "Number of interrupt-sensitive lines must be less than number of GPIOs");
static ostime_t interrupt_time[NUM_DIO_INTERRUPT] = {0};
#if !defined(LMIC_USE_INTERRUPTS)
static void hal_interrupt_init() {
pinMode(plmic_pins->dio[0], INPUT);
if (plmic_pins->dio[1] != LMIC_UNUSED_PIN)
pinMode(plmic_pins->dio[1], INPUT);
if (plmic_pins->dio[2] != LMIC_UNUSED_PIN)
pinMode(plmic_pins->dio[2], INPUT);
static_assert(NUM_DIO_INTERRUPT == 3, "Number of interrupt lines must be set to 3");
}
static bool dio_states[NUM_DIO_INTERRUPT] = {0};
void hal_pollPendingIRQs_helper() {
uint8_t i;
for (i = 0; i < NUM_DIO_INTERRUPT; ++i) {
if (plmic_pins->dio[i] == LMIC_UNUSED_PIN)
continue;
if (dio_states[i] != digitalRead(plmic_pins->dio[i])) {
dio_states[i] = !dio_states[i];
if (dio_states[i] && interrupt_time[i] == 0) {
ostime_t const now = os_getTime();
interrupt_time[i] = now ? now : 1;
}
}
}
}
#else
// Interrupt handlers
static void hal_isrPin0() {
if (interrupt_time[0] == 0) {
ostime_t now = os_getTime();
interrupt_time[0] = now ? now : 1;
}
}
static void hal_isrPin1() {
if (interrupt_time[1] == 0) {
ostime_t now = os_getTime();
interrupt_time[1] = now ? now : 1;
}
}
static void hal_isrPin2() {
if (interrupt_time[2] == 0) {
ostime_t now = os_getTime();
interrupt_time[2] = now ? now : 1;
}
}
typedef void (*isr_t)();
static const isr_t interrupt_fns[NUM_DIO_INTERRUPT] = {hal_isrPin0, hal_isrPin1, hal_isrPin2};
static_assert(NUM_DIO_INTERRUPT == 3, "number of interrupts must be 3 for initializing interrupt_fns[]");
static void hal_interrupt_init() {
for (uint8_t i = 0; i < NUM_DIO_INTERRUPT; ++i) {
if (plmic_pins->dio[i] == LMIC_UNUSED_PIN)
continue;
pinMode(plmic_pins->dio[i], INPUT);
attachInterrupt(digitalPinToInterrupt(plmic_pins->dio[i]), interrupt_fns[i], RISING);
}
}
#endif // LMIC_USE_INTERRUPTS
void hal_processPendingIRQs() {
uint8_t i;
for (i = 0; i < NUM_DIO_INTERRUPT; ++i) {
ostime_t iTime;
if (plmic_pins->dio[i] == LMIC_UNUSED_PIN)
continue;
// NOTE(tmm@mcci.com): if using interrupts, this next step
// assumes uniprocessor and fairly strict memory ordering
// semantics relative to ISRs. It would be better to use
// interlocked-exchange, but that's really far beyond
// Arduino semantics. Because our ISRs use "first time
// stamp" semantics, we don't have a value-race. But if
// we were to disable ints here, we might observe a second
// edge that we'll otherwise miss. Not a problem in this
// use case, as the radio won't release IRQs until we
// explicitly clear them.
iTime = interrupt_time[i];
if (iTime) {
interrupt_time[i] = 0;
radio_irq_handler_v2(i, iTime);
}
}
}
// -----------------------------------------------------------------------------
// SPI
static void hal_spi_init () {
SPI.begin();
}
#if (defined(CFG_sx1261_radio) || defined(CFG_sx1262_radio))
bit_t is_busy() {
// SX126x uses BUSY pin
return digitalRead(pHalConfig->queryBusyPin()) ? true : false;
}
#endif
static void hal_spi_trx(u1_t cmd, u1_t* buf, size_t len, bit_t is_read) {
uint32_t spi_freq;
u1_t nss = plmic_pins->nss;
if ((spi_freq = plmic_pins->spi_freq) == 0)
spi_freq = LMIC_SPI_FREQ;
SPISettings settings(spi_freq, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(settings);
digitalWrite(nss, 0);
// SX126x modems use BUSY pin. Only interact with SPI when BUSY goes LOW
#if (defined(CFG_sx1261_radio) || defined(CFG_sx1262_radio))
while (is_busy());
#endif
SPI.transfer(cmd);
for (; len > 0; --len, ++buf) {
u1_t data = is_read ? 0x00 : *buf;
data = SPI.transfer(data);
if (is_read)
*buf = data;
}
digitalWrite(nss, 1);
SPI.endTransaction();
}
void hal_spi_write(u1_t cmd, const u1_t* buf, size_t len) {
hal_spi_trx(cmd, (u1_t*)buf, len, 0);
}
void hal_spi_read(u1_t cmd, u1_t* buf, size_t len) {
hal_spi_trx(cmd, buf, len, 1);
}
// SX126x modems behave slightly differently to SX127x. They will often need to transfer multiple bytes before reading
#if (defined(CFG_sx1261_radio) || defined(CFG_sx1262_radio))
void hal_spi_read_sx126x(u1_t cmd, u1_t* addr, size_t addr_len, u1_t* buf, size_t buf_len) {
uint32_t spi_freq;
u1_t nss = plmic_pins->nss;
if ((spi_freq = plmic_pins->spi_freq) == 0)
spi_freq = LMIC_SPI_FREQ;
SPISettings settings(spi_freq, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(settings);
digitalWrite(nss, 0);
while (is_busy());
SPI.transfer(cmd);
// Transfer address and NOP bits
for (; addr_len > 0; --addr_len, ++addr) {
u1_t addr_byte = *addr;
SPI.transfer(addr_byte);
}
// Read buf_len bytes to buf
for (; buf_len > 0; --buf_len, ++buf) {
u1_t data = 0x00;
data = SPI.transfer(data);
*buf = data;
}
digitalWrite(nss, 1);
SPI.endTransaction();
}
#endif
// -----------------------------------------------------------------------------
// TIME
static void hal_time_init () {
// Nothing to do
}
u4_t hal_ticks () {
// Because micros() is scaled down in this function, micros() will
// overflow before the tick timer should, causing the tick timer to
// miss a significant part of its values if not corrected. To fix
// this, the "overflow" serves as an overflow area for the micros()
// counter. It consists of three parts:
// - The US_PER_OSTICK upper bits are effectively an extension for
// the micros() counter and are added to the result of this
// function.
// - The next bit overlaps with the most significant bit of
// micros(). This is used to detect micros() overflows.
// - The remaining bits are always zero.
//
// By comparing the overlapping bit with the corresponding bit in
// the micros() return value, overflows can be detected and the
// upper bits are incremented. This is done using some clever
// bitwise operations, to remove the need for comparisons and a
// jumps, which should result in efficient code. By avoiding shifts
// other than by multiples of 8 as much as possible, this is also
// efficient on AVR (which only has 1-bit shifts).
static uint8_t overflow = 0;
// Scaled down timestamp. The top US_PER_OSTICK_EXPONENT bits are 0,
// the others will be the lower bits of our return value.
uint32_t scaled = micros() >> US_PER_OSTICK_EXPONENT;
// Most significant byte of scaled
uint8_t msb = scaled >> 24;
// Mask pointing to the overlapping bit in msb and overflow.
const uint8_t mask = (1 << (7 - US_PER_OSTICK_EXPONENT));
// Update overflow. If the overlapping bit is different
// between overflow and msb, it is added to the stored value,
// so the overlapping bit becomes equal again and, if it changed
// from 1 to 0, the upper bits are incremented.
overflow += (msb ^ overflow) & mask;
// Return the scaled value with the upper bits of stored added. The
// overlapping bit will be equal and the lower bits will be 0, so
// bitwise or is a no-op for them.
return scaled | ((uint32_t)overflow << 24);
// 0 leads to correct, but overly complex code (it could just return
// micros() unmodified), 8 leaves no room for the overlapping bit.
static_assert(US_PER_OSTICK_EXPONENT > 0 && US_PER_OSTICK_EXPONENT < 8, "Invalid US_PER_OSTICK_EXPONENT value");
}
// Returns the number of ticks until time. Negative values indicate that
// time has already passed.
static s4_t delta_time(u4_t time) {
return (s4_t)(time - hal_ticks());
}
// deal with boards that are stressed by no-interrupt delays #529, etc.
#if defined(ARDUINO_DISCO_L072CZ_LRWAN1)
# define HAL_WAITUNTIL_DOWNCOUNT_MS 16 // on this board, 16 ms works better
# define HAL_WAITUNTIL_DOWNCOUNT_THRESH ms2osticks(16) // as does this threashold.
#else
# define HAL_WAITUNTIL_DOWNCOUNT_MS 8 // on most boards, delay for 8 ms
# define HAL_WAITUNTIL_DOWNCOUNT_THRESH ms2osticks(9) // but try to leave a little slack for final timing.
#endif
u4_t hal_waitUntil (u4_t time) {
s4_t delta = delta_time(time);
// check for already too late.
if (delta < 0)
return -delta;
// From delayMicroseconds docs: Currently, the largest value that
// will produce an accurate delay is 16383. Also, STM32 does a better
// job with delay is less than 10,000 us; so reduce in steps.
// It's nice to use delay() for the longer times.
while (delta > HAL_WAITUNTIL_DOWNCOUNT_THRESH) {
// deliberately delay 8ms rather than 9ms, so we
// will exit loop with delta typically positive.
// Depends on BSP keeping time accurately even if interrupts
// are disabled.
delay(HAL_WAITUNTIL_DOWNCOUNT_MS);
// re-synchronize.
delta = delta_time(time);
}
// The radio driver runs with interrupt disabled, and this can
// mess up timing APIs on some platforms. If we know the BSP feature
// set, we can decide whether to use delta_time() [more exact,
// but not always possible with interrupts off], or fall back to
// delay_microseconds() [less exact, but more universal]
#if defined(_mcci_arduino_version)
// unluckily, delayMicroseconds() isn't very accurate.
// but delta_time() works with interrupts disabled.
// so spin using delta_time().
while (delta_time(time) > 0)
/* loop */;
#else // ! defined(_mcci_arduino_version)
// on other BSPs, we need to stick with the older way,
// until we fix the radio driver to run with interrupts
// enabled.
if (delta > 0)
delayMicroseconds(delta * US_PER_OSTICK);
#endif // ! defined(_mcci_arduino_version)
// we aren't "late". Callers are interested in gross delays, not
// necessarily delays due to poor timekeeping here.
return 0;
}
// check and rewind for target time
u1_t hal_checkTimer (u4_t time) {
// No need to schedule wakeup, since we're not sleeping
return delta_time(time) <= 0;
}
static uint8_t irqlevel = 0;
void hal_disableIRQs () {
noInterrupts();
irqlevel++;
}
void hal_enableIRQs () {
if(--irqlevel == 0) {
interrupts();
#if !defined(LMIC_USE_INTERRUPTS)
// Instead of using proper interrupts (which are a bit tricky
// and/or not available on all pins on AVR), just poll the pin
// values. Since os_runloop disables and re-enables interrupts,
// putting this here makes sure we check at least once every
// loop.
//
// As an additional bonus, this prevents the can of worms that
// we would otherwise get for running SPI transfers inside ISRs.
// We merely collect the edges and timestamps here; we wait for
// a call to hal_processPendingIRQs() before dispatching.
hal_pollPendingIRQs_helper();
#endif /* !defined(LMIC_USE_INTERRUPTS) */
}
}
uint8_t hal_getIrqLevel(void) {
return irqlevel;
}
void hal_sleep () {
// Not implemented
}
// -----------------------------------------------------------------------------
#if defined(LMIC_PRINTF_TO)
#if !defined(__AVR)
static ssize_t uart_putchar (void *, const char *buf, size_t len) {
return LMIC_PRINTF_TO.write((const uint8_t *)buf, len);
}
static cookie_io_functions_t functions =
{
.read = NULL,
.write = uart_putchar,
.seek = NULL,
.close = NULL
};
void hal_printf_init() {
stdout = fopencookie(NULL, "w", functions);
if (stdout != nullptr) {
setvbuf(stdout, NULL, _IONBF, 0);
}
}
#else // defined(__AVR)
static int uart_putchar (char c, FILE *)
{
LMIC_PRINTF_TO.write(c) ;
return 0 ;
}
void hal_printf_init() {
// create a FILE structure to reference our UART output function
static FILE uartout;
memset(&uartout, 0, sizeof(uartout));
// fill in the UART file descriptor with pointer to writer.
fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
// The uart is the standard output device STDOUT.
stdout = &uartout ;
}
#endif // !defined(ESP8266) || defined(ESP31B) || defined(ESP32)
#endif // defined(LMIC_PRINTF_TO)
void hal_init (void) {
// use the global constant
Arduino_LMIC::hal_init_with_pinmap(&lmic_pins);
}
// hal_init_ex is a C API routine, written in C++, and it's called
// with a pointer to an lmic_pinmap.
void hal_init_ex (const void *pContext) {
const lmic_pinmap * const pHalPinmap = (const lmic_pinmap *) pContext;
if (! Arduino_LMIC::hal_init_with_pinmap(pHalPinmap)) {
hal_failed(__FILE__, __LINE__);
}
}
// C++ API: initialize the HAL properly with a configuration object
namespace Arduino_LMIC {
bool hal_init_with_pinmap(const HalPinmap_t *pPinmap)
{
if (pPinmap == nullptr)
return false;
// set the static pinmap pointer.
plmic_pins = pPinmap;
// set the static HalConfiguration pointer.
HalConfiguration_t * const pThisHalConfig = pPinmap->pConfig;
if (pThisHalConfig != nullptr)
pHalConfig = pThisHalConfig;
else
pHalConfig = &nullHalConig;
pHalConfig->begin();
// configure radio I/O and interrupt handler
hal_io_init();
// configure radio SPI
hal_spi_init();
// configure timer and interrupt handler
hal_time_init();
#if defined(LMIC_PRINTF_TO)
// printf support
hal_printf_init();
#endif
// declare success
return true;
}
}; // namespace Arduino_LMIC
void hal_failed (const char *file, u2_t line) {
if (custom_hal_failure_handler != NULL) {
(*custom_hal_failure_handler)(file, line);
}
#if defined(LMIC_FAILURE_TO)
LMIC_FAILURE_TO.println("FAILURE ");
LMIC_FAILURE_TO.print(file);
LMIC_FAILURE_TO.print(':');
LMIC_FAILURE_TO.println(line);
LMIC_FAILURE_TO.flush();
#endif
hal_disableIRQs();
// Infinite loop
while (1) {
;
}
}
void hal_set_failure_handler(const hal_failure_handler_t* const handler) {
custom_hal_failure_handler = handler;
}
ostime_t hal_setModuleActive (bit_t val) {
// setModuleActive() takes a c++ bool, so
// it effectively says "val != 0". We
// don't have to.
return pHalConfig->setModuleActive(val);
}
bit_t hal_queryUsingTcxo(void) {
return pHalConfig->queryUsingTcxo();
}
bit_t hal_queryUsingDcdc(void) {
return pHalConfig->queryUsingDcdc();
}
bit_t hal_queryUsingDIO2AsRfSwitch(void) {
return pHalConfig->queryUsingDIO2AsRfSwitch();
}
bit_t hal_queryUsingDIO3AsTCXOSwitch(void) {
return pHalConfig->queryUsingDIO3AsTCXOSwitch();
}
uint8_t hal_getTxPowerPolicy(
u1_t inputPolicy,
s1_t requestedPower,
u4_t frequency
) {
return (uint8_t) pHalConfig->getTxPowerPolicy(
Arduino_LMIC::HalConfiguration_t::TxPowerPolicy_t(inputPolicy),
requestedPower,
frequency
);
}