wake from GPIO using SENSE without a race condition

NCS2.3.0, NRF52833

I use a button to wake my product. The same button is used to turn off. The button is tied low with a 100K resistor and pulled high when pressed.

I found that very occasionally, when I turn the device off, it later fails to wake up when I press the button. I copied the code to EnterSystemOff() from one of the examples.

I read the product spec again and concluded that I must ensure the button is not pressed when I call pm_stat_force or the SENSE wont work. (please correct me if I'm wrong)

So I use this code  


void EnterSystemOff(void)
{
	LOG_INF("Entering system off; press BUTTON to restart");


// #ifdef thread0_id
	common.run_button_thread = false;
	// k_msleep(100);
	// k_thread_abort(thread0_id);
	// k_thread_abort(thread1_id);
// #endif

	LIS2DW12_power_down();

	ClearRGBLEDs();
	nrf_pwm_play_shutdown();
	
	nrf_pwm_stop();
	all_gpios_off();

	do
	{
		//We MUST not call pm_state_force if the button is pressed.
		// So lets wait for the button to be continuously released for at least half a second
		// This is still a bit risky because the button could be pressed again before we call pm_state_force
		
		uint32_t button_unpressed_counter = 0;
		while(button_unpressed_counter < 500) 
		{
			k_sleep(K_MSEC(1));

			if(nrf_gpio_pin_read(MY_BUTTON_PIN))
			{
				button_unpressed_counter = 0;
			}
			else
			{
				button_unpressed_counter++;
			}
		}

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

		/* Now we need to go to sleep. This will let the idle thread run and
		* the pm subsystem will use the forced state. To confirm that the
		* forced state is used, lets set the same timeout used previously.
		*/
		k_sleep(K_SECONDS(2));

		LOG_INF("ERROR: System off failed - resetting");
		// We should never get here because the button should cause a reset which restarts the system.
		// Something serious has gone wrong. so lets try to reset the system. 
		sys_reboot(SYS_REBOOT_COLD);
		k_sleep(K_SECONDS(2));

		// If we're still here then the reset failed. Try to call pm_state_force again.
	} while (true);
}

It's hard to test if this fixes the issue because it was very rare to see the bug anyway.

As you can see there's a loop that waits for at least 500ms without a button press to *try* to ensure it doesn't go to sleep when the pin is high.

Obviously there's still a race condition which I don't like. I wanted to ask if there's a better way to do this. And to check if I've understood properly.

I need to ensure SENSE is always configured on the pin, so the first line of main() is 

nrf_gpio_cfg(
    MY_BUTTON_PIN, 
    NRF_GPIO_PIN_DIR_INPUT, 
    NRF_GPIO_PIN_INPUT_CONNECT, 
    NRF_GPIO_PIN_NOPULL, 
    NRF_GPIO_PIN_S0S1, 
    NRF_GPIO_PIN_SENSE_HIGH);

I can't see how to configure SENSE on the gpio in the zephyr .dts file, so I use the nrf_gpio functions instead and I don't configure any of my gpios in the .dts file.

Thanks for your help,
Kind regards,
-Jason
Related