1.遭遇到问题
在:PIC18 Bootloader 设计基础 一文中,我们讨论了Bootloader与上层应用APP各自编译的方法。在ROM上的空间分配、以及跳转、中断的处理等内容。那篇文章包含了所有与PIC单片机Bootloader设计相关的技术问题。但是距离一个真正可用的Bootloader还有如下问题需要处理:
- PBRQ01>升级文件如何从上位机传送至单片机
- PBRQ02>升级文件本身是否需要处理为一个中间形式
- PBRQ03>指令ROM的烧写如何进行
PIC提供的自定义的Bootloader可以用作设计参考。它的单片机部分的代码开源,还设计了一个上位机软件与之配合。因为考虑跨平台特性,这个软件是基于JRE的一个.jar程序。这种模式,很难适应云升级的模式。相关的链接参见:
https://www.microchip.com/en-us/tools-resources/develop/libraries/microchip-bootloadershttps://www.microchip.com/en-us/tools-resources/develop/libraries/microchip-bootloaders
这个厂商提供的bootloader系统,底层的代码是明的,在MCC中可以看到。现在需要想办法替换掉这个上位机软件。因为我们现在决定使用 Ymodem的语法与设备通讯,我们需要想办法把.hex文件(或者等价物)直接,或者通过其他设备、比如dtu之类的部件,透传到设备。
2.问题应对及编码
2.1升级文件的传输
我们打算使用485串口,然后通过Ymodem协议传输。
相关工作参见:YModem相关知识指引
2.2升级文件的形式
PIC单片机的指令文件,采用了Intel的.hex的格式。这个格式,MicroChip的变体,可以参见:
https://www.microchip.com/en-us/education/developer-help/learn-tools-software/mcu-mpu/mplab-ipe/sqtp-file-format-specification/intel-hexhttps://www.microchip.com/en-us/education/developer-help/learn-tools-software/mcu-mpu/mplab-ipe/sqtp-file-format-specification/intel-hex结构定义清晰,所以,似乎可以直接在这个基础上做。
2.3 烧写
- Ymodem的有效载荷是1024字节。或者128字节。这样会面临Hex本身记录的分帧处理,会涉及到一级缓冲结构;
- Hex文件本身的一笔笔记录并不与PIC单片机指令ROM的烧写规则相适应,需要一级缓冲机构。指令进行程序 rom写入时,必须要64Bytes一次性写入。
- 直接以.hex传入的方法可以,但是需要确保.hex是顺序的。这部分代码我现在统合在一个称为Filter的函数内,下面给出代码:
2.3.1 顶层从485frame拆解出Intel Hex的记录:
这一级只用到了frame_cache缓冲:
//Bootloader App Update过程相关数据结构
#define FLASH_WRITE_BLOCK 64 //指令ROM必须按这个整倍数,对齐到内存整边界写入
#define HEX_MAX_RECORD 16 //HEX文件,单条记录的最大数据载荷长度
#define HEX_RECORD_STR_MAX (1+5*2+0x10*2) //用于流式解析,暂存不完整Hex Recorder的缓冲区长
struct _HexRecorderFilterObj
{
uint16_t targetAddr; //Rom写入地址:需要对齐
uint8_t write_block[FLASH_WRITE_BLOCK]; //注意類型,待写入Flash的缓冲区,bin格式
uint8_t write_block_offset;
uint8_t frame_cache[HEX_RECORD_STR_MAX+1]; //字符格式,Hex recorder缓冲
uint8_t frame_cache_offset;
#ifdef DEBUG_SHOW_HEX_DETAIL
uint16_t debugCnt[4]; /*line, 0cnt, 1cnt, 4cnt*/
uint32_t file_len;
#endif
}hexRecorderFilterObj;
//Bootloader App Update过程主入口
HAL_StatusTypeDef HexRecordFilter(uint8_t *buf, uint32_t len)
{
//:1057F00000000000FFFF0000EE2300007F3E0000DD
do
{
if(hexRecorderFilterObj.frame_cache_offset>0)
{
if(hexRecorderFilterObj.frame_cache_offset>HEX_RECORD_STR_MAX)
{
#ifdef DEBUG_SHOW_HEX_DETAIL
hexRecorderFilterObj.file_len+=hexRecorderFilterObj.frame_cache_offset;
#endif
hexRecorderFilterObj.frame_cache_offset = 0;
}
else if((*buf=='\r') || (*buf == '\n'))
{
//分幀后轉出解碼
if(!Decode_a_hex_frame(hexRecorderFilterObj.frame_cache, hexRecorderFilterObj.frame_cache_offset))
{
//return HAL_ERROR;
}
#ifdef DEBUG_SHOW_HEX_DETAIL
hexRecorderFilterObj.file_len++;
#endif
hexRecorderFilterObj.frame_cache_offset = 0;
}
else hexRecorderFilterObj.frame_cache[hexRecorderFilterObj.frame_cache_offset++] = *buf;
}
else if(*buf == ':')
{
hexRecorderFilterObj.frame_cache[0] = *buf;
hexRecorderFilterObj.frame_cache_offset = 1;
}
else
{
#ifdef DEBUG_SHOW_HEX_DETAIL
hexRecorderFilterObj.file_len++;
#endif
}
buf++;
len--;
}while(len>0);
return HAL_OK;
}
2.3.2 Intel Hex文件单条记录的处理:
//識別到第0幀,繼續向後傳遞
bool Decode_a_hex_frame(uint8_t *frame, uint32_t len)
{
//:1057E000FFFFFF00FFFFFF00FFFFFF00FFFFFF00C5
if(Hex2Byte(frame+1)*2+5*2+1 != len) return false;
#ifdef DEBUG_SHOW_HEX_DETAIL
hexRecorderFilterObj.file_len += len;
hexRecorderFilterObj.debugCnt[0]++;
#endif
switch(Hex2Byte(frame+7))
{
case 0x01:
#ifdef DEBUG_SHOW_HEX_DETAIL
hexRecorderFilterObj.debugCnt[2]++;
#endif
break;
case 0x04:
#ifdef DEBUG_SHOW_HEX_DETAIL
hexRecorderFilterObj.debugCnt[3]++;
#endif
break;
case 0x00:
#ifdef DEBUG_SHOW_HEX_DETAIL
hexRecorderFilterObj.debugCnt[1]++;
#endif
if(!DealHexFrame0(frame, len)) return false;
break;
default:
#ifdef DEBUG_SHOW_HEX_DETAIL
HAL_Delay(1000);
xlog("%d",len);
HAL_Delay(1000);
xlog(frame);
HAL_Delay(1000);
#endif
return false;
}
return true;
}
bool DealHexFrame0(uint8_t *frame, uint32_t len)
{
union
{
uint16_t tgtAddr;
uint8_t b[2];
}addr;
addr.b[1] = Hex2Byte(frame+3);
addr.b[0] = Hex2Byte(frame+5);
uint8_t dataloadLen = Hex2Byte(frame+1);
uint8_t dataloadCur = 0;
do
{
if(hexRecorderFilterObj.targetAddr!=0)
{
if((addr.tgtAddr>=hexRecorderFilterObj.targetAddr)
&& (addr.tgtAddr-hexRecorderFilterObj.targetAddr<=FLASH_WRITE_BLOCK)
&& (hexRecorderFilterObj.write_block_offset<FLASH_WRITE_BLOCK))
{
while((hexRecorderFilterObj.write_block_offset<FLASH_WRITE_BLOCK)&&(dataloadCur<dataloadLen))
{
hexRecorderFilterObj.write_block[hexRecorderFilterObj.write_block_offset++] = Hex2Byte(frame+9+(dataloadCur++)*2);
}
}
else
{
hexRecorderFilterObj.write_block_offset = FLASH_WRITE_BLOCK;
}
}
else
{
hexRecorderFilterObj.targetAddr = addr.tgtAddr + dataloadCur;
hexRecorderFilterObj.write_block_offset = (hexRecorderFilterObj.targetAddr%64);
if(hexRecorderFilterObj.write_block_offset)
{
hexRecorderFilterObj.targetAddr -= hexRecorderFilterObj.write_block_offset;
}
addr.tgtAddr = hexRecorderFilterObj.targetAddr;
continue;
}
if(hexRecorderFilterObj.write_block_offset>=FLASH_WRITE_BLOCK)
{
if(FLASHIF_OK != FLASH_If_Write(hexRecorderFilterObj.targetAddr, hexRecorderFilterObj.write_block, FLASH_WRITE_BLOCK)) return false;
hexRecorderFilterObj.targetAddr = 0;
hexRecorderFilterObj.write_block_offset = 0;
}
}while(dataloadCur<dataloadLen);
return true;
}
注意:到Flash_If_Write的部分,就可以和MCC生成的Memory读写代码做对接了。上面考虑了Hex文件的记录可能不会对齐到特定单片机指令ROM要求的地址边界;但是未考虑Hex记录乱序的问题。
3.结语
这是PIC单片机上Bootloader设计的全部内容。已经测试通过。从着手做,到第一次全部走通,我大概用了45个小时。预期是20个小时。超时225%。后续单机版,代码精简,考虑异常的本地升级的操作估计还有大概10~12个小时的工作量:
下面是时间消耗清单,单位是工时:
[13:00]完成了Bootloader和用户程序的分离,可以由Bootloader调用应用程序。
[18:00]YModem通过485传送单个文件调通。
[20:30]YModem大文件传送无误。
[26:00]YModem多文件传输无误。
[35:00]Hex分帧完毕,尺寸和帧号均与文件可以匹配。
[45:00]Hex帧解析处理完毕。程序下载到设备并成功完成跳转.