ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ATT7022三相电能计量程序:SPI通信、24位补码与三段式校表

ATT7022三相电能计量程序:SPI通信、24位补码与三段式校表

简介:面向电力计量与嵌入式开发者的ATT7022电能计量程序代码包,聚焦高精度单相电能采集场景,适合已具备C语言与单片机基础、希望理解计量芯片寄存器配置、电压电流采样及功率电能计算实现的读者参考。压缩包共13个文件,约58KB,包含C源文件、汇编与中间文件、HEX烧录文件以及Keil工程配置与链接相关文件,可用于还原一套可编译的计量程序结构,并观察初始化、数据采集、通信接口和累计电能处理等模块的组织方式。已有1354人学习,说明其在电能计量学习中具有一定参考价值。包内工程文件便于按原有目录直接打开、编译与对照分析,读者可从中梳理ADC采样、功率积分、I2C/SPI读写及异常处理的代码脉络,也可作为课程设计、毕业设计或小型电能计量项目的起步模板,在此基础上按实际硬件修改采样参数与通信时序。

1. 电能计量芯片ATT7022程序代码:先把通信链路跑通

电能计量芯片 ATT7022 的程序代码,最终要在板子上做的是“读得回来、算得准、校得住”这三件事,但实际动手调试时,最先把人卡住的往往不是计量算法,而是通信:SPI 读回来三个字节全是 0xFF,或者数据每秒钟跳一次。这份代码围绕三相电能计量的完整链路组织,包含底层 SPI 字节搬运、24 位补码寄存器读取、电压电流有效值与有功、无功功率的浮点换算、增益、相位和偏置三段式校表,以及电能脉冲与电量累加。它面向的是做三相导轨表、面板表、充电桩计量模块、能耗监测终端的嵌入式开发,需要你至少有 C 语言、SPI 外设和定时器的基础;如果你手上正好有一块焊着这颗芯片的板子和一台标准源,照着从第 2 章读下去,一晚上能让第一版读数先动起来。

2. 工程结构与通信链路:把 ATT7022 的芯片 ID 先读出来

2.1 目录结构:驱动、换算、业务分三层

我见过不少计量项目的代码,所有东西都堆在main.c里,SPI 收发、系数换算和电量累加挤在同一个函数,等到需要换 MCU 或者换量程,基本只能重写。这套工程的结构按职责切了三层,bsp只负责搬字节,driver负责协议和换算,app负责业务节奏:

att7022_demo/ ├── app/ │ ├── main.c 上电流程与任务调度 │ └── meter_task.c 1 s 计量任务、电量累加、自检 ├── bsp/ │ ├── bsp_spi.c/.h SPI 收发,只管字节 │ └── bsp_gpio.c/.h CS、RESET 引脚控制 ├── driver/ │ ├── att7022_reg.h 寄存器地址与读写命令位 │ ├── att7022.c/.h 读寄存器、换算、校表写入 ├── cal/ │ └── cal_param.c/.h 系数与校表参数,支持从 Flash 载入 └── doc/ └── cal_record.md 校表记录表,每次校准填一行

这个分法带来的实际收益很直接:bsp_spi.c里只有spi_write()和spi_read()两个函数,把它们换成另一颗 MCU 的 HAL 库只需要改这一个文件,driver以上一行不动。我在别的项目上换 MCU 时,正是因为bsp这层隔离,移植只花了半天;而结构混乱的那种工程,往往要重写整个驱动。cal_param单独抽出来,是因为校表参数必须能在出厂后重新写入,还要考虑掉电保存与参数版本升级,这些和寄存器读写的逻辑不应该混在一起。

doc/cal_record.md这个文件别嫌麻烦,每次校表写一行:板号、环境温度、标准源点位、校准前误差、写入的增益值、校准后误差。三个月后客户反馈某批表偏快,你翻这个表就能定位到是哪一轮校准出的问题,比凭记忆猜靠谱得多。

2.2 硬件连接与 SPI 模式:CPOL/CPHA 别拍脑袋

先看连线。这颗芯片对外就 SPI 四根线加一根复位,另外还有 CF 脉冲输出,校表阶段会用到:

芯片引脚接到 MCU说明
SCLKSPI_SCK时钟,建议先降到 500 kHz 调通再往上提
DINSPI_MOSIMCU 输出,命令字节和写入数据走这里
DOUTSPI_MISO芯片输出,读寄存器时返回 24 位数据
CSNGPIO 输出片选,低有效,一次完整读写期间必须一直拉低
RESETGPIO 输出复位,低有效,上电时拉低再释放
CF1/CF2定时器输入捕获有功/无功脉冲,校表和精度验证靠它

SPI 模式是最容易出错的地方。这颗芯片的手册一般要求时钟空闲为高、在第二个边沿采样,也就是 CPOL=1、CPHA=1 的模式 3。我第一次调试图省事,直接按模式 0 配置,读回来永远是 0xFFFFFF,折腾一下午才在示波器上看出采样沿不对。时钟频率别一上来就冲几兆,先用 500 kHz 把通信跑通,确认数据稳定后再逐步提高到手册允许的上限;杜邦线连着调的时候,线一长时钟一高,读出来就是随机数。

还有一点容易忽略:片选拉高之后要留一点空隙再发下一次命令。有些 MCU 的 SPI 外设在 CS 拉高后立刻又开始推时钟,芯片内部的帧状态机还没复位,第二次读就会整体错位一个字节。

提示:调通信阶段先别接标准源,空板子读寄存器就够了,把线和模式确认死,再谈精度。

2.3 初始化与自检:延时、复位、读 ID 三步走

上电初始化其实就三步,但每一步都有个坑。第一步是延时,芯片内部基准和振荡器需要时间稳定,手册通常要求 10 ms 以上,工程里我给 20 ms 留余量;第二步是复位,RESET 拉低几个毫秒再释放,让内部寄存器回到确定状态;第三步是读芯片 ID 或状态寄存器,用一次已知结果的读操作来确认整条链路通了。

/* att7022.c —— 上电初始化,返回 0 表示通信正常 */ uint8_t att7022_init(void) { att7022_port_init(); /* GPIO + SPI 外设,模式 3,500 kHz */ ATT7022_RST_LOW(); /* 硬复位,保证内部寄存器状态确定 */ delay_ms(5); ATT7022_RST_HIGH(); delay_ms(20); /* 等内部基准稳定,手册要求 >10 ms */ att7022_write_cfg(0x000000); /* 写配置寄存器,量程/模式按项目定 */ delay_ms(5); int32_t id = att7022_read_raw(REG_CHIPID); if (id == 0xFFFFFF || id == 0x000000) { return 1; /* 全 1 或全 0,链路没通,别再往下走了 */ } return 0; }

这段代码的关键不是那几行调用,而是最后那个判断:全 0xFF 和全 0x00 是两个完全不同的故障方向。全 0xFF 通常是 MISO 被上拉、芯片根本没驱动总线,问题出在片选、电源或者 SPI 模式;全 0x00 更像是芯片没上电或者复位没释放。把这两种情况在初始化阶段就区分开,后面排查能省掉大量时间。返回值也别丢,main里拿不到 0 就直接进故障指示,别让业务层继续拿着垃圾数据算电量。

att7022_write_cfg()写进去的内容跟具体项目相关:量程、能量累加模式、CF 脉冲的输出选择,这些位定义都在手册里,我一般会把用到的配置项写成宏,别直接甩一个十六进制常量进去,半年后自己都看不懂那串数是什么意思。

3. 数据链路拆解:从 24 位补码到电压、电流、功率的浮点值

3.1 寄存器映射与一次完整读时序

这颗芯片的所有计量结果都是 24 位有符号数,高位在前,一次读操作就是“发一个命令字节,再收三个字节”。命令字节的 bit7 用来区分读和写,bit0 到 bit6 是寄存器地址。地址映射各个型号、各个批次会有细微差异,工程里的做法是把地址全部宏定义在一个头文件里,用之前对着手册核一遍:

/* att7022_reg.h —— 地址以手头芯片手册的映射表为准,这里只列用到的 */ #ifndef __ATT7022_REG_H #define __ATT7022_REG_H #define ATT7022_CMD_READ 0x80u /* bit7 = 1 表示读;写操作时清掉这一位 */ #define REG_SYSSTA 0x00u /* 系统状态 / 配置 */ #define REG_PQA 0x01u /* A 相有功功率 */ #define REG_PQB 0x02u #define REG_PQC 0x03u #define REG_PQ_SUM 0x04u /* 合相有功功率 */ #define REG_RMS_UA 0x0Du /* A 相电压有效值 */ #define REG_RMS_UB 0x0Eu #define REG_RMS_UC 0x0Fu #define REG_RMS_IA 0x10u /* A 相电流有效值 */ #define REG_RMS_IB 0x11u #define REG_RMS_IC 0x12u #define REG_FREQ 0x1Cu /* 电网频率 */ #define REG_CHIPID 0x1Fu /* 芯片 ID / 版本号 */ #endif

读函数的实现要点全在细节里,我把它单独拎出来:

/* 读一个 24 bit 寄存器,返回已做符号扩展的原始值 */ int32_t att7022_read_raw(uint8_t addr) { uint8_t buf[3] = {0}; uint8_t cmd = addr | ATT7022_CMD_READ; int32_t raw; ATT7022_CS_LOW(); spi_write(&cmd, 1); /* 命令字节,先发 */ spi_read(buf, 3); /* 再收 3 字节,高字节在前 */ ATT7022_CS_HIGH(); delay_us(2); /* 帧间隔,别省这一下 */ raw = ((int32_t)buf[0] << 16) | ((int32_t)buf[1] << 8) | (int32_t)buf[2]; if (raw & 0x00800000) { /* 第 24 位是符号位 */ raw |= 0xFF000000; /* 手工扩展到 32 位,否则负数会变成大正数 */ } return raw; }

符号扩展这一行是必须要写的。有功功率在反向送电时是负数,如果不扩符号,0xFFFF00这种值会被解释成 16776960,表就变成“永远正向发电”。同理,无功功率在容性负载下也是负的,功率因数符号判断依赖它。这个 bug 的隐蔽之处在于:正向负载下一切正常,只有现场出现反向潮流或者容性补偿时才暴露,属于典型的“实验室测不出来、现场翻车”类型。

delay_us(2)也别删。有些芯片在片选释放后需要一小段时间才能接受下一条命令,尤其是你连续读七八个寄存器组的时候,少这个延时就会偶发读到上一帧的数据。

3.2 符号扩展与系数换算:K 值到底怎么来

原始值只是码值,要变成伏特和安培,就得有系数。系数有两个来源:一是手册给出的满量程码值和额定值,直接除一下;二是拿标准源实测反推。工程里我一般两者都做,先用手册值让数据“有个大概”,再用实测值修正。

typedef struct { float k_u; /* 电压系数,V/LSB */ float k_i; /* 电流系数,A/LSB */ float k_p; /* 功率系数,W/LSB */ } att7022_cal_t; att7022_cal_t g_cal; /* 标准源加 220.0 V 在 A 相,读到 raw = 3913412,反推系数 */ void cal_init_from_measure(void) { g_cal.k_u = 220.0f / 3913412.0f; /* ≈ 5.62e-5 V/LSB */ g_cal.k_i = 5.0f / 1288340.0f; /* 5 A 点实测反推 */ g_cal.k_p = g_cal.k_u * g_cal.k_i; /* 功率系数理论上就是电压乘电流 */ } /* 读电压有效值,单位 V */ float att7022_read_u(uint8_t reg) { int32_t raw = att7022_read_raw(reg); return (float)raw * g_cal.k_u; }

这里有个常被忽略的细节:k_p不一定严格等于k_u * k_i。芯片内部计算功率的路径和有效值的路径不完全一样,如果实测发现两者差千分之几,那就老老实实把k_p单独实测出来,别强行用乘积去套。我在一个项目上就是因为想当然用了乘积,功率一直偏 0.3%,查了两天才发现是这里。

电压有效值正常情况下不该出现负值,如果读出来是负的,别急着改代码,先怀疑是不是负载有直流分量或者采样通道被干扰了。电流同理。这类“理论上不可能”的值出现在计量数据里,往往意味着硬件侧有问题,代码里加个负值计数上报,比在软件里悄悄取绝对值要有用得多。

3.3 功率因数与电能累加:把瞬时值变成电量

功率因数直接用有功除以视在功率,视在功率由电压有效值乘电流有效值得到。要注意的是分母接近零时的保护,空载状态下电流有效值可能只有几十个码值,直接除会得到一个荒唐的数,业务层拿去显示就是“功率因数 12.7”这种笑话。

/* 功率因数,返回值范围 -1.0 ~ 1.0,空载返回 0 */ float att7022_read_pf(uint8_t reg_p, uint8_t reg_u, uint8_t reg_i) { float p = (float)att7022_read_raw(reg_p) * g_cal.k_p; float u = att7022_read_u(reg_u); float i = (float)att7022_read_raw(reg_i) * g_cal.k_i; float s = u * i; if (s < 1.0f) { /* 视在功率太小,判为空载 */ return 0.0f; } float pf = p / s; if (pf > 1.0f) pf = 1.0f; /* 采样噪声可能导致略微越界 */ if (pf < -1.0f) pf = -1.0f; return pf; }

电量累加是整个工程里最容易埋雷的地方。用float累加瓦时,跑到几千度之后,加上去的 0.0001 级别增量会被浮点精度直接吃掉,表就越跑越慢。正确做法是用 64 位整数按微瓦时累加:

static int64_t g_energy_pos_uwh; /* 正向电量,单位 μWh */ static int64_t g_energy_neg_uwh; /* 反向电量 */ /* 每 1 s 调用一次:功率(W) × 1 s ÷ 3600 = Wh */ void meter_task_1s(void) { float p = (float)att7022_read_raw(REG_PQ_SUM) * g_cal.k_p; int64_t inc = (int64_t)(p * 1000000.0f / 3600.0f); /* 1 s 内的 μWh */ if (inc >= 0) { g_energy_pos_uwh += inc; } else { g_energy_neg_uwh -= inc; /* 取正值存反向电量 */ } }

1 秒这个周期不是随便定的,它要和 CF 脉冲比对。校表时数 100 个脉冲、看用了多少时间,再折算成误差,如果软件累加周期和脉冲输出不同步,两边永远对不上。用 64 位整数还有一个好处:int64_t存微瓦时,能表示的电量远超一块表一辈子的量程,不用担心溢出。

4. 校表实战:增益、相位、偏置三段式校准怎么落地

4.1 校表寄存器与写入时序

校表寄存器就是芯片内部用来修正测量结果的几个 24 位参数,写进去之后,后续所有读数都已经带上修正。写操作和读操作方向相反:命令字节的 bit7 清零,然后连续发三个字节的数据,高位在前。

/* 写一个 24 bit 校表寄存器,写后建议回读校验 */ int att7022_write_cal(uint8_t addr, int32_t value) { uint8_t buf[3]; uint8_t cmd = addr & 0x7F; /* bit7 = 0 表示写 */ buf[0] = (uint8_t)((value >> 16) & 0xFF); buf[1] = (uint8_t)((value >> 8) & 0xFF); buf[2] = (uint8_t)(value & 0xFF); ATT7022_CS_LOW(); spi_write(&cmd, 1); spi_write(buf, 3); ATT7022_CS_HIGH(); delay_us(2); /* 回读确认,写丢了后面所有校准都是白做 */ return (att7022_read_raw(addr) == value) ? 0 : 1; }

回读校验这一步千万别省。SPI 是单向推数据,MCU 发出去了不代表芯片收进去了,尤其是在有干扰的台面上。我在一次批量试产里就是因为没回读,二十几块表里有三块校表参数没写进去,出厂测试才被发现,返工拆壳重烧。

有些型号的校表寄存器带写保护,需要先往特定寄存器写一个解锁序列,写完再上锁。这个序列在手册里,别硬编码在业务函数里,封装成cal_unlock()和cal_lock(),将来换型号只改这两处。

注意:写校表参数期间别让计量任务同时跑,读写交叉会让参数写一半被打断。写之前把计量任务挂起,写完再放开。

4.2 三段式校准顺序:先增益,再相位,最后小信号偏置

校准一定要按顺序来,因为这三者会互相串扰。增益影响全量程,相位主要影响功率因数不为 1 的点,偏置只在小信号段明显。顺序错了,你会陷入“调完增益小信号又不对,调完偏置大信号又偏了”的循环。

步骤负载点调什么观察量
1100% 额定,PF=1.0增益寄存器有功功率误差
250% 额定,PF=0.5L相位补偿寄存器无功与有功的比值
35% 额定,PF=1.0偏置寄存器小信号功率误差
4回到步骤 1 复测微调三点误差是否收敛

增益校准纯粹是比例关系,用标准源输出一个准确的功率值,比较表上的读数,算出修正系数。手算容易把分子分母弄反,我一般写个几行的小脚本:

# cal_gain.py —— 用标准源读数反算增益,避免手算错方向 rated = 2200.0 # 标准源输出的有功功率,W measured = 2207.3 # 表上读到的值,W old_gain = 1.000000 # 当前写入的增益系数 new_gain = old_gain * rated / measured # 读数偏大,系数要往小调 print(f"new gain = {new_gain:.6f}") # 写回寄存器前按位宽取整

方向判断有个口诀:读数偏大,系数往小调。因为最终显示值等于原始码值乘系数,系数小了显示值才会降下来。这个方向我见过不止一个新手弄反,结果越校越偏,最后误差跑到 20% 才反应过来。

相位校准要在感性负载下做,PF=0.5L 是最常用的点。现象是:纯阻性负载下误差很小,一加电感,误差就明显偏负或者偏正,这就是相位补偿没调好。调整方法是每次改 1 个 LSB,观察误差是否线性变化,如果误差跟着线性走,说明方向对了;如果误差反而变大,说明补偿方向反了,换个符号继续。

偏置校准放到最后,在 5% 甚至 1% 额定电流下做。小信号误差的来源是通道失调和噪声,用偏置寄存器把它压下去。这里有个经验:偏置不要一次调到位,调一半,退回来,再调一半,反复两三轮,比一次调死更稳,因为偏置和增益在小信号段还有轻微耦合。

4.3 精度验证:用标准源跑一遍点位表

校准做完不等于结束了,必须跑一遍完整的点位表验证,而且要覆盖功率因数和电流大小的组合。工程里常用的门槛是有功 1.0 点 ±0.2%、0.5L 点 ±0.3%、小信号点 ±0.5%,具体数值按自家产品标准来,别拿别人的指标硬套。

验证方法有两种,一种是直接读寄存器值和标准源读数比,另一种是用 CF 脉冲计数。后者更接近电表实际工况:设定脉冲常数比如 3200 imp/kWh,用定时器捕获 100 个脉冲的时间,和理论时间比,算出误差。脉冲法能顺带验证脉冲输出电路和软件累加是否一致,我更推荐用它做最终判定。

温度点别漏。常温校准完,把表放进高低温箱跑 -25℃、25℃、60℃ 三个温度点,看误差漂移量。如果漂移超过指标,说明你选的偏置参数是“常温最优”而不是“全温区可用”,得往回退一点,牺牲常温精度换温度稳定性。这个取舍在新产品定义阶段就要想清楚。

5. 避坑与排查:读回全 0xFF、功率跳变的现场记录

5.1 通信类:全 FF、错位、偶发丢帧

现象一:上电读什么都是 0xFFFFFF,换个寄存器也一样。原因通常出在物理层:SPI 模式配错、片选没真正拉低、MISO 被上拉电阻拉住而芯片没驱动。先用万用表量芯片供电和复位脚电平,确认芯片是活的;再用逻辑分析仪抓 CS、SCLK、MOSI 四根线,看命令字节有没有真的发出去。解决方式是把时钟降到 500 kHz、模式改成 3、片选拉低后加 1 μs 延时再发时钟,三个动作一起做,九成能通。

现象二:数据能读,但每次都是正确值的两倍或者整体移位一个字节。这是典型的帧长度错。24 位数据必须一次收满三个字节,多收一个或者少收一个,移位后读出来的数就有规律地放大或缩小。另一个可能是命令字节的读写位搞反了,写命令当成读命令发,芯片返回的就是上一次的残留数据。解决办法是固定读一个已知内容的寄存器做基准,比如芯片 ID,确认位序和字节序,再往下做。

现象三:长时间跑偶尔丢一两帧,重启就好。多半是总线竞争或者中断打断。SPI 是共享外设,如果别的模块(比如 Flash)在计量读取过程中抢了总线,片选一乱,整帧就废了。解决方式是把一次寄存器读取做成原子操作:进临界区、关掉可能抢占的中断,或者给 SPI 总线加互斥锁。这个 bug 特别阴,实验室里跑一天不一定出现,现场跑一周必炸。

5.2 计量类:功率跳变、小信号不准、温漂

现象四:空载时功率在正负之间乱跳,电量悄悄往上涨。原因是噪声和失调,加上软件没有做启动电流判断。芯片在零电流时输出不是严格的零,而是几十个码值的抖动,乘以系数就是几瓦,一天下来能累出零点几度。解决办法是设一个启动阈值,一般取 0.4% 额定电流,连续 N 次(比如 5 次)超过阈值才开始计量,低于阈值直接归零;同时把小信号偏置校准做扎实,从源头把零点压下去。

现象五:大电流点很准,小电流点误差跑到 3% 以上,怎么调增益都救不回来。只校了增益没校偏置的典型症状。增益是乘性误差,偏置是加性误差,小信号段加性误差占比大,光调乘法系数永远调不平。回去补做 4.2 里的第三步,在 5% 点单独调偏置,调完再复测大电流点,确认增益没被带跑。

现象六:出厂时全合格,装到现场一个月后误差慢慢变大。两种可能:一是校表参数没写进 Flash 或者写进去没有校验,掉电后回落到默认值;二是温漂,现场柜内温度比实验室高十几度。前者靠存储时加 CRC 校验解决,上电时校验失败就报警而不是静默用默认值;后者要靠高低温箱提前摸底,把参数往温度稳定的方向偏一点。这两种情况我在不同项目上都遇到过,最麻烦的是第二种,因为它在出厂测试里完全看不出来。

6. 二次开发改造:轮询换定时任务,校准参数搬进 Flash

6.1 把裸机轮询改成定时任务加状态机

原始代码最典型的样子是在while(1)里读一遍、算一遍、延时 100 ms 再读。这么写的问题不是效率,而是不可控:一旦某个读操作卡住,整个循环就停了,看门狗要么不喂要么乱喂。改造的思路是把读、算、存拆成三个动作,用一个 1 s 的定时器触发,中间用状态机串起来。

typedef enum { METER_IDLE = 0, METER_READ, /* 读寄存器组 */ METER_CALC, /* 换算 + 电量累加 */ METER_CHECK, /* 自检与异常计数 */ } meter_state_t; static meter_state_t s_state = METER_IDLE; /* 1 s 定时器回调里只置标志,别在里面做 SPI 读写 */ void meter_tick_isr(void) { if (s_state == METER_IDLE) { s_state = METER_READ; } } void meter_poll(void) /* 主循环里调用,非阻塞 */ { switch (s_state) { case METER_READ: meter_read_all(); /* 一次性把 9 个寄存器读进缓存 */ s_state = METER_CALC; break; case METER_CALC: meter_calc_and_accumulate(); s_state = METER_CHECK; break; case METER_CHECK: meter_self_check(); s_state = METER_IDLE; /* 等下一个 tick */ break; default: s_state = METER_IDLE; break; } }

这样改的好处是每次meter_poll()执行时间可控,SPI 读取失败可以在METER_READ状态里重试两次再放弃,而不是把整个系统卡死。中断里只置标志也是一个原则,SPI 传输放在中断里做,时序会被打断,读出来的数据不可信。

6.2 校准参数进 Flash,带版本号和 CRC

校准参数是这块表最值钱的数据,丢了就得返厂重校。存储结构我一般这么设计:头部放魔数、版本号和 CRC,后面才是系数数组。上电时先校验魔数,再算 CRC,任何一个不过就进故障态,绝不静默使用默认值。

字段长度作用
magic4 B0x41545432,标记这是本产品的参数区
version2 B参数结构版本,升级时做兼容
crc162 B覆盖整个参数区的校验
k_u / k_i / k_p12 B三个换算系数
gain / phase / offset24 B三段式校表参数

写 Flash 的时候要注意,先擦后写,写完立刻回读比对;如果参数区跨了两个扇区,得先把整块读到 RAM,改完再整体写回,别就地改一个字节。掉电保护也要考虑,可以在写完参数后置一个“已校准”标志位,上电看到标志位和 CRC 都正常才认为参数有效。

6.3 加一层自检,让表自己说出哪里不对

合相有功应该约等于三相之和,这个关系可以拿来做事后校验。读完全部寄存器后算一下偏差,超过 2% 就记一次异常;频率读数超出 45 到 65 Hz 的范围也记一次;通信连续失败三次就点亮故障灯。这些计数不用实时上报,存在 RAM 里,等上位机来抄的时候一起读走。

我吃过一次亏:一块表在现场跑着,读数看着正常,但 A 相电流通道的线虚焊了,芯片输出一直是零,合相功率靠 B、C 两相撑着,用户少计量了两个月才发现。要是当时有合相校验,这个偏差第一天就会触发异常记录。从那以后我每次做计量类项目,都会在上电自检里强制跑一遍三相平衡校验和通道断线判断,宁可多写五十行代码,也不要让表“沉默地错下去”。

校表参数的写入接口也建议留一个工程模式入口,用按键组合或者私有命令解锁,方便现场重新校准而不用拆机。这个口子要做权限和次数限制,别让它变成谁都能改的漏洞。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进