msp430 程序升級
第一篇
在項目開發(fā)中,至關(guān)重要的是保證產(chǎn)品運行的可靠,如果遇到異常,能否恢復(fù)很重要,而不是像磚頭一樣,程序死在某個地方。固件升級的原理就是重寫向量表,在引導(dǎo)區(qū)更新app區(qū)的flash,然后跳轉(zhuǎn)app區(qū)。實際開發(fā)中就會有以下問題:
1.如果MCU復(fù)位,比如POR,PDR,WDT等復(fù)位,都會使sp指針指向復(fù)位地址。那么MCU從引導(dǎo)區(qū)執(zhí)行,如果APP區(qū)程序有效,應(yīng)該如何控制程序跳轉(zhuǎn)到APP區(qū)。
2.如果APP區(qū)或者引導(dǎo)區(qū)接受新固件,在更新APP區(qū)flash時,如果此時MCU發(fā)生掉電,當(dāng)再次上電后,MCU該如何執(zhí)行?;蛟S有人說,我們有外部的EEP或者外部的FLASH,會使用狀態(tài)和標(biāo)志去記錄當(dāng)時MCU操作flash的狀態(tài),當(dāng)然這些狀態(tài)和標(biāo)志有校驗,并且存儲到外部EEP或FLASH。上電后我們會判斷校驗,然后讀出來作為依據(jù)。在理想情況下,這樣做非常完美,但是MCU在運行中,什么情況都可能發(fā)生。比如電源掉的很快,那么算出來的校驗有什么意義,還怎么保證寫到EEP或FLASH的可靠性,特別是有外部FLASH,幾ma的
電流MCU瞬時根本扛不住。即使是EEP,就算將引導(dǎo)區(qū)配置成最低功耗,這種意外也是不可避免的,此時的標(biāo)志和狀態(tài)只是徒勞。那么會造成一種MCU假死狀態(tài),滯留在引導(dǎo)區(qū),然后死循環(huán)。如果要解除,只能通過仿真器進入仿真模式,更改變量值去解除。而這樣的后果就違背了升級的初衷和產(chǎn)品的可靠。
3.對于新固件的更新,是接收全部數(shù)據(jù)再更新還是接收部分數(shù)據(jù)更新FLASH,這個具體依據(jù)自己使用的硬件資源,不過重點還是在于第二點的處理。
4.如果升級過程中,傳輸數(shù)據(jù)或讀取數(shù)據(jù)突然中斷,或者新的固件驗證失敗,那么這些操作該如何恢復(fù),而不至于MCU假死。
自己實踐中的處理,總結(jié)了如下幾條:
1.首先我們要明白MCU復(fù)位后是要從復(fù)位執(zhí)行,并且MCU中斷后,會跳轉(zhuǎn)到實際中斷向量地址,也就是向量區(qū)重寫。在應(yīng)用區(qū)如果有中斷發(fā)生,MCU會跳轉(zhuǎn)到中斷原始地址,通過跳轉(zhuǎn)指令執(zhí)行位于應(yīng)用區(qū)實際的中斷處理函數(shù)。例如我使用的是MSP430的FR6972,它的FRAM分配是0x4400-0x13FFF,它的向量區(qū)地址在0xFF80-0xFFFF。假如分成兩個區(qū),引導(dǎo)區(qū)0xF000-0xFFFF,APP區(qū)0x7C00-0xEFFF?,F(xiàn)在程序執(zhí)行在0x7C00-0xEFFF的應(yīng)用區(qū),此時MCU響應(yīng)了一個中斷,假設(shè)這個中斷函數(shù)的入口地址是0xEFF2,按照常理,MCU也應(yīng)該執(zhí)行這個地址的內(nèi)容,實際上,MCU會跳轉(zhuǎn)到這個中斷的原始中斷向量地址0xFFF2,因為0xEF80-0xEFFF只是我們虛擬的中斷向量地址,0xFF80-0xFFFF才是真正的中斷向量區(qū)。這也是為什么要在引導(dǎo)區(qū)重寫中斷向量,如
#pragmavector=WDT_VECTOR
__interruptvoidWDT_ISR(void)//0xFFF2
{
asm("br&0xEFF2;");
}
執(zhí)行中斷,棧會保存sp等寄存器的內(nèi)容,執(zhí)行完后會恢復(fù),繼續(xù)執(zhí)行APP區(qū)程序。
2.不管是引導(dǎo)區(qū)和APP區(qū),MCU的寄存器地址都是固定的,ram的地址也是一樣的,但是FLASH是各自獨立的,不能重疊。特別注意的是,在引導(dǎo)區(qū)和APP區(qū)處理全局變量或靜態(tài)變量時,一定要初始化,或者依據(jù)校驗從存儲器恢復(fù),因為跳轉(zhuǎn)(非中斷跳轉(zhuǎn))會導(dǎo)致這些變量是亂的。
3.要明白編譯出的文件格式,知道數(shù)據(jù)要寫到MCU中FLASH的地址。例如MSP430編譯出的文件:
@F000
01020304050607
@F008
314000248C00081C3E4017023F400000
B013B4FE8C00001C8D0000F03E400700
……
@FFC6
38F03EF044F04AF050F056F05CF062F0
68F06EF074F07AF080F086F08CF092F0
98F09EF0A4F0AAF0B0F0
@FFF2
B6F0BCF0C2F0C8F0CEF0D4F008F0
q
@后的內(nèi)容是代碼段的地址,是說明段數(shù)據(jù)要寫入的地址,這些地址不需要寫入到FLASH中。地址的分配與link文件分配有關(guān)。
-Z(CONST)DATA16_C,DATA16_ID,TLS16_ID,DIFUNCT,CHECKSUM=F000-FF7F
-Z(CONST)DATA20_C,DATA20_ID,CODE_ID=F000-FF7F
-Z(CODE)CSTART,ISR_CODE,CODE16=F000-FF7F
-P(CODE)CODE=F000-FF7F
-Z(CONST)SIGNATURE=FF80-FF8F
-Z(CONST)JTAGSIGNATURE=FF80-FF83
-Z(CONST)BSLSIGNATURE=FF84-FF87
-Z(CONST)IPESIGNATURE=FF88-FF8F
-Z(CODE)INTVEC=FF90-FFFF
-Z(CODE)RESET=FFFE-FFFF
像-z,-p這些都是編譯指令,(data)(const)(code)都是說明修飾,DATA16_C,DATA16_ID等都是數(shù)據(jù)段類型描述。
q就是結(jié)束標(biāo)志。
例如stm8l編譯出的我文件格式:
:108000008200FBA0820166548200FE8D82016655CB//起始地址是0x8000
:108010008200FEA78200FEA8820126B18200F1C77D//起始地址是0x8010
:108020008200FEA98200FEAA820113AA8200F38ABE//起始地址是0x8020
:108030008200F5C68200F6EB8200FEAB8200EFD82C//起始地址是0x8030
:108040008200F5F982014AE38200FEAC8200FEADB7//起始地址是0x8040
:108050008200FEAE820136958200FEAF8201164399//起始地址是0x8050
:108060008200FEB082012E8B8200FEB18200F24BB4//起始地址是0x8060
:108070008200FEB28200FEB38200FEB48200FEB532//起始地址是0x8070
……
:108610008D011DBB3D002608BE042602BE0626E9CC
:0F862000BE042602BE06260435020000B60087FF
:10862F00AE013CBF00905FAE01648D00FC0E725F27
:10863F00016435820165725F016635020167350895
……
我們可以很容易百度hex文件格式說明,Hex文件是可以燒錄到MCU中,被MCU執(zhí)行的一種文件格式。整個文件以行為單位,每行以冒號開頭,內(nèi)容全部為16進制碼。例如”:1000080080318B1E0828092820280B1D0C280D2854”。
第一個字節(jié)0x10表示本行數(shù)據(jù)的長度,“80318B1E0828092820280B1D0C280D28”。
第二,三個字節(jié)0x00,0x08表示本行數(shù)據(jù)的起始地址。
第四個字節(jié)0x00表示數(shù)據(jù)類型,數(shù)據(jù)類型說明:
'00'DataRrecord:用來記錄數(shù)據(jù),HEX文件的大部分記錄都是數(shù)據(jù)記錄
'01'EndofFileRecord:用來標(biāo)識文件結(jié)束,放在文件的最后,標(biāo)識HEX文件的結(jié)尾
'02'ExtendedSegmentAddressRecord:用來標(biāo)識擴展段地址的記錄
'03'StartSegmentAddressRecord:開始段地址記錄
'04'ExtendedLinearAddressRecord:用來標(biāo)識擴展線性地址的記錄
'05'StartLinearAddressRecord:開始線性地址記錄
理解了這些文件的內(nèi)容,我們就知道了向量區(qū)需要些的內(nèi)容,這點很重要。同時我們可以根據(jù)自己的通信協(xié)議進行擴展,重新轉(zhuǎn)換這些內(nèi)容,傳輸?shù)組CU中進行固件升級。
4.原始的中斷向量最好與引導(dǎo)區(qū)在一個區(qū)域,在引導(dǎo)區(qū)執(zhí)行,最好關(guān)閉中斷響應(yīng),通過查詢的標(biāo)志位的方式來處理。引導(dǎo)區(qū)的作用就是實現(xiàn)APP區(qū)的FLASH更新,中斷的跳轉(zhuǎn)。如果將原始向量區(qū)分配到APP區(qū),會導(dǎo)致需要很大的外部存儲空間接受新的固件,而且程序的設(shè)計也會頭重腳輕,不建議使用。
5.如何保證固件更新的成功率,和解決擦寫FLASH出現(xiàn)的異常,最好的操作就是先擦寫APP區(qū)的向量區(qū)內(nèi)容,更新完APP區(qū)FLASH,最后寫復(fù)位向量內(nèi)容。這樣可以省去很多的判斷流程,即使中途更新失敗,或者掉電,MCU上電后也可以繼續(xù)更新固件,并且出錯率很低。像MSP430,燒寫FLASH或者仿真下載,如果燒寫時選擇默認Memmoryoption,那么FLASH默認的值是0xFF。當(dāng)知道分配的APP區(qū)的起始地址后,就判斷復(fù)位向量值是否為0xFF,如果是則說明APP區(qū)沒有內(nèi)容,則不跳轉(zhuǎn),接受或更新固件。如果不是,一般情況下APP區(qū)是有內(nèi)容的,則執(zhí)行跳轉(zhuǎn)。
6.在實現(xiàn)固件升級時,最好測試指針類型長度,因為選擇的數(shù)據(jù)模式不同,訪問不到0x10000以上的地址。比如MSP430的,如果選擇mid,所有的指針類型長度占2byte,這樣能訪問的最大地址是0xFFFF,只有選擇large,才可以訪問0x10000以上的地址,但是相應(yīng)的代碼面會增加,因為函數(shù)列表的內(nèi)容就擴大了一倍多,還有其他指針操作。Stm8l區(qū)分的就是near和far,就052r8分好幾種大小的flash,可以在stm8l15x.h中更改宏。還有就是對內(nèi)存的操作,需要注意對齊補齊,特別是32位機,像cortex-0內(nèi)核。比如我經(jīng)常使用
#defineM8(adr)(*((FARuint8_t*)(adr)))
#defineM16(adr)(*((FARuint16_t*)(adr)))
#defineM32(adr)(*((FARuint32_t*)(adr)))
FAR只是一種修飾??梢允÷裕部梢允褂胿olitale。
7.合并引導(dǎo)區(qū)和APP區(qū)文件,然后通過燒程器燒錄完整文件。關(guān)鍵就是只保留一個結(jié)束標(biāo)志。
第二篇
以下我會通過實例代碼來說明,如果有不足,請大家提出建議。
這是stm8L引導(dǎo)區(qū)的main()函數(shù)。
voidmain()
{
//禁止中斷使能
disableInterrupts();
Clk_Config();
IO_Config();
LCD_Config();
//解鎖FLASH操作
FLASH->PUKR=FLASH_RASS_KEY1;
FLASH->PUKR=FLASH_RASS_KEY2;
//從存儲區(qū)讀出數(shù)據(jù)
FLASH_ByteRead(start_EEPROM1ADDR,(uint8_t*)&ImagePara,sizeof(Image));
//使用CRC校驗,返回0沒錯誤
if(TestAdjust((uint8_t*)&ImagePara,sizeof(Image))==0)
jumpflasg=1;
//0x01初始化狀態(tài),判斷口狀態(tài),因為我使用的CPU卡
if((jumpflasg==1)&&(ImagePara.CurState==0x01)&&((GPIOC->IDR&0x02)==0))
{
//擦除APP區(qū)向量
FLASHEraseBlock(APPST_BLOCK_NUM,FLASH_MemType_Program);
//擦除整個APP區(qū)
FlashErase(APPST_BLOCK_NUM,0xEF80);
}
//如果向量地址有效,跳轉(zhuǎn)引導(dǎo)區(qū)
if(ResetVectorValid()==1)
{
asm("LDWX,SP");
asm("LDA,$FF");
asm("LDXL,A");
asm("LDWSP,X");
asm("JPF$9000");
}
//顯示boot
{
LCD->RAM[3]=0x40;
LCD->RAM[4]=0x10;
LCD->RAM[7]=0xFC;
LCD->RAM[8]=0x01;
LCD->RAM[10]=0xC0;
LCD->RAM[11]=0x3F;
}
while(1)
{
//禁止中斷
disableInterrupts();
//實時判校驗
if(TestAdjust((uint8_t*)&ImagePara,sizeof(Image)))
FLASH_ByteRead(start_EEPROM1ADDR,(uint8_t*)&ImagePara,sizeof(Image));
//如果有卡標(biāo)志
if((GPIOC->IDR&0x02)==0)
{
//如果讀卡沒錯誤
if(carderr!=1)
{
Delayms(10);
//讀卡第一個塊內(nèi)容,包含我的塊個數(shù)和校驗和
carderr=Read_ImageInfo();
if(carderr!=1)
{
//更新flash,按塊操作
while((++ImagePara.Block_dyn)<=ImagePara.Block_static)
{
//讀CPU卡內(nèi)容,更新flash
if(Read_Image(ImagePara.Block_dyn)==FALSE)
{
carderr=1;
break;
}
}
if(carderr!=1)
{
//最后寫入向量區(qū)
if(Get_Vector()==FALSE)
{
carderr=1;
}
}
if(carderr!=1)
{
//驗證塊的狀態(tài)
if(Verify_Image()==FALSE)
{
//校驗失敗
ImagePara.CurState=0x02;
}
else
{
//校驗成功
ImagePara.CurState=0x03;
}
//算校驗,更新數(shù)據(jù)
adjust_write((uint8_t*)&ImagePara,start_EEPROM1ADDR,sizeof(Image));
}
}
}
}
else
{
if(ImagePara.CurState==0x02)
{
memset((uint8_t*)&ImagePara,0,sizeof(Image));
ImagePara.CurState=0x01;
adjust_write((uint8_t*)&ImagePara,start_EEPROM1ADDR,sizeof(Image));
}
carderr=0;
}
if(carderr==1)
LCD->RAM[12]=0x40;
else
LCD->RAM[12]=0x00;
if(ImagePara.CurState==0x03)
{
asm("LDWX,SP");
asm("LDA,$FF");
asm("LDXL,A");
asm("LDWSP,X");
asm("JPF$9000");
}
}
}
這是MSP430引導(dǎo)區(qū)main()函數(shù)
voidmain()
{
WDTCTL=WDTPW|WDTHOLD;
Init_cmu();
Init_uart_Mbus();
Init_uart_HW();
Test_Image();
if(TestAdjust((uint8*)&ImagPara,sizeof(Image))==0)
jumpflag=1;
if((jumpflag==1)&&(ImagPara.curstate==0x04))
Flash_Erase(APP_VECSTARTADDR,APP_VECLEN);
if(ResetVectorValid()==1)
asm("mov&0xEFFE,PC;");
while(1)
{
__disable_interrupt();
Test_Image();
CommPara_Mbus.chbuff=&Buff_MBUS[0];
CommPara_HW.chbuff=&Buff_HW[0];
if(UCA1IFG&UCRXIFG)
{
UCA1IFG&=~UCRXIFG;
StreamHandler_putChar((CommData*)&CommPara_Mbus,UCA1RXBUF);
}
if(UCA0IFG&UCRXIFG)
{
UCA0IFG&=~UCRXIFG;
StreamHandler_putChar((CommData*)&CommPara_HW,UCA0RXBUF);
}
ApplayerHandler();
if(ImagPara.curstate==0x04)
{
Flash_Erase(APP_VECSTARTADDR,APP_VECLEN);
Flash_Erase(APP_CODESTARTADDR,APPSIZE);
EEP_TO_FRAM();
if(Flash_check()==0)
{
ImagPara.curstate=0x05;
}
else
{
ImagPara.curstate=0x06;
}
adjust_write((uint8*)&ImagPara,IMAGE_ADDR,sizeof(Image));
}
if(ImagPara.curstate==0x06)
{
asm("mov&0xEFFE,PC;");
}
}
}