Tasmota 中的 AGS02MA TVOC 传感器库:0.4.x 版本演进、API 详解与 I2C 低速通信实践
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
导读
本文以 Tasmota 仓库内置的 AGS02MA-0.4.3 库 为主线,完整梳理该 Arduino 库 0.4.x 系列的核心变更、API 设计、I2C 低速通信机制、校准流程与 TVOC 空气分级知识,并结合 Tasmota 驱动源码 说明它如何被集成到固件中。读完本文,你将掌握 AGS02MA TVOC 传感器的接线与 I2C 地址、25 kHz 低速总线的实现原理、PPB/UGM3 双模式与空气分级表的实战用法,以及如何在 Tasmota 中启用并读取该传感器数据。
一、AGS02MA 库是什么:TVOC 传感器与版本脉络
AGS02MA 是测量空气中TVOC(总挥发性有机物,Total Volatile Organic Compounds)浓度的传感器芯片,它不针对某一种特定气体,而是同时感知多种挥发性有机物。本仓库中对应的 Arduino 库为 lib/lib_i2c/AGS02MA-0.4.3,由 Rob Tillaart、Viktor Balint 与 Beanow 维护,当前版本 0.4.3。
该库被 Tasmota 固件直接复用,驱动实现位于 xsns_118_ags02ma.ino(Tasmota 中该传感器编号 XSNS_118、I2C 设备编号 XI2C_95,对应 I2CDEVICES.md 的登记表)。从源码看,Tasmota 驱动直接调用库的begin()、getSensorVersion()、setPPBMode()、readPPB()、isHeated()等接口,把传感器固件与固件框架解耦。
1.1 0.4.x 版本演进一览(依据 CHANGELOG)
- 0.4.0(2023-12-06):重构 API 与
begin()调用方式——破坏性变更:不能再在begin()中传入引脚,改为由用户先调用Wire.begin()并可选设置 Wire 引脚,再调用begin()。这一改动降低了对各处理器 Wire 实现的依赖。 - 0.4.1(2023-12-10):修复 README 中第 #26 号 issue 指出的文档错误。
- 0.4.2(2024-02-03):功能较丰富的一次更新——README 增加 I2C 多路复用(multiplexer)章节、扩充 PPB "health" 表格;清理示例;重构条件编译代码;新增
setI2CLowSpeed()/setI2CHighSpeed()内部函数;改进错误处理,新增错误码AGS02MA_ERROR_REQUEST(-14)并重写readRegister()。 - 0.4.3(2025-08-15):更新 README、更新许可证、少量编辑,为当前仓库所携带的稳定版本。
更早的 0.3.x 线(0.3.2 起正式加入 CHANGELOG,0.3.3 起加入 keywords.txt 与 GitHub Actions,0.3.4 增加 ESP32 的Wire1 支持)以及 0.1.x/0.2.x 的历史版本在 CHANGELOG 中无详细记录。
1.2 单元测试佐证库的公开契约
库的 unit_test_001.cpp 用 Arduino-CI 框架固化了关键常量与默认值,可作为版本行为的一致性证据:
- 错误码常量:
AGS02MA_OK = 0、AGS02MA_ERROR = -10、AGS02MA_ERROR_CRC = -11、AGS02MA_ERROR_READ = -12、AGS02MA_ERROR_NOT_READY = -13、AGS02MA_ERROR_REQUEST = -14; - I2C 工作时钟常量:
AGS02MA_I2C_CLOCK = 25000(25 kHz); - 默认地址 26(0x1A);默认 I2C 复位速度 100 kHz,可用
setI2CResetSpeed()改为如 400 kHz; - 模式初值 255("not set"),地址初值经构造函数设定,
lastRead()初始为 0。
二、硬件接线、I2C 地址与低速总线警告
2.1 引脚布局(正面从左到右,以数据手册为准)
| 引脚 | 说明 |
|---|---|
| 1 | VDD +5V |
| 2 | SDA 数据线 |
| 3 | GND |
| 4 | SCL 时钟线 |
2.2 固定地址 0x1A 与同地址冲突
传感器 I2C 地址固定为26(0x1A),无法像普通 I2C 芯片那样用地址引脚选择。仓库 README 明确指出,多款 AGS 系列器件共用 0x1A:AGS2616(H2)、AGS3870(CH4)、AGS3871(CO)、AGS02MA(TVOC)。若要在同一条 I2C 总线上同时使用它们,必须借助I2C 多路复用器(如 TCA9548,最多 8 通道)。其代价是代码管理复杂(需记录器件与通道的对应关系),且切换通道会拖慢多路复用器之后的其他设备访问速度。
2.3 低速总线警告:传感器最高只支持 30 kHz
这是 AGS02MA 最关键的工程约束。数据手册标称器件工作在 100 kHz 总线,但实际最高只能以约 30 kHz 通信。库的处理策略(见 AGS02MA.cpp):
- 每次 I2C 操作前调用
_setI2CLowSpeed(),将总线降速至25 kHz(常量AGS02MA_I2C_CLOCK,历史版本曾用 30 kHz 上限,0.3.1 起统一用 25 kHz 换取稳定性); - 操作结束后调用
_setI2CHighSpeed(),把时钟恢复到_I2CResetSpeed(默认100 kHz),以减少对同总线其他设备通信的干扰;复位速度可用setI2CResetSpeed()改为 200/400 kHz; - 在 AVR(如 Arduino UNO)上通过直接写
TWBR = 78; TWSR = 0x01(预分频 4)实现 25 kHz;在 ESP32/ESP8266 等非 AVR 平台通过_wire->setClock(AGS02MA_I2C_CLOCK)实现。
注意:
AGS02MA::isConnected()与_readRegister()/_writeRegister()内部都包裹了"降速—操作—恢复"流程,因此即使总线上挂着其他标准速度器件,也只在 AGS02MA 操作期间短暂占用低速窗口。
2.4 读取时序与 30 ms 寄存器节流
源码_readRegister()与_writeRegister()开头均有while (millis() - _lastRegTime < 30) yield();的节流逻辑,保证两次寄存器操作至少间隔 30 ms。此外,begin()会记录_startTime,isHeated()据此判断预热是否满 120 秒;数据手册建议测量间隔至少 1.5 秒、优选 3 秒,库内示例(如 AGS02MA_PPB.ino)普遍使用 3000 ms 间隔。
三、库 API 全景:从构造到数据读取
API 定义集中在 AGS02MA.h,以下按功能分组给出签名、默认值与使用要点。
3.1 构造与初始化
| 接口 | 说明 |
|---|---|
AGS02MA(uint8_t deviceAddress = 26, TwoWire *wire = &Wire) | 构造器,默认地址 26,默认 Wire 实例;支持传入其他 TwoWire(如 ESP32 的 Wire1) |
bool begin() | 初始化;若 I2C 总线上找不到该地址返回 false。0.4.0 起不再接收引脚参数 |
bool isConnected() | 地址探测:降速后endTransmission(true) == 0即为在线 |
void reset() | 复位内部变量(复位 I2C 复位速度为 100 kHz、清空缓存与错误码) |
3.2 预热与读取节奏
bool isHeated():begin()后是否满 2 分钟(millis() - _startTime > 120000UL)。数据手册指出充分预热可提升测量质量;若未先调用begin(),该函数结果可能不准确。uint32_t lastRead():最近一次成功读取的时间戳(毫秒),从未读取过返回 0,可用于实现异步等待——保持两次测量间隔 ≥1.5 秒(优选 3 秒)。
3.3 器件管理(地址 / 版本 / 生产日期)
bool setAddress(uint8_t addr):写入从机地址寄存器(0x21)。源码限制10 ≤ addr ≤ 119,地址以"地址 + 取反"两两写入并经 CRC8 校验;成功后立即生效且重启后保持。示例见 AGS02MA_setAddress.ino。uint8_t getAddress():返回当前设定地址,默认 26。uint8_t getSensorVersion():读取版本寄存器(0x11),版本字节位于_buffer[3];读取失败或 CRC 错误时返回 255。多数器件报告版本 117,社区也报告存在版本 118(行为有差异,见下文)。uint32_t getSensorDate()(实验性):读取版本寄存器中前 3 字节,经 BCD 转换拼成YYYYMMDD(如Serial.println(dd, HEX)输出20210203),疑似生产日期,仅供调试。
3.4 I2C 时钟复位速度控制
void setI2CResetSpeed(uint32_t speed)/uint32_t getI2CResetSpeed():设置/读取每次 I2C 操作结束后总线要恢复的时钟速度,默认 100 kHz。库文档建议可按需改为 200 或 400 kHz,用于匹配同总线上其他器件的需求。
3.5 测量模式:PPB 与 ug/m³
器件上电默认PPB(十亿分率)模式:
bool setPPBMode():写数据寄存器(0x00)使能 PPB,成功后内部_mode = 0。bool setUGM3Mode():切换为微克/立方米(µg/m³)模式,成功后_mode = 1。uint8_t getMode():返回当前模式,0 = PPB,1 = UGM3,255 = 未设置。
源码中两种模式写入的字节序列不同(PPB:00 FF 00 FF 30;UGM3:02 FD 02 FD 00),并统一经_writeRegister(AGS02MA_DATA)提交。
PPB 与 UGM3 无固定换算关系:二者之比取决于目标气体分子量,PPB 更接近"绝对指示",UGM3 偏"相对指示",气体未知时建议以 PPB 为准。仅供参考的换算公式(未经验证来源)为:μg/m3 = ppb × M × 12.187 / (273.15 + °C),在 1 atm、25 °C 下简化为μg/m3 = ppb × M × 0.04087539829。常见气体换算系数(M 为分子量):
| 气体 | 常用名 | 1 ppb ≈ μg/m³ | M (g/mol) |
|---|---|---|---|
| SO2 | 二氧化硫 | 2.62 | 64 |
| NO2 | 二氧化氮 | 1.88 | 46 |
| NO | 一氧化氮 | 1.25 | 30 |
| O3 | 臭氧 | 2.00 | 48 |
| CO | 一氧化碳 | 1.145 | 28 |
| C6H6 | 苯 | 3.19 | 78 |
3.6 读取传感器
uint32_t readPPB(); // PPB,典型 1..999999 uint32_t readUGM3(); // 微克/立方米- 单次读取约耗时35 ms(含内部 30 ms 节流与请求延迟),源码中通过
delay(30)等待器件就绪后requestFrom()取 5 字节。 - 读取失败时返回上一次缓存值(
_lastPPB/_lastUGM3),避免图表出现跳变;应配合lastError()与lastStatus()判断真实成败。 - 派生包装函数:
float readPPM()(= PPB × 0.001,典型 0.01..999.99)、float readMGM3()(= UGM3 × 0.001)、float readUGF3()(= UGM3 × 0.0283168466,微克/立方英尺)。 - 缓存读取:
lastPPB()、lastUGM3()、lastPPM()。
3.7 错误码与状态字节
错误码(lastError()返回,读取后自动清零):
| 宏 | 值 | 含义 |
|---|---|---|
AGS02MA_OK | 0 | 正常 |
AGS02MA_ERROR | -10 | 通用错误 |
AGS02MA_ERROR_CRC | -11 | CRC8 校验失败 |
AGS02MA_ERROR_READ | -12 | 读取字节数不足(非 5 字节) |
AGS02MA_ERROR_NOT_READY | -13 | 状态字节 RDY=1,器件忙 |
AGS02MA_ERROR_REQUEST | -14 | 请求阶段错误(0.4.2 新增) |
状态字节(lastStatus(),需新一轮读取才会更新):bit7-4 内部使用;bit3-1 表示模式(000 = PPB,001 = ug/m³);bit0 为 RDY(0 = 就绪,1 = 忙)。dataReady()即返回_status & 0x01。
3.8 寄存器直读与校准数据结构
bool readRegister(uint8_t address, RegisterData ®):将寄存器原始数据填入RegisterData{ data[4]; crc; crcValid; },主要用于排障分析,不建议基于原始数据构建业务。与常规方法不同,CRC 错误不会让本方法返回 false 或写入lastError(),而是记录在reg.crcValid中。ZeroCalibrationData{ uint16_t status; uint16_t value; }:由getZeroCalibrationData()填充。源码注释提示 status 疑似位掩码:0x0C(12) 为典型值,0x0D(13) 偶见于 v117,0x7D(125) 见于 v118 断电后(其数据与 12 不同)。
四、校准:正确姿势与 v118 版本风险
4.1 校准 API
bool zeroCalibration():等值于manualZeroCalibration(0),须在新鲜空气中至少 5 分钟后调用。bool manualZeroCalibration(uint16_t value = 0):手动设定零点。v117:0-65535 均视为自动校准;v118:0 = 自动校准,1-65535 = 手动校准。bool getZeroCalibrationData(ZeroCalibrationData &data):读取当前零点状态与数值,成功返回 true。
4.2 v118 版本的校准隐患
README 与 CHANGELOG 多次强调,切勿对版本 118 的器件执行校准:社区 issue 报告 v118 在校准后出现数据异常(已由第二颗 v118 复现),重复校准无法修复,而 v117 无此问题。0.2.0 起校准函数会先读取版本号,拒绝校准任何非 117 版本;v118 还可能存在"仅支持 PPB、不支持 ug/m³ 模式"的情况(详见库 README 引用 issue 11、13 的说明),且断电后数据表现不同。
4.3 完整校准流程示例
参考 AGS02MA_calibrate.ino(默认预热 6 分钟、读取间隔 3000 ms):
#include "AGS02MA.h" #define WARMUP_MINUTES 6 #define READ_INTERVAL 3000 AGS02MA AGS(26); void setup() { Serial.begin(115200); Wire.begin(); bool b = AGS.begin(); Serial.print("BEGIN:\t"); Serial.println(b); uint8_t version = AGS.getSensorVersion(); // 版本读取失败则不校准 if (AGS.lastError() != AGS02MA_OK) { Serial.println("Won't attempt to calibrate."); return; } b = AGS.setPPBMode(); Serial.print("MODE:\t"); Serial.println(b); // 将器件置于户外新鲜空气中预热 WARMUP_MINUTES 分钟,观察 PPB 值稳定 uint32_t start = millis(); while (millis() - start < WARMUP_MINUTES * 60000UL) { Serial.print("[PRE]\tPPB:\t"); Serial.println(AGS.readPPB()); delay(READ_INTERVAL); } AGS02MA::ZeroCalibrationData initialValue; if (!AGS.getZeroCalibrationData(initialValue)) { Serial.println("Read calib failed."); return; } Serial.print("OLD status/value:\t"); Serial.print(initialValue.status); Serial.print("/"); Serial.println(initialValue.value); b = AGS.zeroCalibration(); // 执行零点校准 Serial.print("CALIB:\t"); Serial.println(b); AGS02MA::ZeroCalibrationData zc; while (!AGS.getZeroCalibrationData(zc)) { delay(READ_INTERVAL); } Serial.print("NEW status/value:\t"); Serial.print(zc.status); Serial.print("/"); Serial.println(zc.value); // 若为 v118 或旧状态为 125,提示断电后可能需重设校准值 }关键经验:预热期间 PPB 应稳定(允许噪声)而非持续下降;校准前先备份ZeroCalibrationData;v118 用户应避免自动校准。
五、空气分级:把读数翻译成健康指示
库 README 给出了 TVOC(ppb) 的分级参考表(源自 Kaiterra 的公开资料,属指示性描述,非专业监测结论):
| TVOC (ppb) | 等级 | 描述 | 建议颜色 |
|---|---|---|---|
| ≤ 220 | 1 | 良好 | 绿 |
| ≤ 660 | 3 | 中等 | 黄 |
| ≤ 1430 | 7 | 差 | 橙 |
| ≤ 2200 | 10 | 不健康 | 红 |
| ≤ 3300 | 15 | 很不健康 | 紫(建议加脉冲效果) |
| ≤ 5500 | 25 | 危险 | 深紫(脉冲) |
| > 5500 | 50 | 极危险 | 深紫(脉冲) |
其中"等级"是相对线性标度(以 220 ≈ 1 为基准);颜色为指示性映射,若需连续色阶映射可参考同作者的 map2colour 思路。该表可用于将 AGS02MA 读数映射为指示灯、蜂鸣器或 Web 面板的告警级别。
六、示例程序全景与 Tasmota 集成实践
6.1 仓库内 15 个示例一览(examples/ 目录)
| 示例 | 用途 |
|---|---|
| AGS02MA_minimal | 不依赖库,仅用 Wire 直接requestFrom(26,5)取 5 字节并手工解析状态/PPB/CRC,展示协议本质 |
| AGS02MA_PPB | PPB 模式读取 + 120 秒预热等待 |
| AGS02MA_UGM3 | µg/m³ 模式读取 |
| AGS02MA_calibrate / AGS02MA_calibrate_manual | 自动/手动零点校准全流程 |
| AGS02MA_setAddress | 改写 I2C 地址(示例改为 42)并验证 |
| AGS02MA_get_registers、AGS02MA_readRegister | 寄存器直读与排障 |
| AGS02MA_PPB_TIMING | 时序测试(附 performance_0.3.0/0.3.1 文本记录) |
| AGS02MA_minimal_plotter | 配合 Arduino 串口绘图器可视化 |
| AGS02MA_test | 连通性 + 模式 + 版本 + 连续 PPB 冒烟测试 |
| AGS02MA_test_CRC8、test_CRC8 | CRC8 算法验证 |
| issue/issue.ino | 针对 GitHub issue 的复现脚本 |
AGS02MA_minimal.ino特别值得一读:它以最原始方式演示了数据帧格式——buffer[0]为状态字节、buffer[1..3]按b1*65536 + b2*256 + b3拼出 PPB、buffer[4]为 CRC8,这也正是库内部_readSensor()的解析逻辑(见 AGS02MA.cpp)。
6.2 在 Tasmota 固件中启用与读取
Tasmota 侧驱动 xsns_118_ags02ma.ino 封装了完整状态机:
- 编译启用:需同时开启
USE_I2C与USE_AGS02MA两个宏(可参考 my_user_config.h 与 user_config_override_sample.h 的覆盖方式),并确认 I2C 设备号 95 已启用(对应I2cEnabled(XI2C_95)检查)。 - 启动流程:
Ags02maInit()探测 0x1A 地址 →begin()→ 打印getSensorVersion()→ 强制setPPBMode()→ 登记I2cSetActiveFound(),随后进入120 秒预热状态(STATE_AGS02MA_HEATING,由FUNC_EVERY_SECOND驱动、用isHeated()判定完成)。 - 正常运行:每秒轮询
readPPB(),检查lastStatus()/lastError()并输出日志;JSON 遥测(FUNC_JSON_APPEND)输出"AGS02MA":{"TVOC":<ppb>},Web 页面(FUNC_WEB_SENSOR)显示 "AGS02MA TVOC: ppb";预热期间 JSON 输出"Status":"Heating"。 - 可选集成:开启
USE_DOMOTICZ时,遥测周期内会通过DomoticzSensor(DZ_AIRQUALITY, ppb)上报空气质量。
因此,在 Tasmota 控制台或 MQTT 中即可直接看到TVOC读数,无需自行编写读取逻辑;底层 I2C 低速切换与 30 ms 节流均由库透明处理。
6.3 性能与稳定性记录
- UNO 平台:TWBR=255(30.4 kHz)实测 >500 次读取失败率 <1%;0.3.1 起改为 25 kHz + TWSR 预分频 4 后,4 小时 6000+ 次读取0 错误。
- ESP32/ESP8266 在 30 kHz 下测试良好;更低时钟尚未系统验证。
AGS02MA_PPB_TIMING目录内保留 performance_0.3.0.txt 与 performance_0.3.1_10khz/25khz.txt 等历史性能记录,可作参考。
七、使用建议与已知限制(基于源码与文档事实)
- 务必处理低速 I2C:不要让 AGS02MA 长时间占用 25 kHz 总线——库的设计是在每次操作后恢复默认速度,若自行绕过库,请复制同样的"降速-恢复"策略。
- 控制读取频率:遵循 ≥1.5 秒(优选 3 秒)间隔;库已内置 30 ms 寄存器节流与单次 ~35 ms 耗时,高频调用会拖慢主循环。
- 读取失败读缓存:
readPPB()/readUGM3()失败时返回上次值,务必同时检查lastError()与lastStatus(),避免把脏数据当真实测量。 - 校准分版本对待:v117 可正常自动校准;v118 严禁校准(存在社区确认的数据异常风险),且 v118 可能不支持 ug/m³ 模式。
- 多器件同地址需多路复用:AGS02MA 与 AGS2616/AGS3870/AGS3871 共用 0x1A,同总线共存必须使用 TCA9548 等 MUX。
- 库仍标记为实验性(Experimental):文档明确说明不替代专业空气质量监测系统,用于生产环境前应充分验证。
- Tasmota 集成:确保编译宏与 I2C 设备号正确开启,预热 120 秒后才输出 TVOC 数据,Web 与 MQTT 均可消费该读数。
本文全部技术事实均来自 Tasmota 仓库内 AGS02MA-0.4.3 库 的 CHANGELOG.md、README.md、AGS02MA.h、AGS02MA.cpp、单元测试 及 Tasmota 驱动,未包含任何外部推断性结论。
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考