STM32 address space has size of 4Gb. Lowest address is 0x0000_0000
, highest address is 0xFFFF_FFFF
.
Address space is split in regions.
0x0000_0000
is a boot region. It contains reset vector.0x0800_0000
is a Flash memory region. This region may be used to store read only data and code.0x4000_0000
is a IO base address. All IO functions are mapped to this region.Microcontrollers and microprocessors are initially configured using reset vectors. Reset vector is a memory region at the predefined address that contains addresses of functions (pointers to the functions) to be executed in case of a specific event. These functions are called handlers. For example, reset handler is a function that is called when microprocessor starts; HardFault handler is called when runtime error occurs. A number of IRQ (Interrupt request) handlers are called when hardware interrupt occurs.
ARM microcontrollers and microprocessors have slightly different reset vector implementations. ARM microprocessors reset vector contains assembly instructions to be executed. It usually looks like a number of branch instructions stacked together:
...
b Reset_Handler
b Hardfault_Handler
...
ARM microcontrollers have reset vector looks different. First address of the reset vector is actually an address of the stack beginning. Following are addresses of the handlers. So, ARM microcontroller reset vector looks something like this:
.word _stack_start
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
...
STM32 have three boot modes: RAM, Flash and System. System boot mode uses preloaded bootloader and will not be discussed. Boot modes are controlled by boot mode pins. When one or the other boot mode is selected, boot region 0x0000_0000
is mapped (aliased) to Flash or RAM. For example, when using Flash boot mode, addresses 0x0000_0000
and 0x0800_0000
point to the same physical address in Flash chip.
Aliasing RAM requires some tricks. RAM is volatile therefore upon reboot its contents are not defined. In order to boot in RAM mode, small 2 word ROM is mapped to 0x0000_0000
. It contains constant values of stack (0x2000_5000
) and reset handler (0x2000_0108
).
Reset handler address is different for different density chips. Address
0x2000_0108
is for low density devices;0x2000_01CC
for medium density devices;0x2000_01E0
for others. These values may change in future.
ARM processors use memory mapped IO. All IO functions are mapped to the address space addresses and may be accessed and configured using regular STR
and LDR
memory access ARM instructions.
STM32 board has SMD LED connected to the PC13
pin. This pin may be controlled using General Purpose Input Output bus (GPIO). In order to prepare GPIO pin, two steps required:
After this LED may be turned on and off by writing appropriate values to GPIO register.
GPIO pins may be configured as inputs or outputs. Additionally, different input and output modes are supported. Detailed description of GPIO bus is given in chapter 9 of STM32 datasheet.
GPIO port C subsystem base address is 0x4001_1000
. All GPIO register addresses shell be expressed as an offsets of this address. Real register address is a sum of base address and offset.
GPIO port C pin 13 is controlled by register GPIOC_CRH
with offset 0x04
. Bits 20
-23
configure pin 13. By default output mode is selected (CNF13
= 0b01
, MODE13
= 00
). We will select output mode with open drain and 2 MHz speed (CNF13
= 0b01
, MODE13
= 10
). In order to select this option, we must set bits 20-23 of register GPIOC_CRH
to 0b0110
, i.e. set value of GPIOC_CRH
to (0b0110 << 20)
, where <<
is a left shift operation.
One must avoid changing bits of registers that are not relevant to current operation. In this case, setting GPIOC_CRH
to (0b0110 << 20)
will reconfigure GPIO port C pins 8-15. We will try to configure pin 13 without altering other pins configurations.
In general case we cannot assume that default values are used for other pins, therefore the first step is to read value of GPIOC_CRH
instruction and set pin 13 bits to 0
:
GPIOC_CRH = GPIOC_CRH & (~0b1111<<20)
After this, we apply our new settings:
GPIOC_CRH = GPIOC_CRH | (0b0110<<20)
This may be expressed as:
GPIOC_CRH = (GPIOC_CRH & (~PIN_CONFIG<<PIN_OFFSET)) | (PIN_CONFIG<<PIN_OFFSET)
This approach does not depend on particular GPIOC_CRH
, PIN_CONFIG
or PIN_OFFSET
.
STM32 provides additional mechanisms of register bit setting. These are beyond of this documents scope.
In order for GPIO to work, GPIO clock must be turned on. It is turned off by default in order to reduce power consumption. Detailed description of clock configuration is given in chapter 8 of STM32 datasheet.
Clock subsystem base address is 0x4002_1000
. GPIO port C clock is controlled by 4 bit in register RCC_APB2ENR
at offset 0x18
. In order to enable clock, we set this bit to 1
:
RCC_APB2ENR = RCC_APB2ENR | (1<<4)
In order to set GPIO port C pin 13 to particular value, we write to 13 bit of GPIOC_ODR
register at 0x0c
offset.
We can set pin voltage to logical 1
:
GPIOC_ODR = GPIOC_ODR | (1<<13)
We can set pin voltage to logical 0
:
GPIOC_ODR = GPIOC_ODR & (~1<<13)
Finally, we can toggle pin voltage:
GPIOC_ODR = GPIOC_ODR & (1<<13)
The following code goes inside _start
function in template code.
.syntax unified
.section .data @ 1
gpioc_base:
.word 0x40011000
gpioc_crh:
.word 0x04
gpioc_odr:
.word 0x0c
gpio_set:
.word 0x44144444 @ 2
rcc_apb2enr:
.word 0x18
rcc_base:
.word 0x40021000
rcc_set:
.word (1<<4) @ 3
led_on:
.word 0 @ 4
led_off:
.word (1<<13)
sleep:
.word 0x80000
.section .text
.global Default_Handler @ 5
.thumb_func
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.global Reset_Handler
.thumb_func
Reset_Handler: @ 6
ldr ip, =rcc_base
ldr r1, [ip]
ldr ip, =rcc_apb2enr
ldr r2, [ip]
ldr ip, =rcc_set
ldr r3, [ip]
add r0, r1, r2
str r3, [r0]
ldr ip, =gpioc_base
ldr r1, [ip]
ldr ip, =gpioc_crh
ldr r2, [ip]
add r0, r1, r2
ldr ip, =gpio_set
ldr r3, [ip]
str r3, [r0]
ldr ip, =gpioc_odr
ldr r2, [ip]
add r0, r1, r2
ldr ip, =led_off
ldr r1, [ip]
ldr ip, =led_on
ldr r2, [ip]
Setup: @ 7
loop:
str r2, [r0]
ldr ip, =sleep
ldr r3, [ip]
0:
cmp r3, #0
sub r3, r3, #1
bne 0b
nop
str r1, [r0]
ldr ip, =sleep
ldr r3, [ip]
0:
cmp r3, #0
sub r3, r3, #1
bne 0b
nop
b loop
nop
.end
Code explanation line-by-line: