2021-07-25 20:03:16 +02:00
/*******************************************************************************
*
* 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 "esp_timer.h"
# include "esp_log.h"
2021-09-28 17:35:05 +02:00
# include <time.h>
# include <sys/time.h>
2021-07-25 20:03:16 +02:00
# 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 {
2021-09-26 20:07:57 +02:00
WAIT_KIND_NONE = 0 ,
2021-07-25 20:03:16 +02:00
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 ) ;
2021-09-28 17:35:05 +02:00
static int64_t get_current_time ( ) ;
2021-07-25 20:03:16 +02:00
static void init_io ( void ) ;
static void init_spi ( void ) ;
2022-04-15 12:17:26 +02:00
static void assert_nss ( spi_transaction_t * trans ) ;
static void deassert_nss ( spi_transaction_t * trans ) ;
2021-07-25 20:03:16 +02:00
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 ;
2021-09-28 17:35:05 +02:00
static int64_t time_offset ;
2021-09-29 16:48:59 +02:00
static int32_t initial_time_offset ;
2021-07-25 20:03:16 +02:00
static int64_t next_alarm ;
static volatile bool run_background_task ;
2021-09-26 20:07:57 +02:00
static volatile wait_kind_e current_wait_kind ;
2021-07-25 20:03:16 +02:00
// -----------------------------------------------------------------------------
// 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 )
{
2022-03-26 20:06:03 +01:00
// pin_nss, pin_dio0 and pin_dio1 are required
2021-07-25 20:03:16 +02:00
ASSERT ( pin_nss ! = LMIC_UNUSED_PIN ) ;
ASSERT ( pin_dio0 ! = LMIC_UNUSED_PIN ) ;
ASSERT ( pin_dio1 ! = LMIC_UNUSED_PIN ) ;
2022-03-26 20:06:03 +01:00
gpio_config_t output_pin_config = {
. pin_bit_mask = BIT64 ( pin_nss ) ,
. mode = GPIO_MODE_OUTPUT ,
. pull_up_en = false ,
. pull_down_en = false ,
. intr_type = GPIO_INTR_DISABLE
} ;
2021-07-25 20:03:16 +02:00
if ( pin_rx_tx ! = LMIC_UNUSED_PIN )
2022-03-26 20:06:03 +01:00
output_pin_config . pin_bit_mask | = BIT64 ( pin_rx_tx ) ;
2021-07-25 20:03:16 +02:00
if ( pin_rst ! = LMIC_UNUSED_PIN )
2022-03-26 20:06:03 +01:00
output_pin_config . pin_bit_mask | = BIT64 ( pin_rst ) ;
gpio_config ( & output_pin_config ) ;
gpio_set_level ( pin_nss , 1 ) ;
if ( pin_rx_tx ! = LMIC_UNUSED_PIN )
gpio_set_level ( pin_rx_tx , 0 ) ;
if ( pin_rst ! = LMIC_UNUSED_PIN )
2021-07-25 20:03:16 +02:00
gpio_set_level ( pin_rst , 0 ) ;
// DIO pins with interrupt handlers
2022-03-26 20:06:03 +01:00
gpio_config_t input_pin_config = {
. pin_bit_mask = BIT64 ( pin_dio0 ) | BIT64 ( pin_dio1 ) ,
. mode = GPIO_MODE_INPUT ,
. pull_up_en = false ,
. pull_down_en = true ,
. intr_type = GPIO_INTR_POSEDGE ,
} ;
gpio_config ( & input_pin_config ) ;
2021-07-25 20:03:16 +02:00
ESP_LOGI ( TAG , " IO initialized " ) ;
}
2023-04-25 09:41:37 +02:00
__attribute__ ( ( weak ) ) // duplicate this symbol if your pin is controlled by e.g. I2C
2021-07-25 20:03:16 +02:00
void hal_pin_rxtx ( u1_t val )
{
if ( pin_rx_tx = = LMIC_UNUSED_PIN )
return ;
gpio_set_level ( pin_rx_tx , val ) ;
}
2023-04-25 09:41:37 +02:00
__attribute__ ( ( weak ) ) // duplicate this symbol if your pin is controlled by e.g. I2C
2021-07-25 20:03:16 +02:00
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_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 = {
2022-03-26 20:06:03 +01:00
. mode = 0 ,
2021-07-25 20:03:16 +02:00
. clock_speed_hz = CONFIG_TTN_SPI_FREQ ,
. command_bits = 0 ,
. address_bits = 8 ,
2022-03-26 20:06:03 +01:00
. spics_io_num = - 1 ,
2021-07-25 20:03:16 +02:00
. queue_size = 1 ,
2022-04-15 12:17:26 +02:00
. pre_cb = assert_nss ,
. post_cb = deassert_nss ,
2021-07-25 20:03:16 +02:00
} ;
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 ) ;
2022-04-15 12:17:26 +02:00
}
2023-04-25 09:41:37 +02:00
2022-04-15 12:17:26 +02:00
void IRAM_ATTR assert_nss ( spi_transaction_t * trans )
{
gpio_set_level ( pin_nss , 0 ) ;
}
2022-03-26 20:06:03 +01:00
2022-04-15 12:17:26 +02:00
void IRAM_ATTR deassert_nss ( spi_transaction_t * trans )
{
2022-03-26 20:06:03 +01:00
gpio_set_level ( pin_nss , 1 ) ;
2021-07-25 20:03:16 +02:00
}
// -----------------------------------------------------------------------------
// TIME
/*
* LIMIC uses a 32 bit time system ( ostime_t ) counting ticks . In this
2021-09-28 17:35:05 +02:00
* implementation , each tick is 16 µ s . It will wrap arounnd every 19 hours .
2021-07-25 20:03:16 +02:00
*
* The ESP32 has a 64 bit timer counting microseconds . It will wrap around
* every 584 , 000 years . So we don ' t need to bother .
*
2021-09-28 17:35:05 +02:00
* The time includes an offset initialized from ` gettimeofday ( ) `
* to ensure the time correctly advances during deep sleep .
*
2021-07-25 20:03:16 +02:00
* 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
{
2021-09-28 17:35:05 +02:00
os_diff = - os_diff ;
2021-07-25 20:03:16 +02:00
esp_time = esp_now - ( ( ( int64_t ) os_diff ) < < 4 ) ;
}
return esp_time ;
}
2023-08-07 19:55:31 +02:00
int64_t IRAM_ATTR get_current_time ( )
2021-09-28 17:35:05 +02:00
{
return esp_timer_get_time ( ) + time_offset ;
}
2021-07-25 20:03:16 +02:00
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 ) ;
2021-09-28 17:35:05 +02:00
struct timeval now ;
gettimeofday ( & now , NULL ) ;
2021-09-29 16:48:59 +02:00
time_offset + = ( int64_t ) now . tv_sec * 1000000 ;
initial_time_offset = 0 ;
2021-09-28 17:35:05 +02:00
2021-07-25 20:03:16 +02:00
ESP_LOGI ( TAG , " Timer initialized " ) ;
}
2021-09-29 16:48:59 +02:00
uint32_t hal_esp32_get_time ( void )
{
struct timeval now ;
gettimeofday ( & now , NULL ) ;
return now . tv_sec + initial_time_offset ;
}
void hal_esp32_set_time ( uint32_t time_val )
{
initial_time_offset = time_val ;
time_offset = ( int64_t ) time_val * 1000000 ;
}
2021-07-25 20:03:16 +02:00
void set_next_alarm ( int64_t time )
{
next_alarm = time ;
}
void arm_timer ( int64_t esp_now )
{
if ( next_alarm = = 0 )
return ;
2021-09-28 17:35:05 +02:00
int64_t timeout = next_alarm - get_current_time ( ) ;
2021-07-25 20:03:16 +02:00
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 )
{
2021-09-26 20:07:57 +02:00
current_wait_kind = wait_kind ;
2021-07-25 20:03:16 +02:00
uint32_t bits = ulTaskNotifyTake ( pdTRUE , ticks_to_wait ) ;
2021-09-26 20:07:57 +02:00
current_wait_kind = WAIT_KIND_NONE ;
2021-07-25 20:03:16 +02:00
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 ;
}
}
}
2021-09-26 20:07:57 +02:00
TickType_t hal_esp32_get_timer_duration ( void )
{
wait_kind_e wait_kind = current_wait_kind ;
int64_t alarm_time = next_alarm ;
if ( wait_kind = = WAIT_KIND_NONE | | wait_kind = = WAIT_KIND_CHECK_IO )
return 1 ; // busy, not waiting
if ( alarm_time ! = 0 )
2021-09-28 22:38:05 +02:00
{
TickType_t dur = pdMS_TO_TICKS ( ( alarm_time - get_current_time ( ) + 999 ) / 1000 ) ;
if ( dur > pdMS_TO_TICKS ( 30000 ) )
dur = pdMS_TO_TICKS ( 200 ) ;
return dur ;
}
2021-09-26 20:07:57 +02:00
return 0 ; // waiting indefinitely
}
2021-07-25 20:03:16 +02:00
// Gets current time in LMIC ticks
u4_t IRAM_ATTR hal_ticks ( void )
{
// LMIC tick unit: 16µs
// esp_timer unit: 1µs
2021-09-28 17:35:05 +02:00
return ( u4_t ) ( get_current_time ( ) > > 4 ) ;
2021-07-25 20:03:16 +02:00
}
// 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 )
{
2021-09-28 17:35:05 +02:00
int64_t esp_now = get_current_time ( ) ;
2021-07-25 20:03:16 +02:00
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 )
{
2021-09-28 17:35:05 +02:00
int64_t esp_now = get_current_time ( ) ;
2021-07-25 20:03:16 +02:00
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 ;
2021-09-28 17:35:05 +02:00
arm_timer ( get_current_time ( ) ) ;
2021-07-25 20:03:16 +02:00
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 ( ) ;
2021-09-28 10:14:32 +02:00
set_next_alarm ( 0 ) ;
2021-07-25 20:03:16 +02:00
xTaskNotify ( lmic_task , NOTIFY_BIT_STOP , eSetBits ) ;
2021-09-28 10:14:32 +02:00
lmic_task = xTaskGetCurrentTaskHandle ( ) ;
2021-07-25 20:03:16 +02:00
}
// -----------------------------------------------------------------------------
// Fatal failure
static hal_failure_handler_t * custom_hal_failure_handler = NULL ;
2021-07-27 22:08:00 +02:00
void hal_set_failure_handler ( hal_failure_handler_t * const handler )
2021-07-25 20:03:16 +02:00
{
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 ;
}