接触STM32已经有数天了。现在把对STM32的一些认识分享出来。希望给像我一样的新手一些启发。同时希望高手给看看有无理解不到位或错误的地方,还望指正~~~
STM32内存布局:
程序代码可以烧录在内存的SRAM或main Flash中。其中System memory区存放ST固化的bootloader程序,实现ICP下载,用户不能修改或删除。
程序代码在内存中的布局:
程序代码被编译连接后,其在内存中的组织结构如下图(假设将代码烧录在0x0800 0000处)。在IDE中可以配置程序代码的下载位置(必须位于flash或SRAM中)。
图中给出“程序代码的各个区域”在内存中相对位置的一种示意(实际情况可能并非如此,对于stm32来说中断向量表肯定在程序代码的最开头处存放)。程序代码中各个区域的划分以及在内存中的相对位置由编译器(有专门的内存映射[机制]文件,此文件可能开源也可能非开源)决定。一般情况开发人员应该可以人为的干预。
程序代码使用RAM布局:
当一个程序代码通过编译连接后,其全局、静态变量、堆栈的位置已经赋予了具体的内存地址了。因此其RAM布局就是定死了的。
程序代码的启动过程解析:
根据BOOT引脚的配置,芯片上电后由硬件完成栈顶SP和PC的定位(注1)。PC定位在复位中断函数Reset_Handler(void)的入口地址去执行此中断函数。此函数会调用SystemInit()和__main()两个函数。而__main()会调用开发人员编写的main()函数而main()函数调用子函数实现项目需求。
/*****************
注1:通过配置BOOT[0,1]引脚,来选择程序的启动位置(即根据BOOT引脚的设置,硬件自动将PC指向固定的位置处)。
l 当选择从main Flash memory或system memory启动时,硬件使SP默认从0x0000 0000处取栈顶地址,PC默认从0x0000 0004处取复位中断函数的入口地址。
l 当选择从SRAM启动时,SP默认从0x8000 0000处取栈顶地址,硬件使PC默认从0x8000 0004处取复位中断函数的入口地址
注2:Reset_Handler(void)的函数体在启动文件startup_stm32f10x_xx.s中。
注3:SystemInit()在system_stm32f10x.h中。SystemInit()中含有对中断向量表重定位的语句:
/****************
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Relocation in Internal FLASH. */
******************/
注4:__main()是非开源函数,其总要功能是完成SRAM中变量的初始化工作并调用main()。
*******************/
在调试或软件仿真的时候,可以通过IDE来设置启动的位置(见下图),因此可以随意将程序烧录在main Flash或SRAM的任意位置(由IDE给出PC的定位)。但是将程序“单独”(不与IDE相连)在芯片上运行时,却只能将代码烧录在0x0800 0000或0x2000 0000处,因为PC的定位由硬件根据BOOT引脚确定。
中断向量表偏移量的分析:
由于Cortex-M3芯片固定中断向量表的位置(0x0000 0004),而不固定程序的位置。也就是说程序代码可以烧录在不同的内存中,但芯片发生中断时认为总断向量表默认固定在了0x0000 0004的地方,因此发生中断响应时将从0x0000 0004处取寻找对应中断函数的入口地址。
但程序代码中的中断向量表可不一定位于0x0000 0004处,因此需要重映射中断向量表。重映射是将默认的中断向量表的地址从新映射到偏移量所对应的地址处(并非是“将偏移量处的程度代码的中断向量表内容映射到0x0000 0004处的默认中断向量表中”。当然两种理解方法都讲得通,不过实际情况是第一种。可通过IDE进行验证)。一般在SystemInit()或main()中设置中断向量表的偏移量。
当配置为“从main Flash memory或system memory启动”时,存在一种映射机制,将main Flash memory或system memory区整体映射到boot memroy区。这样一来,在0x0000 0000处正好存储的就是程序代码的中断向量表,因此无需重映射中断向量表(默认偏移量为0)。而“从SRAM启动”时不存在上述的映射机制,因此中断向量表就需要重映射。
通过“注1”可以知道,必须将程序代码烧录在0x0800 0000或0x2000 0000处才能保证由硬件完成的“PC定位”能准确的找到程序代码的中断向量表的复位中断函数Reset_Handler(void)的入口地址。
从芯片上电开始,截止到配置偏移量之前,是不会发生中断请求的(包括系统异常和IQR)。在SystemInit()中含有禁止所有中断的设置。由于中断请求时,硬件自动到0x0000 0004处的默认中断向量表取相应中断函数的入口地址,因此在main()中使能某个中断之前,必须配置好偏移量,这样才能保证0x0000 0004处的默认中断向量表的内容与程序代码中的中断向量表一致。
芯片运行过程中发生的复位中断请求(同其他中断请求)通过查找0x0000 0004处的中断向量表中Reset_Handler(void)的入口地址来执行此函数。但芯片从没供电到供电时,不会引起复位中断请求,而是由硬件锁定PC执行位置直接执行Reset_Handler(void),因为此情况下,0x0000 0004处的默认中断向量表的内容是空的。
IAP分析:
IAP的bootlader代码必须放在0x0800 0000或0x2000 0000处。而APP代码可以放在main Flash或SRAM的任意位置,原因就是通过bootloder代码来定位APP的复位中断函数的入口地址(仿照“硬件根据BOOT引脚定位PC”的过程)。当然在APP中也可以定位bootloader的复位中断函数的入口地址,实现bootloader和APP的相互跳转。
芯片上电执行bootloader代码:
l 当需要更新APP时,与PC通信将APP新代码先安置在SRAM中。接受完后,判断是APPinFlash还是APPinARAM,如果是APPinFlash就进行Flash操作将APP新代码放在Flash指定位置,最后跳转到APPinFlash执行;如果是APPinARAM,直接跳转到APPinARAM执行。
l 当不需要更新APP时,直接跳转到APP代码或显示没有可执行的APP代码。
设计方案:假设使用的芯片是STM32f103ZE。其具有512kB Flash(0x0800 0000~0x0808 0000)和64kB SRAM(0x2000 0000~0x2001 0000)。
Bootloader
放在0x0800 0000处,并限制其最大代码量为64KB。使用的RAM不能超过4KB。
ROM: 0x0800 0000~0x0801 0000 (64KB)
RAM: 0x2000 0000~0x2000 1000 (4KB)
APPinFlash
Bootloader接受APPinFlash代码时首先将其放在SRAM的0x2000 1000处,其最大代码量为0x2000 1000~0x2001 0000(60KB);然后通过Flas操作将其转移到0x0800 1000之后的Flash区域。最后跳转到APPinFlash的reset_handle(),~~~~~。
APPinFlash可以使用的RAM区域为整个SRAM区域。
APPinSRAM
Bootloader接受APPinSRAM代码时将其放在SRAM的0x2000 1000处,其最大代码量为0x2000 1000~0x2001 0000(60KB);然后跳转到APPinSRAM的reset_handle(),~~~~~。
APPinSRAM可以使用的RAM区域为除去APPinSRAM占领的其他SRAM区域。
比如APPinSRAM占领0x2000 1000~0x2000 5000(20KB),那APPinSRAM可以使用的RAM区域为0x2000 0000~0x2000 0FFF(4KB)和0x2000 5000~0x2001 0000(40KB)。
程序代码的ROM和RAM区域可以通过IDE进行设置,不过最多可以将其拆分为2块。
这里需要指出的是,为什么APPinFlash或APPinSRAM的RAM区域可以占用bootloader的RAM区域呢?
执行bootloader代码过程是:reset_handle()------systemini()------__main()------main().在__main()中就会对其通过IDE设定的RAM中初始化全局变量和静态变量,堆栈指针的地址也是早就定死了的。当跳转到APP程序的时候,还是按着如上的流程进行。
|