All the time in the World

Our previous program was not very efficient and burned CPU clock cycles and power just to create a delay , wouldn't it be better if the CPU could be interrupted at the correct time and flip the LED only when it needed to ? If so then we would have all the time in the world to do other things or just let the CPU sleep and save on our battery.

Well we can by using the stm8s105's timers. The timers available are very sophisticated an we will only touch on some of the capabilities here. The stm8s105C6T6 has a total of 4 Timers at it disposal.

  Resolution Capture/Compare
 Max Prescale
 Timer 1
 16 4 Up / Down
 3 Yes Yes 65536
 Timer 2
 16 3 Up 0 No No 32768
 Timer 3
 16 2 Up 0 No No 32768
 Timer 4
 8 - Up 0 No No 128

Timers can be used for a variety of purposes, including:

● Time base generation
● Measuring the pulse lengths of input signals (input capture)
● Generating output waveforms (output compare, PWM and One Pulse Mode)
● Interrupt capability on various events (capture, compare, overflow,update)

We are going to use Timer 2 and its capability to send an interrupt when it is updated.

Essentially timers are counters and Timer 2 can be used in it's most basic way to count upwards from zero until a specific value has been reached , it is then reset back to zero and starts again triggering an interrupt at that time.

The timers take their clock source from the Master Clock but can be prescaled in order to lower the clock frequency that is counted. This is done for Timer2  by setting the lower 4 bits in the TIM2_PSCR register.
The prescale divide is calculated as 2 to the power of TIM2_PSCR as listed below:

 Prescale   Divide
 Timer Clock Freq
 @ fMaster=16mhz
 0x00 0 1 16 mhz
 0x01 1 2 8 mhz
 0x02 2 4 4 mhz
 0x03 3 8 2 mhz
 0x04 4 16 1 mhz
 0x05 5 32 500 khz
 0x06 6 64 250 khz
 0x07 7 128 125 kz
 0x08 8 256 62.5 khz
 0x09 9 512 31.25 khz
 0x0A 10 1024 15,625 khz
 0x0B 11 2048 7.8125 khz
 0x0C 12 4096 3.90625 khz
 0x0D 13 8192 1.953125 khz
 0x0E 14 16384 976.5625 hz
 0x0F 15 32768 488.28125 hz

Using the Maximum prescale divide we have to count to 488 clock pulses to get an interrupt about every second.

The Procedure is as follows ..

  1. Prescale the timers clock by 32768 by placing 0x0F into the TIM2_PSCR prescale register , this must be done by loading both the High and Low bytes separately with the Highest first as per technical reference.
  2. Place 0x01E8 (488 in decimal) into the Auto Refresh Register TIM2_ARR this is the value we count to before returning to zero.
  3. Enable interrupts on timer update so we get an interupt when the counter is updated ( reset to zero ) after 1 second.
  4. Set the CEN bit on the Timer Control Register 1 to enable the timer
  5. Reset the CPU interrupt mask so interrupts are enabled with the rim instruction , sim would disable them again by the way
But first we must set up an interrupt handler to catch the interrupt when it happens. In this interrupt hander we toggle the LED state as we have done so previously.

; Timer 2 update interrupt handler
    interrupt Timer2UpdateInterupt
    bres     TIM2_SR1,#0         ;reset interrupt flag
    bcpl  PD_ODR,#0              ; toggle the led

Then place its address in the interrupt table.

  dc.l {$82000000+Timer2UpdateInterupt}   ; irq13 TIM2 update/overflow

The directive interrupt Timer2UpdateInterupt is to tell the debugger that this is an interrupt routine , otherwise interrupt routines are like subroutines but return with the iret instruction, when an interrupt routine is triggered all of the CPU's registers are preserved on the stack so you don't have to worry about modifying them , the iret instruction restores the registers to there previous values before the interrupt routine was called , the execution of the program then continues for the point is was interrupted

One final point we need to reset the timers interrupt flag in the timer2's Status Register 1 (TIM2_SR1) to signal that it has be handled, otherwise the interrupt will keep on triggering.

Now the complete code ..

    #include ""   
    segment 'rom'
CLK_CKDIVR    EQU $50C6        ; Clock divider register
PD_ODR        EQU $500F        ; Port D data output latch register
PD_DDR        EQU $5011        ; Port D data direction register

TIM2_CR1      EQU $5300        ; TIM2 Control register 1
TIM2_IER      EQU $5301        ; TIM2 Interrupt enable register
TIM2_SR1      EQU $5302        ; TIM2 Status register 1
TIM2_PSCR     EQU $530C        ; TIM2 Prescaler register
TIM2_ARRH     EQU $530D        ; Auto Refresh High
TIM2_ARRL     EQU $530E        ; Auto Refresh Low


    mov CLK_CKDIVR,#$0                    ; set max internal clock

    ldw X,#$stack_segment_end     ; initialize SP
    ldw SP,X
    ldw X,#$ram0_segment_start  ; clear all the ram
    clr (X)
    incw X
    cpw X,#$stack_segment_end
    jrule clear_ram
; now it is now safe to use the stack

; init our program
    bset  PD_DDR,#0            ;PD0 is set to output mode                   

; init timer 2
    mov TIM2_PSCR,#$0F         ;timer 2 prescaler div by 32768
    mov TIM2_ARRH,#$00         ;msb must be loaded first
    mov TIM2_ARRL,#$08         ;we need 488 decimal (0x01E8 for about 1hz
    bset TIM2_IER,#0           ;set bit 0 for update irq's on irq13
    bset TIM2_CR1,#0           ;set CEN bit to enable the timer
     rim                        ;reset the cpu interupt mask
; main program loop
    ;wfi                       ;uncomment this to wait for interrupts
                               ;for lower power, causes problems with
                               ;the debugger when used.
    jra loop_forever

; Timer 2 update interrupt handler
    interrupt Timer2UpdateInterupt
    bres     TIM2_SR1,#0         ;reset interrupt flag
    bcpl  PD_ODR,#0   
; The default interupt handler
    interrupt NonHandledInterrupt

; the interrupt table
    segment 'vectit'
    dc.l {$82000000+start}                                ; reset
    dc.l {$82000000+NonHandledInterrupt}    ; trap
    dc.l {$82000000+NonHandledInterrupt}    ; irq0 TLI External top level interrupt
    dc.l {$82000000+NonHandledInterrupt}    ; irq1 AWU Auto wake up from halt
    dc.l {$82000000+NonHandledInterrupt}    ; irq2 CLK Clock Controller
    dc.l {$82000000+NonHandledInterrupt}    ; irq3 EXTIO Port A external
    dc.l {$82000000+NonHandledInterrupt}    ; irq4 EXTI1 Port B
    dc.l {$82000000+NonHandledInterrupt}    ; irq5 EXTI2 Port C
    dc.l {$82000000+NonHandledInterrupt}    ; irq6 EXTI3 Port D
    dc.l {$82000000+NonHandledInterrupt}    ; irq7 EXTI4 Port E external
    dc.l {$82000000+NonHandledInterrupt}    ; irq8  reserved
    dc.l {$82000000+NonHandledInterrupt}    ; irq9  reserved
    dc.l {$82000000+NonHandledInterrupt}    ; irq10 SPI end of transfer
    dc.l {$82000000+NonHandledInterrupt}    ; irq11 TIM1 Update/overflow/underflow/trigger/break
    dc.l {$82000000+NonHandledInterrupt}    ; irq12 TIM1 Capture/Compare
    dc.l {$82000000+Timer2UpdateInterupt}   ; irq13 TIM2 update/overflow
    dc.l {$82000000+NonHandledInterrupt}    ; irq14 TIM2 capture / compare
    dc.l {$82000000+NonHandledInterrupt}    ; irq15 TIM3 Update/ overflow
    dc.l {$82000000+NonHandledInterrupt}    ; irq16 TIM3 Capture / Compare
    dc.l {$82000000+NonHandledInterrupt}    ; irq17 reserved
    dc.l {$82000000+NonHandledInterrupt}    ; irq18 reserved
    dc.l {$82000000+NonHandledInterrupt}    ; irq19 I2C
    dc.l {$82000000+NonHandledInterrupt}    ; irq20 Uart2 Tx Complete
    dc.l {$82000000+NonHandledInterrupt}    ; irq21 Uart2 Receive Register Data Full
    dc.l {$82000000+NonHandledInterrupt}    ; irq22 ADC1 end of conversion
    dc.l {$82000000+NonHandledInterrupt}    ; irq23 TIM4 Update/Overflow
    dc.l {$82000000+NonHandledInterrupt}    ; irq24 Flash EOP/WR_PG_DIS
    dc.l {$82000000+NonHandledInterrupt}    ; irq25 reserved
    dc.l {$82000000+NonHandledInterrupt}    ; irq26 reserved
    dc.l {$82000000+NonHandledInterrupt}    ; irq27 reserved
    dc.l {$82000000+NonHandledInterrupt}    ; irq28 reserved
    dc.l {$82000000+NonHandledInterrupt}    ; irq29 reserved


This will blink the LED every second , you may have noticed the wfi instruction commented out, This is "wait for interrupt" and it does just that puts the CPU in a low power state while it waits for an interrupt to happen the code the executes after it, which will just call wfi again in this case. However from some reason it interferes with the debugger and so it is left commented out. Just uncomment it for a release version.

We can use multiple timers together , try adding the code below that uses the low resolution 8 bit timer 4 in order to slow down the LED over time.

TIM4_CR1        EQU $5340        ; TIM4 Control register 1
TIM4_IER        EQU $5341        ; TIM4 Interrupt enable register
TIM4_SR            EQU $5342        ; TIM4 Status register
TIM4_PSCR        EQU $5345        ; TIM4 Prescaler register
TIM4_ARR        EQU $5346        ; TIM4 Auto-reload register

; init timer 4
    mov TIM4_PSCR,#$07     ;timer 4 prescaler div by 128
    mov TIM4_ARR,#$FF     ;
    bset TIM4_IER,#0        ;set bit 0 for update irq's on irq23
    bset TIM4_CR1,#0      ;set CEN bit to enable the timer   

    segment 'ram0'

led_speed            dc.b 0

    segment 'rom'

; Timer 4 update interupt handler
    interrupt Timer4UpdateInterupt
    bres     TIM4_SR,#0         ;reset interupt flag
    dec     led_speed
    jrne     skip
    inc     TIM2_ARRL

This code showed us how to use interrupts to blink the LED , however there is an even better way to blink the LED without using any CPU cycles , find out more in always on the pulse.

Atom Age,
5 Sept 2010, 08:04