使用 CycBox 通过 Modbus RTU 调试 Senseair S8 NDIR CO2 传感器
Senseair S8 是一款专为空气质量监测设计的高性能 NDIR CO2 传感器,采用基于 UART TTL 接口的 Modbus RTU 协议。本案例研究详细介绍了使用 CycBox 轮询实时 CO2 浓度和系统健康状态的方法,验证 9600 bps 串口时序,并实现用于 MCU 集成的稳健解析逻辑。
被测设备
- 型号 / 厂商 / 类别:Senseair S8 / Senseair / NDIR CO2 传感器。
- 在系统中的作用:以 ppm(百万分之一)为单位测量环境 CO2 浓度。常用于 HVAC 控制、室内空气质量监测仪和工业安全系统。
- 电气接口:UART TTL(CMOS 逻辑电平)。
- 波特率:9600 bps(固定)。
- 供电电压:4.5 V 至 5.25 V。
- 逻辑电平:3.3 V CMOS。
- 关键数据手册规格:
- 测量范围:400 至 2000 ppm(扩展可达 10,000 ppm)。
- 精度:±40 ppm ±读数的 3%。
- 采样时间:约 2 秒(与灯循环周期匹配)。
- 响应超时:最大 180 ms。
- 参考文档:
TDE2067.pdf(Senseair S8 参考手册)。
线路协议与 CycBox 协议栈
Senseair S8 作为 Modbus RTU 从机工作。虽然它支持独立地址(1–247),但在点对点配置中通常使用"任意传感器"广播地址 254(0xFE)进行调试,无需知道具体的设备 ID 即可通信。
- 传输层:
serial_port_transport,9600 bps,8 数据位,无校验,1 停止位。 - 编解码器:
modbus_rtu_codec,配置 20 ms 接收超时。 - 协议详解:
- 功能码:支持
0x03(读保持寄存器)、0x04(读输入寄存器)和0x06(写单个寄存器)。 - 帧边界:帧间需要至少 3.5 个字符的静默间隔(9600 波特下约 3.6 ms)。
- 负载限制:每个数据包最大总计 39 字节。
- 寄存器大小:16 位大端字节序字。
- 功能码:支持
寄存器映射表(输入寄存器)
| 地址 | 类型 | 名称 | 刻度/单位 | 描述 |
|---|---|---|---|---|
0x0000 | Input | MeterStatus | 位掩码 | 系统健康状态(0x0000 = 正常) |
0x0001 | Input | AlarmStatus | 位掩码 | 保留/告警标志 |
0x0002 | Input | OutputStatus | 位掩码 | PWM 和告警引脚逻辑状态 |
0x0003 | Input | Space CO2 | ppm | CO2 浓度 |
CycBox 配置流程
传输层与编解码器配置
该配置在串口链路上使用标准 Modbus RTU 编解码器。with_receive_timeout 设置为 20 ms,以便在传感器未能在 180 ms 窗口内响应时引擎能够快速恢复。
[
{
"app": {
"app_transport": "serial_port_transport",
"app_codec": "modbus_rtu_codec",
"app_transformer": "disable_transformer",
"app_encoding": "UTF-8"
},
"serial_port_transport": {
"serial_port_transport_flow_control": "none",
"serial_port_transport_parity": "none",
"serial_port_transport_baud_rate": 9600,
"serial_port_transport_stop_bits": "1",
"serial_port_transport_data_bits": 8,
"serial_port_transport_port": "/dev/ttyACM0"
},
"modbus_rtu_codec": {
"with_receive_timeout": 20
}
}
]
Lua 脚本
该脚本自动执行 2 秒的轮询间隔,以匹配传感器内部的灯循环周期。它读取前 4 个输入寄存器,以同时捕获 CO2 值和诊断状态。
-- Senseair S8 CO2 传感器解析器
-- 轮询间隔:2 秒(由数据手册推荐)
-- 从机地址:254 (0xFE) - "任意传感器"广播地址
local SLAVE_ADDR = 254
local POLL_INTERVAL = 2000 -- 2 秒
local timer_counter = 0
function on_start()
log("info", "Senseair S8 script started. Polling every 2s...")
-- 初始轮询:从 0x0000(状态到 CO2)开始读取 4 个输入寄存器
modbus_rtu_read_input_registers(SLAVE_ADDR, 0x0000, 4, 0)
end
function on_timer(timestamp_ms)
timer_counter = timer_counter + 100
if timer_counter >= POLL_INTERVAL then
timer_counter = 0
-- 读取输入寄存器 0x0000(状态)到 0x0003(CO2)
modbus_rtu_read_input_registers(SLAVE_ADDR, 0x0000, 4, 0)
end
end
function on_receive()
-- modbus_rtu_codec 会自动将寄存器解析为命名值。
-- 逻辑 Modicon 地址:30001 (0x0000) 和 30004 (0x0003)
local status = message:get_value("modbus_rtu_254:30001")
local co2 = message:get_value("modbus_rtu_254:30004")
if status ~= nil then
-- 检查 MeterStatus 位以查找错误(0x0000 表示健康)
if status ~= 0 then
log("error", string.format("Sensor Error Detected! MeterStatus: 0x%04X", status))
message.highlighted = true
-- 可选:检查特定的致命错误位(bit 0)
if bit.band(status, 0x01) ~= 0 then
log("error", "Fatal Error flagged by sensor")
end
end
message:add_int_value("System_Status", status)
end
if co2 ~= nil then
log("info", string.format("CO2 Concentration: %d ppm", co2))
message:add_int_value("CO2_Level", co2)
-- 如果 CO2 浓度过高(例如 > 1000 ppm),则在 UI 中高亮显示
if co2 > 1000 then
message.highlighted = true
end
end
-- 如果向 message 添加了值,则返回 true 以便 UI 跟踪
return status ~= nil or co2 ~= nil
end
调试过程
步骤 1 — 验证广播通信
- 目标:确认传感器响应"任意传感器"地址
0xFE。 - 我们做了什么:发送 Modbus 功能
0x04请求,从0x0000开始读取 4 个寄存器。 - 设备返回:
fe 04 08 00 00 00 00 00 00 06 1d d4 b3 - 诊断:设备成功返回 8 字节数据(4 个寄存器)。
0x0000 0000 0000(状态/告警/输出)= 健康。0x061D= 1565 ppm CO2。
- 结果:使用广播 ID 建立通信成功。
步骤 2 — 验证测量稳定性
- 目标:观察传感器的更新速率和读数稳定性。
- 我们做了什么:监控 Lua 脚本在数个周期内的 2 秒自动轮询。
- 观察到的帧:
fe 04 08 00 00 00 00 00 00 06 0c 14 bf (CO2: 1548 ppm)
fe 04 08 00 00 00 00 00 00 05 fb 55 c9 (CO2: 1531 ppm)
fe 04 08 00 00 00 00 00 00 05 ee 94 06 (CO2: 1518 ppm) - 诊断:数值每 2-4 秒更新一次,随着环境稳定而呈下降趋势。响应延迟始终约为 53 ms(远低于数据手册中 180 ms 的限制)。
观察到的行为与验证
- CO2 读数:观察到的数值范围为 1498–1565 ppm。
- 时序:
- TX 到 RX 延迟:测量值约为 53 ms。
- 轮询频率:2000 ms 执行成功;制造商不建议使用更短的间隔(例如 < 500 ms),因为内部灯循环周期的限制。
- 错误状态:
MeterStatus始终保持0x0000,确认会话期间没有内存、范围或致命错误。
移植到 MCU 固件(设计参考)
外设初始化
MCU(例如 STM32L4 或 ESP32)应使用以下参数配置硬件 UART:
| 参数 | 设置 | 备注 |
|---|---|---|
| 波特率 | 9600 bps | 固定 |
| 数据位 | 8 | |
| 校验位 | 无 | |
| 停止位 | 1 (RX) / 2 (TX) | 注意: 数据手册建议发送时使用 2 个停止位。 |
| 流控 | 无 | |
| 中断 | RXNE(RX 非空) | 如果轮询多个传感器,使用 DMA 以提高效率。 |
设备初始化序列
- 上电延时:等待 > 1 秒以使传感器内部稳定。
- 初始状态读取:读取
MeterStatus(IR1)以确保其为0x0000。 - 可选 ABC 配置:如需修改默认的 7.5 天基线校正,可写入
ABC_PERIOD(HR32)。
消息发送
CO2 读取请求的构造如下:
| 字节偏移 | 值 | 描述 |
|---|---|---|
| 0 | 0xFE | 从机地址(或具体 ID 1–247) |
| 1 | 0x04 | 功能码(读输入寄存器) |
| 2-3 | 0x0000 | 起始地址 |
| 4-5 | 0x0004 | 寄存器数量(状态 + CO2) |
| 6-7 | 0xE5C6 | CRC16(小端字节序) |
帧间时序:确保连续 Modbus 请求之间的延迟至少为 4 ms,以满足 T3.5 静默间隔要求。
消息接收与解析
固件应期望针对 4 寄存器读取收到 13 字节的响应:
- 包头校验:确认第一个字节为
0xFE(或特定 ID),第二个字节为0x04。 - CRC 校验:对前 11 字节计算 CRC16,并与最后 2 字节比较。
- 字段提取:
- MeterStatus:字节 3-4(大端字节序)。如果非零,触发错误处理程序。
- CO2 浓度:字节 9-10(大端字节序)。结果以 ppm 为单位。
错误处理矩阵
| 条件 | 动作 |
|---|---|
| 超时 (> 200 ms) | 错误计数器递增;2 秒后重试。 |
| CRC 不匹配 | 丢弃该帧;检查是否存在 EMI 或布线问题。 |
| MeterStatus Bit 0 置位 | 致命错误:传感器需要硬件维护。 |
| MeterStatus Bit 5 置位 | 超出范围:检查 CO2 是否超过 2000/10000 ppm。 |
| Modbus 异常码 02 | 检查寄存器地址;S8 寄存器地址最高至 0x001F。 |
注意事项与建议
- 停止位不对称:数据手册规定接收使用 1 个停止位,但发送使用 2 个停止位。大多数配置为 8N1 的 MCU 也能正常通信,但为严格遵循规范,发送时应使用 2 个停止位。
- 广播地址:虽然
0xFE使用方便,但如果同一 RS-485 总线上有多个 S8 传感器则不能使用。如果构建多节点网络,应通过序列号寄存器分配唯一的 ID(1–247)。 - ABC 逻辑:自动基线校正(ABC)假设传感器在其周期内(默认 7.5 天)至少能接触到一次"新鲜空气"(约 400 ppm)。如果传感器位于 24/7 有人占用的空间中,必须通过
ABC_PERIOD(HR32 = 0)禁用 ABC,以防止读数向下漂移。