零知派——STM32驱动INA219电流功率监测计实现高精度电源管理

科创之家 2026-04-19 85人围观

​ 一款基于零知派标准板的高精度电流/电压/功率监测解决方案

零知派(零知开源)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比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模块按照代码接线所示:

wKgZPGnjKAWANOOyAA6teFK-uO0166.png

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.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/

系统流程图

wKgZO2njKVKAVCB-AASMoAKmlxY751.png

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

wKgZPGnjKkaAYueZAAUn8N-2eDk681.png

3.2 万用表对比测试

将万用表分别打到量程为20V的电压档以及20mA的电流档

进行电压档测试

wKgZO2njKleADGMyABM00lAPd1A574.png

进行电流档测试

wKgZO2njKl6AQIomABhUtOpogt8346.png
参数 万用表测量值 系统测量值 误差
电压 4.90V 4.97V +0.07V
电流 1.917mA 1.893mA -0.024mA
功率 9.393mW 9.408mW -0.015mW

注:电压值和电流值误差控制在整个温度范围内的0.5%精度

3)通过串口输出数据,便于进一步分析

wKgZPGnjKnCAByQ3AAWuDMh0L78018.png

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。

wKgZPGnjKwiAJSKBAAClRZA_m9A226.png

利用欧姆定律计算电流公式:

I_{current}=frac{V_{shunt}}{R_{shunt}}

电流测量:根据Rshunt(0.1Ω)分流电阻两端的电压降计算;电压测量:直接测量总线电压(支持0-26V范围);功率计算:内部乘法器实时计算功率

4.2I2C通信协议

本项目采用软件模拟 I2C(SoftWire)与 INA219 通信

1)串行总线地址

wKgZPGnjKx-ARWIrAABHi0OHMk4765.png

INA219 有两个地址引脚A0 和 A1都设置为GND,该从机地址为0x40

2)软件I2C时序

wKgZO2njKzuALnw5AADhQb7QbwQ963.png

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

4.3寄存器配置

1)配置寄存器(0x00)

wKgZO2njK8WAE9KmAACPivRNpD8374.png

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):操作模式选择

wKgZPGnjK9KAG9JOAADw5hlKVT4190.png

阴影部分的值为默认值,采取连续分流和总线测量模式

2)校准寄存器(0x05)

校准值计算公式:

wKgZPGnjK-aAeaTyAAAx7otm4Zo429.png

其中电流LSB = 最大预期电流 / 32768

Current_LSB=10010^-6=100uA=0.0001A

计算基准值:Cal=0.04096/(Current_LSB/R)=0.04096/(0.0001A0.1R)=4096=0x1000

校准寄存器与缩放

wKgZO2njLF2AVnYTAABWBBLqZAM690.png

如果发现测量到的电流值有误,用电流表测到的实际值为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

  • 随机文章
  • 热门文章
  • 热评文章
不容错过
Powered By Z-BlogPHP