✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 "配置环境" 转移到 "创意实现",极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
目录
[1.1 硬件清单](#1.1 硬件清单)
[1.2 接线方案表](#1.2 接线方案表)
[1.3 具体接线图](#1.3 具体接线图)
[1.4 接线实物图](#1.4 接线实物图)
[3.1 数据采集与滤波](#3.1 数据采集与滤波)
[3.2 模式自动识别](#3.2 模式自动识别)
[3.3 状态圈动态显示](#3.3 状态圈动态显示)
[3.4 JSON数据接口](#3.4 JSON数据接口)
[3.5 主程序接口](#3.5 主程序接口)
[4.1 操作流程](#4.1 操作流程)
[4.2 视频演示](#4.2 视频演示)
[5.1 寄存器映射](#5.1 寄存器映射)
[5.2 I2C总线通信原理](#5.2 I2C总线通信原理)
项目概述
本项目基于零知ESP32 开发板和INA219电流传感器,实现了一个功能完整的锂电池充放电智能监测系统。系统能够实时监测电池的电压、电流、功率等关键参数,并通过TFT显示屏可视化波形显示,同时支持Web远程监控和风扇智能控制
项目难点及解决方案
问题描述:仅读取电流数值无法区分充电 / 放电状态
**解决方案:**利用 INA219 分流电压的极性特性,结合总线电压变化趋势辅助验证
一、系统接线部分
1.1 硬件清单
| 组件 | 数量 | 参数/型号 | 说明 |
|---|---|---|---|
| 零知ESP32开发板 | 1 | ESP32-WROOM-32 | 主控MCU |
| INA219电流传感器 | 1 | I2C地址0x40 | 电流/电压监测 |
| ST7789 TFT屏 | 1 | 320×240 SPI | 显示屏 |
| TP4056充电板 | 1 | 1A充电电流 | 锂电池充电管理 |
| 18650电池+座 | 1 | 3.7V 3500mAh | 被测电池 |
| 直流风扇 | 1 | L9110 风扇模块 | 放电负载 |
| USB Type-C线 | 1 | 数据+电源 | 充电接口 |
| 杜邦线 | 若干 | 公对母/母对母 | 连接线 |
1.2 接线方案表
根据
config.h文件定义的引脚,接线如下:
| 零知 ESP32引脚 | 连接模块 | 功能说明 |
|---|---|---|
| GPIO21 | INA219 SDA | I²C数据线 |
| GPIO22 | INA219 SCL | I²C时钟线 |
| GPIO15 | TFT CS | 片选信号 |
| GPIO13 | TFT DC | 数据/命令选择 |
| GPIO4 | TFT RST | 复位信号 |
| GPIO23 | TFT SDA | SPI数据输出 |
| GPIO18 | TFT SCL | SPI时钟 |
| GPIO25 | 风扇 INA | PWM控制信号 |
| 3.3V | 模块VCC | 电源正极 |
| GND | 模块GND | 电源地线 |
双向电流检测务必按照以下方式接线:
充电回路
| INA219引脚 | 连接目标 | 说明 |
|---|---|---|
| VIN+ | 充电板输出 B+ | 高侧电压检测点 |
| VIN- | 电池正极 | 低侧电压检测点 |
充电时:电流从充电器流出 -> 进入 VIN+ -> 流向 VIN- -> 进入电池。此时电流方向为正 (+),系统识别为充电。
放电回路
| INA219引脚 | 连接目标 | 说明 |
| VIN+ | 负载(风扇)正极 | 高侧电压检测点 |
| VIN- | 电池正极 | 低侧电压检测点 |
|---|
放电时:电流从电池流出 -> 进入 VIN- -> 流向 VIN+ -> 进入负载。此时电流方向为负 (-),系统识别为放电。
1.3 具体接线图
注意:电池负极、负载负极、充电器负极全部共地 (GND),INA219使用5V供电
1.4 接线实物图

二、安装与使用部分
2.1 开源平台-输入"INA219电流传感器"并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器

三、核心代码解析
项目文件结构
ESP32_Battery_Monitor/
├── ESP32_Battery_Monitor.ino # 主程序
├── config.h # 配置文件
├── BatteryMonitor.h/cpp # 电池监测类
├── DisplayHandler.h/cpp # 显示控制类
├── FanController.h/cpp # 风扇控制类
└── WebHandler_Battery.h/cpp # Web服务器类
3.1 数据采集与滤波
cpp
void BatteryMonitor::updateSensorData() {
float rawCurrent = ina219.getCurrent_mA();
// 关键:滑动窗口平均滤波
filteredCurrent = filterCurrentValue(rawCurrent);
// 读取其他参数
currentData.shuntVoltage = ina219.getShuntVoltage_mV();
currentData.voltage = ina219.getBusVoltage_V();
currentData.power = ina219.getPower_mW();
// 保存绝对值用于显示
currentData.current = abs(filteredCurrent);
}
float BatteryMonitor::filterCurrentValue(float newValue) {
// 滑动窗口队列
currentBuffer[filterIndex] = newValue;
filterIndex = (filterIndex + 1) % FILTER_WINDOW_SIZE;
// 计算平均值
float sum = 0;
for (int i = 0; i < FILTER_WINDOW_SIZE; i++) {
sum += currentBuffer[i];
}
return sum / FILTER_WINDOW_SIZE;
}
滤波效果对比:
| 情况 | 原始电流波动 | 滤波后波动 | 改善率 |
|---|---|---|---|
| 充电 | 800±50mA | 800±10mA | 80% |
| 放电 | -300±80mA | -300±15mA | 81% |
| 待机 | ±20mA | ±5mA | 75% |
3.2 模式自动识别
cpp
void BatteryMonitor::updateMode() {
BatteryMode oldMode = currentMode;
// 关键:基于滤波后电流判断
if (filteredCurrent > CURRENT_THRESHOLD) {
currentMode = MODE_CHARGING; // 正值 = 充电
} else if (filteredCurrent < -CURRENT_THRESHOLD) {
currentMode = MODE_DISCHARGING; // 负值 = 放电
} else {
currentMode = MODE_STANDBY; // 接近0 = 待机
}
// 模式切换处理
if (currentMode != oldMode) {
modeStartTime = millis();
totalCapacity_mAh = 0; // 重置累计容量
smoothedTimeEst = -1; // 重置时间预估
}
}
电流范围在+15 ~ -15mA 内判断结果为MODE_STANDBY (死区)
3.3 状态圈动态显示
cpp
void DisplayHandler::drawStatusCircle(BatteryMode mode) {
int x = 220, y = 10, r = 8;
// 清除旧圈
tft.fillCircle(x, y, r + 2, COLOR_BACKGROUND);
uint16_t c;
const char* t;
// 根据模式选择颜色和文字
if (mode == MODE_CHARGING) {
c = COLOR_CHARGE; // 0x07E0 绿色
t = "CHARGING";
} else if (mode == MODE_DISCHARGING) {
c = COLOR_DISCHARGE; // 0xF800 红色
t = "DISCHARGE";
} else {
c = COLOR_STANDBY; // 0x8410 灰色
t = "STANDBY";
}
tft.print(t);
tft.fillCircle(x, y, r, c);
tft.drawCircle(x, y, r, COLOR_TEXT);
}
采用RGB565颜色编码
RGB565格式:RRRRR GGGGGG BBBBB (5+6+5=16位)
比如,灰色 0x8410 = 10000 100000 10000 = (132, 132, 132)
3.4 JSON数据接口
cpp
void WebHandlerBattery::handleData() {
BatteryData data = Battery.getData(); // 单一数据源
BatteryMode mode = Battery.getCurrentMode();
String json = "{";
json += "\"voltage\":" + String(data.voltage, 2);
json += ",\"current\":" + String(data.current, 1);
json += ",\"power\":" + String(abs(data.power), 1);
json += ",\"battery\":" + String(data.batteryPercent, 1);
json += ",\"capacity\":" + String(data.capacity, 1);
json += ",\"mode\":" + String((int)mode);
json += ",\"fan\":" + String(Fan.isRunning() ? "true" : "false");
json += ",\"pwm\":" + String((int)((Fan.getCurrentDuty() / 255.0) * 100));
float timeEst = (mode == MODE_CHARGING) ? Battery.estimateTimeToFull() : Battery.estimateTimeToEmpty();
json += ",\"timeEst\":" + String(timeEst, 0);
if (logBuffer.length() > 0) {
json += ",\"log\":\"" + logBuffer + "\"";
logBuffer = "";
}
json += "}";
server.send(200, "application/json", json);
}
将电压、电流、状态等数据封装为 JSON 格式,实现WebSocket 主动推送数据到ESP32
3.5 主程序接口
cpp
/**************************************************************************************
* 文件: /ESP32_Battery_Monitor/ESP32_Battery_Monitor.ino
* 作者:零知实验室(深圳市在芯间科技有限公司)
* -^^- 零知实验室,让电子制作变得更简单! -^^-
* 时间: 2026-1-13
* 功能特性:
* 单INA219监测: 0x40 (自动识别充放电)、TFT实时波形: 电压/电流/功率三条曲线动态显示 (防抖动优化)、Web网页监控: 修复ESP32 3.x兼容性问题
* PWM循环控制: 风扇转速周期性变化、智能算法: 时间预估平滑处理,防止数值跳动
* 访问方式:
* 连接WiFi热点: ESP32_Battery_Monitor (密码: 12345678),浏览器打开: http://192.168.4.1
* ***************************************************************************************/
#include "config.h"
#include "BatteryMonitor.h"
#include "DisplayHandler.h"
#include "FanController.h"
#include "WebHandler_Battery.h"
// 全局变量
unsigned long lastPrintTime = 0;
unsigned long lastLowVoltageCheck = 0;
void setup() {
Serial.begin(DEBUG_BAUD_RATE);
delay(1000);
DEBUG_PRINTLN("\n\n");
DEBUG_PRINTLN("╔═════════════════════╗");
DEBUG_PRINTLN("║ ESP32 锂电池充放电监测系统 ║");
DEBUG_PRINTLN("╚═════════════════════╝");
DEBUG_PRINTLN();
DEBUG_PRINTLN("=== 系统初始化开始 ===\n");
// 1. 初始化TFT显示屏
DEBUG_PRINTLN("[1/4] 初始化TFT显示屏...");
Display.begin();
// 2. 初始化INA219电池监测
DEBUG_PRINTLN("\n[2/4] 初始化INA219监测模块...");
if (!Battery.begin()) {
DEBUG_PRINTLN("❌ INA219初始化失败,系统停止");
Display.showLowVoltageWarning();
while(1) { delay(100); }
}
// 3. 初始化PWM风扇控制
DEBUG_PRINTLN("\n[3/4] 初始化PWM风扇控制...");
Fan.begin();
// 4. 初始化Web服务器
DEBUG_PRINTLN("\n[4/4] 初始化Web服务器...");
WebBattery.begin();
DEBUG_PRINTLN("\n=== 系统初始化完成 ===");
DEBUG_PRINTLN("=== 开始监测 ===\n");
delay(1000);
}
void loop() {
Battery.update();
Fan.updateCycleMode();
Display.update();
WebBattery.loop();
checkLowVoltage();
printDebugInfo();
delay(10);
}
void checkLowVoltage() {
unsigned long now = millis();
if (now - lastLowVoltageCheck >= 2000) {
if (Battery.isLowVoltage()) {
DEBUG_PRINTLN("\n⚠️⚠️⚠️ 低电压保护触发 ⚠️⚠️⚠️");
Fan.stop();
Display.showLowVoltageWarning();
while(1) { delay(100); }
}
lastLowVoltageCheck = now;
}
}
void printDebugInfo() {
#if DEBUG_ENABLE
unsigned long now = millis();
if (now - lastPrintTime >= 3000) {
BatteryMode mode = Battery.getCurrentMode();
BatteryData data = Battery.getData();
DEBUG_PRINTLN("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
const char* modeStr[] = {"待机", "充电", "放电"};
DEBUG_PRINTF("⚡ 模式: %s\n", modeStr[mode]);
DEBUG_PRINTLN("\n📊 电池数据:");
DEBUG_PRINTF(" 电压: %.2f V\n", data.voltage);
DEBUG_PRINTF(" 电流: %.1f mA\n", data.current);
DEBUG_PRINTF(" 功率: %.1f mW\n", data.power);
DEBUG_PRINTF(" 电量: %.1f %%\n", data.batteryPercent);
DEBUG_PRINTF(" 容量: %.1f mAh\n", data.capacity);
if (Fan.isRunning()) {
float percent = (Fan.getCurrentDuty() / 255.0) * 100.0;
DEBUG_PRINTF("\n🌀 风扇: 运行中 (PWM: %.0f%%)\n", percent);
} else {
DEBUG_PRINTLN("\n🌀 风扇: 停止");
}
if (mode == MODE_CHARGING || mode == MODE_DISCHARGING) {
float timeEst = (mode == MODE_CHARGING) ? Battery.estimateTimeToFull() : Battery.estimateTimeToEmpty();
if (timeEst > 0) {
int hours = (int)(timeEst / 60);
int mins = (int)(timeEst) % 60;
DEBUG_PRINTF("\n⏱ 预计时间: %dh %dm\n", hours, mins);
}
}
DEBUG_PRINTF("\n💾 内存: %d KB 可用\n", ESP.getFreeHeap() / 1024);
DEBUG_PRINTLN("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
lastPrintTime = now;
}
#endif
}
/******************************************************************************
* 深圳市在芯间科技有限公司
* 淘宝店铺:在芯间科技零知板
* 店铺网址:https://shop533070398.taobao.com
* 版权说明:
* 1.本代码的版权归【深圳市在芯间科技有限公司】所有,仅限个人非商业性学习使用。
* 2.严禁将本代码或其衍生版本用于任何商业用途(包括但不限于产品开发、付费服务、企业内部使用等)。
* 3.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/
系统流程图

容量积分计算
// 安时积分:电流 × 时间
totalCapacity_mAh += abs(filteredCurrent) * deltaTime_h;
计算公式:

充放电分别累计,每次模式切换清零重新计算
四、项目结果演示
按接线表完成零知 ESP32、INA219、锂电池、TP4056充电板的接线,确认正负极无误
4.1 操作流程
①系统启动
上电后TFT显示启动画面2秒、自动连接INA219传感器、启动Web服务器(AP模式)
②功能验证
TFT屏显示实时波形和数据面板、连接手机WiFi热点:ESP32_Battery_Monitor和密码:12345678、浏览器访问:http://192.168.4.1

③测试场景
充电测试:连接充电器观察充电状态
放电测试:连接负载观察放电过程、风扇控制:Web界面控制风扇启停

在浏览器中输入串口输出的 ESP32 IP 地址(192.168.4.1),打开监测页面,断开负载,接锂电池充电器,电流变为正值,电压缓慢上升,状态显示 "Charging"
④数据导出

点击网页"导出数据"按钮,将充电中实时电压、电流和功率通过excel表格展示,"清空记录"按钮可以将数据清零,重新开始记录
4.2 视频演示
零知ESP32+INA219:锂电池智能监测系统全功能演示
零知ESP32和INA219电流传感器的锂电池充放电监测系统包括,系统硬件组成和接线详解、TFT显示屏实时波形显示效果、充电/放电状态自动识别演示、Web远程监控界面操作、PWM风扇智能控制功能、低电压保护触发测试、系统数据记录和导出功能
五、INA219电流传感器知识点讲解
INA219是一款基于I²C接口的零漂移、双向电流/功率监测传感器。其核心工作原理如下:

根据芯片手册,经过分流电阻N(采样电阻)后,能够采集到的最低有效电压LSB为10uV。
利用欧姆定律计算电流公式:
电流测量:根据Rshunt(0.1Ω)分流电阻两端的电压降计算;电压测量:直接测量总线电压(支持0-26V范围);功率计算:内部乘法器实时计算功率
5.1 寄存器映射
INA219内部有16个寄存器,本项目使用的主要寄存器:
| 地址 | 寄存器名称 | 功能 | 本项目配置 |
|---|---|---|---|
| 0x00 | Configuration | 配置寄存器 | 0x399F |
| 0x01 | Shunt Voltage | 分流电压 | 只读 |
| 0x02 | Bus Voltage | 总线电压 | 只读 |
| 0x03 | Power | 功率 | 只读 |
| 0x04 | Current | 电流 | 只读 |
| 0x05 | Calibration | 校准寄存器 | 0x1000 |
校准寄存器(0x05)

校准值计算公式:
其中电流LSB = 最大预期电流 / 32768
Current_LSB=10010^-6=100uA=0.0001A
计算基准值:Cal=0.04096/(Current_LSB/R)=0.04096/(0.0001A0.1R)=4096=0x1000
校准寄存器与缩放

如果发现测量到的电流值有误,用电流表测到的实际值为0.290A,INA219测量结果为0.342A
采用Cal的校准公式(缩放校准后的)Cal=4096*0.290/0.3421 = 3472 = 0x0D90
5.2 I2C总线通信原理
INA219 采用标准 I2C 通信协议,SDA(数据)和 SCL(时钟)为双向引脚
1)串行总线地址

INA219 有两个地址引脚A0 和 A1都设置为GND,该从机地址为0x40
从机地址可通过模块上的 A0和A1 引脚短接修改(只短接A0 从机地址为0x41,只短接A1 从机地址为0x44,短接A0和A1 从机地址0x45)
2)软件I2C时序

总线上的所有从机在 SCL 的上升沿移入从机地址字节,其中最后一位指示要进行的是读操作还是写操作。在第九个时钟脉冲期间,被寻址的从机通过生成确认信号并将 SDA 拉至低电平来响应主机
六、常见问题解答(FAQ)
Q1:为什么电流测量值跳动很大?
*A:尝试以下解决方案:*增加FILTER_WINDOW_SIZE滤波窗口大小
Q2:网页无法访问,或数据不刷新?
*A:请解决:*串口查看 IP 地址是否获取成功,确保设备连接同一路由器,修改 WebSocket 端口;确保server.handleClient()和webSocket.loop()在主循环中执行
项目资源整合
INA219数据手册: INA219 DataSheet
INA219 库: RobTillaart/INA219