mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-06-15 04:14:28 +02:00
455 lines
12 KiB
C++
Executable File
455 lines
12 KiB
C++
Executable File
/*******************************************************************************
|
|
*
|
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
|
*
|
|
* Copyright (c) 2018 Manuel Bleichenbacher
|
|
*
|
|
* Licensed under MIT License
|
|
* https://opensource.org/licenses/MIT
|
|
*
|
|
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-iDF.
|
|
*******************************************************************************/
|
|
|
|
#include "../lmic/lmic.h"
|
|
#include "../hal/hal_esp32.h"
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "driver/gpio.h"
|
|
#include "driver/spi_master.h"
|
|
#include "driver/timer.h"
|
|
#include "esp_log.h"
|
|
|
|
#define LMIC_UNUSED_PIN 0xff
|
|
|
|
static const char * const TAG = "ttn_hal";
|
|
|
|
lmic_pinmap lmic_pins;
|
|
HAL_ESP32 ttn_hal;
|
|
|
|
|
|
struct HALQueueItem {
|
|
ostime_t time;
|
|
HAL_Event ev;
|
|
|
|
HALQueueItem() : time(0), ev(DIO0) { }
|
|
HALQueueItem(HAL_Event e, ostime_t t = 0)
|
|
: time(t), ev(e) { }
|
|
};
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Constructor
|
|
|
|
HAL_ESP32::HAL_ESP32()
|
|
: nextTimerEvent(0x200000000)
|
|
{
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// I/O
|
|
|
|
void IRAM_ATTR HAL_ESP32::dioIrqHandler(void *arg)
|
|
{
|
|
uint64_t now;
|
|
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &now);
|
|
BaseType_t higherPrioTaskWoken = pdFALSE;
|
|
HALQueueItem item { (HAL_Event)(long)arg, (ostime_t)now };
|
|
xQueueSendFromISR(ttn_hal.dioQueue, &item, &higherPrioTaskWoken);
|
|
if (higherPrioTaskWoken)
|
|
portYIELD_FROM_ISR();
|
|
}
|
|
|
|
void HAL_ESP32::ioInit()
|
|
{
|
|
// NSS and DIO0 and DIO1 are required
|
|
ASSERT(lmic_pins.nss != LMIC_UNUSED_PIN);
|
|
ASSERT(lmic_pins.dio0 != LMIC_UNUSED_PIN);
|
|
ASSERT(lmic_pins.dio1 != LMIC_UNUSED_PIN);
|
|
|
|
gpio_pad_select_gpio(lmic_pins.nss);
|
|
gpio_set_level((gpio_num_t)lmic_pins.nss, 0);
|
|
gpio_set_direction((gpio_num_t)lmic_pins.nss, GPIO_MODE_OUTPUT);
|
|
|
|
if (lmic_pins.rxtx != LMIC_UNUSED_PIN)
|
|
{
|
|
gpio_pad_select_gpio(lmic_pins.rxtx);
|
|
gpio_set_level((gpio_num_t)lmic_pins.rxtx, 0);
|
|
gpio_set_direction((gpio_num_t)lmic_pins.rxtx, GPIO_MODE_OUTPUT);
|
|
}
|
|
|
|
if (lmic_pins.rst != LMIC_UNUSED_PIN)
|
|
{
|
|
gpio_pad_select_gpio((gpio_num_t)lmic_pins.rst);
|
|
gpio_set_level((gpio_num_t)lmic_pins.rst, 0);
|
|
gpio_set_direction((gpio_num_t)lmic_pins.rst, GPIO_MODE_OUTPUT);
|
|
}
|
|
|
|
dioQueue = xQueueCreate(12, sizeof(HALQueueItem));
|
|
ASSERT(dioQueue != NULL);
|
|
|
|
gpio_pad_select_gpio(lmic_pins.dio0);
|
|
gpio_set_direction((gpio_num_t)lmic_pins.dio0, GPIO_MODE_INPUT);
|
|
gpio_set_intr_type((gpio_num_t)lmic_pins.dio0, GPIO_INTR_POSEDGE);
|
|
gpio_isr_handler_add((gpio_num_t)lmic_pins.dio0, dioIrqHandler, (void *)0);
|
|
|
|
gpio_pad_select_gpio((gpio_num_t)lmic_pins.dio1);
|
|
gpio_set_direction((gpio_num_t)lmic_pins.dio1, GPIO_MODE_INPUT);
|
|
gpio_set_intr_type((gpio_num_t)lmic_pins.dio1, GPIO_INTR_POSEDGE);
|
|
gpio_isr_handler_add((gpio_num_t)lmic_pins.dio1, dioIrqHandler, (void *)1);
|
|
|
|
ESP_LOGI(TAG, "IO initialized");
|
|
}
|
|
|
|
void hal_pin_rxtx(u1_t val)
|
|
{
|
|
if (lmic_pins.rxtx == LMIC_UNUSED_PIN)
|
|
return;
|
|
|
|
gpio_set_level((gpio_num_t)lmic_pins.rxtx, val);
|
|
}
|
|
|
|
void hal_pin_rst(u1_t val)
|
|
{
|
|
if (lmic_pins.rst == LMIC_UNUSED_PIN)
|
|
return;
|
|
|
|
if (val == 0 || val == 1)
|
|
{ // drive pin
|
|
gpio_set_level((gpio_num_t)lmic_pins.rst, val);
|
|
gpio_set_direction((gpio_num_t)lmic_pins.rst, GPIO_MODE_OUTPUT);
|
|
}
|
|
else
|
|
{ // keep pin floating
|
|
gpio_set_level((gpio_num_t)lmic_pins.rst, val);
|
|
gpio_set_direction((gpio_num_t)lmic_pins.rst, GPIO_MODE_INPUT);
|
|
}
|
|
}
|
|
|
|
s1_t hal_getRssiCal (void)
|
|
{
|
|
return lmic_pins.rssi_cal;
|
|
}
|
|
|
|
ostime_t hal_setModuleActive (bit_t val)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bit_t hal_queryUsingTcxo(void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// SPI
|
|
|
|
void HAL_ESP32::spiInit()
|
|
{
|
|
// init device
|
|
spi_device_interface_config_t spiConfig;
|
|
memset(&spiConfig, 0, sizeof(spiConfig));
|
|
spiConfig.mode = 1;
|
|
spiConfig.clock_speed_hz = CONFIG_TTN_SPI_FREQ;
|
|
spiConfig.command_bits = 0;
|
|
spiConfig.address_bits = 8;
|
|
spiConfig.spics_io_num = lmic_pins.nss;
|
|
spiConfig.queue_size = 1;
|
|
spiConfig.cs_ena_posttrans = 2;
|
|
|
|
esp_err_t ret = spi_bus_add_device(lmic_pins.spi_host, &spiConfig, &spiHandle);
|
|
ESP_ERROR_CHECK(ret);
|
|
|
|
ESP_LOGI(TAG, "SPI initialized");
|
|
}
|
|
|
|
void hal_spi_write(u1_t cmd, const u1_t *buf, size_t len)
|
|
{
|
|
ttn_hal.spiWrite(cmd, buf, len);
|
|
}
|
|
|
|
void HAL_ESP32::spiWrite(uint8_t cmd, const uint8_t *buf, size_t len)
|
|
{
|
|
memset(&spiTransaction, 0, sizeof(spiTransaction));
|
|
spiTransaction.addr = cmd;
|
|
spiTransaction.length = 8 * len;
|
|
spiTransaction.tx_buffer = buf;
|
|
esp_err_t err = spi_device_transmit(spiHandle, &spiTransaction);
|
|
ESP_ERROR_CHECK(err);
|
|
}
|
|
|
|
void hal_spi_read(u1_t cmd, u1_t *buf, size_t len)
|
|
{
|
|
ttn_hal.spiRead(cmd, buf, len);
|
|
}
|
|
|
|
void HAL_ESP32::spiRead(uint8_t cmd, uint8_t *buf, size_t len)
|
|
{
|
|
memset(buf, 0, len);
|
|
memset(&spiTransaction, 0, sizeof(spiTransaction));
|
|
spiTransaction.addr = cmd;
|
|
spiTransaction.length = 8 * len;
|
|
spiTransaction.rxlength = 8 * len;
|
|
spiTransaction.tx_buffer = buf;
|
|
spiTransaction.rx_buffer = buf;
|
|
esp_err_t err = spi_device_transmit(spiHandle, &spiTransaction);
|
|
ESP_ERROR_CHECK(err);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// TIME
|
|
|
|
/*
|
|
* LIMIC uses a 32 bit time (ostime_t) counting ticks. In this implementation
|
|
* each tick is 16µs. So the timer will wrap around once every 19 hour.
|
|
* The timer alarm should trigger when a specific value has been reached.
|
|
* Due to the wrap around, an alarm time in the future can have a lower value
|
|
* than the current timer value.
|
|
*
|
|
* ESP32 has 64 bits counters with a pecularity: the alarm does not only
|
|
* trigger when the exact value has been reached but also when the clock is
|
|
* higer than the alarm value. Therefore, the wrap around is more difficult to
|
|
* handle.
|
|
*
|
|
* The approach here is to always use a higher value than the current timer
|
|
* value. If it would be lower than the timer value, 0x100000000 is added.
|
|
* The lower 32 bits still represent the desired value. After the timer has
|
|
* triggered an alarm and is higher than 0x100000000, it's value is reduced
|
|
* by 0x100000000.
|
|
*/
|
|
|
|
static const ostime_t OVERRUN_TRESHOLD = 0x10000; // approx 10 seconds
|
|
|
|
void HAL_ESP32::timerInit()
|
|
{
|
|
timer_config_t config = {
|
|
.alarm_en = false,
|
|
.counter_en = false,
|
|
.intr_type = TIMER_INTR_LEVEL,
|
|
.counter_dir = TIMER_COUNT_UP,
|
|
.auto_reload = false,
|
|
.divider = 1280 /* 80 MHz APB_CLK * 16µs */
|
|
};
|
|
timer_init(TTN_TIMER_GROUP, TTN_TIMER, &config);
|
|
timer_set_counter_value(TTN_TIMER_GROUP, TTN_TIMER, 0x0);
|
|
timer_isr_register(TTN_TIMER_GROUP, TTN_TIMER, timerIrqHandler, NULL, ESP_INTR_FLAG_IRAM, NULL);
|
|
timer_start(TTN_TIMER_GROUP, TTN_TIMER);
|
|
|
|
ESP_LOGI(TAG, "Timer initialized");
|
|
}
|
|
|
|
void HAL_ESP32::prepareNextAlarm(u4_t time)
|
|
{
|
|
uint64_t now;
|
|
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &now);
|
|
u4_t now32 = (u4_t)now;
|
|
|
|
if (now != now32)
|
|
{
|
|
// decrease timer to 32 bit value
|
|
now = now32;
|
|
timer_pause(TTN_TIMER_GROUP, TTN_TIMER);
|
|
timer_set_counter_value(TTN_TIMER_GROUP, TTN_TIMER, now);
|
|
timer_start(TTN_TIMER_GROUP, TTN_TIMER);
|
|
}
|
|
|
|
nextTimerEvent = time;
|
|
if (now32 > time && now32 - time > OVERRUN_TRESHOLD)
|
|
nextTimerEvent += 0x100000000;
|
|
}
|
|
|
|
void HAL_ESP32::armTimer()
|
|
{
|
|
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_DIS);
|
|
timer_set_alarm_value(TTN_TIMER_GROUP, TTN_TIMER, nextTimerEvent);
|
|
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_EN);
|
|
}
|
|
|
|
void HAL_ESP32::disarmTimer()
|
|
{
|
|
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_DIS);
|
|
nextTimerEvent = 0x200000000; // wait indefinitely (almost)
|
|
}
|
|
|
|
void IRAM_ATTR HAL_ESP32::timerIrqHandler(void *arg)
|
|
{
|
|
TTN_CLEAR_TIMER_ALARM;
|
|
BaseType_t higherPrioTaskWoken = pdFALSE;
|
|
HALQueueItem item { TIMER };
|
|
xQueueSendFromISR(ttn_hal.dioQueue, &item, &higherPrioTaskWoken);
|
|
if (higherPrioTaskWoken)
|
|
portYIELD_FROM_ISR();
|
|
}
|
|
|
|
bool HAL_ESP32::wait(WaitKind waitKind)
|
|
{
|
|
TickType_t ticksToWait = waitKind == CHECK_IO ? 0 : portMAX_DELAY;
|
|
while (true)
|
|
{
|
|
HALQueueItem item;
|
|
if (xQueueReceive(dioQueue, &item, ticksToWait) == pdFALSE)
|
|
return false;
|
|
|
|
if (item.ev == WAKEUP)
|
|
{
|
|
if (waitKind != WAIT_FOR_TIMER)
|
|
{
|
|
disarmTimer();
|
|
return true;
|
|
}
|
|
}
|
|
else if (item.ev == TIMER)
|
|
{
|
|
disarmTimer();
|
|
if (waitKind != CHECK_IO)
|
|
return true;
|
|
}
|
|
else // IO interrupt
|
|
{
|
|
if (waitKind != WAIT_FOR_TIMER)
|
|
disarmTimer();
|
|
enterCriticalSection();
|
|
radio_irq_handler_v2(item.ev, item.time);
|
|
leaveCriticalSection();
|
|
if (waitKind != WAIT_FOR_TIMER)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
u4_t hal_ticks()
|
|
{
|
|
uint64_t val;
|
|
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &val);
|
|
return (u4_t)val;
|
|
}
|
|
|
|
void hal_waitUntil(u4_t time)
|
|
{
|
|
ttn_hal.waitUntil(time);
|
|
}
|
|
|
|
void HAL_ESP32::waitUntil(uint32_t time)
|
|
{
|
|
prepareNextAlarm(time);
|
|
armTimer();
|
|
wait(WAIT_FOR_TIMER);
|
|
}
|
|
|
|
void HAL_ESP32::wakeUp()
|
|
{
|
|
HALQueueItem item { WAKEUP };
|
|
xQueueSend(dioQueue, &item, 0);
|
|
}
|
|
|
|
// check and rewind for target time
|
|
u1_t hal_checkTimer(u4_t time)
|
|
{
|
|
return ttn_hal.checkTimer(time);
|
|
}
|
|
|
|
uint8_t HAL_ESP32::checkTimer(uint32_t time)
|
|
{
|
|
uint64_t now;
|
|
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &now);
|
|
u4_t now32 = (u4_t)now;
|
|
|
|
if (time >= now32)
|
|
{
|
|
if (time - now32 < 5)
|
|
return 1; // timer will expire very soon
|
|
}
|
|
else
|
|
{
|
|
if (now32 - time < OVERRUN_TRESHOLD)
|
|
return 1; // timer has expired recently
|
|
}
|
|
|
|
prepareNextAlarm(time);
|
|
return 0;
|
|
}
|
|
|
|
void hal_sleep()
|
|
{
|
|
ttn_hal.sleep();
|
|
}
|
|
|
|
void HAL_ESP32::sleep()
|
|
{
|
|
if (wait(CHECK_IO))
|
|
return;
|
|
|
|
armTimer();
|
|
wait(WAIT_FOR_ANY_EVENT);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// IRQ
|
|
|
|
void hal_disableIRQs()
|
|
{
|
|
// nothing to do as interrupt handlers post message to queue
|
|
// and don't access any shared data structures
|
|
}
|
|
|
|
void hal_enableIRQs()
|
|
{
|
|
// nothing to do as interrupt handlers post message to queue
|
|
// and don't access any shared data structures
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Synchronization between application code and background task
|
|
|
|
void HAL_ESP32::initCriticalSection()
|
|
{
|
|
mutex = xSemaphoreCreateRecursiveMutex();
|
|
}
|
|
|
|
void HAL_ESP32::enterCriticalSection()
|
|
{
|
|
xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
|
|
}
|
|
|
|
void HAL_ESP32::leaveCriticalSection()
|
|
{
|
|
xSemaphoreGiveRecursive(mutex);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void HAL_ESP32::backgroundTask(void* pvParameter) {
|
|
os_runloop();
|
|
}
|
|
|
|
void hal_init_ex(const void *pContext)
|
|
{
|
|
ttn_hal.init();
|
|
}
|
|
|
|
void HAL_ESP32::init()
|
|
{
|
|
// configure radio I/O and interrupt handler
|
|
ioInit();
|
|
// configure radio SPI
|
|
spiInit();
|
|
// configure timer and interrupt handler
|
|
timerInit();
|
|
}
|
|
|
|
void HAL_ESP32::startBackgroundTask() {
|
|
xTaskCreate(backgroundTask, "ttn_lora_task", 1024 * 4, NULL, CONFIG_TTN_BG_TASK_PRIO, NULL);
|
|
}
|
|
|
|
void hal_failed(const char *file, u2_t line)
|
|
{
|
|
ESP_LOGE(TAG, "%s:%d", file, line);
|
|
ASSERT(0);
|
|
}
|
|
|
|
uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency) {
|
|
return LMICHAL_radio_tx_power_policy_paboost;
|
|
}
|