Putting CPU into sleep mode until button is pressed

Hi there,

I'm currently using the nRF52 DK (PCA10040) and nRF5 SDK 17.1.0.

Our device needs to permanently connected to a battery, only wake up on a button press and then execute the code. Before the button is pressed, nothing needs to be done (no advertising, etc.). I have read a lot about SYSTEM_ON and SYSTEM_OFF and I realized SYSTEM_OFF is not suitable for this application (since it will reset the chip and start execution from scratch).

That is why I tried using sd_app_evt_wait(), but this does not work at all. It seems that sd_app_evt_wait() returns immediately.

Here is my code:

static void permanent_sleep()
{
    ret_code_t err_code;

    // configure Button 4 with SENSE enabled
    nrf_gpio_cfg_sense_input(BSP_BOARD_BUTTON_3, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);

    NRF_LOG_INFO("Going to sleep...");

    // enter sleep mode
    err_code = sd_app_evt_wait();
    APP_ERROR_CHECK(err_code);

    NRF_LOG_INFO("Woke up...");
}

int main(void)
{
    bool erase_bonds;
    uint32_t err_code;

    // Initialize.
    log_init();
    timers_init();
    buttons_leds_init(&erase_bonds);
    power_management_init();

    // enter sleep mode
    permanent_sleep();
    
    // remaining code should be executed from here
}

Any idea why this is not working? Most examples I have looked at use idle_state_handle() -> pwr_mgmt_run() within the main loop, but this does not work for me because advertising would need to be started before that.

I'm grateful for any hints how to fix this!

  • Hi,

    First off, I'm not sure I understand why System OFF is not an option for you use-case. Do you have timing requirements that makes resetting of the chip cause too long delay in your application after pressing the button? It is possible to read out the reset reason and perform different operations on a reset from System OFF or from a power-on reset. If you expect the button to be pushed often, System OFF might cause higher current consumption due to extra current during reset/startup, then System ON might be a better alternative.

    The GPIO sense functionality will not generate any events to wake the CPU when you are in System ON mode, this will only wake the CPU when you are in System OFF mode. In order to generate wakeup event in System ON mode, you must use the GPIOTE peripheral. This can be configured in two modes; IN event (dedicated channel for one GPIO), or PORT event (common channel for any enabled GPIO, using the sense signal from the GPIOs to generate the event). You can have a look at the Pin Change Interrupt Example to see how this is used. 

    Regarding entering sleep mode, the sd_app_evt_wait() function is only available if the SoftDevice is present and enabled on the chip. If you are not using the SoftDevice, you can enter system ON using "__WFE()" API.

    Best regards,
    Jørgen

  • Hi, thanks for your answer. I'm checking now the reset reason the way you suggested, but I'm using the approach from the pwr mgmt example to configre the buttons. The GPIOTE approach did not work for me (a hard fault occured during nrf_drv_gpiote_init()).

    However, one problem remains:

    After waking up the chip using a button, the code is executed as it should be. Now, I want to be able to enter sleep mode again by pressing the same button. In this situation nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF); is calle correctly, but the chip does not stay in sleep mode. It seems to return and reset the chip immediately.

    Here is my current code:

    static bool m_is_syson = true;
    
    /**@brief Function for handling events from the BSP module.
     *
     * @param[in]   event   Event generated when button is pressed.
     */
    static void bsp_event_handler(bsp_event_t event)
    {
        ret_code_t err_code;
    
        switch (event)
        {
            case BSP_EVENT_DISCONNECT:
                err_code = sd_ble_gap_disconnect(m_conn_handle,
                                                 BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
                if (err_code != NRF_ERROR_INVALID_STATE)
                {
                    APP_ERROR_CHECK(err_code);
                }
                break; // BSP_EVENT_DISCONNECT
    
            case BSP_EVENT_WHITELIST_OFF:
                if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
                {
                    err_code = ble_advertising_restart_without_whitelist(&m_advertising);
                    if (err_code != NRF_ERROR_INVALID_STATE)
                    {
                        APP_ERROR_CHECK(err_code);
                    }
                }
                break; // BSP_EVENT_KEY_0
    
            case BSP_EVENT_SYSOFF:
                NRF_LOG_INFO("Sleep");
                if (m_is_syson)
                {
                    nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
                }
                else
                {
                    nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_RESET);
                }
    
            default:
                break;
        }
    }
    
    /**@brief Function for initializing buttons and leds.
     *
     * @param[out] p_erase_bonds  Will be true if the clear bonding button was pressed to wake the application up.
     */
    static void buttons_leds_init(bool * p_erase_bonds)
    {
        ret_code_t err_code;
        bsp_event_t startup_event;
    
        bsp_board_init(BSP_INIT_LEDS);
    
        err_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, bsp_event_handler);
        APP_ERROR_CHECK(err_code);
    
        err_code = bsp_event_to_button_action_assign(BSP_BOARD_BUTTON_3,
                                                      BSP_BUTTON_ACTION_LONG_PUSH,
                                                      BSP_EVENT_SYSOFF);
        APP_ERROR_CHECK(err_code);
    
    
        err_code = bsp_btn_ble_init(NULL, &startup_event);
        APP_ERROR_CHECK(err_code);
    
        *p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);
    }
    
    /**@brief Function for application main entry.
     */
    int main(void)
    {
        bool erase_bonds;
        uint32_t err_code;
    
        // Initialize.
        log_init();
        timers_init();
        buttons_leds_init(&erase_bonds);
        power_management_init();
    
        // enter sleep mode
        NRF_LOG_INFO("Going to sleep...");
    
        uint32_t wdt_reset;
        wdt_reset = NRF_POWER->RESETREAS;
        NRF_LOG_INFO("reset: 0x%08x", wdt_reset);
        if (NRF_POWER->RESETREAS == (0x00000004))
        {
            // enter sleep mode, if reset reason was soft reset
            bsp_event_handler(BSP_EVENT_SYSOFF);
        }
    
        NRF_LOG_INFO("Woke up...");
    
        // Initialize TWI/ I2C & Setup ADC
        twi_adc_configuration();
        
        // SOME CONFIGS HERE
        
        for(;;)
        {
            /*
                MAIN LOOP: HERE SOME CODE WITH I2S AND BLE FUNCTIONALITY IS EXECUTED - IF THE CHIP IS IN IDLE, BUTTON 4 SHOULD BE USED TO ENTER SLEEP MODE AGAIN. IF I TRY, THE CHIP RESETS IMMEDIATELY WITHOUT STAYING IN SLEEP MODE.
            */
        }
    
            idle_state_handle();
            NRF_LOG_FLUSH();
        }
    
        bsp_board_leds_off();
    }

  • Moritz_S said:
    but the chip does not stay in sleep mode. It seems to return and reset the chip immediately.

    What is the reset reason for the wakeup?

    Have you implemented some kind of debounce on the button, to prevent it from immediately waking the chip after going to sleep? You can check the button pin using a logic analyzer or a scope to see if multiple spikes are seen when the button is pushed. 

  • Hi, you are right. There are always two spikes. I have seen some examples for debouncing buttons with a timer, but I assume those approaches won't work when the chip is reset? So how to avoid that the button which is used to enter sleep mode will immediately wake up the chip again?

  • You can use a timer to do the debouncing, even if you use the same button to wake up again. when the button is pressed, set a flag to indicate that you will go to sleep, then start the timer for the desired debounce time, then configure the GPIO for wakeup and go to sleep when the timer expires. Note that it is not possible to wake the chip by pressing the button again during this period, so it should be long enough to capture the bounces, but short enough to not cause bad user experience. It is also possible to use external debouncing circuit to prevent the multiple spikes.

Related