STM8L Discovery board timers and interrupts

Posted by Peter Ibbotson on August 08, 2021 · 8 mins read
Categories: Stm8
Tags: Stm8 , Cheapvideo

STM8L Discovery board timers and interrupts

So it’s time to talk about the STM8 timers and interrupts, code for this can be found on github in my stm8discovery board repository in the FlashLedTimer section. The code is in a ST Visual develop project and has several projects (you should select the correct one as the startup project)

https://github.com/peteri/stm8ldiscovery

We are going to build up to having a cheap video output with PAL timing, using the discovery board and the code is in the repository, but first we need to talk about interrupts.

Interrupts and timers why bother?

For our initial flash LEDs code we used a simple delay loop. This has the advantage of being quick to setup but it does come with some disadvantages.

  • Code is timing sensitive, if we do any other work our LEDs will flash at different rates.
  • We can’t do anything else while we wait.

So to help us out we’re going to use one of the STM8 timers (TIM2) to actually time our flashing and when that timer fires it’s going to raise an interrupt. Our main code will just be sitting in a tight loop doing nothing, when the interrupt happens the CPU will lookup the address of the interrupt handler, save away the registers on the stack and jump to the interrupt handler.

This means our timing is very accurate and our main code could be doing something else (or not), we could use the WFI (Wait for Interrupt) instruction to enter into a low power mode and the CPU will wait for the timer to fire.

Altering boardsetup.asm

We need to make some modifications to the board setup file to enable the timer and set our clocks up:

Setup the CPU clocks

Our initial code needs some changes in the init_cpu routine. The first change is we will add a routine to setup the CPU clock.

This change sets the system clock divider to be 1 so we run at the full 16Mhz available to us from the internal high speed clock. The other change we need to do is to turn on timer 2 by sending the clock to the timer (this is turned off by default to save power)

;
; Setup CPU
;
.init_cpu.w
  mov CLK_CKDIVR,#$00 ; Full speed 16Mhz
  bset CLK_PCKENR1,#0 ; Send the clock to timer 2
  ret

Finally we have prefixed our label for the routine with a full stop “.” which tells the assembler to make our label public. We also need to add our newly public label to the boardsetup.inc file as an EXTERN declaration.

Setup the timer

Next we want to setup the timer, we want to have a interrupt every 500ms (to give a 1Hz rate) so we divide the main 16Mhz clock by 128 then setup the timer so every 62500 cycles we get an interrupt.

;
; Setup timers
;
.init_timers.w
  mov TIM2_PSCR,#$07 ;timer 2 prescaler div by 128
  mov TIM2_ARRH,#$F4 ;msb must be loaded first
  mov TIM2_ARRL,#$24 ;we need 62500 for about 1hz
  bset TIM2_IER,#0   ;set bit 0 for update irq's on irq19
  bset TIM2_EGR,#0   ;Trigger update event so preload-registers copy
  bset TIM2_CR1,#0   ;set CEN bit to enable the timer
  ret

We also tell the timer to generate an interrupt every time the timer is reloaded. Finally we force an update event so the timers preload registers copy over when the timer is enabled.

Altering main.asm

We also need to make some changes to main.asm to handle the interrupt.

Add our code to handle the interrupt

First add a handler for the interrupt.

;=========================================
; Interrupt handler for timer 2
; toggles the led.
;=========================================
  interrupt TimerTwoInterrupt
TimerTwoInterrupt.l
  bres TIM2_SR1,#0
  call ledtoggle
  iret

This code is fairly simple, first it clears the interrupt by writing to the TIM2 status register. If we don’t do this we won’t get any more interrupts from the timer. It then calls our original toggle the leds routine to make them flash. Finally it returns from the routine with the iret instruction. When an interrupt happens the stm8 pushes the registers onto the stack before calling the interrupt, the iret instruction pops the registers off the stack before returning, it also clears the flag that tells the CPU to allow interrupts.

You may notice that there is also the pseudo instruction interrupt at the top of the interrupt handler. This tells the debugger that this routine is an interrupt handler and when it’s walking the stack to skip over the saved registers.

Finally our label has a .l on the end of it, the stm8 supports more than 64K of program memory (although most of the parts don’t have it physically) we need to have a .l on the end as we can be called from anywhere and the CPU needs a full 24 bit address to call us.

Setup the vector

Next we setup the vector by adding our handler. For the STM8L152 we’re using, timer 2 is on irq 19, so in the interrupt vector table we need to modify entry 19:

  dc.l {$82000000+NonHandledInterrupt} ; irq18
  dc.l {$82000000+TimerTwoInterrupt}   ; Timer 2
  dc.l {$82000000+NonHandledInterrupt} ; irq20

Not a huge change but required so the CPU knows what to do when the Timer 2 interrupt fires.

Alter the main routine

Finally in our main code section we need to do a few more things, remove the call to our delay routine, add a call to our cpu and timer initialisation routines and enable interrupts.

  call init_cpu
  call clear_memory ; Clear rest of ram
  call init_gpio    ;setup the gpio pins
  call init_timers
  rim                ; turn on interrupts
infinite_loop.l
  jra infinite_loop

Our infinite loop no longer does anything. Note the rim instruction which enables interrupts in the CPU, without it our interrupt won’t happen.

The rim instruction can also be used to allow interrupts to nest and re-enter themselves, this can be useful sometimes and we’re going to see an instance when we finish doing our cheap video where we use it. The stm8 part we’re using has some fancier nesting options in hardware which are more useful still. These are covered in the stm8 documentation.