Save and restore for deep sleep

This commit is contained in:
Manuel Bl 2021-09-28 17:35:05 +02:00
parent de4297a8f4
commit e71d584fca
7 changed files with 278 additions and 21 deletions

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(deep_sleep)

View File

@ -0,0 +1,4 @@
idf_component_register(
SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES ttn-esp32)

View File

@ -0,0 +1,117 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program (in C) sending messages and going to deep sleep in-between.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "ttn.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST HSPI_HOST
#define TTN_SPI_DMA_CHAN 1
#define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19
#define TTN_PIN_NSS 18
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
#define TTN_PIN_RST 14
#define TTN_PIN_DIO0 26
#define TTN_PIN_DIO1 35
#define TX_INTERVAL 60 // in sec
static uint8_t msgData[] = "Hello, world";
void app_main(void)
{
esp_err_t err;
// Initialize the GPIO ISR handler service
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
ESP_ERROR_CHECK(err);
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
err = nvs_flash_init();
ESP_ERROR_CHECK(err);
// Initialize SPI bus
spi_bus_config_t spi_bus_config = {
.miso_io_num = TTN_PIN_SPI_MISO,
.mosi_io_num = TTN_PIN_SPI_MOSI,
.sclk_io_num = TTN_PIN_SPI_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1
};
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Initialize TTN
ttn_init();
// Configure the SX127x pins
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
// The below line can be commented after the first run as the data is saved in NVS
ttn_provision(devEui, appEui, appKey);
// ttn_set_adr_enabled(false);
// ttn_set_data_rate(TTN_DR_US915_SF7);
// ttn_set_max_tx_pow(14);
if (ttn_resume_after_deep_sleep()) {
printf("Resumed from deep sleep.\n");
} else {
printf("Joining...\n");
if (ttn_join())
{
printf("Joined.\n");
}
else
{
printf("Join failed. Goodbye\n");
return;
}
}
printf("Sending message...\n");
ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn_wait_for_idle();
ttn_prepare_for_deep_sleep();
// Schedule wake up
esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
printf("Going to deep sleep...\n");
esp_deep_sleep_start();
}

View File

@ -22,6 +22,8 @@
#include "driver/timer.h"
#include "esp_timer.h"
#include "esp_log.h"
#include <time.h>
#include <sys/time.h>
#define LMIC_UNUSED_PIN 0xff
@ -46,7 +48,7 @@ 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 int64_t get_current_time();
static void init_io(void);
static void init_spi(void);
static void init_timer(void);
@ -64,7 +66,6 @@ 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;
@ -73,6 +74,7 @@ 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 time_offset;
static int64_t next_alarm;
static volatile bool run_background_task;
static volatile wait_kind_e current_wait_kind;
@ -249,11 +251,14 @@ void hal_spi_read(u1_t cmd, u1_t *buf, size_t len)
/*
* 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.
* 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.
*
* The time includes an offset initialized from `gettimeofday()`
* to ensure the time correctly advances during deep sleep.
*
* Based on this timer, future callbacks can be scheduled. This is used to
* schedule the next LMIC job.
*/
@ -276,15 +281,18 @@ int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time)
}
else
{
// one's complement instead of two's complement:
// off by 1 µs and ignored
os_diff = ~os_diff;
os_diff = -os_diff;
esp_time = esp_now - (((int64_t)os_diff) << 4);
}
return esp_time;
}
int64_t get_current_time()
{
return esp_timer_get_time() + time_offset;
}
void init_timer(void)
{
esp_timer_create_args_t timer_config = {
@ -296,6 +304,10 @@ void init_timer(void)
esp_err_t err = esp_timer_create(&timer_config, &timer);
ESP_ERROR_CHECK(err);
struct timeval now;
gettimeofday(&now, NULL);
time_offset = (int64_t)now.tv_sec * 1000000;
ESP_LOGI(TAG, "Timer initialized");
}
@ -308,7 +320,7 @@ void arm_timer(int64_t esp_now)
{
if (next_alarm == 0)
return;
int64_t timeout = next_alarm - esp_timer_get_time();
int64_t timeout = next_alarm - get_current_time();
if (timeout < 0)
timeout = 10;
esp_timer_start_once(timer, timeout);
@ -379,7 +391,7 @@ TickType_t hal_esp32_get_timer_duration(void)
return 1; // busy, not waiting
if (alarm_time != 0)
return pdMS_TO_TICKS((alarm_time - esp_timer_get_time() + 999) / 1000);
return pdMS_TO_TICKS((alarm_time - get_current_time() + 999) / 1000);
return 0; // waiting indefinitely
@ -391,7 +403,7 @@ 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);
return (u4_t)(get_current_time() >> 4);
}
// Wait until the specified time.
@ -399,7 +411,7 @@ u4_t IRAM_ATTR hal_ticks(void)
// 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_now = get_current_time();
int64_t esp_time = os_time_to_esp_time(esp_now, time);
set_next_alarm(esp_time);
arm_timer(esp_now);
@ -423,7 +435,7 @@ void hal_esp32_wake_up(void)
// 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_now = get_current_time();
int64_t esp_time = os_time_to_esp_time(esp_now, time);
int64_t diff = esp_time - esp_now;
if (diff < 100)
@ -440,7 +452,7 @@ void hal_sleep(void)
if (wait(WAIT_KIND_CHECK_IO))
return;
arm_timer(esp_timer_get_time());
arm_timer(get_current_time());
wait(WAIT_KIND_WAIT_FOR_ANY_EVENT);
}

View File

@ -18,6 +18,7 @@
#include "lmic/lmic.h"
#include "ttn_logging.h"
#include "ttn_provisioning.h"
#include "ttn_rtc.h"
#define TAG "ttn"
@ -61,7 +62,7 @@ static bool is_started;
static bool has_joined;
static QueueHandle_t lmic_event_queue;
static ttn_message_cb message_callback;
static ttn_waiting_reason_t waiting_reason = TTN_WAITING_NONE;
static ttn_waiting_reason_t waiting_reason;
static ttn_rf_settings_t last_rf_settings[4];
static ttn_rx_tx_window_t current_rx_tx_window;
static int subband = 2;
@ -212,6 +213,29 @@ bool ttn_join(void)
return join_core();
}
bool ttn_resume_after_deep_sleep(void)
{
if (!ttn_provisioning_have_keys())
{
if (!ttn_provisioning_restore_keys(false))
return false;
}
if (!ttn_provisioning_have_keys())
{
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
return false;
}
start();
if (!ttn_rtc_restore())
return false;
has_joined = true;
return true;
}
// Called immediately before sending join request message
void config_rf_params(void)
{
@ -310,6 +334,12 @@ bool ttn_is_provisioned(void)
return ttn_provisioning_have_keys();
}
void ttn_prepare_for_deep_sleep(void)
{
ttn_rtc_save();
}
void ttn_wait_for_idle(void)
{
while (true)
@ -353,30 +383,26 @@ void ttn_set_adr_enabled(bool enabled)
void ttn_set_data_rate(ttn_data_rate_t data_rate)
{
join_data_rate = data_rate;
if (has_joined)
{
hal_esp32_enter_critical_section();
LMIC_setDrTxpow(data_rate, LMIC.adrTxPow);
hal_esp32_leave_critical_section();
}
else
{
join_data_rate = data_rate;
}
}
void ttn_set_max_tx_pow(int tx_pow)
{
max_tx_power = tx_pow;
if (has_joined)
{
hal_esp32_enter_critical_section();
LMIC_setDrTxpow(LMIC.datarate, tx_pow);
hal_esp32_leave_critical_section();
}
else
{
max_tx_power = tx_pow;
}
}
ttn_rf_settings_t ttn_get_rf_settings(ttn_rx_tx_window_t window)

58
src/ttn_rtc.c Normal file
View File

@ -0,0 +1,58 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Functions for storing and retrieving TTN communication state from RTC memory.
*******************************************************************************/
#include "ttn_rtc.h"
#include "esp_system.h"
#include "lmic/lmic.h"
#include <string.h>
#define LMIC_OFFSET(field) __builtin_offsetof(struct lmic_t, field)
#define LMIC_DIST(field1, field2) (LMIC_OFFSET(field2) - LMIC_OFFSET(field1))
#define TTN_RTC_MEM_SIZE (sizeof(struct lmic_t) - LMIC_OFFSET(radio) - MAX_LEN_PAYLOAD - MAX_LEN_FRAME)
#define TTN_RTC_FLAG_VALUE 0xf30b84ce
RTC_DATA_ATTR uint8_t ttn_rtc_mem_buf[TTN_RTC_MEM_SIZE];
RTC_DATA_ATTR uint32_t ttn_rtc_flag;
void ttn_rtc_save()
{
// Copy LMIC struct except client, osjob, pendTxData and frame
size_t len1 = LMIC_DIST(radio, pendTxData);
memcpy(ttn_rtc_mem_buf, &LMIC.radio, len1);
size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
memcpy(ttn_rtc_mem_buf + len1, (u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, len2);
size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
memcpy(ttn_rtc_mem_buf + len1 + len2, (u1_t *)&LMIC.frame + MAX_LEN_FRAME, len3);
ttn_rtc_flag = TTN_RTC_FLAG_VALUE;
}
bool ttn_rtc_restore()
{
if (ttn_rtc_flag != TTN_RTC_FLAG_VALUE)
return false;
// Restore data
size_t len1 = LMIC_DIST(radio, pendTxData);
memcpy(&LMIC.radio, ttn_rtc_mem_buf, len1);
memset(LMIC.pendTxData, 0, MAX_LEN_PAYLOAD);
size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
memcpy((u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, ttn_rtc_mem_buf + len1, len2);
memset(LMIC.frame, 0, MAX_LEN_FRAME);
size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
memcpy((u1_t *)&LMIC.frame + MAX_LEN_FRAME, ttn_rtc_mem_buf + len1 + len2, len3);
ttn_rtc_flag = 0xffffffff; // invalidate RTC data
return true;
}

30
src/ttn_rtc.h Normal file
View File

@ -0,0 +1,30 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Functions for storing and retrieving TTN communication state from RTC memory.
*******************************************************************************/
#ifndef TTN_RTC_H
#define TTN_RTC_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
{
#endif
void ttn_rtc_save();
bool ttn_rtc_restore();
#ifdef __cplusplus
}
#endif
#endif