msp430 程序升级
扫描二维码
随时随地手机看文章
第一篇
在项目开发中,至关重要的是保证产品运行的可靠,如果遇到异常,能否恢复很重要,而不是像砖头一样,程序死在某个地方。固件升级的原理就是重写向量表,在引导区更新app区的flash,然后跳转app区。实际开发中就会有以下问题:
1.如果MCU复位,比如POR,PDR,WDT等复位,都会使sp指针指向复位地址。那么MCU从引导区执行,如果APP区程序有效,应该如何控制程序跳转到APP区。
2.如果APP区或者引导区接受新固件,在更新APP区flash时,如果此时MCU发生掉电,当再次上电后,MCU该如何执行。或许有人说,我们有外部的EEP或者外部的FLASH,会使用状态和标志去记录当时MCU操作flash的状态,当然这些状态和标志有校验,并且存储到外部EEP或FLASH。上电后我们会判断校验,然后读出来作为依据。在理想情况下,这样做非常完美,但是MCU在运行中,什么情况都可能发生。比如电源掉的很快,那么算出来的校验有什么意义,还怎么保证写到EEP或FLASH的可靠性,特别是有外部FLASH,几ma的
电流MCU瞬时根本扛不住。即使是EEP,就算将引导区配置成最低功耗,这种意外也是不可避免的,此时的标志和状态只是徒劳。那么会造成一种MCU假死状态,滞留在引导区,然后死循环。如果要解除,只能通过仿真器进入仿真模式,更改变量值去解除。而这样的后果就违背了升级的初衷和产品的可靠。
3.对于新固件的更新,是接收全部数据再更新还是接收部分数据更新FLASH,这个具体依据自己使用的硬件资源,不过重点还是在于第二点的处理。
4.如果升级过程中,传输数据或读取数据突然中断,或者新的固件验证失败,那么这些操作该如何恢复,而不至于MCU假死。
自己实践中的处理,总结了如下几条:
1.首先我们要明白MCU复位后是要从复位执行,并且MCU中断后,会跳转到实际中断向量地址,也就是向量区重写。在应用区如果有中断发生,MCU会跳转到中断原始地址,通过跳转指令执行位于应用区实际的中断处理函数。例如我使用的是MSP430的FR6972,它的FRAM分配是0x4400-0x13FFF,它的向量区地址在0xFF80-0xFFFF。假如分成两个区,引导区0xF000-0xFFFF,APP区0x7C00-0xEFFF。现在程序执行在0x7C00-0xEFFF的应用区,此时MCU响应了一个中断,假设这个中断函数的入口地址是0xEFF2,按照常理,MCU也应该执行这个地址的内容,实际上,MCU会跳转到这个中断的原始中断向量地址0xFFF2,因为0xEF80-0xEFFF只是我们虚拟的中断向量地址,0xFF80-0xFFFF才是真正的中断向量区。这也是为什么要在引导区重写中断向量,如
#pragmavector=WDT_VECTOR
__interruptvoidWDT_ISR(void)//0xFFF2
{
asm("br&0xEFF2;");
}
执行中断,栈会保存sp等寄存器的内容,执行完后会恢复,继续执行APP区程序。
2.不管是引导区和APP区,MCU的寄存器地址都是固定的,ram的地址也是一样的,但是FLASH是各自独立的,不能重叠。特别注意的是,在引导区和APP区处理全局变量或静态变量时,一定要初始化,或者依据校验从存储器恢复,因为跳转(非中断跳转)会导致这些变量是乱的。
3.要明白编译出的文件格式,知道数据要写到MCU中FLASH的地址。例如MSP430编译出的文件:
@F000
01020304050607
@F008
314000248C00081C3E4017023F400000
B013B4FE8C00001C8D0000F03E400700
……
@FFC6
38F03EF044F04AF050F056F05CF062F0
68F06EF074F07AF080F086F08CF092F0
98F09EF0A4F0AAF0B0F0
@FFF2
B6F0BCF0C2F0C8F0CEF0D4F008F0
q
@后的内容是代码段的地址,是说明段数据要写入的地址,这些地址不需要写入到FLASH中。地址的分配与link文件分配有关。
-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等都是数据段类型描述。
q就是结束标志。
例如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执行的一种文件格式。整个文件以行为单位,每行以冒号开头,内容全部为16进制码。例如”:1000080080318B1E0828092820280B1D0C280D2854”。
第一个字节0x10表示本行数据的长度,“80318B1E0828092820280B1D0C280D28”。
第二,三个字节0x00,0x08表示本行数据的起始地址。
第四个字节0x00表示数据类型,数据类型说明:
'00'DataRrecord:用来记录数据,HEX文件的大部分记录都是数据记录
'01'EndofFileRecord:用来标识文件结束,放在文件的最后,标识HEX文件的结尾
'02'ExtendedSegmentAddressRecord:用来标识扩展段地址的记录
'03'StartSegmentAddressRecord:开始段地址记录
'04'ExtendedLinearAddressRecord:用来标识扩展线性地址的记录
'05'StartLinearAddressRecord:开始线性地址记录
理解了这些文件的内容,我们就知道了向量区需要些的内容,这点很重要。同时我们可以根据自己的通信协议进行扩展,重新转换这些内容,传输到MCU中进行固件升级。
4.原始的中断向量最好与引导区在一个区域,在引导区执行,最好关闭中断响应,通过查询的标志位的方式来处理。引导区的作用就是实现APP区的FLASH更新,中断的跳转。如果将原始向量区分配到APP区,会导致需要很大的外部存储空间接受新的固件,而且程序的设计也会头重脚轻,不建议使用。
5.如何保证固件更新的成功率,和解决擦写FLASH出现的异常,最好的操作就是先擦写APP区的向量区内容,更新完APP区FLASH,最后写复位向量内容。这样可以省去很多的判断流程,即使中途更新失败,或者掉电,MCU上电后也可以继续更新固件,并且出错率很低。像MSP430,烧写FLASH或者仿真下载,如果烧写时选择默认Memmoryoption,那么FLASH默认的值是0xFF。当知道分配的APP区的起始地址后,就判断复位向量值是否为0xFF,如果是则说明APP区没有内容,则不跳转,接受或更新固件。如果不是,一般情况下APP区是有内容的,则执行跳转。
6.在实现固件升级时,最好测试指针类型长度,因为选择的数据模式不同,访问不到0x10000以上的地址。比如MSP430的,如果选择mid,所有的指针类型长度占2byte,这样能访问的最大地址是0xFFFF,只有选择large,才可以访问0x10000以上的地址,但是相应的代码面会增加,因为函数列表的内容就扩大了一倍多,还有其他指针操作。Stm8l区分的就是near和far,就052r8分好几种大小的flash,可以在stm8l15x.h中更改宏。还有就是对内存的操作,需要注意对齐补齐,特别是32位机,像cortex-0内核。比如我经常使用
#defineM8(adr)(*((FARuint8_t*)(adr)))
#defineM16(adr)(*((FARuint16_t*)(adr)))
#defineM32(adr)(*((FARuint32_t*)(adr)))
FAR只是一种修饰。可以省略,也可以使用volitale。
7.合并引导区和APP区文件,然后通过烧程器烧录完整文件。关键就是只保留一个结束标志。
第二篇
以下我会通过实例代码来说明,如果有不足,请大家提出建议。
这是stm8L引导区的main()函数。
voidmain()
{
//禁止中断使能
disableInterrupts();
Clk_Config();
IO_Config();
LCD_Config();
//解锁FLASH操作
FLASH->PUKR=FLASH_RASS_KEY1;
FLASH->PUKR=FLASH_RASS_KEY2;
//从存储区读出数据
FLASH_ByteRead(start_EEPROM1ADDR,(uint8_t*)&ImagePara,sizeof(Image));
//使用CRC校验,返回0没错误
if(TestAdjust((uint8_t*)&ImagePara,sizeof(Image))==0)
jumpflasg=1;
//0x01初始化状态,判断口状态,因为我使用的CPU卡
if((jumpflasg==1)&&(ImagePara.CurState==0x01)&&((GPIOC->IDR&0x02)==0))
{
//擦除APP区向量
FLASHEraseBlock(APPST_BLOCK_NUM,FLASH_MemType_Program);
//擦除整个APP区
FlashErase(APPST_BLOCK_NUM,0xEF80);
}
//如果向量地址有效,跳转引导区
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));
//如果有卡标志
if((GPIOC->IDR&0x02)==0)
{
//如果读卡没错误
if(carderr!=1)
{
Delayms(10);
//读卡第一个块内容,包含我的块个数和校验和
carderr=Read_ImageInfo();
if(carderr!=1)
{
//更新flash,按块操作
while((++ImagePara.Block_dyn)<=ImagePara.Block_static)
{
//读CPU卡内容,更新flash
if(Read_Image(ImagePara.Block_dyn)==FALSE)
{
carderr=1;
break;
}
}
if(carderr!=1)
{
//最后写入向量区
if(Get_Vector()==FALSE)
{
carderr=1;
}
}
if(carderr!=1)
{
//验证块的状态
if(Verify_Image()==FALSE)
{
//校验失败
ImagePara.CurState=0x02;
}
else
{
//校验成功
ImagePara.CurState=0x03;
}
//算校验,更新数据
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引导区main()函数
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;");
}
}
}