Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 7 of 7

Thread: what is the best method for access to interrupt variables?

  1. #1

    what is the best method for access to interrupt variables?

    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)?

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    23,027
    They are the same. That ATOMIC_BLOCK stuff is just a fancy syntax which ultimately does the exact same global interrupt disable.

  3. #3
    Member
    Join Date
    Jan 2020
    Location
    Port Elizabeth
    Posts
    53
    See https://www.nongnu.org/avr-libc/user...l__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.

  4. #4
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    717
    As I recall, the pedvide ADC library has the obnoxious side effect of turning on interrupts.

  5. #5
    Senior Member
    Join Date
    Jul 2020
    Posts
    468
    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

  6. #6
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,210
    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.

  7. #7
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,685
    Quote Originally Posted by luni View Post
    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

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •