历史上的今天
今天是: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
史海拾趣
|
内容:设计一个十字路口的红、绿、黄三色信号交通灯控制电路 设计要求与数据:1. 用红、绿、黄三色发光二极管作信号灯。主干道为东西向,有红、绿、黄三个灯;支干道为南北向,也有红、绿、黄三个灯。红灯亮禁止通行;绿灯亮允许通行;黄灯亮则给 ...… 查看全部问答> |
|
现在有一个程序是分BANK的,在某一BANK中有一条 printf(\"test0000000000000000000\");编译完后发现 \"test0000000000000000000\"被放到了common bank中,有没有什么办法能让这个字符串被编译到一个指字的地址之后? 比如要把test0000000000000000 ...… 查看全部问答> |
|
Hi All, 我在CE5.0下开发。写了一个流式驱动,然后在PB上修改了platform.reg文件,添加了这么一段: [HKEY_LOCAL_MACHINE\\Drivers\\BuiltIn\\WPA_SUPPLICANT] \"Prefix\"=\"WPA\" \"Dll\"= ...… 查看全部问答> |
|
我定制OS,加了自己的Shell,现在发现一个问题就是在我的Shell起来前,WinCE桌面会出来闪一下,发现是加载了explorer.exe的原因,如果去掉加载这个,则会导致很多API调用不了,现在我的想法是修改explorer.exe,让explorer.exe在后台跑起来,但是不出现那个 ...… 查看全部问答> |
|
at24c512B是16位的地址来存放数据的。 因而在对其eeprom单字节写入时分first world address ACK second world address ACK ,这样我在写入数据的时候我操作是不是将地 ...… 查看全部问答> |
|
我手上有一个stm32的例程是关于printf中断发送的,我看里面的程序没看明白是怎么触发中断的。 例程中printf发送是需要这两个定义的 #ifdef __GNUC__ /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small pri ...… 查看全部问答> |




