I'm trying to figure out an issue with STM8 interrupts and ISRs, and I'm wondering if anybody here has any insights or experience.
I have some code where there is a function that needs to atomically modify a global struct variable, so in that function I was disabling interrupts (with
SIM instruction) before I modified values, then re-enabling them again (with
RIM) before exiting the function. However, I also need to call this function from within an ISR. Naively I just went ahead and did that, but was puzzled to then encounter a problem where the microcontroller appeared to lock up, getting perpetually stuck in the ISR. I soon figured it was due to the use of
SIM/
RIM within the ISR.
I'm trying to figure out why exactly this causes things to go haywire.
Best I can figure is that it essentially boils down to the use of
RIM. My hypothesis is that when I call
RIM before the ISR is finished, the I0/I1 flags in the CC register get reset (to 1/0), which re-enables interrupts (or, technically, sets the interrupt priority level to 0), which causes the ISR to immediately be called again, basically interrupting itself. This then repeats ad-infinitum. Are my thoughts correct?
So, how can I avoid this?
In the
STM8S Reference Manual (RM0016), there is a part in section 6.2 that talks about a procedure for disabling and enabling interrupts within an ISR. But there are some things about this I'm not sure about.
Their recommendation is:
; To disable interrupts
PUSH CC
POP ISR_CC
SIM
; To enable interrupts
PUSH ISR_CC
POP CC
; ISR_CC is a memory location/variable used to store the CC value.
First of all, I don't understand why they are putting the CC value on the stack, then popping it off again to store in a memory location. Why not just leave it on the stack?
Second, when you return from an ISR with
IRET, that automatically restores the CC value that was originally saved automatically when calling the ISR. So, why bother saving/restoring it yourself separately? I'm thinking maybe this is aimed at the scenario where you want to disable interrupts only for a certain sub-section of the ISR, not the entire thing. I'm thinking that if you just wanted to disable all other higher-priority interrupts within an ISR, just execute
SIM at the start and leave
IRET to restore things for you?
So, a solution to my problem (needing to temporarily disable interrupts inside a function that accesses a non-atomic value, but this function is general-purpose, potentially called both outside and within ISRs) may be some variant of the above code. I was thinking of doing the following:
#define atomic_begin() do { __asm__("push cc"); __asm__("sim"); } while(0)
#define atomic_end() do { __asm__("pop cc"); } while(0)
These macros can then wrap the relevant section of code within my atomic-access function, and should work both within and outside ISRs. Is this going to work as I expect? Can anyone see any pitfalls in this?