diyBMS I2C通信协议深度解析:命令表、地址设计与自动分配机制
【免费下载链接】diyBMSDo it yourself battery management system for Lithium ion battery packs/cells项目地址: https://gitcode.com/gh_mirrors/diy/diyBMS
diyBMS是一款自制的锂离子电池(18650)电池组电池管理系统(BMS):ESP8266 作为主控制器,通过隔离 I2C 总线轮询连接在每串电池包上的 ATTINY85 从机模块,实现逐节电压/温度监测与被动均衡。本文完整讲解这套 diyBMS I2C 通信协议:地址如何分配、19 个命令 ID 各自做什么、新模块如何被自动"编址",以及协议内置的容错机制——即使没写过嵌入式代码,也能看懂整套设计。
1️⃣ 系统架构:hub-and-spoke 的 I2C 主从拓扑
上面是电池模块 PCB 背面:左右两组接口分别为I2C IN和I2C OUT(SDA/SCL 两线),说明多个模块像菊花链一样级联在同一条 I2C 总线上;每块板上有一颗 ATTINY85 主芯片,电池接口支持 1.8~4.35V。
整套系统采用"轮毂-辐条"(hub and spoke)结构:
- 主机(Master):ESP8266 控制器,负责 WiFi 监控、数据上报与均衡决策,I2C 总线初始化 在 SDA=GPIO4、SCL=GPIO5 上以 100kHz 时钟通信,并设置了 1000ms 超时:
Wire.setTimeout(1000); // 1000ms 超时 Wire.setClock(100000); // 100kHz Wire.begin(4, 5); // SDA/SCL- 从机(Slave):每个电池包模块上的一颗 ATTINY85,通过板上的 ADUM1250 数字隔离芯片与主总线隔离,负责本串电压、温度的采集与均衡负载开关。
I2C 天然是"两级寻址"协议:7 位从机地址找到是哪块板,随后发送的命令字节决定"做什么"。理解 diyBMS 协议,本质上就是理解这两级设计。
2️⃣ I2C 地址设计:默认地址 21 与 24~48 号段
diyBMS 的地址规划非常克制,见 地址定义:
| 地址 | 用途 | 说明 |
|---|---|---|
| 21 | 出厂默认地址 | 新模块全部"挤"在这个地址上,绿灯闪烁特定图案提示"未配置" |
| 24 ~ 48 | 已配置模块号段 | 最多容纳 24 个模块(对应 24S 电池组) |
为什么需要两个号段?因为出厂时所有模块地址相同(21),上电后不可能同时工作——必须有一套机制把它们"拆开"。这正是下文自动分配机制要解决的问题。
3️⃣ 命令总表:第 6 位是"命令标志位"
协议中最巧妙的一笔在 bms_values.h:命令字节复用第 6 位(COMMAND_BIT)作为"我是一条命令"的标记。这样同一个 1 字节既能表示"执行命令",又能表示"读取数据":
uint8_t cmdByte(uint8_t cmd) { bitSet(cmd, COMMAND_BIT); // 置位第6位 → 命令字节 return cmd; }模块端收到字节后检查该位:置位 → 按命令处理(后面通常还跟着参数);未置位 → 按读取请求处理(主机随后会来收数据)。
写入类命令(ID 1~9)
| ID | 常量 | 参数 | 作用 |
|---|---|---|---|
| 1 | COMMAND_green_led_pattern | 1 字节位图 | 自定义绿灯闪烁图案(诊断用) |
| 3 | COMMAND_factory_default | 无 | 恢复出厂设置并重启 |
| 4 | COMMAND_set_slave_address | 1 字节新地址 | 自动分配的核心命令,写入 EEPROM 后重启生效 |
| 5 | COMMAND_green_led_default | 无 | 恢复默认绿灯图案 |
| 6 | COMMAND_set_voltage_calibration | 4 字节浮点 | 设置电压校准系数(默认 4.430) |
| 7 | COMMAND_set_temperature_calibration | 4 字节浮点 | 设置温度校准系数(默认 1.080) |
| 8 | COMMAND_set_bypass_voltage | 2 字节 mV | 下发均衡目标电压(0 = 关闭均衡) |
| 9 | COMMAND_set_load_resistance | 4 字节浮点 | 设置均衡负载电阻值(默认 10.0Ω) |
完整实现见 i2c_cmds.cpp 命令封装。
读取类命令(ID 10~18)
| ID | 常量 | 返回 | 作用 |
|---|---|---|---|
| 10 | read_voltage | 2 字节 mV | 校准后的电池电压(均衡进行中时返回负载断开瞬间的读数) |
| 11 | read_temperature | 2 字节 | 板载温度(NTC 热敏电阻) |
| 12 | read_voltage_calibration | 4 字节浮点 | 读出电压校准系数 |
| 13 | read_temperature_calibration | 4 字节浮点 | 读出温度校准系数 |
| 14 | read_raw_voltage | 2 字节 | 原始 ADC 值(验证校准是否合理的依据) |
| 15 | read_error_counter | 2 字节 | 通信错误计数器(I2C 异常、总线卡死都会累加) |
| 16 | read_bypass_enabled_state | 1 字节 | 均衡负载当前是否开启 |
| 17 | read_bypass_voltage_measurement | 2 字节 mV | 均衡负载开启时的电压采样 |
| 18 | read_load_resistance | 4 字节浮点 | 读出均衡负载电阻值 |
模块端对应的分发逻辑在 requestEvent() 请求应答。
4️⃣ 自动地址分配机制:新模块如何"对上号"
这是 diyBMS 协议最实用的设计——新增电池模块无需任何跳线设置,流程如下(provision() 源码):
- 在控制器网页管理界面点击添加模块,Web 处理器 置位
runProvisioning标志; - 控制定时器检测到标志后调用
provision():先探测地址 21 上是否有模块(testModuleExists即发一个空 I2C 事务看返回值是否为 0); - 从 24 开始逐个探测到 48,找到第一个空闲地址;
- 向地址 21 下发
COMMAND_set_slave_address(值 = 空闲地址); - 模块端校验新地址必须落在 24~48 区间且不同于当前地址(地址写入与重启),通过则写入 EEPROM、重启;
- 重启后模块以新地址上线,控制器把它加入轮询数组
cell_array。
控制器每次上电还会执行一次全总线扫描 scani2cBus():遍历 24~48,发现存在的模块就读取电压、温度与全部校准参数,并顺手把残留的均衡状态关闭——所以控制器重启不需要重新配置,模块侧 EEPROM 里保存着完整状态。
5️⃣ 数据格式:大端序与 4 字节浮点
命令和数据之外,协议对字节序也有明确约定(字节拆分实现):
- 2 字节数值(mV 电压、温度等):高字节先发,低字节后发,主机端用
word(high, low)合并; - 4 字节浮点(校准系数等):C 的
union让float和uint8_t buffer[4]共享同一块内存,直接按字节流传输,两端字节序一致即可还原; - 一次典型读取 = "主机发 1 字节命令 → 主机再向该地址请求 N 字节数据",N 由命令决定(1/2/4)。
6️⃣ 容错设计:恐慌模式、看门狗与 EEPROM 校验
一个可靠的 BMS 协议必须假设"总会出错"。diyBMS 的对策(loop 主循环):
- 恐慌模式(Panic):模块用
last_i2c_request倒计时(每次收到请求重置为 150),一旦超时说明主机失联——绿灯切换为恐慌闪烁图案B01010101、重建 I2C 接口、累加error_counter;主机恢复通信后自动退出恐慌。用 ID 15 读错误计数器就能远程判断链路质量; - 看门狗定时器:4 秒超时硬重启,兜底所有软件死锁;
- 配置完整性:地址、电压/温度校准、负载电阻存于 EEPROM 并带 CRC32 校验(配置加载),校验失败即回落到出厂默认值;
- 安全边界:均衡目标电压被硬件端限制在 3000~4200mV 且必须低于当前实测电压,越界参数直接拒绝。
7️⃣ 相关源码文件
| 文件 | 职责 |
|---|---|
| Arduino-ESP8266-BMS-Controller/Arduino-ESP8266-BMS-Controller.ino | ESP8266 主控制器:总线扫描、模块轮询、均衡策略 |
| Arduino-BMS-Cell-Module/Arduino-BMS-Cell-Module.ino | ATTINY85 模块固件:ADC 采集、命令处理、EEPROM 存储 |
| Arduino-ESP8266-BMS-Controller/i2c_cmds.cpp | I2C 命令封装、provision()自动分配、模块探测 |
| Arduino-ESP8266-BMS-Controller/bms_values.h | 全部命令 ID 定义与cell_module数据结构 |
| Arduino-ESP8266-BMS-Controller/settings.cpp | 控制器侧 EEPROM 配置(WiFi、均衡阈值等) |
| Arduino-BMS-Controller-Test/Arduino-BMS-Controller-Test.ino | 协议测试工程 |
| docs/homePage.html | 控制器 Web 监控页面 |
小结:diyBMS 用"7 位从机地址 + 1 字节命令"完成了全部通信,第 6 位命令标志区分读写,大端序 + 4 字节浮点保证数据无歧义,而"默认地址 21 + 24~48 空闲探测 + 写 EEPROM 重启"三步曲让 24 块电池板免去了任何人工配置——这是一个教科书级的轻量 I2C 多从机管理方案,值得所有 DIY 电池项目参考。
⚠️ 提示:本项目为 DIY 设计,不适用于安全关键场景;README 亦注明该设计已被作者的 v4 版本取代,本文仅作 I2C 协议原理学习之用。
【免费下载链接】diyBMSDo it yourself battery management system for Lithium ion battery packs/cells项目地址: https://gitcode.com/gh_mirrors/diy/diyBMS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考