mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-07-29 20:32:52 +02:00
Reorganize directories
This commit is contained in:
465
src/hal/hal_esp32.c
Executable file
465
src/hal/hal_esp32.c
Executable file
@ -0,0 +1,465 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* 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 *TAG = "ttn_hal";
|
||||
|
||||
lmic_pinmap lmic_pins;
|
||||
|
||||
typedef enum {
|
||||
DIO0 = 0,
|
||||
DIO1,
|
||||
DIO2,
|
||||
TIMER,
|
||||
WAKEUP
|
||||
} event_t;
|
||||
|
||||
typedef struct {
|
||||
ostime_t time;
|
||||
event_t ev;
|
||||
} queue_item_t;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// I/O
|
||||
|
||||
static QueueHandle_t dio_queue;
|
||||
|
||||
void IRAM_ATTR dio_irq_handler(void *arg)
|
||||
{
|
||||
uint64_t now;
|
||||
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &now);
|
||||
event_t ev = (long)arg;
|
||||
BaseType_t higher_prio_task_woken = pdFALSE;
|
||||
queue_item_t item = {
|
||||
.time = (ostime_t)now,
|
||||
.ev = ev
|
||||
};
|
||||
xQueueSendFromISR(dio_queue, &item, &higher_prio_task_woken);
|
||||
if (higher_prio_task_woken)
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
static void hal_io_init()
|
||||
{
|
||||
// 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(lmic_pins.nss, 0);
|
||||
gpio_set_direction(lmic_pins.nss, GPIO_MODE_OUTPUT);
|
||||
|
||||
if (lmic_pins.rxtx != LMIC_UNUSED_PIN)
|
||||
{
|
||||
gpio_pad_select_gpio(lmic_pins.rxtx);
|
||||
gpio_set_level(lmic_pins.rxtx, 0);
|
||||
gpio_set_direction(lmic_pins.rxtx, GPIO_MODE_OUTPUT);
|
||||
}
|
||||
|
||||
if (lmic_pins.rst != LMIC_UNUSED_PIN)
|
||||
{
|
||||
gpio_pad_select_gpio(lmic_pins.rst);
|
||||
gpio_set_level(lmic_pins.rst, 0);
|
||||
gpio_set_direction(lmic_pins.rst, GPIO_MODE_OUTPUT);
|
||||
}
|
||||
|
||||
dio_queue = xQueueCreate(12, sizeof(queue_item_t));
|
||||
ASSERT(dio_queue != NULL);
|
||||
|
||||
gpio_pad_select_gpio(lmic_pins.dio0);
|
||||
gpio_set_direction(lmic_pins.dio0, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type(lmic_pins.dio0, GPIO_INTR_POSEDGE);
|
||||
gpio_isr_handler_add(lmic_pins.dio0, dio_irq_handler, (void *)0);
|
||||
|
||||
gpio_pad_select_gpio(lmic_pins.dio1);
|
||||
gpio_set_direction(lmic_pins.dio1, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type(lmic_pins.dio1, GPIO_INTR_POSEDGE);
|
||||
gpio_isr_handler_add(lmic_pins.dio1, dio_irq_handler, (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(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(lmic_pins.rst, val);
|
||||
gpio_set_direction(lmic_pins.rst, GPIO_MODE_OUTPUT);
|
||||
}
|
||||
else
|
||||
{ // keep pin floating
|
||||
gpio_set_level(lmic_pins.rst, val);
|
||||
gpio_set_direction(lmic_pins.rst, GPIO_MODE_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SPI
|
||||
|
||||
#define SPI_QUEUE_SIZE 4
|
||||
#define SPI_NUM_TRX_SLOTS (SPI_QUEUE_SIZE + 1)
|
||||
|
||||
static spi_device_handle_t spi_handle;
|
||||
static spi_transaction_t spi_trx_queue[SPI_NUM_TRX_SLOTS];
|
||||
static int spi_trx_queue_head = 0;
|
||||
static int spi_num_outstanding_trx = 0;
|
||||
|
||||
static spi_transaction_t* get_next_spi_trx_desc()
|
||||
{
|
||||
spi_transaction_t* trx = spi_trx_queue + spi_trx_queue_head;
|
||||
memset(trx, 0, sizeof(spi_transaction_t));
|
||||
return trx;
|
||||
}
|
||||
|
||||
static void collect_spi_result()
|
||||
{
|
||||
int head = spi_trx_queue_head;
|
||||
int tail = head - spi_num_outstanding_trx;
|
||||
if (tail < 0)
|
||||
tail += SPI_NUM_TRX_SLOTS;
|
||||
|
||||
spi_transaction_t* trx;
|
||||
esp_err_t err = spi_device_get_trans_result(spi_handle, &trx, 100 / portTICK_PERIOD_MS);
|
||||
ESP_ERROR_CHECK(err);
|
||||
ASSERT(trx == spi_trx_queue + tail);
|
||||
spi_num_outstanding_trx--;
|
||||
}
|
||||
|
||||
static void submit_spi_trx()
|
||||
{
|
||||
if (spi_num_outstanding_trx >= SPI_QUEUE_SIZE)
|
||||
collect_spi_result();
|
||||
|
||||
int head = spi_trx_queue_head;
|
||||
esp_err_t err = spi_device_queue_trans(spi_handle, spi_trx_queue + head, 100 / portTICK_PERIOD_MS);
|
||||
ESP_ERROR_CHECK(err);
|
||||
spi_num_outstanding_trx++;
|
||||
|
||||
head++;
|
||||
if (head >= SPI_NUM_TRX_SLOTS)
|
||||
head = 0;
|
||||
spi_trx_queue_head = head;
|
||||
}
|
||||
|
||||
static void hal_spi_init()
|
||||
{
|
||||
// init device
|
||||
spi_device_interface_config_t spi_device_intf_config = {
|
||||
.mode = 1,
|
||||
.clock_speed_hz = CONFIG_TTN_SPI_FREQ,
|
||||
.command_bits = 0,
|
||||
.address_bits = 8,
|
||||
.spics_io_num = lmic_pins.nss,
|
||||
.queue_size = SPI_QUEUE_SIZE,
|
||||
.cs_ena_posttrans = 2
|
||||
};
|
||||
|
||||
esp_err_t ret = spi_bus_add_device(lmic_pins.spi_host, &spi_device_intf_config, &spi_handle);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ESP_LOGI(TAG, "SPI initialized");
|
||||
}
|
||||
|
||||
void hal_spi_write(u1_t cmd, const u1_t *buf, int len)
|
||||
{
|
||||
spi_transaction_t* trx = get_next_spi_trx_desc();
|
||||
trx->addr = cmd;
|
||||
trx->length = 8 * len;
|
||||
trx->tx_buffer = buf;
|
||||
submit_spi_trx();
|
||||
}
|
||||
|
||||
void hal_spi_read(u1_t cmd, u1_t *buf, int len)
|
||||
{
|
||||
memset(buf, 0, len);
|
||||
spi_transaction_t* trx = get_next_spi_trx_desc();
|
||||
trx->addr = cmd;
|
||||
trx->length = 8 * len;
|
||||
trx->rxlength = 8 * len;
|
||||
trx->tx_buffer = buf;
|
||||
trx->rx_buffer = buf;
|
||||
submit_spi_trx();
|
||||
|
||||
while (spi_num_outstanding_trx > 0)
|
||||
collect_spi_result();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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.
|
||||
*/
|
||||
|
||||
#define OVERRUN_TRESHOLD 0x10000 // approx 10 seconds
|
||||
|
||||
static uint64_t next_timer_event = 0x200000000;
|
||||
|
||||
static void IRAM_ATTR hal_timer_irq_handler(void *arg);
|
||||
|
||||
static void hal_time_init()
|
||||
{
|
||||
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, hal_timer_irq_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);
|
||||
timer_start(TTN_TIMER_GROUP, TTN_TIMER);
|
||||
|
||||
ESP_LOGI(TAG, "Timer initialized");
|
||||
}
|
||||
|
||||
static void hal_prepare_next_alarm(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);
|
||||
}
|
||||
|
||||
next_timer_event = time;
|
||||
if (now32 > time && now32 - time > OVERRUN_TRESHOLD)
|
||||
next_timer_event += 0x100000000;
|
||||
}
|
||||
|
||||
static void hal_arm_timer()
|
||||
{
|
||||
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_DIS);
|
||||
timer_set_alarm_value(TTN_TIMER_GROUP, TTN_TIMER, next_timer_event);
|
||||
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_EN);
|
||||
}
|
||||
|
||||
static void hal_disarm_timer()
|
||||
{
|
||||
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_DIS);
|
||||
next_timer_event = 0x200000000; // wait indefinitely (almost)
|
||||
}
|
||||
|
||||
static void IRAM_ATTR hal_timer_irq_handler(void *arg)
|
||||
{
|
||||
TTN_CLEAR_TIMER_ALARM;
|
||||
BaseType_t higher_prio_task_woken = pdFALSE;
|
||||
queue_item_t item = {
|
||||
.ev = TIMER
|
||||
};
|
||||
|
||||
xQueueSendFromISR(dio_queue, &item, &higher_prio_task_woken);
|
||||
if (higher_prio_task_woken)
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
CHECK_IO,
|
||||
WAIT_FOR_ANY_EVENT,
|
||||
WAIT_FOR_TIMER
|
||||
} wait_open_e;
|
||||
|
||||
static bool hal_wait(wait_open_e wait_option)
|
||||
{
|
||||
TickType_t ticks_to_wait = wait_option == CHECK_IO ? 0 : portMAX_DELAY;
|
||||
while (true)
|
||||
{
|
||||
queue_item_t item;
|
||||
if (xQueueReceive(dio_queue, &item, ticks_to_wait) == pdFALSE)
|
||||
return false;
|
||||
|
||||
if (item.ev == WAKEUP)
|
||||
{
|
||||
if (wait_option != WAIT_FOR_TIMER)
|
||||
{
|
||||
hal_disarm_timer();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (item.ev == TIMER)
|
||||
{
|
||||
hal_disarm_timer();
|
||||
if (wait_option != CHECK_IO)
|
||||
return true;
|
||||
}
|
||||
else // IO interrupt
|
||||
{
|
||||
if (wait_option != WAIT_FOR_TIMER)
|
||||
hal_disarm_timer();
|
||||
hal_enterCriticalSection();
|
||||
radio_irq_handler(item.ev, item.time);
|
||||
hal_leaveCriticalSection();
|
||||
if (wait_option != 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)
|
||||
{
|
||||
hal_prepare_next_alarm(time);
|
||||
hal_arm_timer();
|
||||
hal_wait(WAIT_FOR_TIMER);
|
||||
}
|
||||
|
||||
void hal_wakeUp()
|
||||
{
|
||||
queue_item_t item = {
|
||||
.ev = WAKEUP
|
||||
};
|
||||
xQueueSend(dio_queue, &item, 0);
|
||||
}
|
||||
|
||||
// check and rewind for target time
|
||||
u1_t hal_checkTimer(u4_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
|
||||
}
|
||||
|
||||
hal_prepare_next_alarm(time);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hal_sleep()
|
||||
{
|
||||
if (hal_wait(CHECK_IO))
|
||||
return;
|
||||
|
||||
hal_arm_timer();
|
||||
hal_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
|
||||
|
||||
static SemaphoreHandle_t mutex;
|
||||
|
||||
void hal_initCriticalSection()
|
||||
{
|
||||
mutex = xSemaphoreCreateRecursiveMutex();
|
||||
}
|
||||
|
||||
void hal_enterCriticalSection()
|
||||
{
|
||||
xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void hal_leaveCriticalSection()
|
||||
{
|
||||
xSemaphoreGiveRecursive(mutex);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
static void hal_bgTask(void* pvParameter) {
|
||||
os_runloop();
|
||||
}
|
||||
|
||||
void hal_init()
|
||||
{
|
||||
// configure radio I/O and interrupt handler
|
||||
hal_io_init();
|
||||
// configure radio SPI
|
||||
hal_spi_init();
|
||||
// configure timer and interrupt handler
|
||||
hal_time_init();
|
||||
}
|
||||
|
||||
void hal_startBgTask() {
|
||||
xTaskCreate(hal_bgTask, "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);
|
||||
}
|
45
src/hal/hal_esp32.h
Normal file
45
src/hal/hal_esp32.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef _hal_esp32_h_
|
||||
#define _hal_esp32_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct lmic_pinmap {
|
||||
spi_host_device_t spi_host;
|
||||
uint8_t nss;
|
||||
uint8_t rxtx;
|
||||
uint8_t rst;
|
||||
uint8_t dio0;
|
||||
uint8_t dio1;
|
||||
} lmic_pinmap;
|
||||
|
||||
extern lmic_pinmap lmic_pins;
|
||||
|
||||
void hal_startBgTask();
|
||||
void hal_wakeUp();
|
||||
void hal_initCriticalSection();
|
||||
void hal_enterCriticalSection();
|
||||
void hal_leaveCriticalSection();
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _hal_esp32_h_
|
Reference in New Issue
Block a user