Interlocking with two buttons

a-georgios wrote on Wednesday, February 21, 2018:

I am trying to make sure that when button1 triggers a task, button2 is not able to trigger its task. At any time only button1 or button2 should be on if they were connected to a LED.

I am using this code, does it do the job? what happens if the two interrupts fire simultaneously?

void button1_callback(uint8_t gpio_num, button_event_t event) {
    if (up_task_handle == NULL && down_task_handle == NULL) {
        xTaskCreate(button1_task, "UP Task", 128, NULL, 2, up_task_handle);
    }
}

void button2_callback(uint8_t gpio_num, button_event_t event) {
if (up_task_handle == NULL && down_task_handle == NULL) {
    xTaskCreate(button2_task, "DOWN Task", 128, NULL, 2, down_task_handle);
}

richard_damon wrote on Wednesday, February 21, 2018:

If these are functions called by a Pend from ISR call, then they will run from a common task (the Timer/Service Task) and thus will properly interlock with each other, the problem being that they don’t have the right signature for those. If you have some other singular button task that processes all buttons and calls these as appropriate, then again, you have an external interlock to avoid the race condition in the functions.

Without something like this, you code has a race condition in that you check that the handles are null, and then create a task, and AFTER that task is created you set the handles so the other routine won’t run. It is possible for an event to happen between those two points, ‘racing’ the code to disable the creation of the second task, and cause a malfunction.

I use two different options, depending on the exact need, to handle such a case,

The first, rather than check the two task handles, I would create a flag variable to indicate if one of the tasks is running, Then inside the routine, I would check the flag, and if clear set it then create the task. The big difference from your code, is that the test and set would be done inside a critical section preventing the race condition, something like

  taskENTER_CRITICAL();
  if(flag == 0) {
    flag = 1;
    taskEXIT_CRITICAL();
    .. create the task ..
  } else {
    taskEXIT_CRITICAL()l
  }

When the task is done, it can set the flag back to 0.

The second method would be to use a semaphore as the guard, and the check would attempt to take the semaphore (possibly with a zero timeout for your case) and if it got it, it would create the task. The task when done would give the semaphore back to let the next activation happen.

The first is simpler in the case where you know you will always want to totally ignore the second request. The second allows for including a short delay to aquire the semaphore, so a request to start task2 just as task1 is finishing up might succeed.

a-georgios wrote on Wednesday, February 21, 2018:

Thank you! Wasnt aware of the CRITICAL sections feature! Thanks

void button_callback(uint8_t gpio_num, button_event_t event) {
    taskENTER_CRITICAL();
    if (mutex == 0) {
        mutex = 1;
        taskEXIT_CRITICAL();
        if (gpio_num == button1_gpio) buttonUP_handle(4000);
        else if (gpio_num == button2_gpio) buttonDOWN_handle(4000);        
        mutex = 0;
    }
    else {
        taskEXIT_CRITICAL();
        printf("race condition, doing nothing");
    }
}