最近在学习IAP远程OTA升级单片机固件程序,发现自己对单片机的启动流程还不是那么了解,就总结整理一下吧。
上面内核初始化的最后一步,是把复位中断函数Reset_Handler的地址赋值给PC指针,然后跳转执行复位中断处理函数,我们来看一下在复位中断里内核都做了哪些操作。
我们随便打开一个标准库工程的启动文件,都能找到下面这段代码:
; 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()函数的代码部分:
/**
* @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()主要做了两件事:
注意:
__main 其实不是我们定义的,当编译器编译时,只要遇到这个标号就会定义这个函数,该函数的主要功能是:负责初始化栈、堆,配置系统境,并在最后跳转到用户自定义的main函数,从此来到C的世界。
所以在不同的IDE该函数的名称也有所不同,但所实现的功能大同小异:
最后,总结下STM32从Flash的启动流程:
1、初始化堆栈指针。 单片机复位后从0x0800 0000处读取栈顶地址并保存。
2、初始化PC指针。 从0x0800 0004读取中断向量表的起始地址(复位中断入口地址),接着跳转到复位程序
3、初始向量表,然后设置时钟,设置堆栈。
4、最后跳转到C空间的main函数,即进入用户程序。
类型 | 起始地址 | 存储器 | 用途 |
---|---|---|---|
ROM | 0x0800 0000 | 程序存储器Flash | 存储C语言编译后的程序代码 |
0x1FFF F000 | 系统存储器 | 存储BootLoader,用于串口下载 | |
0x1FFF F800 | 选项字节 | 存储一些独立于程序代码的配置参数 | |
RAM | 0x2000 0000 | 运行内存SRAM | 存储运行过程中的临时变量 |
0x4000 0000 | 外设寄存器 | 存储各个外设的配置参数 | |
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处 |