学习stm32有2年的时间了,但是也只是有些基础的了解。为了深入的学习stm32应用,重新拿起原子写的《stm32开发指南》来深入的学习,然后写下自己的理解。文中有可能会搬一些原子大神的东西。
首先理解IAP的原理
应用编程IAP(In-Application-Programming)是应用在Flash程序存储器的一种编程模式,它可以在应用程序正常运行的情况下,通过调用特定的IAP程序对另外一段程序Flash空间进行读/写操作,甚至可以控制对某段、某页甚至某个字节的读/写操作。主要用于数据存储和固件升级。对于IAP应用,通常会有两个程序,第一个程序Bootloader程序不执行正常功能,只是通过某种方式(串口,usb,SD卡)接收第二个程序,并进行更新。第二个程序APP程序是执行的主体,用于实现应用功能。
对于stm32闪存模块,主要由主存储器、信息块和闪存存储器接口寄存器三部分构成。
主存储器,该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每页 2K 字节。注意, 小容量和中容量产品则每页只有 1K 字节。看出主存储器的起始地址就是 0X08000000, B0、 B1 都接GND 的时候,就是从 0X08000000开始运行代码的。
信息块,该部分分为 2 个小部分,其中启动程序代码,是用来存储 ST 自带的启动程序,用于串口下载代码,当 B0 接 V3.3, B1 接 GND的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能,本章不作介绍。
闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制机构。
对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理;编程与擦除的高电压由内部产生。
对于flash的读写的流程
这里要特别留意一个闪存等待时间,因为 CPU 运行速度比 FLASH 快得多, STM32F103的 FLASH 最快访问速度≤24Mhz,如果 CPU 频率超过这个速度,那么必须加入等待时间,比如我们一般使用72Mhz的主频,那么FLASH等待周期就必须设置为 2,该设置通过 FLASH_ACR寄存器设置。
STM32 的闪存编程是由 FPEC(闪存编程和擦除控制器)模块处理的,这个模块包含 7 个32 位寄存器,他们分别是:
FPEC键寄存器FLASH_KEYR
选择字节键寄存器(FLASH_OPTKEYR)
闪存控制寄存器(FLASH_CR)
闪存状态寄存器(FLASH_SR)
闪存地址寄存器(FLASH_AR)
选择字节寄存器(FLASH_OBR)
写保护寄存器(FLASH_WRPR)
其中 FPEC 键寄存器总共有 3 个键值:
RDPRT 键=0X000000A5
KEY1=0X45670123
KEY2=0XCDEF89AB
STM32 复位后, FPEC 模块是被保护的,不能写入 FLASH_CR 寄存器;通过写入特定的序列到 FLASH_KEYR 寄存器可以打开 FPEC 模块(即写入 KEY1 和 KEY2),只有在写保护被解除后,我们才能操作相关寄存器。
STM32 闪存的编程每次必须写入 16 位(不能单纯的写入 8 位数据哦!),当 FLASH_CR 寄存器的 PG 位为’ 1’时,在一个闪存地址写入一个半字将启动一次编程;写入任何非半字的数据, FPEC 都会产生总线错误。在编程过程中(BSY 位为’ 1’ ),任何读写闪存的操作都会使 CPU暂停,直到此次闪存编程结束。
同样, STM32 的 FLASH 在编程的时候,也必须要求其写入地址的 FLASH 是被擦除了的(也就是其值必须是 0XFFFF),否则无法写入,在 FLASH_SR 寄存器的 PGERR 位将得到一个警告。
编程流程
1、检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁,(向KEYR寄存器中写入特定序列KEY1和KEY2)
(实际中需要查看当前要写入的扇区是否有数据,如果有数据,则需要进行擦除操作,然后再进行下面的步骤)
2、检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的编程操作
3、设置 FLASH_CR 寄存器的 PG 位为’1’,用于表示接下来进行写操作
4、在指定的地址写入要编程的半字
5、等待 BSY 位变为’0’,表示编程完成
6、读出写入的地址并验证数据
我们在 STM32 的 FLASH 编程的时候,要先判断缩写地址是否被擦除了
读出被擦除的页并做验证
1、检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁(向KEYR寄存器中写入特定序列KEY1和KEY2)
2、检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的闪存操作
3、设置 FLASH_CR 寄存器的 PER 位为’1’,用于表示进行页擦除操作
4、用 FLASH_AR 寄存器选择要擦除的页
5、置 FLASH_CR 寄存器的 STRT 位为‘1’表示开始一次擦除操作
6、等待 BSY 位变为’ 0’
读出被擦除的页并做验证
我们来了解下stm32的程序运行流程,如下图所示
程序运行的地址从0x08000000(FLASH)开始运行
1程序开始运行后,从中断向量表中取出复位中断向量,并执行服务程序。
2执行完中断向量程序会跳转至主main函数入口执行,并在死循环中一直执行。
3当在主函数中发生中断时间时,系统强制PC指针指向对应中断向量表对应位置。
4PC指针在中断向量表处取出中断服务程序入口地址,并跳转至对应位置执行。
5中断服务程序执行完成后,PC指针跳回发生中断时系统在main函数中的位置,继续往下执行。
当加入IAP应用后stm32的程序流程变为下图所示
程序运行的地址从0x08000000(FLASH)开始运行
1程序开始运行后,从中断向量表中取出复位中断向量,并执行服务程序。执行完中断向量程序会跳转至Bootloader程序main函数入口执行。
2在main中系统检查是否需要对第二部分代码进行更新,如果需要则执行更新操作,如果不需要则跳过更新操作,跳转至APP程序的入口
3在APP程序入口首先进入重新映射的中断向量口,根据新的中断复位向量,执行复位中断程序,然后跳转至APP程序main入口。
4当在主函数中发生中断时间时,系统强制PC指针指向对应中断向量表对应位置(这里还是强制跳转到地址0x0800004中断向量表位置,而不是APP程序的中断向量表)。
5程序根据中断向量表的中偏移跳转到对饮给的中断源的新的中断服务程序中。
5中断服务程序执行完成后,PC指针跳回发生中断时系统在main函数中的位置,继续往下执行。
两个注意点:
1) 新程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始;
2) 必须将新程序的中断向量表相应的移动,移动的偏移量为 x;
对于程序的设置
IAP程序
通过串口设置程序的接收
根据要求设置存储APP程序的地址
APP程序
设置程序存储区,与数据存储区 在Target->Read/Only Memory Areas-> IROM与IRAM处设置,这里APP程序的存储首地址设为0x08010000,大小为0x70000(即前0x10000为存放IAP程序)
设置中断向量表偏移地址SCB->VTOR = FLASH_BASE | 0x10000;
生成BIN文件
关于向量表偏移的几个疑惑。
1关于SCB->VTOR中写入偏移地址的要求。
在<<权威指南>>中,有这么一段话:
NVIC 中有一个寄存器,称为“向量表偏移量寄存器”(在地址0xE000_ED08 处),通过修改它的值就能定位向量表。但必须注意的是:向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是2 的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有32 个中断,则共有32+16(系统异常)=48 个向量,向上增大到2 的整次幂后值为64,因此地址地址必须能被64*4=256 整除,从而合法的起始地址可以是:0x0, 0x100, 0x200 等。
向量表偏移量寄存器,也就是SCB->VTOR.它的第29位,用来标识向量表是在CODE区还是RAM区,从而0X1,就是最高3位不去动,低位,根据上面这段话的理解,STM32自己有60个中断,加上CM3的16个,总共有76个中断,扩大到2的整次幂,那就是128,然后再乘以4,得到512,也就是0X200.根据这样计算,合法的偏移地址应该是0X0,0X200,0X400,0X600... 所以对于STM32F103ZET6,重映射的中断向量表的地址应该为0x200的倍数。
在NVIC_SetVectorTable函数中,有SCB->VTOR = NVIC_VectTab | (Offset & (uint32_t)0x1FFFFF80);
其中的offset为要设置的偏移地址,而0x1FFFFF80为寄存器自己的特性(前三位与后七位无效),NVIC_VecTab为基地址,所以三者综合才是最终的重映射的向量表地址。
2、关于以下函数的解释
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)//检查栈顶地址是否合法.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4);//用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr);//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app();//跳转到APP.
}
}
appxaddr是存放APP程序的首地址。而(*(vu32*)appxaddr)则是读取APP程序首地址里的数据。这个数据为用户代码的堆栈堆栈地址指向RAM,而RAM的起始地址是0x20000000,因此上面的判断语句执行:判断用户代码的堆栈地址是否落在:0x20000000~0x2001ffff区间中,这个区间的大小为128K。0x2001ffff的得来是由tm32的ram区大小所决定的。我们使用的多为64k所以内存最大到0x2000ffff这里将0x2FFE0000改为0x2FFF0000也可以。
对于(iapfun)*(vu32*)(appxaddr+4);中其实存放的是复位地址,调用MSR_MSP之后,将把用户代码的栈顶地址设为站定指针。jump函数实现了设置PC指针为复位地址。
CORTEX-M3上电后后检测BOOT引脚的电平来决定PC的位置。例:BOOT设置为FLASH启动,启动后CPU会先取两个地址:一个是栈顶地址,另一个是复位地址。因此才有了第4、第5点的写法。