For avr 8 bit micro controller a single bit ( flag ) must be set or cleared in some 8 bit integer variable. This set/clear function can be called from normal context ( main ) and also from interrupt handler ( Isr() ). As this, the variable must be made volatile
to prevent it from reordering or keeping it somewhere in registers. std::atomic
is not a valid option here, as there is no underlying OS nor a multi cpu core nor caches so some kind of memory fences are not needed. Even std::atomic
is not part of any avr c++ library.
The operation to set a flag is something like: some_flags|= new_set_flags
.
But with c++20
I get the warning: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
.
It is no problem to rewrite the functions by using a temporary variable, but it feels that this is not the intend of making the volatile keyword deprecated in this situation.
BTW: As the variable is stored in RAM, the cpu is not able to set a bit in the memory in a single assembler instruction. As this, the whole operation must be atomic. For that use case the avr-lib has ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
which simply disables interrupts.
#include <util/atomic.h>
volatile uint8_t some_flags;
void SetFlag( uint8_t new_set_flags )
{
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
uint8_t tmp = some_flags;
tmp |= new_set_flags;
some_flags = tmp;
... vs ...
some_flags|= new_set_flags; // main.cpp:65:19: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
}
}
void SomeIsr()
{
SetFlag( 0x02 );
}
int main()
{
SetFlag( 0x01);
}
Question: What is the "correct" way of setting a flag to a int variable if this flags/variable is used in different context on a bare metal controller with a single core without OS nor MMU.
It becomes very curious if you want to set a bit on a IO register, which is on AVR something like PORTA|=0x01;
and the compiler can take that action within a single assembler instruction.
#include <avr/io.h>
int main()
{
PORTA|=0x01; // main.cpp:57:10: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
}
0000006c <main>:
6c: d8 9a sbi 0x1b, 0 ; 27
6e: 90 e0 ldi r25, 0x00 ; 0
70: 80 e0 ldi r24, 0x00 ; 0
72: 08 95 ret
The rationale is that compound assignments or pre-post incrementations or decrementations are not atomic even on a volatile variable, while a programmer could see it as a single operation. Moreover, the standard says that E1 op= E2 is the same as E1 = E1 op E2 except that E1 is evaluated only once.
That means that non cautious programmers could use
volatile uint8_t some_flags;
...
some_flags|= new_set_flags;
with the expectation that it will be atomic, even in presence of hardware interruptions while it is not required to be.
At the machine level it looks like 3 operations:
load value from memory
update accumulator register
store value to memory
That means that without more precautions, a race condition occurs if 2 executions threads (here normal processing and an ISR) are interleaved:
normal loads
! ISR takes the processor
ISR loads updates and stores
! return from ISR
normal updates and stores erasing the change from ISR
When the program uses a temp variable it is evident that race conditions could occur.
What is bad for you, it that the C++ commitee has deprecated that use with the intention to later fully remove it.
So you can:
some_flags = some_flags | new_set_flags;
I prefere the last way because for a volatile byte, there are no reason that the compiler produces a less efficient code, and it is conformant from early C version to the last C++ one
References: