iap需要准备两个文件
1、bootloader
2、app
stm启动流程
1、在0x0处去一个字给栈顶指针,0x4赋值给pc
0x4存储值有三种:flash(0x080000000),system memory(st官方写的可以iap的代码),Sram(0x20000000)
根据不同的启动方式,会将0x0和0x4映射到不同的地址
例如flash启动 0x00000000 和 0x00000004 地址被映射到 内部 FLASH 的首地址 0x08000000 和 0x08000004
然后将这两个地址的值赋值给下面两个指针
第一个字会赋值给msp,第二个给pc
MSP:主栈栈顶指针,用于给程序存放数据用的栈顶
pc:用于跳转到Reset_handler
在 Reset_Handler
中,会进行一些必要的系统初始化工作,比如设置系统时钟、初始化堆栈(如果MSP在上一步没有被正确设置,这里会重新设置)、清除BSS段、初始化C/C++全局和静态变量等。
执行完Reset_handler后跳转到main
对于app
正常的代码会从0x08000004取出pc复位中断向量,跳转到Reset_handler去执行代码
但是对于app启动流程,我们要将原本位置的0x080000004的代码放到单片机的
0x08000000+N+M
在执行完iap后,iap跳转到app的地址执行代码
在iap中我们要先初始化栈顶指针在跳转到app
但是有一个问题当cpu触发中断时会回到0x08000000去查找中断服务函数
1、为了保证在main触发中断时不会从头开始查找中断向量表,要配置偏移量
2、使app代码的程序起始地址更改位置
这是将app代码存储在flash的配置方法,将0x08000000-0x0800FFFF留给bootloader
偏移量为 0X200的倍数
下面是Sram配置
我们将0x20000000-0x200000FFF留给bootloader 4k
将0x20001000-0x20019000给app使用100K
将其余部分留给APP程序的ram使用24k
如果要配置成sram那要修改偏移量SCB->VTOR = SRAM_BASE | 0x1000;
最后生成bin文件
fromelf.exe --bin -o "$L@L.bin" "#L"
BootLoader
对于bootlaoder有2个基础
1、flash的读写功能
2、串口接收大数据的功能
Flash读写
flash要先擦后写,要将需要写的扇区全部擦除(置1)
flash.c
#include "stmflash.h"
#include "stdio.h"
u32 STMFLASH_ReadWord(u32 faddr){
return *(vu32*)faddr;
}
//用于给擦除函数提供扇区,返回值在flash.h
uint16_t STMFLASH_GetFlashSector(u32 addr)
{
if(addr<ADDR_FLASH_SECTOR_1)return FLASH_Sector_0;
else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_Sector_1;
else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_Sector_2;
else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_Sector_3;
else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_Sector_4;
else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_Sector_5;
else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_Sector_6;
else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_Sector_7;
else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_Sector_8;
else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_Sector_9;
else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_Sector_10;
return FLASH_Sector_11;
}
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite){
FLASH_Status statue=FLASH_COMPLETE;
u32 addr=WriteAddr;
u32 enaddr=WriteAddr+NumToWrite*4;
if(addr<0x08000000||addr%4) return ;
FLASH_Unlock();
FLASH_DataCacheCmd(DISABLE);
if(addr<0x1FFF0000){
while(addr<enaddr) //扫清一切障碍.(对非 FFFFFFFF 的地方,先擦除)
{
if(STMFLASH_ReadWord(addr)!=0XFFFFFFFF)
//有非 0XFFFFFFFF 的地方,要擦除这个扇区
{
statue=FLASH_EraseSector(STMFLASH_GetFlashSector(addr),VoltageRange_3);
//VCC=2.7~3.6V 之间!!
if(statue!=FLASH_COMPLETE)break;//发生错误了
}else addr+=4;
}
printf("success erase\r\n");
}
if(statue==FLASH_COMPLETE){
while(WriteAddr<enaddr){
//printf("into write app\r\n");
if(FLASH_ProgramWord(WriteAddr,*pBuffer)!=FLASH_COMPLETE){
break;
}
WriteAddr+=4;
pBuffer++;
}
}
FLASH_DataCacheCmd(ENABLE);
FLASH_Lock();
}
/*********************************
传入读取的地址,存储数组,还有长度
使用STMFLASH_ReadWord读字,传入地址
每读一个字都传入(4个字节)地址加4
**********************************/
void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead){
u32 i;
for(i=0;i<NumToRead;i++){
pBuffer[i]=STMFLASH_ReadWord(ReadAddr);
ReadAddr+=4;
}
}
flash.h
#ifndef __STMFLASH_H
#define __STMFLASH_H
#include "sys.h"
#define STM32_FLASH_BASE 0x08000000 //flash的起始地址
//FLASH的扇区起始地址
#define ADDR_FLASH_SECTOR_0 ((u32)0x08000000)//16K
#define ADDR_FLASH_SECTOR_1 ((u32)0x08004000)
#define ADDR_FLASH_SECTOR_2 ((u32)0x08008000)
#define ADDR_FLASH_SECTOR_3 ((u32)0x0800C000)
#define ADDR_FLASH_SECTOR_4 ((u32)0x08010000) //16K
#define ADDR_FLASH_SECTOR_5 ((u32)0x08020000) //128K
#define ADDR_FLASH_SECTOR_6 ((u32)0x08040000)
#define ADDR_FLASH_SECTOR_7 ((u32)0x08060000)
#define ADDR_FLASH_SECTOR_8 ((u32)0x08080000)
#define ADDR_FLASH_SECTOR_9 ((u32)0x080A0000)
#define ADDR_FLASH_SECTOR_10 ((u32)0x080C0000)
#define ADDR_FLASH_SECTOR_11 ((u32)0x080E0000)
u32 STMFLASH_ReadWord(u32 faddr); //读一个字
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite);
void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead);
#endif
串口中断函数
#define USART_REC_LEN 120*1024 //定义最大接收字节数 120k
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000))); //接收缓冲,最大USART_REC_LEN个字节.
//意思是将USART_RX_BUF数组存放在0X20001000这个地址
u16 USART_RX_STA=0; //接收状态标记
u32 USART_RX_CNT=0; //接收的字节数
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if(USART_RX_CNT<USART_REC_LEN){
USART_RX_BUF[USART_RX_CNT]=Res;
USART_RX_CNT++;
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif
然后对于主程序的流程
要控制代码大小不能超过预留的空间
先接收代码函数
当在一段时间内oldcount==USART_RX_CNT说明没有新的数据传进来,USART_RX_CNT不会改变
if(USART_RX_CNT){
if(oldcount==USART_RX_CNT){
applen=USART_RX_CNT;
oldcount=0;
USART_RX_CNT=0;
printf("代码接收完成\r\n");
printf("代码长度位%d\r\n",applen);
}else oldcount=USART_RX_CNT;
}
更新固件函数
解释一下
if((* (vu32*)(0X20001000+4)&0xFF000000)==0x08000000)
0X20001000+4将这个地址转换成uint32*整形32指针类型,然后再取这个地方的地址,看他的值是不是再0x08000000-0x08FFFFFF之间
因为我们将串口接收的数组放在了0X20001000这个地方,将app的bin文件通过串口放到了这个地方,而对于app来说,0X20001000+4存放的就是pc指针,他的值就是0x08010000,所以(* (vu32*)(0X20001000+4)的值就是0x08010000
如果没有偏移使用flash启动,pc的值位0x08000000
if(key==WKUP_PRES){//WK_UP按下
if(applen){
printf("开始更新固件");
LCD_ShowString(30,210,200,16,16,"Copying APP2FLASH...");
if((*(vu32*)(0X20001000+4)&0xFF000000)==0x08000000){
iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applen);
LCD_ShowString(30,210,200,16,16,"Copying APP2FLASH SUCCESS");
printf("固件更新完成\r\n");
}
else{
printf("非flash的应用程序\r\n");
}
}
else{
printf("没有固件需要更新\r\n");
}
clearflag=7;//标志更新了显示,并且设置 7*300ms 后清除显示
}
跳转到flash程序,要先初始化好msp,再直接跳转就行
FLASH_APP1_ADDR 0x08010000 这个地方存储的是app的栈顶指针msp
同过这个MSR_MSP(* (vu32*)appxaddr);将整段程序赋值一个新的栈,栈顶指针就是app的msp
if(key==KEY2_PRES){
printf("开始执行app程序\r\n");
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000){
iap_load_app(FLASH_APP1_ADDR);
}
else{
printf("非法地址无法执行flash app\r\n");
}
clearflag=7;//标志更新了显示,并且设置 7*300ms 后清除显示
}
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app(); //跳转到APP.
}
}
ok结束了