基于串口环形队列的IAP实现!|全球短讯
IAP很常见了。STM32串口IAP分享
我这里主要是记录一下我所使用的方法,调试也花了两天时间。
(资料图片仅供参考)
我所用的型号是STM32F103C8T6,这个IC有64KFlash和20K的RAM,也有小道说有后置隐藏的64K,也就是说其实是有128K,我一直也没有测试,有空测测,有大神这样说,估计是可以的。
这里重点记录一下我写的IAP思路和代码以及细节和遇到坑的地方。先大体的概述一下,最后贴上我认为重点的代码。
在概述之前先要解决一个问题,那就是sram空间和flash空间的问题,sram只有20K,flash有64k。
解决的办法有很多:
1)最常见的就是自己写上位机软件,通过分包发送,期间还可以加入加密算法,校验等等。
2)使用环形队列,简单点说就是个环形数组,一边接收上位机数据,一边往flash里面写。
这里条件限制就采用第二种方法。所以即使是分给A和B的25K空间的flash空间,sram只有20K也是不能一次接收完所有的bin数据的,这里我只开辟了一个1K的BUF,使用尾插法写入,我的测试应用程序都在5-6K,用这样的方法可以在9600波特率下测试稳定,也试过57600的勉强可以的,115200就不行了。
环形队列代码如下:
C文件:
#include"fy_looplist.h"#include"fy_includes.h"#ifndefNULL#defineNULL0#endif#ifndefmin#definemin(a,b)(a)<(b)?(a):(b)//<获取最小值#endif#defineDEBUG_LOOP1staticintCreate(_loopList_s*p,unsignedchar*buf,unsignedintlen);staticvoidDelete(_loopList_s*p);staticintGet_Capacity(_loopList_s*p);staticintGet_CanRead(_loopList_s*p);staticintGet_CanWrite(_loopList_s*p);staticintRead(_loopList_s*p,void*buf,unsignedintlen);staticintWrite(_loopList_s*p,constvoid*buf,unsignedintlen);struct_typdef_LoopList_list={Create,Delete,Get_Capacity,Get_CanRead,Get_CanWrite,Read,Write};//初始化环形缓冲区staticintCreate(_loopList_s*p,unsignedchar*buf,unsignedintlen){if(NULL==p){#ifDEBUG_LOOPprintf("ERROR:inputlistisNULL\n");#endifreturn0;}p->capacity=len;p->buf=buf;p->head=p->buf;//头指向数组首地址p->tail=p->buf;//尾指向数组首地址return1;}//删除一个环形缓冲区staticvoidDelete(_loopList_s*p){if(NULL==p){#ifDEBUG_LOOPprintf("ERROR:inputlistisNULL\n");#endifreturn;}p->buf=NULL;//地址赋值为空p->head=NULL;//头地址为空p->tail=NULL;//尾地址尾空p->capacity=0;//长度为空}//获取链表的长度staticintGet_Capacity(_loopList_s*p){if(NULL==p){#ifDEBUG_LOOPprintf("ERROR:inputlistisNULL\n");#endifreturn-1;}returnp->capacity;}//返回能读的空间staticintGet_CanRead(_loopList_s*p){if(NULL==p){#ifDEBUG_LOOPprintf("ERROR:inputlistisNULL\n");#endifreturn-1;}if(p->head==p->tail)//头与尾相遇{return0;}if(p->headtail)//尾大于头{returnp->tail-p->head;}returnGet_Capacity(p)-(p->head-p->tail);//头大于尾}//返回能写入的空间staticintGet_CanWrite(_loopList_s*p){if(NULL==p){#ifDEBUG_LOOPprintf("ERROR:inputlistisNULL\n");#endifreturn-1;}returnGet_Capacity(p)-Get_CanRead(p);//总的减去已经写入的空间}//p--要读的环形链表//buf--读出的数据//count--读的个数staticintRead(_loopList_s*p,void*buf,unsignedintlen){intcopySz=0;if(NULL==p){#ifDEBUG_LOOPprintf("ERROR:inputlistisNULL\n");#endifreturn-1;}if(NULL==buf){#ifDEBUG_LOOPprintf("ERROR:inputbufisNULL\n");#endifreturn-2;}if(p->headtail)//尾大于头{copySz=min(len,Get_CanRead(p));//比较能读的个数memcpy(buf,p->head,copySz);//读出数据p->head+=copySz;//头指针加上读取的个数returncopySz;//返回读取的个数}else//头大于等于了尾{if(lenhead-p->buf))//读的个数小于头上面的数据量{copySz=len;//读出的个数memcpy(buf,p->head,copySz);p->head+=copySz;returncopySz;}else//读的个数大于头上面的数据量{copySz=Get_Capacity(p)-(p->head-p->buf);//先读出来头上面的数据memcpy(buf,p->head,copySz);p->head=p->buf;//头指针指向数组的首地址//还要读的个数copySz+=Read(p,(char*)buf+copySz,len-copySz);//接着读剩余要读的个数returncopySz;}}}//p--要写的环形链表//buf--写出的数据//len--写的个数staticintWrite(_loopList_s*p,constvoid*buf,unsignedintlen){inttailAvailSz=0;//尾部剩余空间if(NULL==p){#ifDEBUG_LOOPprintf("ERROR:listisempty\n");#endifreturn-1;}if(NULL==buf){#ifDEBUG_LOOPprintf("ERROR:bufisempty\n");#endifreturn-2;}if(len>=Get_CanWrite(p))//如果剩余的空间不够{#ifDEBUG_LOOPprintf("ERROR:nomemory\n");#endifreturn-3;}if(p->head<=p->tail)//头小于等于尾{tailAvailSz=Get_Capacity(p)-(p->tail-p->buf);//查看尾上面剩余的空间if(len<=tailAvailSz)//个数小于等于尾上面剩余的空间{memcpy(p->tail,buf,len);//拷贝数据到环形数组p->tail+=len;//尾指针加上数据个数if(p->tail==p->buf+Get_Capacity(p))//正好写到最后{p->tail=p->buf;//尾指向数组的首地址}returnlen;//返回写入的数据个数}else{memcpy(p->tail,buf,tailAvailSz);//填入尾上面剩余的空间p->tail=p->buf;//尾指针指向数组首地址//剩余空间剩余数据的首地址剩余数据的个数returntailAvailSz+Write(p,(char*)buf+tailAvailSz,len-tailAvailSz);//接着写剩余的数据}}else//头大于尾{memcpy(p->tail,buf,len);p->tail+=len;returnlen;}}/*********************************************ENDOFFILE********************************************/
1、整体思路
把64K的flash空间分成了4个部分,第一部分是BootLoader,第二部分是程序A(APP1),第三部分是程序B(APP2),第四部分是用来存储一些变量和标记的。下面是空间的分配情况。BootLoader程序可以用来更新程序A,而程序A又更新程序B,程序B可以更新程序A。
最开始的时候想的是程序A、B都带更新了干嘛还多此一举,其实这个Bootloader还是需要的。如果之后程序A、B和FLAG三部分,假设一种情况,在程序B中更新程序A中遇到问题,复位后直接成砖,因为程序A在其实地址,上电直接运行程序A,而程序A现在出问题了,那就没招了。
所以加上BootLoader情况下,不管怎么样BootLoader的程序是不会错的,因为更新不会更新BootLoader,计时更新出错了,还可以进入BootLoader重新更新应用程序。我见也有另外一种设计方法的,就是应用程序只有一个程序A,把程序B区域的flash当作缓存用,重启的时候判断B区域有没有更新程序,有的话就把B拷贝到A,然后擦除B,我感觉这样其实也一样,反正不管怎么样这部分空间是必须要预留出来的。
这里在keil中配置的只有起始地址和大小,并没有结束地址,我这里也就不详细计算了。总体就是这样的。
2、Bootloader部分
BootLoader的任务有两个,一是在串口中断接收BIN的数据和主循环内判断以及更新APP1的程序,二是在在程序开始的时候判断有没有可用的用户程序进而跳转到用户程序(程序A或者程序B)。
简单介绍下执行流程:
系统上电首先肯定是执行BootLoader程序的,因为它的起始地址就是0x08000000,首先是初始化,然后判断按键是否手动升级程序,按键按下了就把FLAG部分的APP标记写成0xFFFF(这里用的宏定义方式),再执行执行App_Check(),否则就直接执行App_Check()。
App_Check函数是来判断程序A和程序B的,最开始BootLoader是用swd方式下载的,下载的时候全片擦除,所以会执行主循环的Update_Check函数。此时串口打印出“等待接收APP1的BIN”,这个时候发送APP1的BIN过去,等接受完了,会写在FLAG区域写个0xAAAA,代表程序A写入了,下次启动可以执行程序A。
主要代码部分
#include"fy_includes.h"/*晶振使用的是16M其他频率在system_stm32f10x.c中修改使用printf需要在fy_includes.h修改串口重定向为#definePRINTF_USARTUSART1*//*Bootloader程序完成三个任务步骤1.检查是否有程序更新,如果有就擦写flash进行更新,如果没有进入步骤2步骤2.判断app1有没有可执行程序,如果有就执行,如果没有进入步骤3步骤3.串口等待接收程序固件*/#defineFLAG_UPDATE_APP10xBBAA#defineFLAG_UPDATE_APP20xAABB#defineFLAG_APP10xAAAA#defineFLAG_APP20xBBBB#defineFLAG_NONE0xFFFF_loopList_slist1;u8rxbuf[1024];u8temp8[2];u16temp16;u32rxlen=0;u32applen=0;u32write_addr;u8overflow=0;u32now_tick=0;u8_cnt_10ms=0;staticvoidApp_Check(void){//获取程序标号STMFLASH_Read(FLASH_PARAM_ADDR,&temp16,1);if(temp16==FLAG_APP1)//执行程序A{if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//可执行?{printf("执行程序A...\r\n");IAP_RunApp(FLASH_APP1_ADDR);}else{printf("程序A不可执行,擦除APP1程序所在空间...\r\n");for(u8i=10;i<35;i++){STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512);}printf("程序A所在空间擦除完成...\r\n");printf("将执行程序B...\r\n");if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)//可执行?{printf("执行程序B...\r\n");IAP_RunApp(FLASH_APP2_ADDR);}else{printf("程序B不可执行,擦除APP2程序所在空间...\r\n");for(u8i=35;i<60;i++){STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512);}printf("程序B所在空间擦除完成...\r\n");}}}if(temp16==FLAG_APP2)//执行程序B{if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)//可执行?{printf("执行程序B...\r\n");IAP_RunApp(FLASH_APP2_ADDR);}else{printf("程序B不可执行,擦除APP2程序所在空间...\r\n");for(u8i=35;i<60;i++){STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512);}printf("程序B所在空间擦除完成...\r\n");printf("将执行程序A...\r\n");if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//可执行?{printf("执行程序A...\r\n");IAP_RunApp(FLASH_APP1_ADDR);}else{printf("程序A不可执行,擦除APP1程序所在空间...\r\n");for(u8i=10;i<35;i++){STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512);}printf("程序A所在空间擦除完成...\r\n");}}}if(temp16==FLAG_NONE){printf("擦除App1程序所在空间...\r\n");for(u8i=10;i<35;i++){STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512);}printf("程序A所在空间擦除完成...\r\n");}}staticvoidUpdate_Check(void){if(_list.Get_CanRead(&list1)>1){_list.Read(&list1,&temp8,2);//读取两个数据temp16=(u16)(temp8[1]<<8)|temp8[0];STMFLASH_Write(write_addr,&temp16,1);write_addr+=2;}if(GetSystick_ms()-now_tick>10)//10ms{now_tick=GetSystick_ms();_cnt_10ms++;if(applen==rxlen&&rxlen)//接收完成{if(overflow){printf("接收溢出,无法更新,请重试\r\n");SoftReset();//软件复位}else{printf("\r\n接收BIN文件完成,长度为%d\r\n",applen);temp16=FLAG_APP1;STMFLASH_Write(FLASH_PARAM_ADDR,&temp16,1);//写入标记temp16=(u16)(applen>>16);STMFLASH_Write(FLASH_PARAM_ADDR+2,&temp16,1);temp16=(u16)(applen);STMFLASH_Write(FLASH_PARAM_ADDR+4,&temp16,1);SoftReset();//软件复位}}elseapplen=rxlen;//更新长度}if(_cnt_10ms>=50){_cnt_10ms=0;Led_Tog();if(!rxlen){printf("等待接收App1的BIN文件\r\n");}}}intmain(void){NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁止JTAG保留SWDSystick_Configuration();Led_Configuration();Key_Configuration();Usart1_Configuration(9600);USART_ITConfig(USART1,USART_IT_IDLE,DISABLE);//关闭串口空闲中断printf("thisisbootloader!\r\n\r\n");if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==SET){Delay_ms(100);if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==SET)//开机按下keyup进行更新{printf("主动更新,");temp16=FLAG_NONE;STMFLASH_Write(FLASH_PARAM_ADDR,&temp16,1);}else{}}App_Check();printf("执行BootLoader程序...\r\n");_list.Create(&list1,rxbuf,sizeof(rxbuf));write_addr=FLASH_APP1_ADDR;while(1){Update_Check();}}//USART1串口中断函数voidUSART1_IRQHandler(void){if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET){u8temp=USART1->DR;if(_list.Write(&list1,&temp,1)<=0){overflow=1;}rxlen++;}}
其中的宏:
//FLASH起始地址#defineSTM32_FLASH_BASE0x08000000//STM32FLASH的起始地址#defineFLASH_APP1_ADDRSTM32_FLASH_BASE+0x2800//偏移10K#defineFLASH_APP2_ADDRSTM32_FLASH_BASE+0x8c00//偏移35K#defineFLASH_PARAM_ADDRSTM32_FLASH_BASE+0xF000//偏移60K
3、程序A和程序B部分
这两个都是用户程序,这两个程序都带有更新程序功能,我这里用作测试的A和B程序大体都差不多,不同的地方就是程序A接收的BIN用来更新程序B,程序B接收的BIN用来更新A,还有就是中断向量表便宜不同以及打印输出不同。
应用程序部分没什么说的,程序A和B很类似,这里贴上A的代码
#include"fy_includes.h"/*晶振使用的是16M其他频率在system_stm32f10x.c中修改使用printf需要在fy_includes.h修改串口重定向为#definePRINTF_USARTUSART1*//*APP1程序完成两个任务1.执行本身的app任务,同时监听程序更新,监听到停止本身的任务进入到状态22.等待接收完成,完成后复位重启*/#defineFLAG_UPDATE_APP10xBBAA#defineFLAG_UPDATE_APP20xAABB#defineFLAG_APP10xAAAA#defineFLAG_APP20xBBBB#defineFLAG_NONE0xFFFF_loopList_slist1;u8rxbuf[1024];u8temp8[2];u16temp16;u32rxlen=0;u32applen=0;u32write_flsh_addr;u8update=0;u8overflow=0;u32now_tick;u8_cnt_10ms=0;staticvoidUpdate_Check(void){if(update)//监听到有更新程序{write_flsh_addr=FLASH_APP2_ADDR;//App1更新App2的程序overflow=0;rxlen=0;_list.Create(&list1,rxbuf,sizeof(rxbuf));printf("擦除APP2程序所在空间...\r\n");for(u8i=35;i<60;i++)//擦除APP2所在空间程序{STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512);}printf("程序B所在空间擦除完成...\r\n");while(1){if(_list.Get_CanRead(&list1)>1){_list.Read(&list1,&temp8,2);//读取两个数据temp16=(u16)(temp8[1]<<8)|temp8[0];STMFLASH_Write(write_flsh_addr,&temp16,1);write_flsh_addr+=2;}if(GetSystick_ms()-now_tick>10)//10ms{now_tick=GetSystick_ms();_cnt_10ms++;if(applen==rxlen&&rxlen)//接收完成{if(overflow){printf("\r\n接收溢出,请重新尝试\r\n");SoftReset();//软件复位}printf("\r\n接收BIN文件完成,长度为%d\r\n",applen);temp16=FLAG_APP2;STMFLASH_Write(FLASH_PARAM_ADDR,&temp16,1);//写入标记temp16=(u16)(applen>>16);STMFLASH_Write(FLASH_PARAM_ADDR+2,&temp16,1);temp16=(u16)(applen);STMFLASH_Write(FLASH_PARAM_ADDR+4,&temp16,1);printf("系统将重启....\r\n");SoftReset();//软件复位}elseapplen=rxlen;//更新长度}if(_cnt_10ms>=50){_cnt_10ms=0;Led_Tog();if(!rxlen){printf("等待接收App2的BIN文件\r\n");}}}//while(1)}}staticvoidApp_Task(void){if(GetSystick_ms()-now_tick>500){now_tick=GetSystick_ms();printf("正在运行APP1\r\n");Led_Tog();}}intmain(void){SCB->VTOR=FLASH_APP1_ADDR;RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁止JTAG保留SWDSystick_Configuration();Led_Configuration();Usart1_Configuration(9600);printf("thisisAPP1!\r\n");Delay_ms(500);while(1){Update_Check();App_Task();}}//USART1串口中断函数voidUSART1_IRQHandler(void){if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET){u8temp=USART1->DR;if(update){if(_list.Write(&list1,&temp,1)<=0){overflow=1;}}else{rxbuf[rxlen]=temp;}rxlen++;}if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET){u8temp=USART1->DR;temp=USART1->SR;if(strstr((char*)rxbuf,"AppUpdate")&&rxlen){update=1;USART_ITConfig(USART1,USART_IT_IDLE,DISABLE);//关闭串口空闲中断}else{Usart1_SendBuf(rxbuf,rxlen);}rxlen=0;}}
这里如果要移植需要注意的就是向量表的偏移以及更新擦写的区域。
4、剩余的4Kflash空间部分
这里其实只是用来存储2个变量,一个是程序运行标记,一个是接收到的程序长度,程序标记还有点把子用,程序长度其实要不要都无所谓。
5、遇到的坑
最值得一说的就是更新部分,最开始程序没有加入擦除flash,遇到的情况就是下载完BootLoader后发送app1没问题,在app1中更新App2也没问题,然后app2再更新app1就出问题了。直观的结果就是循环队列溢出,原因就是app2在更新app1前没有去擦除app1所在的flash,所以在写的时候就要去擦除,这样就写的很慢,然而串口接收是不停的收,所以就是写不过来。
本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。
猜你喜欢:
分享一份嵌入式软件工具清单!
易懂 | 手把手教你编写你的第一个上位机
实用 | 10分钟教你搭建一个嵌入式web服务器
嵌入式常用通信传输协议动图,收藏!
适用于嵌入式的差分升级通用库!
分享一种灵活性很高的协议格式(附代码例子)
分享几个实用的代码片段(第二弹)
在公众号聊天界面回复1024,可获取嵌入式资源;回复m,可查看文章汇总
关键词:
2023-03-28 23:12:46
2023-03-28 21:53:48
2023-03-28 20:21:10
2023-03-28 19:08:53
2023-03-28 17:57:24
2023-03-28 17:13:11
2023-03-28 16:19:44
2023-03-28 15:47:54
2023-03-28 14:21:42
2023-03-28 12:56:36
2023-03-28 11:15:59
2023-03-28 10:30:46
2023-03-28 09:24:25
2023-03-28 05:59:26
2023-03-28 00:53:39
2023-03-27 22:07:29
2023-03-27 20:07:20
2023-03-27 18:55:05
2023-03-27 17:49:37
资讯
品牌