当前位置:首页 > 单片机 > 单片机
[导读] 第一篇在项目开发中,至关重要的是保证产品运行的可靠,如果遇到异常,能否恢复很重要,而不是像砖头一样,程序死在某个地方。固件升级的原理就是重写向量表,在引导区更新app区的flash,然后跳转app区

第一篇


在项目开发中,至关重要的是保证产品运行的可靠,如果遇到异常,能否恢复很重要,而不是像砖头一样,程序死在某个地方。固件升级的原理就是重写向量表,在引导区更新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;");

}

}

}

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

仿真的概念其实使用非常广,最终的含义就是使用可控的手段来模仿真实的情况。在嵌入式系统的设计中,仿真应用的范围主要集中在对程序的仿真上。

关键字: 单片机 仿真器 程序

步进电动机是将电脉冲激励信号转换成相应的角位移或线位移的离散值控制电动机,这种电动机每当输入一个电脉冲就动一步,所以又称脉冲电动机。

关键字: 步进电机 正反转 程序

KeilμVision4是Keil软件公司为8051系列微控制器及其兼容产品设计的集成式软件开发环境。μVision4集成了C51编译器和A51汇编器,其界面类似于Microsoft VS,支持C语言和汇编语言程序的编写...

关键字: 程序 编译 链接

双方各执一词,谁也不退让,吴雄昂的身份成为薛定谔的猫:在Arm公司眼中,他已经被罢免一切职位;在安谋中国声明里,吴仍然一切照常。这种叠加态或许还会持续一段时间,但叠加态应该很快就会塌缩成一个确定结果。同样,安谋中国董事会...

关键字: ARM 中国董事会 程序

除了指令空间,自定义指令对应的程序出入口也有严格限制。自定义指令在使用过程中出现任何错误时,Arm的工具链都能及时对其进行识别、提取,并且进行相应的控制。目前也已经有第三方编译器,可以识别自定义指令集可能会出现的错误。专...

关键字: 指令空间 程序 ARM

如果说一众美国科技公司遵循特朗普命令断供华为,还算某种程度上维护所谓程序正义不得已而为之;那么各路本应对政治保持中立的国际技术标准组织,先后宣布剔除华为成员资格,绝对算助纣为虐;而联邦快递将华为委托寄送目的地为中国的快递...

关键字: 快递 华为 程序

在软件开发过程中,我们希望软件可以运行无误。但是常常事与愿违,程序经常跑飞,或者卡死。原因有很多,有可能是因为软件系统设计的原因,或者外部传感器的失效,再或者是程序的Bug等。为了防止程序在出现问题之后,可以顺利复位和重...

关键字: 软件 程序 传感器

进程是程序的执行过程。程序是静态的,是存在于外存之中的,电脑关机后依然存在。进程是动态的,是存在于内存之中的,是程序的执行过程,电脑关机后就不存在进程了。进程的内容来源于程序,进程的启动过程就是把程序从外存加载到内存的过...

关键字: 程序 静态 操作系统

摘 要:结合车联网高峰论坛上的一些最新观点,对车联网的一些新进展作了介绍。主要包括大数据和云计算在车联网 的应用,车联网的电商化及互联网化趋势。车联网的商业模式需要突破,跨界合作和服务创新是一种有益的尝试。认为只有开放的...

关键字: 车联网 进展 大数据 电商 程序 开放

这几天打算复习下stm32有关的硬件资源,就想着从最开始做起,熟悉下当初所学的知识。学习stm32最初都基本是从流水灯开始的,今天就开始点亮流水灯。

关键字: STM32 程序 编程
关闭
关闭