一款基于零知派标准板的高精度电流/电压/功率监测解决方案
零知派(零知开源)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从“配置环境”转移到“创意实现”,极大降低了技术门槛。零知开源编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知派(零知开源)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
访问零知实验室,获取更多实战项目和教程资源吧!
www.lingzhilab.com
目录
一、硬件系统设计
1.1 硬件清单
1.2 接线方案表
1.3 具体接线图
1.4 连接实物图
二、软件架构设计
2.1 系统初始化
2.2 数据采集与处理
2.3 UI显示功能
2.4 INA219库文件解析
三、项目演示
3.1 监测过程与比较
3.2 万用表对比测试
3.3 视频演示
四、INA219 电流功率监测计技术讲解
4.1 INA219工作原理
4.2 I2C通信协议
4.3 寄存器配置
五、常见问题解答(Q&A)
Q1:测量值不准确怎么办?
Q2:软件 I2C 通信不稳定怎么办?
Q3:软件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 时钟线 |
| SDA | A4 | 软件 I2C 数据线 | |
| VCC | 3.3V | 传感器电源 | |
| GND | GND | 接地 | |
| Vin+ | 被测电源正极/3.3V | ||
| Vin- | 负载正极 | 通过 0.1Ω 电阻连接到 Vin+ | |
| ST7789 | 直插 | 直插零知派标准板TFT扩展引脚 | 硬件 SPI通信 |
INA219电流功率监测计的Vin+接3.3V供电电源、Vin-接负载(本项目连接到LED模块IN引脚)
1.3 具体接线图
INA219与零知派标准板通过软件I2C进行通信;各器件接线、LED模块按照代码接线所示:
1.4 连接实物图
二、软件架构设计
2.1 系统初始化
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 数据采集与处理
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("ntBUSttSHUNTttCURRENTttPOWERttOVFttCNVR");
Serial.print("t");
Serial.print(busVoltage, 2);
Serial.print("tt");
Serial.print(INA.getShuntVoltage_mV(), 2);
Serial.print("tt");
Serial.print(current, 2);
Serial.print("tt");
Serial.print(power, 2);
Serial.print("tt");
Serial.print(INA.getMathOverflowFlag()); // 数学溢出标志
Serial.print("tt");
Serial.print(INA.getConversionFlag()); // 转换完成标志
Serial.println();
delay(1000); // 1秒刷新一次
}
每秒从 INA219 读取总线电压、电流和功率;使用循环缓冲区将新数据存入历史缓冲区,实现数据的滚动存储
2.3UI显示功能
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 轴范围;使用不同颜色绘制三条曲线,分别表示电压、电流和功率
项目代码
/**************************************************************************************
* 文件: /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("ntBUS(V)ttSHUNT(mV)tCURRENT(mA)tPOWER(mW)tOVFttCNVR");
Serial.print("t");
Serial.print(busVoltage, 2); // 总线电压(保留2位小数)
Serial.print("tt");
Serial.print(INA.getShuntVoltage_mV(), 2); // 分流电阻电压(mV)
Serial.print("tt");
Serial.print(current, 2); // 电流(mA)
Serial.print("tt");
Serial.print(power, 2); // 功率(mW)
Serial.print("tt");
Serial.print(INA.getMathOverflowFlag()); // 数学溢出标志(1=溢出,需重新校准)
Serial.print("tt");
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 总线
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 读写寄存器实现数据交互
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 视频演示
https://live.csdn.net/v/522314?spm=1001.2014.3001.5501
系统启动过程、实时数据监测、波形显示效果,以及在不同负载条件下系统的响应特性。可以看到当电路负载变化时,电流和功率读数实时更新,波形图表平滑滚动,系统响应迅速且稳定。
四、INA219 电流功率监测计技术讲解
INA219是一款基于I2C接口的高端电流分流和功率监控器,能够监测分流器压降和电源电压。其内部包含16位ADC、可编程增益放大器(PGA) 和精密基准电压源,能够实现高精度测量
4.1 INA219工作原理
根据芯片手册,经过分流电阻N(采样电阻)后,能够采集到的最低有效电压LSB为10uV。
利用欧姆定律计算电流公式:
电流测量:根据Rshunt(0.1Ω)分流电阻两端的电压降计算;电压测量:直接测量总线电压(支持0-26V范围);功率计算:内部乘法器实时计算功率
4.2I2C通信协议
本项目采用软件模拟 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通信失败如何排查?
A:检查引脚配置是否正确(SCL接零知标准板A5、SDA接零知标准板A4);采用I2C扫描函数确认INA219地址设置(默认0x40)
项目资源整合
INA219 库(支持 SoftWire): RobTillaart/INA219
INA219 数据手册: INA219 DataSheet
- 随机文章
- 热门文章
- 热评文章
- 攀岩世界杯柯桥站摘铜,骆知鹭备战奥运资格赛添信心
- 韦乐平:智算网络性能成为集群算力提升的关键
- 让辽中南城市群迸发东北振兴增长极属性
- 巴勒斯坦“入联”申请遭美否决,中方表态
- 美联储态度转鹰?普徕仕:预计今年仍会降息两次?
- 守护美好生活每一步 建信人寿全程护航2024上海半程马拉松
- 韩国就岸田文雄向靖国神社献祭品深表失望和遗憾
- 中央气象台4月22日06时发布暴雨黄色预警
- 英镑本周料收复近期失地,关注英国PMI和美国PCE
- 一见·时隔一月,总书记再次考察“为基层减负”
- 故宫:“五一”假期未成年人团队可快速预约、检票
- ATFX汇市:欧元区和美国的制造业PMI将发布,市场预期乐观
- 浙江休闲农业去年接待3.6亿人次 营业收入达471亿元
- 1“赛事+”提升城市“流量” 陕西商洛拓经济发展新“赛道”
- 2“五一”临近 持基过节的投资者要注意这几点
- 3华发股份:成功入选“人民优选”品牌 五一黄金周热销30亿
- 4钟鼓楼老街区的古都新事
- 5金税四期试点上线,财税体制改革拉开帷幕!或有资金借道信创ETF基金(562030)逢跌进场布局
- 6非常危险!女子摔成粉碎性骨折!又是因为洞洞鞋,夏天多人中招……
- 7到2027年产业规模达到2000亿元 浙江发布历史经典产业高质量发展计划
- 8初步数据:我国一季度经常账户顺差392亿美元
- 9IDC:24Q1全球PC出货量恢复增长 达到疫情前水平
- 10“发现山西之美”TDC旅游发现者大会举办:共话文旅新生态 邀客体验新玩法
- 11国门“夫妻档” 国庆共坚守
- 12北交所一周审核动态:2家企业更新进展 胜业电气二轮问询回复中称家电头部客户对价格敏感度较低
- 13(中国新貌)“国宝”大熊猫:栖居更美境 云游更广天
- 1大裁员下,特斯拉两名顶级高管离职
- 2奇瑞将与欧洲高端品牌签署技术平台授权协议
- 32024中国长三角青年企业家交流大会在杭州举办
- 4雷克萨斯GX中东版 全部在售 2023款 2022款 2020款 2019款 2018款成都远卓名车雷克萨斯GX中东版团购钜惠20万 欢迎上门试驾
- 5零跑C16将搭载中创新航磷酸铁锂电池
- 6Q1净利微增7%,宁德时代股东总数较2023年年末减少10728户
- 7哪吒,需要背水一战
- 8“新”中有“机”!创新服务承接新流量 撬动消费升级
- 9非创始版SU7何时交付 小米:工厂生产爬坡 全力提高产能
- 10央媒评卧铺挂帘:谁买的票谁做主
- 11江西南昌首部“多规合一”国土空间总体规划获批
- 12方程豹旗舰硬派越野!豹8正式亮相:仰望U8“青春版”登场
- 13583家族/造型霸气 方程豹豹8量产版发布



