STM32 启动执行逻辑与代码烧入方法详解:从底层原理到实操落地
- 背景概要
- STM32启动和执行的核心逻辑链条
- 代码烧入到STM32的途径方法
- 结束语
背景概要
在学习STM32时候我们知道代码需要通过一些下载器(如ST-Link、J-Link)或者串口下载烧入到STM32芯片内,从而让芯片执行我们的代码逻辑链条。但是我们是否有想过这样问题:STM32到底是如何知道我们需要下载烧入程序,如何知道我们要运行程序,STM32在执行我们程序之前需要做什么工作,启动它时候它干了什么,STM32在启动和执行我们程序时候它的工作流程逻辑底层原理到底是什么,它是怎么一步步工作的?又或者说当我们学习STM32时候不免的接触到的“中断向量表”“
“boot引脚或者说Bootloader程序”它们到底是什么,有什么用,在STM32工作时候扮演着一个什么样的角色,这也是本文所聚焦和要回答的内容。
本文总共聚焦两个问题:
- 代码如何烧入到STM32里面,其途径是什么
- 当代码烧入后STM32上电或复位后的启动和执行逻辑
本文将以上面聚焦的问题进行详细形象生动化的回答,不仅能让初学者能理解代码是如何被运行起来的,还能让有基础的读者能从更加深刻化理解STM32为学习它打下更坚固的基础
本文将会以两个视角来展开对于STM32工作的底层逻辑链条讲解:
- 第一个视角将以稍微专业的名词术语讲解这方面知识,给具备这方面基础的读者节约时间,使其迅速了解STM32启动和执行的核心逻辑链条、
- 第二个视角将会以形象的比喻来生动化这方面知识,让读了第一视角还有些疑惑的读者彻底理解,同时让刚刚学习这方面读者也能从零理解它。
STM32启动和执行的核心逻辑链条
STM32启动和执行的核心逻辑链条流程图可以大概的归纳为下面这个流程图
下面进行这个流程图的详细讲解:
当你的代码烧入到STM32也就是存储到STM32的Flash区域后,当STM32上电或者复位后,STM32的CPU就会访问STM32单片机的起始地址0x0000 0000 (STM32是32位的所以内存地址就是32个bit位)经过STM32芯片的存储器重映射,CPU从起始访问地址0x0000 0000到访问为STM32的ROM区域中三个存储器之一,而具体访问哪一个存储器取决于STM32的boot引脚的配置,这点我们可以参考STM32手册的boot引脚配置图(如下图)
之后当CPU访问了其中某一个存储器后,这里我们以访问了Flash存储器为例子,其他同理,当CPU访问Flash存储器(地址位0x0800 0000)后就会发现位于Flash起始地址0x0800 0000上的中断向量表,接下来CPU将完成四个工作(也就是中断向量表的任务):
①初始化栈顶指针 (MSP):CPU首先读取向量表的第一个字(4字节,地址0x0800 0000),并将这个值赋给主堆栈指针(MSP)。这是为后续执行C代码建立栈空间的基础,没有它程序无法运行。
②找到复位入口:CPU紧接着读取第二个字(地址0x0800 0004),这个值就是复位服务程序的入口地址。
③跳转执行_mian:CPU然后跳转到这个“复位服务程序”的地址开始执行。这个函数是由芯片厂商提供的,通常由汇编编写,是启动流程的真正起点,复位服务程序会调用一个非常重要的函数 __main(注意,这不是我们的main()函数)。它是由编译器自动生成,负责搭建C语言运行环境。
它主要做两件大事:
1.初始化数据段 (.data段):将已经初始化的全局变量和静态变量的初始值从Flash ROM中拷贝到RAM中去。因为Flash是只读的,而变量需要在RAM中被修改。
2.清零零初始化段 (.bss段):将未初始化或显式初始化为0的全局变量和静态变量所在的内存区域清零。
④执行程序代码main():__main函数完成所有初始化工作后,CPU最终会自动调用以及不断扫描执行用户的 main() 函数(这块代码位于Flash存储器内)。
如果你不是很理解上面CPU的几个工作的话下面请看这个比喻:
我们先设定一个背景:
空的快递仓库 = STM32的内存(RAM),里面有很多货架(地址),但现在是空的。
仓库管理员 =STM32。
文件=中断向量表
新来的工人 = CPU。
总部档案室 = Flash存储器,里面存放着永久性的文件和指令(你的程序代码和初始值)。
① 初始化栈顶指针 (MSP):搭建“临时工作台”
专业解释: CPU从向量表第一个条目读取值,并赋给MSP寄存器,建立栈空间。
形象解释: 工人(CPU)上班第一天,仓库管理员(STM32)告诉工人(CPU)产生的垃圾和临时包裹(局部变量、函数调用记录)具体放在哪里要去看一个文件(中断向量表),告诉他这个文件在他的办公室桌子上(地址0x0800 0000),工人来到他的办公室桌子上(地址0x0800 0000)了看到了文件上(中断向量表)规定了:“工人(CPU)产生的垃圾和临时包裹(局部变量、函数调用记录)必须放在的特定的位置。”工人(CPU)害怕记不住于是乎拿出来一个小本子记录(MSP寄存器)它把这个文件上说的地方记下来了(也就是对MSP的初始值设置完成了)接下来当 工人产生了一个临时包裹(比如调用一个函数,有一个局部变量 int a;),他就把它堆在“货架 2000 1000”上。
同时,他在小本本上把当前堆放点更新为 2000 0FFC(向前挪一个位置)。因为他知道,下一个包裹要往前面的空位堆,不能堆在同一个地方。当他处理完这个函数,需要把临时包裹扔掉时,他就根据小本本的记录,找到最近堆的那个包裹,把它清理掉。然后在小本本上把堆放点又改回 2000 1000。这个过程就是栈的“后进先出”。
为什么必须做?: 没有这个工作台,工人就没地方放临时包裹(局部变量)、也没法记录干到哪了(函数调用返回地址),整个仓库的运营将瞬间瘫痪。这是一切C代码执行的基础。
————————————————————————————————————
②找到复位入口: 找到“启动专家”
专业解释::CPU从向量表第二个条目读取复位服务程序的入口地址。
形象解释: 当工人把“临时工作台”搭建好后(也就是完成了初始化栈顶指针 MSP),仓库管理员(STM32)给工人(CPU)下达了一个指令:去办公室(复位向量的地址)找到启动专家(复位服务程序),让他干活。
为什么必须做?: 工人自己不知道如何准备仓库环境,他必须找到一个专业的负责人来执行一套复杂的准备工作。
————————————————————————————————————
③跳转执行_mian:“启动专家”干活:搭建C语言环境
专业解释::跳转到复位服务程序执行,它调用__main函数来初始化.data和.bss段。
形象解释: 当工人找到了“启动专家”(复位服务程序)叫他干活后。专家他并没有自己干活,而是叫来了他的两位得力助手(__main函数):
——助手A:搬运档案(初始化.data段)
专家发现,有些每天都要用的常用文件(已初始化的全局变量,如 int a = 5;)的原始档案还锁在总部档案室(Flash) 里。
他命令助手A:“去档案室,把这些常用文件的复印件全部搬到仓库的日常办公区(RAM) 来!这样我们平时修改数据就方便了。”(因为档案室是只读的,不能直接在上面写字)。
助手B:清理场地(初始化.bss段)
专家又发现,仓库里规划好的一片新场地(未初始化的全局变量,如 int b;)现在还堆满了建筑垃圾(随机值)。
——他命令助手B:“把这片新场地全部打扫干净,清零! 这样我们以后就可以直接使用了。
工人(CPU)立刻走到仓库最里面的2000 1000号货架,挂上一个“临时物品堆放起点”的牌子,并在他的小本本(MSP寄存器)上记下这个位置。
为什么必须做?: 这是C语言程序能正确运行的隐藏前提。如果不把初始值从Flash拷到RAM,变量就没有正确的值;如果不清理RAM的垃圾值,未初始化的变量就是随机的,程序行为会不可预测。
————————————————————————————————————
最后当这个工厂在不断运行时候如果出现了突发事件(各种中断的),那么工人(CPU)将会再次到仓库管理员(STM32)的办公室桌子上(地址0x0800 0000)去阅读那份文件(中断向量表)这个文件就会详细告诉当遇到什么类型的突发事件(各种中断的)应该去找什么部分(各个中断的处理函数Handler)去负责。
boot引脚在我们STM32单片机的下图红色框区域内,它的状态由跳线帽所决定,它是配置STM32上电后CPU根据存储器重映射到ROM的哪一个存储器的核心依据,只有在上电或者复位时候STM32单片机才会检查boot引脚的配置从而确定该去ROM的哪个存储器
下面这个图详细的讲解了STM32的两大类存储器ROM和RAM,以及它们各自具体的存储器和用途
相信你读完上面的这个比喻后就能彻底了弄懂了STM32启动和执行的核心逻辑链条,以及我们开头所说的那几个问题了
代码烧入到STM32的途径方法
根据上面的STM32启动和执行的核心逻辑链条,我们知道了STM32的CPU最后会不断的扫描Flash里面存储的我们的main()函数代码,也就是说我们如果想要代码被STM32执行,就要把代码烧入到STM32的Flash区域内。而代码烧入到STM32的Flash途径主要有两种:
- 直接法:通过下载器(如ST-Link)下载——把编译好代码连接上位机(电脑)和单片机(STM32)然后通过编译器(如Keil5)下载按钮直接把代码程序下载到Flash区域,这也是我们经常 用的方法
- 间接法:通过串口下载——把编译好的代码先下载至系统存储器里,再经过系统存储器的Bootloader程序,把代码刷新下载到Flash里面
也就是可以总结为下面这个流程图:
相信对于直接法大家已经不陌生了,那么我们重点来看一下间接法:
间接法要求我们通过串口下载也就是把代码先下载到系统存储器,然后再这个存储器内由Bootloader程序帮我们完成把代码下载刷新到Flash里面,也就是要求我们两步走:
①配置STM32上电复位后是系统存储器
②通过串口下载并且执行Bootloader程序把代码下载刷新到Flash里面
对于第一步而言也就是我们只需要根据上文我们说的通过跳线帽把BOOT0置1,BOOT1置0,并且按下复位按键就能使得单片机上电后CPU访问到ROM的系统存储器
对于第二步而言我们得用串口将电脑和STM32的USART1连接在一起(通过连接USB转TTL模块到STM32的USART1)然后在电脑上打开烧录工具(如STM32CubeProgrammer, Flash Loader Demonstrator等),选择正确的串口号,设置波特率(常用115200),然后进行下载就好了
由于我们前面已经做了前置工作STM32上电复位后CPU访问的是系统存储器,而系统存储器里面又内置了Bootloader程序,它收到串口下载的代码后将会自动执行这段程序,从而完成把代码下载到Flash里面。
接着如果要执行Flash里面代码,就需要通过跳线帽把BOOT0置0,BOOT1置0,然后复位,让CPU访问Flash然后执行代码
以上就是代码烧入到STM32的两种途径方法。
内置的Bootloader程序是什么:这段Bootloader代码是由ST公司在芯片生产过程中就预先固化在芯片内部一个特殊、受保护的ROM区域(称为“系统存储器”)。用户无法修改或擦除它,这段代码的唯一功能就是与外界通信,接收新的程序数据,并将其写入到用户Flash内存的指定位置。要运行这段Bootloader程序,只需要CPU从“系统存储器”启动,也就是BOOT0置1,BOOT1置0(在上文表格有提及)然后给芯片复位(按下NRST复位键)。
当Bootloader运行后干什么,它会通过你选择的接口(这里是串口)等待主机(通常是电脑上的烧录软件)发送指令(也就是下载你的代码)当Bootloader收到指令后会回复一个应答信号(ACK,0x79),表明连接成功,Bootloader已就绪,然后将会执行以下操作:
擦除命令:告诉Bootloader擦除Flash的特定扇区。
- 写命令:告诉Bootloader准备接收数据,并指定要写入的Flash地址。
- 校验命令:读取已写入的数据进行校验。
- 执行命令:让芯片从用户Flash地址开始运行程序。
所有数据发送并写入完成后,电脑串口的烧入软件会发送一个“跳转”或“执行”命令。Bootloader会复位外设,然后将CPU的程序计数器(PC)指向用户Flash的起始地址(通常是0x08000000)。
但是最后,非常重要的一步的是你要执行你的代码:需要将BOOT0引脚重新接回低电平(GND),并再次复位芯片。这样,CPU就会从用户Flash启动,运行你刚刚下载的新程序。
结束语
在构思和撰写这篇文章的过程中,我始终在思考一个问题:如何让那些尚不了解或对STM32启动与执行逻辑感到陌生的朋友,能够真正理解其中的原理?不仅明白“怎么做”,更懂得“为什么这样做”。
于是,我尝试从一个初学者的视角出发,一步步拆解STM32的启动流程与程序运行机制,并重点解析了两种常见的程序烧录方式背后的原理。我的目标,是希望帮助你从根本上建立清晰的认识——不再只是会写代码、会下载程序,而是真正看懂每一步背后发生了什么。
作为一名STM32的初学者,我深知入门时遇到的困惑与障碍。正因为自己走过弯路,才更希望后来者能少一些迷茫。在写作的四个多小时里,我反复查阅资料、梳理逻辑、构思比喻,只为了把复杂的概念讲得透彻、生动。虽然文章中难免仍有不足之处,但我衷心希望它能够为你打开一扇理解底层原理的窗。
如果你觉得这篇文章对你有所帮助,请不妨点一个赞👍——你的认可,是我持续分享的最大动力。感谢你的阅读,期待与你共同进步!