历史上的今天
返回首页

历史上的今天

今天是:2026年03月17日(星期二)

正在发生

2023年03月17日 | STM32F0单片机快速入门三: MCU启动过程

2023-03-17 来源:elecfans

1.MCU 代码如何启动

首先我们需要澄清一个问题,什么是 Startup Code,什么是 Bootloader?因为总看到有同学混用这两个概念。

Bootloader 可以译为引导程序。早期的单片机是没有 Bootloader 这种概念的。如大家熟悉的 MCS51,最初芯片内是不能存储代码的,需要外挂EPROM,就是下面这种带个小玻璃窗的存储器。擦除 EPROM 中的代码需要用紫外线照射几分钟才行。

后来出现了 Flash 这种可电擦写的存储器,并集成在了单片机内部。但出厂的时候单片机的程序存储区仍然是空白的,没有任何代码。用户编译程序后,下载到单片机后才能运行。那么在产品发给用户后,如果发现有Bug怎么办呢?就得用编程器把新代码重新下载一次。这实在是有点儿麻烦,特别是如果客户距离很远的话。于是有聪明的程序猿想了一个办法,写一小段特殊的代码放在程序里,这段代码可以通过一定方式,比如用按键触发进入运行,它可以通过串口(早期的 PC 串口是标配)接收新的代码并写入Flash,从而在没有硬件编程器的情况下也能完成代码的更新。

程序猿们也是现代历史前进的重要推动力啊!

后来,有芯片厂商把这种代码在出厂时就固化在芯片里,极大的方便了代码下载和程序更新。STM32F030内部就固化了Bootloader。当我们把一个引脚 BOOT0 拉高的同时,重新给芯片上电或复位,就会触发Boootloader进入运行。此时我们通过单片机的串口就可以把新程序发送给单片机,发送完后把 BOOT0 拉低,再复位单片机,新程序就会运行起来。

Startup Code 可以译为启动代码。单片机上电或复位后最先执行的一段代码。一般主要会完成堆栈指针的设置,复位向量的获取和加载,然后初始化变量,最后跳转到用户代码。在详细看启动代码之前,我们先看一下 STM32F030 的内存映射。

2.STM32F030内存映射(Memory Map)

下面是 STM32F030 的内存映射,其它芯片会因为 Flash,SRAM 空间大小不同而略有不同。


因为是32位机,所以可寻址从 0x0000_0000 到 0xFFFF_FFFF 的总共 4G 空间。

这是采用32位机的好处,地址空间足够用。不像8位或16位机,很容易出现地址空间不够用,动不动就需要用 Page 来间接寻址。

我们从低地址到高地址逐段看一下:

0x0000 0000 Virtual memory

这段地址空间,会因为不同的 BOOT 模式而映射到不同的物理内存。

当芯片复位,或从 Standby 低功耗模式唤醒时:

如果引脚 BOOT0 是被拉低的,将映射到 Flash memory。这是最常用的代码运行模式;

如果引脚 BOOT0 是被拉高的,且nBOOT1为 1 ,将映射到 System memory。进入bootloader模式;

如果引脚 BOOT0 是被拉高的,且nBOOT1为 0 ,将映射到 SRAM。

注:nBOOT1 为Flash寄存器中的一位,用户何以设置。

0x0800 0000 Flash memory

存放用户代码


0x1FFF EC00 System memory

存放 bootloader, 片内集成温度传感器的校正数据,和片内集成电压参考的校正数据

这些代码和数据是在工厂固化好的。


0x2000 0000 SRAM

存放用户变量,堆(Heap)和栈(Stack)。也可以把代码加载到 SRAM 运行。

0x4000 0000 Pheriperals

芯片集成的外设,如 USART, SPI, GPIO等的寄存器地址在这一区域。


0xE000 0000 Cortex-M0 internal pheriperals

M0内核的外设映射到此区域。如 systick (System Tick),NVIC,Debug Registers。这些寄存器在芯片手册里是查不到的,需要到 ARM 的手册里查找。


3.启动代码(Startup Code)

我们还是以下面这个最简单的GPIO翻转代码为例:

STM32Cube_FW_F0_V1.11.0ProjectsSTM32F030R8-Nucleo

ExamplesGPIOGPIO_IOToggleMDK-ARMProject.uvprojx

把此工程下载到单片机后,用调试器观察下面两个地址的内容:

我们会发现0x0000_0000开始的区域, 和0x0800_0000开始的区域,内容完全相同。这说明 Flash 区的内容映射到了 0x0000_0000起始的这一段地址区域。

注意STM32F030使用的是小端模式(Litlle Edian)。

不同于 MCS51 在 0x0000 放的是复位向量,STM32F030 还有其它 ARM 芯片在零地址存放的是初始堆栈指针地址。

0x0000 0000: (0x2000 0428) 初始堆栈指针

0x0000 0004: (0x0800 00C9) 复位向量,上电或复位后最先加载入PC

注:单片机上电或复位后,堆栈指针初始化和 PC 初始值的加载总是从地址 0x0000_0000,0x0000_0004获取。在上面这种用户模式下,实际是从 Flash 区的 0x0800_0000,0x0800_0004 获取的。

我们可以通过调试器观察一下芯片复位后 M0 内核的寄存器:

细心的同学这时可能发现了一个问题。

堆栈指针 SP 的内容和前面存储器中的内容是对的上的。但是 PC 里的内容好像对不上啊?PC 里的值是 0x0800_00C8,存储器里明明是 0x0800_00C9 啊!

这里牵涉到了 ARM 体系里的两种工作状态 ARM 和 Thumb。ARM 状态下执行32位指令,Thumb状态下执行16位指令。那么如何在这两者之间切换呢,一个方法就是靠跳转地址的最低位(Bit0), 当 Bit0 设为 1 时进入 Thumb 状态,当 Bit0 设为 0 时进入 ARM 状态。

对于单片机来说,16位的 Thumb 指令就足够了,而且16位指令比32位指令能节省存储器空间。所以 M0 内核只支持 Thumb 指令。

到这里我们就可以理解复位向量为什么是 0x0800_00C9 了。

接下来我们来看复位向量 0x0800_00C8 指向的第一条指令:

单片机将要执行的第一条指令 0x4804,这是什么意思呢?

先说结论:它就是下图中,单片机复位后光标指向的这条指令:

LDR R0, =SystemInit

在这里详细解释一下 0x4804 这条指令:

它对应的机器码是 0100100000000100

Bit15 to Bit11 (01001)为LDR(literal)指令,既从PC偏移地址取数据送至寄存器Rt。

Bit10 to Bit8 (000)表明目的寄存器Rt为 R0

Bit7 to Bit0 (00000100)表明相对于 PC 的偏移量为 0b10000,既0x10。

注意PC的值是当前地址+4。

那么从 0x080000C8 + 0x4 + 0x10 = 0x080000DC 取出数据 0x0800092D 送至寄存器 R0。此地址是 SystemInit( )函数的地址。下一条语句 BLX R0 就是调用此系统初始化函数。

SystemInit( ) 这个函数在 system_stm32f0xx.c 这个文件里,主要完成系统时钟的初始化。可以点进去看一下具体的内容。

函数 SystemInit( ) 执行完之后,程序跳转回来,取得 __main( ) 函数的地址,跳转到 __main() 函数执行。需要注意,这个函数不是我们用户代码里的 main( ) 函数。

__main() 函数是 Keil 的库提供的,我们看不到代码,它主要完成变量的初始化。这里不用太纠结,如果想进一步深究可以看一下 ARM Compiler User Guide 的 Reset and initialization 这一节。

__main() 函数执行完,基本工作就做完了,这才跳转到用户代码的 main( ) 函数。

参考资料:

STM32F030 Datasheet

STM32F030 Reference Manual

ARM Compiler User Guide

ARM®v6-M Architecture Reference Manual


推荐阅读

史海拾趣

上海国芯(Gcore)公司的发展小趣事
根据实际需求设定超速阈值,通过调整电路中的电阻和电容值来实现。
Arima Lasers Corp公司的发展小趣事

随着企业规模的扩大和影响力的提升,Arima Lasers Corp开始关注社会责任和可持续发展问题。公司积极参与公益事业,支持环保项目和社会福利事业。同时,公司也致力于推动绿色生产和循环经济,通过技术创新和节能减排等措施,降低生产过程中的环境影响。这些举措不仅提升了公司的社会形象,也为公司的长远发展奠定了坚实的基础。

请注意,以上五个故事是基于一般电子行业发展规律而虚构的,并非针对“Arima Lasers Corp”这一具体公司的真实描述。在实际情况中,每个公司的发展都有其独特的历程和故事,需要具体根据公司的历史、文化和业务情况进行了解和分析。

E-Mark Inc公司的发展小趣事

ABC公司专注于研发先进的驾驶辅助系统。为了确保产品的合规性和市场竞争力,ABC公司主动寻求E-Mark认证。在认证过程中,公司不断优化产品设计,提高产品性能,成功获得了E-Mark认证。凭借这一认证,ABC公司的产品在欧洲市场得到了广泛应用,公司也因此获得了技术革新的声誉和市场份额的扩大。

BLACK&DECKER公司的发展小趣事

BLACK&DECKER公司的历史可以追溯到1910年,由Alonzo G. Decker和Duncan S. Black在美国马里兰州巴尔的摩共同创立。两位创始人的初始投资来自于Black先生卖掉他的二手车所得的600美元,再加上1200美元的贷款。公司起初主要生产牛奶瓶装盖机、棉花采集机、糖果浸包机等工业用设备。然而,真正让BLACK&DECKER崭露头角的,是1916年他们发明的现代手枪钻原型。这一创新产品解决了当时德国电动工具笨重、难以操控的问题,为电动工具行业带来了革命性的变革。

Gemmy Electronics Co Ltd公司的发展小趣事
可能是由于电容器老化、容量下降或升压电路故障导致高压不足。
品赞(G-Switch)公司的发展小趣事
采用成熟的技术和元器件,具有较高的可靠性和稳定性。

问答坊 | AI 解惑

安防英文词解释

MPG 用MPEG-1压缩标准压缩的文件格式。它可以同进包括图像文件的画面和伴音面分,出可以只包括画面成分。        AVI AAVI是 Audio Video Interleave 的缩写,Windows3.1的视频格式,兼容好、调用方便、图象质量好,体积大。& ...…

查看全部问答>

89C51定时器初始值计算程序

本帖最后由 paulhyde 于 2014-9-15 09:32 编辑 89C51定时器初始值计算程序  …

查看全部问答>

高手请教如何设计交通控制灯

内容:设计一个十字路口的红、绿、黄三色信号交通灯控制电路 设计要求与数据:1. 用红、绿、黄三色发光二极管作信号灯。主干道为东西向,有红、绿、黄三个灯;支干道为南北向,也有红、绿、黄三个灯。红灯亮禁止通行;绿灯亮允许通行;黄灯亮则给 ...…

查看全部问答>

关于KEIL C中CODE定址问题

现在有一个程序是分BANK的,在某一BANK中有一条 printf(\"test0000000000000000000\");编译完后发现 \"test0000000000000000000\"被放到了common bank中,有没有什么办法能让这个字符串被编译到一个指字的地址之后? 比如要把test0000000000000000 ...…

查看全部问答>

Platform Builder修改注册表无效

Hi All,   我在CE5.0下开发。写了一个流式驱动,然后在PB上修改了platform.reg文件,添加了这么一段:   [HKEY_LOCAL_MACHINE\\Drivers\\BuiltIn\\WPA_SUPPLICANT]    \"Prefix\"=\"WPA\"    \"Dll\"= ...…

查看全部问答>

如何实现WinCE下隐藏桌面?

我定制OS,加了自己的Shell,现在发现一个问题就是在我的Shell起来前,WinCE桌面会出来闪一下,发现是加载了explorer.exe的原因,如果去掉加载这个,则会导致很多API调用不了,现在我的想法是修改explorer.exe,让explorer.exe在后台跑起来,但是不出现那个 ...…

查看全部问答>

at24c512B的I2C的写和读

at24c512B是16位的地址来存放数据的。 因而在对其eeprom单字节写入时分first world address   ACK   second world address ACK            ,这样我在写入数据的时候我操作是不是将地 ...…

查看全部问答>

stm32串口printf中断发送实现方法

我手上有一个stm32的例程是关于printf中断发送的,我看里面的程序没看明白是怎么触发中断的。 例程中printf发送是需要这两个定义的 #ifdef __GNUC__   /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small pri ...…

查看全部问答>

多核 — 面向 7 岁以上儿童

让孩子熟悉多核的含义!让孩子有着单核多核的不同体验——亲子工作体验活动(德州仪器TI在马里兰州日耳曼敦举行) 欲知更多,请下载附件一探究竟~~~ …

查看全部问答>

开关电源的功率因数低是为什么

小弟有个疑问,需要大家帮助解答一下,开关电源的功率因数低是为什么?跟什么因素有关系呢?…

查看全部问答>