ESA2GJK1DH1K升级篇: 移植升级程序到自己的项目(BootLoader程序制作)
前言
此代码兼容STM32F103全系列
为避免添加上升级程序造成内存不足,请使用128KB Flash及其以上的型号
这篇文章是为了能够让大家快速移植我的升级模板程序到自己的项目
BootLoader 程序制作
拷贝文件到自己的项目
拷贝到自己的项目(我准备了一个工程作为叙述)
在自己工程的定时器里面添加以下信息
if(IAPStructValue.PutDataFlage && IAPStructValue.UpdateFlage)IAPStructValue.DownloadTimeout++; else IAPStructValue.DownloadTimeout=0; IAPStructValue.MainTimeout++;
在自己工程的主函数添加如下信息
#include "IAP.h" IAP();
IAPLoadAPPProgram(); IAPDownloadTimeoutFunction(); IAPMainTimeoutFunction(); IAPWriteData();
大家把当前的程序下载到单片机,然后看一下串口1的打印信息
user1ROMStart: 0x8004000 用户程序1 Flash存储的开始地址
user1ROMSize : 0x5c00 用户程序1 程序大小
user2ROMStart: 0x8009c00 用户程序2 Flash存储的开始地址
user2ROMSize : 0x5c00 用户程序2 程序大小
大家可以在下面这个文件根据自己的芯片进行设置
所选芯片Flash大小:这个根据自己的芯片设置
BootLoader程序大小: BootLoader程序产生的bin文件大小
假设自己的BootLoader程序的bin文件大小是 15K
则可以设置上面的值 为16,18,20等
假设自己的BootLoader程序的bin文件大小是 20K
则可以设置上面的值 为22,24,26等
存储用户数据所用Flash大小: 这个根据自己需要的设置,
但是必须设置,因为咱升级的时候也需要记录数据
可以是2,4,6,8等等等等
设置好以后系统便会根据大家的设置打印出来APP用户程序的信息
当前Flash存储分配如下图
BootLoader程序占用 16KB
两份用户程序各占23KB,
第一份APP用户程序从0x08004000开始存储
第二份APP用户程序从0x08009C00开始存储
剩余的2KB用来存储其它信息
然后接着说
这节我就用串口来模拟一下
1.每隔3S询问下升级信息
2.串口调试助手需要返回的升级信息格式
{"version":"1.0.456","SumBin1":103,"SumBin2":244}
前面是版本号,后面两个是两份程序文件的校验和(下一节会说明校验和怎么获取值)
然后需要把信息交给 IAPVersionDispose 函数处理
3.大家需要进入 IAPVersionDispose 函数添加一些内容
4.因为我是用串口模拟的,所以我直接这样写
5.测试
在发送给模块升级信息以后,模块先提取升级信息里面的版本号
如果和当前的不一致,再尝试提取bin文件的校验和
然后,接着发送询问哪一份文件的指令
6.需要在串口中断中写上以下程序
以下程序就是把接收的程序写到环形队列里面
主循环会提取里面的数据,然后写到flash里面
if(IAPStructValue.PutDataFlage && (IAPStructValue.PutDataFlage^IAPStructValue.Overflow))//可以往环形队列里面写数据,同时没有溢出 { if(PutData(&rb_tIAP,NULL,&Res,1) == -1) //&Res :为数据地址 1:写一个数据,可以写多个 { IAPStructValue.Overflow = 1;//环形队列溢出 } }
IAPStructValue.PutDataFlage 是允许写入的标志
这个标志是在这里允许的
7.还需要做个处理
在程序接收超时(接收完成)的地方写上
因为我是用的串口接收数据,所以如果串口进入了空闲中断
我就认为程序接收完了
说一下流程
其实现在就需要把对应的程序文件发给单片机了
不过咱还没有制作好用户程序,所以需要到下一节再接着测试步骤
现在看细节处理
虽然我的升级模板可以保证可靠的把程序写入Flash并且如果检测有问题则自动切换到上一份程序运行,
但是需要避免另一件事情(用户程序本身执行了一段时间以后出问题了.....)
结果造成了不停的重启....
解决方案是利用一个按钮
我一直按着那个按钮.
在进入BootLoader程序的时候,我检测下那个按钮是不是按下了,如果按下了就不尝试加载用户程序了
但是咱还需要控制程序重新升级
按下按钮超过3S,
指示灯快闪,
松开按钮,
快闪3S后,写入升级标志,清除版本号,重启
BootLoader判断有升级标志,则不会尝试加载用户程序
就会执行获取升级信息->获取程序文件
然后就完成了重新升级
大家会有疑惑,如果运行了用户程序怎么控制升级呢???
其实用户程序可以定时获取下程序版本,如果不一致,就置位一下升级标志,然后重启即可
提醒
越往后我的源码基本上都再次做了封装!
我只告诉大家如何使用我的源码,使用哪些接口!
大家想研究我的源码,就去看前面几节
补充
在前面的升级程序中串口里面是这样写的
void USART1_IRQHandler(void)//串口1中断服务程序 { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { Res =USART_ReceiveData(USART1); //读取接收到的数据 Usart1ReadBuff[Usart1ReadCnt] = Res; //接收的数据存入数组 Usart1ReadCnt++; if(Usart1ReadCnt > Usart1ReadLen -10)//防止数组溢出 { Usart1ReadCnt = 0; } Usart1IdleCnt = 0; if(HttpDataStartFlage) { //可以往环形队列里面写数据,同时没有溢出 if(IAPStructValue.PutDataFlage && (IAPStructValue.PutDataFlage^IAPStructValue.Overflow)) { if(PutData(&rb_tIAP,NULL,&Res,1) == -1) { IAPStructValue.Overflow = 1;//环形队列溢出 } } } //解析http数据-------------------------------Start //HTTP/1.1 200 OK if(!HttpHeadOK && IAPStructValue.PutDataFlage) { if(Res=='H' && HttpHeadCnt==0)HttpHeadCnt++; else if(Res=='T' && HttpHeadCnt==1)HttpHeadCnt++; else if(Res=='T' && HttpHeadCnt==2)HttpHeadCnt++; else if(Res=='P' && HttpHeadCnt==3)HttpHeadCnt++; else if(Res=='/' && HttpHeadCnt==4)HttpHeadCnt++; else if(Res=='1' && HttpHeadCnt==5)HttpHeadCnt++; else if(Res=='.' && HttpHeadCnt==6)HttpHeadCnt++; else if(Res=='1' && HttpHeadCnt==7)HttpHeadCnt++; else if(Res==' ' && HttpHeadCnt==8)HttpHeadCnt++; else if(Res=='2' && HttpHeadCnt==9)HttpHeadCnt++; else if(Res=='0' && HttpHeadCnt==10)HttpHeadCnt++; else if(Res=='0' && HttpHeadCnt==11)HttpHeadCnt++; else if(Res==' ' && HttpHeadCnt==12)HttpHeadCnt++; else if(Res=='O' && HttpHeadCnt==13)HttpHeadCnt++; else if(Res=='K' && HttpHeadCnt==14){HttpHeadOK = 1;HttpHeadCnt=0;HttpDataLength=0;} else { HttpHeadCnt=0; } } #ifdef UserContentLength //Content-Length: XXXXXXXX if(HttpHeadOK && !HttpDataLengthOK)//获取http发过来的数据个数 { if(Res=='-' && HttpHeadCnt==0) HttpHeadCnt++; else if(Res=='L' && HttpHeadCnt==1)HttpHeadCnt++; else if(Res=='e' && HttpHeadCnt==2)HttpHeadCnt++; else if(Res=='n' && HttpHeadCnt==3)HttpHeadCnt++; else if(Res=='g' && HttpHeadCnt==4)HttpHeadCnt++; else if(Res=='t' && HttpHeadCnt==5)HttpHeadCnt++; else if(Res=='h' && HttpHeadCnt==6)HttpHeadCnt++; else if(Res==':' && HttpHeadCnt==7)HttpHeadCnt++; else if(Res==' ' && HttpHeadCnt==8)HttpHeadCnt++; else if(HttpHeadCnt>=9 && HttpHeadCnt<=16 )//最大99999999个字节. 16:99999999 17:999999999 18:9999999999 { if(Res!=0x0D) { HttpDataLength = HttpDataLength*10 + Res - '0'; HttpHeadCnt++; } else { HttpDataLengthOK = 1; HttpHeadCnt = 0; } } else { HttpHeadCnt = 0; } } if(HttpHeadOK && HttpDataLengthOK && HttpDataLength && !HttpHeadEndOK) #else if(HttpHeadOK && !HttpHeadEndOK) #endif {//0D 0A 0D 0A if(Res==0x0D && HttpHeadCnt==0)HttpHeadCnt++; else if(Res==0x0A && HttpHeadCnt==1)HttpHeadCnt++; else if(Res==0x0D && HttpHeadCnt==2)HttpHeadCnt++; else if(Res==0x0A && HttpHeadCnt==3){HttpHeadEndOK = 1;} else HttpHeadCnt = 0; } if(HttpHeadEndOK == 1)//http数据的head已经过去,后面的是真实数据 { HttpHeadEndOK=0; HttpHeadCnt = 0; HttpDataLengthOK=0; HttpDataStartFlage=1; } //解析http数据-------------------------------end } }
其实大家应该明白为什么这样写
毕竟是http访问,会有数据头,上面就是为了去掉数据头