我這里主要是記錄一下我所使用的方法,調試也花了兩天時間。 我所用的型號是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_LOOP1 staticintCreate(_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_LOOP printf("ERROR:inputlistisNULL "); #endif return0; } p->capacity=len; p->buf=buf; p->head=p->buf;//頭指向數組首地址 p->tail=p->buf;//尾指向數組首地址 return1; } //刪除一個環形緩沖區 staticvoidDelete(_loopList_s*p) { if(NULL==p) { #ifDEBUG_LOOP printf("ERROR:inputlistisNULL "); #endif return; } p->buf=NULL;//地址賦值為空 p->head=NULL;//頭地址為空 p->tail=NULL;//尾地址尾空 p->capacity=0;//長度為空 } //獲取鏈表的長度 staticintGet_Capacity(_loopList_s*p) { if(NULL==p) { #ifDEBUG_LOOP printf("ERROR:inputlistisNULL "); #endif return-1; } returnp->capacity; } //返回能讀的空間 staticintGet_CanRead(_loopList_s*p) { if(NULL==p) { #ifDEBUG_LOOP printf("ERROR:inputlistisNULL "); #endif return-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_LOOP printf("ERROR:inputlistisNULL "); #endif return-1; } returnGet_Capacity(p)-Get_CanRead(p);//總的減去已經寫入的空間 } //p--要讀的環形鏈表 //buf--讀出的數據 //count--讀的個數 staticintRead(_loopList_s*p,void*buf,unsignedintlen) { intcopySz=0; if(NULL==p) { #ifDEBUG_LOOP printf("ERROR:inputlistisNULL "); #endif return-1; } if(NULL==buf) { #ifDEBUG_LOOP printf("ERROR:inputbufisNULL "); #endif return-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_LOOP printf("ERROR:listisempty "); #endif return-1; } if(NULL==buf) { #ifDEBUG_LOOP printf("ERROR:bufisempty "); #endif return-2; } if(len>=Get_CanWrite(p))//如果剩余的空間不夠 { #ifDEBUG_LOOP printf("ERROR:nomemory "); #endif return-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... "); IAP_RunApp(FLASH_APP1_ADDR); } else { printf("程序A不可執行,擦除APP1程序所在空間... "); for(u8i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512); } printf("程序A所在空間擦除完成... "); printf("將執行程序B... "); if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)//可執行? { printf("執行程序B... "); IAP_RunApp(FLASH_APP2_ADDR); } else { printf("程序B不可執行,擦除APP2程序所在空間... "); for(u8i=35;i<60;i++) { STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512); } printf("程序B所在空間擦除完成... "); } } } if(temp16==FLAG_APP2)//執行程序B { if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)//可執行? { printf("執行程序B... "); IAP_RunApp(FLASH_APP2_ADDR); } else { printf("程序B不可執行,擦除APP2程序所在空間... "); for(u8i=35;i<60;i++) { STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512); } printf("程序B所在空間擦除完成... "); printf("將執行程序A... "); if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//可執行? { printf("執行程序A... "); IAP_RunApp(FLASH_APP1_ADDR); } else { printf("程序A不可執行,擦除APP1程序所在空間... "); for(u8i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512); } printf("程序A所在空間擦除完成... "); } } } if(temp16==FLAG_NONE) { printf("擦除App1程序所在空間... "); for(u8i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512); } printf("程序A所在空間擦除完成... "); } } 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("接收溢出,無法更新,請重試 "); SoftReset();//軟件復位 } else { printf(" 接收BIN文件完成,長度為%d ",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文件 "); } } } intmain(void) { NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//開啟AFIO時鐘 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁止JTAG保留SWD Systick_Configuration(); Led_Configuration(); Key_Configuration(); Usart1_Configuration(9600); USART_ITConfig(USART1,USART_IT_IDLE,DISABLE);//關閉串口空閑中斷 printf("thisisbootloader! "); 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程序... "); _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任務,同時監聽程序更新,監聽到停止本身的任務進入到狀態2 2.等待接收完成,完成后復位重啟 */ #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程序所在空間... "); for(u8i=35;i<60;i++)//擦除APP2所在空間程序 { STMFLASH_Erase(FLASH_BASE+i*STM_SECTOR_SIZE,512); } printf("程序B所在空間擦除完成... "); 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(" 接收溢出,請重新嘗試 "); SoftReset();//軟件復位 } printf(" 接收BIN文件完成,長度為%d ",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("系統將重啟.... "); SoftReset();//軟件復位 }elseapplen=rxlen;//更新長度 } if(_cnt_10ms>=50) { _cnt_10ms=0; Led_Tog(); if(!rxlen) { printf("等待接收App2的BIN文件 "); } } }//while(1) } } staticvoidApp_Task(void) { if(GetSystick_ms()-now_tick>500) { now_tick=GetSystick_ms(); printf("正在運行APP1 "); Led_Tog(); } } intmain(void) { SCB->VTOR=FLASH_APP1_ADDR; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//開啟AFIO時鐘 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁止JTAG保留SWD Systick_Configuration(); Led_Configuration(); Usart1_Configuration(9600); printf("thisisAPP1! "); 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,所以在寫的時候就要去擦除,這樣就寫的很慢,然而串口接收是不停的收,所以就是寫不過來。
審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。
舉報投訴
-
FlaSh
+關注
關注
10文章
1622瀏覽量
147761 -
調試
+關注
關注
7文章
572瀏覽量
33899 -
串口
+關注
關注
14文章
1543瀏覽量
76207 -
IAP
+關注
關注
2文章
163瀏覽量
24251 -
bootloader
+關注
關注
2文章
234瀏覽量
45550
原文標題:基于串口環形隊列的IAP實現!
文章出處:【微信號:技術讓夢想更偉大,微信公眾號:技術讓夢想更偉大】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
環形隊列在串口數據接收中的使用
前言??書接上回,前文主要介紹了環形隊列的實現原理以及C語言實現及測試過程,本文將回歸到嵌入式平臺的應用中,話不多說,淦,上干貨!實驗目的HAL庫下
發表于 12-06 06:27
STM32進階之串口環形緩沖區實現
碼代碼的應該學數據結構都學過隊列。環形隊列是隊列的一種特殊形式,應用挺廣泛的。因為有太多文章關于這方面的內容,理論知識可以看別人的,下面寫得挺好的:STM32進階之
發表于 12-06 10:00
?2938次閱讀
嵌入式環形隊列與消息隊列的實現原理
嵌入式環形隊列,也稱為環形緩沖區或循環隊列,是一種先進先出(FIFO)的數據結構,用于在固定大小的存儲區域中高效地存儲和訪問數據。其主要特點包括固定大小的數組和兩個指針(頭指針和尾指針
評論