Go Down

Topic: Replacement for AVR libc ATOMIC_BLOCK macros, now for DUE and other platforms. (Read 9 times) previous topic - next topic

pYro_65

[UPDATED V1.2.1b]

Hi all,

I have been working on a project that requires safety from interrupts so I did a bit of research, and found the ATOMIC_BLOCK macros.
They seem to be widely accepted as an easy way of controlling the global interrupt flag. After using it for a while I decided I'm not to keen on the syntax created from using the macros, my main reason is it looks like a function definition nested inside another function, which is not allowed in c++. Also, I have a need for some objects to be able to automatically control their atomic operations, I needed a more user friendly approach.

The idea I have come up with is quite simple. All you have to do is declare a variable of the class of blocking operation you want. The class has no callable methods. It works purely on side effects. The atomic operation lasts for the lifetime of the variable, therefore you can return from a function and still expect the global interrupt flag to be restored. This removes the need to use a temporary to store the value while the interrupt flag is restored before return.

Here is a comparison breakdown.

ATOMIC_BLOCK macros
Blocking type.

  • ATOMIC_BLOCK

  • NONATOMIC_BLOCK


Blocking mode.

  • ATOMIC_RESTORESTATE

  • NONATOMIC_RESTORESTATE

  • ATOMIC_FORCEON

  • NONATOMIC_FORCEOFF



New method. (V1.1)
Blocking type.

  • AtomicBlock

  • NonAtomicBlock


Blocking mode.

  • Atomic_RestoreState

  • Atomic_Force


Extensions for inline atomic blocks.

  • Protect function



V1.2 Now supports:

AVR 8-bit processors

  • Arduino 8-bit family ( Compiled / Tested )

  • Teensy 2.0 ( Not Compiled / Not Tested )

  • Teensy++ 2.0 ( Not Compiled / Not Tested )


ARM Cortex-M3

  • Arduino DUE ( Compiled / Not Tested )

  • Olimex STM32 / Leaflabs Maple. ( Compiled / Not Tested )


ARM Cortex-M4

  • Teensy 3.0 ( Compiled / Not Tested )



I have also extended the functionality with a safe mode ( v1.0) . ATOMIC_BLOCK( ATOMIC_RESTORESTATE ) simply writes an old copy of the status register into SREG. With the safe mode, only the global interrupt bit is touched. This can be used by adding a 'Safe' suffix to the blocking type, or passing  '_Safe' with the blocking mode.

There are 12 blocking variations, 8 of which are unique. A complete breakdown of the library is at the top of the file provided ( AtomicBlock.h ). Without safe mode on ( safe mode has no effect when using blocking mode 'Atomic_Force' ) the library will emulate ATOMIC_BLOCK functionality with no extra overhead.


Here is a short example of how to use the library with variable declarations.

Code: [Select]
#include "AtomicBlock.h"

void setup() { return; }

//New method
void loop()
{
 AtomicBlock< Atomic_RestoreState > a_Block;
 {
   NonAtomicBlock< Atomic_Force > a_NoBlock;

 }
 return;
}

/*
//Old method ( macros )
void loop()
{
 ATOMIC_BLOCK( ATOMIC_RESTORESTATE ){

   NONATOMIC_BLOCK( NONATOMIC_FORCEOFF ){
     
   }
 }
 return;
}
*/


The 'Protect' function usage.
Each blocking type contains a function named 'Protect'. It can be used to
protect any kind of element.

E.g.

   - Typedefs make using 'Protect' easier.
      typedef AtomicBlock< Atomic_Force > MyBlock;
   
      For the sake of these examples 'i' is an 'int', and inc is the funciton below.
      
Code: [Select]
void inc( int &i_Val ) { ++i_Val; }

   - Protected writes.
      
Code: [Select]
MyBlock::Protect( i ) = analogRead( A0 );

   - Protected reads.
      
Code: [Select]
Serial.println( MyBlock::Protect( i ) );

   - Protected non-member function calls.
      
Code: [Select]
MyBlock::Protect( inc )( i );

   - Protected pointers.
Code: [Select]
*( &MyBlock::Protect( i ) ) = 77;
(*MyBlock::Protect( &i ))++;

      
   - No unnessecary instructions.
   
   - This will not produce any code.
         
Code: [Select]
MyBlock::Protect( 1 + 2 + 3 + 4 + 5 );
      
   - These two lines produce exactly the same code.
Code: [Select]
digitalWrite( MyBlock::Protect( 13 ), digitalRead( MyBlock::Protect( 13 ) ) ^ 1 );
digitalWrite( 13, digitalRead( 13 ) ^ 1 );

      
      - This will only turn interrupts on and off once.
Code: [Select]
MyBlock::Protect( MyBlock::Protect( MyBlock::Protect( MyBlock::Protect( i ) ) ) );
      
      
CAUTION: 'Protect' will only protect one element. Statements as arguments are not going to work as you may expect.         
   E.g.
   
      - Wrong use. (argument statement will not be blocked. If you use the result, it will be inside the atomic block.)
         
Code: [Select]
MyBlock::Protect( PORTK |= _BV( 5 ) );
         
      - Correct usage. (just 'Protect' PORTK)
         
Code: [Select]
MyBlock::Protect( PORTK ) |= _BV( 5 );
         
LIMITATIONS:

   * I have chosen not to support any sort of member function protection. Once I have validated the current system
   I can look further into it. The required interface seems to generalise the type system too much and breaks some
   existing functionality as the compiler cannot disambiguate the control paths.

If you aren't logged in you can also download the code from 'google code' here.

I'm after any feedback/ideas for improvment.
Cheers,
Chris.

The files listed in this post are:

  • AtomicBlock.h This file is the original AVR stable version.

  • AtomicBlock_121b.h This file is the new multi-platform (beta) version.


westfw

Quote
[the old syntax] looks like a function definition nested inside another function

I dunno.  It looks like a pretty common macro structure to me; see the recent RunEvery macro, for instance.  ( http://arduino.cc/forum/index.php/topic,124974.0.html )  I've seen a lot of this sort of macro in major C programs (in some ways, it adds object-oriented behavior to C):
Code: [Select]
FOR_ALL_INTERFACES(idb) {
  FOR_ALL_PACKETS_IN_Q(idb->ipQ) {
    // Blah blah blah.
  }
}


Your new syntax isn't valid in C code...
And the old form is avr-libc "standard", extending well beyond Arduino.

pYro_65

#2
Oct 02, 2012, 02:50 am Last Edit: Oct 02, 2012, 02:59 am by pYro_65 Reason: 1

Your new syntax isn't valid in C code...
And the old form is avr-libc "standard", extending well beyond Arduino.


It is C++, not C macros.
And it does not use any gcc specific attributes making it far more portable.

From avr-libc for the ATOMIC_BLOCK macros.
Quote
Note:
The macros in this header file require the ISO/IEC 9899:1999 ("ISO C99") feature of for loop variables that are declared inside the for loop itself. For that reason, this header file can only be used if the standard level of the compiler (option --std=) is set to either c99 or gnu99.

WizenedEE


And it does not use any gcc specific attributes making it far more portable.

From avr-libc for the ATOMIC_BLOCK macros.
Quote
Note:
The macros in this header file require the ISO/IEC 9899:1999 ("ISO C99") feature of for loop variables that are declared inside the for loop itself. For that reason, this header file can only be used if the standard level of the compiler (option --std=) is set to either c99 or gnu99.



Along with this whole thing which is certainly not standard:
Code: [Select]

#define ATOMIC_RESTORESTATE uint8_t sreg_save \
__attribute__((__cleanup__(__iRestore))) = SREG

Coding Badly

Code: [Select]
         {
            NonAtomicBlock< Atomic_Force > a_NoBlock;
            // protected code goes here
            // destructor has to be called here or very bad things happen           
         }


You rely on the fact that the descructor is called precisely at the end of scope.  What happens if the optimizer realizes a_NoBlock is not used and invokes the destructor early?

Go Up