I have a nRF51822 program in which a button triggered event doesn't work as expected and crashes the program. The program may have potential race condition problem. Here is the problem description.
The program measures some sensor values and send them out in advertising packets. The measurement is made up of a chain of single-shot timer event handlers as illustrated in the pseudo code as follows:
volatile uint8_t data_update_ongoing = 0;
void measurement_start(void)
{
data_update_ongoing = 1;
// Signal LED1
nrf_gpio_pin_set(LED_1);
nrf_delay_ms(100);
nrf_gpio_pin_clear(LED_1);
// Some init code here
uint32_t err_code = app_timer_start(m_app_timer1, APP_TIMER1_TICKS, NULL);
APP_ERROR_CHECK(err_code);
}
void app_timer1_handler(void * p_context)
{
// Process something here
// Start app_timer2
uint32_t err_code = app_timer_start(m_app_timer2, APP_TIMER1_TICKS, NULL);
APP_ERROR_CHECK(err_code);
}
...
void app_timer4_handler(void * p_context)
{
// Process something
// Finishing measurement
data_update_ongoing = 0;
// Signal LED2
nrf_gpio_pin_set(LED_2);
nrf_delay_ms(100);
nrf_gpio_pin_clear(LED_2);
}
There are two places that measurement_start() can be called. One is the handler of a periodic timer "m_du_timer", and the other is a button handler. But there should be only a single active measurement, either triggered by the periodic timer or button. If the button is pressed when the measurement is active (triggered by the periodic timer), the program should just let the measurement finish and do data processing after that. Or the button handler will call measurement_start(). Similarly, if the periodic timer times out when the measurement is still active (triggered by the button press), then it should just let it finish and do nothing. Or the periodic timer handler will call measurement_start(). The pseudo code for this part is as follows:
void app_periodic_timer_handler(void * p_context)
{
if (data_update_ongoing == 0) measurement_start();
// If measurement is active, then do nothing
}
void button_event_handler(uint8_t pin_no, uint8_t button_action)
{
if (button_action==APP_BUTTON_PUSH)
{
switch (pin_no)
{
case BUTTON1:
if (data_update_ongoing==0) measurement_start();
while (data_update_ongoing) {} // wait till the measurement finishes
// Some data processing code here
break;
default:
APP_ERROR_HANDLER(pin_no);
break;
}
}
}
I can see the data updated correctly if I don't touch the button, and both LED1 and LED2 blink. However, if I press the button, only LED1 blinks and after that the program freezes. After some time the program resets itself because I see initialization values in ADV packets. If I remove the line "if (data_update_ongoing==0) measurement_start();", the program will not freeze. I can confirm the button and GPIOTE are initialized correctly by replacing some other test code (such blinking some LEDs) in the button_event_handler() and seeing them behaving correctly.
I will appreciate if anyone points out what I get wrong here. Suggestions are also need to handle this potential race condition in a better way.