what is the best method for access to interrupt variables?

Status
Not open for further replies.

NikoTeen

Well-known member
If a program needs access to variables which are used/changed by an interrupt routine they have to be declared volatile.
That's clear so far, but what is the best method then to access such variables?

There are examples of code which use ATOMIC_BLOCK to copy the variable, here value, and then use value_copy outside the interrupt:
Code:
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
      {
        value_copy = value;
      }
Other ones use cli() and sei():
Code:
cli();
value_copy = value;
sei();

cli() and sei() are disabling/enabling all interrupts.
What happens with ATOMIC_BLOCK(ATOMIC_RESTORESTATE)?
 
They are the same. That ATOMIC_BLOCK stuff is just a fancy syntax which ultimately does the exact same global interrupt disable.
 
See https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

As far as I can tell
1) cli() - sei() disables all interrupts and then enables ALL interrupts
2) ATOMIC_BLOCK(ATOMIC_RESTORESTATE) also disables all interrupts but then only enables interrupts that were previously enabled.

So ATOMIC_BLOCK(ATOMIC_RESTORESTATE) would seem to be a more cautious and exact approach.

Furthermore the syntax
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
}
with opening and closing braces, seems, to my mind at least, to be stylistically preferable and less error prone.
 
As I recall, the pedvide ADC library has the obnoxious side effect of turning on interrupts.
 
I would hope cli() and sei() clear and set the global flag, which doesn't affect interrupt state,
merely defers handling. From what I gather with ARM interrupts there's a register for deferring
interrupts below a particular priority level, which would be more fine-grained way to implement
a critical section(*) where you know the priority of the interrupts of interest.

(*) https://en.wikipedia.org/wiki/Critical_section
 
You can also use the <atomic> utility to define atomic variables as shown here:
Code:
#include<atomic>

volatile std::atomic<uint32_t> myAtomicVar;  // define a uint32_t variable which ensures that all operations on it are atomic. 

void isr(){
    myAtomicVar++;                    // both, isr and normal code change the variable, normally leads to problems...
}

void setup(){
}

void loop(){
    myAtomicVar++;                     //safe to do this since we declared the variable  as atomic<uint32_t>. 

    Serial.println(myAtomicVar);
}

Here the corresponding disassembly of the increment in loop:
Code:
void loop()
{
      66:	push	{r4, lr}
      68:	dmb	ish
      6c:	ldrex	r3, [r2]           <----- ldrex & strex guards r3 (myAtomicVar)
      70:	adds	r3, #1             <----- myAtomicVar++
      72:	strex	r1, r3, [r2]
      76:	cmp	r1, #0             <-----   will be != 0 if myAtomicVar was changed by an interrupt between the ldrex/strex block -> repeat the operation
      78:	bne.n	6c <loop+0x8>
....
As you can see, the compiler protects the atomic variable by a ldrex/strex 'bracket' . The advantage is that you don't need to disable interrupts during the operation. In case myAtomicVar is changed by an interrupt in the guarded section the operation will repeat automatically.

Unfortunately for the ARM M processors this only works if the protected variable is < 4bytes wide. But it's an interesting concept anyway.
 
You can also use the <atomic> utility to define atomic variables as shown here:
Code:
void loop(){ ...
    myAtomicVar++;                     //safe to do this since we declared the variable  as atomic<uint32_t>. 
...

Here the corresponding disassembly of the increment in loop:
Code:
void loop()
{
      6c:	ldrex	r3, [r2]           <----- ldrex & strex guards r3 (myAtomicVar)
      70:	adds	r3, #1             <----- myAtomicVar++
      72:	strex	r1, r3, [r2]
....

Doing the T_4.x micros() used the ldrex/strex do assure two values read [millis tick and the CYCCNT when it was updated] were from the same milli time tick, and it works.

But the above seems it could end up with a double increment++ ? If the _isr changes it after the adds completes rather than before?

Also because this will - at least from the reading** found doing the micros() - repeat the code on ANY interrupt. It doesn't actually verify that specific bytes changed - just that any interrupt occurred.

Also if interrupts are off and code turns them 'off' then 'on' without reference to the incoming state - interrupts will be enabled on exit. AFAIK T_3.x micros() does this.


** Ref reading will be on the T_4.0 beta thread as it was done
 
Status
Not open for further replies.
Back
Top