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.