Variable access from Hardware interrupt ISR and multiple tasks

I have a boolean variable which is set to true in Hardware interrupt ISR. This interrupt is trigged by external switch in my hardware. This boolean variable is read in multiple other tasks and is reset to false by these tasks and these tasks take some actions if they detect this variable is true i.e. external switch is pressed. I am protecting this variable using mutex in multiple tasks but it is not recommended to use mutex in ISR and I don’t want to disable interrupt by using CRITICAL region in tasks. How should I protect this variable in ISR to ensure that when ISR access it, no other task can access the same variable?

Just reading a variable is a very short deterministic operation. Why do you not want to use a critical section for that?

I do not think there is a need for mutual exclusion here as you do not increment or decrement your flag but set it to an absolute value, so even if your isr interrupts your task in between, say, mov#0,r4 and mov r4,somewhere, youf flag will eventually be consistent, no?

Yes this is short time but it will be repeated for number of times and by 4 tasks in my application. Tasks wait for user to press the external switch and hence tasks will be in this loop reading the switch after task delay of 50ms. Hence, it will disable interrupts for number of times by different tasks. In such a case if any data transfer is in progress on hardware interfaces like UART, I2C and SPI, which are on interrupt based in my application then it will disturb those transfer repeatedly.
Can I use same mutex in ISR with no wait option in ISR?

As has been pointed out, reading and writing a Boolean variable should be atomic on most processors. The only reason I see to need exclusion is if you want to make sure that only ONE task sees the flag high for each time it is set.

If that is the case, then the ISR doesn’t need protection, as it is just setting the flag, so, unless the ISR is checking it first to somehow mark an error if it is still set, there is no exclusion needed for it.

That said, if you do need the exclusion for reading, so only one task gets the flag per setting, I would likely just define a simple inline function like:

Inline bool getflag() {
    vtaskENTER_CRITICAL();
    bool flagval = global_flag;
    global_flag = false;
    vtaskEXIT_CRITICAL();
    return flagval;
}

Note, the critical section is VERY short, and will delay ISRs by only a few cycles. You could use a mutex instead, but that costs the tasks a lot more time.

Only ONE task sees the flag high for each time it is set - is not required. ISR does not check before setting the flag, it just set the flag high. So in this case, from your explanation does this mean I don’t need any kind of exclusion here?

If this is true

you probably need it. Otherwise there would be race conditions.
But to be honest I don’t fully understand what you’re trying to achieve.
Do you want that all tasks get notified by the external switch ?
Then a single, global flag is probably not the right way.

Let me provide more details.
Task 1 - read external temperature sensor and starts beeping alarm if temperature reading goes beyond certain value. In such a condition this task starts monitor external switch. If switch pressed (boolean varable set) it stops beeping sound. It also reset the boolean variable.
Task 2 - Same as task 1 but read another temperature sensor.
Task 3 - Monitor switch every 1s. If switch is pressed for > 5s then shutdown the device.
ISR - set the boolean variable if switch is pressed - high to low edge triggered interrupt.

All 3 tasks are executing and ISR is called whenever switch is pressed. So there is only one boolean variable which is monitored by all 3 tasks. Please let me know if this is correct approach.

So, how do the tasks decide if they are to reset the variable?

It almost sounds like each task could just read the switch itself and do what it needs (no resetting) or the ISR sets and clears the flag as it sees the switch pressed and released.

The alert tasks just read before beeping and stop if set, the monitor task just reads it periodically. Side note, the way you are defining it, someone just mashing the button fast might or might not trigger the monitor task.

If Task 1 or Task 2 or both waiting for the switch press then whenever any one of them get the flag set (i.e. switch press) then it will reset the flag and stop beeping.
ISR only set the flag. It does not reset the flag.
You are correct for monitor task - it needs continuous switch press condition for 5s.

If you want to stick to the approach a polling a variable by the 3 tasks you could define a flag per task (i.e. have 3 flag variables) that each task gets its own flag to handle independently.
Or just use a counter variable counted up on each trigger in the ISR. The tasks could read and store the (initial) counter value on startup in a task local variable and can poll/detect if the counter has changed later on. On change the task local variable is updated to the current counter value. Maybe that’s sufficient for your use case.
That would avoid any potential race condition and the need to protect the access to the variable.

The issue is that if neither Task 1 or Task 2 reset the flag, it will never get reset. If Task 3 resets the flag, it might do it before Task 1 or Task 2 see it, so the silence operation gets lost.

Also, as I was mentioning, Task 3 has no way to know the button has been continously depressed. It only knows that it was pressed 5 times in a row at the exact second mark.

Oh…this is a problem. If somebody press the switch then ISR will set the flag. Now when Task 1 or Task 2 starts beeping, it will immediately get flag in set condition and hence it will stop the beeping again. Practically no sound even though there is alarm condition occurred. Is my understanding correct? What is your suggestion for implementing this solution?

Update: Referring to my code I noticed that Task 3 does not take switch as pressed just on condition of flag. It also check MCU GPIO on which switch is connected. If flag is set and GPIO input is high then Task 3 increments it’s counter. If it find this condition in a row at the exact second mark then it shutdown the device or else it reset the flag. And hence the condition I described does not occur for Task 1 and Task 2.

As I said, Task 1 and Task 2 really just need to check if the switch is currently pressed. That does mean you need to hold the button long enough to get to the point in the beep loop it is checked.

If you want to remember so you don’t need to push for that long (if a slower beep), then one option would be to check the physical switch when you start a NEW alert, and if not pressed reset the flag. Thus only a press AFTER the alert starts gets recorded.

Note, it does sound like you want the interlock so only one task sees the flag, so if both alarms trigger, it takes two presses to clear both, instead of one press OCCATIONALLY clearing both.

As Task1 and Task 2 are running every 50ms, practically long button press is not required.

Yes, when Task 1 or Task 2 starts alert, they reset the flag.

No, actually I want both tasks to stop beeping on single button press.

So what about the proposal using a button press counter posted before ?
That would allow that Task1 and Task2 (and Task3) can handle button events independently and no concurrent reset of a flag variable is needed.

Please confirm my understanding about using button press counter:
ISR - only increments button press counter
Task 1 & Task2 - When alert condition arrives, it reads existing value of button press counter and keeping comparing if it change due to next button press.
Task 3 - In this task, I will have to check change in counter value as well as GPIO condition. Because user will press the button and hold for >5s, so ISR will be called only once to increment counter by 1 as it is falling edge triggered interrupt.
If above understanding is correct then coming back to original question - do I need mutual exclusion for accessing button press counter?

Yes, exactly. And since you do not concurrently modify the global counter - it’s only counted up by the ISR - you don’t need to exclude anything from accessing it. The tasks just read it.
Assuming write/read an integer (the counter) is atomic as it is on all platforms I’m aware of.

1 Like

Thankyou…I will go ahead using counter. My MCU is 32-bit and I think it does has atomic read/write, still I will check with MCU supplier.