stm32 通用bootloader_STM32上电了之后都发生了啥?

本文详细介绍了STM32F103RCT6在上电后从不同启动模式到SystemInit的整个过程。首先,通过BOOT引脚配置启动模式,如主闪存、系统存储器或内嵌SRAM。接着,解释了寄存器组,特别是堆栈指针R13(MSP和PSP)的作用。在接入电源后,单片机加载栈指针,并进入复位子程序Reset_Handler。然后,调用SystemInit进行系统初始化,设置系统时钟。最后,执行__main函数前的准备工作。
摘要由CSDN通过智能技术生成
茶余饭后,我经常会思考一些在开发过程中所遇到的问题,例如,在STM32上电之后为什么能精确地找到我的main函数,.bss与.data的建立是在什么时候完成的,为什么我没有写任何的拷贝代码,但单片机却任然能将Flash中的代码拷贝到SRAM中执行?在一开始作为初学者,这些问题并没有影响到我的开发,但随着自己渐渐地深入,我开始需要在STM32中运行μC/OS,需要优化SPI来提升LCD的刷新速率,需要外接存储器,需要自己画板子,这时候这些问题便开始变得很重要了

硬件平台是意法半导体公司的STM32F103RCT6,使用ST-LINKKeil uVision5上进行调试

如果接下来的内容你都了如指掌,那么省去这些废话,从标题'接入电源'处开始看吧

启动模式(Boot modes)

在单片机上电之前,我们需要根据BOOT[1:0]引脚来配置启动模式,一般情况下你一开始把程序烧录在哪个存储器就选择从哪个存储器启动,BOOT引脚与启动模式的配置关系如下图所示:

7cea7497e3898e96b8e2af52003d445e.png
  • Main Flash memory(主闪存)

这是正常情况下默认的启动模式,从主闪存启动(一般情况下是单片机中最大的那块存储器),你可以把这个理解成电脑上的辅存/外存(HDD(机械硬盘)或者SSD(固态硬盘)),在此模式下,整个闪存被映射到以地址'0x0000 0000'开始的存储区域(从原有的地址'0x0800 0000'也可以访问主闪存),我们通过调试可以发现地址'0x0000 0000'处的字节信息其实和地址'0x0800 0000'处的是一样的(这里并不是说有两份,而是主闪存被同时映射到两个地址上去了,因此两个不同的地址其实都是在访问主闪存)

82010cca922555d3d9364d735a80b025.png
不同的地址,相同的内容
  • System memory(系统存储器)

传说中内嵌的Boot Loader便在其中,它是一段ST(意法半导体公司)在生产芯片时便烧录好的程序(仔细思考一下当你在以ISP(In-System Programming)的方式使用串口下载程序到单片机的时候,单片机是如何接受来自串口的各种下载指令并将接收到的代码块存放在指定存储器位置的?这个时候的启动模式正是从系统存储器启动,上电后内部的Boot Loader负责暂时接管单片机来接收来自串口的指令与数据),整个闪存被映射到以地址'0x0000 0000'开始的存储区域(从原有的地址'0x1FFF F000'也可以访问主闪存)

  • Embedded SRAM(内嵌静态存储器)

从内嵌的SRAM启动,你可以把这个理解成电脑上的主存(一般也称作内存),其只能在地址'0x2000 0000'处访问,在某些情况下为了对单片机进行快速下载调试,会设置成这种模式并将代码烧录到其中,但缺点是单片机掉电之后其中的数据会丢失(SRAM的特性)

我们都知道闪存的擦写次数是有限的,而且在写入速度上比SRAM慢很多,因此我们可以直接把程序下载到SRAM中进行调试,下图是在Keil中的相关设置:

2ab1fcde727f7cf33a31beb581a5b79b.png
共48KB的SRAM,分两份,低24KB空间用来充当Flash的角色,高24KB空间才是用作内存

寄存器组(Registers)

Cortex-M3处理器共有R0-R15共16个寄存器

  • R0-R12通用寄存器(General purpose register),没有什么特别之处
  • R13比较特殊,实际上R13是两个寄存器,MSP(Main Stack Pointer)PSP(Process Stack Pointer),但在同一时刻只使用其中一个,具体使用哪一个取决于当前单片机所处的模式,MSP寄存器是单片机复位后默认使用的堆栈指针,也用于异常、中断、fault的服务程序、属于特权级线程模式下的使用。PSP寄存器一般在单片机有内置操作系统的情况下,在处于用户级线程模式下的用户程序中使用,这时PSP指向当前用户程序所处的堆栈栈顶
  • R14连接寄存器LR(Link Register),在代码进入子程序、异常、中断、fault的时候用于保存断点地址,以便在子程序(或其他情况)正常结束之后返回到断点(当超过两层嵌套时,LR保存当前程序的上层断点地址,而LR中被挤出的值被压入堆栈)
  • R15程序计数器PC(Program Counter Register)的,不考虑流水线,单片机在执行完当前机器指令后,下一条指令总是从PC所存储的地址处执行,随后PC自加1

接入电源

那么,现在你终于将开发板的USB接入到了电源适配器上...

这里我选择默认从主闪存启动

装载栈指针

接入电源后单片机便默认处于特权级线程模式(Privileged thread mode),首先将从地址'0x0000 0000'处取一个字(4个字节)装载到R13(此模式下对应的是MSP,主堆栈指针)

bbe8d51c6837895cde3d8347dde932b3.png

这个地址正是下图中所定义的堆栈栈顶标号'__initial_sp'(下面两张图均来自启动文件'startup_stm32f10x_hd.s')

1662c36fe00239a575ca46f2a6533beb.png
初始堆栈定义

'DCD'伪指令用于定义以一个字为单位的数据,从中可以看到,向量表的第一个字是栈顶地址,第二个字便是Reset_Handler复位程序,其余的是各种中断(异常或fault)服务程序的入口地址(此处向量表的内容没有列全)

6090f3eb8fce30d8394333f39170ae23.png
向量表

进入复位子程序

随后把地址'0x0000 0004'处所存储的32位数据装载到PC,接下来便跳转到PC所存储的地址处(因为STM32默认使用的是小端法(Little endian,低地址值处存放低位字节),因此此时装入PC的数据是'0x0800 0145'而不是'0x4501 0008'),即标号'Reset_Handler'复位子程序处,在这里执行上电后的复位操作

e3ef150a2ed780a8535838714fc1a72f.png
Reset_Handler子程序

'EXPORT'与'IMPORT'分别是向外导出与往内引入符号(说明子程序'_main'与'SystemInit'并非在此文件中定义),'LDR R0, =SystemInit'表示使用LDR伪指令,将标号'SystemInit'的内容装载到寄存器R0中,后续的'BLX R0'将使得MCU跳转至'SystemInit'子程序中去进行系统初始化,同理在'SystemInit'子程序结束之后,执行''__main'子程序

SystemInit系统初始化

这里我们找到了位于'system_stm32f10x.c'源文件中的'SystemInit'函数

void SystemInit(void) {

    /* 时钟控制寄存器RCC_CR的HSION位置1,将开启内部8MHz振荡器 */
    RCC->CR |= (uint32_t)0x00000001;

    /*
     * ADC预分频 ADCPRE[1:0] := 00 (ADCCLK时钟是PCLK2时钟的2分频)
     * 高速APB(APB2)预分频 PPRE2[2:0] := 000 (不分频,HCLK与PCLK2同频率)
     * 低速APB(APB1)预分频 PPRE1[2:0] := 000 (不分频,HCLK与PCLK1同频率)
     * AHB预分频 HPRE[3:0] := 000 (不分频,SYSCLK与HCLK同频率)
     * 系统时钟切换状态 SWS[1:0] := 00 (选择HSI作为系统时钟)
     */
    RCC->CFGR &= (uint32_t)0xF8FF0000;

    /* 
     * PLL使能 PLLON := 0 (PLL关闭)
     * 时钟安全系统使能 CSSON := 0 (时钟监测器关闭)
     * 外部高速时钟使能 HSEON := 0 (HSE振荡器关闭)
     */
    RCC->CR &= (uint32_t)0xFEF6FFFF;

    /* 外部高速时钟旁路 HSEBYP := 0 (取消外部高速时钟的就绪状态) */
    RCC->CR &= (uint32_t)0xFFFBFFFF;

    /*
     * 全速USB OTG预分频 OTGFSPRE := 0 (PLLVCO时钟(2倍的PLLCLK)被除以3来得到OTGFSCLK(此时PLL必须输出72MHz))
     * PLL倍频系数 PLLMUL[3:0] := 0000 (保留)
     * PREDIV1分频因子的低位 PLLXTPRE := 0 (这一位还是结合手册看吧,稍微复杂)
     * PLL输入时钟源 PLLSRC := 0 (HSI振荡器时钟经2分频后作为PLL输入时钟)
     */
    RCC->CFGR &= (uint32_t)0xFF80FFFF;

    /* 失能一大堆的就绪中断标志 */
    RCC->CIR = 0x009F0000;

    /* 配置系统时钟频率以及HCLK、PCLK2、PCLK1的预分频 */
    SetSysClock();

    /* 'SCB->VTOR'向量表偏移寄存器存放的是向量表的地址,这使得我们可以重定位向量表 */
    SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
}

进入'SetSysClock'函数,这里我们可以看到有多个预置的系统时钟配置方案,默认情况下使用最高分频72MHz

static void SetSysClock(void) {
#ifdef SYSCLK_FREQ_HSE
    SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
    SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
    SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
    SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
    SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
    SetSysClockTo72();
#endif 
}

进入'SetSysClockTo72'

static void SetSysClockTo72(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
    /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
    /* Enable HSE */    
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
    /* Wait till HSE is ready and if Time out is reached exit */
    do {
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;  
    } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    if ((RCC->CR & RCC_CR_HSERDY) != RESET) HSEStatus = (uint32_t)0x01;
    else HSEStatus = (uint32_t)0x00;

    if (HSEStatus == (uint32_t)0x01) {
        /* Enable Prefetch Buffer */
        FLASH->ACR |= FLASH_ACR_PRFTBE;

        /* Flash 2 wait state */
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

 
        /* HCLK = SYSCLK */
        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
        /* PCLK2 = HCLK */
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
        /* PCLK1 = HCLK */
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

        /*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
        
        /* Enable PLL */
        RCC->CR |= RCC_CR_PLLON;

        /* Wait till PLL is ready */
        while((RCC->CR & RCC_CR_PLLRDY) == 0);
    
        /* Select PLL as system clock source */
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

        /* Wait till PLL is used as system clock source */
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08);
    }
    else { 
        /* If HSE fails to start-up, the application will have wrong clock 
         configuration. User can add here some code to deal with this error */
    }
}

__main,在main函数之前的准备工作

...

未完待续...随缘更新

参考文档:
《STM32F10x-中文参考手册》《Cortex-M3 权威指南》《RM0008 Reference manual》《AN2606 Application note》《PM0056 Programming manual》《Arm Debug Interface Architecture Specification》《Fundamentals of Electric Circuits 5th》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值