1、固件文件相关定义
(1)ImageA在flash空间的布局
a.ImageA的起始位置为flash的page1。
#define BIM_IMG_A_PAGE 1
b.分配给ImageA的空间大小为62个页。
#define BIM_IMG_A_AREA 62
(2)ImageB在flash空间的布局
a.ImageB的起始位置为flash的page8。
#define BIM_IMG_B_PAGE 8
b.分配给ImageB的空间大小为62个页。
#define BIM_IMG_B_AREA (124 - BIM_IMG_A_AREA)
(3)CRC及固件信息首部在页中的偏移
a.CRC在页中的偏移
#define BIM_CRC_OSET 0x00
b.固件首部在页中的偏移
#define BIM_HDR_OSET 0x00
2.固件信息首部结构定义
typedef struct {
uint16 crc0; /*CRC must not be 0x0000 or 0xFFFF*/
uint16 crc1; /*CRC-shadow must be 0xFFFF.*/
uint16 ver; /*User-defined Image Version Number*/
uint16 len; /*Image length in 4-byte blocks*/
uint8 uid[4]; /*User-defined Image Identification bytes.*/
uint8 res[4]; /*Reserved space for future use.*、
} img_hdr_t;
其中crc0实际上不属于固件信息的首部,但是由于它的位置紧挨着固件首部的crc1,为了代码方便,这才将它包含进来。crc0是独立存在的,由编译器编译时计算得到的。在编译固件ImageA与ImageB工程时,在对应的链接文件cc254x_f256_imgA.xcl与cc254x_f256_imgB.xcl的最后分别有一句:-J2,crc=8005,0804-_BANK7_END和-J2,crc=8005,4004-_BANK4_END,这两句表示在编译这两个固件工程时计算固件的CRC16值,这里的crc0就是用于保存编译器计算得到的crc值。
crc1才是真正属于固件信息首部的crc成员,它是在代码中计算的,通过比较由代码中计算得到的crc1与由编译器得到的crc0,就可以判断当前固件是否有效。
ver用于保存当前固件的版本,在空中升级时,就是判断ver来决定是否要升级。
len用于保存固件的大小,需要注意的是,它以字HAL_FLASH_WORD_SIZE为单位。
uid[4]用来标志固件身份,如对于ImageA,用uid[4]={’A’,’A’,’A’,’A’}来标志ImageA的身份;对于ImageB来说,用uid[4]={’B’,’B’,’B’,’B’}来标志ImageB的身份。
res[4]保留给以后拓展用。
3、定义缓冲数组pgbuf,可以用于缓冲一个页的数据
__no_init uint8 pgBuf[HAL_FLASH_PAGE_SIZE];
4、定义用于保存当前运行固件种类的变量
__no_init __data uint8 JumpToImageAorB @ 0x09;
当JumpToImageAorB = 0时,表示当前正运行ImageA代码。
当JumpToImageAorB = 1时,表示当前正运行ImageB代码。
注意:__no_init __data表示不让编译器布局这个变量位置;而@ 0x09则指定这个变量放到IDATA的0x09地址处。
halSleepExec() 让系统进入睡眠
在这函数之前,有句#pragma optimize=none表示不对这个函数进行优化。要进入睡眠很简单,只要设置电源的供电模式寄存器PCON强让设备进入睡眠模式。
PCON = 0x01;
crcCalc() 计算crc值
参数:
page-要计算的起始页
1、读取起始页的数据,因为这个页包含固件信息的首部。
HalFlashRead(page, 0, pgBuf, HAL_FLASH_PAGE_SIZE);
2、读取固件信息首部
const img_hdr_t *pImgHdr = (const img_hdr_t *)(pgBuf + BIM_HDR_OSET);
3、获取固件的结束位置
(1)如果是ImageB
uint8 pageBeg = page;
uint8 pageEnd = pImgHdr->len / (HAL_FLASH_PAGE_SIZE / HAL_FLASH_WORD_SIZE); /*固件所占的页个数*/
uint16 osetEnd = (pImgHdr->len - (pageEnd * (HAL_FLASH_PAGE_SIZE / HAL_FLASH_WORD_SIZE)))*HAL_FLASH_WORD_SIZE;/*结束位置在最后一页的偏移*/
pageEnd += pageBeg;/*结束位置所在页*/
(2)如果是ImageA,则还需要跳过ImageB的区域。
if (pageBeg == BIM_IMG_A_PAGE)
{
pageEnd += BIM_IMG_B_AREA;
}
4、配置随机数发生器为计算CRC用
ADCCON1 &= 0xF3; /*随机数发生器配置成16为的线性反馈以为计寄存器(LFSR)*/
RNDL = 0x00;
RNDL = 0x00;/*通过连续两次写入RNDL寄存器来产生CRC需要的种子数*/
5、开始计算固件的 CRC值。
(1)读取每个页的值,写入RNDH开始计算
for (uint16 oset = 0; oset < HAL_FLASH_PAGE_SIZE; oset++)
{
...
RNDH = pgBuf[oset];
}
HalFlashRead(page, 0, pgBuf, HAL_FLASH_PAGE_SIZE);
(2)开始计算时,跳过固件首部与CRC相关的4个字节(上面的crc0与crc1)
if ((page == pageBeg) && (oset == BIM_CRC_OSET))
{
oset += 3; // Skip the CRC and shadow.
}
(3)如果到了结束为止在,则计算出最后的CRC16的值
else if ((page == pageEnd) && (oset == osetEnd))
{
uint16 crc = RNDH;
crc = (crc << 8) | RNDL;
return crc;
}
(4)如果在计算ImageA的CRC时,需要跳过中间的ImageB空间,在继续继续计算下去。
if (++page == BIM_IMG_B_PAGE)
{
page += BIM_IMG_B_AREA;
}
注意:对于ImageA,由于它分成了两部分,所以需要特别处理;而ImageB位于ImageA两部分中间的连续空间中,不需要做什么特殊处理。ImageA与ImageB的布局如下:
crcCheck() 检验固件的CRC是否有效
参数:
page-固件的起始页
crc-传递过来的crc值
1、设置DMA通道0,用做烧写flash用
HAL_DMA_SET_ADDR_DESC0(&dmaCh0);
2、计算固件的CRC值,并与编译器得到的CRC值比较,如果相同,则将计算好的CRC值写到flash空间中去,并复位。
if (crc[0] == crcCalc(page))
{
uint16 addr = page * (HAL_FLASH_PAGE_SIZE / HAL_FLASH_WORD_SIZE) +BIM_CRC_OSET / HAL_FLASH_WORD_SIZE;
crc[1] = crc[0];
crc[0] = 0xFFFF;
HalFlashWrite(addr, (uint8 *)crc, 1);
HAL_SYSTEM_RESET();
}
main() 主函数
1、先运行ImageB的代码
(1)获取ImageB的CRC值
HalFlashRead(BIM_IMG_B_PAGE, BIM_CRC_OSET, (uint8 *)crc, 4);
(2)判断ImageB的CRC是否正确
if ((crc[0] != 0xFFFF) && (crc[0] != 0x0000))
if (crc[0] == crc[1])
(3)如果CRC检验正确,则跳转到Image程序入口处执行,并设置标志位。
JumpToImageAorB = 1;
asm("LJMP 0x4030");
HAL_SYSTEM_RESET(); /*不能执行到这句*/
(4)如果ImageB得到CRC未校验,则开始计算CRC值。
else if (crc[1] == 0xFFFF)
{
crcCheck(BIM_IMG_B_PAGE, crc);
}
2、ImageB不存在或无效,再运行ImageA的代码。
(1)获取的ImageA的CRC值
HalFlashRead(BIM_IMG_A_PAGE, BIM_CRC_OSET, (uint8 *)crc, 4);
(2)判断ImageA的CRC值是否有效
if ((crc[0] != 0xFFFF) && (crc[0] != 0x0000))
if (crc[0] == crc[1])
(3)如果CRC检验正确,则跳到ImageA代码入口处执行,并设置标志位。
JumpToImageAorB = 0;
asm("LJMP 0x4030");
HAL_SYSTEM_RESET(); /*不能执行到这句*/
(4)如果ImageA的CRC未检验,则开始计算CRC值。
else if (crc[1] == 0xFFFF)
{
crcCheck(BIM_IMG_A_PAGE, crc);
}
3、如ImageA与ImageB都无效,则进入睡眠,以节省电量。
SLEEPCMD |= 0x03; /*设置供电模式3,PM3*/
halSleepExec();/*进入睡眠*/
HAL_SYSTEM_RESET(); /*不应执行到该处*/
在理解BIM工程代码时,可能会有很多容易理解错误或理解不透的地方。下面给出我理解遇到的两个误区。
1、crcCalc()函数中,在计算ImageA的结束位置时,需要跳过ImageB的空间。如果固件的大小pImgHdr->len小于7个page时,即ImageA代码空间小于原先分配给ImageA-Part1的7个页空间,这时还要跳过ImageB空间(pageEnd += BIM_IMG_B_AREA;),那岂不是要占用ImageB空间了。
其实这种情况是不会发生的,ImageA的大小肯定超过7个page空间,因为Image包含的关于蓝牙协议部分代码就不止7个页大小了,所以这里就直接忽略这种情况,也就没有做任何预防处理了。
2、在crcCheck()中,如果固件的校验值有效,则写入flash空间:
crc[1] = crc[0];
crc[0] = 0xFFFF;
HalFlashWrite(addr, (uint8 *)crc, 1);
这里将crc0 = 0xFFFF烧写进flash空间中,那岂不是要破坏掉原来由编译器计算得到的CRC值了?
举例说,在校验ImageA时,flash的0x0800-0x0801保存着ImageA编译时计算得到的CRC值,0x0802-0x0803保存着由BIM工程调用crcCheck(BIM_IMG_A_PAGE, crc)计算得到的CRC值。在校验前,flash中CRC的值如下:
在调用crcChanck()后,crc在flash的内容变成:ff ff d1 cf。这是种误区,要知道要写flash可以按字为单位写,但是已经写过的空间除非擦除过,否则就不能在重新写入。这里就是这样,因为原先crc0所在的flash位置已经被写如果,所以这里再将0xffff写入不会改变原先的值,所以调用crcChanck()后crc在flash中的值应该是: