目录标题
前言
最近在学习IAP远程OTA升级单片机固件程序,发现自己对单片机的启动流程还不是那么了解,就总结整理一下吧。
启动流程概述
- 1.内核初始化;
- 1.内核复位和NVIC寄存器部分清零;
- 2.内核设置堆栈:内核从向量表0地址读出堆栈地址,并设置主堆栈指针(SP_main);
- 3.设置PC和LR寄存器
- a. LR设置未初始复位值0xFFFF FFFF
- b. 单片机的内部硬件机制自动将PC指针定位到中断向量表的复位中断向量处,把复位中断函数Reset_Handler的地址赋值给PC指针,然后跳转执行Reset_Handler。
- 2.强制PC指针指向中断向量表的复位中断向量执行复位中断函数;
- 3.在复位中断函数中调用 SystemInit 函数,初始化时钟 ,配置中断向量表等
- 4.调用 __main 函数完成全局/静态变量的初始化和重定位工作,初始化堆栈和库函数
- 5.跳转到main函数中执行
复位中断函数详解
上面内核初始化的最后一步,是把复位中断函数Reset_Handler的地址赋值给PC指针,然后跳转执行复位中断处理函数,我们来看一下在复位中断里内核都做了哪些操作。
我们随便打开一个标准库工程的启动文件,都能找到下面这段代码:
c
; Reset handler //程序注释(汇编中;表示注释)
Reset_Handler PROC //定义了一个子程序:Reset_Handler
EXPORT Reset_Handler [WEAK] //EXPORT 表明此函数可供启动模块调用
IMPORT __main //IMPORT 表明函数定义在外部,链接时需要去寻找
IMPORT SystemInit
LDR R0, =SystemInit //将SystemInit地址加载到R0寄存器
BLX R0 //跳转到R0执行SystemInit程序
LDR R0, =__main //将__main地址加载到R0寄存器
BX R0 //跳转到R0执行_main程序
ENDP //表明程序结束
根据代码我们可以看出,复位中断主要是调用了SystemInit 和 __main 这两个函数,下面我们再来详细介绍下这两个函数都做了什么工作。
SystemInit函数详解
利用跳转功能,我们可以看到SystemInit()函数的代码部分:
c
/**
* @brief 设置微控制器系统
* 初始化嵌入式Flash接口、PLL,并且更新SystemCoreClock 变量
* @note 此功能仅能在复位后使用.
* @param None
* @retval None
*/
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
根据上面的代码,我们可以看出,SystemInit()主要做了两件事:
- 1、初始化时钟:SYSCLK,HCLK,PCLK2 PCLK1 预分频器等
- 2、配置中断向量表:中断向量表的定位是在 Flash 还是 SRAM,以及是否需要偏移
注意:
- 可以通过system_stm32f1xx文件中的宏定义修改系统时钟频率(通过设置锁相环的相关系数),中断向量表的地址(位于SRAM还是Flsah,是否偏移,偏移地址多少等参数)
- 函数内含VTOR寄存器(即中断向量偏移)设置:SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; 产品IAP由BootLoader跳转到app程序时,需设置中断向量偏移。
__main函数详解
__main 其实不是我们定义的,当编译器编译时,只要遇到这个标号就会定义这个函数,该函数的主要功能是:负责初始化栈、堆,配置系统境,并在最后跳转到用户自定义的main函数,从此来到C的世界。
所以在不同的IDE该函数的名称也有所不同,但所实现的功能大同小异:
- 完成全局变量/静态变量/常量的初始化和重定位工作
- a. 跳转进入__scatterload_rt2函数:通过设置四个寄存器来配置待copy内容(静态变量、全局变量、常量)的的加载域和运行域,设置待copy内容的大小,为后续__scatterload_cpy()函数服务。
- b. 跳转进入__scatterload_cpy函数,完成静态变量、全局变量、常量的从flash到SRAM的重定位。
- c. 跳转进入__scatterload_zeroinit函数,完成未初始化的全局变量的初始化。
- 初始化堆栈(这里指程序栈)
- 跳转进入__user_steup_stackheap函数:调用 __user_libspac__user_libspace 为C库保持了静态数据。这是一个96字节,0初始化的数据块,该块由C库创建。在C库初始化期间可以用来当做临时栈。再调用 __user_initial_stackheap 用户的初始化堆栈函数,实现用户的堆栈的配置,调用 _fp_init 和 __rt_fp_status_addr (C库函数) 两个函数调用实现浮点运算的支持。
- 如果在"魔法棒"---"Target"中编译勾选了"Use Micro_lib",程序则采用单区存放堆栈的方式;否则,采用双区存储的方式,分别初始化堆区、栈区。
- 程序跳转,进入main()函数,执行用户代码。
最后,总结下STM32从Flash的启动流程:
1、初始化堆栈指针。 单片机复位后从0x0800 0000处读取栈顶地址并保存。
2、初始化PC指针。 从0x0800 0004读取中断向量表的起始地址(复位中断入口地址),接着跳转到复位程序
3、初始向量表,然后设置时钟,设置堆栈。
4、最后跳转到C空间的main函数,即进入用户程序。
附录
stm32单片机的存储器映像
类型 | 起始地址 | 存储器 | 用途 |
---|---|---|---|
ROM | 0x0800 0000 | 程序存储器Flash | 存储C语言编译后的程序代码 |
ROM | 0x1FFF F000 | 系统存储器 | 存储BootLoader,用于串口下载 |
ROM | 0x1FFF F800 | 选项字节 | 存储一些独立于程序代码的配置参数 |
RAM | 0x2000 0000 | 运行内存SRAM | 存储运行过程中的临时变量 |
RAM | 0x4000 0000 | 外设寄存器 | 存储各个外设的配置参数 |
RAM | 0xE000 0000 | 内核外设寄存器 | 存储内核各个外设的配置参数 |
中断向量表的映射
BOOT启动方式主要有三种,主闪存存储器启动、系统存储器启动、内置SRAM启动,BOOT1和BOOT0在芯片复位时的电平状态决定了芯片复位后从哪个区域开始执行程序。
注:启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到0x00000000地址位置)。所以说STM32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。
对应的BOOT引脚状态及启动地址如下图:
BOOT1 | BOOT0 | 启动模式 | 启动地址 | 说明 |
---|---|---|---|---|
X | 0 | 主闪存存储器Flash | 0x0800 0000 | 中断向量表定位于FLASH区,主闪存被选为启动区域,最常用,用户代码。同时复位后PC指针位于0x2000000处 |
0 | 1 | 系统存储器 | 0x1FFF F000 | 系统存储器被选为启动区域,程序功能由厂家设置。中断向量表定位于内置Bootloader区,此时可通过串口下载程序的二进制文件到flash区 |
1 | 1 | 内置SRAM | 0x2000 0000 | 内置SRAM被选为启动区域,中断向量表定位于SRAM区,同时复位后PC指针位于0x2000000处 |