On the Cortex-M processors, you typically use critical sections to isolate accesses of interrupt handlers from the main program. The assembler needed for this is pretty straightforward, the question is how do we best implement it in C++? First a version for Gnu C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class InterruptLock { public: InterruptLock() { __asm volatile( "MRS %[oldLevel], PRIMASK": [oldLevel] "=r" ( oldLevel) ::); __asm volatile( "CPSID I" :: "r"( oldLevel):); __asm volatile( ""::: "memory"); } ~InterruptLock() { __asm volatile( ""::: "memory"); __asm volatile( "MSR PRIMASK, %[oldLevel]": : [oldLevel] "r" (oldLevel):); } private: unsigned char oldLevel; }; |
Here we do a few things to ensure the compiler generates correct and optimal code for us:
- The saved interrupt status is accessed directly as a register. This allows the compiler to keep the status in a register if beneficial, and since you need to move it into a register for access anyways, no performance is lost.
- The fake dependency on line 5 ensures lines 4 and 5 are never swapped. We could do this in one
__asm
statement, but splitting it into two allows the compiler to insert a store between the two of them, if needed, which minimizes the time spent blocking interrupts. - Lines 6 and 9 are memory barriers which prevent the compiler from moving any memory accesses outside the lock.
- The
volatile
keyword is needed to ensure that the compiler does not optimize these statements away, as they appear to him to have no effect. - Be careful when calling it, it must be
InterruptLock var;
If you are using the Keil compiler, the lock would look pretty much the same. Here the intrinsic __schedule_barrier()
is used to ensure that the compiler does not reorder code across the lock.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class InterruptLock { public: InterruptLock() __attribute__((always_inline)) { register unsigned char PRIMASK __asm( "primask"); oldLevel = PRIMASK; __asm( "CPSID I"); __schedule_barrier(); } ~InterruptLock() __attribute__((always_inline)) { __schedule_barrier(); register unsigned char PRIMASK __asm( "primask"); PRIMASK = oldLevel; } private: unsigned char oldLevel; InterruptLock( const InterruptLock& other); InterruptLock& operator=( const InterruptLock&); }; |