Hi there,
I'm working on a low-power LoRaWAN project using a custom board featuring the ISP4520 module which contains the nRF52832 chip and has a LoRa section based on Semtech SX126x
series transceiver.
I am using nRF Connect SDK 2.7 with Zephyr.
I'm using Zephyr's lorawan API to control the LoRa part of my project.
My problem is that after sending data through LoRa, the sleep current stays around 400uA for an indeterminate amount of time before dropping back to the pre LoRa sleep current, which is around 7uA.
The goal is to reliably and deterministically force the system to deep sleep after the LoRa transmission is complete.
So my question is: What could be causing this and how do I fix it?
I have tried
- using the power management function pm_device_action_run(lora_dev, PM_DEVICE_ACTION_SUSPEND);, even though SDK 2.7 handles this automatically
- explicitly turning off the HFCLK
- disabled the pin where the interrupts from the SX126x chip arrive
- reset the SX126x chip after LoRa transmission
- checked that the SPI instance that controls the LoRa peripheral is suspended
Here is a screenshot illustrating the behavior:
Here is my main loop (attached below also the full code):
lorawan_init(lora_dev, LORAWAN_DR_2, channel); // Initialize LoRaWAN (possible chhannels: 0 through 7 for EU868)
while (1) {
uint8_t data[] = {0x01, 0x02, 0x03}; // Test data
LOG_INF("Sending data... \n");
ret = lorawan_send_data(data, 3); // Try sending data
if (ret == -ENOTCONN) { // If we are not joined, join the network
LOG_INF("Rejoining...");
lorawan_join_network();
// Send data
LOG_INF("Sending data... \n");
ret = lorawan_send_data(data, 3);
}
else if (ret != 0) {
LOG_ERR("Failed to send data: %d", ret);
}
else {
LOG_INF("Data sent successfully");
}
k_msleep(10000); // Wait for 10 seconds before sending the next data
}And here is what I have in my prj.conf file:
# Logging CONFIG_LOG=y CONFIG_USE_SEGGER_RTT=y CONFIG_LOG_MODE_IMMEDIATE=y # GPIO CONFIG_GPIO=y # I2C CONFIG_I2C=y # LoRa and LoRaWAN Configurations CONFIG_SPI=y # SPI2 is used for LoRa module CONFIG_LORA=y CONFIG_HAS_SEMTECH_RADIO_DRIVERS=y CONFIG_HAS_SEMTECH_LORAMAC=y CONFIG_LORAMAC_REGION_EU868=y CONFIG_LORA_SX126X=y CONFIG_LORAWAN=y CONFIG_LORAWAN_LOG_LEVEL_DBG=y # verbose debugging logs for lora # LoRaWAN Application Configurations CONFIG_MAIN_STACK_SIZE=8056 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 # Enable NVS storage for LoRaWAN session persistence CONFIG_SETTINGS=y CONFIG_SETTINGS_RUNTIME=y CONFIG_NVS=y CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_MPU_ALLOW_FLASH_WRITE=y
#include "lora.h"
LOG_MODULE_REGISTER(lora);
struct lorawan_join_config join_cfg; // lorawan config file for initial join
uint8_t dev_eui[] = LORAWAN_DEV_EUI;
uint8_t join_eui[] = LORAWAN_JOIN_EUI;
uint8_t app_key[] = LORAWAN_APP_KEY;
static void lorwan_datarate_changed(enum lorawan_datarate dr)
{
uint8_t unused, max_size;
lorawan_get_payload_sizes(&unused, &max_size);
LOG_INF("New Datarate: DR_%d, Max Payload %d", dr, max_size);
}
static void dl_callback(uint8_t port, bool data_pending, int16_t rssi, int8_t snr, uint8_t len, const uint8_t *data)
{
LOG_INF("Port %d, Pending %d, RSSI %ddB, SNR %ddBm", port, data_pending, rssi, snr);
if (data) {
LOG_HEXDUMP_INF(data, len, "Payload: ");
}
}
struct lorawan_downlink_cb downlink_cb = {
.port = LW_RECV_PORT_ANY,
.cb = dl_callback
};
uint8_t battery_level_cb(void){
// Read battery voltage from npm1300
uint16_t v_bat_adc = npm1300_measure_battery_voltage(I2C1_BUS);
// Convert ADC value (0-1023) to range 0-254 (corresponding to 0V-5V)
uint8_t battery_level = (uint8_t)((uint32_t)(v_bat_adc)*254/1023);
// TO DO: 0 and 255 have special meanings, handle them properly
//return battery_level;
return 111; // Placeholder value for testing
};
int lorawan_set_single_channel(uint8_t channel)
{
if (channel >= 8) {
LOG_ERR("Channel %d out of range (0..7)", channel);
return -EINVAL;
}
// Build channels mask: one uint16_t element (channels 0..7) for EU868
uint16_t channels_mask[LORAWAN_CHANNELS_MASK_SIZE_EU868] = { 0 };
// Enable only the specified channel
channels_mask[0] = 1 << channel;
return lorawan_set_channels_mask(channels_mask, LORAWAN_CHANNELS_MASK_SIZE_EU868);
}
/**
* @brief Initalizes LoRa-Wan communication
* @param lora_dev pointer to the lora device from the alias list
* @param dr the desired datarate (spreading factor)
* @param channel the uplink channel to use
* @retval 0 if successul, otherwise the error code provided by the function that failed
*/
int lorawan_init(const struct device* lora_dev, enum lorawan_datarate dr, uint8_t channel)
{
// Check if device is ready
if (!device_is_ready(lora_dev)) {
LOG_ERR("LoRa device not ready: %s", lora_dev->name);
return 1;
}
// start the lora stack
int ret = lorawan_start();
if (ret) {
LOG_ERR("lorawan_start failed: %d", ret);
return ret;
}
// set the region to EU868
ret = lorawan_set_region(LORAWAN_REGION_EU868);
if (ret) {
LOG_ERR("Failed to set region: %d", ret);
return ret;
}
// Force uplink to use a single channel
ret = lorawan_set_single_channel(channel);
if (ret) {
LOG_ERR("Failed to set single channel: %d", ret);
return ret;
}
// set the device class to Class A
ret = lorawan_set_class(LORAWAN_CLASS_A);
if (ret) {
LOG_ERR("Failed to set device class: %d", ret);
return ret;
}
// Set the spreading factor (data rate) to SF10 (DR2)
lorawan_enable_adr(false); // Disable Adaptive Data Rate
ret = lorawan_set_datarate(dr); // Set data rate. DR_2 → SF10, 125kHz Bandwidth
if (ret) {
LOG_ERR("Failed to set datarate: %d", ret);
return ret;
}
lorawan_register_downlink_callback(&downlink_cb); // For receiving downlink messages, not used currently
lorawan_register_dr_changed_callback(lorwan_datarate_changed); // From SDK example, not sure what it does
// Register battery level callback: currently has no effect, as the server side must request battery level (?)
lorawan_register_battery_level_callback(battery_level_cb); // Register battery level callback
LOG_INF("LoRaWAN initialized");
return 0;
}
int lorawan_join_network(void)
{
LOG_INF("lorawan_join_network called.");
int ret = 0;
join_cfg.mode = LORAWAN_ACT_OTAA;
join_cfg.dev_eui = dev_eui;
join_cfg.otaa.join_eui = join_eui;
join_cfg.otaa.app_key = app_key;
join_cfg.otaa.nwk_key = app_key;
// TO DO: What to do if we get stuck trying to join the network for a long time? reboot?
do {
LOG_INF("---------------Trying to join--------------------");
ret = lorawan_join(&join_cfg);
if (ret == -ETIMEDOUT) {
LOG_ERR("Join failed due to timeout: %d", ret);
} else if(ret != 0) {
LOG_ERR("Join failed: %d", ret);
} else {
LOG_INF("Join successful");
}
} while (ret != 0);
return ret;
}
/**
* @brief Send LoRa Data
* @param payload pointer to uint8_t array containing the data to be sent
* @retval 0 if successully sent, otherwise the error code provided by lorawan_send
*/
int lorawan_send_data(uint8_t *payload, uint8_t size){
int ret = lorawan_send(
2, // FPort
payload,
size,
LORAWAN_MSG_UNCONFIRMED // gateway should not confirm the message. (alternative: LORAWAN_MSG_CONFIRMED)
);
if (ret != 0) {
LOG_ERR("lorawan_send failed: %d.", ret);
}
return ret;
}
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/gpio.h>
#include "power.h"
#include "lora.h"
LOG_MODULE_REGISTER(main);
const struct device* lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0)); // spi2 is connected to lora module
/* ============================================================
* main()
* ============================================================ */
int main(void)
{
LOG_INF("LoRA Application Started\n");
int ret = 0;
/************************ LoRA *******************************/
k_msleep(5000); // Wait for monitor to be connected (debugging)
LOG_INF("LORAWAN INIT");
uint8_t channel = 5; // Uplink channel to use (0-7 for EU868)
lorawan_init(lora_dev, LORAWAN_DR_2, channel); // Initialize LoRaWAN (possible chhannels: 0 through 7 for EU868)
while (1) {
uint8_t data[] = {0x01, 0x02, 0x03}; // Test data
LOG_INF("Sending data... \n");
ret = lorawan_send_data(data, 3); // Try sending data
if (ret == -ENOTCONN) { // If we are not joined, join the network
LOG_INF("Rejoining...");
lorawan_join_network();
// Send data
LOG_INF("Sending data... \n");
ret = lorawan_send_data(data, 3);
}
else if (ret != 0) {
LOG_ERR("Failed to send data: %d", ret);
}
else {
LOG_INF("Data sent successfully");
}
k_msleep(10000); // Wait for 10 seconds before sending the next data
}
}
