Hi,
I'm looking for solution, how to make atomic execution block on Cortex-M3 uC. What is the best solution for this?
Best regards, Lukas
Any particular reason why disabling interrupts at the beginning of the block, and re-enabling them at the end, is not feasible ?
The reason is that some instruction blocks in my code can't be interrupted, otherwise it produces a lot of problems. I've prepared some macro for this purpose:
/** * Global interrupts disabling macro. This function disables all of possible * interrupts sources, by filling with 1 the "Interrupt Clear-Enable Register" * @return 1, always */ static __inline u32 __IntDisable(void) { *(CLRENA+0) = 0xFFFFFFFF; *(CLRENA+1) = 0xFFFFFFFF; return 1; } /** * Creates a block of code that is guaranteed to be executed * atomically. Upon entering the block the "Interrupt Set-Enable Register" * are saved, and restored upon exiting the block. * !!!! WRARNING - never call return from inside ATOMIC_BLOCK(), use RETURN_ATOMIC() instead !!!! */ #define ATOMIC_BLOCK() for ( u32 _SETENA_0 = *(SETENA+0), _SETENA_1 = *(SETENA+1), __ToDo = __IntDisable(); \ __ToDo ; \ __ToDo = 0, *(SETENA+0) = _SETENA_0, *(SETENA+1) = _SETENA_1 ) /** * This function makes return from inside atomic block. It works almost like normal return, but before * returning all SETENA registers are resotred. */ #define RETURN_ATOMIC(_value) { *(SETENA+0) = _SETENA_0; *(SETENA+1) = _SETENA_1; return _value;} \
It seems to works very good, there is only one disadvantge with return instruction. It can't be called from inside ATOMIC_BLOCK(). Solution for this is using something like __attrib__(__cleanup__) just like in GCC compiler. But I can't find antything similiar in ARMCC. Any idea ?
Lukas
Have you try using __disable_irq() and __enable_irq()? www.keil.com/.../armccref_cjaeaeha.htm
They are built-in intrinsic that set and clear PRIMASK. When PRIMASK is set, only NMI and hardfault handler can execute.
I am always suspicious about disabling/enabling interrupts to make a piece of code atomic. With these pipelined cores it often is possible that an interrupt is still taken between the instruction disabling the interrupts and the next instruction. If/when this happens, the whole interrupt service is run with interrupts disabled, and then the atomic code. No problem if you don't have any time critical interrupts, but if you do, this may cause you to miss the deadline. I don't know if this applies for the Cortex-M3 though.
With these pipelined cores it often is possible that an interrupt is still taken between the instruction disabling the interrupts and the next instruction.
The exact workings of the interrupt system of a MCU should be described, in great detail, in the datasheet.
Also, if the interrupt is serviced between the "disable interrupts" instruction and the next (which is the first of the atomic block), then the atomic block is still that - atomic. It's not being interrupted.
The problem is that some processors will perform at least one instruction after the "disable interrupt" instruction, so one or more nop instructions may be needed.
The exact workings of the interrupt system is seldom fully documented in the data sheets :(
Sometimes the information about the need for nop instructions are only available in application notes. And sometimes the application notes are not even for the specific processor, but for a different processor in the same - or similar - family. In the end, you may have to call the manufacturers support engineers to get the answer...
On some processors, the disable interrupt function might take sometime to take effect. This is because the masking is done via control register accesses through the bus. (there could be wait state on the bus and the write operation could be buffered, and the interrupt controller might need a cycle to update to the new setting).
On Cortex-M3, the interrupt mask registers are inside the processors (e.g. FAULTMASK, PRIMASK). As soon as PRIMASK/FAULTMASK/BASEPRI is set, the effect come immediately. So if you set the PRIMASK and the interrupt arrived at the same time, the interrupt will have to wait until the mask is cleared. To do this, the interrupt mask registers are accessible by CPS, MSR and MRS instructions only, for example:
CPSID i ; Set PRIMASK, disable interrupts CPSIE i ; Clear PRIMASK, enable interrupts CPSID f ; Set FAULTMASK, disable interrupts & hard fault handler CPSIE f ; Clear FAULTMASK, enable interrupts & ; hard fault handler
In C, the function
__disable_irq() translate to CPSID i
and
__enable_irq() translate to CPSIE i
These functions are also available for other ARM cores (e.g. ARM7TDMI). But instead of changing PRIMASK, __disable_irq change the "I" bit in CPSR.