STM32学习笔记1——启动

本文最后更新于:2024年10月15日 晚上

0x00 Reference

Cortex-M3/M4芯片启动流程概括_cortex-m4 mcu的启动与初始化过程-CSDN博客

01.一探究竟STM32的三种启动模式_stm32启动模式-CSDN博客

0x01 加载中断向量表

stm32基于ARM cortex-m3 内核设计,所以先来看cm3内核的启动流程

cm3内核在上电 or 复位之后会先做两件事

  1. 去0x00000000的地址读取MSP寄存器的初始值(MSP就是栈顶指针,这里就是将栈的初始地址赋给MSP,让内核找到栈的位置,在之后的启动流程中可能遇到)
  2. 去0x00000004的地址读取PC寄存器的初始值——Reset中断处理函数的地址(PC寄存器就是指向当前程序的地址,CPU就是不停地从PC中取址然后执行)

在PC获取到初始值之后,CPU就会跳转到PC所指的Reset中断服务函数开始执行程序

OK,这里有一个问题为什么0x00000000的地址一定是MSP的初始值,0x00000004的地址一定是PC寄存器的初始值呢?这是因为CM3内核规定了中断向量表的结构,并且规定了向量表存放的地址一定是0x00000000。可以看到向量表的前两位分别是MSP的初始值和复位中断处理函数的地址

img

另外一个问题是,为什么在STM32上刷写程序是写入到0x08000000,而不是0x00000000呢?这是因为ST公司在设置STM32时为了灵活性,设计了一个硬件电路,这个电路会根据BOOT0和BOOT1的选择不同而将不同的地址映射到0x00000000

打个比方,当boot0:boot1 = 0:0 时,CPU上电去0x00000000的地址读取中断向量表,而该地址已经被硬件电路重新映射到了0x08000000了,对于CPU来说,它认为它读取的地址是0x00000000,而它实际读取的内容是来自0x08000000的flash中的数据

img

STM32 的三种启动方式

  • 第一种方式(boot0 = 0):Flash memory启动方式
    启动地址:0x08000000 是STM32内置的Flash,一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。基本上都是采用这种模式。
  • 第二种方式(boot0 = 1;boot1 = 0):System memory启动方式
    启动地址:0x1FFF0000从系统存储器启动,这种模式启动的程序功能是由厂家设置的。一般来说,这种启动方式用的比较少。系统存储器是芯片内部一块特定的区域,STM32在出厂时,由ST在这个区域内部预置了一段BootLoader, 也就是我们常说的ISP程序, 这是一块ROM,出厂后无法修改。一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的BootLoader 中,提供了串口下载程序的固件,可以通过这个BootLoader将程序下载到系统的Flash中。
  • 第三种方式(boot0 = 1;boot1 = 1):SRAM启动方式。
    启动地址:0x20000000 内置SRAM,既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。假如我只修改了代码中一个小小的 地方,然后就需要重新擦除整个Flash,比较的费时,可以考虑从这个模式启动代码(也就是STM32的内存中),用于快速的程序调试,等程序调试完成后,在将程序下载到SRAM中。

img

0x02 Reset 中断服务函数

前面说到cm3复位后会跳转到Reset中断服务函数,那这个函数在哪呢?

在写stm32的程序时,会有一个startup_xxxx.s的启动文件,使用stm32cubeMX会自动根据编译器的不同生成不同的启动文件。

为什么不同编译器的启动文件不同呢?

首先,启动文件使用的是汇编语言,虽然针对同一个指令集,理论上来说其汇编语言也是相同的,但是由于其汇编器的不同,因此在汇编语言的细节上也有差异,针对GNU的编译器的启动文件在ARMCC编译器可能就编译不了了,所以针对不同的编译器所使用的启动文件也就不同了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* @brief This is the code that gets called when the processor first
* starts execution following a reset event. Only the absolutely
* necessary set is performed, after which the application
* supplied main() routine is called.
* @param None
* @retval : None
*/

.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:

/* Call the clock system initialization function.*/
bl SystemInit

/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit

CopyDataInit:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4

LoopCopyDataInit:
adds r4, r0, r3
cmp r4, r1
bcc CopyDataInit

/* Zero fill the bss segment. */
ldr r2, =_sbss
ldr r4, =_ebss
movs r3, #0
b LoopFillZerobss

FillZerobss:
str r3, [r2]
adds r2, r2, #4

LoopFillZerobss:
cmp r2, r4
bcc FillZerobss

/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main
bx lr
.size Reset_Handler, .-Reset_Handler

这是一个使用GCC编译器的Reset_Handler,由STM32cubeMX自动生成,主要做了如下的事情

  • 调用SystemInit来初始化系统。
  • 将初始化数据从Flash复制到RAM。
  • 清零BSS段(未初始化数据段)。
  • 调用__libc_init_array来执行C库的构造函数。
  • 调用main函数,这是C程序开始执行的地方。

这里有两个值得注意的地方,首先是SystemInit,如果定义了DATA_IN_ExtSRAM,则会初始化外部SRAM,启动FSMC控制器;而如果定义了USER_VECT_TAB_ADDRESS,则会重定向 向量表

1
2
3
4
5
6
7
8
9
10
11
12
13
void SystemInit (void)
{
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif

/* Configure the Vector Table location -------------------------------------*/
#if defined(USER_VECT_TAB_ADDRESS)
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#endif /* USER_VECT_TAB_ADDRESS */
}

另外一个值得注意的是,使用GNU工具链,需要调用__libc_init_array来初始化C库(newlib),而使用ARMCC编译器生成的启动文件中却没有这一步。

在执行完Reset_Handler后,程序就会跳转到main函数开始执行用户程序了


STM32学习笔记1——启动
https://goooforward.github.io/blog/2024/09/24/study/STM32学习笔记1——启动/
作者
tangyuwei
发布于
2024年9月24日
许可协议