适用型号:stm32f103c8t6
编译器:GCC
传统的启动文件使用汇编语言实现,可读性很低,现在分析其内容,使用C语言重新实现一遍。
首先附上成品,使用C23标准:- #include <stddef.h>
- #include <stdint.h>
- #include <string.h>
- // some macros
- #define cast(__value, __type) ((__type)(__value))
- #define ptr(__type) typeof(__type *)
- #define array(__type, ...) typeof(__type[##__VA_ARGS__])
- #define func(__ret, ...) typeof(__ret(__VA_ARGS__))
- #define readonly(__type) typeof(const __type)
- /**
- * @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
- }
复制代码 以STM32CubeMX生成使用的CMake工具链的stm32f103c8t6的项目为例,有一个启动文件 startup_stm32f103xb.s 和链接器脚本 STM32F103XX_FLASH.ld,启动文件中定义的内容是上电以后执行的第一件事情,而链接器脚本指定程序的链接方式和内存区域分配方式。
首先分析链接器文件:- /* 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;
- FLASH 区域,可读(r)、可执行(x),大小为 64K,起始地址为 0x8000000。
- /* Highest address of the user mode stack */
- _estack = ORIGIN(RAM) + LENGTH(RAM); /* end of RAM */
复制代码 定义程序堆栈起始地址 _estack,由于堆栈是从高地址向低地址延伸,所以起始地址定义为 RAM 区域的结束位置。考虑到寻址方式为“word”,因此在C语言中,可以使用 uint32_t 类型来表示。- /* 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.a、libm.a、libgcc.a 等标准库符号,因为使用newlib。
接着分析启动文件:
首先是- .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);
复制代码 其中 [[gnu::weak]] 是C23语法,用于定义弱符号。
然后是函数体:- 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]] // c23 属性,标记函数永不返回
- void Reset_Handler(void){
- SystemInit();
- data_init(); // 复制数据到data段
- bss_init(); // 清零bss段
- __libc_init_array(); // 调用C++静态构造函数
- main(); // 进入主程序
- while(true);
- }
复制代码 其中 data_init 和 bss_init 是我们自己定义的两个函数,用于初始化 .data 和 .bss 段,其汇编代码如下:- /* 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
复制代码 改写为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);
- uint32_t *dst = (uint32_t *)(data_start + offset);
- *dst = *src;
- offset += 4; // 指针偏移4字节,也即一个 word 的长度
- }
- // 清零部分略
复制代码 可以看到,本质上就是把 flash 中的数据拷贝到 ram 中,拷贝的长度为 _edata - _sdata,源地址为 flash 中的 _sidata,目的地址为 ram 中的 _sdata。清零部分同理,只不过是把拷贝改为设置为0.
因此,可以借助C库函数 memcpy 和 memset 来实现 data_init 和 bss_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
- .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
- };
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |