
一块功率计,电压读得对,电流也读得对,相乘后却可能得到一份不属于任何一轮采样的结果。
问题可能藏在两个 I²C 读取之间。主机读完电压,INA226 恰好更新了结果寄存器;随后读到的电流,已经属于下一轮。给每个寄存器单独加重试,甚至给软件总线加锁,都没有保证它们来自同一轮转换。
对需要成组记录电压、电流的低速监测,这个边界值得单独处理。下面保留一块 STM32F103C8T6 验证板的硬件,重写最小采样固件,让它先完成一轮测量,再把整组数据交给上层。
先用一个可以复现的合成反例说明问题。
A 轮完整结果为 12V、1A,乘积 12W。B 轮为 5V、2A,乘积 10W。若读取顺序恰好是“读 A 的电压、芯片更新到 B、读 B 的电流”,软件会算出 12×2=24W。

图1为合成时序示意,数值用于演示允许发生的读取交错,不是板卡实测波形,也不证明某一块板已经出现该故障。
TI 的 INA226 手册给出了这个分析的依据。连续双量模式先转换分流电压,再转换总线电压;完成所设平均次数及内部计算后,更新对应的输出寄存器。旧结果会保留到下一组完整结果到来,读取数据寄存器不会暂停正在进行的转换。
因此,一次读到完整的 16 位寄存器,与连续几次读取属于同一轮,是两个不同的问题。只在一轮开始前看到 CVRF,也不能阻止连续模式紧接着完成下一轮。

图2为同一 REV1.0 工程从 EDA 重新导出的原理图。复杂处在下面拆开看,完整源文件随附件提供。

图3保留了 U1、R9、R3/R4 的真实连接。R9 标称 0.005Ω;A0、A1 接地,七位 I²C 地址是 0x40。0x80 是加上读写位后的写地址字节,不能把它原样填进要求七位地址的 HAL 接口。
这张图还有一个容易忽略的细节。VBUS 接 VIN,处在 R9 上游;电流则由流过 R9 的负载支路电流换算得到。这里的 V×I 表示分流器上游、该负载支路对应的功率。它没有扣掉 R9 的损耗,也没有把板上 DC/DC 的自身耗电算进负载支路电流,不能直接叫“负载端精确功率”或“整机总输入功率”。
R3、R4 都是上拉到 3.3V 的 4.7kΩ。新固件把 PB6/PB7 配成开漏,释放高电平时由上拉电阻拉高,并检查 SCL 是否真正变高以及每个字节的 ACK。

图4为顶层,R9 位于下方输入输出之间,U1 在它上方,R3/R4 紧邻采样与主控区域。排查时要沿着 R9 两侧的取样连接追到 U1,不能只检查负载大电流路径是否连通。

图5为底铜和底丝印的自顶向下透视,与图4使用相同坐标,未作镜像,所以底层文字反向。它不是翻板后的实物照片。两张 PCB 图用于核对布局和连接,不能据铜皮外观推定可持续通过多大电流;丝印中的电压电流范围也没有在本文完成热、电源及保护验收。
本次固件只保留采样与串口诊断,不把屏幕菜单也塞进来。转换参数沿用四次平均、总线与分流各 1.1ms,仅把模式改成单次双量触发。
#define INA_CFG_IDLE 0x4320u
#define INA_CFG_ONCE 0x4323u
#define INA_CAL 1024u0x4323 的 MODE=011。每次写入都会触发一组测量,即使写入的值和上次相同,也不能省去这次写操作。名义转换时间为 (1.1+1.1)×4=8.8ms,驱动仍要等完成标志,不能延时 8.8ms 后直接假定已经完成。
流程分成三个边界。
第一,触发前核对配置与校准,写配置开启新一轮。初始化先进入关断模式,写入校准值,并关闭本专题没有使用的 ALERT 功能。
第二,轮询 Mask/Enable,检查同一次读取返回的 CVRF 和 OVF。CVRF 是 bit3,表示转换、平均、乘法都完成;OVF 是 bit2,表示内部运算溢出,电流及功率可能无效。
读取 Mask/Enable 会清除 CVRF。 因此,读到就绪后必须保存这次返回值,不能为了“再确认一下”立即重读,再拿已清除的标志判断超时。配置写入也会清除 CVRF,关断模式写入除外。
第三,就绪后一次读完分流、总线、电流,复查配置和校准。全部成功才更新结果,读完前不启动下一轮。此时结果寄存器不会像连续模式那样自行开始下一组更新。
附件中的采集函数如下,底层回调和结果类型也在同一个工程中。
ina_result ina_capture(const ina_io *io, ina_sample *out) {
uint16_t cfg, cal, status, bus, shunt, current;
uint32_t start = io->millis(io->ctx), ready;
if (!io->read(io->ctx, 0, &cfg) || !io->read(io->ctx, 5, &cal)) return INA_IO;
if ((cfg != INA_CFG_IDLE && cfg != INA_CFG_ONCE) || cal != INA_CAL) return INA_CONFIG;
if (!io->write(io->ctx, 0, INA_CFG_ONCE)) return INA_IO;
for (;;) {
if ((uint32_t)(io->millis(io->ctx) - start) >= 30u) return INA_TIMEOUT;
if (!io->read(io->ctx, 6, &status)) return INA_IO;
ready = io->millis(io->ctx);
if ((uint32_t)(ready - start) >= 30u) return INA_TIMEOUT;
if (status & 8u) break; /* Reading register 6 clears CVRF: retain this returned word. */
io->sleep_ms(io->ctx, 1);
}
if (status & 4u) return INA_OVERFLOW;
/* One shot is complete. No next conversion is started until this function is called again. */
if (!io->read(io->ctx, 1, &shunt) || !io->read(io->ctx, 2, &bus) ||
!io->read(io->ctx, 4, ¤t) || !io->read(io->ctx, 0, &cfg) ||
!io->read(io->ctx, 5, &cal)) return INA_IO;
if (cfg != INA_CFG_ONCE || cal != INA_CAL) return INA_CONFIG;
if ((uint32_t)(io->millis(io->ctx) - start) >= 30u) return INA_TIMEOUT;
ina_sample next = {out->seq + 1u, ready, bus, signed16(shunt), signed16(current)};
*out = next; /* Only successful calls publish a complete sample. Not a thread-safe primitive. */
return INA_OK;
}函数先把新值放在局部变量中。通信失败、没有按时完成、溢出或者配置不符,都会提前返回,外部的上一份有效数据不变。成功时才增加 seq,让上层明确知道有没有拿到新数据。
这里的结构体赋值是单线程内的提交点,不是跨中断、跨任务的原子操作。使用 RTOS 时,应由一个任务独占 INA226,并通过队列或受保护的快照向其他任务发布;不能让另一个任务在中途重写配置或触发下一轮。
30ms 是本配置的成功截止线。底层每个寄存器事务另有 5ms 名义软件时限,阻塞时错误返回可能稍晚于截止线,不能把它当成硬实时保证。改变平均次数或转换时间后,应连同截止线一起重算。
设 Current_LSB=1mA/bit、Rshunt=5mΩ,按手册公式计算:
CAL = 0.00512 ÷ (0.001 × 0.005) = 1024。
这是按名义阻值计算的缩放参数,不是一次实体标定。电阻公差、温漂、焊接和取样压降,需要在有参考仪表的条件下另行处理。
总线电压每位 1.25mV,电流按这里设置为每位 1mA,主机乘积可写成:
int64_t ina_power_uw(const ina_sample *s) {
/* 1.25mV/bit * 1mA/bit = 1.25uW/bit^2. Signed, rounded toward zero. */
return (int64_t)s->bus_raw * s->current_raw * 5 / 4;
}其中 current_raw 已按有符号 16 位数解释,再参与运算。乘法先提升到 64 位,防止较大数值在中间步骤溢出。这里计算的是主机有符号乘积,没有借用芯片功率寄存器去推断反向功率行为。
还要划清一个测量边界。总线电压与分流电压是顺序转换,四次平均也覆盖一段时间。同一轮不等于同时采样,平均电压乘平均电流也不等于任意动态负载下的平均瞬时功率。 这个改动解决的是寄存器跨轮拼接,适用于低速成组监测;开关瞬态与相位问题需要相应的采样硬件和测试方法。

图6中 PB6=SCL、PB7=SDA,USART1 PA9 为 TX、PA10 为 RX。最小固件使用内部 HSI 8MHz,SysTick 提供 1ms 计时,不依赖外部晶体,也不驱动屏幕。
构建只需要 Python 3.12 和 Zig 0.13.0,无第三方 Python 包。解压附件,进入 firmware 目录,将命令末尾改为本机 Zig 路径:
python build.py C:/Tools/zig-0.13.0/zig.exe脚本先编译并运行主机测试,再生成 STM32F103C8 的 ELF、BIN,核对 Flash 地址、初始栈和复位向量。本次生成的 Flash 镜像为 3084 字节。附件包含启动文件、链接脚本、I²C 适配器、核心驱动和测试,不需要从文章片段自行拼工程。
准备烧录时,用 ST-Link 连接 PA13/SWDIO、PA14/SWCLK、地和 3.3V 电压参考,BOOT0 为低;在 STM32CubeProgrammer 中确认目标后下载 ELF 并校验。若使用 BIN,基址填 0x08000000。具体接线及前提见附件 README。
串口使用 9600、8N1,PA9 接 3.3V 逻辑 USB-UART 的 RX,并共地。不要用 USB-UART 的 3.3V 引脚给整块功率板供电。正常采集会输出 OK、递增 seq 和带单位的数值;失败则输出错误码与 last_seq,不把旧值冒充新读数。
本文没有验收原板 DC/DC、LDO 和高电流负载路径。复现固件应使用供电已经验收的板卡;独立搭台可用已验证供电的 3.3V STM32F103C8 与 INA226 板,先确认实际分流电阻和上拉电压,再从低压限流小负载核对缩放及方向。不同分流电阻必须重算 CAL,不能直接套用二进制。

图7由本次实际运行日志重排,属于主机模型与交叉构建结果,没有连接实体板。
测试模型复现了连续模式的 24W 跨轮反例。改用单次触发后,同一组合成输入交付 B 轮的 10W。随后逐一让 13 个采集通信操作失败,检查错误上报和上一份有效数据是否保留;初始化中的 5 个操作也逐一注入失败。
另外覆盖了旧 CVRF 清除后重触发、只消费一次就绪返回字、永不就绪、OVF、配置或校准失配、读取中复位、32 位毫秒回绕、负电流及 64 位功率中间量。这些测试检验驱动在约定模型下的行为,不能替代逻辑分析仪检查开漏边沿、ACK 与实际转换时序。
上板排查可以沿着错误码走:I/O 错误先查地址、上拉、地线与被拉低的总线;配置错误检查芯片是否复位、是否被其他代码改写;超时先核对转换参数与时钟;OVF 则检查校准和输入量程。不要把“错误时仍有上次读数”误当作采集还在正常更新,seq 和状态要一起看。
低速功率监测里,一份数据最先需要交代的,是它从哪一轮来。先把转换边界和失败行为讲清楚,后面的显示、记录和报警才有可靠的输入。
最小工程及同版硬件源文件:下载完整工程,附件名 ina226-one-shot-v1.0.0.zip。独立固件按 MIT 提供;原理图、PCB 源文件及其派生图片按项目 GPL-3.0 保留,硬件作者 HumphreyLin。许可文本与必要声明见附件。
硬件项目 https://oshwhub.com/HumphreyLin/INA226gong-shuai-ji-yan-zheng-ba 。上游固件仅作核验,未复制进附件,仓库 https://gitee.com/humphreylin/INA226-program ,核对版本 d3aaa39061824052b58e9e10e1437a3cb78f7715,其 LICENSE 为 AGPL-3.0。
技术依据为 TI INA226 SBOS547C 第6.3.1、6.5.1、7.1.1、7.1.5及7.1.7节 https://www.ti.com/document-viewer/INA226/datasheet ,以及 ST RM0008 Rev21、PM0056 Rev7。硬件图从同版 EDA 工程重新导出,只作裁剪和保真缩放;时序图为明确标注的合成示意。已完成主机故障模型测试与目标构建,未进行实体板烧录、精度、温升或功率额定验证。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。