一款基于零知派标准板的高精度电流/电压/功率监测解决方案
✔零知派(零知开源)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 "配置环境" 转移到 "创意实现",极大降低了技术门槛。零知开源编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知派(零知开源)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
目录
[1.1 硬件清单](#1.1 硬件清单)
[1.2 接线方案表](#1.2 接线方案表)
[1.3 具体接线图](#1.3 具体接线图)
[1.4 连接实物图](#1.4 连接实物图)
[2.1 系统初始化](#2.1 系统初始化)
[2.2 数据采集与处理](#2.2 数据采集与处理)
[2.3 UI显示功能](#2.3 UI显示功能)
[2.4 INA219库文件解析](#2.4 INA219库文件解析)
[3.1 监测过程与比较](#3.1 监测过程与比较)
[3.2 万用表对比测试](#3.2 万用表对比测试)
[3.3 视频演示](#3.3 视频演示)
[四、INA219 电流功率监测计技术讲解](#四、INA219 电流功率监测计技术讲解)
[4.1 INA219工作原理](#4.1 INA219工作原理)
[4.2 I2C通信协议](#4.2 I2C通信协议)
[4.3 寄存器配置](#4.3 寄存器配置)
[Q2:软件 I2C 通信不稳定怎么办?](#Q2:软件 I2C 通信不稳定怎么办?)
(1)项目概述
本项目基于STM32F103RBT6 微控制器和零知派INA219 电流功率监测计,实现了一个高精度的电源监控系统。该系统能够实时监测电路中的总线电压 、电流消耗 和功率输出,并通过ST7789 TFT显示屏进行可视化展示。通过软件I2C(SoftWire)驱动INA219传感器,实现了稳定的数据采集和实时波形显示
(2)项目难点及解决方案
问题描述:INA219 库默认使用硬件 I2C 通信,与零知标准板的 STM32F103RBT6 存在兼容性问题
**解决方案:**将INA219库中所有TwoWire类型替换为SoftWire类型;调整构造函数,使其接受SoftWire指针参数
一、硬件系统设计
1.1 硬件清单
| 序号 | 元件名称 | 型号规格 | 数量 |
|---|---|---|---|
| 1 | 主控板 | 零知派标准板(STM32F103RBT6) | 1 |
| 2 | 电流功率监测计 | 零知派INA219 | 1 |
| 3 | 显示屏 | ST7789(240×320) | 1 |
| 5 | 杜邦线 | 公对公、公对母 | 若干 |
| 6 | USB 数据线 | Mini USB | 1 |
| 7 | LED 模块 | 5mm 发光二极管 | 1 |
1.2 接线方案表
| 元件 | 引脚 | 连接到 零知派标准板的引脚 | 备注 |
|---|---|---|---|
| INA219 | SCL | A5 | 软件 I2C 时钟线 |
| INA219 | SDA | A4 | 软件 I2C 数据线 |
| INA219 | VCC | 3.3V | 传感器电源 |
| INA219 | GND | GND | 接地 |
| INA219 | Vin+ | 被测电源正极/3.3V | |
| INA219 | Vin- | 负载正极 | 通过 0.1Ω 电阻连接到 Vin+ |
| ST7789 | 直插 | 直插零知派标准板TFT扩展引脚 | 硬件 SPI通信 |
| ST7789 | |||
| ST7789 | |||
| ST7789 | |||
| ST7789 | |||
| ST7789 |
INA219电流功率监测计的Vin+接3.3V供电电源、Vin-接负载(本项目连接到LED模块IN引脚)
1.3 具体接线图
INA219与零知派标准板通过软件I2C进行通信;各器件接线、LED模块按照代码接线所示:

1.4 连接实物图

二、软件架构设计
2.1 系统初始化
cpp
void setup(void) {
Serial.begin(115200); // 初始化串口通信
tft.init(240, 320); // 初始化TFT显示屏
tft.invertDisplay(false); // 设置显示方向
tft.setRotation(1); // 旋转显示屏
tft.fillScreen(BACKGROUND); // 清屏
showSplashScreen(); // 显示启动画面
delay(1500);
sw.begin(); // 初始化软件I2C
sw.setClock(100000); // 设置I2C时钟频率为100kHz
if (!INA.begin()) { // 初始化INA219传感器
Serial.println("Failed to find INA219 chip");
while (1) { delay(10); } // 初始化失败则死循环
}
// 校准传感器:最大电流0.5A,分流电阻0.1Ω
if (!INA.setMaxCurrentShunt(0.5, 0.1)) {
Serial.println("Calibration failed!");
while(1); // 校准失败则死循环
}
// 初始化历史数据缓冲区
for (int i = 0; i < HISTORY_SIZE; i++) {
voltageHistory[i] = 0;
currentHistory[i] = 0;
powerHistory[i] = 0;
}
drawStaticUI(); // 绘制静态UI界面
Serial.println("System initialized");
Serial.print("INA219_LIB_VERSION: ");
Serial.println(INA219_LIB_VERSION);
}
初始化软件 I2C 并设置通信速率,进行INA219传感器校准;初始化历史数据缓冲区,用于存储绘图数据
2.2 数据采集与处理
cpp
void loop(void) {
// 读取传感器数据
float busVoltage = INA.getBusVoltage(); // 读取总线电压(V)
float current = INA.getCurrent_mA() - 0.8; // 读取电流(mA)并进行零点校准
float power = INA.getPower_mW(); // 读取功率(mW)
// 存储历史数据
voltageHistory[historyIndex] = busVoltage;
currentHistory[historyIndex] = current;
powerHistory[historyIndex] = power;
// 更新UI显示
updateUI(busVoltage, current, power);
// 更新历史数据索引(循环缓冲区)
historyIndex = (historyIndex + 1) % HISTORY_SIZE;
// 串口输出数据
Serial.println("\n\tBUS\t\tSHUNT\t\tCURRENT\t\tPOWER\t\tOVF\t\tCNVR");
Serial.print("\t");
Serial.print(busVoltage, 2);
Serial.print("\t\t");
Serial.print(INA.getShuntVoltage_mV(), 2);
Serial.print("\t\t");
Serial.print(current, 2);
Serial.print("\t\t");
Serial.print(power, 2);
Serial.print("\t\t");
Serial.print(INA.getMathOverflowFlag()); // 数学溢出标志
Serial.print("\t\t");
Serial.print(INA.getConversionFlag()); // 转换完成标志
Serial.println();
delay(1000); // 1秒刷新一次
}
每秒从 INA219 读取总线电压、电流和功率;使用循环缓冲区将新数据存入历史缓冲区,实现数据的滚动存储
2.3 UI显示功能
cpp
void updateUI(float voltage, float current, float power) {
updatePanelValues(voltage, current, power); // 更新右侧面板的实时数据
// 清除图表区域并重新绘制坐标轴
tft.fillRect(GRAPH_X + 1, GRAPH_Y + 1, GRAPH_WIDTH - 1, GRAPH_HEIGHT - 1, BACKGROUND);
drawAxes();
// 计算最大值用于自动缩放
float maxVal = 0.1; // 确保至少有一个较小的初始值
for (int i = 0; i < HISTORY_SIZE; i++) {
if (voltageHistory[i] > maxVal) maxVal = voltageHistory[i];
if (currentHistory[i] > maxVal) maxVal = currentHistory[i];
if (powerHistory[i] > maxVal) maxVal = powerHistory[i];
}
maxVal *= 1.15; // 增加15%的余量
// 绘制历史曲线
for (int i = 1; i < HISTORY_SIZE; i++) {
// 计算历史索引(循环缓冲区)
int prevIndex = (historyIndex + i - 1) % HISTORY_SIZE;
int currIndex = (historyIndex + i) % HISTORY_SIZE;
// 计算X坐标
int x1 = GRAPH_X + (i-1) * 2;
int x2 = GRAPH_X + i * 2;
if (x2 > GRAPH_X + GRAPH_WIDTH) break; // 超出图表区域则停止
// 绘制电压曲线(黄色)
int y1_voltage = GRAPH_Y + GRAPH_HEIGHT - constrain(voltageHistory[prevIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
int y2_voltage = GRAPH_Y + GRAPH_HEIGHT - constrain(voltageHistory[currIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
tft.drawLine(x1, y1_voltage, x2, y2_voltage, VOLTAGE_COLOR);
// 绘制电流曲线(绿色)
int y1_current = GRAPH_Y + GRAPH_HEIGHT - constrain(currentHistory[prevIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
int y2_current = GRAPH_Y + GRAPH_HEIGHT - constrain(currentHistory[currIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
tft.drawLine(x1, y1_current, x2, y2_current, CURRENT_COLOR);
// 绘制功率曲线(青色)
int y1_power = GRAPH_Y + GRAPH_HEIGHT - constrain(powerHistory[prevIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
int y2_power = GRAPH_Y + GRAPH_HEIGHT - constrain(powerHistory[currIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
tft.drawLine(x1, y1_power, x2, y2_power, POWER_COLOR);
}
// 绘制Y轴刻度值
tft.setTextColor(0xAD75);
tft.setTextSize(1);
tft.setCursor(GRAPH_X + 2,GRAPH_Y + 5);
tft.print(maxVal, 1); // 最大值
tft.setCursor(GRAPH_X + 2,GRAPH_Y + GRAPH_HEIGHT/2 + 5);
tft.print(maxVal/2, 1); // 中间值
tft.setCursor(GRAPH_X - 5, GRAPH_Y + GRAPH_HEIGHT + 5);
tft.print("0"); // 最小值
}
采用自动缩放机制,根据历史数据的最大值动态调整 Y 轴范围;使用不同颜色绘制三条曲线,分别表示电压、电流和功率
项目代码
cpp
/**************************************************************************************
* 文件: /STM32F103_INA219_RealTime_PowerMonitor/STM32F103_INA219_RealTime_PowerMonitor.ino
* 作者:零知派(深圳市在芯间科技有限公司)
* -^^- 零知派,让电子制作变得更简单! -^^-
* 时间: 2026-4-16 15:30:45
* 说明: 基于零知派标准板(STM32F103RBT6主控)和零知派INA219电流功率监测计,通过软件I2C(SoftWire)实现传感器稳定通信。
* 实时采集总线电压、负载电流及功率数据,在ST7789 TFT屏可视化展示实时数值与历史趋势曲线,同步串口输出数据供调试分析。
***************************************************************************************/
#include "INA219.h"
#include <Adafruit_GFX.h> // 图形显示基础库(适配ST7789)
#include <Adafruit_ST7789.h> // ST7789 TFT屏硬件驱动库
#include <SoftWire.h> // 软件I2C库
// -------------------------- 硬件引脚定义模块 --------------------------
/**
* ST7789 TFT屏硬件SPI引脚配置
* 硬件SPI固定引脚:SCK=13(时钟)、SDA(MOSI)=11(数据),以下为可配置引脚
*/
#define TFT_CS 10 // TFT片选引脚(低电平有效)
#define TFT_DC 2 // TFT数据/命令选择引脚(高=数据,低=命令)
#define TFT_RST 4 // TFT复位引脚(低电平复位,可不接则设为-1)
// -------------------------- 全局参数配置模块 --------------------------
/**
* INA219传感器配置
* @param 0x44: INA219默认I2C地址0x40(可通过A0/A1引脚修改为0x41/0x44/0x45)
* @param &sw: 绑定软件I2C实例
*/
SoftWire sw(SCL, SDA, SOFT_STANDARD); // 软件I2C引脚
INA219 INA(0x44, &sw);
/**
* ST7789 TFT屏配置
* 分辨率:240x320(宽x高),旋转后实际显示方向由setRotation(1)决定
*/
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
#define TFT_WIDTH 240 // ST7789屏物理宽度(像素)
#define TFT_HEIGHT 320 // ST7789屏物理高度(像素)
/**
* 波形绘制历史数据配置
* HISTORY_SIZE:波形缓存长度(决定时间轴跨度),建议根据刷新频率调整
*/
const int HISTORY_SIZE = 100; // 最大历史数据点数(100个采样点,对应100秒)
float voltageHistory[HISTORY_SIZE]; // 电压历史数据缓存
float currentHistory[HISTORY_SIZE]; // 电流历史数据缓存
float powerHistory[HISTORY_SIZE]; // 功率历史数据缓存
int historyIndex = 0; // 历史数据当前写入索引(循环覆盖)
/**
* 显示界面颜色配置(RGB565格式,5位红+6位绿+5位蓝)
*/
#define BACKGROUND ST77XX_BLACK // 屏幕背景色
#define VOLTAGE_COLOR ST77XX_YELLOW // 电压波形/文本颜色
#define CURRENT_COLOR ST77XX_GREEN // 电流波形/文本颜色
#define POWER_COLOR ST77XX_CYAN // 功率波形/文本颜色
#define TEXT_COLOR ST77XX_WHITE // 通用文本颜色
#define AXIS_COLOR ST77XX_WHITE // 坐标轴颜色
#define PANEL_COLOR 0x18A0 // 数值面板背景色(RGB565:深灰蓝)
/**
* 显示区域坐标配置(像素单位)
* 波形区:左上方主要区域;数值面板:右侧固定面板
*/
#define GRAPH_WIDTH 200 // 波形显示区域宽度
#define GRAPH_HEIGHT 120 // 波形显示区域高度
#define GRAPH_X 10 // 波形区左上角X坐标
#define GRAPH_Y 60 // 波形区左上角Y坐标
#define PANEL_WIDTH 80 // 实时数值面板宽度
#define PANEL_X 240 // 数值面板左上角X坐标(补充:原代码240超出屏宽,建议改为150)
#define PANEL_Y 0 // 数值面板左上角Y坐标
// -------------------------- 初始化函数 --------------------------
/**
* @brief 系统初始化入口函数
* @details 完成串口、TFT屏、INA219传感器、数据缓存、静态UI的初始化
*/
void setup(void) {
// 1. 串口初始化(调试用,波特率115200)
Serial.begin(115200);
// 2. TFT屏初始化
tft.init(TFT_WIDTH, TFT_HEIGHT); // 初始化屏显参数
tft.invertDisplay(false); // 关闭显示反转(true为反色显示)
tft.setRotation(1); // 旋转屏幕(0-3,1为90度顺时针)
tft.fillScreen(BACKGROUND); // 清屏(背景色)
showSplashScreen(); // 显示启动画面
delay(1500); // 启动画面停留1.5秒
// 3. INA219传感器初始化
sw.begin(); // 启动软件I2C
sw.setClock(100000); // 设置I2C时钟频率(100kHz,INA219最大支持400kHz)
if (!INA.begin()) { // 检测INA219是否连接
Serial.println("Failed to find INA219 chip");
while (1) { delay(10); } // 硬件错误,死循环报错
}
// 校准INA219(最大预期电流0.5A,分流电阻0.1Ω)
if (!INA.setMaxCurrentShunt(0.5, 0.1)) {
Serial.println("Calibration failed!");
while(1); // 校准失败,死循环报错
}
// 4. 历史数据缓存初始化(清零)
for (int i = 0; i < HISTORY_SIZE; i++) {
voltageHistory[i] = 0;
currentHistory[i] = 0;
powerHistory[i] = 0;
}
// 5. 绘制静态UI框架(仅初始化时绘制一次)
drawStaticUI();
// 初始化完成提示
Serial.println("System initialized");
Serial.print("INA219_LIB_VERSION: ");
Serial.println(INA219_LIB_VERSION);
}
// -------------------------- 主循环函数 --------------------------
/**
* @brief 系统主循环
* @details 每秒采集一次数据,更新缓存、UI和串口输出,循环执行
*/
void loop(void) {
// 1. 读取INA219数据(核心传感器数据采集)
float busVoltage = INA.getBusVoltage(); // 读取总线电压(V)
float current = INA.getCurrent_mA() - 0.8; // 读取电流(mA),减去0.8mA校准零点偏移
float power = INA.getPower_mW(); // 读取功率(mW)
// 2. 存储数据到历史缓存(循环覆盖)
voltageHistory[historyIndex] = busVoltage;
currentHistory[historyIndex] = current;
powerHistory[historyIndex] = power;
// 3. 更新屏幕显示(实时数值+波形)
updateUI(busVoltage, current, power);
// 4. 更新缓存索引(循环复用缓存)
historyIndex = (historyIndex + 1) % HISTORY_SIZE;
// 5. 串口输出调试信息(格式化打印)
Serial.println("\n\tBUS(V)\t\tSHUNT(mV)\tCURRENT(mA)\tPOWER(mW)\tOVF\t\tCNVR");
Serial.print("\t");
Serial.print(busVoltage, 2); // 总线电压(保留2位小数)
Serial.print("\t\t");
Serial.print(INA.getShuntVoltage_mV(), 2); // 分流电阻电压(mV)
Serial.print("\t\t");
Serial.print(current, 2); // 电流(mA)
Serial.print("\t\t");
Serial.print(power, 2); // 功率(mW)
Serial.print("\t\t");
Serial.print(INA.getMathOverflowFlag()); // 数学溢出标志(1=溢出,需重新校准)
Serial.print("\t\t");
Serial.print(INA.getConversionFlag()); // 转换完成标志(1=数据有效)
Serial.println();
// 6. 采样间隔(1秒,可根据需求调整,最小受INA219转换时间限制)
delay(1000);
}
// -------------------------- 屏幕显示功能模块 --------------------------
/**
* @brief 启动画面显示
* @details 系统初始化时显示品牌/功能提示,提升用户体验
*/
void showSplashScreen() {
tft.fillScreen(BACKGROUND); // 清屏
tft.setTextColor(ST77XX_YELLOW); // 设置文本颜色为黄色
tft.setTextSize(3); // 文本大小(1=8x8像素,3=24x24像素)
tft.setCursor(70, 80); // 设置文本光标位置(X,Y)
tft.print("POWER"); // 打印"POWER"
tft.setCursor(85, 120); // 调整光标位置
tft.print("MONITOR"); // 打印"MONITOR"
tft.setTextColor(ST77XX_CYAN); // 切换文本颜色为青色
tft.setTextSize(1); // 缩小文本大小
tft.setCursor(90, 180); // 调整光标位置
tft.print("Initializing..."); // 打印初始化提示
}
/**
* @brief 绘制静态UI框架
* @details 绘制仅需初始化一次的界面元素(标题、边框、坐标轴、面板、图例)
*/
void drawStaticUI() {
tft.fillScreen(BACKGROUND); // 清屏
// 1. 绘制标题
tft.setTextColor(TEXT_COLOR);
tft.setTextSize(2);
tft.setCursor(50, 5);
tft.print("POWER MONITOR");
// 2. 绘制波形区边框
tft.drawRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT, AXIS_COLOR);
drawAxes(); // 绘制坐标轴
// 3. 绘制实时数值面板(右侧)
tft.fillRect(PANEL_X, PANEL_Y, PANEL_WIDTH, 240, PANEL_COLOR); // 面板背景
tft.drawRect(PANEL_X, PANEL_Y, PANEL_WIDTH, 240, TEXT_COLOR); // 面板边框
tft.setTextColor(TEXT_COLOR);
tft.setTextSize(1);
tft.setCursor(PANEL_X + 10, PANEL_Y + 10);
tft.print("REALTIME");
tft.setCursor(PANEL_X + 15, PANEL_Y + 20);
tft.print("VALUES");
tft.drawFastHLine(PANEL_X, PANEL_Y + 35, PANEL_WIDTH, TEXT_COLOR); // 分隔线
// 4. 绘制波形图例
drawLegend();
// 5. 绘制坐标轴标签
tft.setTextSize(1);
tft.setCursor(GRAPH_X - 5, GRAPH_Y - 15);
tft.print("Value"); // Y轴标签
tft.setCursor(GRAPH_X + GRAPH_WIDTH - 30, GRAPH_Y + GRAPH_HEIGHT + 5);
tft.print("Time (s)"); // X轴标签
// 6. 绘制数值面板静态文本(V/I/P标签)
drawPanelStaticText();
}
/**
* @brief 绘制波形区坐标轴
* @details 绘制X/Y轴、轴尖标记和水平网格线,提升波形可读性
*/
void drawAxes() {
// 绘制Y轴(垂直轴)
tft.drawFastVLine(GRAPH_X, GRAPH_Y, GRAPH_HEIGHT, AXIS_COLOR);
tft.drawLine(GRAPH_X, GRAPH_Y, GRAPH_X - 3, GRAPH_Y + 5, AXIS_COLOR); // Y轴上尖
tft.drawLine(GRAPH_X, GRAPH_Y, GRAPH_X + 3, GRAPH_Y + 5, AXIS_COLOR);
// 绘制X轴(水平轴)
tft.drawFastHLine(GRAPH_X, GRAPH_Y + GRAPH_HEIGHT, GRAPH_WIDTH, AXIS_COLOR);
tft.drawLine(GRAPH_X + GRAPH_WIDTH, GRAPH_Y + GRAPH_HEIGHT,
GRAPH_X + GRAPH_WIDTH - 5, GRAPH_Y + GRAPH_HEIGHT - 3, AXIS_COLOR); // X轴右尖
tft.drawLine(GRAPH_X + GRAPH_WIDTH, GRAPH_Y + GRAPH_HEIGHT,
GRAPH_X + GRAPH_WIDTH - 5, GRAPH_Y + GRAPH_HEIGHT + 3, AXIS_COLOR);
// 绘制水平网格线(4等分)
for (int i = 1; i < 4; i++) {
int yPos = GRAPH_Y + i * GRAPH_HEIGHT / 4; // 网格线Y坐标
for (int x = GRAPH_X; x < GRAPH_X + GRAPH_WIDTH; x += 4) { // 虚线绘制
tft.drawPixel(x, yPos, 0x5AEB); // 浅灰色像素(RGB565)
}
}
}
/**
* @brief 绘制数值面板静态文本
* @details 绘制V/I/P标签和单位,仅初始化时绘制
*/
void drawPanelStaticText() {
tft.setTextColor(TEXT_COLOR);
tft.setTextSize(2);
// 电压标签
tft.setCursor(PANEL_X + 10, PANEL_Y + 50);
tft.print("V:");
// 电流标签
tft.setCursor(PANEL_X + 10, PANEL_Y + 110);
tft.print("I:");
// 功率标签
tft.setCursor(PANEL_X + 10, PANEL_Y + 170);
tft.print("P:");
// 单位标注
tft.setTextSize(1);
tft.setCursor(PANEL_X + 55, PANEL_Y + 70);
tft.print("V"); // 电压单位
tft.setCursor(PANEL_X + 55, PANEL_Y + 130);
tft.print("mA"); // 电流单位
tft.setCursor(PANEL_X + 55, PANEL_Y + 190);
tft.print("mW"); // 功率单位
}
/**
* @brief 绘制波形图例
* @details 绘制电压/电流/功率的颜色标识,方便用户识别波形
*/
void drawLegend() {
int legendY = GRAPH_Y + GRAPH_HEIGHT + 25; // 图例Y坐标
// 电压图例
tft.fillRect(20, legendY, 15, 3, VOLTAGE_COLOR); // 颜色块
tft.setTextColor(VOLTAGE_COLOR);
tft.setTextSize(1);
tft.setCursor(40, legendY - 3);
tft.print("VOLTAGE");
// 电流图例
tft.fillRect(100, legendY, 15, 3, CURRENT_COLOR);
tft.setTextColor(CURRENT_COLOR);
tft.setCursor(120, legendY - 3);
tft.print("CURRENT");
// 功率图例
tft.fillRect(180, legendY, 15, 3, POWER_COLOR);
tft.setTextColor(POWER_COLOR);
tft.setCursor(200, legendY - 3);
tft.print("POWER");
}
/**
* @brief 更新整个UI界面
* @details 包含实时数值更新和波形绘制,是界面动态更新的核心函数
* @param voltage 总线电压(V)
* @param current 电流(mA)
* @param power 功率(mW)
*/
void updateUI(float voltage, float current, float power) {
// 1. 更新右侧数值面板
updatePanelValues(voltage, current, power);
// 2. 清空波形区(保留边框和坐标轴)
tft.fillRect(GRAPH_X + 1, GRAPH_Y + 1, GRAPH_WIDTH - 1, GRAPH_HEIGHT - 1, BACKGROUND);
drawAxes(); // 重新绘制坐标轴(避免被清空)
// 3. 计算波形最大值(用于归一化显示)
float maxVal = 0.1; // 初始值(避免除以0)
for (int i = 0; i < HISTORY_SIZE; i++) {
if (voltageHistory[i] > maxVal) maxVal = voltageHistory[i];
if (currentHistory[i] > maxVal) maxVal = currentHistory[i];
if (powerHistory[i] > maxVal) maxVal = powerHistory[i];
}
maxVal *= 1.15; // 留15%余量,避免波形超出显示区
// 4. 绘制波形(循环绘制历史数据点)
for (int i = 1; i < HISTORY_SIZE; i++) {
// 计算缓存索引(循环读取)
int prevIndex = (historyIndex + i - 1) % HISTORY_SIZE;
int currIndex = (historyIndex + i) % HISTORY_SIZE;
// 计算X坐标(时间轴)
int x1 = GRAPH_X + (i-1) * 2;
int x2 = GRAPH_X + i * 2;
if (x2 > GRAPH_X + GRAPH_WIDTH) break; // 超出显示区则停止
// 绘制电压波形(归一化到显示区高度)
int y1_voltage = GRAPH_Y + GRAPH_HEIGHT - constrain(voltageHistory[prevIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
int y2_voltage = GRAPH_Y + GRAPH_HEIGHT - constrain(voltageHistory[currIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
tft.drawLine(x1, y1_voltage, x2, y2_voltage, VOLTAGE_COLOR);
// 绘制电流波形
int y1_current = GRAPH_Y + GRAPH_HEIGHT - constrain(currentHistory[prevIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
int y2_current = GRAPH_Y + GRAPH_HEIGHT - constrain(currentHistory[currIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
tft.drawLine(x1, y1_current, x2, y2_current, CURRENT_COLOR);
// 绘制功率波形
int y1_power = GRAPH_Y + GRAPH_HEIGHT - constrain(powerHistory[prevIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
int y2_power = GRAPH_Y + GRAPH_HEIGHT - constrain(powerHistory[currIndex] / maxVal * GRAPH_HEIGHT, 0, GRAPH_HEIGHT);
tft.drawLine(x1, y1_power, x2, y2_power, POWER_COLOR);
}
// 5. 绘制Y轴数值标签(最大值、中间值、0)
tft.setTextColor(0xAD75); // 浅紫色
tft.setTextSize(1);
tft.setCursor(GRAPH_X + 2, GRAPH_Y + 5);
tft.print(maxVal, 1); // 最大值(保留1位小数)
tft.setCursor(GRAPH_X + 2, GRAPH_Y + GRAPH_HEIGHT/2 + 5);
tft.print(maxVal/2, 1); // 中间值
tft.setCursor(GRAPH_X - 5, GRAPH_Y + GRAPH_HEIGHT + 5);
tft.print("0"); // 最小值
}
/**
* @brief 更新实时数值面板
* @details 清空原有数值区域并打印新值,避免文本重叠
* @param voltage 总线电压(V)
* @param current 电流(mA)
* @param power 功率(mW)
*/
void updatePanelValues(float voltage, float current, float power) {
tft.setTextColor(TEXT_COLOR);
tft.setTextSize(1);
// 1. 更新电压数值
tft.fillRect(PANEL_X + 25, PANEL_Y + 65, 30, 12, PANEL_COLOR); // 清空原有数值
tft.setCursor(PANEL_X + 25, PANEL_Y + 70);
tft.print(voltage, 2); // 保留2位小数
// 2. 更新电流数值
tft.fillRect(PANEL_X + 25, PANEL_Y + 125, 30, 12, PANEL_COLOR);
tft.setCursor(PANEL_X + 25, PANEL_Y + 130);
tft.print(current, 2);
// 3. 更新功率数值
tft.fillRect(PANEL_X + 25, PANEL_Y + 185, 30, 12, PANEL_COLOR);
tft.setCursor(PANEL_X + 25, PANEL_Y + 190);
tft.print(power, 2);
}
/******************************************************************************
* 深圳市在芯间科技有限公司
* 淘宝旗舰店:零知派
* 网 址:https://shop533070398.taobao.com
* 版权说明:
* 1.本代码的版权归【深圳市在芯间科技有限公司】所有,仅限个人非商业性学习使用。
* 2.严禁将本代码或其衍生版本用于任何商业用途(包括但不限于产品开发、付费服务、企业内部使用等)。
* 3.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/
系统流程图

2.4 INA219库文件解析
1)传感器软件I2C 初始化
初始化传感器对象,指定 I2C 地址和软件 I2C 总线
cpp
INA219::INA219(const uint8_t address, SoftWire *wire) // SoftWire使用I2C
{
_address = address;
_wire = wire;
// no calibrated values by default.
_current_LSB = 0;
_maxCurrent = 0;
_shunt = 0;
_error = 0;
}
bool INA219::begin()
{
if (! isConnected()) return false;
return true;
}
bool INA219::isConnected()
{
if ((_address < 0x40) || (_address > 0x4F)) return false;
_wire->beginTransmission(_address);
return ( _wire->endTransmission() == 0);
}
2)寄存器读写函数
INA219 通过 I2C 读写寄存器实现数据交互
cpp
uint16_t INA219::_readRegister(uint8_t reg)
{
_wire->beginTransmission(_address);
_wire->write(reg);
int n = _wire->endTransmission();
if (n != 0)
{
_error = -1;
return 0;
}
uint16_t value = 0;
if (2 == _wire->requestFrom(_address, (uint8_t)2))
{
value = _wire->read();
value <<= 8;
value |= _wire->read();
}
else
{
_error = -2;
return 0;
}
return value;
}
uint16_t INA219::_writeRegister(uint8_t reg, uint16_t value)
{
_wire->beginTransmission(_address);
_wire->write(reg);
_wire->write(value >> 8);
_wire->write(value & 0xFF);
int n = _wire->endTransmission();
if (n != 0)
{
_error = -1;
}
return n;
}
三、项目演示
3.1 监测过程与比较
系统实时显示电压、电流和功率信息,并通过波形图表展示历史数据变化趋势
界面包含三个主要区域
左侧波形区:实时显示电压、电流、功率的三通道波形;右侧数据区:实时数值显示,包含单位标识;底部图例:颜色标识区分不同参数

Tips
零知派标准板上的A1和A0都与Vs+引脚连接引出排针接口,通过跳线帽短接直接切换0x41、0x44和0x45 I2C总线地址,本项目将A1通过跳线帽短接,I2C地址为0x44

3.2 万用表对比测试
将万用表分别打到量程为20V的电压档以及20mA的电流档
进行电压档测试

进行电流档测试

| 参数 | 万用表测量值 | 系统测量值 | 误差 |
| 电压 | 4.90V | 4.97V | +0.07V |
| 电流 | 1.917mA | 1.893mA | -0.024mA |
| 功率 | 9.393mW | 9.408mW | -0.015mW |
|---|
注:电压值和电流值误差控制在整个温度范围内的0.5%精度
3)通过串口输出数据,便于进一步分析

3.3 视频演示
零知标准板+INA219锂电池监测系统全功能演示
系统启动过程、实时数据监测、波形显示效果,以及在不同负载条件下系统的响应特性。可以看到当电路负载变化时,电流和功率读数实时更新,波形图表平滑滚动,系统响应迅速且稳定。
四、INA219 电流功率监测计技术讲解
INA219是一款基于I2C接口的高端电流分流和功率监控器,能够监测分流器压降和电源电压。其内部包含16位ADC、可编程增益放大器(PGA) 和精密基准电压源,能够实现高精度测量
4.1 INA219工作原理

根据芯片手册,经过分流电阻N(采样电阻)后,能够采集到的最低有效电压LSB为10uV。
利用欧姆定律计算电流公式:
电流测量:根据Rshunt(0.1Ω)分流电阻两端的电压降计算;电压测量:直接测量总线电压(支持0-26V范围);功率计算:内部乘法器实时计算功率
4.2 I2C通信协议
本项目采用软件模拟 I2C(SoftWire)与 INA219 通信
1)串行总线地址

INA219 有两个地址引脚A0 和 A1都设置为GND,该从机地址为0x40
2)软件I2C时序

总线上的所有从机在 SCL 的上升沿移入从机地址字节,其中最后一位指示要进行的是读操作还是写操作。在第九个时钟脉冲期间,被寻址的从机通过生成确认信号并将 SDA 拉至低电平来响应主机
4.3 寄存器配置
1)配置寄存器(0x00)

BRNG位(13):总线电压范围选择(0=16V, 1=32V);PG位(11-12):PGA增益设置(00=±40mV, 01=±80mV, 10=±160mV, 11=±320mV);BADC位(7-10):总线ADC分辨率和平均模式;SADC位(3-6):分流ADC分辨率和平均模式;
eg:MODE位(0-2):操作模式选择

阴影部分的值为默认值,采取连续分流和总线测量模式
2)校准寄存器(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
五、常见问题解答(Q&A)
Q1:测量值不准确怎么办?
***A:可以尝试:***重新校准传感器,确保setMaxCurrentShunt()函数的参数与实际使用的分流电阻匹配;进行零点校准,在无负载情况下测量电流值,并在代码中进行补偿
Q2:软件 I2C 通信不稳定怎么办?
***A:可以:***调整i2c_delay参数,确保通信时序正确;降低通信速率(如从 400kHz 降至 100kHz)
Q3:软件I2C通信失败如何排查?
检查引脚配置是否正确(SCL接零知标准板A5、SDA接零知标准板A4);采用I2C扫描函数确认INA219地址设置(默认0x40)
项目资源整合
INA219 库(支持 SoftWire): RobTillaart/INA219
INA219 数据手册: INA219 DataSheet