mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-06-14 20:14:27 +02:00
Save and restore for deep sleep
This commit is contained in:
parent
de4297a8f4
commit
e71d584fca
10
examples/deep_sleep_in_c/CMakeLists.txt
Normal file
10
examples/deep_sleep_in_c/CMakeLists.txt
Normal 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)
|
4
examples/deep_sleep_in_c/main/CMakeLists.txt
Normal file
4
examples/deep_sleep_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
117
examples/deep_sleep_in_c/main/main.c
Normal file
117
examples/deep_sleep_in_c/main/main.c
Normal 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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
44
src/ttn.c
44
src/ttn.c
@ -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
58
src/ttn_rtc.c
Normal 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
30
src/ttn_rtc.h
Normal 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
|
Loading…
Reference in New Issue
Block a user