
大家好,之前分享過很多軟件版本升級相關的文章:#軟件版本升級
今天分享一篇MCU端基于環形隊列接收升級數據包的實現文章,希望能夠幫助到大家。
以下為原文:
這里重點記錄一下我寫的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"
#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
};
//初始化環形緩沖區
static int Create(_loopList_s* p,unsigned char *buf,unsigned int len)
{
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULL\n");
#endif
return 0;
}
p->capacity = len;
p->buf = buf;
p->head = p->buf;//頭指向數組首地址
p->tail = p->buf;//尾指向數組首地址
return 1;
}
//刪除一個環形緩沖區
static void Delete(_loopList_s* p)
{
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULL\n");
#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 NULL\n");
#endif
return -1;
}
return p->capacity;
}
//返回能讀的空間
static int Get_CanRead(_loopList_s *p)
{
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULL\n");
#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 NULL\n");
#endif
return -1;
}
return Get_Capacity(p) - Get_CanRead(p);//總的減去已經寫入的空間
}
// p--要讀的環形鏈表
// buf--讀出的數據
// count--讀的個數
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 NULL\n");
#endif
return -1;
}
if(NULL == buf)
{
#if DEBUG_LOOP
printf("ERROR: input buf is NULL\n");
#endif
return -2;
}
if(p->head < p->tail)//尾大于頭
{
copySz = min(len, Get_CanRead(p)); //比較能讀的個數
memcpy(buf, p->head, copySz); //讀出數據
p->head += copySz; //頭指針加上讀取的個數
return copySz; //返回讀取的個數
}
else //頭大于等于了尾
{
if (len < Get_Capacity(p)-(p->head - p->buf))//讀的個數小于頭上面的數據量
{
copySz = len;//讀出的個數
memcpy(buf, p->head, copySz);
p->head += copySz;
return copySz;
}
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);//接著讀剩余要讀的個數
return copySz;
}
}
}
// p--要寫的環形鏈表
// buf--寫出的數據
// len--寫的個數
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)//個數小于等于尾上面剩余的空間
{
memcpy(p->tail, buf, len);//拷貝數據到環形數組
p->tail += len;//尾指針加上數據個數
if (p->tail == p->buf+Get_Capacity(p))//正好寫到最后
{
p->tail = p->buf;//尾指向數組的首地址
}
return len;//返回寫入的數據個數
}
else
{
memcpy(p->tail, buf, tailAvailSz); //填入尾上面剩余的空間
p->tail = p->buf; //尾指針指向數組首地址
//剩余空間 剩余數據的首地址 剩余數據的個數
return tailAvailSz + Write(p, (char*)buf+tailAvailSz, len-tailAvailSz);//接著寫剩余的數據
}
}
else //頭大于尾
{
memcpy(p->tail, buf, len);
p->tail += len;
return len;
}
}
/*********************************************END OF FILE********************************************/把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中配置的只有起始地址和大小,并沒有結束地址,我這里也就不詳細計算了。總體就是這樣的。
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修改串口重定向為#define PRINTF_USART USART1
*/
/*
Bootloader程序
完成三個任務
步驟1.檢查是否有程序更新,如果有就擦寫flash進行更新,如果沒有進入步驟2
步驟2.判斷app1有沒有可執行程序,如果有就執行,如果沒有進入步驟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)//執行程序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(u8 i=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(u8 i=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(u8 i=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(u8 i=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(u8 i=10;i<35;i++)
{
STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
}
printf(" 程序A所在空間擦除完成...\r\n");
}
}
static void Update_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();//軟件復位
}
}else applen = rxlen;//更新長度
}
if(_cnt_10ms>=50)
{
_cnt_10ms=0;
Led_Tog();
if(!rxlen)
{
printf(" 等待接收App1的BIN文件 \r\n");
}
}
}
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!\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串口中斷函數
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這兩個都是用戶程序,這兩個程序都帶有更新程序功能,我這里用作測試的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.執行本身的app任務,同時監聽程序更新,監聽到停止本身的任務進入到狀態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)//監聽到有更新程序
{
write_flsh_addr = FLASH_APP2_ADDR;//App1更新App2的程序
overflow=0;
rxlen=0;
_list.Create(&list1,rxbuf,sizeof(rxbuf));
printf(" 擦除APP2程序所在空間...\r\n");
for(u8 i=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();//軟件復位
}else applen = rxlen;//更新長度
}
if(_cnt_10ms>=50)
{
_cnt_10ms=0;
Led_Tog();
if(!rxlen)
{
printf(" 等待接收App2的BIN文件 \r\n");
}
}
}//while(1)
}
}
static void App_Task(void)
{
if(GetSystick_ms() - now_tick >500)
{
now_tick = GetSystick_ms();
printf(" 正在運行APP1 \r\n");
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!\r\n");
Delay_ms(500);
while(1)
{
Update_Check();
App_Task();
}
}
//USART1串口中斷函數
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;
}
}這里如果要移植需要注意的就是向量表的偏移以及更新擦寫的區域。
這里其實只是用來存儲2個變量,一個是程序運行標記,一個是接收到的程序長度,程序標記還有點把子用,程序長度其實要不要都無所謂。
最值得一說的就是更新部分,最開始程序沒有加入擦除flash,遇到的情況就是下載完BootLoader后發送app1沒問題,在app1中更新App2也沒問題,然后app2再更新app1就出問題了。
直觀的結果就是循環隊列溢出,原因就是app2在更新app1前沒有去擦除app1所在的flash,所以在寫的時候就要去擦除,這樣就寫的很慢,然而串口接收是不停的收,所以就是寫不過來。
原文:https://blog.csdn.net/qq997758497/article/details/90211868
文章來源于網絡,版權歸原作者所有,如有侵權,請聯系刪除。

覺得文章不錯,點擊“分享”、“贊”、“推薦” 唄!