czwartek, 31 maja 2012

ARM (STM32) - Blink Led and Interrupt Service Routine in ASM

UPDATE: No need to use GCC linker.


Most beginners tutorials in MCU's do not show how to set up and handle interrupts, and most of tutorials do not show how to code in ASM. Here is a complete tutorial showing how to blink a LED with STM32, set up IRQ and do that in ASM. Assembler is really simple and logic, just bare in mind that almost whatever you write in ASM will be translated straight to machine code. That's the whole secret.

First of all: I love FASM. It's very clear, simple and powerful. Programming huge, modern chips that run without any OS in ASM could be exhausting (there are almost no libraries and macro-instructions that could help out writing bare-metal code - maybe it should be created?) but it allows to understand how the chip works. Let's give it a try:

OS: Windows 7
Compiler: FASMARM download from here
Linker: LD from CodeSourcery none EABI GCC
Processor: STM32f103RB
Development board configuration: GPIOA P0 - button which couples to + VCC, P2 LED - hi state activates it.

Code:
 format elf                     ; Output format.  
 processor    37DA2FEh          ; Instruction encoding that fits our processor.  
 thumb                          ; This processor supports only thumb instruction set.  
 include 'vector_macro.inc'     ; Include file with some helpful macros dealing  
                                ; with vector table.  
 section ".text"  
 org 08000000h                  ; Offset address. Both this and placing code in  
                                ; section with an offset is necessary.  
 ; Begining of what goes into flash memory:  
         dw   Stack_Pointer            ; Stack pointer  
         vector _Start                 ; Reset vector  
         vector _NMI_handler           ; Some essential vectors  
         vector _HardFault_handler     ;  
         vector _MemManage_handler     ;  
         vector _BusFault_handler      ;  
         vector _UsageFault_handler    ;  
         default_handler 15            ; Skip 15 unused vectors  
         vector _EXTI_Line0            ; Our handler's address  

 _Start:  
         movlit r6, RCC_APB2ENR  
         movs r0, 4h                   ; GPIOA clock enable.  
         str r0, [r6]  

         movlit r6, GPIOA_CRL  
         movlit r0, 0x44444144         ; GPIOA config: P0 floating input  
         str r0, [r6]                  ; P2 - 10MHz output.

         movlit r6, EXTI               ; EXTI line 0 not masked.  
         movlit r0, 0x00000001  
         str r0, [r6]  

         movlit r6, (EXTI + 8)         ; EXTI0 rising trigger selected.  
         movlit r0, 0x00000001  
         str r0, [r6]  

         movlit r6, NVIC               ; Interrupt set enable.  
         movlit r0, 0x40  
         str r0, [r6]  

 ; Values stored in the GPIO output register:  
         movs r2, 0                    ; Turn off LED  
         movs r3, 4h                   ; Turn on LED  
         movlit r6, GPIOA_ODR          ; Point to PortA output data register.  

 loop:                                 ; Infinite loop. Turning the LED on and off.  
         str r2, [r6]                  ; Clear Port A, pin 2, turning on LED.  
         movlit r1, LEDDELAY  
 delay1:  
         subs r1, 1  
         bne delay1  
         str r3, [r6]                  ; Set Port A, pin 2, turning off LED.  
         movlit r1, LEDDELAY  
 delay2:  
         subs r1, 1  
         bne delay2  
         b loop  

 _EXTI_Line0:                          ; EXTI0 IRQ handler.  
         push {r6}                     ; Let's save whatever we modify on the stack.  
         push {r3}  
         push {r0}

         movlit r6, (EXTI + 0x14)      ; Clear pending IRQ. Otherwise the IRQ would be  
         movlit r0, 1                  ; permanently serviced.  
         str r0, [r6]  

         movlit r6, GPIOA_ODR          ; Point to PortA output data register.  
         str r3, [r6]                  ; Set Port A, pin 2, turning on LED.  

         add r9, 1                     ; When the exception takes place, r9 is being  
                                       ; incremented.  
         pop {r0}
         pop {r3}  
         pop {r6}  

         bx lr                         ; Branch to the address stored in link register.  

 _dummy:   
 _NMI_handler:   
 _HardFault_handler:   
 _MemManage_handler:   
 _BusFault_handler:   
 _UsageFault_handler:  
 _Default_handler:  
            add r0, 1                  ; In case of fault r0 is being incremented  
            b _dummy   
 ; Peripherals addresses:  
     GPIOA_CRL       =  0x40010800  
     GPIOA_ODR       =  0x4001080C  
     RCC_APB2ENR     =  0x40021018  
     EXTI            =  0x40010400  
     AFIO            =  0x40010000  
     NVIC            =  0xE000E100  

 ; Some config:  
     Stack_Pointer   =  0x20005000  
     LEDDELAY        = 1000000    

This is our code. First of all we put a couple of directives informing assembler how the code should be processed. We include a file with macro-definitions (it will be discussed later on). The very first thing that needs to be in the memory is the stack pointer. The second thing is the entry address where execution starts. First thing we do during the actual code execution is peripherals configuration. loop is an infinite loop that turns the led on and off. _EXTI_Line0 is a label defining our ISR.

Save the source in a file called example.ASM

File with some macro-definitions:
 macro vector name {dw name + 1}  
 macro default_handler repeat  
 {  
     rept repeat  
     {  
         dw _Default_handler + 1  
     \}  
 }       
 macro movlit reg,const {   
     local  .const,.skip   
     ldr   reg,[.const]   
     b    .skip   
     align  4   
   .const:   
     dw   const   
   .skip:   
 }  

Macro vector creates a label. Adding one to the address is essential cause in Thumb state every address loaded into memory must have LSB set. Macro default_handler creates a certain number of labels referring to a label named _Default_handler. It's necessary to skip unused vectors. Macro movlit is a way to load a const value into a register. Save the macro-definition file in the same place where the source file is and name it vector_macro.inc

Time for the linker script:
 SECTIONS  
 {  
     . = 0x08000000; /* start of flash */  
     .text : { *(.text)  }  
        
     .data :  
     {   
      *(.data)   
      *(.rom)  
     }  
      
     . = 0x20000000;  /*start of internal RAM */ 
     .ram : { *(.ram) }  
     .bss :  
     {  
      *(.bss)  
      *(.ram)  
     }   
 }                   

Save the linker script in link.ld

Compile the ASM source file. Your output is an object file called example.O Now we need to link the object file to create an executable file that could be loaded into flash memory. Use command:
$ arm-none-eabi-ld -v -T link.ld -o example.elf example.o 

Once we get the output file we can burn it. I'm using OpenOCD 0.5.0 and JTAG-lock-pick which is fully compatible with Amontec JTAGkey. Run OpenOCD with:
$ openocd -f /interface/jtagkey.cfg -f /target/stm32f1x.cfg

In a new console window connect with openocd server on port 4444:
telnet localhost 4444

Stop the core with:
reset halt

Move the example.elf file to your home folder and burn it in the flash memory:
flash write_image erase example.elf

After a successful download to chip you can start debugging the program. Button pressing causes External IRQ being serviced. To prove it reset the core:
reset

stop it:
halt

check the current register state:
reg

resume the core:
resume

Trigger the interrupt by pressing the button and stop the core one more time:
halt

Check the register state:
reg

you can see that R9 has been incremented during our ISR.

Use step command during halted to watch a single instruction being executed and use mdw address_number to read a word from a certain address. Have fun!

Update: 
To load the image without linking you need to change the line



 format elf


to:

 format binary as 'bin'


in the source file. Compile it with FASMARM. Fire up OpenOCD and load the image with:

flash write_image erase name_of_the_file.bin 0x8000000

The number in the command indicates the address where the flash memory begins - change it for different chips.

Brak komentarzy:

Prześlij komentarz