IAP很常見了,我這里主要是記錄一下我所使用的方法,調試也花了兩天時間。我所用的型號是STM32F103C8T6,這個片子估計是目前性價比最高的了,所以平時也都是用的這個。這個IC有64KFlash和20K的RAM,也有小道說有后置隱藏的64K,也就是說其實是有128K,我一直也沒有測試,有空測測,有大神這樣說,估計是可以的。這里重點記錄一下我寫的IAP思路和代碼以及細節(jié)和遇到坑的地方。先大體的概述一下,最后貼上我認為重點的代碼。
在概述之前先要解決一個問題,那就是sram空間和flash空間的問題,sram只有20K,flash有64k。
解決的辦法有很多:
1)最常見的就是自己寫上位機軟件,通過分包發(fā)送,期間還可以加入加密算法,校驗等等。
2)使用環(huán)形隊列,簡單點說就是個環(huán)形數(shù)組,一邊接收上位機數(shù)據(jù),一邊往flash里面寫。
這里條件限制就采用第二種方法。所以即使是分給A和B的25K空間的flash空間,sram只有20K也是不能一次接收完所有的bin數(shù)據(jù)的,這里我只開辟了一個1K的BUF,使用尾插法寫入,我的測試應用程序都在5-6K,用這樣的方法可以在9600波特率下測試穩(wěn)定,也試過57600的勉強可以的,115200就不行了。
環(huán)形隊列代碼如下:
C文件:
#include "fy_looplist.h" #include "fy_includes.h" #ifndef NULL #define NULL 0 #endif #ifndef min #define min(a, b) (a)<(b)?(a):(b) //< 獲取最小值 #endif #define DEBUG_LOOP 1 static int Create(_loopList_s* p,unsigned char *buf,unsigned int len); static void Delete(_loopList_s* p); static int Get_Capacity(_loopList_s *p); static int Get_CanRead(_loopList_s *p); static int Get_CanWrite(_loopList_s *p); static int Read(_loopList_s *p, void *buf, unsigned int len); static int Write(_loopList_s *p, const void *buf, unsigned int len); struct _typdef_LoopList _list= { Create, Delete, Get_Capacity, Get_CanRead, Get_CanWrite, Read, Write }; //初始化環(huán)形緩沖區(qū) static int Create(_loopList_s* p,unsigned char *buf,unsigned int len) { if(NULL == p) { #if DEBUG_LOOP printf("ERROR: input list is NULLn"); #endif return 0; } p->capacity = len; p->buf = buf; p->head = p->buf;//頭指向數(shù)組首地址 p->tail = p->buf;//尾指向數(shù)組首地址 return 1; } //刪除一個環(huán)形緩沖區(qū) static void Delete(_loopList_s* p) { if(NULL == p) { #if DEBUG_LOOP printf("ERROR: input list is NULLn"); #endif return; } p->buf = NULL;//地址賦值為空 p->head = NULL;//頭地址為空 p->tail = NULL;//尾地址尾空 p->capacity = 0;//長度為空 } //獲取鏈表的長度 static int Get_Capacity(_loopList_s *p) { if(NULL == p) { #if DEBUG_LOOP printf("ERROR: input list is NULLn"); #endif return -1; } return p->capacity; } //返回能讀的空間 static int Get_CanRead(_loopList_s *p) { if(NULL == p) { #if DEBUG_LOOP printf("ERROR: input list is NULLn"); #endif return -1; } if(p->head == p->tail)//頭與尾相遇 { return 0; } if(p->head < p->tail)//尾大于頭 { return p->tail - p->head; } return Get_Capacity(p) - (p->head - p->tail);//頭大于尾 } //返回能寫入的空間 static int Get_CanWrite(_loopList_s *p) { if(NULL == p) { #if DEBUG_LOOP printf("ERROR: input list is NULLn"); #endif return -1; } return Get_Capacity(p) - Get_CanRead(p);//總的減去已經(jīng)寫入的空間 } // p--要讀的環(huán)形鏈表 // buf--讀出的數(shù)據(jù) // count--讀的個數(shù) static int Read(_loopList_s *p, void *buf, unsigned int len) { int copySz = 0; if(NULL == p) { #if DEBUG_LOOP printf("ERROR: input list is NULLn"); #endif return -1; } if(NULL == buf) { #if DEBUG_LOOP printf("ERROR: input buf is NULLn"); #endif return -2; } if(p->head < p->tail)//尾大于頭 { copySz = min(len, Get_CanRead(p)); //比較能讀的個數(shù) memcpy(buf, p->head, copySz); //讀出數(shù)據(jù) p->head += copySz; //頭指針加上讀取的個數(shù) return copySz; //返回讀取的個數(shù) } else //頭大于等于了尾 { if (len < Get_Capacity(p)-(p->head - p->buf))//讀的個數(shù)小于頭上面的數(shù)據(jù)量 { copySz = len;//讀出的個數(shù) memcpy(buf, p->head, copySz); p->head += copySz; return copySz; } else//讀的個數(shù)大于頭上面的數(shù)據(jù)量 { copySz = Get_Capacity(p) - (p->head - p->buf);//先讀出來頭上面的數(shù)據(jù) memcpy(buf, p->head, copySz); p->head = p->buf;//頭指針指向數(shù)組的首地址 //還要讀的個數(shù) copySz += Read(p,(char*)buf+copySz, len-copySz);//接著讀剩余要讀的個數(shù) return copySz; } } } // p--要寫的環(huán)形鏈表 // buf--寫出的數(shù)據(jù) // len--寫的個數(shù) static int Write(_loopList_s *p, const void *buf, unsigned int len) { int tailAvailSz = 0;//尾部剩余空間 if(NULL == p) { #if DEBUG_LOOP printf("ERROR: list is empty n"); #endif return -1; } if(NULL == buf) { #if DEBUG_LOOP printf("ERROR: buf is empty n"); #endif return -2; } if (len >= Get_CanWrite(p))//如果剩余的空間不夠 { #if DEBUG_LOOP printf("ERROR: no memory n"); #endif return -3; } if (p->head <= p->tail)//頭小于等于尾 { tailAvailSz = Get_Capacity(p) - (p->tail - p->buf); //查看尾上面剩余的空間 if (len <= tailAvailSz)//個數(shù)小于等于尾上面剩余的空間 { memcpy(p->tail, buf, len);//拷貝數(shù)據(jù)到環(huán)形數(shù)組 p->tail += len;//尾指針加上數(shù)據(jù)個數(shù) if (p->tail == p->buf+Get_Capacity(p))//正好寫到最后 { p->tail = p->buf;//尾指向數(shù)組的首地址 } return len;//返回寫入的數(shù)據(jù)個數(shù) } else { memcpy(p->tail, buf, tailAvailSz); //填入尾上面剩余的空間 p->tail = p->buf; //尾指針指向數(shù)組首地址 //剩余空間 剩余數(shù)據(jù)的首地址 剩余數(shù)據(jù)的個數(shù) return tailAvailSz + Write(p, (char*)buf+tailAvailSz, len-tailAvailSz);//接著寫剩余的數(shù)據(jù) } } else //頭大于尾 { memcpy(p->tail, buf, len); p->tail += len; return len; } } /*********************************************END OF FILE********************************************/
頭文件
#ifndef __FY_LOOPLIST_H #define __FY_LOOPLIST_H //環(huán)形緩沖區(qū)數(shù)據(jù)結構 typedef struct { unsigned int capacity; //空間大小 unsigned char *head; //頭 unsigned char *tail; //尾 unsigned char *buf; //數(shù)組的首地址 } _loopList_s; struct _typdef_LoopList { int (*Create) (_loopList_s* p,unsigned char *buf,unsigned int len); void (*Delete)(_loopList_s* p); int (*Get_Capacity)(_loopList_s *p); int (*Get_CanRead)(_loopList_s *p); int (*Get_CanWrite)(_loopList_s *p); int (*Read)(_loopList_s *p, void *buf, unsigned int len); int (*Write)(_loopList_s *p, const void *buf, unsigned int len); }; extern struct _typdef_LoopList _list; #endif
1、整體思路
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現(xiàn)在出問題了,那就沒招了。所以加上BootLoader情況下,不管怎么樣BootLoader的程序是不會錯的,因為更新不會更新BootLoader,計時更新出錯了,還可以進入BootLoader重新更新應用程序。我見也有另外一種設計方法的,就是應用程序只有一個程序A,把程序B區(qū)域的flash當作緩存用,重啟的時候判斷B區(qū)域有沒有更新程序,有的話就把B拷貝到A,然后擦除B,我感覺這樣其實也一樣,反正不管怎么樣這部分空間是必須要預留出來的。
這里在keil中配置的只有起始地址和大小,并沒有結束地址,我這里也就不詳細計算了??傮w就是這樣的。
2、Bootloader部分
BootLoader的任務有兩個,一是在串口中斷接收BIN的數(shù)據(jù)和主循環(huán)內判斷以及更新APP1的程序,二是在在程序開始的時候判斷有沒有可用的用戶程序進而跳轉到用戶程序(程序A或者程序B)。
簡單介紹下執(zhí)行流程:
系統(tǒng)上電首先肯定是執(zhí)行BootLoader程序的,因為它的起始地址就是0x08000000,首先是初始化,然后判斷按鍵是否手動升級程序,按鍵按下了就把FLAG部分的APP標記寫成0xFFFF(這里用的宏定義方式),再執(zhí)行執(zhí)行App_Check(),否則就直接執(zhí)行App_Check()。
App_Check函數(shù)是來判斷程序A和程序B的,最開始BootLoader是用swd方式下載的,下載的時候全片擦除,所以會執(zhí)行主循環(huán)的Update_Check函數(shù)。此時串口打印出“等待接收APP1的BIN”,這個時候發(fā)送APP1的BIN過去,等接受完了,會寫在FLAG區(qū)域寫個0xAAAA,代表程序A寫入了,下次啟動可以執(zhí)行程序A。
主要代碼部分
#include "fy_includes.h" /* 晶振使用的是16M 其他頻率在system_stm32f10x.c中修改 使用printf需要在fy_includes.h修改串口重定向為#define PRINTF_USART USART1 */ /* Bootloader程序 完成三個任務 步驟1.檢查是否有程序更新,如果有就擦寫flash進行更新,如果沒有進入步驟2 步驟2.判斷app1有沒有可執(zhí)行程序,如果有就執(zhí)行,如果沒有進入步驟3 步驟3.串口等待接收程序固件 */ #define FLAG_UPDATE_APP1 0xBBAA #define FLAG_UPDATE_APP2 0xAABB #define FLAG_APP1 0xAAAA #define FLAG_APP2 0xBBBB #define FLAG_NONE 0xFFFF _loopList_s list1; u8 rxbuf[1024]; u8 temp8[2]; u16 temp16; u32 rxlen=0; u32 applen=0; u32 write_addr; u8 overflow=0; u32 now_tick=0; u8 _cnt_10ms=0; static void App_Check(void) { //獲取程序標號 STMFLASH_Read(FLASH_PARAM_ADDR, temp16,1); if(temp16 == FLAG_APP1)//執(zhí)行程序A { if(((*(vu32*)(FLASH_APP1_ADDR+4)) 0xFF000000)==0x08000000)//可執(zhí)行? { printf(" 執(zhí)行程序A...rn"); IAP_RunApp(FLASH_APP1_ADDR); } else { printf(" 程序A不可執(zhí)行,擦除APP1程序所在空間...rn"); for(u8 i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序A所在空間擦除完成... rn"); printf(" 將執(zhí)行程序B... rn"); if(((*(vu32*)(FLASH_APP2_ADDR+4)) 0xFF000000)==0x08000000)//可執(zhí)行? { printf(" 執(zhí)行程序B...rn"); IAP_RunApp(FLASH_APP2_ADDR); } else { printf(" 程序B不可執(zhí)行,擦除APP2程序所在空間...rn"); for(u8 i=35;i<60;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序B所在空間擦除完成...rn"); } } } if(temp16 == FLAG_APP2)//執(zhí)行程序B { if(((*(vu32*)(FLASH_APP2_ADDR+4)) 0xFF000000)==0x08000000)//可執(zhí)行? { printf(" 執(zhí)行程序B...rn"); IAP_RunApp(FLASH_APP2_ADDR); } else { printf(" 程序B不可執(zhí)行,擦除APP2程序所在空間... rn"); for(u8 i=35;i<60;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序B所在空間擦除完成... rn"); printf(" 將執(zhí)行程序A... rn"); if(((*(vu32*)(FLASH_APP1_ADDR+4)) 0xFF000000)==0x08000000)//可執(zhí)行? { printf(" 執(zhí)行程序A...rn"); IAP_RunApp(FLASH_APP1_ADDR); } else { printf(" 程序A不可執(zhí)行,擦除APP1程序所在空間...rn"); for(u8 i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序A所在空間擦除完成...rn"); } } } if(temp16 == FLAG_NONE) { printf(" 擦除App1程序所在空間...rn"); for(u8 i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序A所在空間擦除完成...rn"); } } static void Update_Check(void) { if(_list.Get_CanRead( list1)>1) { _list.Read( list1, temp8,2);//讀取兩個數(shù)據(jù) 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("接收溢出,無法更新,請重試 rn"); SoftReset();//軟件復位 } else { printf(" rn 接收BIN文件完成,長度為 %d rn",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();//軟件復位 } }else applen = rxlen;//更新長度 } if(_cnt_10ms>=50) { _cnt_10ms=0; Led_Tog(); if(!rxlen) { printf(" 等待接收App1的BIN文件 rn"); } } } int main(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(" this is bootloader!rnrn"); 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(" 執(zhí)行BootLoader程序... rn"); _list.Create( list1,rxbuf,sizeof(rxbuf)); write_addr = FLASH_APP1_ADDR; while(1) { Update_Check(); } } //USART1串口中斷函數(shù) void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { u8 temp = USART1->DR; if(_list.Write( list1, temp,1)<=0) { overflow=1; } rxlen++; } }
其中的宏:
//FLASH起始地址 #define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址 #define FLASH_APP1_ADDR STM32_FLASH_BASE+0x2800 //偏移10K #define FLASH_APP2_ADDR STM32_FLASH_BASE+0x8c00 //偏移35K #define FLASH_PARAM_ADDR STM32_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修改串口重定向為#define PRINTF_USART USART1 */ /* APP1程序 完成兩個任務 1.執(zhí)行本身的app任務,同時監(jiān)聽程序更新,監(jiān)聽到停止本身的任務進入到狀態(tài)2 2.等待接收完成,完成后復位重啟 */ #define FLAG_UPDATE_APP1 0xBBAA #define FLAG_UPDATE_APP2 0xAABB #define FLAG_APP1 0xAAAA #define FLAG_APP2 0xBBBB #define FLAG_NONE 0xFFFF _loopList_s list1; u8 rxbuf[1024]; u8 temp8[2]; u16 temp16; u32 rxlen=0; u32 applen=0; u32 write_flsh_addr; u8 update=0; u8 overflow=0; u32 now_tick; u8 _cnt_10ms=0; static void Update_Check(void) { if(update)//監(jiān)聽到有更新程序 { write_flsh_addr = FLASH_APP2_ADDR;//App1更新App2的程序 overflow=0; rxlen=0; _list.Create( list1,rxbuf,sizeof(rxbuf)); printf(" 擦除APP2程序所在空間...rn"); for(u8 i=35;i<60;i++)//擦除APP2所在空間程序 { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序B所在空間擦除完成...rn"); while(1) { if(_list.Get_CanRead( list1)>1) { _list.Read( list1, temp8,2);//讀取兩個數(shù)據(jù) 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(" rn 接收溢出,請重新嘗試 rn"); SoftReset();//軟件復位 } printf(" rn 接收BIN文件完成,長度為 %d rn",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(" 系統(tǒng)將重啟....rn"); SoftReset();//軟件復位 }else applen = rxlen;//更新長度 } if(_cnt_10ms>=50) { _cnt_10ms=0; Led_Tog(); if(!rxlen) { printf(" 等待接收App2的BIN文件 rn"); } } }//while(1) } } static void App_Task(void) { if(GetSystick_ms() - now_tick >500) { now_tick = GetSystick_ms(); printf(" 正在運行APP1 rn"); Led_Tog(); } } int main(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(" this is APP1!rn"); Delay_ms(500); while(1) { Update_Check(); App_Task(); } } //USART1串口中斷函數(shù) void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { u8 temp = 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) { u8 temp = USART1->DR; temp = USART1->SR; if(strstr((char *)rxbuf,"App Update") rxlen) { update=1; USART_ITConfig(USART1, USART_IT_IDLE, DISABLE);//關閉串口空閑中斷 } else { Usart1_SendBuf(rxbuf,rxlen); } rxlen=0; } }
這里如果要移植需要注意的就是向量表的偏移以及更新擦寫的區(qū)域。
4、剩余的4Kflash空間部分
這里其實只是用來存儲2個變量,一個是程序運行標記,一個是接收到的程序長度,程序標記還有點把子用,程序長度其實要不要都無所謂。
5、遇到的坑
最值得一說的就是更新部分,最開始程序沒有加入擦除flash,遇到的情況就是下載完BootLoader后發(fā)送app1沒問題,在app1中更新App2也沒問題,然后app2再更新app1就出問題了。直觀的結果就是循環(huán)隊列溢出,原因就是app2在更新app1前沒有去擦除app1所在的flash,所以在寫的時候就要去擦除,這樣就寫的很慢,然而串口接收是不停的收,所以就是寫不過來。
審核編輯:彭菁
-
STM32
+關注
關注
2264文章
10854瀏覽量
354289 -
串口
+關注
關注
14文章
1540瀏覽量
76059 -
IAP
+關注
關注
2文章
163瀏覽量
24204 -
代碼
+關注
關注
30文章
4722瀏覽量
68229
發(fā)布評論請先 登錄
相關推薦
評論