STM8 eForth Interrupts
Low-level Interrupts in Forth
Working with certain STM8S peripherals requires using low-level interrupts.
Interrupt service routines (ISR) can be coded in Forth with the help of the STM8 eForth lightweight-context-switch feature: almost all aspects are under user control as long as a certain usage pattern is observed. By default, the data stack is 8 cells deep, which should be more than sufficient for most applications. Low-level interrupts run with the highest "software priority". ISRs have a higher priority than the background task but one ISR can't preempt another.
Note that STM8 eForth also has a Background Task feature for executing input-processing-output code with a constant rate, and an Idle Task, e.g. for implementing protocols with string evaluation. These use cases don't require interrupt code.
Low level interrupt code depends on the following words:
SAVEC: save the context
IRET: restore the context and return from the interrupt
The very nature if interrupt code (real time, context change) makes interactive debugging difficult but it's possible to test ISRs using the interactive console. A good way to do that is to test words, e.g. buffer or protocol handlers, interactively before using them in a interrupt routine. The MODBUS and nRF24L01 libraries on the STM8 eForth Example Code were developed using mixed interactive and "pin-debugging" methods.
Architecture differences in STM8 Families
Unfortunately there are significant architecture differences between STM8S and STM8L which often require different implementations of ISRs. For STM8 family independent functionality in libraries it's necessary to separate low level interrupt configuration and peripheral handlers from higher level control.
As an example, the STM8 Families STM8S and STM8L have strikingly different architectures, and trade-offs for external interrupt configuration:
STM8S uses "configuration per port" for PA, PB, PC, PD and PE:
- STM8S assigns one interrupt vector and one edge/level configuration to a port (EXTI_CR1 for ports A through D, EXTI_CR2 for port E)
- interrupts are enabled or disabled per GPIO (Px_CR2)
- interrupts remain active as long as the interrupt condition is present
- the ports PF, PG, PH and PH can't produce external interrupts
- one TLI (top level interrupt) is assigned to either PC3 or PD7
STM8L provides more versatile, but also more complex features (see STM8L reference manual 12.6))
- configuration per port (PB/PG, PD/PH, PE/PF) or per GPIO index Px0 ... Px7 (PA, PB, PC, PD, PE) and PF0
- for both modes interrupts are enabled or disabled per GPIO (Px_CR2)
- interrupts remain active unless EXTI_SRx is cleared by writing a
1to the corresponding bit
Other peripherals often have less obvious or minor differences, however, sometimes key features, like STM8L USART DMA, make family dependent code rather attractive.
Wake-Up Timer Interrupt Example
As a simple example, the following code implements an AWU (Auto Wake Up) handler.
The AWU is used to provide an internal wake-up time base that can be used to return from the MCU "Active-Halt" power saving mode. This time base can be clocked by the low speed internal (LSI) RC oscillator clock or by the HSE crystal oscillator clock (both divided by a prescaler). Usage of the register names commencing with "AWU_" below are explained in the STM8S Reference Manual, Chapter 12.
RAM #require :NVM \res MCU: STM8S103 \res export AWU_APR AWU_TBR AWU_CSR1 INT_AWU DECIMAL :NVM \ interrupt handler, "headerless" code SAVEC AWU_CSR1 C@ IRET ;NVM ( xt ) INT_AWU ! : initawu ( -- ) \ AWU period about 1s 31 AWU_APR C! 12 AWU_TBR C! 16 AWU_CSR1 C! ; : HALT ( -- ) \ encodes the STM8 `HALT` instruction [ $8E C, ] ; RAM \\ Example initawu HALT \ test - will return after the AWU period
The "nameless" interrupt handler is initiated with
:NVM (put start address on stack, switch to
NVM mode, start compiler mode). It first does a Forth VM context switch with
AWU_CSR1 then clears the AWU interrupt flag, and
IRET restores the Forth VM context, and returns to with the STM8 IRET instruction. The stack can be left unbalanced (
DROP isn't required).
;NVM switches back to interpreter mode, and the start address of the handler (the execution token xt which is still on the stack) is then written to the AWU interrupt vector.
initawu initializes the Auto Wakeup Timer with about 1s delay, and the word
HALT encodes the STM8 HALT instruction that shuts down the CPU clock until an interrupt occurs.
initawu followed by
HALT is executed, the CPU stops, and returns through the execution of the Auto Wake-Up Timer interrupt.
Guidelines for STM8 eForth Interrupt Code
When writing interrupt handlers in Forth, the following practice is recommended:
- interrupts should only be used for low-level code, e.g. for using µC peripherals that require interrupts to work
- care should be taken regarding data- and return stack use (8 levels should be more than sufficient, the return stack must be balanced)
- only the data processing that's absolutely required should be performed in interrupt routines. The rest should be done in a low-priority task (e.g. background)
- the code should be fast - if in doubt use pin-debugging with a scope or a simple logic analyzer for testing the timing
- high-level words, e.g, output string formatting, should not be used unless a proper context switch has been done (example: background task code in
- when using character I/O potential side effects should be carefully assessed