文章目录
- BOOT引脚选择
- 向量表加载
- 硬件加载动作
- Reset_Handler
-
- SystemInit()
- __main
-
- [.data 拷贝](#.data 拷贝)
- [.bss 清零](#.bss 清零)
- __rt_entry
- main()
BOOT引脚选择

0x00000000~0x0007FFFF这段空间属于重映射区,根据BOOT的不同选择映射到不同的内存区域
| 模式 | 启动地址 | 说明 |
|---|---|---|
| 主 Flash(正常情况) | 0x08000000 | 正常用户程序 |
| System Memory | 0x1FFF0000 | 内置 BootLoader(不能修改) |
| SRAM | 0x20000000 | 调试/特殊用途 |
向量表加载
假设我们现在选择了主flash模式,内存映射到0x08000000开始执行,首先进行的就是读取加载向量表
对于向量表我们必须要知道的是
- 每个占 4 字节(32位)(一个函数指针)
- 数组[中断号] = 函数地址
- 发生中断 → 查表 → 跳转函数
系统异常向量表
| 地址(Flash) | 偏移 | 向量名 | 功能 |
|---|---|---|---|
| 0x08000000 | 0x00 | Initial MSP | 主栈初始值 |
| 0x08000004 | 0x04 | Reset_Handler | 复位入口(程序起点) |
| 0x08000008 | 0x08 | NMI_Handler | 不可屏蔽中断 |
| 0x0800000C | 0x0C | HardFault_Handler | 硬Fault异常 |
| 0x08000010 | 0x10 | MemManage_Handler | 内存管理异常 |
| 0x08000014 | 0x14 | BusFault_Handler | 总线错误 |
| 0x08000018 | 0x18 | UsageFault_Handler | 使用错误 |
| 0x0800001C | 0x1C | Reserved | 保留 |
| 0x08000020 | 0x20 | Reserved | 保留 |
| 0x08000024 | 0x24 | Reserved | 保留 |
| 0x08000028 | 0x28 | Reserved | 保留 |
| 0x0800002C | 0x2C | SVC_Handler | 系统服务调用 |
| 0x08000030 | 0x30 | DebugMon_Handler | 调试监视 |
| 0x08000034 | 0x34 | Reserved | 保留 |
| 0x08000038 | 0x38 | PendSV_Handler | 上下文切换(RTOS关键) |
| 0x0800003C | 0x3C | SysTick_Handler | 系统滴答定时器 |
外设中断向量表
| 地址(Flash) | 偏移 | 向量名 | 功能 |
|---|---|---|---|
| 0x08000040 | 0x40 | WWDG_IRQHandler | 窗口看门狗 |
| 0x08000044 | 0x44 | PVD_IRQHandler | 电压检测 |
| 0x08000048 | 0x48 | TAMPER_IRQHandler | 篡改检测 |
| 0x0800004C | 0x4C | RTC_IRQHandler | RTC |
| 0x08000050 | 0x50 | FLASH_IRQHandler | Flash 操作 |
| 0x08000054 | 0x54 | RCC_IRQHandler | 时钟控制 |
| 0x08000058 | 0x58 | EXTI0_IRQHandler | 外部中断0 |
| 0x0800005C | 0x5C | EXTI1_IRQHandler | 外部中断1 |
| 0x08000060 | 0x60 | EXTI2_IRQHandler | 外部中断2 |
| 0x08000064 | 0x64 | EXTI3_IRQHandler | 外部中断3 |
| 0x08000068 | 0x68 | EXTI4_IRQHandler | 外部中断4 |
| 0x0800006C | 0x6C | DMA1_Channel1_IRQHandler | DMA1通道1 |
| 0x08000070 | 0x70 | DMA1_Channel2_IRQHandler | DMA1通道2 |
| 0x08000074 | 0x74 | DMA1_Channel3_IRQHandler | DMA1通道3 |
| 0x08000078 | 0x78 | DMA1_Channel4_IRQHandler | DMA1通道4 |
| 0x0800007C | 0x7C | DMA1_Channel5_IRQHandler | DMA1通道5 |
| 0x08000080 | 0x80 | DMA1_Channel6_IRQHandler | DMA1通道6 |
| 0x08000084 | 0x84 | DMA1_Channel7_IRQHandler | DMA1通道7 |
| 0x08000088 | 0x88 | ADC1_2_IRQHandler | ADC |
| 0x0800008C | 0x8C | USB_HP_CAN1_TX_IRQHandler | USB/CAN发送 |
| 0x08000090 | 0x90 | USB_LP_CAN1_RX0_IRQHandler | USB/CAN接收 |
| 0x08000094 | 0x94 | CAN1_RX1_IRQHandler | CAN RX1 |
| 0x08000098 | 0x98 | CAN1_SCE_IRQHandler | CAN状态 |
| 0x0800009C | 0x9C | EXTI9_5_IRQHandler | 外部中断5~9 |
| 0x080000A0 | 0xA0 | TIM1_BRK_IRQHandler | 定时器1 |
| 0x080000A4 | 0xA4 | TIM1_UP_IRQHandler | 定时器1更新 |
| 0x080000A8 | 0xA8 | TIM1_TRG_COM_IRQHandler | 定时器1触发 |
| 0x080000AC | 0xAC | TIM1_CC_IRQHandler | 定时器1捕获比较 |
| 0x080000B0 | 0xB0 | TIM2_IRQHandler | 定时器2 |
| 0x080000B4 | 0xB4 | TIM3_IRQHandler | 定时器3 |
| 0x080000B8 | 0xB8 | TIM4_IRQHandler | 定时器4 |
| 0x080000BC | 0xBC | I2C1_EV_IRQHandler | I2C1事件 |
| 0x080000C0 | 0xC0 | I2C1_ER_IRQHandler | I2C1错误 |
| 0x080000C4 | 0xC4 | I2C2_EV_IRQHandler | I2C2事件 |
| 0x080000C8 | 0xC8 | I2C2_ER_IRQHandler | I2C2错误 |
| 0x080000CC | 0xCC | SPI1_IRQHandler | SPI1 |
| 0x080000D0 | 0xD0 | SPI2_IRQHandler | SPI2 |
| 0x080000D4 | 0xD4 | USART1_IRQHandler | 串口1 |
| 0x080000D8 | 0xD8 | USART2_IRQHandler | 串口2 |
| 0x080000DC | 0xDC | USART3_IRQHandler | 串口3 |
| 0x080000E0 | 0xE0 | EXTI15_10_IRQHandler | 外部中断10~15 |
| 0x080000E4 | 0xE4 | RTCAlarm_IRQHandler | RTC闹钟 |
| 0x080000E8 | 0xE8 | USBWakeUp_IRQHandler | USB唤醒 |
硬件加载动作
c
MSP = *(0x00000000);
PC = *(0x00000004);
从 0x00000000 (已经被映射为0x08000000)读出一个值 → 放进 MSP(主栈指针)
MSP 指向主栈的栈顶,在 SRAM 的高地址处
SRAM 0x20000000 ~ 0x20004FFF(20KB)
MSP 初始值为 0x20005000
从 0x00000004(已经被映射为0x08000004[Reset_Handler]) 读出一个值 → 放进 PC(程序入口)
Reset_Handler
这段程序在startup_stm32f103xx.s启动文件当中存储
c
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
;
先执行 SystemInit()
再跳到 __main
由 __main 完成C运行时初始化
最后进入 main()
SystemInit()
以F4芯片举例,这个函数的位置在system_stm32f4xx.c,SystemInit 负责最早期的系统级初始化,具体内容由芯片和工程模板决定
c
/**
* @brief Setup the microcontroller system
* Initialize the FPU setting, vector table location and External memory
* configuration.
* @param None
* @retval None
*/
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the Vector Table location -------------------------------------*/
#if defined(USER_VECT_TAB_ADDRESS)
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#endif /* USER_VECT_TAB_ADDRESS */
}
- 浮点单元配置
- 外部内存配置,如果有就把外部内存控制器配好;
- 如果用户指定了新的向量表地址,就把 CPU 的向量表基址改过去。
__main
MDK/ARM 编译器运行库当中,这个函数的具体运行流程如下
.data 拷贝
.bss 清零
调用 __rt_entry
.data 拷贝
把"有初值的全局/静态变量"从 Flash 的 .data初始化区 搬到 SRAM的 .data区
.bss 清零
把"未初始化的全局/静态变量"在 RAM的 .bss区 中全部清零
__rt_entry
__rt_entry 做的事:
- 给程序准备好栈和堆
- 把C库准备好(printf这些能用)
- 如果是C++,把全局对象构造好
- 最后调用 main()
main()
开始执行main函数
如果启用RTOS,之前步骤都不变,在main函数当中才进行RTOS的初始化