Bootloader Design of PIC18 series MCU - 进阶篇

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帧解析处理完毕。程序下载到设备并成功完成跳转.

4.额外的问题

与Bootloader相关的一些其他的技术问题和解决方案再简单的描述一下

  • 485如果没有握手,字节流会不受控地涌入,如果涉及关键字检索,必须考虑字节与字节之间发送的最小时间间隔。因为Bootloader如果不做特殊处理,是无法使用中断的,注意这个。
  • 使用绝对地址定义的变量来在Bootloader和App之间传递信息是最便捷的途径。
  • 我有近乎100%的把握可以激活Bootloader的485中断,然后让这些代码在 bootloader和App之间复用。
  • Map文件是对生成的代码做空间优化的最重要的参考。printf, strcpy,memcpy,%都有更省空间的特化方案可以用。
  • 除了芯片手册。XC8-CC手册是重要的参考文件。

附录A:最终的执行效果

gpBootLoader 0.96
G:goto App | L:Load .hex | ?:Help
CCCCCCCCCC

App(1.0.202307-_4VRef_500nS_sampleTicks) Started.
100us/Sa,SampleRange= 50Hzpress ? for help

上面是正常执行,下面是YModem烧写文件的状态:115200波特率可以达到4KB/sec的上传及烧写速度:

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
Starting ymodem transfer.  Press Ctrl+C to cancel.
Transferring gpbt1810.hex...
   15%      11 KB       2 KB/sec    00:00:22 ETA   0 Errors  

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子正

thanks, bro...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值