When booting a Cortex-M3 based STM32F1 microcontroller from RAM instead of flash memory, understanding how to properly configure the vector table is crucial for getting the system up and running. The vector table contains the initial stack pointer value and the set of exception and interrupt vectors that tell the processor where to jump when handling events like resets, faults, and interrupts. Modifying the vector table to work with a RAM-booted system takes some care, but can be accomplished by following a few key steps.
Background on the Cortex-M3 Vector Table
The Cortex-M3 vector table is an array of function pointers residing at the very beginning of the microcontroller’s memory map. The pointers tell the processor where to fetch the interrupt service routines (ISRs) and exception handlers needed for that particular system. The vector table layout is defined by the Cortex-M3 architecture, consisting of the following entries:
- Stack pointer value on reset
- Reset handler
- NMI interrupt handler
- HardFault exception handler
- Memory Management Fault handler
- Bus Fault handler
- Usage Fault handler
- Reserved spots for future exceptions
- SVCall system call handler
- Debug Monitor handler
- Reserved spot
- PendSV interrupt handler for context switching
- SysTick interrupt handler
- External Interrupt handlers
By default on the STM32F1, the vector table resides in flash memory starting at address 0x00000000. The processor fetches the contents of the vector table on boot to determine the reset handler location and initial stack pointer value. All exception and interrupt handling during runtime also references the vector table to run the associated ISR code.
Challenges with Booting from RAM
Typically, microcontrollers boot out of flash memory where the vector table sits by default. But in some cases, designers may want to boot from RAM instead for greater flexibility or to allow for flash memory updates. When booting from RAM, the default vector table in flash will no longer be valid since code is executing from a different memory region. We need a way to redirect the processor to use a vector table located in RAM instead.
On the STM32F1, the boot configuration pins BOOT0 and BOOT1 determine which memory device to boot from. By setting BOOT0=1 and BOOT1=1, we can force a RAM boot, but this alone doesn’t solve the vector relocation issue. The processor will still expect the vector table to be at address 0x00000000 on reset.
Vector Table Relocation
To solve this, we need to place a small piece of code called a bootloader at address 0x00000000 in flash memory. This bootloader code will execute on reset, and its job is to copy the vector table from flash to RAM, and then configure the VTOR register to point to the new vector table location. The VTOR register tells the processor where to find the vector table on subsequent fetches. Once this relocation is complete, the bootloader can then jump to the reset handler contained in the new vector table, which will now be executing code from the RAM region.
Here are the steps the bootloader code needs to perform for vector relocation when booting the STM32F1 from RAM:
- Copy vector table from flash to RAM using memcpy or manually
- Update VTOR register to point to new vector table location in RAM
- Clear the RAM region to 0s before vector table copy
- Configure stack pointer value in RAM vector table
- Jump to reset handler in RAM vector table
The bootloader itself needs to be carefully crafted as position independent code since it lives at a hard-coded flash address. Coding the relocation steps in assembly language provides the most control.
Example Bootloader Implementation
Here is some example code showing one way to implement the vector relocation bootloader for RAM booting on the STM32F1: /* Bootloader code @ 0x00000000 in flash */ .section .bootloader, “ax” /* Copy vector table from flash to RAM */ ldr r1, =_vtable_ram ldr r0, =_vtable_flash mov r2, #0x100 bl memcpy /* Set VTOR to new vector table location */ ldr r0, =_vtable_ram ldr r1, =0xE000ED08 /* VTOR register address */ str r0, [r1] /* Set initial SP value in RAM vector table */ ldr r0, =_stack_top_ram ldr r1, =_vtable_ram str r0, [r1] /* Jump to reset handler in RAM vector table */ ldr r0, =_reset_handler_ram bx r0 .pool _vtable_ram: .word 0 /* Start of RAM vector table */ _vtable_flash: .word 0x08000000 /* Start of flash vector table */ _reset_handler_ram: .word 0 /* Reset handler in RAM */ _stack_top_ram: .word 0 /* Top of stack in RAM */
This simple bootloader starts by copying the vector table word-by-word from flash to a RAM location using memcpy. It then updates the VTOR register to point to this new RAM location. The stack pointer entry in the vector table is also updated to point to a valid RAM address for the initial stack. Finally, it jumps to the reset handler located in the new vector table.
With this bootloader code in place, the system will now look for the vector table in RAM after reset even when booting from the BOOT0/BOOT1 pins. The rest of our Cortex-M3 application code can reside in RAM and execute as needed.
Summary
Booting Cortex-M3 based microcontrollers like the STM32F1 from RAM requires relocating the vector table from flash to RAM. A small bootloader executing from flash memory on reset can handle this relocation using the VTOR register and some careful pointer manipulation. Implementing vector relocation opens up more flexibility for designers to execute code from RAM or update flash contents on the fly. With the right bootloader approach, RAM booting on Cortex-M3 devices like the STM32F1 can be achieved successfully.