Install latest version of ARM toolchain
cd ~
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi.tar.xz
tar xf arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi.tar.xz
Install openocd, modify its config file path and test it
sudo apt update
sudo apt install openocd
cd /usr/local/share
sudo ln -s /usr/share/openocd .
openocd -f board/stm32f4discovery.cfg
General build process flow
Written in linker command language
The purpose of the linker script is basically to tell the linker:
Where to start executing code when the processor boots
Which memory regions exist on the microcontroller
Where to place the different code sections in memory
final script
ENTRY (reset_handler )
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000 , LENGTH = 1024K
SRAM (rwx) : ORIGIN = 0x20000000 , LENGTH = 112K
}
SECTIONS
{
.isr_vector :
{
KEEP (*(.isr_vector ))
} >FLASH
.text :
{
. = ALIGN (4 );
*(.text )
*(.rodata )
. = ALIGN (4 );
_etext = .;
} >FLASH
.data :
{
. = ALIGN (4 );
_sdata = .;
*(.data )
. = ALIGN (4 );
_edata = .;
} >SRAM AT > FLASH
.bss :
{
. = ALIGN (4 );
_sbss = .;
*(.bss )
. = ALIGN (4 );
_ebss = .;
} >SRAM
}
In startup.c we must do the following:
Initialize the main stack pointer and interrupt vector table in the .isr_vector section
Copy the .data section from flash to SRAM
Zero-fill the .bss section
final code
#include <stdint.h>
#define ISR_VECTOR_SIZE_WORDS 97
#define SRAM_START (0x20000000U)
#define SRAM_SIZE (112U * 1024U)
#define SRAM_END (SRAM_START + SRAM_SIZE)
#define STACK_POINTER_INIT_ADDRESS (SRAM_END)
extern uint32_t _etext , _sdata , _edata , _sbss , _ebss ;
void main (void );
void reset_handler (void );
void default_handler (void );
void nmi_handler (void ) __attribute__((weak , alias ("default_handler" )));
void hard_fault_handler (void ) __attribute__((weak , alias ("default_handler" )));
void mem_manage_handler (void ) __attribute__((weak , alias ("default_handler" )));
void bus_fault_handler (void ) __attribute__((weak , alias ("default_handler" )));
void usage_fault_handler (void ) __attribute__((weak , alias ("default_handler" )));
void svcall_handler (void ) __attribute__((weak , alias ("default_handler" )));
void debug_monitor_handler (void ) __attribute__((weak , alias ("default_handler" )));
void pendsv_handler (void ) __attribute__((weak , alias ("default_handler" )));
void systick_handler (void ) __attribute__((weak , alias ("default_handler" )));
// continue adding device interrupt handlers
uint32_t isr_vector [ISR_VECTOR_SIZE_WORDS ] __attribute__((section (".isr_vector" ))) = {
STACK_POINTER_INIT_ADDRESS ,
(uint32_t )& reset_handler ,
(uint32_t )& nmi_handler ,
(uint32_t )& hard_fault_handler ,
(uint32_t )& mem_manage_handler ,
(uint32_t )& bus_fault_handler ,
(uint32_t )& usage_fault_handler ,
0 ,
0 ,
0 ,
0 ,
(uint32_t )& svcall_handler ,
(uint32_t )& debug_monitor_handler ,
0 ,
(uint32_t )& pendsv_handler ,
(uint32_t )& systick_handler ,
// continue adding device interrupt handlers
};
void default_handler (void )
{
while (1 );
}
void reset_handler (void )
{
// Copy .data from FLASH to SRAM
uint32_t data_size = (uint32_t )& _edata - (uint32_t )& _sdata ;
uint8_t * flash_data = (uint8_t * ) & _etext ;
uint8_t * sram_data = (uint8_t * ) & _sdata ;
for (uint32_t i = 0 ; i < data_size ; i ++ )
{
sram_data [i ] = flash_data [i ];
}
// Zero-fill .bss section in SRAM
uint32_t bss_size = (uint32_t )& _ebss - (uint32_t )& _sbss ;
uint8_t * bss = (uint8_t * ) & _sbss ;
for (uint32_t i = 0 ; i < bss_size ; i ++ )
{
bss [i ] = 0 ;
}
main ();
}
To create a simple blink app we should do following steps:
Enable the peripheral clock for GPIO port A
Set PA5 as a push-pull output
Toggle the pin at a fixed interval in the super loop
final main
#include <stdint.h>
#define PERIPHERAL_BASE (0x40000000U)
#define AHB1_BASE (PERIPHERAL_BASE + 0x20000U)
#define GPIOD_BASE (AHB1_BASE + 0xC00U)
#define RCC_BASE (AHB1_BASE + 0x3800U)
#define RCC_AHB1ENR_OFFSET (0x30U)
#define RCC_AHB1ENR ((volatile uint32_t*) (RCC_BASE + RCC_AHB1ENR_OFFSET))
#define RCC_AHB1ENR_GPIODEN (0x03U)
#define GPIO_MODER_OFFSET (0x00U)
#define GPIOD_MODER ((volatile uint32_t*) (GPIOD_BASE + GPIO_MODER_OFFSET))
#define GPIO_MODER_MODER13 (26U)
#define GPIO_ODR_OFFSET (0x14U)
#define GPIOD_ODR ((volatile uint32_t*) (GPIOD_BASE + GPIO_ODR_OFFSET))
#define LED_PIN 13
void main (void )
{
* RCC_AHB1ENR |= (1 << RCC_AHB1ENR_GPIODEN );
// do two dummy reads after enabling the peripheral clock, as per the errata
volatile uint32_t dummy ;
dummy = * (RCC_AHB1ENR );
dummy = * (RCC_AHB1ENR );
* GPIOD_MODER |= (1 << GPIO_MODER_MODER13 );
while (1 )
{
* GPIOD_ODR ^= (1 << LED_PIN );
for (uint32_t i = 0 ; i < 1000000 ; i ++ );
}
}
cd ~
arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc main.c startup.c -T linker.ld -o blink.elf -mcpu=cortex-m4 -mthumb -nostdlib
To check which memory sections an .elf or .o file contains
arm-none-eabi-objdump -h < filename>
# OR
arm-none-eabi-readelf -S < filename>
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c " program blink.elf verify reset exit"