大部分MCU都可以通過IAP對片內flash進行讀寫來實作韌體升級,
這里主要是STM32如何實作IAP升級,
不同內核的stm32方式可能略有不同,這里先說F1內核的IAP程序,以STM32F103C8T6為例,
一、片內FLASH讀寫
實作IAP,首先要實作片內FLASH讀寫
1、擦除程式區,呼叫庫函式FLASH_ErasePage可以按頁做擦除
int FlashErase(uint32_t addr)
{
uint8_t retry_time;
uint8_t i;
retry_time = 200;
FLASH_Unlock();
for(i=0; i<55; i++)
{
FLASH_ErasePage((uint32_t)addr);
while((FLASH->SR & FLASH_FLAG_EOP) == 0)
{
delay_ms(1);
retry_time--;
if(retry_time == 0)
{
return 1;
}
}
//標記清零
FLASH->SR |= FLASH_FLAG_EOP;
addr += 0x400;
delay_ms(1);
}
FLASH_Lock();
return 0;
}
2、讀片內FLASH
直接指標讀指定FLASH地址就可以了
#define PARA_START_ADDR1 0x0800f800 //引數首地址
uint8_t *src;
src = (uint8_t *)PARA_START_ADDR1;
printf("flash data = %x\r\n",*(src));
需特別注意,位元組的存盤順序,默認應該是小端的,比如一個2位元組的變數,存入0xAA55,則*(src)取到的是0x55,,*(src+1)取到的才是0xAA
FALSH讀取可以用來檢查寫入的程式是否正確或者用來檢查一些保存的引數
3、寫入片內FLASH
//保存配置引數
void Save_Para(uint32_t addr)
{
uint8_t *s;
uint16_t i,data,len
uint8_t retry_time;
s = (uint8_t *)&System_Para; //要寫入的資料地址
FLASH_Unlock();
FLASH_ErasePage(addr);
retry_time = 200;
while((FLASH->SR & FLASH_FLAG_EOP) == 0)
{
delay_ms(1);
retry_time--;
if(retry_time == 0)
{
break;
}
}
//標記清零
FLASH->SR |= FLASH_FLAG_EOP;
for(i=0; (i+1)<=len; i+=2)
{
data = *(uint16_t *)(s+i);
FLASH_ProgramHalfWord(addr+i,data);
retry_time = 10;
while((FLASH->SR & FLASH_FLAG_EOP) == 0)
{
delay_ms(1);
retry_time--;
if(retry_time == 0)
{
break;
}
}
//標記清零
FLASH->SR |= FLASH_FLAG_EOP;
}
if(i == len)
{
data = 0xff00+*(s+i);//ff00
FLASH_ProgramHalfWord(addr+i,data);
retry_time = 10;
while((FLASH->SR & FLASH_FLAG_EOP) == 0)
{
delay_ms(1);
retry_time--;
if(retry_time == 0)
{
break;
}
}
//標記清零
FLASH->SR |= FLASH_FLAG_EOP;
}
//標記清零
FLASH->SR |= FLASH_FLAG_EOP;
FLASH_Lock();
}
寫入片內FLASH之前一定要先擦除,擦除一次最少擦一頁(page),寫入則可以一次寫word或者halfword(4位元組或者2位元組)
二、程式間跳轉
知道如何讀寫片內FLASH,可以開始實作IAP了
1、首先需要確認程式的地址
STM32一般flash的起始地址是0x08000000
為了實作IAP,我們需要一個啟動程式(boot)和一個主程式(app),所以需要搭建兩個工程,這兩個工程的程式地址是不同的
比如STM32F103C8T6的程式空間為64KB,我們為啟動程式留4KB
則:
啟動程式起始地址為0x08000000,程式空間為0x1000
主程式起始地址為0x08001000,程式空間為0xF000
這個引數在工程的這里設定

有時可以用片外Flash存盤程式端,程式先寫到片外,在升級程序中再讀進來更新片內程式,這樣需要有片外的存盤器,當然也可以用片記憶體儲,
比如F103C8T6,我們把0x08000000到0x08001000這4KB做啟動程式
0x08001000到0x08008000這28KB做主程式
0x08008000至0x0800FFFF這32KB做程式和引數備份區
這樣不用片外存盤器也可以做IAP,只是程式空間會被浪費掉一半,而且STM32片內FLASH讀寫壽命不是很長,
2、啟動程式設定跳轉,啟動程式中需要添加跳轉到主程式的代碼
#define PGM_START_ADDR 0x08001000 //程式起始地址
void Check_And_Jump(void)
{
//跳轉到主程式
/* Test if user code is programmed starting from address "ApplicationAddress" */
if (((*(__IO uint32_t*)PGM_START_ADDR) & 0x2FFE0000 ) == 0x20000000)
{
/* Jump to user application */
JumpAddress = *(__IO uint32_t*) (PGM_START_ADDR + 4);
Jump_To_Main = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) PGM_START_ADDR);
Jump_To_Main();
}
}
3、主程式啟動時設定中斷向量偏移,加在MAIN函式開始的地方就行,偏移量就是啟動程式的長度0x1000
int main(void)
{
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x1000); //中斷向量轉移
這樣分別燒進去啟動程式,主程式,設備上電后,就會先進入啟動程式,隨后跳到主程式中
三、韌體升級
結合片內FLASH讀寫和程式間跳轉,就可以實作片內FALSH升級了
1、首先需要主程式的BIN檔案,需要配置這一項

C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o D:\stm32Work\bin\wifi.bin D:\STM32F1\WifiBoard\OBJ\LED.axf
開始的引數為編譯器位置,然后是你要寫入的位置,最后是工程所在的axf檔案位置,
配置好這一項,對主程式工程點擊rebuild,就可以在前面指定的路徑找到主程式的bin檔案了

2、傳輸BIN檔案
傳輸主程式的BIN檔案可以通過本地傳輸,也可以是網路傳輸,具體看需求,簡單測驗可以考慮用TTL串口來傳輸,可以用通用的一些串口除錯工具以HEX格式直傳檔案,也可以自己寫一個上位機指定一些串口通訊規則,
(1)一般由于RAM有限,一次讀寫的緩沖區最好設小一點,分多次傳完檔案
(2)寫進去之后,最好再讀出來檢查是否正確,因為確實存在寫入失敗的情況
//寫入256位元組
FlashWriteMultiData((pack_index)*256+start_pos, PACK_LEN, (uint8_t *)RS232_RX_BUF+4);
//將寫入的位元組再讀回來,檢查校驗和
FlashReadData((pack_index)*256+start_pos,PACK_LEN,checkBuf);
(3)應當考慮加校驗來驗證單包和整包資料的完整性,因為升級一旦出問題,整個系統就無法恢復了,只能重新燒寫,所以應該盡量小心,避免程式寫入可能存在的錯誤
3、回到啟動程式
主程式通過串口接收資料,寫到片內FALSH后半段后,重啟就可以回到啟動程式,
//保存升級狀態
SystemPara.IAP_Status = 0x0F;
SavePara(PARA_START_ADDR1);
//重啟
delay_ms(500);
NVIC_SystemReset();
4、要有引數保存的機制
因為升級寫完FALSH重啟回到啟動程式時,啟動程式是不知道當前是不是處于升級程序中的,所以我們最少需要1個存在FALSH里的標志位來標識當前行程是正常上電還是升級重啟,最好直接指定FALSH里的一片區域專門做引數存盤的地方,
這樣進入啟動程式時,先判斷當前所處的狀態,再決定是直接跳轉到主程式,還是開始程式刷寫,
以下代碼是直接從工程里取出來的,沒有做修改,只能做做參考,我就不重新寫一遍完整流程了
啟動時,將保存在程式備份區的程式,刷到主程式所在位置:
uint8_t IAP_Write(void)
{
uint16_t j;
uint32_t data;
uint32_t left_len,addr=0;
static uint16_t check_sum = 0;
static uint16_t flash_read_checknum;
left_len = System_Para.Filesize-4;
while(left_len)
{
if(left_len > 256)
{
Flash_Read_Data(addr,256,Prog_data);
addr += 256;
left_len = System_Para.Filesize-4-addr;
for(j=0;j<256;j++)
{
check_sum += Prog_data[j];
}
}
else
{
Flash_Read_Data(addr,left_len,Prog_data);
for(j=0;j<left_len;j++)
{
check_sum += Prog_data[j];
}
left_len = 0;
}
}
flash_read_checknum = Flash_Read_Byte(System_Para.Filesize-2);
flash_read_checknum = (flash_read_checknum<<8) + Flash_Read_Byte(System_Para.Filesize-1);
if(flash_read_checknum == check_sum)
{
//校驗和正確
Flash_Read_Data(0,256,Prog_data);
if(((*(__IO uint32_t*)Prog_data) & 0x2FFE0000 ) != 0x20000000)
{
//程式錯誤,直接回傳主程式
return 10;
}
}
else
{
//校驗和錯誤,直接回傳主程式
return 10;
}
total_data = System_Para.Filesize;
//擦除程式區,失敗就回傳0
if(FlashErase((uint32_t)PGM_START_ADDR)) //清除程式空間
{
//FLASH燒寫擦除程序中失敗,不能直接回傳主程式,因為主程式已經沒有代碼了
return 0;
}
FLASH_Unlock();
addr = 0;
left_len = total_data;
while(left_len)
{
if(left_len > 256)
{
Flash_Read_Data(addr,256,Prog_data);
for(j=0;j<256;j+=4)
{
data = *((uint32_t *)(Prog_data+j));
if(Inner_FLASH_Program(addr+PGM_START_ADDR+j,data) == 0)
{
return 0; //失敗
}
}
addr += 256;
left_len = total_data-addr;
}
else
{
Flash_Read_Data(addr,left_len,Prog_data);
for(j=0;j<left_len;j+=4)
{
data = *((uint32_t *)(Prog_data+j));
if(Inner_FLASH_Program(addr+PGM_START_ADDR+j,data) == 0)
{
return 0;
}
}
left_len = 0;
}
}
FLASH_Lock();
return 10;
}
//片內程式讀取
void Flash_Read_Data(uint32_t data_addr,uint32_t len,uint8_t *addr)
{
uint16_t i;
uint8_t *src;
data_addr += 0x8008000;
src = (uint8_t *)data_addr;
for(i=0;i<len;i++)
{
*(addr+i) = *(src+i);
}
}
void Data_Init(void)
{
uint8_t *dst;
uint8_t *src;
uint16_t i;
uint16_t checksum = 0;
//讀取系統引數
dst = (uint8_t *)&System_Para;
src = (uint8_t *)PARA_START_ADDR1;
if((*(src) == 0x55)&&(*(src+1) == 0xAA)) //引數沒溢位
{
for(i=0;i<sizeof(System_Para);i++)
{
checksum += *(src+i);
*(dst+i) = *(src+i);
}
//校驗正確
if(checksum == (*(src+1023)<<8) + *(src+1022)) //校驗成功
{
if(System_Para.IAP_Status == 0x0F) //FTP下載完成
{
if((System_Para.Filesize>0x100) && (System_Para.Filesize<0x10000))
{
if(IAP_Write() == 0)
{
delay_ms(100);
NVIC_SystemReset();
}
}
System_Para.IAP_Status = 0x00;
Save_Para(PARA_START_ADDR1);
NVIC_SystemReset();
}
}
}
Check_And_Jump();
}
主程式,將升級程式寫到程式備份區域:
void RS232_Rec_Deal(void)
{
uint32_t i,j;
uint8_t pack_type;
uint16_t pack_index;
uint8_t pack_check;
uint8_t checksum;
uint8_t * pos;
uint8_t check_buf[256];
uint8_t *addr;
u8 dx;
uint32_t start_pos = 0;
if(RS232_RX_STA&0x8000) //接收完成
{
if(RS232_RX_BUF[0] == 0xAA) //串口升級
{
start_pos = 0x8008000;
pack_type = RS232_RX_BUF[1]; //幀型別
pack_index = ((uint16_t)RS232_RX_BUF[2]<<8)+RS232_RX_BUF[3]; //幀序號
pack_check = RS232_RX_BUF[4+PACK_LEN]; //幀校驗
//算校驗和
checksum = 0;
for(i=0;i<PACK_LEN+3;i++)
{
checksum += RS232_RX_BUF[1+i];
}
if(pack_type == UPDATE_START) //啟動幀
{
if(checksum == pack_check) //校驗正確
{
Update_Sta.total_pack = ((uint16_t)RS232_RX_BUF[4]<<8)+RS232_RX_BUF[5]; //總包數
Update_Sta.prog_check = ((uint16_t)RS232_RX_BUF[6]<<8)+RS232_RX_BUF[7]; //檔案校驗和
memset(Update_Sta.pack_sta,0,256);
FlashErase(start_pos); //擦除片內
Update_Rep(REP_STA,START_REC);
}
else
{
Update_Rep(REP_STA,DATA_ERR);
}
}
else if(pack_type == UPDATE_DATA) //資料幀
{
if(checksum == pack_check) //校驗正確
{
//先寫入,然后再讀出來,檢查是否正確
Flash_Write_MultiData((pack_index)*256+start_pos, PACK_LEN, (uint8_t *)RS232_RX_BUF+4);
Flash_Read_Data((pack_index)*256+start_pos,PACK_LEN,check_buf);
for(i=0;i<PACK_LEN;i++)
{
if(RS232_RX_BUF[4+i] != check_buf[i])
{
break;
}
}
if(i >= PACK_LEN)
{
Update_Sta.pack_sta[pack_index] = 1;
Update_Rep(REP_REC,DATA_RECOK); //接收成功
if(pack_index == (Update_Sta.total_pack-1)) //最后一包
{
//中間漏包,則判定失敗
for(j=0;j<Update_Sta.total_pack;j++)
{
if(Update_Sta.pack_sta[j] == 0)
{
Update_Rep(REP_STA,DATA_ERR);
RS232_RX_STA = 0;
return;
}
}
for(j=0;j<255;j++)
{
if(check_buf[255-j] != 0)
{
break;
}
}
System_Para.Filesize = Update_Sta.total_pack*PACK_LEN-j;
pos = (uint8_t *)start_pos;
if(( *(pos+System_Para.Filesize-4) == ((TERMINAL_TYPE&0xff00)>>8)) \
&&( *(pos+System_Para.Filesize-3) == (TERMINAL_TYPE&0xff)))
{
Update_Rep(REP_STA,UPDATA_OK);
System_Para.IAP_Status = 0x0F;
Save_Para(PARA_START_ADDR1);
delay_ms(500);
NVIC_SystemReset();
}
else
{
Update_Rep(REP_STA,DATA_ERR);
}
}
}
else
{
Update_Rep(REP_REC,DATA_RECFAIL);
Update_Rep(REP_STA,DATA_ERR);
}
}
else
{
Update_Rep(REP_REC,DATA_RECFAIL);
}
}
}
RS232_RX_STA = 0;
}
}
參照以上流程,就基本可以實作用片內FLASH完成IAP升級了,如果用片外存盤器,方法基本是一樣的,就不多說了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/240886.html
標籤:其他
上一篇:5G + 邊緣計算系列文章
