nRF52840 sometimes won't wake up.

Hi,

I'm using nRF52840 with nRF Connect SDK v2.2.0.

I need my device to go to sleep and wake up after a button press.

I followed "system_off" example.

In my main function I have this snippet:

static int disable_ds_1(const struct device *dev) {
    ARG_UNUSED(dev);

    pm_policy_state_lock_get(PM_STATE_SOFT_OFF, PM_ALL_SUBSTATES);
    return 0;
}

SYS_INIT(disable_ds_1, PRE_KERNEL_2, 0);

Then I initialize GPIO and P0.25 (button line): I set it as input and enable internal pull-up.

Then I configure port event to wake up when the button is pressed (low state):

    /* Configure to generate PORT event (wakeup) on push button press */
    nrf_gpio_cfg_sense_set(NRF_DT_GPIOS_TO_PSEL(DT_ALIAS(push_button), gpios), NRF_GPIO_PIN_SENSE_LOW);

Then, the application counts several minutes, and after that, it automatically shuts down using this code. This code is also executed after long button press and then release, if the user wants to shut down the device without waiting for timeout:

    pm_state_force(0u, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0, 0});

    k_sleep(K_SECONDS(2U));

This code works fine most of the time, but sometimes it won't wake up after pressing the button. The only way to recover from this state is to reset the MCU or power cycle the board.

I was looking for similar issues in DevZone, and I found this topic although it's related to a different Nordic MCU:

nrf52833 failed to wake up from SOFT_OFF - Nordic Q&A - Nordic DevZone - Nordic DevZone (nordicsemi.com)

I don't use Bluetooth mesh, but I do use "regular" BLE functionalities.

When the system starts I enable BLE like that:

// Registering callback here...
err = bt_enable(NULL);
err = bt_le_adv_start(BT_LE_ADV_CONN, ad_, ARRAY_SIZE(ad_), sd_, ARRAY_SIZE(sd_));
smp_bt_register();

Following the ideas from the other ticket mentioned above, I modified the code so that the BLE is disabled before putting the system to sleep:

const int ble_disable_err = bt_disable();

if (ble_disable_err) {
    NVIC_SystemReset();
}

pm_state_force(0u, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0, 0});
k_sleep(K_SECONDS(2U));
NVIC_SystemReset();

I also reset the system in case of unlikely errors when disabling BLE or going to SOFT_OFF - this is better than having the board not waking up.

This worked for me. To test it, I hooked up Arduino to the button line to simulate long press-then-release to put it to sleep and then short press to wake up.

Without disabling BLE, the board got stuck after 30-50 cycles several times. When I added the fix, I kept the test running overnight (around 3000 cycles) and the issue did not occur anymore. This made me think that the problem is fixed for good.

Can anyone explain what is the root cause and if the changes I made make sense? Or my fix only covers a real root cause that Iam not aware of?

  • Hi Runar,

    I finally found some time to prepare a simple project running on nRF52840dk.

    I was able to reproduce the wake-up issue in the setup running on the DK. Same situation here - disabling BLE before going to sleep fixes the problem.

    Please find below the source code. There are only 3 files, but if you need a zip file, let me know then I can attach it as well.

    If there are any new findings or better (proper?) ways to overcome this wake-up issue, then please let me know.

    nrf52_wakeup_issue/src/main.c:

    // nrf52_wakeup_issue/src/main.c
    /* How to reproduce:
    Power the DK via J3 micro USB.
    After DK goes to sleep for the first time, start Arduino code and immediatelly connect THE_PIN to the Button 4 of DK.
    Keep running for a while until DK won't wake up.
    
    I did this test 4 times:
    1. won't wake up after 12 min
    2. won't wake up after 16 min
    3. won't wake up after 3 min
    4. won't wake up after 35 min
    
    Arudino Mega board
    Code simulating pressing the button after DK goes to sleep
    
    #define THE_PIN 53
    
    void setup() {
        pinMode(THE_PIN, INPUT);
        Serial.begin(9600);
        Serial.println("Start");
        delay(10000);
    }
    int counter = 0;
    void loop() {
        digitalWrite(THE_PIN, LOW);
        pinMode(THE_PIN, OUTPUT);
        Serial.println("Low for 100 ms");
        delay(100);
    
        // Wait until the board wakes up and goes to sleep again
        pinMode(THE_PIN, INPUT);
        Serial.println("Float for 10 seconds");
        delay(10000);
    }
    
     */
    #include <stdint.h>
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/devicetree.h>
    #include <hal/nrf_gpio.h>
    #include <zephyr/pm/pm.h>
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/settings/settings.h>
    
    static const struct bt_data ad_[] = {
        BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
        BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
    };
    
    static const struct bt_data sd_[] = {
        BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
    };
    
    void main(void) {
        gpio_pin_configure(DEVICE_DT_GET(DT_NODELABEL(gpio0)), 15, GPIO_OUTPUT_HIGH); // LED3 on DK
        gpio_pin_configure(DEVICE_DT_GET(DT_NODELABEL(gpio0)), 25, GPIO_INPUT | GPIO_PULL_UP); // BUTTON4 on DK
        nrf_gpio_cfg_sense_set(25, NRF_GPIO_PIN_SENSE_LOW);
        gpio_pin_set(DEVICE_DT_GET(DT_NODELABEL(gpio0)), 15, 0);
    
        if (bt_enable(NULL)) {
            printk("bt_enable() failed\n");
            goto err;
        }
    
        if (bt_le_adv_start(BT_LE_ADV_CONN, ad_, ARRAY_SIZE(ad_), sd_, ARRAY_SIZE(sd_))) {
            printk("bt_le_adv_start() failed\n");
            goto err;
        }
    
        for (int i = 5; i >= 0; --i) {
            k_sleep(K_MSEC(1000));
            printk("Shutting down the system in %d seconds...\n", i);
        }
    
        // When bt_disable() is called, there is no wakeup issue.
        // bt_disable();
        
        gpio_pin_set(DEVICE_DT_GET(DT_NODELABEL(gpio0)), 15, 1);
        pm_state_force(0u, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0, 0});
    
    err:
        while (1) {
            k_sleep(K_MSEC(2000));
            printk("Error\n");
        }
    }

    nrf52_wakeup_issue/CMakeLists.txt:

    # nrf52_wakeup_issue/CMakeLists.txt
    cmake_minimum_required(VERSION 3.20.0)
    # list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
    set_property(GLOBAL PROPERTY CSTD c11)
    find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
    project(wakeup_test)
    file(GLOB APPLICATION_SOURCES src/main.c)
    target_sources(app PRIVATE ${APPLICATION_SOURCES})
    target_include_directories(app PRIVATE src)

    nrf52_wakeup_issue/prj.conf:

    # nrf52_wakeup_issue/prj.conf
    CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y
    CONFIG_BT=y
    CONFIG_NEWLIB_LIBC=y
    CONFIG_BT_PERIPHERAL=y
    CONFIG_BT_SMP=y
    CONFIG_PM_DEVICE=y
    CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=2048

  • Hello,

    Two questions from me:

    - Do you power cycle the nRF52840DK after programming the board and before executing testing? This to ensure the chip is not in debug interface mode (which is not proper system OFF, but emulated system OFF). That is kind of the only thing I can think of that may cause this to fail.

    - Is there any difference if you add a __disable_irq(); before pm_state_force()? What if you use NRF_POWER->SYSTEMOFF=1; directly instead of pm_state_force().

    Kenneth

  • Hi Kenneth,

    After programming the nRF52840DK, and before running the test, I always switch the DK off, unplug and plug the USB cable back in.

    Regarding proposed modifications, here's the results:

    Mod 1:

        for (int i = 5; i >= 0; --i) {
            k_sleep(K_MSEC(1000));
            printk("Shutting down the system in %d seconds (w/ __disable_irq();)...\n", i);
        }
    
        // When bt_disable() is called, there is no wakeup issue.
        // bt_disable();
        
        gpio_pin_set(DEVICE_DT_GET(DT_NODELABEL(gpio0)), 15, 1);
        __disable_irq();
        pm_state_force(0u, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0, 0});
    
    err:
        while (1) {
            k_sleep(K_MSEC(2000));
            printk("Error. Infinite loop reached\n");
        }
    }

    This resulted in failure to enter OFF state. I observed "Error. Infinite loop reached" logs running very fast, without 2000 ms delay, as most likely disabled interrupts make the scheduler nonfunctional.

    Mod 2:

        for (int i = 5; i >= 0; --i) {
            k_sleep(K_MSEC(1000));
            printk("Shutting down the system in %d seconds (w/NRF_POWER->SYSTEMOFF = 1;)...\n", i);
        }
    
        // When bt_disable() is called, there is no wakeup issue.
        // bt_disable();
        
        gpio_pin_set(DEVICE_DT_GET(DT_NODELABEL(gpio0)), 15, 1);
        NRF_POWER->SYSTEMOFF = 1;
    
    err:
        while (1) {
            k_sleep(K_MSEC(2000));
            printk("Error. Infinite loop reached\n");
        }
    }

    The board got stuck after several minutes from starting the test. It won't wake up.

    Mod 3:

        for (int i = 5; i >= 0; --i) {
            k_sleep(K_MSEC(1000));
            printk("Shutting down the system in %d seconds (w/ __disable_irq(); and NRF_POWER->SYSTEMOFF = 1;)...\n", i);
        }
    
        // When bt_disable() is called, there is no wakeup issue.
        // bt_disable();
        
        gpio_pin_set(DEVICE_DT_GET(DT_NODELABEL(gpio0)), 15, 1);
        __disable_irq();
        NRF_POWER->SYSTEMOFF = 1;
    
    err:
        while (1) {
            k_sleep(K_MSEC(2000));
            printk("Error. Infinite loop reached\n");
        }
    }

    Same here, stuck after several minutes from starting the test.

  • Hello,

    Thanks for trying, then I guess it likely is related to requirement that Runar suggested earlier: "Before entering System OFF mode, all on-going EasyDMA transactions need to have completed. See peripheral specific chapters for more information about how to acquire the status of EasyDMA transactions." This requirement can for instance be ensured by disabling bt stack before going to system OFF, alternatively just stop advertisment and disconnect any potential link before entering system OFF.

    Kenneth

  • Seems like this concludes the discussion.

    Thank you guys for your help.

Related