High Sleep Current (400uA<) After LoRaWAN TX. nRF Connect SDK 2.7, Zephyr, nRF52832, Semtech SX126x

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; 
}

lora.h
#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
    }
}

  • Hello,

    Don't have experience with the driver in question.

    Looking at the description it looks like you are doing things right here, maybe also check if it's possible any TIMER is running.

    I did try to search a bit online on the issue you have, and I get some indication the problem may be related to the SX126x transceiver remaining in standby RC mode instead of sleep mode. There seems to be a SetSleep command that you should run after the TX, maybe you can try to check if this command is executed as expected here. A logic analyzer trace might be helpful.

    Hope that helps,
    Kenneth

  • Hi Kenneth,
    Thank you for your reply.

    It does look like a timer is causing this, or rather it looks like something wakes up periodically. 
    When I zoom in, we can see that the 400uA average current is caused by these periodic spikes. 



    I've been testing a bit and the spikes don't always happen at the same frequency. Most often, I've observed a period of 10ms, but I've also seen 20ms, 3ms and around 2.3ms. Interestingly, the average current is always around 400uA.
    I have disabled everything else (including logging) to rule out other factors, even if the high sleep current only occurs after a Lora transmission anyways.
    I have tried disabling the RTC timers, the High Frequency Clock and the Low Frequency Clock. This only broke the ability of the nRF to wake up again, but the high average current after LoRa remained.
    Do you have any insights here? Is there a way to check which timers are in use or a way to simply kill all timers that could be in use?

    Regarding you second suggestion: I did find the SX126xSetSleep() function but calling it seems to have no effect on the sleep current.

    Best regards,
    Lukas

  • Hi Lukas,

    When you measure current consumption, do you measure the nRF52832 only, or is it the combination of nRF52832+SX126x?

    Kenneth

  • Hi Kenneth,

    I measure both together. I am using a module where both come packaged together, so I don't think it is possible to measure either one in isolation.
    https://www.insightsip.com/products/lora-modules/isp4520

    Best,
    Lukas

  • Hi Lukas,

    So it could potentially be the SX126x that draw this current then.

    The only remaining thing I could think of for the nRF52832 would be to check if any TIMERx is running during the period.

    Kenneth

Related