
1. 从一个“电费焦虑”说起这个项目到底在做什么去年夏天我收到一张电费单比前年同期多了将近四百块。家里电器没添置空调温度也没调低钱到底花在哪儿了我翻遍电力公司的App只能看到每天的总用电量至于哪个时段、哪台设备在“偷电”完全是个黑盒。这种无力感相信很多人都有——你明明想省电却连敌人在哪都不知道。这就是我动手做Smart ElectricityGuard的直接原因。它的核心逻辑非常朴素用一块ESP32作为主控配合电流互感器CT和电压采样电路实时采集入户或单台设备的电压、电流、功率、功率因数把数据通过 Wi-Fi 上传到本地服务器或云平台再结合分时电价算出实时电费和累计成本。你打开手机或电脑就能看到“此刻客厅空调正在以 1.8kW 运行今天已经花了 6.3 元”。这个项目适合谁如果你有基础的电工常识会焊接、能看懂简单的电路图并且玩过 Arduino 或 ESP-IDF那你可以直接复现。如果你是完全零基础的小白也不用慌我会把每个环节拆到“为什么选这个电阻”“为什么代码里要乘这个系数”的程度。整套方案的成本控制在 80 到 150 元之间取决于你手头已有的工具和模块。需要提前说明的是这个项目涉及220V 强电安全永远是第一位的。我在后面会专门用一节讲强电隔离和校准的注意事项这部分内容你务必逐字看完再动手。2. 方案选型为什么是 ESP32而不是 Arduino Uno 或树莓派2.1 主控芯片的取舍逻辑市面上做电能监测的方案大致分三类第一类是专用计量芯片如 BL0937、HLW8032配 8 位单片机第二类是 ESP32 这类带 Wi-Fi 的 32 位 MCU第三类是树莓派加 ADC 扩展板。我最终选 ESP32理由有三个而且每个都经过实测验证。第一内置 Wi-Fi 和蓝牙。电能监测的核心价值在于“远程可见”如果主控没有网络能力你还得额外挂一个 ESP-01 或串口转 Wi-Fi 模块成本和复杂度都上去了。ESP32 把 Wi-Fi 和蓝牙集成在单芯片里省掉至少 15 元的模块钱和一堆杜邦线。第二双核 240MHz 的处理能力。电能计算需要做均方根RMS运算和功率因数计算采样率至少要 1kHz 以上才能保证精度。Arduino Uno 的 16MHz 单核在跑采样时几乎没法同时处理网络协议栈而 ESP32 的一个核心专门跑采样算法另一个核心跑 Wi-Fi 和 MQTT互不干扰。第三ADC 和 DMA 的配合。ESP32 的 ADC 虽然线性度一般但配合 DMA 可以实现连续采样不用 CPU 频繁中断。这一点在做真有效值True RMS计算时非常关键后面我会详细讲怎么用 I2S 接口读 ADC 来绕过 Wi-Fi 对采样的干扰。树莓派的优势是算力强、能跑 Python但它的 GPIO 没有模拟输入必须外挂 ADC 芯片如 MCP3008而且树莓派本身功耗高、需要 5V 2A 供电长期运行还得考虑散热和 SD 卡寿命。对于“7×24 小时挂机监测”这个场景ESP32 的功耗和稳定性明显更合适。2.2 电流采样方案CT 互感器 vs 霍尔传感器 vs 分流电阻电流采样是电能监测的“心脏”选错了后面怎么校准都白搭。我试过三种方案直接给结论推荐开口式电流互感器CT型号选 SCT-013-030 或 ZMPT101B 配套的电流版本。分流电阻方案在火线上串一个毫欧级电阻测压降精度最高但它不隔离强电直接进 ADC一旦电阻烧断或焊接不良220V 直接灌进 ESP32芯片和人都危险。霍尔传感器如 ACS712隔离性好但温漂大零点漂移明显测小电流时误差能到 10% 以上。CT 互感器通过电磁感应取电流天然隔离精度在 1% 到 3% 之间价格只要十几块是家用监测的甜点选择。具体接线时CT 的输出是交流小电流信号需要在输出端并联一个burden resistor负担电阻把电流转成电压。以 SCT-013-030 为例它的变比是 1800:1额定输入 30A输出 1V。如果你要测的最大电流是 20A那么负担电阻选 62Ω 左右输出峰值约 0.69V正好落在 ESP32 ADC 的 0-3.3V 量程内。这个电阻的精度直接影响测量精度建议用 1% 金属膜电阻。2.3 电压采样为什么不用简单的电阻分压电压采样看起来简单——两个电阻分压把 220V 降到 1V 以内就行。但这里有个坑电阻分压不隔离而且交流信号的负半周会被 ADC 截掉。我的做法是用ZMPT101B 电压互感器它同样是电磁隔离输出一个以 2.5V 为中心的交流信号正好覆盖正负半周。ZMPT101B 模块自带一个可调电位器用来校准输出电压幅度。你需要在输入端接 220V用万用表测输出端交流电压调到约 1V AC峰值约 1.414V这样加上 2.5V 的直流偏置后信号范围在 1.086V 到 3.914V 之间。等等3.914V 超过了 ESP32 的 3.3V 上限所以实际要调到输出峰值不超过 0.8V即交流有效值约 0.57V。这个细节很多教程都不提但它是烧 ADC 的常见原因。3. 硬件搭建从原理图到实际焊接的完整流程3.1 核心元器件清单与选型理由先列清单再说每个东西为什么选它。元器件推荐型号数量选型理由主控ESP32-WROOM-321双核、Wi-Fi、ADC、I2S性价比最高电流互感器SCT-013-0301开口式免断线30A量程覆盖家用电压互感器ZMPT101B1隔离采样输出可调模块化方便负担电阻62Ω 1% 金属膜1将CT电流转为电压精度关键偏置电阻10kΩ 1%2生成1.65V直流偏置滤波电容10μF 16V2稳定偏置电压滤高频噪声电源5V 1A 隔离电源模块1给ESP32供电隔离市电显示屏SSD1306 OLED 0.96寸1本地显示实时数据可选外壳阻燃ABS接线盒1强电部分必须封闭这里重点说两个东西。隔离电源模块不是随便选的它把 220V 转成 5V 的同时初级和次级之间没有电气连接这样即使后面电路出问题也不会把市电引到 USB 口上。我用的是一款 5V 1A 的 AC-DC 隔离模块输入 85-265V 宽电压输出纹波小于 100mV。阻燃接线盒是安全底线。所有强电接线火线进、火线出、零线都在盒子内部完成盒子外面只留一个 USB 口给调试用。我见过有人把 CT 和分压电阻直接焊在洞洞板上用热熔胶固定这种做法的风险极高一旦胶软化导致短路后果不堪设想。3.2 电路连接一步步照着接整个电路分三部分强电侧、信号调理侧、数字侧。我按信号流向来说。强电侧220V 火线从接线盒的 L 端子进入先经过一个 1A 保险丝然后分成两路。一路接到 ZMPT101B 的输入端另一路穿过 CT 互感器的穿孔CT 是开口式的火线从中间穿过即可然后接到负载比如空调插座。零线直接接到负载的另一端。注意CT 只能穿一根线如果火线和零线同时穿过磁场抵消输出为零。信号调理侧CT 的输出两根线接到负担电阻两端同时并联一个 10μF 电容滤高频。负担电阻的一端通过 10kΩ 电阻接到 3.3V另一端通过另一个 10kΩ 电阻接地这样在 CT 无电流时ADC 读到的电压是 1.65V。ZMPT101B 模块的输出端同样需要加偏置但模块本身已经带了偏置电路你只需要把它的输出接到 ESP32 的另一个 ADC 引脚即可。数字侧ESP32 的 GPIO34 接电流信号GPIO35 接电压信号。这两个引脚都是 ADC1 通道因为 ADC2 在 Wi-Fi 工作时会被占用这是 ESP32 的一个硬件限制很多人踩坑。OLED 接 I2CSDA 接 GPIO21SCL 接 GPIO22。注意ESP32 的 ADC 参考电压是 1.1V但开发板内部有分压实际量程是 0-3.3V。不过 ADC 在接近 0V 和 3.3V 时非线性严重所以信号要控制在 0.2V 到 3.1V 之间。3.3 焊接与组装强电部分的“三查三验”焊接顺序建议从低压到高压。先把 ESP32、OLED、电阻电容焊在洞洞板或 PCB 上用 USB 供电测试程序能跑通再接信号调理部分最后接强电。强电部分焊完后必须做三查三验查短路用万用表蜂鸣档测 L 和 N 之间电阻正常应该是无穷大负载断开时。如果导通说明有短路绝对不能上电。查绝缘测 L 和外壳金属部分之间的电阻应该大于 10MΩ。查极性确认火线进了保险丝和 CT零线直接到负载不要接反。三验是验保险丝通断、验 CT 穿孔方向虽然交流方向不影响有效值但影响相位校准、验电源模块输出电压是否为 5V。我自己的习惯是第一次上电用隔离变压器给整个装置供电这样即使接错也不会跳闸或烧东西。如果没有隔离变压器至少要在插座前串一个漏电保护器。4. 固件开发采样、计算、上云的代码实现4.1 开发环境搭建Arduino 还是 ESP-IDF这个问题我被问过无数次。如果你只是想快速跑通用Arduino IDE配 ESP32 开发板包就够了安装方法是在首选项里添加开发板管理器网址然后搜索 esp32 安装。国内网络下载慢的话可以用乐鑫的国内镜像源或者直接下载离线包。但如果你要做高精度采样和稳定运行我建议用ESP-IDF因为它的 ADC 驱动更底层可以配置采样衰减和校准参数。Arduino 的analogRead()默认衰减是 11dB量程 0-3.3V但线性度没有校准。ESP-IDF 提供了adc_cali接口可以根据芯片内部的 eFuse 参数做校准精度能提升一个档次。我最终用的是 PlatformIO 配 ESP-IDF 框架在 VSCode 里开发。这样既有 Arduino 的库生态又能调用 IDF 的底层 API。配置文件platformio.ini里指定framework espidf然后通过idf_component_register引入需要的组件。4.2 采样策略为什么用 I2S 而不是定时器中断ESP32 的 ADC 在 Wi-Fi 工作时会受到射频干扰读数跳动很大。我试过用定时器每 1ms 触发一次analogRead()结果功率读数在 0 到 500W 之间乱跳根本没法用。后来改用I2S 接口读 ADC问题解决了。I2S 本来是音频接口但 ESP32 的 I2S 外设可以配置成从 ADC 连续采样数据通过 DMA 直接搬到内存CPU 不用干预。这样采样率可以稳定在 10kHz 以上而且 Wi-Fi 射频对它的影响小很多。具体配置是I2S 模式设为I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN采样率设为 16000HzDMA 缓冲区设 8 个每个 256 字节。然后调用i2s_read()读取数据每次读 256 个样本做 RMS 计算。4.3 真有效值计算从采样点到功率值交流电的有效值不是峰值除以根号二那是理想正弦波。实际电网波形有畸变必须用真有效值公式Vrms sqrt( (1/N) * Σ(v[i]^2) ) Irms sqrt( (1/N) * Σ(i[i]^2) ) P (1/N) * Σ(v[i] * i[i])其中 N 是一个周期内的采样点数。50Hz 的周期是 20ms采样率 16kHz 的话N320。我实际用 400 个点覆盖 25ms确保整周期。代码里要注意ADC 读到的原始值是 0-4095需要先转成电压float voltage (raw / 4095.0) * 3.3;然后减去偏置 1.65V得到交流分量。电流通道还要除以负担电阻和 CT 变比还原成实际电流。功率因数 PF P / (Vrms * Irms)。如果 PF 小于 0.5说明负载是感性或容性比如电机或开关电源这时候电费计算要考虑无功功率但家用电表通常只计有功所以我们就按 P 算。4.4 电费计算与数据上传电费计算分两步先算累计电量再套分时电价。累计电量就是功率对时间积分energy_wh power_w * (delta_t_ms / 3600000.0);delta_t_ms 是两次采样的间隔我设 1000ms 算一次这样 energy_wh 就是瓦时。然后根据当前时间判断处于峰段、平段还是谷段乘以对应电价。我这边峰段 0.55 元/度谷段 0.35 元/度差价不小所以分时统计很有意义。数据上传我用 MQTT主题是home/energy/room1payload 是 JSON{v: 220.3, i: 1.82, p: 401.2, pf: 0.98, wh: 1234.5, cost: 0.68}本地服务器用 Mosquitto然后 Node-RED 订阅这个主题存 InfluxDBGrafana 做可视化。这套组合是 IoT 项目的标配稳定且免费。5. 校准与精度验证让读数对得起电费单5.1 两点校准法用已知负载反推系数刚焊好的板子读数肯定不准必须校准。我用的是两点校准法先接一个已知功率的负载比如 100W 白炽灯记录 ADC 读数和实际功率再换一个 1000W 的负载比如电暖器再记录一组。然后用线性回归算出增益和偏移。具体操作是在代码里定义两个校准系数cal_v和cal_i初始值设为 1.0。接 100W 灯串一个功率计看实际功率调整cal_i直到读数一致。再换 1000W再调。一般两次就能收敛。电压校准更简单用万用表测插座电压比如 223.5V然后调cal_v让显示值一致。5.2 相位校准感性负载下的误差修正如果你测的是纯电阻负载白炽灯、电热水壶相位误差可以忽略。但测电机、空调、电脑电源时电流波形滞后电压如果采样通道之间有延迟功率计算会偏小。ESP32 的两个 ADC 是轮流采样的不是严格同步所以电流和电压之间有一个固定的时间差。这个时间差对应的相位误差可以用公式修正P_corrected P_measured / cos(θ_error)θ_error 是采样延迟对应的角度。我实测下来在 16kHz 采样率下两个通道的延迟约 62.5μs对应 50Hz 的 1.125 度cos 值约 0.9998影响很小。但如果你的采样率低比如 1kHz延迟 1ms对应 18 度cos 值 0.95误差就有 5% 了。所以高采样率不仅是为了精度也是为了相位一致性。5.3 与电表对比实测误差在 2% 以内校准完后我把它和家里的智能电表做了 24 小时对比。电表读数 8.32 度我的装置读数 8.47 度误差 1.8%。这个误差主要来自 CT 的非线性小电流时误差大和 ADC 的量化噪声。对于家用监测来说这个精度完全够用毕竟你不是拿它去跟电力公司打官司而是看趋势、找耗电大户。6. 踩坑记录与常见问题速查6.1 读数跳动大、Wi-Fi 一开就乱这是最典型的问题原因有三个一是用了 ADC2 引脚Wi-Fi 工作时 ADC2 被占用二是没有用 I2S 采样CPU 中断被 Wi-Fi 打断三是电源纹波大5V 电源质量差。解决办法换到 ADC1 的 GPIO32-39改用 I2S 采样在电源输出端并一个 1000μF 电解电容和一个 0.1μF 陶瓷电容。6.2 CT 输出始终为零先检查火线是否真的穿过了 CT 的穿孔而且只穿了一根。如果穿了两根火线和零线一起磁场抵消输出为零。再检查负担电阻是否焊好用万用表测 CT 输出端在带载时是否有交流电压。6.3 电费计算不对累计值飞涨多半是delta_t_ms设错了。如果你用millis()算时间差要注意它溢出后归零。我建议用esp_timer_get_time()返回 64 位微秒数几百年都不会溢出。另外功率单位是瓦时间单位要转成小时别把毫秒当小时算。6.4 常见问题速查表现象可能原因排查方法解决措施读数跳动大ADC2被Wi-Fi占用检查引脚号换到ADC1引脚电流始终为零CT穿孔错误检查火线是否单根穿过重新穿线功率偏小相位误差对比纯电阻负载提高采样率或加相位补偿电费飞涨时间单位错误检查delta_t计算用esp_timer_get_time上电跳闸强电短路万用表测L-N电阻检查焊接排除短路数据不上传MQTT配置错误串口打印连接状态检查IP、端口、主题特别提醒调试强电部分时千万不要用手直接碰电路板。我习惯用一把绝缘的塑料镊子来调整电位器并且每次上电前都默念一遍“火线、零线、地线”。7. 后续扩展从单点监测到全屋能源管理这个项目做完后我发现它的扩展空间比想象中大。最简单的扩展是加一个SD 卡模块把数据本地存一份防止网络断了丢数据。再进一步可以加继电器模块当某个设备功率超过阈值时自动断电比如电暖器忘了关半夜自动切断。如果想做全屋监测可以部署多个 ESP32 节点每个节点测一路通过 MQTT 汇总到一个树莓派做总控。树莓派上跑 Node-RED 和 Grafana手机随时看。我目前做了三个节点空调、热水器、厨房插座一个月下来发现热水器是真正的电费杀手每天保温耗电 2 度多后来加了个定时开关电费直接降了 15%。代码和原理图我整理成了开源仓库包含 ESP-IDF 工程、校准脚本和 Grafana 仪表盘模板。如果你照着做遇到问题大概率是强电接线或 ADC 引脚选错了回头检查这两处基本都能解决。