回答

收藏

零知派——STM32驱动INA226 高精度功率监测仪(可视化数据记录仪+一键导出Excel)

#开源分享 #开源分享 103 人阅读 | 0 人回复 | 2026-04-11

​    ✔零知派(零知开源)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 “配置环境” 转移到 “创意实现”,极大降低了技术门槛。零知派编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知派(零知开源)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
www.lingzhilab.com

项目概述
        本项目基于零知标准板(主控芯片为 STM32F103RBT6)和 INA226 高精度电流/功率监测芯片,搭建了一套完整的LED负载功率监测系统。实时采集LED负载的总线电压、电流和功率。在 ST7789 240x320 TFT彩屏上,以三路独立波形(电压、电流、功率)直观展示数据变化趋势,按照 PLX-DAQ 格式实时接收串口输出数据
项目难点及解决方案
        问题描述:实时地在TFT屏幕上绘制波形和更新数值,同时不影响数据的串口导出
解决方案:先清空波形区域,再重绘网格和轴线,最后绘制新数据点

一、系统接线部分1.1 硬件清单
硬件名称 数量 说明
零知标准板 (STM32F103RBT6) 1 主控板
INA226 功率监测模块 1 内置0.1Ω分流电阻,建议选择成品模块
ST7789 TFT彩屏 (240x320) 1 SPI接口屏幕,用于数据显示
LED (功率负载) 1 如3W大功率LED
电阻 (如220Ω) 1 用于限制LED电流,确保不超0.5A
杜邦线若干 - 用于连接各模块
5V/3.3V电源 1 为系统供电

1.2 接线方案表
        按照代码定义进行连接,ST7789直插零知标准板TFT扩展口,无需单独接线:
INA226模块 零知标准板引脚零知标准板引脚 LED负载 电源
VCC 3.3V 5V (或3.3V) LED 正极电源 正极 (如5V)
GND GND GND LED 负极 IN+ 引脚
SCL A5 无需连接 分流电阻 -
SDA A4 无需连接 IN- 引脚 电源 负极 (GND)
VBUS 电源 正极 (LED+端) D10 - -
IN+ LED 负极 D2 - -
IN- 电源 负极 (GND) D4 - -

1.3 具体接线图


        请注意:确保电流严格按照 电源正极 → LED → IN+ → 分流电阻 → IN- → 电源负极 的路径流动

1.4 接线实物图
        VBUS必须连接到被测电源的正极(LED正极),INA226正确采样总线电压。如果VBUS悬空,功率寄存器始终为0


二、安装与使用部分2.1 开源平台-输入"INA226 高精度功率监测仪"并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器


三、代码讲解部分
        本项目代码核心在于四个部分:INA226的初始化与校准、波形数据的采集与缓存、UI界面的刷新与绘制,以及数据导出格式
3.1 INA226 初始化与校准
  1. void setup() {
  2.   Serial.begin(115200);
  3.   while (!Serial);

  4.   // ── TFT 初始化
  5.   tft.init(TFT_WIDTH, TFT_HEIGHT);
  6.   tft.invertDisplay(true);
  7.   tft.setRotation(1);
  8.   tft.fillScreen(C_BG);
  9.   showSplashScreen();
  10.   delay(1500);

  11.   // ── 软件 I2C + INA226 初始化
  12.   sw.begin();
  13.   sw.setClock(100000);

  14.   if (!ina226.begin()) {
  15.     tft.fillScreen(C_BG);
  16.     tft.setTextColor(0xF800); tft.setTextSize(2);
  17.     tft.setCursor(20, 100); tft.print("INA226 NOT FOUND");
  18.     tft.setTextColor(C_DIM); tft.setTextSize(1);
  19.     tft.setCursor(20, 128); tft.print("SDA=A4  SCL=A5");
  20.     Serial.println("未检测到INA226,请检查接线!");
  21.     while (1) delay(10);
  22.   }

  23.   int err = ina226.setMaxCurrentShunt(0.5, 0.1, true);
  24.   if (err != 0) {
  25.     Serial.print("校准失败,错误码: 0x");
  26.     Serial.println(err, HEX);
  27.     while (1) delay(10);
  28.   }

  29.   // ── 清空历史缓存
  30.   for (int i = 0; i < HISTORY_SIZE; i++) {
  31.     voltageHistory[i] = 0;
  32.     currentHistory[i] = 0;
  33.     powerHistory[i]   = 0;
  34.   }

  35.   // ── 绘制静态界面
  36.   drawStaticUI();

  37.   // ── PLX-DAQ 初始化(必须在 Serial.begin 之后)
  38.   plxDaqInit();

  39.   Serial.println("系统初始化完成");
  40.   Serial.print("INA226_LIB_VERSION: ");
  41.   Serial.println(INA226_LIB_VERSION);

  42.   lastSampleTime = millis();
  43. }
复制代码
1)校准函数:ina226.setMaxCurrentShunt(0.5, 0.1, true)
maxCurrent = 0.5:预期的最大电流值,这个值决定了电流寄存器的最小分辨率(Current_LSB)
根据Current_LSB = Maximum_Expected_Current / 2^15 公式:Current_LSB = 0.5 / 32768 ≈ 15.26e-6 A/bit = 15.26 μA/bit
这意味着电流寄存器的每1个LSB代表15.26μA
        shunt = 0.1:外部分流电阻的阻值(Ω),INA226需要这个值来将测得的差分电压换算成电流;normalize = true:归一化标志位

2)校准寄存器的计算公式:
        库函数根据归一化后的Current_LSB和R_SHUNT计算出校准值,并写入0x05寄存器。之后,INA226内部硬件会自动执行Current = (V_SHUNT × CAL) / 2048的计算,并将结果存入电流寄存器


3.2 UI刷新与波形绘制
  1. void updateUI(float voltage, float current, float power) {
  2.   // 1. 更新右侧数值面板
  3.   updatePanelValues(voltage, current, power);

  4.   // 2. 清除波形区域,只保留左侧Y轴一列
  5.   tft.fillRect(GRAPH_X + 1, GRAPH_Y, GRAPH_WIDTH - 1, GRAPH_HEIGHT, C_BG);

  6.   // 3. 重绘网格和轴线
  7.   for (int row = 1; row < 4; row++) {
  8.     int yh = GRAPH_Y + row * GRAPH_HEIGHT / 4;
  9.     tft.drawFastHLine(GRAPH_X + 1, yh, GRAPH_WIDTH - 1, C_GRID_H);
  10.   }
  11.   // ... (重绘纵向虚线)
  12.   tft.drawFastVLine(GRAPH_X, GRAPH_Y, GRAPH_HEIGHT, C_AXIS);
  13.   tft.drawFastHLine(GRAPH_X, GRAPH_Y + GRAPH_HEIGHT, GRAPH_WIDTH, C_AXIS);

  14.   // 4. 找到当前缓存区中所有数据的最大值,并乘以1.15作为Y轴动态范围
  15.   float maxVal = 0.1f;
  16.   for (int i = 0; i < HISTORY_SIZE; i++) {
  17.     if (voltageHistory[i] > maxVal) maxVal = voltageHistory[i];
  18.     if (currentHistory[i] > maxVal) maxVal = currentHistory[i];
  19.     if (powerHistory[i]   > maxVal) maxVal = powerHistory[i];
  20.   }
  21.   maxVal *= 1.15f;

  22.   // 5. 绘制Y轴量程标注
  23.   tft.fillRect(GRAPH_X + 1, GRAPH_Y, 28, 7, C_BG);
  24.   tft.setCursor(GRAPH_X + 1, GRAPH_Y + 1); tft.print(maxVal, 1);
  25.   // ... (绘制中点、零点)

  26.   // 6. 绘制三路折线图
  27.   #define CALC_Y(val) \
  28.       (GRAPH_Y + GRAPH_HEIGHT - 1 - \
  29.        (int)constrain((val) / maxVal * (float)(GRAPH_HEIGHT - 2), 0.0f, (float)(GRAPH_HEIGHT - 2)))
  30.   for (int i = 1; i < HISTORY_SIZE; i++) {
  31.     int prevIdx = (historyIndex + i - 1) % HISTORY_SIZE;
  32.     int currIdx = (historyIndex + i)     % HISTORY_SIZE;

  33.     int x1 = GRAPH_X + (i - 1) * 2;
  34.     int x2 = GRAPH_X + i * 2;
  35.     if (x2 >= GRAPH_X + GRAPH_WIDTH) break;

  36.     tft.drawLine(x1, CALC_Y(voltageHistory[prevIdx]),
  37.                  x2, CALC_Y(voltageHistory[currIdx]),  VOLTAGE_COLOR);
  38.     tft.drawLine(x1, CALC_Y(currentHistory[prevIdx]),
  39.                  x2, CALC_Y(currentHistory[currIdx]),  CURRENT_COLOR);
  40.     tft.drawLine(x1, CALC_Y(powerHistory[prevIdx]),
  41.                  x2, CALC_Y(powerHistory[currIdx]),    POWER_COLOR);
  42.   }
  43.   // ... (绘制光标竖线)
  44. }
复制代码
        动态Y轴范围:实时扫描整个历史缓冲区,找出电压、电流、功率中的最大值,并在此基础上增加15%的余量作为当前Y轴的上限
        坐标映射:宏定义CALC_Y(val)是实现数据到屏幕像素转换的核心,根据当前最大值maxVal,将真实数据val线性映射到波形区的高度范围(GRAPH_HEIGHT)内。constrain函数确保了映射结果不会超出波形区边界
        波形绘制:通过循环,将历史缓冲区中相邻的两个数据点用drawLine连接起来。x坐标的间隔为2像素,y坐标通过CALC_Y计算得出

3.3 PLX-DAQ数据导出
        使用PLX-DAQ格式,无需实现复杂的文件系统或SD卡写入,只需通过串口发送文本,就可以利用PC端强大的Excel软件完成数据的记录、保存和可视化分析
  1. // ========================== 主循环 ==========================
  2. void loop() {
  3.   unsigned long now = millis();

  4.   // 按采样间隔执行
  5.   if (now - lastSampleTime >= SAMPLE_INTERVAL) {
  6.     lastSampleTime = now;

  7.     float busVoltage = ina226.getBusVoltage();
  8.     float current    = ina226.getCurrent_mA();
  9.     float power      = ina226.getPower_mW();

  10.     // current -= 0.8;  // 零点漂移补偿(如需要)

  11.     // 存入波形缓存
  12.     voltageHistory[historyIndex] = busVoltage;
  13.     currentHistory[historyIndex] = current;
  14.     powerHistory[historyIndex]   = power;

  15.     // 刷新 TFT 界面
  16.     updateUI(busVoltage, current, power);

  17.     // 发送 PLX-DAQ 数据到 Excel
  18.     plxDaqSend(busVoltage, current, power);

  19.     historyIndex = (historyIndex + 1) % HISTORY_SIZE;
  20.   }
  21. }

  22. // ========================== PLX-DAQ 函数 ==========================

  23. /**
  24. * PLX-DAQ 初始化
  25. * 向 Excel 发送列标题定义指令,建立数据表头和图表列绑定
  26. * 必须在 setup() 末尾调用一次
  27. *
  28. * PLX-DAQ 指令说明:
  29. *   CLEARDATA          - 清空 Excel 已有数据,从 A1 重新开始
  30. *   LABEL,xxx,xxx,...  - 定义各列的表头名称
  31. *   RESETTIMER         - 将内置计时器归零
  32. *   ROW,DATA,x,x,...   - 发送一行数据(对应 LABEL 顺序)
  33. *   CELL,SET,行,列,值  - 直接写入指定单元格(可选)
  34. */
  35. void plxDaqInit() {
  36.   delay(500);  // 等待 Excel 插件就绪

  37.   // 清空旧数据,从头开始记录
  38.   Serial.println("CLEARDATA");

  39.   // 定义列标题
  40.   // 格式:LABEL,列A标题,列B标题,...
  41.   Serial.println("LABEL,Sample,Time_ms,Voltage_V,Current_mA,Power_mW");

  42.   // 重置 PLX-DAQ 内置计时器
  43.   Serial.println("RESETTIMER");

  44.   delay(100);
  45. }

  46. /**
  47. * PLX-DAQ 数据发送
  48. * 每次采样后调用,向 Excel 写入一行数据
  49. *
  50. * Excel 列对应关系:
  51. *   A列 = Sample    采样序号(从1开始递增)
  52. *   B列 = Time_ms   运行时间戳(秒,保留1位小数)
  53. *   C列 = Voltage_V 总线电压(V,保留3位小数)
  54. *   D列 = Current_mA 电流(mA,保留2位小数)
  55. *   E列 = Power_mW  功率(mW,保留2位小数)
  56. *
  57. * PLX-DAQ ROW 指令格式:
  58. *   ROW,DATA,值1,值2,值3,...
  59. *   值的顺序必须与 LABEL 定义完全一致
  60. */
  61. void plxDaqSend(float voltage, float current, float power) {
  62.   sampleCount++;

  63.   // 构建 PLX-DAQ 数据行
  64.   // ROW,DATA 是固定前缀,后接各列数据,逗号分隔
  65.   Serial.print("ROW,DATA,");
  66.   Serial.print(sampleCount);          // A列:采样序号
  67.   Serial.print(",");
  68.   Serial.print(millis() / 1000.0, 1); // B列:时间戳(秒,1位小数)
  69.   Serial.print(",");
  70.   Serial.print(voltage, 3);           // C列:电压 V(3位小数)
  71.   Serial.print(",");
  72.   Serial.print(current, 2);           // D列:电流 mA(2位小数)
  73.   Serial.print(",");
  74.   Serial.println(power, 2);           // E列:功率 mW(2位小数,println自动换行)

  75.   // ── 可选:同步输出普通调试信息(不影响PLX-DAQ解析,需注释掉避免干扰)
  76.   // 注意:PLX-DAQ 只解析以 ROW/LABEL/CLEARDATA 等关键字开头的行
  77.   //       其余行会被忽略,普通 Serial.print 调试信息不会干扰 Excel 采集
  78.   //       但建议调试完成后注释掉以保持输出整洁
  79.   Serial.print("[DBG] V="); Serial.print(voltage,3);
  80.   Serial.print(" I="); Serial.print(current,2);
  81.   Serial.print("mA P="); Serial.print(power,2); Serial.println("mW");
  82. }
复制代码
PLX-DAQ协议:打开串口时,它会识别特定的命令和数据格式
        DATA 命令:告诉PLX-DAQ,接下来的一行是数据行,数据项之间用逗号分隔
        TIMESTAMP 命令(可选):可以在数据前添加时间戳、LABEL 命令(可选):可以在数据开始前定义列标题

3.4 数据结构与波形缓存
  1. // -------------------------- 引脚定义 --------------------------
  2. #define TFT_CS    10
  3. #define TFT_DC    2
  4. #define TFT_RST   4

  5. // -------------------------- 屏幕参数 --------------------------
  6. #define TFT_WIDTH  240
  7. #define TFT_HEIGHT 320
  8. Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

  9. // -------------------------- INA226 --------------------------
  10. SoftWire sw(SCL, SDA, SOFT_STANDARD);
  11. INA226 ina226(0x40, &sw);

  12. // -------------------------- 波形缓存配置 --------------------------
  13. const int HISTORY_SIZE = 96;
  14. float voltageHistory[HISTORY_SIZE];
  15. float currentHistory[HISTORY_SIZE];
  16. float powerHistory[HISTORY_SIZE];
  17. int   historyIndex = 0;

  18. // -------------------------- PLX-DAQ 配置 --------------------------
  19. unsigned long sampleCount = 0;        // 采样计数器(Excel A列)
  20. unsigned long lastSampleTime = 0;     // 上次采样时间戳
  21. #define SAMPLE_INTERVAL 1000          // 采样间隔 ms(与 delay 保持一致)

  22. // -------------------------- 显示区域坐标 --------------------------
  23. #define GRAPH_X       8
  24. #define GRAPH_Y       26
  25. #define GRAPH_WIDTH   196
  26. #define GRAPH_HEIGHT  174
  27. #define PANEL_X       210
  28. #define PANEL_Y       0
  29. #define PANEL_WIDTH   110

  30. // -------------------------- 颜色定义 (RGB565) --------------------------
  31. #define C_BG        0x0000
  32. #define C_TITLEBAR  0x0883
  33. #define C_PANEL     0x0883
  34. #define C_DIVIDER   0x2965
  35. #define C_GRID_H    0x10A2
  36. #define C_GRID_V    0x0841
  37. #define C_AXIS      0x3186
  38. #define C_TEXT      0xFFFF
  39. #define C_DIM       0x8410
  40. #define C_LABEL     0xAD55
  41. #define VOLTAGE_COLOR 0xFD20
  42. #define CURRENT_COLOR 0x07EF
  43. #define POWER_COLOR   0xF81F
  44. #define BACKGROUND  C_BG
  45. #define TEXT_COLOR  C_TEXT
  46. #define AXIS_COLOR  C_AXIS
  47. #define PANEL_COLOR C_PANEL

  48. // -------------------------- 函数声明 --------------------------
  49. void showSplashScreen();
  50. void drawStaticUI();
  51. void drawAxes();
  52. void drawPanelStaticText();
  53. void drawLegend();
  54. void updateUI(float voltage, float current, float power);
  55. void updatePanelValues(float voltage, float current, float power);
  56. void plxDaqInit();
  57. void plxDaqSend(float voltage, float current, float power);
复制代码
        voltageHistory、currentHistory、powerHistory三个独立的缓冲区,分别存储三种物理量的历史值

系统流程图


四、项目结果演示4.1 操作流程①编译烧录
        按照接线方案表连接好所有硬件,并仔细检查电流检测回路和VBUS连接是否正确。打开零知派软件,将项目代码烧录到零知标准板

②打开串口监视器/Excel
查看实时数据
        打开零知派的串口监视器(波特率115200),可以看到每秒输出的电压、电流、功率数据

导出数据到Excel
        安装PLX-DAQ v2加载项到Excel、在Excel中打开PLX-DAQ工具、选择正确的COM口和波特率(115200)、点击“Connect”按钮。此时,Excel会开始实时接收并显示数据

③系统运行
        TFT屏幕将显示启动画面,然后进入主界面
观察屏幕,右侧面板将实时更新电压、电流、功率的数值;左侧波形区域将开始绘制三条不同颜色的波形曲线,并随时间滚动


4.2 视频演示
视频演示了基于零知标准板(STM32F103)和INA226芯片的功率监测系统。系统通过TFT彩屏实时显示电压、电流、功率的数值及动态波形。同时,通过串口以PLX-DAQ格式输出数据,可直接导入Excel进行实时图表绘制和数据记录。视频展示了系统从启动到数据导出的完整过程

五、INA226 技术讲解
        INA226内部的核心是一个16位ΔΣ型ADC,可以测量两个电压:分流电压(IN+ 与 IN- 之差)和总线电压(VBUS引脚对地电压)。测量结果经过数字滤波和平均后,存入相应的电压寄存器

如果校准寄存器被正确编程,硬件乘法器会自动计算出电流和功率值,并存入电流寄存器和功率寄存器
5.1 校准寄存器的计算
        INA226本身并不知道你使用了多大的分流电阻,也不知道希望电流数据以何种分辨率呈现。校准就是告诉芯片这两个参数,从而使它能正确计算电流和功率

计算公式
Max_Expected_Current=0.5A、R_SHUNT=0.1Ω
        理论Current_LSB=0.5/32768≈15.26μA/bit,归一化后,库选择Current_LSB=20μA/bit(大于15.26的最小整数倍)
电压、电流、功率的读取流程
        getBusVoltage()、getCurrent_mA()、getPower_mW() 函数内部完成了寄存器读取和数值转换

5.2 I2C通信协议
        INA226作为I2C从设备,地址可通过A0/A1引脚配置,本代码使用默认地址0x40

操作时序
对寄存器的写入操作由主机发送首个字节开始,该字节为从机地址,且读写位为低电平


操作时序
从设备读取数据时,通过写操作存入寄存器指针的最后一个值,将决定读操作期间读取哪个寄存器


寄存器映射


低侧检测电流路径
电源正极 → LED负载 → INA226 IN+ → [分流电阻] → INA226 IN- → 电源负极(GND)
                                              |——    电压测量差分对   ——|


六、常见问题解答(FAQ)Q1: 为什么屏幕上的电流或功率一直显示0?
        A: 请检查:1) 电流检测回路是否正确(IN+接负载侧,IN-接地侧);2) VBUS是否连接到负载电源正极;3) 校准参数中的分流电阻值是否与实际一致
Q2: Excel无法接收到数据?
       A: 确保PLX-DAQ选择的COM口正确,波特率与代码一致(115200),且没有其他程序占用串口
Q3: 能否测量负电流(如电池放电/充电双向)?
        A: 可以。INA226的电流寄存器是有符号数,当电流反向(从IN-流向IN+)时,读数会变为负数。您可以在代码中直接获取负值并显示

项目资源整合
INA226数据手册:    ina226.pdf
INA226库文件:       RobTillaart/INA226

分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条