The previous program was created to be the simplest yet easy to read program (it could be made smaller) to blink a LED from the st8sdiscovery board but it made some assumptions that would not exist in a more extensive program. You may have wondered why we ditched all the generated code the IDE created. It was in order to make things as simple as possible. Now we will look at what should be done in initialising a program and the stm8 MCU so it does not take its revenge on us later. Steps required to initialise the stm8 CPU
stm8/ #include "mapping.inc" segment 'rom' start 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 bset $5011,#0 ; init our program loop_forever ldw X,#$FFFF loop_delay nop decw X jrne loop_delay bcpl $500F,#0 jra loop_forever ; the default interupt handler interrupt NonHandledInterrupt NonHandledInterrupt iret ; the interupt table segment 'vectit' dc.l {$82000000+start} ; reset dc.l {$82000000+NonHandledInterrupt} ; trap dc.l {$82000000+NonHandledInterrupt} ; irq0 dc.l {$82000000+NonHandledInterrupt} ; irq1 dc.l {$82000000+NonHandledInterrupt} ; irq2 dc.l {$82000000+NonHandledInterrupt} ; irq3 dc.l {$82000000+NonHandledInterrupt} ; irq4 dc.l {$82000000+NonHandledInterrupt} ; irq5 dc.l {$82000000+NonHandledInterrupt} ; irq6 dc.l {$82000000+NonHandledInterrupt} ; irq7 dc.l {$82000000+NonHandledInterrupt} ; irq8 dc.l {$82000000+NonHandledInterrupt} ; irq9 dc.l {$82000000+NonHandledInterrupt} ; irq10 dc.l {$82000000+NonHandledInterrupt} ; irq11 dc.l {$82000000+NonHandledInterrupt} ; irq12 dc.l {$82000000+NonHandledInterrupt} ; irq13 dc.l {$82000000+NonHandledInterrupt} ; irq14 dc.l {$82000000+NonHandledInterrupt} ; irq15 dc.l {$82000000+NonHandledInterrupt} ; irq16 dc.l {$82000000+NonHandledInterrupt} ; irq17 dc.l {$82000000+NonHandledInterrupt} ; irq18 dc.l {$82000000+NonHandledInterrupt} ; irq19 dc.l {$82000000+NonHandledInterrupt} ; irq20 dc.l {$82000000+NonHandledInterrupt} ; irq21 dc.l {$82000000+NonHandledInterrupt} ; irq22 dc.l {$82000000+NonHandledInterrupt} ; irq23 dc.l {$82000000+NonHandledInterrupt} ; irq24 dc.l {$82000000+NonHandledInterrupt} ; irq25 dc.l {$82000000+NonHandledInterrupt} ; irq26 dc.l {$82000000+NonHandledInterrupt} ; irq27 dc.l {$82000000+NonHandledInterrupt} ; irq28 dc.l {$82000000+NonHandledInterrupt} ; irq29 end We have added the include file mapping.inc which was automatically generated for us, It defines stack_segment_end , stack_segment_start and ram_segment_start. We use these to initialise the stack pointer and clear all of the 2k of ram. We have also added a default interrupt handler NonHandledInterupt and set up the interrupt vector table. Some points to note. We can only load the Stack Pointer SP from another register not an immediate value , so we load it from the X register which has been set to that value. The clr(X) (clear) is an example of direct indexed addressing , the X register has the value of the memory location which is cleared to zero then the X Register is incremented to the value of the next memory location and clears that within the loop, This continues until the cpw (compare word) instruction finds that X is greater than the value of stack_segment_end. The interrupt table is started at the new segment 'vectit' which lies within the flash rom at address 0x8000 the beginning of the flash rom. The dc.l directive forces the long word given as its argument into the object code at the current address so the interrupt table is set up from 0x8000 to 0x8080. The iret instruction is used to return from an interrupt routine back to the code before the interrupt occurred. Wait a minute shouldn't the LED blink faster ?Yes it should when the st3s105 boots up it runs on the internal RC oscillator which is set to 1/8 of its frequency @ about 2mhz so in real world application we would probably want this to run as fast a possible as soon as possible.First lets slow down the LED blinking so if stays on and off for about 8 seconds. Change the code to have another inner loop using the other 16 bit register Y. We will then know if the clock has indeed been set to maximum by the fact the LED will light for about 1 second. bset $5011,#0 ; init our program loop_forever ldw X,#$FFFF loop_delay ldw Y,#$0050 loop_inner_delay decw Y jrne loop_inner_delay decw X jrne loop_delay The STM8S105C6 Clock"The clock controller is designed to be powerful, very robust, and at the same time easy to use. Its purpose is to allow you to obtain the best performance in your application while at the same time get the full benefit of all the microcontroller’s power saving capabilities. You can manage all the different clock sources independently and distribute them to the CPU and to the various peripherals. Prescalers are available for the master and CPU clocks."4 different clock sources can be used to drive the master clock: ● 1-24 MHz high speed external crystal oscillator (HSE) ( the discovery board is 16mhz) ● Up to 24 MHz high speed user-external clock (HSE user-ext) ● 16 MHz high speed internal RC oscillator (HSI) ● 128 kHz low speed internal RC (LSI) In order to speed up the internal clock we need to look at the Clock divider register (CLK_CKDIVR).In order to product the final CPU Clock it is divided twice once by HSIDIV and then by CPUDIV. CLK_CKDIVR 0x50C6
For HSIDIV1: HSIDIV0 00: fMaster = 16mhz 01: fMaster = 16mhz/2 = 8mhz 10: fMaster = 16mhz/4 = 4mhz 11: fMaster = 16mhz/8 = 2mhz For CPUDIV2: CPUDIV1: CPUDIV0 000: fCpu = fMaster 001: fCpu = fMaster / 2 010: fCpu = fMaster / 4 011: fCpu = fMaster / 8 100: fCpu = fMaster / 16 101: fCpu = fMaster / 32 110: fCpu = fMaster / 64 111: fCpu = fMaster /128 So to get the maximum speed we simply set this register to zero. By setting the maximum divide (0x1F) we can set the CPU clock as low as 15.625khz if we ever wanted to. segment 'rom' start mov $50C6,#$0 ; set the internal rc clock divide to 1 ldw X,#$stack_segment_end ; initialize SP ldw SP,X So adjust the code to the above and the LED should now blink for 1 second approximately. Hang on the Discovery has a 16mhz external crystal oscillator wouldn't that be better ?Yes it should be more stable and accurate than the internal rc clock, here is some code without any explanation .... segment 'rom' start
bset $50C5,#$1 ; initialize SP ldw SP,X Tidying up the codeTo make the code more readable we can use symbolic names for the hardware registers rather than refering to the absolute address in memory, to do this we use the EQU directive of the assembler, for instance
We can also make blink a subroutine , we call it blink_one blink_once ldw X,#$FFFF loop_delay ldw Y,#$0050 loop_inner_delay decw Y jrne loop_inner_delay decw X jrne loop_delay bcpl PD_ODR,#0 ret The subroutine is called from the main loop, loop_forever ; main program loop loop_forever call blink_once jra loop_forever What happens here is that the call instruction jumps to blink_once and the delay loops are executed, once it has blinked the LED it will find and execute the ret instruction, this tells the CPU to return to whence it came just after the original call instruction and carry on executing the instructions that follow. In this case it will execute the jra loop_forever instruction. Note that the memory address that the CPU will return to is held on the the stack which is pointed to by the SP register , this is why it is important to set-up the stack and SP at the beginning of the program in order to be able to call subroutines and use the push and pop instructions which we have not covered yet but also use the stack. The complete code of our tidy version stm8/ #include "mapping.inc" segment 'rom'
CLK_SWR EQU $50C4 ; Clock master switch register
CLK_SWCR EQU $50C5 ; Clock switch control register
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 start mov CLK_CKDIVR,#$0 ; set max internal clock bset CLK_SWCR,#$1 ; enable external clcok (clock is 16mhz) mov CLK_SWR,#$B4 ; switch in external cyrstal clock wait_hw_clock btjf CLK_SWCR,#$3,wait_hw_clock ; wait swif bres CLK_SWCR,#$3 ; clear swif 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 ; it is now safe to use the stack ; init our program bset PD_DDR,#0 ; PD0 is set to output mode ; main program loop loop_forever call blink_once jra loop_forever ; blink is now a subroutine ; this delays for about a second then toggles the ; LED On or Off blink_once ldw X,#$FFFF loop_delay ldw Y,#$0050 loop_inner_delay decw Y jrne loop_inner_delay decw X jrne loop_delay bcpl PD_ODR,#0 ret ; the default interupt handler interrupt NonHandledInterrupt NonHandledInterrupt iret ; the interupt table segment 'vectit' dc.l {$82000000+start} ; reset dc.l {$82000000+NonHandledInterrupt} ; trap dc.l {$82000000+NonHandledInterrupt} ; irq0 TLI External top level interupt 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+NonHandledInterrupt} ; 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 Recieve 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 code is not very efficient and wastes time and CPU cycels in the delay loop , next we will show how we can have all the time in the world |