手把手学STM32(二)虚拟串口在线升级IAP代码篇
在上一篇博客中我提到了STM32F407+虚拟串口在线升级的基本原理,这一篇我讲一下关于一些,在虚拟串口在线升级的代码和注意的一些细节。
在线升级原理不清楚的请移步参考上一篇[我用一张图了解虚拟串口IAP](https://blog.youkuaiyun.com/cs111211/article/details/105142049)
1.升级代码讲解
1)拷贝app代码到flash。
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)
{
FLASH_Status status = FLASH_COMPLETE;
u32 addrx=0;
u32 endaddr=0;
if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址
FLASH_Unlock(); //解锁
FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间禁止数据缓存。
addrx=WriteAddr; //写入起始地址
endaddr=WriteAddr+NumToWrite*4; //写入结束地址
if(addrx<0X1FFF0000) //只有主存储区才执行擦除操作 !
while(addrx<endaddr) //对非0xFF的地方先擦除
if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//擦除非0XFFFFFFFF地方
{
status=FLASH_EraseSector(STMFLASH_GetFlashSector(addrx),VoltageRange_3);//VCC=2.7~3.6V之间
if(status!=FLASH_COMPLETE)break; //发生错误了
}else addrx+=4;
}
}
if(status==FLASH_COMPLETE)
{
while(WriteAddr<endaddr)//写数据
{
if(FLASH_ProgramWord(WriteAddr,*pBuffer)!=FLASH_COMPLETE)//写入数据
{
break; //写数据异常
}
WriteAddr+=4;
pBuffer++;
}
}
FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
FLASH_Lock();//上锁
}
u32 iapbuf[512]; //缓冲
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
u32 t;
u16 i=0;
u32 temp;
u32 fwaddr=appxaddr;//写入当前的地址
u8 *dfu=appbuf;
for(t=0;t<appsize;t+=4)
{
temp=(u32)dfu[3]<<24;
temp|=(u32)dfu[2]<<16;
temp|=(u32)dfu[1]<<8;
temp|=(u32)dfu[0];
dfu+=4;//偏移4个地址
iapbuf[i++]=temp;
if(i==512)
{
i=0;
STMFLASH_Write(fwaddr,iapbuf,512);
fwaddr+=2048;//偏移2048 512*4=2048
}
}
if(i)STMFLASH_Write(fwaddr,iapbuf,i);//
}
2)代码的使用方法
上面这部分代码是将app写入到FLASH的指定区域的方法,需要在工程里把flash的库加到工程中。
也就是stm32f4xx_flash.c。
这些函数是用来写app的,我们需要调用接口是void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)。这里有三个输入的参数:
- appxaddr:是写入flash的指定地址。具体哪些地址可写入可以参考我的写入初始地址。我是把app栈顶地址写到了。#define FLASH_APP1_ADDR 0x08010000这个地址。因为boot的程序不会太大,所以我们给boot程序预留了64k的内存也就是0x10000 ,flash的起始地址是0x08000000所以第一个app的起始地址是0x080000000+0x10000=0x08010000。当然这里可以下载多个app到flash里。(只要FLASH内存够)具体哪些地方可以存app可具体查看芯片datasheet里的flash扇区的地址。
- appbuf:是app程序的缓冲区,也就是app的code()
- appsize:app程序的大小(字节)
2.跳转代码以及注意点
先上代码,转跳到app的程序
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法
{
__disable_irq();//关闭所有中断
RCC_AHB2PeriphResetCmd(RCC_AHB2Periph_OTG_FS, ENABLE);
RCC_AHB2PeriphResetCmd(RCC_AHB2Periph_OTG_FS, DISABLE); //复位USB OTG
TIM_Cmd(TIM3,DISABLE);//定时器失能
jump2app=(iapfun)*(vu32*)(appxaddr+4); //代码区第二个字是程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr); //初始化app堆栈指针(第一个字用于存放栈顶地址)
jump2app(); //转跳到app
}
}
上面这段代码是转跳到应用程序的代码,这里要画个重点。
1)关闭所有中断
在转跳到app程序前要把已经开启的所有中断关闭掉。因为升级只上一次电,如果不关闭中断的话,中断寄存器什么的都没有变。在跳到app程序后中断仍然在,但是中断函数却没了,程序不知道中断是来自哪个地方的,所以会导致程序卡死。所以使用__disable_irq();//关闭所有中断
2)失能已经开启的外设
这个和上一条的注意点要类似,因为在我的bootloader 程序中用到了定时器和usb虚拟串口,而app程序中也用到了定时器和虚拟串口。所以要想在app里也能够正常使用这些外设的话必须在转跳之前将这些外设失能,而失能外设的方法就是disable它的时钟。
RCC_AHB2PeriphResetCmd(RCC_AHB2Periph_OTG_FS, ENABLE);
RCC_AHB2PeriphResetCmd(RCC_AHB2Periph_OTG_FS, DISABLE); //这两句就是复位USB OTG
TIM_Cmd(TIM3,DISABLE);//是定时器失能。
3)实现跳转
jump2app=(iapfun)(vu32)(appxaddr+4); 这句对应的是app中断向量表的第二项,复位地址强制转化为函数指针
MSR_MSP((vu32)appxaddr); //初始化app堆栈指针(第一个字用于存放栈顶地址)
这句话是设置主函数栈指针。
最后一句 jump2app(); //转跳到app调用函数,去app复位地址去执行复位操作
3.app程序的生成方法
1)设置栈顶地址
APP程序在主函数变量定义之后加入这句话
SCB->VTOR=0x8000000|0X10000;
这句话的意思是app栈顶地址是0x08010000,同理可设其他地址。
2)设置add
在target for options中的target选项中照下图编辑,在IROM1后的第一个框后的地址改为0x08010000这个地址是app的起始地址,第二个框改成0xf0000这个框是app程序的大小我这里用的flash是1m的所以size=1M-64k=0xf0000。这两个框里面的内容必须是0x400的整数倍。
4)生成bin文件
在设置完地址之后,我们需要一个把程序转化成bin文件的东西,在keilv5里面可以实现这个功能首先找到 fromelf.exe的路径然后在找到你output里的文件名我这里工程是USART,然后编译后会生成一个.axf后缀的文件.
然后在user选项中的Run#1前点上勾,在后面的框填入如下格式的一段路径
E:\MDK5\ARM\ARMCC\bin\fromelf.exe --bin -o …\OBJ\USART.bin …\OBJ\USART.AXF
前面的是 fromelf.exe的路径\ --bin -o …\OBJ\USART.bin …\OBJ\USART.AXF
然后点ok,点击编译然后在输出区出现下面的提示说明成功生成bin文件。在OBJ文件夹中可以找到USART.bin这个文件。
4.app程序的一些注意点
1.在app中开启中断,需要在mian开头的位置,开启中断以及初始化用到的外设和使能相应的外设时钟。
2.user里的路径一定要输入正确不然会报错。我的是在 E:\MDK5\ARM\ARMCC\bin\fromelf.exe路径下,根据自己的MDK装的位置不同这个会不同。
3.一定要在程序中加入 SCB->VTOR=0x8000000|0X10000;。不然不升级之后代码不能运行。