使用C语言实现STM32的启动文件

适用型号:stm32f103c8t6

编译器:GCC

传统的启动文件使用汇编语言实现,可读性很低,现在分析其内容,使用C语言重新实现一遍。

完整的代码

首先附上成品两份,第一份使用最新的C23标准,第二份使用传统的C11标准:

#include <stddef.h>
#include <stdint.h>
#include <string.h>
// some macros
#define cast(__value, __type) ((__type)(__value))
#define readonly(__type)      typeof(const __type)
#define ptr(__type)           typeof(__type *)
#define array(__type, ...)    typeof(__type[__VA_ARGS__])
#define func(__ret, ...)      typeof(__ret(__VA_ARGS__))
/**
* @syntax unified
* @cpu cortex-m3
* @fpu softvfp
* @thumb
*/
typedef func(void) inteHandlerType;                    // 定义中断处理函数类型
typedef readonly(ptr(inteHandlerType)) inteVectorType; // 定义中断向量表元素类型
array(inteVectorType) f_pfnVectors; // 声明 中断向量表
inteHandlerType Default_Handler;    // 声明 默认中断处理函数
inteHandlerType Reset_Handler;      // 声明 复位函数
extern func(void) SystemInit; // defined in @system_stm32f1xx.c
extern func(int) main;        // defined in @main.c
extern func(void) __libc_init_array;
static func(void) data_init;
static func(void) bss_init;
// 栈顶地址
/* Highest address of the user mode stack */
extern uint8_t _estack; // 为了和 @sysmem.c 中的定义保持一致,使用uint8_t
// 定义在链接器脚本中的符号
/* defined in linker script */
extern uint32_t _sidata; /* start address for the initialization values of the .data section.*/
/* start address for the .data section. defined in linker script */
extern uint32_t _sdata;
/* end address for the .data section. defined in linker script */
extern uint32_t _edata;
/* start address for the .bss section. defined in linker script */
extern uint32_t _sbss;
/* end address for the .bss section. defined in linker script */
extern uint32_t _ebss;
readonly(uint32_t) BootRAM = 0xF108F85F;
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
#define __HandlerAttribute [[gnu::weak]] [[gnu::alias("Default_Handler")]]
__HandlerAttribute func(void) NMI_Handler;
__HandlerAttribute func(void) HardFault_Handler;
__HandlerAttribute func(void) MemManage_Handler;
__HandlerAttribute func(void) BusFault_Handler;
__HandlerAttribute func(void) UsageFault_Handler;
__HandlerAttribute func(void) SVC_Handler;
__HandlerAttribute func(void) DebugMon_Handler;
__HandlerAttribute func(void) PendSV_Handler;
__HandlerAttribute func(void) SysTick_Handler;
__HandlerAttribute func(void) WWDG_IRQHandler;
__HandlerAttribute func(void) PVD_IRQHandler;
__HandlerAttribute func(void) TAMPER_IRQHandler;
__HandlerAttribute func(void) RTC_IRQHandler;
__HandlerAttribute func(void) FLASH_IRQHandler;
__HandlerAttribute func(void) RCC_IRQHandler;
__HandlerAttribute func(void) EXTI0_IRQHandler;
__HandlerAttribute func(void) EXTI1_IRQHandler;
__HandlerAttribute func(void) EXTI2_IRQHandler;
__HandlerAttribute func(void) EXTI3_IRQHandler;
__HandlerAttribute func(void) EXTI4_IRQHandler;
__HandlerAttribute func(void) DMA1_Channel1_IRQHandler;
__HandlerAttribute func(void) DMA1_Channel2_IRQHandler;
__HandlerAttribute func(void) DMA1_Channel3_IRQHandler;
__HandlerAttribute func(void) DMA1_Channel4_IRQHandler;
__HandlerAttribute func(void) DMA1_Channel5_IRQHandler;
__HandlerAttribute func(void) DMA1_Channel6_IRQHandler;
__HandlerAttribute func(void) DMA1_Channel7_IRQHandler;
__HandlerAttribute func(void) ADC1_2_IRQHandler;
__HandlerAttribute func(void) USB_HP_CAN1_TX_IRQHandler;
__HandlerAttribute func(void) USB_LP_CAN1_RX0_IRQHandler;
__HandlerAttribute func(void) CAN1_RX1_IRQHandler;
__HandlerAttribute func(void) CAN1_SCE_IRQHandler;
__HandlerAttribute func(void) EXTI9_5_IRQHandler;
__HandlerAttribute func(void) TIM1_BRK_IRQHandler;
__HandlerAttribute func(void) TIM1_UP_IRQHandler;
__HandlerAttribute func(void) TIM1_TRG_COM_IRQHandler;
__HandlerAttribute func(void) TIM1_CC_IRQHandler;
__HandlerAttribute func(void) TIM2_IRQHandler;
__HandlerAttribute func(void) TIM3_IRQHandler;
__HandlerAttribute func(void) TIM4_IRQHandler;
__HandlerAttribute func(void) I2C1_EV_IRQHandler;
__HandlerAttribute func(void) I2C1_ER_IRQHandler;
__HandlerAttribute func(void) I2C2_EV_IRQHandler;
__HandlerAttribute func(void) I2C2_ER_IRQHandler;
__HandlerAttribute func(void) SPI1_IRQHandler;
__HandlerAttribute func(void) SPI2_IRQHandler;
__HandlerAttribute func(void) USART1_IRQHandler;
__HandlerAttribute func(void) USART2_IRQHandler;
__HandlerAttribute func(void) USART3_IRQHandler;
__HandlerAttribute func(void) EXTI15_10_IRQHandler;
__HandlerAttribute func(void) RTC_Alarm_IRQHandler;
__HandlerAttribute func(void) USBWakeUp_IRQHandler;
/******************************************************************************
*
* The minimal vector table for a Cortex M3.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
[[gnu::section(".isr_vector")]] // gnu extension to place the vector table in a specific address
array(inteVectorType) f_pfnVectors =
{
cast(&_estack, ptr(void)),          /* Stack pointer */
Reset_Handler,                      /* Reset Handler */
NMI_Handler,                        /* NMI Handler */
HardFault_Handler,                  /* Hard Fault Handler */
MemManage_Handler,                  /* MPU Fault Handler */
BusFault_Handler,                   /* Bus Fault Handler */
UsageFault_Handler,                 /* Usage Fault Handler */
nullptr, nullptr, nullptr, nullptr, /* Reserved */
SVC_Handler,                        /* SVCall Handler */
DebugMon_Handler,                   /* Debug Monitor Handler */
nullptr,                            /* Reserved */
PendSV_Handler,                     /* PendSV Handler */
SysTick_Handler,                    /* SysTick Handler */
/* IRQ Handlers */
WWDG_IRQHandler,
PVD_IRQHandler,
TAMPER_IRQHandler,
RTC_IRQHandler,
FLASH_IRQHandler,
RCC_IRQHandler,
EXTI0_IRQHandler,
EXTI1_IRQHandler,
EXTI2_IRQHandler,
EXTI3_IRQHandler,
EXTI4_IRQHandler,
DMA1_Channel1_IRQHandler,
DMA1_Channel2_IRQHandler,
DMA1_Channel3_IRQHandler,
DMA1_Channel4_IRQHandler,
DMA1_Channel5_IRQHandler,
DMA1_Channel6_IRQHandler,
DMA1_Channel7_IRQHandler,
ADC1_2_IRQHandler,
USB_HP_CAN1_TX_IRQHandler,
USB_LP_CAN1_RX0_IRQHandler,
CAN1_RX1_IRQHandler,
CAN1_SCE_IRQHandler,
EXTI9_5_IRQHandler,
TIM1_BRK_IRQHandler,
TIM1_UP_IRQHandler,
TIM1_TRG_COM_IRQHandler,
TIM1_CC_IRQHandler,
TIM2_IRQHandler,
TIM3_IRQHandler,
TIM4_IRQHandler,
I2C1_EV_IRQHandler,
I2C1_ER_IRQHandler,
I2C2_EV_IRQHandler,
I2C2_ER_IRQHandler,
SPI1_IRQHandler,
SPI2_IRQHandler,
USART1_IRQHandler,
USART2_IRQHandler,
USART3_IRQHandler,
EXTI15_10_IRQHandler,
RTC_Alarm_IRQHandler,
USBWakeUp_IRQHandler,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, /* Reserved */
cast(BootRAM, ptr(void))
/* @0x108. This is for boot in RAM mode for STM32F10x Medium Density devices. */
};
/**
* @brief  This is the code that gets called when the processor receives an
*         unexpected interrupt.  This simply enters an infinite loop, preserving
*         the system state for examination by a debugger.
*
* @param  None
* @retval : None
*/
void Default_Handler(void)
{
while (true) {
/* Infinite loop */
}
}
/**
* @brief  This is the code that gets called when the processor first
*          starts execution following a reset event. Only the absolutely
*          necessary set is performed, after which the application
*          supplied main() routine is called.
* @param  None
* @retval : None
*/
[[noreturn]] // the function will never return
void Reset_Handler(void)
{
/* Call the clock system initialization function */
SystemInit();
/* Initialize data and bss sections */
data_init();
bss_init();
/* Call static constructors */
__libc_init_array();
/* Call the application's entry point */
main();
/* Should never reach here */
while (true) {
/* Infinite loop */
}
}
/**
* @brief data sector initialization function
*
*/
static void data_init(void)
{
auto   src       = cast(&_sidata, ptr(uint8_t)); // flash addr
auto   dst_start = cast(&_sdata, ptr(uint8_t));  // ram start
auto   dst_end   = cast(&_edata, ptr(uint8_t));  // ram end
size_t data_size = dst_end - dst_start;          // get data size
memcpy(dst_start, src, data_size);               // copy data from flash to ram
}
/**
* @brief bss sector zero initialization function
*
*/
/* BSS zero initialization function */
static void bss_init(void)
{
auto   dst_start = cast(&_sbss, ptr(uint8_t)); // ram start
auto   dst_end   = cast(&_ebss, ptr(uint8_t)); // ram end
size_t bss_size  = dst_end - dst_start;        // get bss size
memset(dst_start, 0x00, bss_size);             // clear bss section
}

C11标准:

#include <stddef.h>
#include <stdint.h>
#include <string.h>
/**
* @syntax unified
* @cpu cortex-m3
* @fpu softvfp
* @thumb
*/
// 声明中断向量表
void (*const g_pfnVectors[])(void);
void Default_Handler(void); // 声明 默认中断处理函数
void Reset_Handler(void);   // 声明 复位函数
extern void SystemInit(void);        // defined in @system_stm32f1xx.c
extern int  main(void);              // defined in @main.c
extern void __libc_init_array(void); // defined in @newlib
static void data_init(void);
static void bss_init(void);
// 栈顶地址
/* Highest address of the user mode stack */
extern uint8_t _estack; // 为了和 @sysmem.c 中的定义保持一致,使用uint8_t
// 定义在链接器脚本中的符号
/* defined in linker script */
extern uint32_t _sidata; /* start address for the initialization values of the .data section.*/
/* start address for the .data section. defined in linker script */
extern uint32_t _sdata;
/* end address for the .data section. defined in linker script */
extern uint32_t _edata;
/* start address for the .bss section. defined in linker script */
extern uint32_t _sbss;
/* end address for the .bss section. defined in linker script */
extern uint32_t _ebss;
const uint32_t BootRAM = 0xF108F85F;
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
#define __HandlerAttribute __attribute__((weak, alias("Default_Handler")))
__HandlerAttribute void NMI_Handler(void);
__HandlerAttribute void HardFault_Handler(void);
__HandlerAttribute void MemManage_Handler(void);
__HandlerAttribute void BusFault_Handler(void);
__HandlerAttribute void UsageFault_Handler(void);
__HandlerAttribute void SVC_Handler(void);
__HandlerAttribute void DebugMon_Handler(void);
__HandlerAttribute void PendSV_Handler(void);
__HandlerAttribute void SysTick_Handler(void);
__HandlerAttribute void WWDG_IRQHandler(void);
__HandlerAttribute void PVD_IRQHandler(void);
__HandlerAttribute void TAMPER_IRQHandler(void);
__HandlerAttribute void RTC_IRQHandler(void);
__HandlerAttribute void FLASH_IRQHandler(void);
__HandlerAttribute void RCC_IRQHandler(void);
__HandlerAttribute void EXTI0_IRQHandler(void);
__HandlerAttribute void EXTI1_IRQHandler(void);
__HandlerAttribute void EXTI2_IRQHandler(void);
__HandlerAttribute void EXTI3_IRQHandler(void);
__HandlerAttribute void EXTI4_IRQHandler(void);
__HandlerAttribute void DMA1_Channel1_IRQHandler(void);
__HandlerAttribute void DMA1_Channel2_IRQHandler(void);
__HandlerAttribute void DMA1_Channel3_IRQHandler(void);
__HandlerAttribute void DMA1_Channel4_IRQHandler(void);
__HandlerAttribute void DMA1_Channel5_IRQHandler(void);
__HandlerAttribute void DMA1_Channel6_IRQHandler(void);
__HandlerAttribute void DMA1_Channel7_IRQHandler(void);
__HandlerAttribute void ADC1_2_IRQHandler(void);
__HandlerAttribute void USB_HP_CAN1_TX_IRQHandler(void);
__HandlerAttribute void USB_LP_CAN1_RX0_IRQHandler(void);
__HandlerAttribute void CAN1_RX1_IRQHandler(void);
__HandlerAttribute void CAN1_SCE_IRQHandler(void);
__HandlerAttribute void EXTI9_5_IRQHandler(void);
__HandlerAttribute void TIM1_BRK_IRQHandler(void);
__HandlerAttribute void TIM1_UP_IRQHandler(void);
__HandlerAttribute void TIM1_TRG_COM_IRQHandler(void);
__HandlerAttribute void TIM1_CC_IRQHandler(void);
__HandlerAttribute void TIM2_IRQHandler(void);
__HandlerAttribute void TIM3_IRQHandler(void);
__HandlerAttribute void TIM4_IRQHandler(void);
__HandlerAttribute void I2C1_EV_IRQHandler(void);
__HandlerAttribute void I2C1_ER_IRQHandler(void);
__HandlerAttribute void I2C2_EV_IRQHandler(void);
__HandlerAttribute void I2C2_ER_IRQHandler(void);
__HandlerAttribute void SPI1_IRQHandler(void);
__HandlerAttribute void SPI2_IRQHandler(void);
__HandlerAttribute void USART1_IRQHandler(void);
__HandlerAttribute void USART2_IRQHandler(void);
__HandlerAttribute void USART3_IRQHandler(void);
__HandlerAttribute void EXTI15_10_IRQHandler(void);
__HandlerAttribute void RTC_Alarm_IRQHandler(void);
__HandlerAttribute void USBWakeUp_IRQHandler(void);
/******************************************************************************
*
* The minimal vector table for a Cortex M3.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
__attribute__((section(".isr_vector"))) void (*const g_pfnVectors[])(void) =
{
(void *)&_estack,       /* Stack pointer */
Reset_Handler,          /* Reset Handler */
NMI_Handler,            /* NMI Handler */
HardFault_Handler,      /* Hard Fault Handler */
MemManage_Handler,      /* MPU Fault Handler */
BusFault_Handler,       /* Bus Fault Handler */
UsageFault_Handler,     /* Usage Fault Handler */
NULL, NULL, NULL, NULL, /* Reserved */
SVC_Handler,            /* SVCall Handler */
DebugMon_Handler,       /* Debug Monitor Handler */
NULL,                   /* Reserved */
PendSV_Handler,         /* PendSV Handler */
SysTick_Handler,        /* SysTick Handler */
/* IRQ Handlers */
WWDG_IRQHandler,
PVD_IRQHandler,
TAMPER_IRQHandler,
RTC_IRQHandler,
FLASH_IRQHandler,
RCC_IRQHandler,
EXTI0_IRQHandler,
EXTI1_IRQHandler,
EXTI2_IRQHandler,
EXTI3_IRQHandler,
EXTI4_IRQHandler,
DMA1_Channel1_IRQHandler,
DMA1_Channel2_IRQHandler,
DMA1_Channel3_IRQHandler,
DMA1_Channel4_IRQHandler,
DMA1_Channel5_IRQHandler,
DMA1_Channel6_IRQHandler,
DMA1_Channel7_IRQHandler,
ADC1_2_IRQHandler,
USB_HP_CAN1_TX_IRQHandler,
USB_LP_CAN1_RX0_IRQHandler,
CAN1_RX1_IRQHandler,
CAN1_SCE_IRQHandler,
EXTI9_5_IRQHandler,
TIM1_BRK_IRQHandler,
TIM1_UP_IRQHandler,
TIM1_TRG_COM_IRQHandler,
TIM1_CC_IRQHandler,
TIM2_IRQHandler,
TIM3_IRQHandler,
TIM4_IRQHandler,
I2C1_EV_IRQHandler,
I2C1_ER_IRQHandler,
I2C2_EV_IRQHandler,
I2C2_ER_IRQHandler,
SPI1_IRQHandler,
SPI2_IRQHandler,
USART1_IRQHandler,
USART2_IRQHandler,
USART3_IRQHandler,
EXTI15_10_IRQHandler,
RTC_Alarm_IRQHandler,
USBWakeUp_IRQHandler,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* Reserved */
(void *)BootRAM
/* @0x108. This is for boot in RAM mode for STM32F10x Medium Density devices. */
};
/**
* @brief  This is the code that gets called when the processor receives an
*         unexpected interrupt.  This simply enters an infinite loop, preserving
*         the system state for examination by a debugger.
*
* @param  None
* @retval : None
*/
void Default_Handler(void)
{
while (1) {
/* Infinite loop */
}
}
/**
* @brief  This is the code that gets called when the processor first
*          starts execution following a reset event. Only the absolutely
*          necessary set is performed, after which the application
*          supplied main() routine is called.
* @param  None
* @retval : None
*/
__attribute__((noreturn)) // the function will never return
void Reset_Handler(void)
{
/* Call the clock system initialization function */
SystemInit(); // 定义在 @system_stm32f1xx.c 中
/* Initialize data and bss sections */
data_init();
bss_init();
/* Call static constructors */
__libc_init_array();
/* Call the application's entry point */
main();
/* Should never reach here */
while (1) {
/* Infinite loop */
}
}
/**
* @brief data sector initialization function
*
*/
static void data_init(void)
{
uint8_t *src       = (uint8_t *)&_sidata; // flash addr
uint8_t *dst_start = (uint8_t *)&_sdata;  // ram start
uint8_t *dst_end   = (uint8_t *)&_edata;  // ram end
size_t   data_size = dst_end - dst_start; // get data size
memcpy(dst_start, src, data_size);        // copy data from flash to ram
}
/**
* @brief bss sector zero initialization function
*
*/
/* BSS zero initialization function */
static void bss_init(void)
{
uint8_t *dst_start = (uint8_t *)&_sbss;   // ram start
uint8_t *dst_end   = (uint8_t *)&_ebss;   // ram end
size_t   bss_size  = dst_end - dst_start; // get bss size
memset(dst_start, 0x00, bss_size);        // clear bss section
}

程序分析

C23标准前置知识

auto 关键字

C23 标准新增类似于 C++ 的 auto 关键字,用于简化变量定义:

auto i = 1;             // int;
auto str = "Hello";     // char *str;
auto len = strlen(str); // size_t

bool 类型变量

C23 标准正式将 bool 类型转正,另附带 truefalse 两个关键字,以后不用再 #include <stdbool.h> 了。

nullptr 关键字

C23 标准新增 C++ 风格的 nullptr 关键字及其类型 nullptr_t,用于表示空指针,再也不用先 #include <stdio.h> 再使用 NULL 了;

typeof 关键字

C23 标准转正了 GNU C 拓展中的 typeof 关键字,用于获取表达式的类型,类似于 C++ 中的 decltype。使用方式和 sizeof 类似:

sizeof 关键字的使用方法:

sizeof(type)
sizeof expr
sizeof(expr)

typeof 的使用方法:

typeof(type)
typeof(expr)

使用例:

// 使用表达式
int x = 1;
typeof(x) y = 2; // y 的类型为 int
typeof(x + 1) z = 3; // z 的类型为 int
// 使用类型
typeof(int) a = 4; // a 的类型为 int
typeof(int *) b = 5; // b 的类型为 int *
typeof(int[10]) c = {}; // c 的类型为 int[10]

结合宏定义,可以大大简化 C 语言的类型声明:

#define readonly(__type)      typeof(const __type)
#define ptr(__type)           typeof(__type *)
#define array(__type, ...)    typeof(__type[__VA_ARGS__])
#define func(__ret, ...)      typeof(__ret(__VA_ARGS__))

使用例:

int a                     = 1;
readonly(int) b           = 10;        // 相当于 const int b = 10;
ptr(int) p                = &a;        // p 的类型为 int *
array(int, 10) arr        = {1, 2, 3}; // arr 是大小为10的int数组
ptr(array(int, 10)) arr_p = &arr;      // arr_p是指向 大小为10的int数组的指针
func(int, int, char) *f   = &foo;      // f 为 指向 int f(int, char) 类型函数的函数指针
ptr(func(int)) mp         = main;      // mp 为指向 int foo(void) 类型函数的函数指针

高级用法:

简化 const

// const int *p;
ptr(readonly(int)) p = &a;
// int *const p;
readonly(ptr(int)) p = &a;
// const int* const p;
readonly(ptr(readonly(int))) p = &a;

简化复合类型的声明:

// 相当于 int *p_arr[10],arr是个数组,数组的元素类型是指针
array(ptr(int), 10) p_arr;
// 相当于 int (*arr_p)[10],p_arr是个指针,指针指向一个数组,数组的元素类型是int
ptr(array(int, 10)) arr_p;
// 原始定义非常令人费解:int *(*var[5])(void);
array(ptr(func(ptr(int), void)), 5) var;
// 现在通俗易懂:var是个数组,数组有五个元素,每个元素都是指针,指针指向函数,函数的返回值为指向int类型的指针,参数为空。

上述 array 宏之所以使用可变参数,是为了支持 int arr[] 类型的数组声明:

array(int) arr; // 相当于 int arr[]; 声明的时候不用指定大小
array(int, 10) arr = {}; // 相当于 int arr[10] = {}; 定义的时候必须指定大小

此外,上述 func 宏可以用于声明函数,但不可以用于实现函数(否则参数往哪里写呢?):

func(void) foo; // 正确,可以用于声明函数
// 正确,后续实现函数必须用原生方式
void foo(){
}
// 错误,typeof 不能实现定义函数
func(void) foo{
};

C23 属性

C23 标准正式支持 [[attr]] 属性,类似于 C++ 中的属性。如果需要用到编译器拓展内容,则需要加上 namespace ,如 [[gnu::weak]] [[clang::overloadable]] 等,用于表明这是编译器拓展内容,不是标准 C 语言内容。

本文用到的标准属性有:

  • noreturn:用于声明函数不会返回

GNU C 拓展属性

GNU C 拓展属性,传统的方式是使用 __attribute__((attr)) 语法,如 __attribute__((section(".isr_vector")))。C23 之后支持使用 [[gnu::attr]] 语法。

本文中使用到的 GNU C 拓展属性有:

  • section("name"):用于指定符号的存储位置,可以精细控制变量的绝对地址,这在中断向量组的定义中非常有用,因为它的地址是绝对固定的。
  • weak:用于定义弱符号,弱符号可以被覆盖,相当于提供了一个“默认值”,允许用户重新定义。
  • alias("symbol"):用于给符号起别名,这样调用A就相当于调用了B。

链接器脚本分析

以STM32CubeMX生成使用的CMake工具链的stm32f103c8t6的项目为例,有一个启动文件 startup_stm32f103xb.s 和链接器脚本 STM32F103XX_FLASH.ld,启动文件中定义的内容是上电以后执行的第一件事情,而链接器脚本指定程序的链接方式和内存区域分配方式。

首先分析链接器脚本文件。原始内容内容:

/* Entry Point */
ENTRY(Reset_Handler)
/* Specify the memory areas */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 20K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 64K
}
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM);    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x0;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text)           /* .text sections (code) */
*(.text*)          /* .text* sections (code) */
*(.glue_7)         /* glue arm to thumb code */
*(.glue_7t)        /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .;        /* define a global symbols at end of code */
} >FLASH
/* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata)         /* .rodata sections (constants, strings, etc.) */
*(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.ARM.extab (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4);
} >FLASH
.ARM (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
. = ALIGN(4);
} >FLASH
.preinit_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
} >FLASH
.init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
} >FLASH
.fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(4);
} >FLASH
/* used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
. = ALIGN(4);
_sdata = .;        /* create a global symbol at data start */
*(.data)           /* .data sections */
*(.data*)          /* .data* sections */
*(.RamFunc)        /* .RamFunc sections */
*(.RamFunc*)       /* .RamFunc* sections */
. = ALIGN(4);
} >RAM AT> FLASH
/* Initialized TLS data section */
.tdata : ALIGN(4)
{
*(.tdata .tdata.* .gnu.linkonce.td.*)
. = ALIGN(4);
_edata = .;        /* define a global symbol at data end */
PROVIDE(__data_end = .);
PROVIDE(__tdata_end = .);
} >RAM AT> FLASH
PROVIDE( __tdata_start = ADDR(.tdata) );
PROVIDE( __tdata_size = __tdata_end - __tdata_start );
PROVIDE( __data_start = ADDR(.data) );
PROVIDE( __data_size = __data_end - __data_start );
PROVIDE( __tdata_source = LOADADDR(.tdata) );
PROVIDE( __tdata_source_end = LOADADDR(.tdata) + SIZEOF(.tdata) );
PROVIDE( __tdata_source_size = __tdata_source_end - __tdata_source );
PROVIDE( __data_source = LOADADDR(.data) );
PROVIDE( __data_source_end = __tdata_source_end );
PROVIDE( __data_source_size = __data_source_end - __data_source );
/* Uninitialized data section */
.tbss (NOLOAD) : ALIGN(4)
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .;         /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.tbss .tbss.*)
. = ALIGN(4);
PROVIDE( __tbss_end = . );
} >RAM
PROVIDE( __tbss_start = ADDR(.tbss) );
PROVIDE( __tbss_size = __tbss_end - __tbss_start );
PROVIDE( __tbss_offset = ADDR(.tbss) - ADDR(.tdata) );
PROVIDE( __tls_base = __tdata_start );
PROVIDE( __tls_end = __tbss_end );
PROVIDE( __tls_size = __tls_end - __tls_base );
PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) );
PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1) );
PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) );
PROVIDE( __arm64_tls_tcb_offset = MAX(16, __tls_align) );
.bss (NOLOAD) : ALIGN(4)
{
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;         /* define a global symbol at bss end */
__bss_end__ = _ebss;
PROVIDE( __bss_end = .);
} >RAM
PROVIDE( __non_tls_bss_start = ADDR(.bss) );
PROVIDE( __bss_start = __tbss_start );
PROVIDE( __bss_size = __bss_end - __bss_start );
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack (NOLOAD) :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a:* ( * )
libm.a:* ( * )
libgcc.a:* ( * )
}
}

进行逐段分析:

/* Entry Point */
ENTRY(Reset_Handler)

定义了入口函数,也即上电之后执行的第一个函数,此处为 Reset_Handler

/* Specify the memory areas */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 20K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 64K
}

定义了内存区域,分别为:

  • RAM 区域,可执行(x)、可读(r)、可写(w)的区域,大小为 20K,起始地址为 0x20000000。
  • FLASH 区域,可读(r)、可执行(x),大小为 64K,起始地址为 0x8000000。
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM);    /* end of RAM */

定义程序栈起始地址 _estack,由于栈是从高地址向低地址延伸,所以起始地址定义为 RAM 区域的结束位置。

/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x0;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

定义堆和栈的大小。如果需要使用 malloc 等动态内存分配,则分配的内存就位于堆中。此处不用,因此设为0。

然后是段定义:

/* Define output sections */
SECTIONS
{
/*...... */
}

分为几个段:

  /* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH

为中断向量表所在的地址

  /* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata)         /* .rodata sections (constants, strings, etc.) */
*(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH

定义const 变量存放在 Flash 中。

接下来的 .ARM.extab.ARM.preinit_array.init_array.fini_array 与C++有关,略过。

接着是

  /* used by the startup to initialize data */
_sidata = LOADADDR(.data);

定义符号 _sidata,用于指定 .data 段在Flash中的起始地址,这样就可以在C代码文件中使用这个“变量”

  /* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
. = ALIGN(4);
_sdata = .;        /* create a global symbol at data start */
*(.data)           /* .data sections */
*(.data*)          /* .data* sections */
*(.RamFunc)        /* .RamFunc sections */
*(.RamFunc*)       /* .RamFunc* sections */
. = ALIGN(4);
} >RAM AT> FLASH

此处定义了 .data 段,用于存放已经初始化过的全局变量,这些变量的值会存放在Flash中,在程序运行时,在启动文件中将其复制到RAM中的对应位置。

  .bss (NOLOAD) : ALIGN(4)
{
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;         /* define a global symbol at bss end */
__bss_end__ = _ebss;
PROVIDE( __bss_end = .);
} >RAM

定义了 .bss 段,用于存放未初始化的全局变量,这些变量的值在程序运行时会被初始化为0。

 /* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack (NOLOAD) :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM

定义了一个 .user_heap_stack 段,用于堆栈的分配,保存在RAM中。

  /* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a:* ( * )
libm.a:* ( * )
libgcc.a:* ( * )
}

移除 libc.alibm.alibgcc.a 等标准库符号,减小程序体积。

启动文件分析并重写

接着分析启动并重写文件:

  .syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb
.global g_pfnVectors
.global Default_Handler
/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss
.equ  BootRAM, 0xF108F85F
/**
* @brief  This is the code that gets called when the processor first
*          starts execution following a reset event. Only the absolutely
*          necessary set is performed, after which the application
*          supplied main() routine is called.
* @param  None
* @retval : None
*/
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
/* Call the clock system initialization function.*/
bl  SystemInit
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit
CopyDataInit:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
LoopCopyDataInit:
adds r4, r0, r3
cmp r4, r1
bcc CopyDataInit
/* Zero fill the bss segment. */
ldr r2, =_sbss
ldr r4, =_ebss
movs r3, #0
b LoopFillZerobss
FillZerobss:
str  r3, [r2]
adds r2, r2, #4
LoopFillZerobss:
cmp r2, r4
bcc FillZerobss
/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main
bx lr
.size Reset_Handler, .-Reset_Handler
/**
* @brief  This is the code that gets called when the processor receives an
*         unexpected interrupt.  This simply enters an infinite loop, preserving
*         the system state for examination by a debugger.
*
* @param  None
* @retval : None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The minimal vector table for a Cortex M3.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
.word WWDG_IRQHandler
.word PVD_IRQHandler
.word TAMPER_IRQHandler
.word RTC_IRQHandler
.word FLASH_IRQHandler
.word RCC_IRQHandler
.word EXTI0_IRQHandler
.word EXTI1_IRQHandler
.word EXTI2_IRQHandler
.word EXTI3_IRQHandler
.word EXTI4_IRQHandler
.word DMA1_Channel1_IRQHandler
.word DMA1_Channel2_IRQHandler
.word DMA1_Channel3_IRQHandler
.word DMA1_Channel4_IRQHandler
.word DMA1_Channel5_IRQHandler
.word DMA1_Channel6_IRQHandler
.word DMA1_Channel7_IRQHandler
.word ADC1_2_IRQHandler
.word USB_HP_CAN1_TX_IRQHandler
.word USB_LP_CAN1_RX0_IRQHandler
.word CAN1_RX1_IRQHandler
.word CAN1_SCE_IRQHandler
.word EXTI9_5_IRQHandler
.word TIM1_BRK_IRQHandler
.word TIM1_UP_IRQHandler
.word TIM1_TRG_COM_IRQHandler
.word TIM1_CC_IRQHandler
.word TIM2_IRQHandler
.word TIM3_IRQHandler
.word TIM4_IRQHandler
.word I2C1_EV_IRQHandler
.word I2C1_ER_IRQHandler
.word I2C2_EV_IRQHandler
.word I2C2_ER_IRQHandler
.word SPI1_IRQHandler
.word SPI2_IRQHandler
.word USART1_IRQHandler
.word USART2_IRQHandler
.word USART3_IRQHandler
.word EXTI15_10_IRQHandler
.word RTC_Alarm_IRQHandler
.word USBWakeUp_IRQHandler
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word BootRAM          /* @0x108. This is for boot in RAM mode for
STM32F10x Medium Density devices. */
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler,Default_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler,Default_Handler
.weak SVC_Handler
.thumb_set SVC_Handler,Default_Handler
.weak DebugMon_Handler
.thumb_set DebugMon_Handler,Default_Handler
.weak PendSV_Handler
.thumb_set PendSV_Handler,Default_Handler
.weak SysTick_Handler
.thumb_set SysTick_Handler,Default_Handler
.weak WWDG_IRQHandler
.thumb_set WWDG_IRQHandler,Default_Handler
.weak PVD_IRQHandler
.thumb_set PVD_IRQHandler,Default_Handler
.weak TAMPER_IRQHandler
.thumb_set TAMPER_IRQHandler,Default_Handler
.weak RTC_IRQHandler
.thumb_set RTC_IRQHandler,Default_Handler
.weak FLASH_IRQHandler
.thumb_set FLASH_IRQHandler,Default_Handler
.weak RCC_IRQHandler
.thumb_set RCC_IRQHandler,Default_Handler
.weak EXTI0_IRQHandler
.thumb_set EXTI0_IRQHandler,Default_Handler
.weak EXTI1_IRQHandler
.thumb_set EXTI1_IRQHandler,Default_Handler
.weak EXTI2_IRQHandler
.thumb_set EXTI2_IRQHandler,Default_Handler
.weak EXTI3_IRQHandler
.thumb_set EXTI3_IRQHandler,Default_Handler
.weak EXTI4_IRQHandler
.thumb_set EXTI4_IRQHandler,Default_Handler
.weak DMA1_Channel1_IRQHandler
.thumb_set DMA1_Channel1_IRQHandler,Default_Handler
.weak DMA1_Channel2_IRQHandler
.thumb_set DMA1_Channel2_IRQHandler,Default_Handler
.weak DMA1_Channel3_IRQHandler
.thumb_set DMA1_Channel3_IRQHandler,Default_Handler
.weak DMA1_Channel4_IRQHandler
.thumb_set DMA1_Channel4_IRQHandler,Default_Handler
.weak DMA1_Channel5_IRQHandler
.thumb_set DMA1_Channel5_IRQHandler,Default_Handler
.weak DMA1_Channel6_IRQHandler
.thumb_set DMA1_Channel6_IRQHandler,Default_Handler
.weak DMA1_Channel7_IRQHandler
.thumb_set DMA1_Channel7_IRQHandler,Default_Handler
.weak ADC1_2_IRQHandler
.thumb_set ADC1_2_IRQHandler,Default_Handler
.weak USB_HP_CAN1_TX_IRQHandler
.thumb_set USB_HP_CAN1_TX_IRQHandler,Default_Handler
.weak USB_LP_CAN1_RX0_IRQHandler
.thumb_set USB_LP_CAN1_RX0_IRQHandler,Default_Handler
.weak CAN1_RX1_IRQHandler
.thumb_set CAN1_RX1_IRQHandler,Default_Handler
.weak CAN1_SCE_IRQHandler
.thumb_set CAN1_SCE_IRQHandler,Default_Handler
.weak EXTI9_5_IRQHandler
.thumb_set EXTI9_5_IRQHandler,Default_Handler
.weak TIM1_BRK_IRQHandler
.thumb_set TIM1_BRK_IRQHandler,Default_Handler
.weak TIM1_UP_IRQHandler
.thumb_set TIM1_UP_IRQHandler,Default_Handler
.weak TIM1_TRG_COM_IRQHandler
.thumb_set TIM1_TRG_COM_IRQHandler,Default_Handler
.weak TIM1_CC_IRQHandler
.thumb_set TIM1_CC_IRQHandler,Default_Handler
.weak TIM2_IRQHandler
.thumb_set TIM2_IRQHandler,Default_Handler
.weak TIM3_IRQHandler
.thumb_set TIM3_IRQHandler,Default_Handler
.weak TIM4_IRQHandler
.thumb_set TIM4_IRQHandler,Default_Handler
.weak I2C1_EV_IRQHandler
.thumb_set I2C1_EV_IRQHandler,Default_Handler
.weak I2C1_ER_IRQHandler
.thumb_set I2C1_ER_IRQHandler,Default_Handler
.weak I2C2_EV_IRQHandler
.thumb_set I2C2_EV_IRQHandler,Default_Handler
.weak I2C2_ER_IRQHandler
.thumb_set I2C2_ER_IRQHandler,Default_Handler
.weak SPI1_IRQHandler
.thumb_set SPI1_IRQHandler,Default_Handler
.weak SPI2_IRQHandler
.thumb_set SPI2_IRQHandler,Default_Handler
.weak USART1_IRQHandler
.thumb_set USART1_IRQHandler,Default_Handler
.weak USART2_IRQHandler
.thumb_set USART2_IRQHandler,Default_Handler
.weak USART3_IRQHandler
.thumb_set USART3_IRQHandler,Default_Handler
.weak EXTI15_10_IRQHandler
.thumb_set EXTI15_10_IRQHandler,Default_Handler
.weak RTC_Alarm_IRQHandler
.thumb_set RTC_Alarm_IRQHandler,Default_Handler
.weak USBWakeUp_IRQHandler
.thumb_set USBWakeUp_IRQHandler,Default_Handler

首先是

  .syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb

定义了cpu、fpu、指令集等内容,略过。

然后

.global g_pfnVectors
.global Default_Handler

相当于C语言中声明了两个全局变量。第一个是中断向量组,第二个是默认的中断处理函数。

中断向量组中是紧凑排列的各种中断函数的入口地址,我们知道所有的中断函数都是 void handler(void) 类型,因此它等价为一个函数指针数组,为了防止不小心修改,可以添加 const 限定符:

typedef void(*const inteFunp)(void);
const inteFunp g_pfnVectors[];

然后是

/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss
.equ  BootRAM, 0xF108F85F

声明了几个 .word 类型的变量,相当于C语言中的 uint32_t 类型。这些符号定义在链接器脚本中,用于指定各个数据段的起始、终止地址。最后的 .equ BootRAM, 0xF108F85F 定义了从RAM中启动的地址,需要加在中断向量组的最后。

接着是 Reset_Handler 函数的定义,它是上电之后第一个执行的函数。下面是它的声明:

  .section .text.Reset_Handler // 定义代码段
.weak Reset_Handlel           // 定义为弱符号
.type Reset_Handler, %function // 定义为函数类型

这一部分可以改写为C代码:

[[gnu::weak]] void Reset_Handler(void);

然后是函数体:

Reset_Handler:
bl SystemInit // 调用 SystemInit 函数
// 从 Flash 中复制数据到 data 段
// 清零 bss 段
bl __libc_init_array // 调用 C++ 静态构造函数
bl main // 调用 main 函数,进入主程序
bx lr   // 相当于 main 函数中的 return

可以改写为C代码:

void data_init(void);
void bss_init(void);
[[noreturn]] void Reset_Handler(void)
{
SystemInit();        // 调用 SystemInit 函数
data_init();         // 复制数据到data段
bss_init();          // 清零bss段
__libc_init_array(); // 调用C++静态构造函数
main();              // 进入主程序
while (true);
}

其中 data_initbss_init 是我们自己定义的两个函数,用于初始化 .data.bss 段,其汇编代码如下(注释部分为C风格伪代码):

/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata    // r0 = _sdata;
ldr r1, =_edata    // r1 = _edata;
ldr r2, =_sidata   // r2 = _sidata;
movs r3, #0        // r3 = 0;
b LoopCopyDataInit // goto LoopCopyDataInit;
CopyDataInit:        // CopyDataInit:
ldr r4, [r2, r3]   //   r4 = *(uint32_t*)(r2 + r3);
str r4, [r0, r3]   //   *(uint32_t*)(r0 + r2) = r4;
//   // 两句合起来等价于:
//   // *(uint32_t*)(r0 + r2) = *(uint32_t*)(r2 + r2);
adds r3, r3, #4    //   r3 += 4;
LoopCopyDataInit:    // LoopCopyDataInit:
adds r4, r0, r3    //   r4 = r0 + r3;
cmp r4, r1         //   cmp res(r4,r1); // 假设有个enum cmp用于存储两个数字比较情况
bcc CopyDataInit   //   if(cmp == less){ goto CopyDataInit; }
/* Zero fill the bss segment. */
ldr r2, =_sbss
ldr r4, =_ebss
movs r3, #0
b LoopFillZerobss
FillZerobss:
str  r3, [r2]
adds r2, r2, #4
LoopFillZerobss:
cmp r2, r4
bcc FillZerobss

可以看到,本质上就是把 flash 中的数据拷贝到 ram 中,拷贝的长度为 _edata - _sdata,源地址为 flash 中的 _sidata,目的地址为 ram 中的 _sdata。清零部分同理,只不过是把拷贝改为设置为0。其逻辑改写为C代码如下:

extern uint32_t _sdata;
extern uint32_t _edata;
extern uint32_t _sidata;
uint32_t data_start  = _sdata;  // data段的起始地址
uint32_t data_end    = _edata;  // data段的结束地址
uint32_t source_addr = _sidata; // flash中data的数据的起始地址
uint32_t offset      = 0;
// 以 word 为单位,也即4字节为单位,从 flash 中拷贝数据到 ram 中
while (offset < data_end) {
uint32_t *src = (uint32_t *)(source_addr + offset); // 地址转为uint8_t*,方便计算长度
uint32_t *dst = (uint32_t *)(data_start + offset);
*dst = *src; // 从 Flash 中拷贝数据到 RAM 中
offset += 4; // 指针偏移4字节,也即一个 word 的长度
}
// 清零部分略

可以借助C库函数 memcpymemset 来实现 data_initbss_init 函数,肯定比逐个复制更高效:

/* Data copy function */
static void data_init(void)
{
size_t data_size = _edata - _sdata; // get data size
memcpy(_sdata, _sidata, data_size); // copy data from flash to ram
}
/* BSS zero initialization function */
static void bss_init(void)
{
size_t bss_size = _ebss - _sbss; // get bss size
memset(_sbss, 0x00, bss_size);   // clear bss section
}

然后是 Default_Handler 函数:

/**
* @brief  This is the code that gets called when the processor receives an
*         unexpected interrupt.  This simply enters an infinite loop, preserving
*         the system state for examination by a debugger.
*
* @param  None
* @retval : None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop // goto Infinite_Loop;
.size Default_Handler, .-Default_Handler

它是中断处理函数的默认实现,当发生未知中断时,它会进入一个无限循环,保持系统状态,等待调试器来查看。易知它是个死循环,因此可以改写为C代码:

void Default_Handler(void){
while(true){
}
}

最后是中断向量组定义:

/******************************************************************************
*
* The minimal vector table for a Cortex M3.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
// ......
.word BootRAM          /* @0x108. This is for boot in RAM mode for
STM32F10x Medium Density devices. */

以及其弱符号定义:

  .weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler
// ......

改写为C代码:

// 函数声明
[[gnu::weak]] [[gnu::alias("Default_Handler")]] func(void) NMI_Handler;
[[gnu::weak]] [[gnu::alias("Default_Handler")]] func(void) HardFault_Handler;
// ......
[[gnu::weak]] [[gnu::alias("Default_Handler")]] func(void) USBWakeUp_IRQHandler;
// 向量组定义
[[gnu::section(".isr_vector")]]  
const (*const f_pfnVectors[]) = {
(void(*)void)&_estack,
Reset_Handler,
// ...///
(void(*)void)BootRAM
};

最后,对启动文件进行调整润色,就得到了C语言实现的启动文件。

版权声明:cnblogshot 发表于 2025-10-13 10:46:41。
转载请注明:使用C语言实现STM32的启动文件 | 程序员导航网

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...