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_DETAILuint16_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)
{
//:1057F00000000000FFFF0000EE2300007F3E0000DDdo{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)
{
//:1057E000FFFFFF00FFFFFF00FFFFFF00FFFFFF00C5if(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]++; 
#endifif(!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帧解析处理完毕。程序下载到设备并成功完成跳转.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/17625.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】多组输入

C系列文章目录 目录 C系列文章目录 一、什么是多组输入&#xff1f; 二、如何使用多组输入 2.1&#xff0c;试题举例讲解 2.2&#xff0c;错误解法 2.3&#xff0c;我们实现多组输入的思路 2.4&#xff0c;第一种正确的解法 2.5&#xff0c;第二种正确的解法 2.6&…

结合具体场景举例说明chatgpt预训练模型中Tokenization的原理

假设我们有一个场景&#xff0c;Alice想向Chatbot询问一部电影的推荐。她发送了一条消息&#xff1a;“你好&#xff0c;能给我推荐一部好看的电影吗&#xff1f;” 在这个场景中&#xff0c;Chatbot使用了ChatGPT预训练模型。首先&#xff0c;Chatbot需要对Alice的消息进行Tok…

视频无损放大修复工具:Topaz Video AI对Mac和Windows的系统要求

Topaz Video AI是一款基于人工智能技术的视频增强软件&#xff0c;旨在提供高质量的视频修复、增强和转换功能。它可以通过智能算法和图像处理技术&#xff0c;改善视频的清晰度、稳定性、降噪效果&#xff0c;还能进行视频转码和格式转换。 Mac&#xff1a;Topaz Video AI fo…

性能监控平台 | Prometheus+InfluxDB + Grafana!

在本文中&#xff0c;我将把几个常用的监控部分给梳理一下。前面我们提到过&#xff0c;在性能监控图谱中&#xff0c;有操作系统、应用服务器、中间件、队列、缓存、数据库、网络、前端、负载均衡、Web 服务器、存储、代码等很多需要监控的点。显然这些监控点不能在一个专栏中…

LabVIEW开发图像采集和图像处理程序

LabVIEW开发图像采集和图像处理程序 扫描电子显微镜&#xff08;SEM&#xff09;是一种功能强大的工具&#xff0c;广泛用于高分辨率的生物和半导体样品检测。然而&#xff0c;对于大面积或3D成像&#xff0c;SEM成像是一个耗时的过程。MBSEM旨在通过同时扫描多个像素来减少采…

怎么用JMeter操作MySQL数据库?看完秒懂!

近期用JMeter做接口测试&#xff0c;遇到了一个需要用到数据数据库的场景&#xff1a;一个关于数据报告的页面&#xff0c;需要将数据库里面的数据求和或者取均值之后&#xff0c;展示出来&#xff0c;如果要断言的话&#xff0c;需要连接数据库&#xff0c;通过写sql语句&…

OpenCV读取一张8位无符号单通道图像并显示

#include <iostream> #include <opencv2/imgcodecs.hpp> #include <opencv2/opencv.hpp> #include

Spring—事务及事务的传播机制

Spring—事务及事务的传播机制 &#x1f50e;事务的定义&#x1f50e;Spring—事务的实现铺垫Spring 编程式事务Spring 声明式事务Transactional 的参数注意事项Transactional 的工作原理 &#x1f50e;Spring—事务的隔离级别MySQL—事务的隔离级别Spring—事务的隔离级别Spri…

Prometheus学习

Prometheus学习 promethueus exporter就是以 后台进程的方式运行在系统当中&#xff0c;不断去采集数据 1、红框框中是 prometheus基于数学算法输入的一个查询输入框 2.监控项的分类 3.数据采集的形式分类&#xff08;promethueus exporter就是以 后台进程的方式运行在系统当…

前端vue入门(纯代码)27_路由的query参数

安静地努力&#xff01;&#xff01;&#xff01; 【25.Vue Router--路由的query参数】 多级路由在src/router/index.js中【三级路由】的配置如下&#xff1a; // 该文件专门用于创建整个应用的路由器 import VueRouter from "vue-router"; //引入组件 import Abo…

springboot家政服务管理平台

本系统为了数据库结构的灵活性所以打算采用MySQL来设计数据库&#xff0c;而java技术&#xff0c;B/S架构则保证了较高的平台适应性。本文主要介绍了本系统的开发背景&#xff0c;所要完成的功能和开发的过程&#xff0c;主要说明了系统设计的重点、设计思想。 本系统主要是设…

Docker学习笔记23

Docker Swarm架构&#xff1a; Swarm中以集群&#xff08;Cluster&#xff09;为单位进行管理&#xff0c;支持服务层面的操作。 集群是Swarm所管理的对象。 基本概念&#xff1a; 节点&#xff08;Node&#xff09;为Swarm集群中的一个Docker Engine实例。其中管理节点&#…