问题提出
大家不妨设想一下,cpu 的工作是什么,cpu 是没有主观意识的,它只会按照特定的指令执行相应的操作,用专业术语来说就是:取指 -> 译码 -> 执行,译码和执行肯定是在 cpu 内部进行操作的,并且前提是已经取到了指令。那现在问题来了,指令在哪?
cpu上电复位后执行的第一步操作就是取指令
- 问题1:指令存储在何处
我们在电脑上编写的程序最终是要烧写到芯片内部的 FLASH中(此处特指STM32)。
- 问题2:如何将可执行文件烧写至 FLASH 上
STM32 的启动方式有很多种,从主存 FLASH 启动,从 system memory 启动,从 SRAM 中启动。
- 问题3:从 SRAM 中启动,为什么需要重新设置中断向量表
接下来,我们将围绕这三个问题进行解答
猜想
既然 cpu 上电复位后第一步操作就是取指令,那么这个指令肯定是存储在掉电不丢失的存储介质上(rom、flash)。
- 猜想1:指令存储在掉电不丢失的存储介质上
我们最终生成的、cpu可以执行的可执行文件肯定是要通过某种外设将用户程序烧写到 FLASH 上,这一点肯定是毋庸置疑的,因为 cpu 与外围设备进行数据交互的时候是通过外设控制器来进行的。
- 猜想2:通过某种外设将可执行文件烧写至 FLASH 上
STM32 的 FLASH 基地址为 0X0800 0000 ,SRAM 基地址为 0X2000 0000。可不可能是因为这两个存储介质的地址不同,所以才要重新设置中断向量表。
因为我们都知道,中断向量表的首地址就是程序的入口地址。
- 猜想3:可能与基地址有关
实验验证
实验前必备知识
1. XIP设备
eXecute In Place
,即芯片内执行,指应用程序可以直接在 flash 闪存内运行,不必再把代码读到系统 RAM中。在我们的印象里,应用程序必须要从硬盘中加载到内存当中才可以被运行,但实际上应用程序是可以直接在flash 闪存运行的,也就是说,cpu 可以直接从 flash 中取出指令。对于 STM32 而言,它是有 XIP 设备的。
STM32F1 内存图
如上图所示,FLASH、SYSTEM MEMORY、OPTION BYTES 都是STM32内部的XIP设备。
F1 内存图信息不是很全,再看下 F4 的内存图。
STM32F4内存图:
我们可以看到,不论是 F1 还是 F4,XIP
设备都属于内存图中的 BLOCK0
区域内。
这样我们大概就知道了 STM32 内部的 XIP
设备在 0x0000 0000 ~ 0x1FFF FFFF
内。
上述的内存图是通过映射的方式将芯片的框图进行映射得到的,也就是说,上述这幅图是为了开发人员更好地面向芯片编程而抽象出来的一幅图。我们先来看下面这副图
STM32F1框图:
STM32F4框图:
对比两幅框图可以看出,F4 比 F1 复杂很多,特别体现在外设上,架构还是差不多的。
红色箭头所指向的就是译码电路。
如果你学过微机原理,那么你肯定知道,外设是通过译码电路连接到地址总线上,每一个外设都有其相对应的内存范围,当 cpu 发出的地址信息处于某一个外设的地址范围内,就选中了该外设,cpu就可以与该外设进行数据交互。
一个外设对应一个内存范围,那所有的外设结合起来,是不是就是对应一张图了。
2. STM32 启动配置
在 STM32F10xxx
里,可以通过 BOOT[1:0]
引脚选择三种不同启动模式。
在系统复位后,SYSCLK
的第 4 个上升沿, BOOT 引脚的值将被锁存。用户可以通过设置 BOOT1
和 BOOT0
引脚的状态,来选择在复位后的启动模式。
在启动延迟之后, CPU
从地址 0x0000 0000
获取堆栈顶的地址,并从启动存储器的 0x0000 0004
指示的地址开始执行代码。(这里先不验证,在之后的博客中会进行验证,但你需要记住,后面用的上)
因为固定的存储器映像,代码区始终从地址 0x0000 0000
开始(通过 ICode
和 DCode
总线访问),而数据区(SRAM
)始终从地址 0x2000 0000
开始(通过系统总线访问)。Cortex-M3
的 CPU 始终从 ICode
总线获取复位向量,即启动仅适合于从代码区开始(典型地从 Flash 启动)。STM32F10xxx
微控制器实现了一个特殊的机制,系统可以不仅仅从 Flash 存储器或系统存储器启动,还可以从内置 SRAM 启动。
根据选定的启动模式,主闪存存储器、系统存储器或 SRAM 可以按照以下方式访问:
- 从主闪存存储器启动:主闪存存储器被映射到启动空间(
0x00000000
),但仍然能够在它原有的地址(0x08000000
)访问它,即闪存存储器的内容可以在两个地址区域访问,0x00000000
或0x08000000
。 - 从系统存储器启动:系统存储器被映射到启动空间(
0x00000000
),但仍然能够在它原有的地址(互联型产品原有地址为0x1FFFB000
,其它产品原有地址为0x1FFFF000
)访问它。 - 从内置 SRAM 启动:只能在
0x20000000
开始的地址区访问SRAM
(当从内置SRAM
启动,在应用程序的初始化代码中,必须使用NVIC
的异常表和偏移寄存器,重新映射向量表到SRAM
中)。
一般情况下都是从主闪存模式启动的,也就是用户代码被烧写到 0x08000000
地址处。
内嵌的自举程序 (Bootloader)
内嵌的自举程序存放在系统存储区,由 ST 在生产线上写入,用于通过可用的串行接口对闪存存储器进行重新编程,也就是这个自举程序在出厂的时候就已经固化了。大家可以想一下内嵌的自举程序的作用是什么?想不出来也没关系,后面会讲到。
如果想要详细了解这个自举程序到底干了什么,可以看下官方文档:
STM32 microcontroller system memory boot mode
3. 可执行文件的形成过程
STM32 | hex文件、bin文件、axf文件的区别?
大家可以看下这篇博文,写的还是挺不错的!描述了最终烧写到STM32中的可执行代码的形成过程。
4. 三种复位
- 硬件复位
顾名思义通过硬件给系统一个复位,比如在电路板上设计一复位电路,通过按下按键就可以给系统实现一个复位,而无论系统在执行什么样的程序。复位后初始化一些配置芯片,硬件复位的作用区域一般是全局的。
- 软件复位
是通过软件给系统一个复位信号,如低电平或许是高电平(具体看系统设置)来实现复位操作软件复位一般是一些块结构复位。
- 上电复位
系统在上电的瞬间就执行复位操作, 上电复位里面包括硬件复位和软复位的操作,硬件复位和软复位是从上电复位里面的某点开始的启动操作。
复位需要初始化CPU系统,包括CPU和内存等。
验证猜想
1. 验证猜想-1
对于猜想1,其实不需要验证。代码肯定是要存储在掉电不丢失的存储介质上,否则,每次重新上电都要重新烧写程序,这是与事实相反的。而在实验前必备知识中,我们了解到 STM32
内部的 XIP
设备,那不就是代码存储的地方吗?并且也在 STM32
启动方式中详细地描述了代码存储位置。
- 如果从主FLASH启动,用户代码存储在0X 0800 0000
- 如果从
SYSTEM MEMORY
启动,里面存储的是Bootloader
,是芯片出厂的时候就已经固化好了的,可以从中读数据,但是不可以向其中写数据,它的作用就是:将用户程序通过可用的外设烧写到指定的地址处,然后启动STM32
。 - 如果从 SRAM 启动,用户代码存储在
0X2000 0000
2. 验证猜想-2
实验前的必备知识中已经大概地描述了最终烧写到 STM32
中的可执行文件的形成过程,现在我们需要验证的就是如何将可执行文件烧写到指定的存储设备中去(假设是 FLASH
,其实也可以是 SRAM
)
我第一次使用 Flymcu
(串口下载软件的时候),我脑海里就有一个疑问,就是这个软件到底是怎样使得 STM32
将生成的代码烧写到内部 FLASH
上的。这真的是很不可思议!因为 STM32
上电复位后肯定是要执行代码的,可是我还没有给它代码呢,它怎么会运作呢?当时我真的很迷惑。
其实,STM32
出厂的时候 Bootloader
(用于将用户程序下载到 STM32
内部指定地址处的固件(程序))就已经固化在了 System Memory
上了,可读写无效。
从 STM32
启动配置一节中我们知道,可以通过对 BOOT1
和 BOOT0
引脚上高低电平的改变从而实现 STM32
启动方式的不同。
如上所说,Bootloader
存储在 Sytem Memory
上,如果想要让 Bootloader
运行(将用户程序下载到指定内存地址处),那启动模式肯定是要选择以系统存储器的方式启动。
BOOT1 = 0 BOOT0 = 1 -> 系统存储器模式
因此,外部电路的设计的目的就是要能够达到能够对 BOOT1
和 BOOT0
引脚上高低电平改变的能力。
接下来,我们就以正点原子的原理图(探索者)为例,来看下 STM32
外部的电路到底是如何设计的,以及Flymcu
到底是怎样控制 BOOT1
和 BOOT0
引脚上高低电平改变从而达到具有使得 STM32
从系统存储器启动的的神奇能力。
如上图所示,这就是正点原子探索者一键下载电路。
一键下载电路涉及到模电知识,下面这篇文章写的还不错,并且还描述了CH340G芯片引脚的作用和功能。
stm32一键下载电路(下一篇文章)
从上图我们可以知道,Flymcu 肯定是通过 usb 线将数据或指令写入 CH340G
内(CH340 D
+ CH340 D-
)然后CH340G
根据来自 usb 的指令进行相应的工作。
CH340G在此电路中的工作就两个:
- 与STM32进行数据交互
- 控制BOOT0和RESET高低电平的变化
特别注意:正点原子探索者BOOT0和BOOT1引脚默认都是接地
BOOT0、BOOT1是通过跳线帽和地进行连接的
现在我们知道了,控制 BOOT1
和 BOOT0
引脚上高低电平改变是 CH340G
的作用,而 CH340G
是严格按照来自Flymcu 的指令进行的,所以,控制 BOOT1
和 BOOT0
引脚上高低电平改变的幕后黑手就是 Flymcu
。
注意:向 FLASH 中烧写程序不仅仅只有串口,由于硬件平台的限制,因此分析串口下载。
问题来了,那 Flymcu 到底干了什么,它是如何将用户程序烧写的 STM32 内部指定地址处?
以跑马灯为例,看下Flymcu烧写程序过程中输出的信息。
DTR
电平置低:复位RTS
电平置高:进入 Bootloader- 延时
100ms
:有谁能够告诉我为什么 DTR
电平置高:释放复位RTS
维持高 :此时开始运行Bootloader
- 开始连接 :
Flymcu
要与STM32
连接肯定是要发送特定的指令,并且当STM32
接收到预先约定好的指令时,也会发送特定的回复。(和接头的性质差不多)
注意:STM32
从 Bootloader
启动到能够与外部设备进行数据交互需要一定的时间,因此连接需要一定的时间
- 读出关于芯片相关的数据
- 读出选项字节
- 进行全片擦除,去除写保护,再次重启
Bootloader
(有大佬能告诉我为什么) - 编写程序,从
0x0800 0000
处开始运行
上述的过程中,大家比较疑惑的地方就是,Flymcu
发送给 STM32
的指令到底是什么,这个指令肯定是事先就确定好的,在这个文档中提及到了。
stm32 microcontroller system memory boot mode
这个手册中的内容大家可以自己详细地去看下,内容不多,我就粗略地说一下必要的知识点。
硬件连接需要(文档中的第35页)
通过串口与外部设备进行数据交互时STM32外部电路设计。
通过 DFU
与外部设备进行数据交互时 STM32
外部电路设计
还有其他的连接方式,我想表述的意思就是:
Bootloader
与外部设备进行数据交互的方式有很多种,不仅仅只有串口,只是由于硬件平台有限(正点原子只有通过串口下载的接口(调试接口除外))而根据不同的交互方式,STM32
外部的电路设计又大不相同。
Bootloader启动流程
从启动流程中我们就可以得到Flymcu发送给STM32的特定连接指令为:0x7F。
STM32F40xxx/41xxx devices bootloader version
通过版本信息中红色画线部分可以得知,当 Bootloader
接收到相应的命令之后,就会连续发送两个 response
。我们这个时候再看下 Flymcu 中的输出信息,
通过红色画线部分可以看出,Flymcu
接收到两个来自 Bootloader
的信息。
此时,接头成功!!!!!
接头成功后肯定就可以进行数据交互了!!!
因此,我们最初的猜想是正确的:即 STM32 通过某种外设将可执行文件烧写至 FLASH 上(也可以是 SRAM)
验证猜想-3
博客当中已经多次提及到,STM32
不仅可以从 FLASH
上启动,还可以从 SRAM
上启动。并且在STM32启动配置中有一个小提示:从 SRAM
中启动,需要重新设置中断向量表。
中断向量表的设置是用户在用户程序中自己实现的!!!
要验证这个猜想,可以从 SRAM
中启动,但是不设置中断向量表,看一下会出现什么情况。
由于正点原子的电路设计(因为我使用的就是正点原子的探索者开发板),使得无法通过串口进行 SRAM
启动,只能通过调试接口下载程序。
注意:SRAM是掉电数据就会丢失的存储器介质,因此使用时(前提是已经掉电)要重新下载程序从
SRAM
中启动的最主要的目的是用来调试程序,产品中的用户程序肯定都是存储在 FLASH 上的,不然每次掉电后用户程序都没了!!!
如何通过调试接口将用户程序下载到 SRAM
处,可以参考一下下面两篇博文:
- STM32 内部 SRAM 调试程序
- 在 SRAM 中调试代码
假设你现在已经实现了能够通过调试接口将用户程序下载到 SRAM
处,那么接下来,我们来验证一下。如果没有重新设置中断向量表会出现什么结果。
得出结论,总结归纳
对于最开始提出的三个猜想,现在可以得出结论:
- 指令存储在掉电不丢失的存储介质上
- STM32 通过某种外设将可执行文件烧写至掉电不丢失的存储介质上
- 中断向量表的首地址就是程序的入口地址
注意: 通过串口下载程序,实际上是 Flymcu(上位机)与 STM32 内置的 Bootloader 进行数据交互,但两者直接需要特定的硬件环境(CH340G(USB转串口芯片))
注意: 内存图中的 reserved
有些是不使用,有些是不能用(有其他重要的作用:可读写忽略),不可以修改其值
看完这篇博客,你的脑海里必须得有一个流程的框架:
- 用户面向单片机编写用户代码(C,C++,ASM)
- 用户代码通过交叉编译工具链生成单片机可以执行的可执行文件(HEX,BIN,AXF)
- 上位机:各种烧写工具(不局限于
Flymcu
(因为Bootloader
与外部设备进行数据交互不仅仅只是通过USART)))与单片机内部的Bootloader
进行数据交互(目的是将可执行文件下载到指定的存储地址处)
将可执行文件下载到指定存储地址处,然后还会继续等待上位机的 command
,可以通过复位或上位机发送跳转到用户代码入口地址的命令执行用户程序。
这个流程的框架总结一句话就是:
可执行程序 -> cpu执行第一条用户代码的流程
至于 cpu
执行第一条用户代码之后的流程后面的博客会详细说明,但毋庸置疑的是,这是一个重要转折点,在这个点之后执行的是你自己编写的代码,你比较熟悉这个过程,但是在这个点之前,对大部分人来说都是都是比较陌生的,但是但你对这个过程了解之后,会对你的知识体系有非常大的提升。
希望这篇博客对你有所帮助。