这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 基于HKB-08A电子血压计

共1条 1/1 1 跳转至

基于HKB-08A电子血压计

高工
2026-08-29 19:07:49     打赏
HKB-08A 电子血压计

今天买个了电子血压计模块,今天用html实现了,效果如下:

image.png

本文以 HKB-08A 血压传感器模块为例,完整拆解一个典型的嵌入式二进制串口协议: 帧结构怎么设计的、指令怎么组包发送、接收端怎么可靠解包。 文中的分析方法和代码可以直接套用到绝大多数 "帧头 + 长度 + 校验 + 命令字" 类协议上。

1. 模块与链路参数

项目参数
接口UART(TTL 电平)
波特率115200,8 数据位,1 停止位,无校验(8N1)
供电5V DC,350~500mA(气泵工作时电流较大,不能用弱供电口)
量程0~300 mmHg,分辨率 1 mmHg
接线模块 TX → 主机 RX,模块 RX → 主机 TX,共地

注意它是二进制协议,不是文本协议——用串口助手以字符模式看是一堆乱码,必须切到 HEX 模式观察。

2. 帧结构

所有帧(主机下发、模块应答)格式统一:

+------+------+------+--------+------+-----------+
| 0xFF | 0xC0 | LEN  | CKSUM  | CMD  | DATA ...  |
+------+------+------+--------+------+-----------+
帧头    帧头   长度    校验和   命令字  数据(可选)

字段含义:

  • 帧头 FF C0:2 字节固定同步头,接收端靠它做帧同步(resync)。

  • LEN:从 LEN 字节自己开始、到帧尾的总字节数。即 LEN = 1(LEN) + 1(CKSUM) + 1(CMD) + N(DATA) = 3 + N

    • 无数据的命令帧:LEN = 3

    • 实时压力帧:LEN = 5

    • 测量结果帧:LEN = 8

  • CKSUM:校验和 = 从 LEN 开始(含 LEN)到帧尾所有字节累加取低 8 位(mod 256)。 注意 CKSUM 自己不参与求和——求和时跳过它。

  • CMD:命令字,决定 DATA 怎么解释。

  • DATA:0~N 字节载荷。

用厂商手册的样例帧验证

结果帧样例:FF C0 08 E0 AC 00 7B 00 4A 67

FF C0          帧头
08             LEN = 8,即 08 起到帧尾共 8 字节 → 全帧 10 字节 ✓
E0             CKSUM
AC             CMD = 0xAC,测量结果
00 7B          SSYH SSYL → 收缩压 = 0x007B = 123 mmHg
00 4A          SZYH SZYL → 舒张压 = 0x004A = 74 mmHg
67             XL        → 心率   = 0x67   = 103 bpm

校验:08 + AC + 00 + 7B + 00 + 4A + 67 = 0x1E0,取低 8 位 = 0xE0

这个"手工验算"的步骤在调试任何新协议时都值得做一遍——能快速确认自己对 LEN 含义、校验范围的理解是否正确。

3. 指令集(主机 → 模块)

功能CMD完整帧说明
唤醒0xAAFF C0 03 AD AA模块应答同样内容;上电/连接后建议连发 3 次(厂商例程做法)
启动测量0xA0FF C0 03 A3 A0袖带开始充气,测量全程 30~60 秒
停止测量0xA8FF C0 03 AB A8中止测量、放气

校验口算验证:03 + AA = 0xAD03 + A0 = 0xA303 + A8 = 0xAB

模块应答(模块 → 主机):

CMDLENDATA含义
0xAA3唤醒应答
0xA83停止应答
0xA05QYH QYL实时袖带压力,测量过程中持续上报
0xAC8SSYH SSYL SZYH SZYL XL测量结果
0xAD4X错误,X 为错误码

错误码(手册已确认部分):

  • X=1:充气异常,11 秒内压力未达到 50mmHg(检查袖带是否佩戴、气路是否漏气)

  • X=3:超压保护,压力超过 295mmHg

  • 其余码值手册未列全,程序里应做兜底显示

高字节里的标志位

压力值和血压值都是 16 位(高字节在前),但高字节的高位是标志位,不是数值

  • QYH.4:压力帧中的状态标志

  • SSYH.7:结果帧中的有效性标志

所以取数值时要把高字节掩码后再拼接,不能直接 (H << 8) | L

压力 QY  = ((QYH & 0x0F) << 8) | QYL     // 实际量程 0~300,1 个数据位就够,多掩几位无害
收缩压 SSY = ((SSYH & 0x7F) << 8) | SSYL
舒张压 SZY = ((SZYH & 0x7F) << 8) | SZYL
心率  XL   = XL                          // 单字节

厂商的 Arduino/STM32 例程偷懒直接只用低字节(血压极少超过 255),上位机里按掩码方式取更稳。

4. 发送端:组包

发送逻辑就一条规则:先算 LEN,再算 CKSUM,拼字节发出去

JavaScript(Web Serial,上位机 host/index.html 实际代码):

function buildFrame(cmd, data = []) {
 const len = 3 + data.length;             // LEN + CKSUM + CMD + DATA
 const cksum = (len + cmd + data.reduce((a, b) => a + b, 0)) & 0xFF;
 return new Uint8Array([0xFF, 0xC0, len, cksum, cmd, ...data]);
}

// 通过 Web Serial 发送
async function sendFrame(frame) {
 const writer = port.writable.getWriter();
 try { await writer.write(frame); }
 finally { writer.releaseLock(); }        // 注意每次用完必须释放锁
}

await sendFrame(buildFrame(0xAA));   // 唤醒  → FF C0 03 AD AA
await sendFrame(buildFrame(0xA0));   // 启动  → FF C0 03 A3 A0
await sendFrame(buildFrame(0xA8));   // 停止  → FF C0 03 AB A8

C(单片机侧,对照厂商例程):

uint8_t build_frame(uint8_t *buf, uint8_t cmd, const uint8_t *data, uint8_t n)
{
   uint8_t len = 3 + n, sum = 0, i;
   buf[0] = 0xFF; buf[1] = 0xC0; buf[2] = len;
   buf[4] = cmd;
   for (i = 0; i < n; i++) buf[5 + i] = data[i];
   for (i = 2; i < 5 + n; i++) sum += buf[i];   // LEN..帧尾,跳过 buf[3]
   buf[3] = sum;                                 // 自然溢出即 mod 256
   return 5 + n;
}

工程实践要点:

  • 连接建立后连发 3 次唤醒,间隔约 100ms——模块可能处于睡眠,单发一次不一定醒得来,这是厂商例程里的经验做法。

  • 启动测量前把本地波形缓冲清空,避免上一次的压力曲线混进本次。

5. 接收端:解包(重点)

串口是字节流,没有天然的帧边界。一次 read() 可能拿到半帧、一帧半、或好几帧粘在一起。所以解包必须是一个带状态的字节流处理器,而不是"读一次解一帧"。

5.1 解包四步

  1. 拼接:新数据追加到接收缓冲区尾部。

  2. 同步(resync):从缓冲区头部扫描帧头 FF C0,找不到就丢弃扫过的字节。

  3. 等齐:按 LEN 算出整帧长度(2 + LEN),缓冲区不够就退出等下一批数据。

  4. 校验 + 取帧:校验和通过才交给命令分发;失败则只丢一个字节回到第 2 步(防止把下一帧的帧头误判掉)。

5.2 参考实现(JavaScript)

let rxBuf = [];   // 接收字节缓冲(跨 read 调用保持)

// 每次串口读到数据就调用
function onSerialData(uint8arr) {
 rxBuf.push(...uint8arr);
 drainFrames();
}

function drainFrames() {
 while (true) {
   // 1) 同步:找 FF C0
   let i = 0;
   while (i + 1 < rxBuf.length && !(rxBuf[i] === 0xFF && rxBuf[i + 1] === 0xC0)) i++;
   if (i > 0) rxBuf.splice(0, i);
   if (rxBuf.length < 3) return;

   // 2) 长度预检 + 等齐
   const len = rxBuf[2];
   if (len < 3 || len > 32) { rxBuf.splice(0, 1); continue; }  // 非法 LEN:丢 1 字节重新同步
   const total = 2 + len;
   if (rxBuf.length < total) return;                            // 帧没收全,等下一批

   // 3) 校验(CKSUM 不参与求和)
   const frame = rxBuf.slice(0, total);
   let sum = 0;
   for (let k = 2; k < total; k++) if (k !== 3) sum = (sum + frame[k]) & 0xFF;
   if (sum !== frame[3]) {
     rxBuf.splice(0, 1);      // 校验失败只丢 1 字节,而不是整帧
     continue;
   }

   // 4) 取帧分发
   rxBuf.splice(0, total);
   handleFrame(frame[4], frame.slice(5));
 }
}

5.3 命令分发

function handleFrame(cmd, data) {
 switch (cmd) {
   case 0xA0: {  // 实时袖带压力
     const qy = ((data[0] & 0x0F) << 8) | data[1];
     updatePressure(qy);        // 更新数值 + 推入波形缓冲
     break;
   }
   case 0xAC: {  // 测量结果
     const ssy = ((data[0] & 0x7F) << 8) | data[1];
     const szy = ((data[2] & 0x7F) << 8) | data[3];
     const xl  = data[4];
     showResult(ssy, szy, xl);
     break;
   }
   case 0xAD:   // 错误帧
     showError(ERR_TEXT[data[0]] || `未知错误码 ${data[0]}`);
     break;
   case 0xAA:   // 唤醒应答
   case 0xA8:   // 停止应答
     logAck(cmd);
     break;
   default:
     logWarn(`未知命令字 0x${cmd.toString(16)}`);  // 协议升级时保命的一行
 }
}

5.4 状态机视角

上位机侧其实还隐含一层业务状态机:

        发送 A0                收到 AC(结果)
空闲 ────────────► 测量中 ──────────────► 空闲(显示结果)
 ▲                  │
 │                  ├─ 收到 AD(错误) ────► 空闲(显示错误原因)
 │                  └─ 发送 A8(停止) ────► 空闲
 └─────── 收到 AA(唤醒应答) ───────────────┘

测量中持续收到 0xA0 压力帧,用来画袖带压力曲线——充气到高点再阶梯下降的过程本身就是一次完整的示波法测量记录,导出 CSV 后可以离线分析。

6. 调试与排错清单

实战中这类协议最常见的问题和排查顺序:

现象可能原因排查
完全收不到数据TX/RX 没交叉;没共地;供电不足换线、量 GND;气泵启动时电流大,5V 供电要够
收到乱码波特率不对;当成文本显示了确认 115200 8N1;串口工具切 HEX 模式
校验大量失败对 LEN 含义或校验范围理解错了拿手册样例帧手工验算(见第 2 节)
解出的值离谱没掩掉高字节标志位;大小端搞反对照"标志位"一节检查取值逻辑
发命令没反应模块在睡眠;帧格式错先连发 3 次唤醒;把发送的 HEX 和手册逐字节对
测量立即报错 X=1袖带没戴好/气路漏气11 秒充不到 50mmHg 就会触发,先检查气路

工具建议:

  • HEX 日志必不可少——上位机里把每一帧的收发都以 HEX 形式带时间戳记下来,出问题先翻日志。

  • 没有硬件时可以用"虚拟串口对"(如 com0com)+ 一个脚本模拟模块回帧,先把解包逻辑调通。

  • 单元测试里最值得覆盖的就是解包器:半帧到达、多帧粘连、垃圾字节前缀、校验失败后的重新同步,各构造一个用例。

7. 小结

这个协议虽小,但把二进制串口协议的经典要素占全了:固定帧头同步、长度自描述、累加和校验、命令字分发、标志位与数值复用字节。掌握"组包算校验、解包做流式同步"这两条主线,再看 Modbus、自定义传感器协议,结构上都大同小异。







关键词: HKB-08A     电子     血压计    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]