nrf desktop - how to add soft power off

Hi,

I'm using nRF Connect v2.1.0.

How can I add a soft power off to nrf_desktop with a gpio interrupt wakeup? It looks like all of the demo hardware uses disconnecting switches, where I have a dedicated GPIO input with a momentary button for powering on/off.

I used the simple button example to add power button detection to board.c, and I used the code from the system_off example to shut it down from within my button callback, but it just reboots and turns right back on. I also tried creating and submitting a power_down_event to send to the power manager (I tried both "power_down_event" and "force_power_down_event"), with the same result. I also tried calling the turn_board_off() function that was already in board.c, which didn't do much.

I thought maybe it was failing because I was shutting down from within my button ISR so I added a worker function that I kick off with k_work_reschedule from the ISR, and then submit the powerdown event from the worker function, but I get the same result. It turns off and right back on.

Thanks,

Glen

  • Hi Glen, 

    Could you show how you put the device to system off and how you configure the wake up source ?
    There could be a chance that the wake up source is triggered right after the device enter deep sleep. 

    Maybe you can try to test using different buttons, one for putting the device to sleep and another one to wake the device up ? 
    I don't think putting the device to deep sleep in an ISR could be the reason it wake up. 

    Have you tried to use force_power_down()  
    I tried this in the nRF Desktop code, compile with nRF52840 dk board and it seems to work: 

    void main(void)
    {
    	if (app_event_manager_init()) {
    		LOG_ERR("Application Event Manager not initialized");
    	} else {
    		module_set_state(MODULE_STATE_READY);
    	}
    	k_sleep(K_SECONDS(3));
    	force_power_down();
    }
    

    The board enter deep sleep after awhile (more than 3 seconds, but I assume it's because it takes time to shutdown). Pressing button 3 or 4 wakes the board up. 

  • Hmm - nice to know there's just a force_power_down function to call. It's still doing the same thing though, even when I put the call in main like you did so the button isn't involved.

    Is there some way to see the cause of a powerup, or some way to see what interrupts are active before I call the shutdown function that might be causing it to wake? I can set a breakpoint in my shut down function if there are some registers I can look at.

    Maybe some other pin in my system is still configured as an interrupt without me realizing it, or maybe some module isn't enabled for power management so it's waking me up. 

    None of the KEEP_ALIVE_EVENTS things in prj.conf are set to true.

    It might also be good if I can figure out how to disable parts of the code to see if it works up until I turn on the ADC or some other peripheral

    Here's the code I'm using for the wakeup button. I added it to the init part of app_event_handler in board.c:

    #define POWER_BTN_NODE	DT_ALIAS(powerbtn)
    static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(POWER_BTN_NODE, gpios, {0});
    static struct gpio_callback button_cb_data;
    
    
    static bool app_event_handler(const struct app_event_header *aeh)
    {
    	static bool initialized;
    
    	if (is_module_state_event(aeh)) {
    		const struct module_state_event *event = cast_module_state_event(aeh);
    
    		if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
    			__ASSERT_NO_MSG(!initialized);
    			initialized = true;
    
    			turn_board_on();
    
             // Set up the power button interrupt
             gpio_pin_configure_dt(&button, GPIO_INPUT);
             gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
             gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
             gpio_add_callback(button.port, &button_cb_data);

  • Hi Glen, 

    You can read the RESETREAS register at address 0x40000400 to know what trigger wakeup/reset. Most likely it would be wake up from system off. 

    Could you try testing what I did on a Dev Kit ? From what I could see when testing with the DK it worked fine, the device enter deep sleep until I pressed on Button 3 or 4. 

    Have you checked if P0.24 and P0.25 is not connected to anything pulling it down ? 

    How do you configure your GPIO_INPUT in device tree? Did you use any pull up /down internally/externally ? 

    If you can test on a devkit and if it work as expected, then it must be something in the difference in the hardware causing the issue. 

  • Hi Hung,

    I've continued to work on this and I've made some progress, but simply turning the device off and on is proving to me much harder than we imagined.

    The primary thing keeping it from going to sleep is the custom motion sensor that I added. I've just turned that off for the time being and I did get it to go to sleep - I'll figure out how to disable it for sleep mode, but now I'm fighting a related issue I'm hoping you can help me with.

    Our device has a full keyboard including a power button, and we want to use the power button to turn on and off, not every key on the keyboard. Right now, no matter what I do, I haven't been able to keep the other keys on the keyboard from waking the device up.

    I decided to just accept my failure to disable the wakeup interrupt for the row/column stuff and put in some code that would go immediately back to sleep when a button was pressed other than the power button - but that doesn't work either. When it's powered off and I press one of the normal keyboard buttons, the device wakes up, but my is_button_event() check doesn't get called. What's up with that?

    static bool app_event_handler(const struct app_event_header *aeh)
    {
    	static bool initialized;
    
    	if (is_button_event(aeh)) 
       {
          // Check to see if a keyboard button other than the power button has been pressed while we're off.
          // If so, go back to off mode.
    		if (m_keyboard_is_on == false)
          {
             k_work_reschedule(&worker, K_MSEC(20));	// This fires off a worker task that calls force_power_down
             return true;
          }
          return false;
    	}
    }	
    
    APP_EVENT_LISTENER(MODULE, app_event_handler);
    APP_EVENT_SUBSCRIBE_FIRST(MODULE, button_event);

    Instead it's hitting this part of that same app_event_handler as if it's starting up from scratch - which may make sense if 

    force_power_down takes it to it's in PM_STATE_SOFT_OFF:

    	if (is_module_state_event(aeh)) {
    		const struct module_state_event *event = cast_module_state_event(aeh);
    
    		if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
    			__ASSERT_NO_MSG(!initialized);
    			initialized = true;
    
    			turn_board_on();
    

    I asked my customer if they were OK with using the power button to go to sleep and any key to wake up, and they said no - so I have to find a way to solve this problem. Any ideas?

    I have a partial solution going, but it's not great. I am forcing it into PM_STATE_SOFT_OFF and then when I get into main the very first thing I do it check to see if the button is still held, and only allow the device to power up if so (if not I jump back to SOFT_OFF), but it takes about a second to boot up from that state, so you have to hold down the power button for a second to get it to power up and the LEDs on the board flash on briefly during the boot process, so you can see a flicker every time you press a wrong button that shouldn't wake it up.

    Thanks for your help,

    Glen

  • Here's the current state of things. I have gotten pretty decent power-off behavior with this code, and a check first-thing in main() to see if they are holding down the power button for my wakeup (if not then I jump straight back to PM_STATE_SOFT_OFF):

       force_power_down();		// This puts the device into the suspended state
       k_sleep(K_MSEC(1200));  // Wait longer than 1 second to make sure the system gets the power off event, which it checks for every 1 second
       pm_state_force(0u, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0}); // Force a hard off where the interrupt wakeup resets the processor

    With the >1s delay, the system has time to send the force power down event around and get itself set up for sleep, and then the pm_state_force seems to do a good job of fully shutting it down.

    Now my only issue is that it takes so long to wake up from PM_STATE_SOFT_OFF that in order to determine that it was the power button that woke us up that I have to hold the power button for about a second to get it to successfully turn on. This might be acceptable - we'll see what the customer says.

    I would still love it if I could get all interrupt sources except for my power button line disabled (power button is on its own pin... it's not in the keyboard matrix), but so far I've been unsuccessful at that.

Related