本站为个人非经营性网站,仅用于技术学习与分享,不提供任何商品、服务、报价或收费咨询。 合规信息

工业压力变送器:标定链路与双总线升级

这是我做过的信号链最完整的一个个人实验:24 位压力芯体 → 归一化 → 量程映射 → 零点 → 两点校准 → 可选温补 → 4–20 mA 输出,同时挂 RS485 与 CAN 两条总线, 再配一个能通过 RS485 整包升级固件的串口 Bootloader。 这篇记录写清楚它做了什么、每一段是怎么实现的,也如实写出它还缺什么—— 尤其是升级可靠性那一块,我认为它离“能放心用在现场”还有明确距离。

压力变送器 GD32F425RE RS485 CAN Bootloader / OTA 已发布
已发布 · 个人学习项目

工业压力变送器(GD32F425RE + RS485/CAN Bootloader)

这个项目做的是把压力芯体的读数变成“可信的、能上总线的工程量”,并且让固件能通过总线升级。 采集侧用 24 位数字压力芯体,标定分两层:一层是零点加两点增益偏移的线性标定, 一层是 21 个系数的二维多项式温度补偿;输出侧准备了 4–20 mA 电流环的 PWM 链路。 通信侧同时挂了 RS485(自定义命令集)与 CAN,并配一个 64 KB 的串口 Bootloader, 支持用一条 4 字节命令 01 67 41 CA 触发升级。 结论是:功能链路都跑通了,采样、标定、双总线上报、OTA 升级都能工作; 但升级的完整性校验只有三重很弱的检查,没有 CRC、没有版本比对、也没有回滚, 这部分我在最后一节按条列出来,不做美化。

PLATFORMGD32F425RE / Cortex-M4F / 512KB Flash / 192KB+64KB RAM
STACK裸机 + 自定义 Modbus 命令集 + CAN
TOOLSKeil MDK + 串口/CAN 调试工具
STATUS个人学习项目 · 功能已实现

一、这个变送器要做什么

需求可以一句话说清:把压力测出来,按工业现场习惯的方式送出去,并且以后能远程改固件。 但拆开之后是三条互相牵扯的链路,每一条都有自己的坑。

  • 采集与标定链路:从 24 位压力芯体读出码值,换算成压力工程量, 再用现场标定把偏差压到可用范围。这条链路决定“数据准不准”。
  • 双总线上报链路:RS485 上跑一套自定义命令集(读实时压力/温度、读参数、 写参数、置零、改设备地址等),CAN 上跑另一套自定义协议(设备信息、状态查询、 周期上报、心跳、置零、参数读写)。这条链路决定“数据怎么出去、参数怎么改”。
  • 升级链路:一个独立的 Bootloader 工程,通过 RS485 接收整包固件写入应用区并跳转。 这条链路决定“现场能不能不拆机改固件”。

约束也很具体:这是要长期通电、挂在工业现场总线上的设备, RS485 跑的是很常见的 9600 bps 8N1,CAN 跑 250 kbps; 标定参数必须掉电不丢;升级过程最怕的不是慢,而是“升到一半失败之后设备不起来了”。 后面第七节会看到,最后这条恰恰是这个项目最薄弱的地方。

顺带说明一下项目的定位:这是我个人用来学习完整信号链与 Bootloader 的练习, 不是产品交付代码。我把“哪些跑通了、哪些还没做”分开写,也是为了自己后面回看时不会被早期的乐观误导。

二、硬件与信号链:24 位 ADC 与压力芯体

主控是 GD32F425RE,Cortex-M4F 内核,外部 12 MHz 晶振,512 KB Flash, 192 KB SRAM 加 64 KB 的紧耦合内存。整个工程没有用 RTOS: 四个定时器中断只负责置事件标志,主循环按标志顺序处理, 这种“事件标志轮询”的写法在资源够用、实时性要求不苛刻的场合比上 RTOS 更省心。 两个工程的编译选项也不一样:Bootloader 用 -O0(要保证擦写与跳转逻辑不被优化打乱), 应用用 -O1。

外设分配上有一个我觉得很值得保留的习惯:调试串口跑在 921600, 高波特率换来的是打印日志几乎不占采样时间。 相比之下 RS485 是 9600 8N1(方向脚单独一根 GPIO),CAN 是 250 kbps, 压力芯体走 I²C 400 kHz。板级上用宏在多款压力芯体之间切换, 当前启用的是数字输出的那一款——也就是说,压力值是通过 I²C 读寄存器拿到的, 而不是 MCU 自己采模拟量。

芯体侧的寄存器我是这样用的:0x02 是状态、0x06 是压力数据、 0x09 是温度数据、0x30 是命令、0xA5 是系统配置, 另外 0xD1 是满量程 DPH、0xD4 是零点量程 DPL。 读数据之前要先等就绪位,判据是“状态字节的某一位为 0、或者整字节为 0xFF 就继续等”, 最多重试 2000 次。

这里有一个我自己踩过的认知坑:驱动文件名和它实际操作的器件不是一回事。 工程里那个驱动文件的名字看起来是某款外置 ADC 的型号, 但里面读的寄存器(0x02 / 0x06 / 0xD1 / 0xD4) 全是数字压力芯体的。我一开始按文件名去查数据手册,查了半天对不上, 后来才发现是同一套板卡支持多个型号、驱动被复用时改了内容没改名字。 从那以后我看代码的习惯是先看它操作哪些寄存器,再看它叫什么。

采样节拍也值得记一笔:TIMER1 在定时器时钟 200 MHz 下预分频 200-1, 计数周期 500000-1,也就是 500 ms 采一次。 而同一份板级配置头文件里,注释写的是 100 ms——注释和实算差了 5 倍,以实算为准。 每次读到采集相关的代码,我都会把预分频和周期重新算一遍,这一步花不了两分钟, 但能避免“以为 100 ms、实际 500 ms”这类判断错误。

信号链环节输入处理输出
读取芯体 0x06 的 3 个字节等就绪位(最多重试 2000 次)后读出3 字节原始数据
拼装3 字节原始数据字节反转后拼成 24 位整数0 ~ 16777215
归一化24 位整数除以 223(8388608)-1.0 ~ +1.0
量程映射归一化值用芯体 DPH/DPL 把 10%~100% 段映射到量程压力工程量
零点压力工程量减去现场置零值去零点后的压力
两点校准去零点后的压力乘增益、加偏移校准后压力
温补(可选)温度 + 压力21 系数二维多项式(当前开关关闭)补偿后压力
输出补偿后压力分段线性换算成定时器比较值4 ~ 20 mA 对应占空比

温度这一路顺带说一下:0x09 读回 3 字节同样是 24 位有符号, 换算关系是“除以 65536 再加 25”,也就是读回 0 对应 25 ℃。 温度补偿能做到什么程度,我在 《压力传感器的标定与温度补偿:从线性拟合到二维多项式》 里单独写了一篇,包括它的 21 个系数是怎么来的、以及它能修什么、修不了什么。

三、标定链路全貌

把第二节的链路纵向拉直,就是标定的全貌: 原始值 → 归一化 → 量程 → 零点 → 两点校准 → 温补 → 输出。 我在调试时最大的收获不是某一层的算法,而是“每一层分别由谁负责、参数从哪来”这件事要说清楚, 否则现场出问题时根本不知道该改哪一层。

这条链路的分层写法本身属于核心实现,所以这里不给代码,改成一张分层表: 每一层干什么、输入输出是什么量纲、有没有需要标定的系数,都在表里说清楚。 分层的目的很实际——出问题时能一眼看出是哪一层的参数不对。

层级作用输入 → 输出(量纲)是否有系数
归一化与量程映射 把芯体吐出的整数变成有物理意义的量 24 位码值(整数)→ 压力工程量 用芯体公开资料给出的标度与量程寄存器,不是标定出来的
现场置零 把当前工况当作零点,修固定零点偏移 压力工程量 → 去零点后的压力工程量(同量纲) 有:一个零点量,由现场置零命令写入
两点校准 同时修零点与灵敏度误差 压力工程量 → 校准后压力工程量(同量纲) 有:增益与偏移,实测标定得到的系数(不公开)
温度补偿(可选) 修零点与灵敏度随温度的漂移 温度 + 压力工程量 → 补偿后压力工程量 有:多维多项式系数,离线拟合得到(不公开)
输出换算 把工程量变成后级能用的驱动量 压力工程量 → 定时器比较值(计数量纲) 有:分段线性的锚点,跟着后级硬件实测标定(不公开)

这条链路里有一部分是“每次上电都要重新获取”的,不能存进参数区: 芯体的量程 DPH/DPL 就是这一类。代码里的做法是上电时从芯体寄存器读回, 读失败就退回一个缺省量程。但文件里另一处硬编码的初值给的是另一组数—— 两个缺省量程并不一致,这意味着“读不到芯体”这件事会安静地把量程换成另一个数。 这两组缺省值本身属于产品参数,本文不列具体数字。 我认为这是这条链路里最该先修的一处,因为它不报错、只让数据变小。

另一部分(零点、增益、偏移)必须掉电不丢,所以整块参数结构体放在 Flash 的 0x08040000 一个扇区里:写是“擦扇区 + 整块写回”,读是整块读出。 这个做法简单直接,但结构体里没有魔数、没有版本号、没有 CRC, 而且 Bootloader 与 App 各有一份结构体定义、App 侧还多两个字段,却共用同一个地址—— 同一块 Flash 被两种布局解读,字段会整体错位。 关于参数区该怎么加固,我写在 《Flash 参数存储:结构体直存的问题与双备份做法》 里,这里只留一张“链路里哪几段真的在跑”的现状表,因为这是我调试时最需要先确认的信息。

链路环节当前状态依据备注
读取与归一化启用采样节拍 500 ms 触发状态就绪判据 + 最多 2000 次重试
量程映射(DPH/DPL)启用上电从芯体读回读失败退缺省值,与硬编码初值不一致
现场置零启用自定义命令集中的置零命令写入参数区,掉电保留
两点校准启用校验命令写入增益/偏移参数掉电保留
温度补偿关闭总开关宏为 0函数在、系数在,但整条被旁路
去极值平均滤波已禁用源码里标注禁用滤波策略转到校准系数域
4–20 mA 输出未执行校准系数尚未标定(全为零值,等于未启用)初始化函数被条件判断挡住
RS485 与 CAN启用主循环每轮处理参数读写与上报都可用
四层叠加:芯体量程映射、零点校准、两点增益偏移、温度补偿,每层标注输入输出与是否有系数
图 1 · 标定分层图

四、双总线:RS485 自定义命令集与 CAN

RS485 侧我要先说清楚一件事:它不是标准的 Modbus 从站。 帧结构是“设备地址 + 命令码 + CRC 低字节 + CRC 高字节”这 4 个字节起步, 帧识别的方式是在 400 字节接收缓冲里搜索第一个 0xFA 或本机地址作为帧起始, 之后的字节按命令码分派。也就是说,它是“广播地址 0xFA + 本机地址”的双寻址变体, 而不是标准 Modbus 那套功能码体系。超时清缓冲由 100 ms 的定时器事件负责。

这个设计有它的好处(一条命令可以同时发给所有设备),也有代价: 帧头是“搜索”出来的,如果数据区里恰好出现 0xFA,就可能被误判成新帧的起点。 所以这类协议必须把长度、CRC 覆盖范围、以及数据区里出现帧头字节怎么办这几件事写进文档, 否则换一个人对接就会出问题。

命令码(十进制)作用备注
3读型号信息最早用来确认对面是什么设备
30 / 31读 / 写系数系数按地址索引,与参数区结构体对应
32 / 33读 / 写配置设备地址、串口参数等
48初始化设备恢复一批默认参数
66写设备地址改地址后需要重新对齐上位机配置
69 / 104读 / 写 SN序列号
73 / 74读压力与温度浮点与整数两种返回格式
95置零把当前值写进零点参数
100 / 101读 / 写输出配置按二级分派选择要改的项
102校验(写两点校准系数)增益与偏移
103请求进入升级模式就是下一节的 OTA 命令

CRC 这一块我踩了个很典型的坑:代码里两个变量一个叫 crc_h、一个叫 crc_l,但实际赋值是 “取模 256 的那个赋给了 h”——也就是变量名和内容相反, 低字节被装进了名字里带 h 的变量。发送顺序也顺着这个反转来。 自己跟自己收发是自洽的(因为两端用的是同一套代码), 一对接第三方上位机就暴露了。这类问题的教训很直接: 命名错了比逻辑错了更难查,因为逻辑至少还能用标准报文验证。

半双工方向切换也是这类设备的经典坑。代码里的做法是:发完所有字节之后 delay_1ms(10),然后才把方向脚切回接收。 原因是 9600 bps 下一个字节约 1.04 ms,必须等移位寄存器真正排空,否则最后一个字节会被削掉。 这个 10 ms 是硬编码的,而串口参数里波特率是可配的—— 也就是说,改了波特率就得同步改这个延时,否则表现为“偶发丢最后一字节”, 而且换线换设备都无效。我在 《Modbus RTU 从站实现笔记:寄存器表、CRC 与异常码》 里记过同源的判断:现象是通信问题,根因往往是时序问题。

CAN 侧是另一套自定义协议:设备信息、状态查询、读内部压力、CAN 置零、读状态、 压力信息、写参数、读参数这一组命令;报文 ID 按型号用条件编译分组, 每个功能都有一对 ID 与对应的应答 ID(例如心跳用的一个 ID 是 0x09048000, 其余 ID 这里不逐一列出)。上报和心跳分别挂在两个定时器事件上: 一个定时器事件触发周期数据上报,另一个触发心跳。 有一个细节要注意:Bootloader 与 App 的 CAN 默认波特率不一样 (App 默认 250 kbps,Boot 默认 1000 kbps),升级前后如果用的是 CAN 工具观察, 要记得切过来,否则会误判成“设备没起来”。

对比项RS485 通道CAN 通道
物理层与速率UART3,9600 8N1,单独方向脚CAN0,250 kbps
协议自定义命令集(广播地址 + 本机地址双寻址)自定义命令集,按型号分组报文 ID
主要用途读实时值、读写参数、置零、触发升级周期上报、心跳、置零、参数读写
触发方式主循环轮询解析;100 ms 定时器清超时缓冲两个定时器事件分别触发上报与心跳
最容易出问题的地方CRC 字节序、方向切换延时升级前后波特率默认值不同
主控居中,一侧接压力芯体,另一侧分出两条总线:RS485 与 CAN,下方是模拟量输出通道
图 2 · 双总线结构图

五、Bootloader:分区、OTA 命令与升级流程

Bootloader 是一个独立工程,占 Flash 最前面的 64 KB,应用区从 0x08010000 开始。 分区是这样划的:

区域起始地址大小说明
Bootloader0x0800000064 KB结束地址宏为 0x0800FFFF
应用区 APP10x08010000100 KBApp 里把中断向量表偏移硬编码成与之一致
备份区 APP20x08029000100 KB地址已定义,但这条路径没有使用
参数区0x080400001 个扇区整个参数结构体整块存放

触发升级的命令是一个只有 4 个字节的短帧,字段构成我逐个对过, 整理成下面这张表。它属于协议触发方式,与标定参数无关, 所以字段表保留;但代码里我用占位符代替真实取值,不把可直接使用的帧交出去。

字节值含义
00x01设备地址
10x67命令码 103(十进制),即“请求进入升级模式”
20x41CRC 低字节
30xCACRC 高字节

这里最值得记的是 CRC 的位置约定。校验用的是标准 CRC-16/MODBUS (初值 0xFFFF、反射多项式 0xA001,都是公开标准), 而且只覆盖前两个字节(地址 + 命令码)。 我按公开标准自己复算过一遍:算出来的低字节先发、高字节后发, 与帧字段表里的字节完全吻合;顺序反过来就永远对不上, 而且自己跟自己收发还是自洽的,只有对接第三方才会暴露。 这也解释了为什么一条 4 字节的命令就能触发升级——它不携带任何参数, 只是个“请进 Boot”的信号,真正的固件在后面单独传。

/* 4 字节触发升级:地址 + 命令码 + CRC16(低字节在前)。地址与命令码用占位符表示,不给具体值。 */
uint8_t  frame[4];
uint16_t crc;
frame[0] = DEVICE_ADDR;                 /* 占位符:本机设备地址 */
frame[1] = CMD_ENTER_BOOT;              /* 占位符:请求进入升级模式 */
crc      = crc16_modbus(frame, 2);      /* CRC 只覆盖前两个字节 */
frame[2] = (uint8_t)(crc & 0xFF);       /* 低字节在前 */
frame[3] = (uint8_t)(crc >> 8);         /* 高字节在后 */

收到这条命令之后,设备侧的动作顺序是固定的四步: 回一个同格式的 4 字节应答 → 把升级标志置 1 → 擦写参数区保存这个标志 → 软复位。 复位后 Bootloader 读到标志为 1,于是打印提示、擦除应用区、等待新镜像。 这个顺序不能换:先回应答是为了让上位机确认“命令收到了、我要重启了”; 先写标志再复位是为了防止“复位了但标志没落盘”。 升级标志是三态的:0 表示正常启动应用,1 表示进入升级模式, 而擦除后的空片值是 0xFF,会被归一化成 0——这是为了首次烧录时不要误进升级流程。

接收与落盘的过程是这样的:RS485 每收到一个字节就放进一个 64 KB 的静态缓冲, 同时复位并重启超时定时器;串口的空闲中断用来判定“这一帧结束了”, 并记录本次收到的字节数。落盘之前有三重检查:镜像长度必须大于 20 KB、 最后两个字节必须是 0(拿它当“固件结束标志”)、 写完之后再检查应用区第 4 个字节(复位向量的高字节)是否落在 Flash 区。 三项都过了,才把升级标志清 0、回写参数区、然后跳转。

跳转本身有两道标准保护,我按理解重写成一段最小片段。 第一道看向量表第 0 项(栈顶)是不是落在 SRAM 区间,第二道看第 1 项(复位入口) 是不是落在 Flash 区。这两道检查的目的是避免“跳进一片空 Flash”之后直接 HardFault。

/* 跳转前的两道检查(按个人理解重写的最小片段;真实工程用内联汇编改 MSP 再跳转):
   两道分别判"栈顶是否落在 SRAM 区间""复位入口是否落在 Flash 区",掩码按区间的地址范围取。 */
int app_is_valid(uint32_t app_addr)
{
    uint32_t sp = *(volatile uint32_t *)app_addr;         /* 第 0 项:栈顶 */
    uint32_t pc = *(volatile uint32_t *)(app_addr + 4);   /* 第 1 项:复位入口 */
    if (!in_sram_range(sp))  return 0;   /* 栈顶必须落在 SRAM 区间 */
    if (!in_flash_range(pc)) return 0;   /* 复位入口必须在 Flash 区 */
    return 1;
}

顺便提一句应用区侧的对齐问题:App 里把中断向量表偏移写成了与 APP1 地址一致的常量。 这本身没错,但它是硬编码的——如果哪天调整了 Bootloader 的结束地址, 而忘了同步改这一处,结果不是“少一个功能”,而是所有中断全部跑飞。 我在 《Bootloader 跳转与 OTA:分区规划、向量表偏移与容易踩的坑》 里专门写过这类“两处常量必须一起改”的坑,这次算是又见了一次。

六、问题与解决

下面这些是调试过程中真实卡住过我的问题,我按“现象 → 原因 → 办法”整理。 我把现象写得尽量具体,因为回头查问题时,能对上现象比记住原因更有用。

现象原因办法
设备自己收发完全正常,一接第三方上位机就“CRC 校验失败” CRC 的两个变量名与实际内容相反(带 h 的装的是低字节),发送顺序也跟着反了 按标准约定重新对齐:低字节先发,接收侧以固定偏移取低/高字节;把变量名改成与内容一致
偶发丢最后一个字节,换线、换设备、换波特率都无效 发完立刻把方向脚切回接收,收发器的移位寄存器还没排空(9600 bps 下 1 字节约 1.04 ms) 发完固定延时 10 ms 再切回接收;同时把这个延时与波特率绑定,改波特率必须同步改
跳转到应用后立刻 HardFault,连打印都没有 应用区是空的或被擦了一半,向量表取出来的栈顶/入口是非法值 跳转前查两道:栈顶必须落在 SRAM 区间、复位入口必须落在 Flash 区;不满足就留在 Boot 等新镜像
采样响应比预期慢很多 定时器注释写的是 100 ms,按预分频 200-1 与周期 500000-1 实算其实是 500 ms 以实算为准,把注释改对;每写一次定时器参数就回算一遍周期
读不到芯体量程时,输出整段被压扁 读失败退回的缺省量程与源码里硬编码的初值不一致 两处缺省值统一,并且在参数区里记一个“量程来源”标志,供上位机判断
改完参数后设备表现异常,复位也恢复不了 参数区只有一个裸结构体:擦写过程中掉电会留下全 0xFF,浮点变成异常值,读回后不做校验 加魔数、版本、CRC 与默认值回退;每个系数再做一次合理区间检查
CAN 工具在升级过程中“看不到设备” Bootloader 与 App 的 CAN 默认波特率不同 升级前后切换工具波特率,并把两边的默认值在文档里写清楚

七、可靠性缺口清单

这一节我按源码事实写,不做美化。升级链路的完整校验在这版代码里只有三重很弱的检查: 镜像长度大于 20 KB、末尾两个字节为 0、复位向量合法。 除此之外没有 CRC 或哈希校验、没有版本号比对、没有 A/B 回滚; 而升级流程是“先把应用区擦掉,再等新镜像”,所以一旦中间失败, 应用区已经是空的,设备只能靠 Bootloader 停在那里等重发——这是明确的变砖风险。

检查项现状风险我会怎么加固
镜像完整性 只有“长度 > 20 KB + 末尾两字节为 0” 传输误码、上位机发错文件、镜像被截断都无法识别,不合格的镜像只打印日志后被丢弃 镜像头部带长度与 CRC32(或哈希),Boot 写入前先校验整包
版本比对 无 无法防止降级或重复升级同一版本,也无法在升级后确认设备里是哪一版 镜像头带版本号,Boot 与参数区版本比对,并支持“读版本”命令
回滚能力 无;备份区地址已定义但未使用 新镜像校验失败或首次启动异常时没有退路,应用区已被擦除,属明确变砖风险 A/B 双镜像 + 递增序号 + 启动失败的自动回退
升级标志的掉电语义 单副本放在参数区,擦写期间掉电会丢 标志丢失后设备可能直接去启动一个不完整的应用 标志三副本存放,或改成“请求—确认”两阶段写入
接收缓冲边界 64 KB 静态缓冲,写入时没有上界检查 上位机多发字节会越界写,破坏相邻内存;同时它也占了主 SRAM 的约三分之一 加上界检查,改成“分包接收 + 边收边写 Flash”,不再整包驻留内存
错误分支的退出 镜像不合法或应用区为空时,打印错误后仍持续喂看门狗 设备会永久停在错误循环里,不会自动复位,也没有进入等待升级状态 错误分支停止喂狗,或有限次重试后强制进入等待升级
参数区校验 无魔数、无版本、无 CRC;Boot 与 App 结构体定义不一致 参数损坏会伪装成传感器故障;两个工程对同一块区域的理解不同 统一结构体定义 + 头部魔数/版本/CRC + 默认值回退
时序常量与配置的绑定 方向切换延时硬编码 10 ms,波特率可配 改波特率后可能出现“偶发丢最后一字节”,且极难复现 延时由波特率算出(每字节位数 ÷ 波特率 × 余量),不再手写常数

这张表里的每一项我在写的时候都犹豫过要不要放上来,因为放了就显得这个项目“不完整”。 但升级失败导致设备变砖这件事,如果在文档里被含糊过去,将来吃亏的一定是我自己。 所以我的结论是:这版 Bootloader 适合在能接线、能重烧的环境里学习使用, 不适合直接放到够不着的地方去升级。

八、局限与还没做完的部分

除了上面那张缺口表,还有几件事是“代码在、但没在跑”或者“想做但没做”的,一并记下来:

  • 温度补偿默认关闭。21 个系数的二维多项式写好了,但总开关是 0, 现场跑的是纯线性那一层。要真正启用,得先按温度点重新拟合系数并做全程复核。
  • 4–20 mA 输出没有执行。三个校准系数全为 0,初始化函数被条件判断挡住, 这条链路目前只是“代码完整”。而且它的标定常数与后级电流环硬件绑死,换板必须重标。
  • 板载温度器件被禁用。单总线温度器件的初始化在应用里被注释掉, 温度改由压力芯体自带的温度寄存器提供——省了一颗器件,但也意味着温度只反映芯体自身状态。
  • 没有 RTOS,也没有任务优先级体系。四个定时器事件加主循环轮询, 在现在这个功能规模下够用;如果以后要在采集之外加更多协议栈,这个结构会开始吃紧。
  • 没有设备端的版本读取闭环。App 侧虽然有一个软件版本号常量, 但没有通过总线对外提供“我现在跑的是哪一版、校验值是多少”这一信息, 升级成功与否只能靠功能表现间接判断。
  • 以太网相关的能力只是预留。CAN 命令枚举里有一个以太网使能的条目, 但整个工程里没有找到网络协议栈或收发实现。
  • 参数区没有做双备份。这是我认为下一个最该补的点, 因为它同时影响标定数据的可信度和升级的可靠性。

如果让我排一个后续顺序,我会这样排: 先把参数区的魔数/版本/CRC/默认回退补上(收益最大、风险最低), 再给升级链路加镜像 CRC32 与 A/B 回滚(代价是要重新规划分区,但这是把“学习项目”变成“敢用”的分界线), 然后是接收缓冲的边界检查与分包写入,最后才是启用温度补偿并重标一遍。

参考资料与说明

  • 数字压力芯体的公开数据手册(I²C 接口、压力/温度寄存器、量程寄存器 DPH/DPL 的定义), 用于确认寄存器地址与 24 位数据格式。
  • Modbus 应用协议规范(Modbus Application Protocol Specification)中关于 CRC16 与异常码的定义。 本文的 RS485 命令集是自定义实现,不是标准功能码体系。
  • CAN 2.0 规范与 ISO 11898 系列标准,用于确认报文格式与仲裁机制。
  • I²C 总线规范(NXP UM10204),用于确认从地址读写位与时序约定。
  • Cortex-M4 内核的向量表与启动流程公开文档,用于说明栈顶与复位入口的校验方式。
  • 本文涉及的芯片与器件型号仅用于说明技术方案,与相关厂商无隶属或授权关系; 文中的分区地址、命令码、参数与常量来自个人学习项目中的实际实现, 示例代码为按个人理解重写的最小片段,不代表任何产品或交付代码。
  • 出于对个人项目实现细节的保护,本文只公开方法与思路;涉及核心实现的部分以步骤与字段说明代替代码,示例代码为按个人理解重写的通用片段。