STM32学习:IAP简单的IAP例⼦
章节概述:
以⼀个最简单的例⼦⽰范IAP程序(没有⽂件通讯,没有跳转判断),需要借助IDE进⾏分区数据的划分以及下载。
准备
IDE:keil-MDK 5
MCU:STM32F103ZET6为例(Flash地址为0x08000000—0x0807ffff,共512KB)。
BSP:STM32-HAL
启动⽅式:FLASH启动
前32KB存放BootLoader程序(0x08000000 ~ 0x08007fff)
剩余的空间存放APP程序(0x08008000 ~ 0x0807ffff)
假定跳转的APP程序的实际物理地址为:0x08008000
分别新建2个⼯程,名字可以为:bootLoader与app。
从Bootloader跳转到APP
Bootloader
为了区分执⾏分区的不同,添加串⼝打印功能。(略)
例如:printf("Hello Bootloader\r\n");
添加下⾯的代码以实现跳转功能:
节选⾃CubeMX的例程:STM32Cube\Repository\xxx\Projects\Applications\IAP
#define NVIC_VectTab_RAM ((u32)0x20000000)
#define NVIC_VectTab_FLASH ((u32)0x08000000)
#define PHYSICAL_ADDRESS_Flash (0x08000000) // 程序烧写的物理地址
#define APPLICATION_POSADDR (0x0000C000) // APP 偏移量
#define APPLICATION_ADDRESS ((PHYSICAL_ADDRESS_Flash) | (APPLICATION_POSADDR)) // 最终跳转的地址,实际上就是0x08008000
typedef void (*pFunction)(void);
pFunction JumpToApplication;
uint32_t JumpAddress;
void NVIC_SetVectorTable(uint32_t base, uint32_t offset)
{
/* close interruption*/
__set_FAULTMASK(1);
/* set vector table*/
//NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0xffset);
SCB->VTOR = base | offset;
/* open interruption*/
__set_FAULTMASK(0);
}
int main(void)
{
// 在BootLoader程序的中断向量表指向设置中应有这么⼀句:
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); //设置中断向量表指向
/* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000) //①
{
/* Jump to user application */
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);// ②
JumpToApplication = (pFunction) JumpAddress;//③
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS); // ④
JumpToApplication(); // ⑤
}
while (1)
{}
}
stm32怎么使用printf解析:
①因为⽤户程序开始位置(0x08008000处)的前4个字节存放的是堆栈的地址,堆栈地址必定是指向RAM空间的,⽽STM32的RAM空间起始地址为0x20000000,所以要进⾏判断。
APPLICATION_ADDRESS存放的是⽤户程序Flash的⾸地址
(*(volatile u32*)APPLICATION_ADDRESS)的意思是取⽤户程序⾸地址⾥⾯的数据,这个数据就是⽤户代码的堆栈地址,堆栈地址指向RAM,⽽RAM的起始地址是
0x20000000,因此上⾯的判断语句执⾏:判断⽤户代码的堆栈地址是否落在:0x20000000~0x2001ffff区间中,这个区间的⼤⼩为128K,笔者查阅STM32各型号的RAM⼤⼩,⽬前RAM最⼤的容量可以做到192K+4K,时钟频率为168MHZ。⼀般情况下,我们使⽤的芯⽚较多的落在<128K RAM的区间,因此上⾯的判断语句是没有太⼤问题的。
②程序跳转地址的确认,前⾯已经说过0x08008004处的4个字节存放的是复位函数的⼊⼝地址,该句的意思为获得ApplicationAddress + 4地址处的数据,即为获得新的复位函数⼊⼝地址。
③令Jump_To_Application这个函数指针指向复位函数⼊⼝地址。
④堆栈的初始化,调⽤__set_MSP重新设定栈顶代地址,把栈顶地址设置为⽤户代码指向的栈顶地址。
⑤跳转到新的复位函数。设置PC指针为复位地址。
App
为了区分执⾏分区的不同,添加串⼝打印功能。(略)
例如:printf("Hello App\r\n");
添加下⾯的代码以配合跳转功能:
#define NVIC_VectTab_RAM ((u32)0x20000000)
#define NVIC_VectTab_FLASH ((u32)0x08000000)
#define APPLICATION_POSADDR (0x0000C000) // APP 偏移量
void NVIC_SetVectorTable(uint32_t base, uint32_t offset)
{
/* close interruption*/
__set_FAULTMASK(1);
/* set vector table*/
/
/NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0xffset);
SCB->VTOR = base | offset;
/* open interruption*/
__set_FAULTMASK(0);
}
int main(void)
{
// 在⽤户程序的中断向量表指向设置⽤应有这么⼀句:
NVIC_SetVectorTable(NVIC_VectTab_FLASH, APPLICATION_POSADDR); //设置中断向量表指向
...;
}
⼯程设置
程序写完了,但是编译还需要进⾏设置。
BootLoader⼯程
为了确保Address Range不超过0x08008000,超过会导致app分区被覆盖。
点击:MDK-Options-Debug-Settings:
Flash Download,勾选Reset and Run、Program、Reset and Run
点击列表中的Flash(如果没有则需要点击Add进⾏添加)
修改Size为:0x08004000(只要不超过0x8000即可)
编译,烧写。
APP ⼯程
为了修改链接地址,以及烧录范围。
MDK-Options-Target-Read/Only Memory Areas
勾选IROM1:填⼊0x08008000。
MDK-Options-Target-Debug-Settings:
点击Flash Download,勾选Reset and Run、Program、Reset and Run
点击列表中的Flash(如果没有则需要点击Add进⾏添加)
修改Start为:0x08008000
修改Size为:0x08004000(只要不超过0x80000即可)
编译,烧写。
现象
观察到下⾯的打印信息。
Hello Bootloader
Hello app
注意到2段程序都有NVIC_SetVectorTable函数,可以设置将其注释掉,然后做如下的实验:
配置BootLoader中打开了定时器中断,写好对应的处理函数,例如在中断响应中打印"Boot"。
配置User app中同样打开了定时器中断,写好了对应的处理函数,例如在中断响应中打印"User"。
观察到,尽管两边都实现了中断以及响应,但是都在执⾏着BootLoader的中断响应。
IAP实现总结
STM32的IAP⽅案实现需要在进⾏⽤户程序之前加⼀段Bootloader程序。
BootLoader程序的作⽤就是:
要么:设置新的栈顶指针,直接跳转到⽤户程序。
要么:删除原有的⽤户程序,读取*.bin⽂件数据并将数据重新写⼊新的⽤户程序。
对于⽤户程序相⽐普通的编程只需要做三步改动即可:
改变代码存放的地址空间
改变中断向量表
修改⽣成*.bin⽂件
实际上的IAP⼯程
本⽂介绍了简单的IAP调整,没有对⽂件的传输以及写⼊。
传输有很多⽅法,可以是先把hex⽂件转换为bin。再通过通讯⽅式完成⽂件传输、写⼊;或者直接传输HEX⽂件,再在MCU进⾏解析(难)
我们讨论对FLASH的读写。
对FLASH的读写
问:如何进⾏对STM32的Flash进⾏擦除和写⼊操作。
答:STM32-HAL库的读写⾮常⽅便,但是要注意读写地址与页的关系
使⽤STM32的固件库函数,只需调⽤⼏个库函数即可轻松解决,使⽤的固件库为stm32f10x_flash.c⽂件,对Flash的操作过程简要为:Flash解锁Flash擦除Flash写⼊Flash上锁。(对Flash编程的更详细操作参考STM32F10xxx闪存编程⼿册)
//解锁Flash
FLASH_Unlock();
FLASH_SetLatency(FLASH_Latency_2); //因为系统时钟为72M所以要设置两个时钟周期的延时
// 擦除
for(i=0;i<240;i++)
{
if(FLASH_ErasePage(FLASH_ADDR+i*2048) != FLASH_COMPLETE) //⼀定要判断是否擦除成功
return ERROR;
/*
说明:FLASH_ErasePage(uint32_t Page_Address)即为Flash擦除操作,按页擦除,每页2KB,Page_Address为页的起始地址,如0x08000000是第⼀页起始地址,0x08000800为第⼆页起始地址,这⾥的操作擦除了0x08008000—0x0807ffff地址空 */
}
#if 0
unsigned char buf[1024]; //假设待写⼊的代码数据
unsigned short temp; //临时数据
for(i=0;i<512;i++)
{
temp = (buf[2*i+1]<<8) | buf[2*i]; //2个字节整合为1个半字
if(FLASH_ProgramHalfWord(ADDR,temp) != FLASH_COMPLETE) //判断是否写⼊成功
{
Return ERROR;
}
ADDR +=2; //地址要加2,因为每次写⼊的是2个字节(1个半字)
}
/*
说明:因为STM32的Flash写⼊为双字节(1个半字)写⼊,FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)函数即为对地址为Address写⼊1个半字的Data,每次写⼊完成后地址要加2。
*/
#endif
FLASH_Lock(); //Flash 上锁,⼀个固件库函数即可实现。
问:怎么进⾏PC指针的强制跳转,跳转时需要做些什么。
答:关闭中断是可选的,设置栈顶指针是必须的。
arm内核通⽤:disable_irq(); //关闭所有中断 enable_irq();//打开所有中断
设置栈顶指针:__set_MSP();
附录:存储容量与起⽌地址的关系式
以字节(Byte)为计算单位:存储容量 = 储存单元数 × 储存字长(字节)。
以Kb为计算单位:储存单元数 × 储存字长(字节)÷ 1024 。
⾸尾地址的差再加⼀即是储存单元的个数,每⼀个储存单元存放⼀个字节,可以得到总储存单元⼀共有多少字节;
再根据1M = 1024 K = 1024 × 1024Byte,那么就可以很容易地计算出三者的关系了。
例如:容量6K,⾸地址0x0000,末地址为?
解答:6K = 6 × 1024 Byte ,未地址 = 6 × 1024 + 000H = 1800H
附录:确认代码的链接地址
为什么在IDE中设置有关的地址呢,为了解答这个问题就需要我们知道:链接地址、运⾏地址、加载地址、存储地址关系。
运⾏地址与链接地址:是类似的。
加载地址与存储地址:是等价的。
链接地址
在程序编译的时候,每个⽬标⽂件都是由源代码编译得到,最终多个⽬标⽂件链接⽣成⼀个最终的可执⾏⽂件,⽽链接地址就是指⽰链接器,各个⽬标⽂件的在可执⾏程序中的
位置。
⽐如,⼀个可执⾏程序a.out由a.o、b.o、c.o组成,那么最终的a.out中谁在前、谁在中间、谁在结尾,都可以通过制定链接地址来决定。
链接地址是静态的,在进⾏程序编译的时候指定的。
注意,CPU不关⼼链接地址是物理地址还是虚拟地址。
总结:
1、链接地址是给编译器⽤的,⽤来计算代码中相关地址偏移的
2、只要和PC值相关的就是位置⽆关代码(相对偏移),和PC⽆关的就是位置相关代码(绝对值)
运⾏地址
程序实际在内存中运⾏时候的地址,⽐如CPU要执⾏⼀条指令,那么必然要通过给PC赋值,从对应的地址空间中去取出来,那么这个地址就是实际的运⾏地址。
运⾏地址是动态的,如果你将程序加载到内存中时,改变存放在内存的地址,那么运⾏地址也就随之改变了。
注意,CPU同样不关⼼运⾏地址是虚拟地址还是物理地址。
链接地址和运⾏地址区别
⾸先要区分开两个概念:位置相关代码和位置有关代码。位置⽆关代码:mov、b或bl等指令,在跳转时,地址是PC+相对偏移量,这样的指令没有绝对地址,都是相对PC的偏
移,这样,及时运⾏地址和链接地址不⼀样,也不影响实际代码的执⾏。
位置相关代码:ldr r0, =label,这⾥的label(标签)实际就是链接地址,这条指令实质就是将标号的地址放到PC⾥实现跳转,但是如果实际运⾏的地址和链接的地址不⼀样,这样就会
由于跳转的地址不是实际运⾏地址⽽出错,这就叫做位置相关代码。
条件相关代码和条件⽆关代码之间的本质区别就是:指令中相关地址是运⾏地址还是链接地址,如果是运⾏地址那么就是位置⽆关代码,因为运⾏地址是变化的相对量;如果是
加载地址,那么就是绝对量(链接时候指定好的),这就是位置相关代码了。
总结,当链接指定的地址和实际运⾏的地址⼀样的时候,链接地址==运⾏地址;当链接指定的地址和实际运⾏的地址不⼀样的时候,如果整个代码中的地址都是相对偏移量,那
么整个程序仍然运⾏畅通⽆阻,否则,整个程序运⾏结果就会出错(因为指定运⾏地址和实际运⾏地址不符)。
加载地址
每⼀个程序⼀开始都是存放在flash中的,⽽运⾏是在内存中,这个时候就需要从flash中将指令读取到内存中(运⾏地址),flash的地址就是加载地址(存储地址)。
指令在flash中存放的存储地址,就是存储地址。
所以,加载地址指的是将代码从⼀个地址A搬移到地址B,这时候地址A就是加载地址。
附录:STM32F0xx 实现中断向量表重定义
在STM32F103等cortex-m3/m4内核的单⽚机上可以通过设置SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;该寄存器的值来实现中断向量表的重定义。
在Coretext-M3与M4核中,在System Control Block中存在⼀个向量表偏移量寄存器 VTOR(0xE000ED08),系统产⽣中断后,内核通过这个寄存器的值来到中断向量
表的地址,进⽽执⾏中断例程代码。
当然,此寄存器的值是可以修改的,它的默认值为0。实际上在⼤部分的M3和M4的⼯程中,⼀般都是在SystemInit函数中对此寄存器的值进⾏设置。
由于STM32F0XX采⽤的是M0核,它是没有这个VTOR寄存器的,那么它⼜是怎么到中断向量表的地址的呢?
如何将中断向量表的寻位置从0x0800 0000修改到0x0800 3000(假设为APP的地址)? 我们重新回顾之前的分析,可以得出有2种⽅法:
修改寄存器VTOR的值(M3/M4 使⽤)
内存重映射(M0使⽤)
通过将SRAM重映射到地址0x0000 0000,那么,M0系统产⽣中断后,CPU还是从地址0x0000 0000寻中断⼊⼝,但是,实际上不再是寻址0x0800 0000,⽽是寻址0x2000 0000,这么⼀来,接下来我们就只需要将中断向量表整个拷贝到SRAM上,也就是0x2000 0000上,就这样,CPU就可以正常寻址中断向量表了。
基于内存重映射实现的基本思想:
1、将中断向量表放⼊到RAM的起始地址(只需要在应⽤程序中保留RAM其实地址的0x100⼤⼩不使⽤即可)。
2、在bootload中将应⽤程序的中断向量表从Flash中拷贝到RAM中。
3、设置STM32F0xx中断向量表位于RAM中,主要⽤到的寄存器如下:
具体实现代码如下:
/*
* Function: void clock_init(void)
* Parameter: none
* Return: none
*/
int main(void)
{
memcpy((void*)0x20000000, (void*)APP_FLASHADDR, VECTOR_SIZE);
SYSCFG->CFGR1 |= 0x03; // SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
JumpToApp();
while (1);
}
memcpy((void*)0x20000000, (void*)APP_FLASHADDR, VECTOR_SIZE);
APP_FLASHADDR是应⽤程序的起址地址,从这⾥开始的VECTOR_SIZE字节,存放是的应⽤程序的中断向量表。
VECTOR_SIZE是指中断向量表的⼤⼩,具体多⼤可以在startup.s⽂件⾥计算得到。
VECTOR_SIZE 的计算:
根据startup.s中的语句可以推导出来。
每⼀个DCD都代表⼀个中断向量,例如:
DCD USART1_IRQHandler ; USART1
;
这⾥的“USART1_IRQHandler"其实就是UART1中断服务程序USART1_IRQHandler这个函数,同时,它也代表这个函数的⼊⼝地址。
这张表包括45个元素,每个元素是⼀个长度为4字节的地址。除了第⼀个地址是SP(堆栈指针)外,其它的地址都是某个中断服务程序的⼊⼝地址。
针对本例,就应该是45*4=180(0xB4)个字节。
在执⾏完以上两⾏代码后,若发⽣中断,CPU就会去SRAM(即0x2000 0000处)取中断向量了,所以,以0x20000000作为起始地址之后的VECTOR_SIZE个字节就不能被改动了。为了达到这VECTOR_SIZE个字节不被修改的⽬的,如下两种⽅法可以实现。
在⼯程⽂件内修改SRAM的起始地址及长度:
MDK-Options-Target-Read/Write Memory Areas
勾选IRAM1:填⼊0x020000B4(⼤于或等于0x020000B4即可)
如果使⽤了分散加载⽂件,则在分散加载⽂件中修改SRAM的起始地址及长度也能达到⽬的。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论