最近发现对基础不太熟悉,写篇笔记记录一下MCU启动到用户C语言运行,之前做了那些工作,同时flash和Ram又分别保存了那个数据,每一段又是什么意义,方便后续自己忘记了,查阅。
一、 MCU启动
在MCU上电/复位之后到程序开始运行前,Cortex - M处理器会从存储器中读取出两个字(注意在32位系统中一个字位32bit),即读取地址0x00000000和0x00000004的数据,这两个地址存放的值分别为MSP(Main stack pointer)主栈指针的初始值,以及代表复位处理处理起始地址的复位向量。处理器读出这两个字用于初始化MSP主栈指针,和PC(Program Counter)程序计数器。
Cortex - M3和M4的寄存器组如下图所示。(图片参考了《ARM Cortex - M3和Cortex - M4权威指南》第三版)
需要注意的是,主流MCU的启动方式一般有3种,GD32F4为例,通过配置外部引进BOOT0、BOOT1的电平状态选择启引导方式。区别就是将0x00000000映射到不同的地址,如果选择FLASH启动,处理器对0x00000000的读取其实就是对0x08000000地址的读取。
下图为GD32F4xx的启动引导配置说明(截图来源《GD32F4xx_User_Manual_Rev2.8_CN》)
二、启动文件 -> 用户主函数执行
从上电开始,这期间MCU在跑的程序,其实是启动文件.s编写的程序内容。在MCU工程中,会有一个.s的启动文件,一般这个启动文件由器件厂商提供。
启动文件的功能主要为:
1–初始化栈指针MSP=_initial_sp。
简单来讲就是在程序第一个字放置了堆栈指针MSP的值。
2–初始化复位程序计数寄存器值=Reset_Handler(弱函数)。
简单来讲就是在程序第二个字放置了Reset_Handler复位中断的函数入口地址
3–初始化异常/ 中断向量表。
简单来讲就是在第3字后,放各个系统各种类型中断的函数入口地址(弱函数,如果用户未定义默认指向Default_Handler ),这些各种中断的入口地址,我们一般称之为中断向量表。
4–系统时钟配置。
这个是在Reset_Handler函数里面,里面会调用一个函数,有些启动文件没有默认定义这个函数,导致有些工程的SystemInit函数必须由用户定义。
5– C库函数_main初始化用户堆栈的调用。
_main是进入main函数之前的初始化函数,可以理解为,进入main之前先调用了__main()函数去初始化一些内容,然后再到我们可以在C环境中编写的程序。
具体启动文件内容详解,可以去看其他博主的介绍,这里列出链接:
STM32启动文件讲解_XYJ_Tiger的博客-CSDN博客
STM32F10x启动文件详解_stm32f10x_md_超级网吧的博客-CSDN博客
3-STM32启动文件详解_南山府嵌入式的博客-CSDN博客
需要注意的是上面介绍的是.s文件的功能,实际程序跑起来后运行顺序如下:
1.赋值MSP。
2.进入Reset_Handler去处理
Reset_Handler中会进入SystemInit 和 __main函数,然后就进入C语言运行环境。
注意:如果Reset_Handler处理期间有中断的话,就会跳去中断向量表然后找到中断处理函数地址,再跳过去处理中断。这也是Cortex - M3/M4内核先赋值MSP的原因所在。
那问题来了,__main的函数源码在哪呢?参考其他博主的博客。
MDK __main()代码执行分析_keil _maim_TS_up的博客-CSDN博客
三、 MCU内部Flash、RAM中的数据
我们写个程序,用编译后最终会生成一个可以烧写入芯片flash的可执行文件,常见的两种,一种是.bin,一种是.hex文件。
STM32的内存又有了6个储存数据段和3种储存属性区的概念
参考下文链接:
两种存储器,三种内存大小,六段段
MDK编译出来后,会分为4个中类型
Code、RO Data、RW Data、ZI Data
Code :代表执行的代码,程序中所有的函数,存在flash中;
RO-data :代表只读数据,程序中所定义的全局常量数据(局部常量放在栈区),存在flash中;
RW-data:代表已初始化的读写数据,程序中定义并且初始化的全局变量和静态变量,存在flash中,程序运行后(具体由__main来实现,会将在Flash区域中的RW-data拷贝到RAM中);
ZI-data:zero initial,就是程序中用到的变量并且被系统初始化为0的变量的字节数 (在ram中)
其实我主要关心的是,程序编译出来后,数据是怎么放置的,程序运行之后数据又是怎么放置的。
如图,编译出来的数据会放到Flash中,运行的时候,由用户的配置,启动文件会设置堆栈指针,__main函数会将RW-data搬运到RAM中。Heap根据程序的运行申请的内存会增长,Stack向下增长,,当两者指针交叉时,系统出栈回的数据就是混乱的,就会死机。
事实上,在IAR或者MDK编译器中,可以特别声明某段代码,将他们整个拷贝到RAM中,在RAM里面运行,因为RAM的读写速度快很多,以此加快该断代码的运行速度。