|
# 基于boot+雙app區間的方案
前言
在普通的項目中,MCU的內存稍微大一點的項目,在項目需要留出升級功能的時候,都會選擇boot+app+app緩存區這種方案,這樣讓MCU內存小的情況下也能使用boot+app+app緩存區(外部Flash)等任何存儲方式,原理簡單可行也易于移植,但此方案有個缺點是在正常接收完app固件后還需要復位到boot中將接收到的緩存固件重新復制到app區,這使得整個升級方案耗時比較長時間,并且上位機或者手機app升級控制臺不好設計(需要為每一個不同大小的升級固件設置一個倒計時時間),此文將采用boot+雙app區間方案,不再需要進入boot中去復制緩存固件到app區,并且固件經過更深入的設計可以實現固件秒回滾的功能。
一、分區
將MCU Flash分區,分成boot區、appC區、appD區三個區間,其中boot區大小為2K左右,本文設置為2K足夠了(Program Size: Code=1868 RO-data=336 RW-data=24 ZI-data=2000),appC和appD占用剩余的Flash,如果Flash有其他功能使用時相應減除掉,本文采用STM32F103CBT6,容量為128K,除去2Kboot和2K升級配置參數剩余124K,預留4K用做其他,appC/appD分區60K大小,
二、boot設計
1 boot功能負責根據2K升級配置參數來啟動相應的分區app,升級配置參數結構體包含:
typedef struct _APP_FIRMWARE_{
uint32_t Flag;//標志
uint32_t State;//當前升級狀態
uint32_t Packet;//包數
uint32_t Version;//版本號
uint32_t FileSize;//文件大小
uint32_t CheckSumC;//校驗和
uint32_t CheckSumD;//校驗和
uint8_t BuilTime[4];//文件編譯時間
uint8_t FileName[32];//文件名稱
uint32_t PartitionNumber;//分區號
uint32_t FlashAddrBase;//Flash首地址
uint32_t BootAddrBase;//Boot首地址
uint32_t AppAddrBase;//App首地址
uint32_t AppBakAddrBase;//AppBak首地址
uint32_t BootSize;//Boot空間大小
uint32_t AppSize;//App空間大小
uint32_t AppBakSize;//AppBak空間大小
uint32_t RunState;//當前運行狀態
uint32_t ConfigAddrBase;//Config首地址
uint32_t ConfigSize;//Config空間大小
}APP_FIRMWARE;
2 其中uint32_t PartitionNumber;//分區號即為當前運行的分區號,boot只需要讀取Flash內容后再判斷此變量即可直接啟動分區app固件。
boot啟動函數如下,在main函數中調用即可:
void Bootstrap(void)
{
uint32_t appAddress = AppFirmUnion.AppFirmware.AppAddrBase;
uint8_t RunState = 0;
LoadFirmwareParameter();
for(;;)
{
if(AppFirmUnion.AppFirmware.PartitionNumber == UPGRADE_NUMBER_C)
{
appAddress = AppFirmUnion.AppFirmware.AppAddrBase;
RunState |= 0x01 << 0;
}
if(AppFirmUnion.AppFirmware.PartitionNumber == UPGRADE_NUMBER_D)
{
appAddress = AppFirmUnion.AppFirmware.AppBakAddrBase;
RunState |= 0x01 << 1;
}
if(SetupApp(appAddress))
{
//兩個都運行錯誤
if((RunState & (0x01 << 2)) && (RunState & (0x01 << 3)))
{
while(1);
}
//A運行錯誤,調到B
if(RunState & (0x01 << 0))
{
RunState |= 0x01 << 2;
AppFirmUnion.AppFirmware.PartitionNumber = UPGRADE_NUMBER_C;
}
//B運行錯誤,調到A
if(RunState & (0x01 << 1))
{
RunState |= 0x01 << 3;
AppFirmUnion.AppFirmware.PartitionNumber = UPGRADE_NUMBER_D;
}
}
}
}
3 啟動分區app函數如下:
#define __IO volatile
pFunction Jump_To_Application;
uint32_t JumpAddress;
uint8_t SetupApp(uint32_t appAddress)
{
if(((*(__IO uint32_t *)(0x08000000 + appAddress+4))&0xFF000000) == 0x08000000)//判斷是否為0X08XXXXXX.
{
JumpAddress = *(__IO uint32_t*) (0x08000000 + appAddress + 4); //===Jump to user application
Jump_To_Application = (pFunction) JumpAddress;
Mcu_Misc_Set_MSP(*(__IO uint32_t*)(0x08000000 + appAddress)); //===Initialize user application's Stack Pointer
__disable_irq(); //關閉中斷
if (appAddress == CHIP_APP_BAK_FLASH_ADDRESS*1024)
{
Mcu_Misc_SetVTOR(CHIP_APP_BAK_FLASH_ADDRESS*1024);
}
else
{
Mcu_Misc_SetVTOR(CHIP_APP_FLASH_ADDRESS*1024);
}
Jump_To_Application(); //===Jump to application
}
return 1;
}
boot因為功能簡單,無需太多代碼,一般占用內存會很少,這使得內存使用率極大提高。
4 app設計
首先上位機發送升級的一些信息(固件的大小,包數,編譯時間、兩個分區的固件校驗碼等,注意此處給的是單個app的信息,合并app時候會說明此處)給MCU,MCU收到后回復請求下一包的命令并帶有當前的運行區間信息,此時上位機可以根據請求的包數和當前運行區間的信息讀取前半部分或者后半部分的數據(固件), app在需要升級的時候將數據(固件)接收后,先判斷當前運行的區間,如果是運行在C區間,則將D區間擦除,然后不斷的寫入D區間,當寫入數據(固件)完成后將接收到的固件校驗和和接收數據計算出來的校驗和比對一樣就激活另一個區間,然后軟復位即可。
......
case PROTOCOL_COMMAND_UPGRADE_START://開始升級
StartUpgrade(&data[1],&length);
packets = 0;
T1Protocol_Obj.length = 6;
T1Protocol_Obj.data[0] = PROTOCOL_COMMAND_UPGRADE_WRITE;
T1Protocol_Obj.data[1] = packets >> 0;
T1Protocol_Obj.data[2] = packets >> 8;
T1Protocol_Obj.data[3] = packets >> 16;
T1Protocol_Obj.data[4] = packets >> 24;
T1Protocol_Obj.data[5] = AppFirmUnion->AppFirmware.PartitionNumber;
vTaskDelay(200);
break;
case PROTOCOL_COMMAND_UPGRADE_WRITE://寫入數據(固件)
packets = data[4];packets <<= 8;
packets |= data[3];packets <<= 8;
packets |= data[2];packets <<= 8;
packets |= data[1];
LogE("Source packets:%d\r\n",packets);
WriteUpgradeData(packets,&data[5],length-5);
{
packets ++;
T1Protocol_Obj.length = 6;
T1Protocol_Obj.data[1] = packets >> 0;
T1Protocol_Obj.data[2] = packets >> 8;
T1Protocol_Obj.data[3] = packets >> 16;
T1Protocol_Obj.data[4] = packets >> 24;
T1Protocol_Obj.data[5] = AppFirmUnion->AppFirmware.PartitionNumber;
}
Tpackets = packets;
if(xUpgradeTimerUser == NULL)
{
xUpgradeTimerUser = xTimerCreate(
"Timer's name",
2000,
pdTRUE,
(void *)0,
vUpgradeTimerCallback);
}
xTimerStart(xUpgradeTimerUser,0);
ReTpackets = 20;
break;
case PROTOCOL_COMMAND_UPGRADE_ACTIVATE://激活分區
T1Protocol_Obj.length = 2;
T1Protocol_Obj.data[1] = 1;
UpgradeActivate((UPGRADE_NUMBER)data[2]);
LogE("激活升級分區:%d\r\n",data[2]);
xTimerStop(xUpgradeTimerUser,0);
vTaskDelay(2000);
Mcu_Misc_Soft_Reset();
break;
......
激活分區函數:
void UpgradeActivate(UPGRADE_NUMBER partition)
{
AppFirmUnion.AppFirmware.PartitionNumber = partition;
Drive_Memory_WriteDatas(AppFirmUnion.AppFirmware.ConfigAddrBase,(uint32_t *)&AppFirmUnion.UData,sizeof(APP_FIRMWARE_UNION)/4);//===寫回固件狀態
}
寫入數據(固件)函數:
int WriteUpgradeData(uint32_t packets,uint8_t dat[],uint16_t length)
{
static uint32_t Address=0,CheckSum = 0,LastPacket=0;
APP_FIRMWARE_UNION *AppFirmUnion = GetAppFirmUnion();
//LogI("packets:[%d]\r\n",packets);
if(packets == LastPacket && packets != 0)
{
//LogE("[重傳包]\r\n");
return -1;
}
if(packets >= AppFirmUnion->AppFirmware.Packet && packets != 0)
{
//LogE("[包大于設定值]\r\n");
return -2;
}
if(packets == 0)
{
CheckSum = 0;
SetUpDataState(UPDATE_STATE_UPGRADE_UPDATAING);
if(AppFirmUnion->AppFirmware.PartitionNumber == UPGRADE_NUMBER_C)
{
Drive_Memory_ErasePages(AppFirmUnion->AppFirmware.AppBakAddrBase,
AppFirmUnion->AppFirmware.AppBakSize/DRIVE_MEMORY_SECTOR_SIZE);
}
else
{
Drive_Memory_ErasePages(AppFirmUnion->AppFirmware.AppAddrBase,
AppFirmUnion->AppFirmware.AppSize/DRIVE_MEMORY_SECTOR_SIZE);
}
}
//計算校驗值
for(uint32_t i = 0;i < length;i ++)
{
CheckSum += dat[i];
}
//計算包所在的內存地址
if(AppFirmUnion->AppFirmware.PartitionNumber == UPGRADE_NUMBER_C)
{
Address = AppFirmUnion->AppFirmware.AppBakAddrBase + packets * 1024;
}
else
{
Address = AppFirmUnion->AppFirmware.AppAddrBase + packets * 1024;
}
//LogI("Address:[%08x] CheckSum:[%08x]\r\n",Address,CheckSum);
Drive_Memory_WritePage(Address,(uint32_t *)&dat[0],length/4);
if(AppFirmUnion->AppFirmware.PartitionNumber == UPGRADE_NUMBER_C)
{
if((packets == (AppFirmUnion->AppFirmware.Packet - 1)) &&
(AppFirmUnion->AppFirmware.CheckSumD != CheckSum))
{
//LogE("CheckSum Err:%08x %08x\r\n",AppFirmUnion->AppFirmware.CheckSum,CheckSum);
SetUpDataState(UPDATE_STATE_UPGRADE_CHECKSUM_ERROR);
return -3;
}
}
else
{
if((packets == (AppFirmUnion->AppFirmware.Packet - 1)) &&
(AppFirmUnion->AppFirmware.CheckSumC != CheckSum))
{
//LogE("CheckSum Err:%08x %08x\r\n",AppFirmUnion->AppFirmware.CheckSum,CheckSum);
SetUpDataState(UPDATE_STATE_UPGRADE_CHECKSUM_ERROR);
return -3;
}
}
LastPacket = packets;
if(packets == (AppFirmUnion->AppFirmware.Packet - 1))
{
//LogA("Upgrade Complete\r\n");
SetUpDataState(UPDATE_STATE_UPGRADE_OVER);
return 1;
}
return 0;
}
此方案在升級的過程中上位機都是在第一次開始升級作為主動方,其他情況都視為MCU作為主動方,當傳輸數據圖中有中斷傳輸流程時,MCU會不斷的重置定時器,以便定時器超時后會再次主動向上位機請求當前的包數來完成重發機制。
關于升級的文件:此方案需要將appC和appD合并后制作成雙app升級的bin固件,可以手動將appD放在appC的末尾達到合并的目的或者參考以下附件腳本并如何設置次腳本可參考“基于KEIL 的合并boot.bin&app.bin的腳本文件”一文中介紹的原理來實現自動編譯并合并bin文件。
附件:MergeTool.Bat.zip下載后將名稱重命名為MergeTool.Bat即可
注意:此方案的appC和appD不同點只是啟動的地址和映射的中斷不一樣,其他的地方都是一模一樣。
總結
此方案不同于一般升級方案,不需要再次到boo中復制文件的等待時間,并且經過進一步的設計可以實現固件回滾,上位機傳輸完成固件后可以及時與新的app通訊并獲取到升級后的信息,給實際用戶體驗到傳輸完成既可查看升級的結果。
|
-
-
MergeTool.Bat.zip
2022-8-22 16:08 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
4.38 KB, 下載次數: 22, 下載積分: 黑幣 -5
|