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-bootloaders https://www.microchip.com/en-us/tools-resources/develop/libraries/microchip-bootloaders
https://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-hex https://www.microchip.com/en-us/education/developer-help/learn-tools-software/mcu-mpu/mplab-ipe/sqtp-file-format-specification/intel-hex结构定义清晰,所以,似乎可以直接在这个基础上做。
https://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帧解析处理完毕。程序下载到设备并成功完成跳转.



















