A basic project is implemented to blink the LD2 LED pin on the STM32401RE and communicate with the computer via UART Serial Communication. This involved sending a simple "Hello World" message to a serial COM port at the press of the B1 User Button.
The startup functionality was modified using the Thumb Instruction Set on the ARM Cortex-M4 Core processor. This was done by going into the ASM and modifying it into C code and by relocating the Vector Table from flash memory to static random-access memory (i.e. SRAM).
The project files were generated using the STM32CubeMX Graphical Tool Software. The project configuration can be viewed and modified in the (Startup_Test.ioc
) file.
The USART2 peripheral is configured in Asynchronous Mode. We selected the Makefile toolchain to work with individually installed tools on the VSCode Editor.
Flashing the project onto the STM32 Nucleo Board required the ARM GCC C Compiler, Make Automation Tool, and the Open On-Chip Debugger (OpenOCD) Debugger for Embedded Devices.
These tools were added to the System Path on the Windows OS.
This project build and debug settings are specified in the (.vscode
) directory. The (launch.json
) and (c_cpp_properties.json
) were modified to integrate the debug functionality into VSCode.
The Cortex-Debug Extension made it easier to look at register contents during runtime.
Importing the System View Description from the (STM32F401.svd
) file in the launch settings gave the ability to view the peripheral register values during runtime as well.
The startup of the project was later optimized by translating the relevant functionality of the (startup_stm32f401xe.s
) ASM file in the (startup_stm32f401xe.c
) C file.
This required importing externally defined constants for memory addresses from the linker script (STM32F401RETx_FLASH.ld
).
// Start Address For the Initialization Values of the .data Section.
extern uint32_t _sidata;
// Start Address For the .data Section.
extern uint32_t _sdata;
// End Address For the .data Section.
extern uint32_t _edata;
// Start Address For the .bss Section.
extern uint32_t _sbss;
// End Address for the .bss Section.
extern uint32_t _ebss;
// Top of Stack.
extern uint32_t _estack;
The void Reset_Handler();
function is run when the program is flashed onto the MCU. This acts as the real entry point of our program before continuing onto our int main(void);
function. .
This initializes the data (i.e.initialized data) and bss (i.e. uninitialized data) segments of the flash memory. We explicitly run the void SystemInit();
function from the ST Board Support Package and void __libc_init_array();
to initialize the static objects and run their constructors.
We provide weak definitions for our Hardware Interrupts as function pointers as shown:
__weak void NMI_Handler(void) { Default_Handler(); }
__weak void HardFault_Handler(void) { Default_Handler(); }
__weak void MemManage_Handler(void) { Default_Handler(); }
__weak void BusFault_Handler(void) { Default_Handler(); }
...
...
__weak void SPI4_IRQHandler(void) { Default_Handler(); }
The functions defined here will be overwritten if there are other functions with the same name.
We allocate the Hardware Interrupts as a vector in a section in flash memory. This tells the processor where to jump in flash memory with the following lines of code:
__attribute__((section(".isr_vector")))
const void(*VectorTable[])(void) = { ... }
Based on the STM32F40x Datasheet, we fill in the Vector Table with the interrupt order specified in Table 43. Vector Table for STM32F40x and STM3241x.
The reference to the ASM startup file was removed and the reference to the C startup file was added in the (Makefile
).
The Vector Table was later relocated from flash memory to static random-access memory (i.e. SRAM).
This was done by modifying the Vector Table Offset Register (i.e. VTOR) in the (system_stm32f4xx.c
) source file.
We modify the System Control Block (i.e. SCB), which provides system implementation information and system control. It can be accessed only from a privileged thread. This is done with the following line of code.
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
SRAM is volatile memory, which means that data is lost when the power is removed. Due to this, we copy the Vector Table from the flash memory to SRAM.
This allows dynamically changing Exception Handler entrance points during runtime. Also, if the flash memory is very slow, this allows for faster vector fetch.
For ARM Cortex-M4 Core processors, the Vector Table is of the format below, where IRQ0 ... IRQ239 are the starting addresses for Device Specific Interrupt Service Routines (i.e. ISR).
We use the GDB Debug Console to set breakpoints in the void SystemInit();
function. We write the command x/256xw 0x08000000
and view 256 Words of memory written at the starting address of the flash memory.
We see that the Vector Table occupies memory addresses from 0x08000000
to 0x0800019C
in flash memory. This is equivalent to 412 Bytes occupied by 104 unique entries in the Vector Table. From this, we know to copy 104 Words of data in the void CopyVectTab(...);
function.
Now, we use the GDB Debug Console to write the command x/104xw 0x20000200
and view 104 Words of memory written at the offset address in SRAM.
The Cortex-M4 Programming Manual provided by STMicroelectronics states that SysTick Exception is generated when the 24-Bit SysTick Timer (i.e. STK) counts down from the reload value to 0. The SysTick Timer (i.e. STK) reloads this value on the subsequent clock edge and counts down again.
Let's use our Debug Console to see if the SysTick Vector holds the starting address of its Exception Handler. We first run the GDB command x/xw 0x2000023C
to determine the intended starting address, 0x80006B8
. Note that the starting address is stored in the Vector Table as 0x80006B9
to inform the processor that it's executing a Thumb Instruction.
Then we run command disassemble 0x80006B8
to verify this is the starting address of the void SysTick_Handler(void);
function as shown below.
We see that the Vector Table copied to SRAM matches that in flash memory and that it maps the starting addresses of the Exception Handlers. From this, we assume we are successful at this stage.
The PendSV Exception Type is a request for system-level service that's used for context switching between threads when other exceptions are inactive. As for the SysTick Vector, we determine the PendSV Vector to be located at address 0x20000238
. From this, we determine the PendSV Handler starting address to be 0x80006B6
.
While debugging, we can write the command set {int}0x2000023C = 0x80006B7
. This will instead store the starting address of the PendSV Handler at the SysTick Vector, with the Least Significant Bit (i.e. LSB) set to 1 to indicate the Thumb Instruction. By setting a breakpoint in the void PendSV_Handler(void);
function, we can see this on the next system tick.
Flashing the (Startup_Test.elf
) executable onto the STM32 Nucleo Board required modifying the (Makefile
) to include the make flash
command.
#######################################
# flash
#######################################
flash: all
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "program $(BUILD_DIR)/$(TARGET).elf verify reset exit"
The PuTTY SSH client is used to establish a terminal connection with the STM32 device as shown below.
The videos in the Demonstration
directory show the UART Communication as well as the output on the STM32 Nucleo Board. I have embedded a low resolution compressed version below.
Compressed_Flash_Board.mp4
This project was created and tested following tutorials from the EmbeddedGeek Youtube Channel as well as the Foundations of Embedded Systems with ARM Cortex and STM32 Udemy Course.