標(biāo)題:
基于boot+雙app區(qū)間的方案
[打印本頁(yè)]
作者:
moon_001
時(shí)間:
2022-8-22 16:13
標(biāo)題:
基于boot+雙app區(qū)間的方案
# 基于boot+雙app區(qū)間的方案
前言
在普通的項(xiàng)目中,MCU的內(nèi)存稍微大一點(diǎn)的項(xiàng)目,在項(xiàng)目需要留出升級(jí)功能的時(shí)候,都會(huì)選擇boot+app+app緩存區(qū)這種方案,這樣讓MCU內(nèi)存小的情況下也能使用boot+app+app緩存區(qū)(外部Flash)等任何存儲(chǔ)方式,原理簡(jiǎn)單可行也易于移植,但此方案有個(gè)缺點(diǎn)是在正常接收完app固件后還需要復(fù)位到boot中將接收到的緩存固件重新復(fù)制到app區(qū),這使得整個(gè)升級(jí)方案耗時(shí)比較長(zhǎng)時(shí)間,并且上位機(jī)或者手機(jī)app升級(jí)控制臺(tái)不好設(shè)計(jì)(需要為每一個(gè)不同大小的升級(jí)固件設(shè)置一個(gè)倒計(jì)時(shí)時(shí)間),此文將采用boot+雙app區(qū)間方案,不再需要進(jìn)入boot中去復(fù)制緩存固件到app區(qū),并且固件經(jīng)過(guò)更深入的設(shè)計(jì)可以實(shí)現(xiàn)固件秒回滾的功能。
一、分區(qū)
將MCU Flash分區(qū),分成boot區(qū)、appC區(qū)、appD區(qū)三個(gè)區(qū)間,其中boot區(qū)大小為2K左右,本文設(shè)置為2K足夠了(Program Size: Code=1868 RO-data=336 RW-data=24 ZI-data=2000),appC和appD占用剩余的Flash,如果Flash有其他功能使用時(shí)相應(yīng)減除掉,本文采用STM32F103CBT6,容量為128K,除去2Kboot和2K升級(jí)配置參數(shù)剩余124K,預(yù)留4K用做其他,appC/appD分區(qū)60K大小,
二、boot設(shè)計(jì)
1 boot功能負(fù)責(zé)根據(jù)2K升級(jí)配置參數(shù)來(lái)啟動(dòng)相應(yīng)的分區(qū)app,升級(jí)配置參數(shù)結(jié)構(gòu)體包含:
typedef struct _APP_FIRMWARE_{
uint32_t Flag;//標(biāo)志
uint32_t State;//當(dāng)前升級(jí)狀態(tài)
uint32_t Packet;//包數(shù)
uint32_t Version;//版本號(hào)
uint32_t FileSize;//文件大小
uint32_t CheckSumC;//校驗(yàn)和
uint32_t CheckSumD;//校驗(yàn)和
uint8_t BuilTime[4];//文件編譯時(shí)間
uint8_t FileName[32];//文件名稱
uint32_t PartitionNumber;//分區(qū)號(hào)
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;//當(dāng)前運(yùn)行狀態(tài)
uint32_t ConfigAddrBase;//Config首地址
uint32_t ConfigSize;//Config空間大小
}APP_FIRMWARE;
2 其中uint32_t PartitionNumber;//分區(qū)號(hào)即為當(dāng)前運(yùn)行的分區(qū)號(hào),boot只需要讀取Flash內(nèi)容后再判斷此變量即可直接啟動(dòng)分區(qū)app固件。
boot啟動(dòng)函數(shù)如下,在main函數(shù)中調(diào)用即可:
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))
{
//兩個(gè)都運(yùn)行錯(cuò)誤
if((RunState & (0x01 << 2)) && (RunState & (0x01 << 3)))
{
while(1);
}
//A運(yùn)行錯(cuò)誤,調(diào)到B
if(RunState & (0x01 << 0))
{
RunState |= 0x01 << 2;
AppFirmUnion.AppFirmware.PartitionNumber = UPGRADE_NUMBER_C;
}
//B運(yùn)行錯(cuò)誤,調(diào)到A
if(RunState & (0x01 << 1))
{
RunState |= 0x01 << 3;
AppFirmUnion.AppFirmware.PartitionNumber = UPGRADE_NUMBER_D;
}
}
}
}
3 啟動(dòng)分區(qū)app函數(shù)如下:
#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(); //關(guān)閉中斷
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因?yàn)楣δ芎?jiǎn)單,無(wú)需太多代碼,一般占用內(nèi)存會(huì)很少,這使得內(nèi)存使用率極大提高。
4 app設(shè)計(jì)
首先上位機(jī)發(fā)送升級(jí)的一些信息(固件的大小,包數(shù),編譯時(shí)間、兩個(gè)分區(qū)的固件校驗(yàn)碼等,注意此處給的是單個(gè)app的信息,合并app時(shí)候會(huì)說(shuō)明此處)給MCU,MCU收到后回復(fù)請(qǐng)求下一包的命令并帶有當(dāng)前的運(yùn)行區(qū)間信息,此時(shí)上位機(jī)可以根據(jù)請(qǐng)求的包數(shù)和當(dāng)前運(yùn)行區(qū)間的信息讀取前半部分或者后半部分的數(shù)據(jù)(固件), app在需要升級(jí)的時(shí)候?qū)?shù)據(jù)(固件)接收后,先判斷當(dāng)前運(yùn)行的區(qū)間,如果是運(yùn)行在C區(qū)間,則將D區(qū)間擦除,然后不斷的寫入D區(qū)間,當(dāng)寫入數(shù)據(jù)(固件)完成后將接收到的固件校驗(yàn)和和接收數(shù)據(jù)計(jì)算出來(lái)的校驗(yàn)和比對(duì)一樣就激活另一個(gè)區(qū)間,然后軟復(fù)位即可。
......
case PROTOCOL_COMMAND_UPGRADE_START://開(kāi)始升級(jí)
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://寫入數(shù)據(jù)(固件)
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://激活分區(qū)
T1Protocol_Obj.length = 2;
T1Protocol_Obj.data[1] = 1;
UpgradeActivate((UPGRADE_NUMBER)data[2]);
LogE("激活升級(jí)分區(qū):%d\r\n",data[2]);
xTimerStop(xUpgradeTimerUser,0);
vTaskDelay(2000);
Mcu_Misc_Soft_Reset();
break;
......
激活分區(qū)函數(shù):
void UpgradeActivate(UPGRADE_NUMBER partition)
{
AppFirmUnion.AppFirmware.PartitionNumber = partition;
Drive_Memory_WriteDatas(AppFirmUnion.AppFirmware.ConfigAddrBase,(uint32_t *)&AppFirmUnion.UData,sizeof(APP_FIRMWARE_UNION)/4);//===寫回固件狀態(tài)
}
寫入數(shù)據(jù)(固件)函數(shù):
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("[包大于設(shè)定值]\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);
}
}
//計(jì)算校驗(yàn)值
for(uint32_t i = 0;i < length;i ++)
{
CheckSum += dat[i];
}
//計(jì)算包所在的內(nè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;
}
此方案在升級(jí)的過(guò)程中上位機(jī)都是在第一次開(kāi)始升級(jí)作為主動(dòng)方,其他情況都視為MCU作為主動(dòng)方,當(dāng)傳輸數(shù)據(jù)圖中有中斷傳輸流程時(shí),MCU會(huì)不斷的重置定時(shí)器,以便定時(shí)器超時(shí)后會(huì)再次主動(dòng)向上位機(jī)請(qǐng)求當(dāng)前的包數(shù)來(lái)完成重發(fā)機(jī)制。
關(guān)于升級(jí)的文件:此方案需要將appC和appD合并后制作成雙app升級(jí)的bin固件,可以手動(dòng)將appD放在appC的末尾達(dá)到合并的目的或者參考以下附件腳本并如何設(shè)置次腳本可參考“基于KEIL 的合并boot.bin&app.bin的腳本文件”一文中介紹的原理來(lái)實(shí)現(xiàn)自動(dòng)編譯并合并bin文件。
附件:MergeTool.Bat.zip下載后將名稱重命名為MergeTool.Bat即可
注意:此方案的appC和appD不同點(diǎn)只是啟動(dòng)的地址和映射的中斷不一樣,其他的地方都是一模一樣。
總結(jié)
此方案不同于一般升級(jí)方案,不需要再次到boo中復(fù)制文件的等待時(shí)間,并且經(jīng)過(guò)進(jìn)一步的設(shè)計(jì)可以實(shí)現(xiàn)固件回滾,上位機(jī)傳輸完成固件后可以及時(shí)與新的app通訊并獲取到升級(jí)后的信息,給實(shí)際用戶體驗(yàn)到傳輸完成既可查看升級(jí)的結(jié)果。
MergeTool.Bat.zip
2022-8-22 16:08 上傳
點(diǎn)擊文件名下載附件
下載積分: 黑幣 -5
4.38 KB, 下載次數(shù): 22, 下載積分: 黑幣 -5
歡迎光臨 (http://www.torrancerestoration.com/bbs/)
Powered by Discuz! X3.1