noInterrupts/interrupts functions

I have a question (partly for the Spark engineers and partly for you ARM experts) about interrupts. I am moving from a project with a single ISR (for a radio module) to one that will now have two independent interrupts going (hardware timer, probably TIM4, and an external interrupt for the radio module).

In some cases, I need to do atomic things and disable interrupts briefly. The standard process would be:

noInterrupts();
… do some stuff …
interrupts();

BUT, I don’t necessarily want to just turn interrupts back on. It depends on where I’m executing this code from. If inside an ISR or other situation, I don’t want that. What I REALLY want is to get the current interrupts state, save it, disable interrupts for a bit, then restore that original state. Like:

state = getInterruptsEnabed();
noInterrupts();
… do some stuff …
setInterruptsEnabled(state);

I dug into the firmware code on Github to see what noInterrupts() is doing, and it’s disabling the interrupts for a set of “user exposed” interrupts. Code snippet below:

NVIC_DisableIRQ(EXTI0_IRQn);
NVIC_DisableIRQ(EXTI1_IRQn);
NVIC_DisableIRQ(EXTI3_IRQn);
NVIC_DisableIRQ(EXTI4_IRQn);
NVIC_DisableIRQ(EXTI9_5_IRQn);

I’m trying to figure out, for this “set” of interrupts, how to quickly check the enabled/disabled state. The above code implies that not ALL interrupts are being turned off on the uC, just some. I looked at the ARM docs and I guess you could access the BASEPRI register to see if all interrupts are on/off, but this doesn’t match up with the way noInterrupts() / interrupts() works, since some would always be on? I can’t seem to find an NVIC_* function to just check the state.

I’m sure I’m missing some simple/nice way to do what I’m asking. Can someone provide an approach?

Many thanks!
Joe

I hope this isn’t a stupid answer, but what would be changing the interrupt state besides your code? Couldn’t you just add a bool variable to track the currently set interrupt state?

Thanks for the reply @mumblepins. Only my code would be changing the interrupt state, but there are 3 threads running (the main one in loop() and two smaller ones driven off a hardware timer and an external interrupt). Both the hardware timer thread and the external interrupt thread use SPI to talk to a radio module. To prevent the SPI transactions from being messed up by an interrupt (and to prevent 2 of the threads from interfering with each other in regards to SPI access), I wanted to disable interrupts while in SPI transactions and then re-enable when done. The issue is I’m not certain I would know the correct state to restore (worried mostly about the potential for a nested function call where I’m re-enabling interrupts too early, and the calling function still needs interrupts disabled). But… I’m probably overthinking this. :smile:

I agree a global variable would be needed. I think using a shared mutex lock is the best way to solve this. I’ll just acquire the lock before disabling interrupts and then I’ll know the thread with the lock is allowed to re-enable them.

@jdr, I believe you can access the STM32F103 EXTI registers directly to read the Interrupt Mask bits for example:

enabled = EXTI->IMR & EXTI_IMR_MR0; //For interrupt mask on Line 0

Note that all the EXTI management registers are 32bits. :smile:

2 Likes

This is a very common requirement. Like the old V7 code:

s = spl6();
/*
 * Code that needs mutex from interrupts.
 */
...
splx(s);

I believe there are suitable functions exposed by the STM32 library, but I’m not in a position to troll through the source and look right now(*). I ended up using this exact behaviour for some experiments recently. Of course the function names were about 50 characters long, but the basic functionality existed.

Worth noting is that there are atomic operations available at the chip level, which are exposed via gcc. @mdma has started using them in the spark firmware, because I had to update my local compiler to use them :smile:. I know these fulfill a different need than spln()/splx(), but they are both useful tools.

(*) See also:day job.

2 Likes

Super helpful, thanks @AndyW! I’ll have to go searching through the Spark firmware code (unless @mdma can point me to where he’s using this stuff), but I also found the below info through the ARM docs. Seems they do have some fairly straightforward instructions (LDREX, SDREX) you can leverage to implement a mutex in ASM and then use it from C. I’ll play with them and see how it goes.

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ch01s03s02.html

Also, thanks @peekay123. Will look into those registers, too.

1 Like

Here’s my solution. I sanity tested it in a single thread and the assembly code is doing the right thing (correct value written to the mutex var when locking/unlocking). I will need to write a multi-threaded test later to make sure, but I’m pretty sure this is going to work. Here’s the code for others to use. Let me know if you see any issues? The functions return true on success (lock or unlock, respectively). e.g., if (__try_mutex_lock(&my_lock)) { … throwConfetti(); } :smile:

static inline bool __try_mutex_lock(volatile uint8_t *lock_var)
{
   const static uint8_t is_locked_val = 1;
   uint8_t __ex_flag, __res;

   asm volatile("Top:ldrexb  %0, [%2]      @ Load lock_var value into __res                                                            \n\t"
                "cmp         %0, %3        @ Compare __res to is_locked_val (is it locked?)                                            \n\t"
                "beq         Out           @ It's locked, branch to label Out (exit - __res will equal 1)                              \n\t"
                "strexb      %1, %3, [%2]  @ Attempt store-exclusive.  Store is_locked_val in lock_var and put op result in __ex_flag  \n\t"
                "cmp         %1, #1        @ Compare __ex_flag to 1 (did the store fail?)                                              \n\t"
                "beq         Top           @ It failed, branch to label Top and try the lock again                                     \n\t"
                "dmb                       @ Ensure prior memory changes take effect now                                               \n\t"
                "Out:                      @ Exit label - on success, __res = 0, __ex_flag = 0                                             "
                : "=&r" (__res), "=&r" (__ex_flag)
                : "r" (lock_var), "r" (is_locked_val)
                : "cc","memory" );

   return ((__res | __ex_flag) == 0);  // __res = 0 on success, 1 on failure to acquire lock
}


static inline bool __try_mutex_unlock(volatile uint8_t *lock_var)
{
   const static uint8_t is_unlocked_val = 0;
   uint8_t __ex_flag;

   asm volatile("ldrexb  %0, [%1]      @ Load lock_var value into __ex_flag (an ldrex op is REQUIRED before strex)                   \n\t"
                "strexb  %0, %2, [%1]  @ Attempt store-exclusive.  Store is_unlocked_val in lock_var and put op result in __ex_flag      "
                : "=&r" (__ex_flag)
                : "r" (lock_var), "r" (is_unlocked_val)
                : "cc","memory" );

   return (__ex_flag == 0);  // __ex_flag is 0 on success
}

volatile uint8_t  my_lock = 0;  // Init
1 Like

Nice bit of code! though you don’t have to roll your own. The GCC atomics were added to the 4.8 series of compilers. See __sync_bool_compare_and_swap et al.

https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

3 Likes