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.
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:
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 ..
; Timer 2 update interrupt handler interrupt Timer2UpdateInterupt Timer2UpdateInterupt bres TIM2_SR1,#0 ;reset interrupt flag bcpl PD_ODR,#0 ; toggle the led iret 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 .. stm8/ #include "mapping.inc" 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 start 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 clear_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 loop_forever ;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 Timer2UpdateInterupt bres TIM2_SR1,#0 ;reset interrupt flag bcpl PD_ODR,#0 iret ; The default interupt handler interrupt NonHandledInterrupt NonHandledInterrupt iret ; 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 end 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 rim segment 'ram0' led_speed dc.b 0 segment 'rom' ; Timer 4 update interupt handler interrupt Timer4UpdateInterupt Timer4UpdateInterupt bres TIM4_SR,#0 ;reset interupt flag dec led_speed jrne skip inc TIM2_ARRL skip iret 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. |