diff --git a/.vscode/settings.json b/.vscode/settings.json index 050206a..532e202 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,10 @@ "oslmic.h": "c", "hal_esp32.h": "c", "esp_log.h": "c", - "nvs_flash.h": "c" + "nvs_flash.h": "c", + "__config": "c", + "__nullptr": "c", + "stdint.h": "c", + "*.ipp": "c" } } \ No newline at end of file diff --git a/src/TheThingsNetwork.cpp b/src/TheThingsNetwork.cpp index 6f575f8..a2d8356 100644 --- a/src/TheThingsNetwork.cpp +++ b/src/TheThingsNetwork.cpp @@ -82,7 +82,7 @@ TheThingsNetwork::TheThingsNetwork() ASSERT(ttnInstance == nullptr); ttnInstance = this; - ttn_hal.initCriticalSection(); + hal_esp32_init_critical_section(); } TheThingsNetwork::~TheThingsNetwork() @@ -92,7 +92,7 @@ TheThingsNetwork::~TheThingsNetwork() void TheThingsNetwork::configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1) { - ttn_hal.configurePins(spi_host, nss, rxtx, rst, dio0, dio1); + hal_esp32_configure_pins(spi_host, nss, rxtx, rst, dio0, dio1); #if LMIC_ENABLE_event_logging logging = TTNLogging::initInstance(); @@ -106,33 +106,33 @@ void TheThingsNetwork::configurePins(spi_host_device_t spi_host, uint8_t nss, ui lmicEventQueue = xQueueCreate(4, sizeof(TTNLmicEvent)); ASSERT(lmicEventQueue != nullptr); - ttn_hal.startLMICTask(); + hal_esp32_start_lmic_task(); } void TheThingsNetwork::reset() { - ttn_hal.enterCriticalSection(); + hal_esp32_enter_critical_section(); LMIC_reset(); LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100); waitingReason = eWaitingNone; - ttn_hal.leaveCriticalSection(); + hal_esp32_leave_critical_section(); } void TheThingsNetwork::shutdown() { - ttn_hal.enterCriticalSection(); + hal_esp32_enter_critical_section(); LMIC_shutdown(); - ttn_hal.stopLMICTask(); + hal_esp32_stop_lmic_task(); waitingReason = eWaitingNone; - ttn_hal.leaveCriticalSection(); + hal_esp32_leave_critical_section(); } void TheThingsNetwork::startup() { - ttn_hal.enterCriticalSection(); + hal_esp32_enter_critical_section(); LMIC_reset(); - ttn_hal.startLMICTask(); - ttn_hal.leaveCriticalSection(); + hal_esp32_start_lmic_task(); + hal_esp32_leave_critical_section(); } bool TheThingsNetwork::provision(const char *devEui, const char *appEui, const char *appKey) @@ -210,12 +210,12 @@ bool TheThingsNetwork::joinCore() return false; } - ttn_hal.enterCriticalSection(); + hal_esp32_enter_critical_section(); xQueueReset(lmicEventQueue); waitingReason = eWaitingForJoin; LMIC_startJoining(); - ttn_hal.wakeUp(); - ttn_hal.leaveCriticalSection(); + hal_esp32_wake_up(); + hal_esp32_leave_critical_section(); TTNLmicEvent event; xQueueReceive(lmicEventQueue, &event, portMAX_DELAY); @@ -224,10 +224,10 @@ bool TheThingsNetwork::joinCore() TTNResponseCode TheThingsNetwork::transmitMessage(const uint8_t *payload, size_t length, port_t port, bool confirm) { - ttn_hal.enterCriticalSection(); + hal_esp32_enter_critical_section(); if (waitingReason != eWaitingNone || (LMIC.opmode & OP_TXRXPEND) != 0) { - ttn_hal.leaveCriticalSection(); + hal_esp32_leave_critical_section(); return kTTNErrorTransmissionFailed; } @@ -235,8 +235,8 @@ TTNResponseCode TheThingsNetwork::transmitMessage(const uint8_t *payload, size_t LMIC.client.txMessageCb = messageTransmittedCallback; LMIC.client.txMessageUserData = nullptr; LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm); - ttn_hal.wakeUp(); - ttn_hal.leaveCriticalSection(); + hal_esp32_wake_up(); + hal_esp32_leave_critical_section(); while (true) { @@ -280,7 +280,7 @@ bool TheThingsNetwork::isProvisioned() void TheThingsNetwork::setRSSICal(int8_t rssiCal) { - ttn_hal.rssiCal = rssiCal; + hal_esp32_set_rssi_cal(rssiCal); } bool TheThingsNetwork::adrEnabled() diff --git a/src/hal/hal_esp32.c b/src/hal/hal_esp32.c new file mode 100755 index 0000000..cf73b66 --- /dev/null +++ b/src/hal/hal_esp32.c @@ -0,0 +1,538 @@ +/******************************************************************************* + * + * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x + * + * Copyright (c) 2018-2019 Manuel Bleichenbacher + * + * Licensed under MIT License + * https://opensource.org/licenses/MIT + * + * Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF. + *******************************************************************************/ + +#include "hal_esp32.h" +#include "../lmic/lmic.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "driver/timer.h" +#include "esp_timer.h" +#include "esp_log.h" + +#define LMIC_UNUSED_PIN 0xff + +#define NOTIFY_BIT_DIO 1 +#define NOTIFY_BIT_TIMER 2 +#define NOTIFY_BIT_WAKEUP 4 +#define NOTIFY_BIT_STOP 8 + + +#define TAG "ttn_hal" + + +typedef enum { + WAIT_KIND_CHECK_IO, + WAIT_KIND_WAIT_FOR_ANY_EVENT, + WAIT_KIND_WAIT_FOR_TIMER +} wait_kind_e; + + +static void lmic_background_task(void* pvParameter); +static void qio_irq_handler(void* arg); +static void timer_callback(void *arg); +static int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time); + +static void init_io(void); +static void init_spi(void); +static void init_timer(void); + +static void set_next_alarm(int64_t time); +static void arm_timer(int64_t esp_now); +static void disarm_timer(void); +static bool wait(wait_kind_e wait_kind); + +static spi_host_device_t spi_host; +static gpio_num_t pin_nss; +static gpio_num_t pin_rx_tx; +static gpio_num_t pin_rst; +static gpio_num_t pin_dio0; +static gpio_num_t pin_dio1; +static int8_t rssi_cal = 10; + + +static TaskHandle_t lmic_task; +static uint32_t dio_interrupt_time; +static uint8_t dio_num; + +static spi_device_handle_t spi_handle; +static spi_transaction_t spi_transaction; +static SemaphoreHandle_t mutex; +static esp_timer_handle_t timer; +static int64_t next_alarm; +static volatile bool run_background_task; + + +// ----------------------------------------------------------------------------- +// I/O + +void hal_esp32_configure_pins(spi_host_device_t host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1) +{ + spi_host = host; + pin_nss = (gpio_num_t)nss; + pin_rx_tx = (gpio_num_t)rxtx; + pin_rst = (gpio_num_t)rst; + pin_dio0 = (gpio_num_t)dio0; + pin_dio1 = (gpio_num_t)dio1; + + // Until the background process has been started, use the current task + // for supporting calls like `hal_waitUntil()`. + lmic_task = xTaskGetCurrentTaskHandle(); +} + + +void IRAM_ATTR qio_irq_handler(void *arg) +{ + dio_interrupt_time = hal_ticks(); + dio_num = (u1_t)(long)arg; + BaseType_t higher_prio_task_woken = pdFALSE; + xTaskNotifyFromISR(lmic_task, NOTIFY_BIT_DIO, eSetBits, &higher_prio_task_woken); + if (higher_prio_task_woken) + portYIELD_FROM_ISR(); +} + +void init_io(void) +{ + // pin_nss and pin_dio0 and pin_dio1 are required + ASSERT(pin_nss != LMIC_UNUSED_PIN); + ASSERT(pin_dio0 != LMIC_UNUSED_PIN); + ASSERT(pin_dio1 != LMIC_UNUSED_PIN); + + gpio_pad_select_gpio(pin_nss); + gpio_set_level(pin_nss, 0); + gpio_set_direction(pin_nss, GPIO_MODE_OUTPUT); + + if (pin_rx_tx != LMIC_UNUSED_PIN) + { + gpio_pad_select_gpio(pin_rx_tx); + gpio_set_level(pin_rx_tx, 0); + gpio_set_direction(pin_rx_tx, GPIO_MODE_OUTPUT); + } + + if (pin_rst != LMIC_UNUSED_PIN) + { + gpio_pad_select_gpio(pin_rst); + gpio_set_level(pin_rst, 0); + gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT); + } + + // DIO pins with interrupt handlers + gpio_pad_select_gpio(pin_dio0); + gpio_set_direction(pin_dio0, GPIO_MODE_INPUT); + gpio_set_intr_type(pin_dio0, GPIO_INTR_POSEDGE); + + gpio_pad_select_gpio(pin_dio1); + gpio_set_direction(pin_dio1, GPIO_MODE_INPUT); + gpio_set_intr_type(pin_dio1, GPIO_INTR_POSEDGE); + + ESP_LOGI(TAG, "IO initialized"); +} + +void hal_pin_rxtx(u1_t val) +{ + if (pin_rx_tx == LMIC_UNUSED_PIN) + return; + + gpio_set_level(pin_rx_tx, val); +} + +void hal_pin_rst(u1_t val) +{ + if (pin_rst == LMIC_UNUSED_PIN) + return; + + if (val == 0 || val == 1) + { + // drive pin + gpio_set_level(pin_rst, val); + gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT); + } + else + { +#if defined(CONFIG_TTN_RESET_STATES_ASSERTED) + // drive up the pin because the hardware is nonstandard + gpio_set_level(pin_rst, 1); + gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT); +#else + // keep pin floating + gpio_set_level(pin_rst, val); + gpio_set_direction(pin_rst, GPIO_MODE_INPUT); +#endif + } +} + +s1_t hal_getRssiCal(void) +{ + return rssi_cal; +} + +ostime_t hal_setModuleActive(bit_t val) +{ + return 0; +} + +bit_t hal_queryUsingTcxo(void) +{ + return false; +} + +uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency) +{ + return LMICHAL_radio_tx_power_policy_paboost; +} + + + +// ----------------------------------------------------------------------------- +// SPI + +void init_spi(void) +{ + // init device + spi_device_interface_config_t spi_config = { + .mode = 1, + .clock_speed_hz = CONFIG_TTN_SPI_FREQ, + .command_bits = 0, + .address_bits = 8, + .spics_io_num = pin_nss, + .queue_size = 1, + .cs_ena_posttrans = 2 + }; + + esp_err_t ret = spi_bus_add_device(spi_host, &spi_config, &spi_handle); + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "SPI initialized"); +} + +void hal_spi_write(u1_t cmd, const u1_t *buf, size_t len) +{ + memset(&spi_transaction, 0, sizeof(spi_transaction)); + spi_transaction.addr = cmd; + spi_transaction.length = 8 * len; + spi_transaction.tx_buffer = buf; + esp_err_t err = spi_device_transmit(spi_handle, &spi_transaction); + ESP_ERROR_CHECK(err); +} + +void hal_spi_read(u1_t cmd, u1_t *buf, size_t len) +{ + memset(buf, 0, len); + memset(&spi_transaction, 0, sizeof(spi_transaction)); + spi_transaction.addr = cmd; + spi_transaction.length = 8 * len; + spi_transaction.rxlength = 8 * len; + spi_transaction.tx_buffer = buf; + spi_transaction.rx_buffer = buf; + esp_err_t err = spi_device_transmit(spi_handle, &spi_transaction); + ESP_ERROR_CHECK(err); +} + + +// ----------------------------------------------------------------------------- +// TIME + +/* + * LIMIC uses a 32 bit time system (ostime_t) counting ticks. In this + * implementation each tick is 16µs. It will wrap arounnd every 19 hours. + * + * The ESP32 has a 64 bit timer counting microseconds. It will wrap around + * every 584,000 years. So we don't need to bother. + * + * Based on this timer, future callbacks can be scheduled. This is used to + * schedule the next LMIC job. + */ + +// Convert LMIC tick time (ostime_t) to ESP absolute time. +// `os_time` is assumed to be somewhere between one hour in the past and +// 18 hours into the future. +int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time) +{ + int64_t esp_time; + uint32_t os_now = (uint32_t)(esp_now >> 4); + + // unsigned difference: + // 0x00000000 - 0xefffffff: future (0 to about 18 hours) + // 0xf0000000 - 0xffffffff: past (about 1 to 0 hours) + uint32_t os_diff = os_time - os_now; + if (os_diff < 0xf0000000) + { + esp_time = esp_now + (((int64_t)os_diff) << 4); + } + else + { + // one's complement instead of two's complement: + // off by 1 µs and ignored + os_diff = ~os_diff; + esp_time = esp_now - (((int64_t)os_diff) << 4); + } + + return esp_time; +} + +void init_timer(void) +{ + esp_timer_create_args_t timer_config = { + .callback = &timer_callback, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "lmic_job" + }; + esp_err_t err = esp_timer_create(&timer_config, &timer); + ESP_ERROR_CHECK(err); + + ESP_LOGI(TAG, "Timer initialized"); +} + +void set_next_alarm(int64_t time) +{ + next_alarm = time; +} + +void arm_timer(int64_t esp_now) +{ + if (next_alarm == 0) + return; + int64_t timeout = next_alarm - esp_timer_get_time(); + if (timeout < 0) + timeout = 10; + esp_timer_start_once(timer, timeout); +} + +void disarm_timer(void) +{ + esp_timer_stop(timer); +} + +void timer_callback(void *arg) +{ + xTaskNotify(lmic_task, NOTIFY_BIT_TIMER, eSetBits); +} + +// Wait for the next external event. Either: +// - scheduled timer due to scheduled job or waiting for a given time +// - wake up event from the client code +// - I/O interrupt (DIO0 or DIO1 pin) +bool wait(wait_kind_e wait_kind) +{ + TickType_t ticks_to_wait = wait_kind == WAIT_KIND_CHECK_IO ? 0 : portMAX_DELAY; + while (true) + { + uint32_t bits = ulTaskNotifyTake(pdTRUE, ticks_to_wait); + if (bits == 0) + return false; + + if ((bits & NOTIFY_BIT_STOP) != 0) + return false; + + if ((bits & NOTIFY_BIT_WAKEUP) != 0) + { + if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER) + { + disarm_timer(); + return true; + } + } + else if ((bits & NOTIFY_BIT_TIMER) != 0) + { + disarm_timer(); + set_next_alarm(0); + if (wait_kind != WAIT_KIND_CHECK_IO) + return true; + } + else // IO interrupt + { + if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER) + disarm_timer(); + hal_esp32_enter_critical_section(); + radio_irq_handler_v2(dio_num, dio_interrupt_time); + hal_esp32_leave_critical_section(); + if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER) + return true; + } + } +} + +// Gets current time in LMIC ticks +u4_t IRAM_ATTR hal_ticks(void) +{ + // LMIC tick unit: 16µs + // esp_timer unit: 1µs + return (u4_t)(esp_timer_get_time() >> 4); +} + +// Wait until the specified time. +// Called if the LMIC code needs to wait for a precise time. +// All other events are ignored and will be served later. +u4_t hal_waitUntil(u4_t time) +{ + int64_t esp_now = esp_timer_get_time(); + int64_t esp_time = os_time_to_esp_time(esp_now, time); + set_next_alarm(esp_time); + arm_timer(esp_now); + wait(WAIT_KIND_WAIT_FOR_TIMER); + + u4_t os_now = hal_ticks(); + u4_t diff = os_now - time; + return diff < 0x80000000U ? diff : 0; +} + +// Called by client code to wake up LMIC to do something, +// e.g. send a submitted messages. +void hal_esp32_wake_up(void) +{ + xTaskNotify(lmic_task, NOTIFY_BIT_WAKEUP, eSetBits); +} + +// Check if the specified time has been reached or almost reached. +// Otherwise, save it as alarm time. +// LMIC calls this function with the scheduled time of the next job +// in the queue. If the job is not due yet, LMIC will go to sleep. +u1_t hal_checkTimer(uint32_t time) +{ + int64_t esp_now = esp_timer_get_time(); + int64_t esp_time = os_time_to_esp_time(esp_now, time); + int64_t diff = esp_time - esp_now; + if (diff < 100) + return 1; // timer has expired or will expire very soon + + set_next_alarm(esp_time); + return 0; +} + +// Go to sleep until next event. +// Called when LMIC is not busy and not job is due to be executed. +void hal_sleep(void) +{ + if (wait(WAIT_KIND_CHECK_IO)) + return; + + arm_timer(esp_timer_get_time()); + wait(WAIT_KIND_WAIT_FOR_ANY_EVENT); +} + + +// ----------------------------------------------------------------------------- +// IRQ + +void hal_disableIRQs(void) +{ + // nothing to do as interrupt handlers post message to queue + // and don't access any shared data structures +} + +void hal_enableIRQs(void) +{ + // nothing to do as interrupt handlers post message to queue + // and don't access any shared data structures +} + +void hal_processPendingIRQs(void) +{ + // 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_init_critical_section(void) +{ + mutex = xSemaphoreCreateRecursiveMutex(); +} + +void hal_esp32_enter_critical_section(void) +{ + xSemaphoreTakeRecursive(mutex, portMAX_DELAY); +} + +void hal_esp32_leave_critical_section(void) +{ + xSemaphoreGiveRecursive(mutex); +} + +// ----------------------------------------------------------------------------- + +void lmic_background_task(void* pvParameter) +{ + while (run_background_task) + os_runloop_once(); + vTaskDelete(NULL); +} + +void hal_init_ex(const void *pContext) +{ + // configure radio I/O and interrupt handler + init_io(); + // configure radio SPI + init_spi(); + // configure timer and alarm callback + init_timer(); +} + +void hal_esp32_start_lmic_task(void) +{ + run_background_task = true; + xTaskCreate(lmic_background_task, "ttn_lmic", 1024 * 4, NULL, CONFIG_TTN_BG_TASK_PRIO, &lmic_task); + + // enable interrupts + gpio_isr_handler_add(pin_dio0, qio_irq_handler, (void *)0); + gpio_isr_handler_add(pin_dio1, qio_irq_handler, (void *)1); +} + +void hal_esp32_stop_lmic_task(void) +{ + run_background_task = false; + gpio_isr_handler_remove(pin_dio0); + gpio_isr_handler_remove(pin_dio1); + disarm_timer(); + xTaskNotify(lmic_task, NOTIFY_BIT_STOP, eSetBits); +} + + +// ----------------------------------------------------------------------------- +// Fatal failure + +static hal_failure_handler_t* custom_hal_failure_handler = NULL; + +void hal_set_failure_handler(const hal_failure_handler_t* const handler) +{ + custom_hal_failure_handler = handler; +} + +void hal_failed(const char *file, u2_t line) +{ + if (custom_hal_failure_handler != NULL) + (*custom_hal_failure_handler)(file, line); + + ESP_LOGE(TAG, "LMIC failed and stopped: %s:%d", file, line); + + // go to sleep forever + while (true) + { + vTaskDelay(portMAX_DELAY); + } +} + + +// ----------------------------------------------------------------------------- +// RSSI + +void hal_esp32_set_rssi_cal(int8_t cal) +{ + rssi_cal = cal; +} diff --git a/src/hal/hal_esp32.cpp b/src/hal/hal_esp32.cpp deleted file mode 100755 index 21367b9..0000000 --- a/src/hal/hal_esp32.cpp +++ /dev/null @@ -1,529 +0,0 @@ -/******************************************************************************* - * - * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x - * - * Copyright (c) 2018-2019 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 - -#define NOTIFY_BIT_DIO 1 -#define NOTIFY_BIT_TIMER 2 -#define NOTIFY_BIT_WAKEUP 4 -#define NOTIFY_BIT_STOP 8 - - -static const char* const TAG = "ttn_hal"; - -HAL_ESP32 ttn_hal; - -TaskHandle_t HAL_ESP32::lmicTask = nullptr; -uint32_t HAL_ESP32::dioInterruptTime = 0; -uint8_t HAL_ESP32::dioNum = 0; - - -// ----------------------------------------------------------------------------- -// Constructor - -HAL_ESP32::HAL_ESP32() - : rssiCal(10), nextAlarm(0) -{ -} - -// ----------------------------------------------------------------------------- -// I/O - -void HAL_ESP32::configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1) -{ - spiHost = spi_host; - pinNSS = (gpio_num_t)nss; - pinRxTx = (gpio_num_t)rxtx; - pinRst = (gpio_num_t)rst; - pinDIO0 = (gpio_num_t)dio0; - pinDIO1 = (gpio_num_t)dio1; - - // Until the background process has been started, use the current task - // for supporting calls like `hal_waitUntil()`. - lmicTask = xTaskGetCurrentTaskHandle(); -} - - -void IRAM_ATTR HAL_ESP32::dioIrqHandler(void *arg) -{ - dioInterruptTime = hal_ticks(); - dioNum = (u1_t)(long)arg; - BaseType_t higherPrioTaskWoken = pdFALSE; - xTaskNotifyFromISR(lmicTask, NOTIFY_BIT_DIO, eSetBits, &higherPrioTaskWoken); - if (higherPrioTaskWoken) - portYIELD_FROM_ISR(); -} - -void HAL_ESP32::ioInit() -{ - // pinNSS and pinDIO0 and pinDIO1 are required - ASSERT(pinNSS != LMIC_UNUSED_PIN); - ASSERT(pinDIO0 != LMIC_UNUSED_PIN); - ASSERT(pinDIO1 != LMIC_UNUSED_PIN); - - gpio_pad_select_gpio(pinNSS); - gpio_set_level(pinNSS, 0); - gpio_set_direction(pinNSS, GPIO_MODE_OUTPUT); - - if (pinRxTx != LMIC_UNUSED_PIN) - { - gpio_pad_select_gpio(pinRxTx); - gpio_set_level(pinRxTx, 0); - gpio_set_direction(pinRxTx, GPIO_MODE_OUTPUT); - } - - if (pinRst != LMIC_UNUSED_PIN) - { - gpio_pad_select_gpio(pinRst); - gpio_set_level(pinRst, 0); - gpio_set_direction(pinRst, GPIO_MODE_OUTPUT); - } - - // DIO pins with interrupt handlers - gpio_pad_select_gpio(pinDIO0); - gpio_set_direction(pinDIO0, GPIO_MODE_INPUT); - gpio_set_intr_type(pinDIO0, GPIO_INTR_POSEDGE); - - gpio_pad_select_gpio(pinDIO1); - gpio_set_direction(pinDIO1, GPIO_MODE_INPUT); - gpio_set_intr_type(pinDIO1, GPIO_INTR_POSEDGE); - - ESP_LOGI(TAG, "IO initialized"); -} - -void hal_pin_rxtx(u1_t val) -{ - if (ttn_hal.pinRxTx == LMIC_UNUSED_PIN) - return; - - gpio_set_level(ttn_hal.pinRxTx, val); -} - -void hal_pin_rst(u1_t val) -{ - if (ttn_hal.pinRst == LMIC_UNUSED_PIN) - return; - - if (val == 0 || val == 1) - { - // drive pin - gpio_set_level(ttn_hal.pinRst, val); - gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_OUTPUT); - } - else - { -#if defined(CONFIG_TTN_RESET_STATES_ASSERTED) - // drive up the pin because the hardware is nonstandard - gpio_set_level(ttn_hal.pinRst, 1); - gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_OUTPUT); -#else - // keep pin floating - gpio_set_level(ttn_hal.pinRst, val); - gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_INPUT); -#endif - } -} - -s1_t hal_getRssiCal (void) -{ - return ttn_hal.rssiCal; -} - -ostime_t hal_setModuleActive (bit_t val) -{ - return 0; -} - -bit_t hal_queryUsingTcxo(void) -{ - return false; -} - -uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency) -{ - return LMICHAL_radio_tx_power_policy_paboost; -} - - - -// ----------------------------------------------------------------------------- -// 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 = pinNSS; - spiConfig.queue_size = 1; - spiConfig.cs_ena_posttrans = 2; - - esp_err_t ret = spi_bus_add_device(spiHost, &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 system (ostime_t) counting ticks. In this - * implementation each tick is 16µs. It will wrap arounnd every 19 hours. - * - * The ESP32 has a 64 bit timer counting microseconds. It will wrap around - * every 584,000 years. So we don't need to bother. - * - * Based on this timer, future callbacks can be scheduled. This is used to - * schedule the next LMIC job. - */ - -// Convert LMIC tick time (ostime_t) to ESP absolute time. -// `osTime` is assumed to be somewhere between one hour in the past and -// 18 hours into the future. -int64_t HAL_ESP32::osTimeToEspTime(int64_t espNow, uint32_t osTime) -{ - int64_t espTime; - uint32_t osNow = (uint32_t)(espNow >> 4); - - // unsigned difference: - // 0x00000000 - 0xefffffff: future (0 to about 18 hours) - // 0xf0000000 - 0xffffffff: past (about 1 to 0 hours) - uint32_t osDiff = osTime - osNow; - if (osDiff < 0xf0000000) - { - espTime = espNow + (((int64_t)osDiff) << 4); - } - else - { - // one's complement instead of two's complement: - // off by 1 µs and ignored - osDiff = ~osDiff; - espTime = espNow - (((int64_t)osDiff) << 4); - } - - return espTime; -} - -void HAL_ESP32::timerInit() -{ - esp_timer_create_args_t timerConfig = { - .callback = &timerCallback, - .arg = nullptr, - .dispatch_method = ESP_TIMER_TASK, - .name = "lmic_job" - }; - esp_err_t err = esp_timer_create(&timerConfig, &timer); - ESP_ERROR_CHECK(err); - - ESP_LOGI(TAG, "Timer initialized"); -} - -void HAL_ESP32::setNextAlarm(int64_t time) -{ - nextAlarm = time; -} - -void HAL_ESP32::armTimer(int64_t espNow) -{ - if (nextAlarm == 0) - return; - int64_t timeout = nextAlarm - esp_timer_get_time(); - if (timeout < 0) - timeout = 10; - esp_timer_start_once(timer, timeout); -} - -void HAL_ESP32::disarmTimer() -{ - esp_timer_stop(timer); -} - -void HAL_ESP32::timerCallback(void *arg) -{ - xTaskNotify(lmicTask, NOTIFY_BIT_TIMER, eSetBits); -} - -// Wait for the next external event. Either: -// - scheduled timer due to scheduled job or waiting for a given time -// - wake up event from the client code -// - I/O interrupt (DIO0 or DIO1 pin) -bool HAL_ESP32::wait(WaitKind waitKind) -{ - TickType_t ticksToWait = waitKind == CHECK_IO ? 0 : portMAX_DELAY; - while (true) - { - uint32_t bits = ulTaskNotifyTake(pdTRUE, ticksToWait); - if (bits == 0) - return false; - - if ((bits & NOTIFY_BIT_STOP) != 0) - return false; - - if ((bits & NOTIFY_BIT_WAKEUP) != 0) - { - if (waitKind != WAIT_FOR_TIMER) - { - disarmTimer(); - return true; - } - } - else if ((bits & NOTIFY_BIT_TIMER) != 0) - { - disarmTimer(); - setNextAlarm(0); - if (waitKind != CHECK_IO) - return true; - } - else // IO interrupt - { - if (waitKind != WAIT_FOR_TIMER) - disarmTimer(); - enterCriticalSection(); - radio_irq_handler_v2(dioNum, dioInterruptTime); - leaveCriticalSection(); - if (waitKind != WAIT_FOR_TIMER) - return true; - } - } -} - -// Gets current time in LMIC ticks -u4_t IRAM_ATTR hal_ticks() -{ - // LMIC tick unit: 16µs - // esp_timer unit: 1µs - return (u4_t)(esp_timer_get_time() >> 4); -} - -// Wait until the specified time. -// Called if the LMIC code needs to wait for a precise time. -// All other events are ignored and will be served later. -u4_t hal_waitUntil(u4_t time) -{ - return ttn_hal.waitUntil(time); -} - -uint32_t HAL_ESP32::waitUntil(uint32_t osTime) -{ - int64_t espNow = esp_timer_get_time(); - int64_t espTime = osTimeToEspTime(espNow, osTime); - setNextAlarm(espTime); - armTimer(espNow); - wait(WAIT_FOR_TIMER); - - u4_t osNow = hal_ticks(); - u4_t diff = osNow - osTime; - return diff < 0x80000000U ? diff : 0; -} - -// Called by client code to wake up LMIC to do something, -// e.g. send a submitted messages. -void HAL_ESP32::wakeUp() -{ - xTaskNotify(lmicTask, NOTIFY_BIT_WAKEUP, eSetBits); -} - -// Check if the specified time has been reached or almost reached. -// Otherwise, save it as alarm time. -// LMIC calls this function with the scheduled time of the next job -// in the queue. If the job is not due yet, LMIC will go to sleep. -u1_t hal_checkTimer(uint32_t time) -{ - return ttn_hal.checkTimer(time); -} - -uint8_t HAL_ESP32::checkTimer(u4_t osTime) -{ - int64_t espNow = esp_timer_get_time(); - int64_t espTime = osTimeToEspTime(espNow, osTime); - int64_t diff = espTime - espNow; - if (diff < 100) - return 1; // timer has expired or will expire very soon - - setNextAlarm(espTime); - return 0; -} - -// Go to sleep until next event. -// Called when LMIC is not busy and not job is due to be executed. -void hal_sleep() -{ - ttn_hal.sleep(); -} - -void HAL_ESP32::sleep() -{ - if (wait(CHECK_IO)) - return; - - armTimer(esp_timer_get_time()); - 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 -} - -void hal_processPendingIRQs() -{ - // 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::lmicBackgroundTask(void* pvParameter) -{ - HAL_ESP32* instance = (HAL_ESP32*)pvParameter; - while (instance->runBackgroundTask) - os_runloop_once(); - vTaskDelete(nullptr); -} - -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 alarm callback - timerInit(); -} - -void HAL_ESP32::startLMICTask() -{ - runBackgroundTask = true; - xTaskCreate(lmicBackgroundTask, "ttn_lmic", 1024 * 4, this, CONFIG_TTN_BG_TASK_PRIO, &lmicTask); - - // enable interrupts - gpio_isr_handler_add(pinDIO0, dioIrqHandler, (void *)0); - gpio_isr_handler_add(pinDIO1, dioIrqHandler, (void *)1); -} - -void HAL_ESP32::stopLMICTask() -{ - runBackgroundTask = false; - gpio_isr_handler_remove(pinDIO0); - gpio_isr_handler_remove(pinDIO1); - disarmTimer(); - xTaskNotify(lmicTask, NOTIFY_BIT_STOP, eSetBits); -} - - -// ----------------------------------------------------------------------------- -// Fatal failure - -static hal_failure_handler_t* custom_hal_failure_handler = nullptr; - -void hal_set_failure_handler(const hal_failure_handler_t* const handler) -{ - custom_hal_failure_handler = handler; -} - -void hal_failed(const char *file, u2_t line) -{ - if (custom_hal_failure_handler != nullptr) - (*custom_hal_failure_handler)(file, line); - - ESP_LOGE(TAG, "LMIC failed and stopped: %s:%d", file, line); - - // go to sleep forever - while (true) - { - vTaskDelay(portMAX_DELAY); - } -} diff --git a/src/hal/hal_esp32.h b/src/hal/hal_esp32.h index 4eee5fe..4278071 100644 --- a/src/hal/hal_esp32.h +++ b/src/hal/hal_esp32.h @@ -2,7 +2,7 @@ * * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x * - * Copyright (c) 2018-2019 Manuel Bleichenbacher + * Copyright (c) 2018-2021 Manuel Bleichenbacher * * Licensed under MIT License * https://opensource.org/licenses/MIT @@ -10,85 +10,33 @@ * Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF. *******************************************************************************/ -#ifndef _hal_esp32_h_ -#define _hal_esp32_h_ +#ifndef HAL_ESP32_H +#define HAL_ESP32_H -#include -#include -#include -#include -#include -#include -#include -#include +#include "freertos/FreeRTOS.h" +#include "driver/spi_master.h" -enum WaitKind { - CHECK_IO, - WAIT_FOR_ANY_EVENT, - WAIT_FOR_TIMER -}; +#ifdef __cplusplus +extern "C" { +#endif +void hal_esp32_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1); +void hal_esp32_start_lmic_task(void); +void hal_esp32_stop_lmic_task(void); -class HAL_ESP32 -{ -public: - HAL_ESP32(); +void hal_esp32_wake_up(void); +void hal_esp32_init_critical_section(void); +void hal_esp32_enter_critical_section(void); +void hal_esp32_leave_critical_section(void); - void configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1); - void init(); - void startLMICTask(); - void stopLMICTask(); - - void wakeUp(); - void initCriticalSection(); - void enterCriticalSection(); - void leaveCriticalSection(); - - void spiWrite(uint8_t cmd, const uint8_t *buf, size_t len); - void spiRead(uint8_t cmd, uint8_t *buf, size_t len); - uint8_t checkTimer(uint32_t osTime); - void sleep(); - - uint32_t waitUntil(uint32_t osTime); - - spi_host_device_t spiHost; - gpio_num_t pinNSS; - gpio_num_t pinRxTx; - gpio_num_t pinRst; - gpio_num_t pinDIO0; - gpio_num_t pinDIO1; - int8_t rssiCal; - -private: - static void lmicBackgroundTask(void* pvParameter); - static void dioIrqHandler(void* arg); - static void timerCallback(void *arg); - static int64_t osTimeToEspTime(int64_t espNow, uint32_t osTime); - - void ioInit(); - void spiInit(); - void timerInit(); - - void setNextAlarm(int64_t time); - void armTimer(int64_t espNow); - void disarmTimer(); - bool wait(WaitKind waitKind); - - static TaskHandle_t lmicTask; - static uint32_t dioInterruptTime; - static uint8_t dioNum; - - spi_device_handle_t spiHandle; - spi_transaction_t spiTransaction; - SemaphoreHandle_t mutex; - esp_timer_handle_t timer; - int64_t nextAlarm; - volatile bool runBackgroundTask; -}; - -extern HAL_ESP32 ttn_hal; +void hal_esp32_set_rssi_cal(int8_t rssi_cal); -#endif // _hal_esp32_h_ \ No newline at end of file +#ifdef __cplusplus +} +#endif + + +#endif // HAL_ESP32_H diff --git a/src/ttn_provisioning.c b/src/ttn_provisioning.c index f9b4656..9e6119f 100644 --- a/src/ttn_provisioning.c +++ b/src/ttn_provisioning.c @@ -18,7 +18,7 @@ #include "esp_system.h" #include "nvs_flash.h" #include "lmic/lmic.h" -//#include "hal/hal_esp32.h" +#include "hal/hal_esp32.h" #if defined(TTN_HAS_AT_COMMANDS) #define UART_NUM CONFIG_TTN_PROVISION_UART_NUM @@ -305,10 +305,9 @@ void process_line(void) if (reset_needed) { - // TODO - // ttn_hal.enterCriticalSection(); + hal_esp32_enter_critical_section(); LMIC_reset(); - // ttn_hal.leaveCriticalSection(); + hal_esp32_leave_critical_section(); LMIC.client.eventCb(LMIC.client.eventUserData, EV_RESET); }