工业压力变送器(GD32F425RE + RS485/CAN Bootloader)
这个项目做的是把压力芯体的读数变成“可信的、能上总线的工程量”,并且让固件能通过总线升级。
采集侧用 24 位数字压力芯体,标定分两层:一层是零点加两点增益偏移的线性标定,
一层是 21 个系数的二维多项式温度补偿;输出侧准备了 4–20 mA 电流环的 PWM 链路。
通信侧同时挂了 RS485(自定义命令集)与 CAN,并配一个 64 KB 的串口 Bootloader,
支持用一条 4 字节命令 01 67 41 CA 触发升级。
结论是:功能链路都跑通了,采样、标定、双总线上报、OTA 升级都能工作;
但升级的完整性校验只有三重很弱的检查,没有 CRC、没有版本比对、也没有回滚,
这部分我在最后一节按条列出来,不做美化。
一、这个变送器要做什么
需求可以一句话说清:把压力测出来,按工业现场习惯的方式送出去,并且以后能远程改固件。 但拆开之后是三条互相牵扯的链路,每一条都有自己的坑。
- 采集与标定链路:从 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 | 启用 | 主循环每轮处理 | 参数读写与上报都可用 |
四、双总线: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 字节序、方向切换延时 | 升级前后波特率默认值不同 |
五、Bootloader:分区、OTA 命令与升级流程
Bootloader 是一个独立工程,占 Flash 最前面的 64 KB,应用区从 0x08010000 开始。
分区是这样划的:
| 区域 | 起始地址 | 大小 | 说明 |
|---|---|---|---|
| Bootloader | 0x08000000 | 64 KB | 结束地址宏为 0x0800FFFF |
| 应用区 APP1 | 0x08010000 | 100 KB | App 里把中断向量表偏移硬编码成与之一致 |
| 备份区 APP2 | 0x08029000 | 100 KB | 地址已定义,但这条路径没有使用 |
| 参数区 | 0x08040000 | 1 个扇区 | 整个参数结构体整块存放 |
触发升级的命令是一个只有 4 个字节的短帧,字段构成我逐个对过, 整理成下面这张表。它属于协议触发方式,与标定参数无关, 所以字段表保留;但代码里我用占位符代替真实取值,不把可直接使用的帧交出去。
| 字节 | 值 | 含义 |
|---|---|---|
| 0 | 0x01 | 设备地址 |
| 1 | 0x67 | 命令码 103(十进制),即“请求进入升级模式” |
| 2 | 0x41 | CRC 低字节 |
| 3 | 0xCA | CRC 高字节 |
这里最值得记的是 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 内核的向量表与启动流程公开文档,用于说明栈顶与复位入口的校验方式。
- 本文涉及的芯片与器件型号仅用于说明技术方案,与相关厂商无隶属或授权关系; 文中的分区地址、命令码、参数与常量来自个人学习项目中的实际实现, 示例代码为按个人理解重写的最小片段,不代表任何产品或交付代码。
- 出于对个人项目实现细节的保护,本文只公开方法与思路;涉及核心实现的部分以步骤与字段说明代替代码,示例代码为按个人理解重写的通用片段。