简介:一套基于STM32F103的智能电表交流电压电流检测与远程监控方案,采用TV1005M电压互感器与TA1005M电流互感器采集交流信号,结合WiFi模块与手机APP,可实时查看电压、电流、功率、电量;当功率超过200W时自动断开继电器,也可手动控制。整个资源包共84个文件,以带详细备注的C语言源程序为主(36个.h、34个.c),同时包含Keil工程文件、hex烧录文件、PCB设计PDF、元器件清单xls以及手机端apk安装包,压缩后仅952KB,结构清晰便于查找。已有607人浏览学习,配套资料完整,器件清单可一对一无缝使用。这套设计不仅适合电子竞赛、课程设计直接参考,也可作为智能电表、物联网能耗监测产品的原型基础。通过源码和PCB文件,读者能深入理解交流采样原理、互感器接线方法、WiFi透传通信、继电器过流保护等关键技术,并快速移植到自己的项目中。
1. 互感器进 STM32 之前,先把测量链路想清楚
做智能电表最容易被低估的不是 ADC 精度,而是“电压电流怎么安全地送进单片机”。市电 220V 是致命信号,任何直接分压进 ADC 的方案在工程上都不合格。用 TV1005M 电压互感器和 TA1005M 电流互感器把强电转为弱电小信号,再经运放调理进 STM32 的 ADC,是当前最稳妥也最主流的做法。
这篇写的就是这条完整链路:从互感器输出特性、信号调理参数计算,到 STM32 的多通道采样策略、有效值算法、校准方法和常见坑。适合正在做电能计量、智能断路器、物联网电表或者实验室交流测量项目的开发者,默认你已经能跑通 STM32 的基础工程,本文重点放在模拟链路设计和采样程序上。
很多人把程序写对了但读数不对,问题几乎都出在偏置电压没算准,或者两个互感器的输出没有统一到同一个 ADC 量程内。下面先从互感器的真实输出说起。
2. TV1005M 与 TA1005M 的输出特性及信号调理电路参数计算
2.1 两种互感器的本质区别:电压型与电流型输出
TV1005M 是电压互感器,外形像一个小变压器,本质是一个微型电压隔离器,原边和副边绕组匝比接近 1:1,但原边串了大阻值电阻把市电高压转换成额定的小电流,副边并联精密电阻取电压信号。
TA1005M 是电流互感器,穿心式结构,原边是穿过互感器中心孔的那根导线,副边输出与初级电流成比例的交流电流信号,按照 1000:1 的匝比,原边 5A 时副边输出 5mA。千万不要直接把这个 5mA 电流送 ADC,必须先经过并联取样电阻转换成电压。
这两种互感器共用一个关键设计思路:次级输出的是与待测信号成线性比例的小信号,必须经过 I-V 转换或者分压匹配,把信号幅度调整到 STM32 ADC 的最佳输入范围,同时加上 1.65V 左右的直流偏置,让交流信号在 ADC 的 0~3.3V 输入范围内心摆动。
2.2 电压通道信号调理:采样电阻与偏置电路参数计算
TV1005M 的典型线性工作区是输入电压在 0~1000V 范围内保持比例输出。数据手册给出的额定输入是 2mA,原边和副边的电流相同,副边输出也是 2mA。副边并联的取样电阻决定了你能得到多大的交流电压摆幅。
以市电 220V 为例,原边需要串联一个限流电阻:
R 限流 = 220V / 0.002A = 110kΩ
工程上常用 100kΩ 和 10kΩ 两个电阻串联,既达到阻值又留出耐压余量,避免单个贴片电阻承受 220V 的峰值约 311V,可能导致击穿。两个电阻各分担一半电压应力,可靠性明显更好。
副边取样电阻选多大,取决于你给后级运放分配多少的输入摆幅。住往取 100Ω 左右,这样在 220V 输入时,副边输出有效值为 0.2V,峰值约 0.28V。这个幅度直接进 STM32 ADC 太小了,因为 ADC 参考电压是 3.3V,最小分辨率约 0.8mV,0.28V 的峰值摊到 4096 个码值上只用了约 340 个码,严重浪费分辨率。
所以电压通道要加一级放大,把 0.2Vrms 放大到 1.3Vrms 左右。放大倍数 Av = 1.3 / 0.2 = 6.5,用同相比例运放电路来搭,Rf = 5.6kΩ,Rg = 1kΩ,Av = 1 + 5.6 / 1 = 6.6,非常接近。运放供电用 3.3V 单电源,同相端需要提供一个 1.65V 的偏置,常用两个 10kΩ 电阻分压 3.3V 得到 1.65V,再经过一个 10uF 电容并联一个 0.1uF 电容做低阻抗去耦。
2.2.1 电压通道运放直流偏置必须为 ADC 量程中点
为什么偏置一定要放在 1.65V?因为 STM32 ADC 只接受正电压,交流信号有正有负,不抬高电平就会削底,导致负半周完全测量不到。把信号整体平移 1.65V 后,交流波形的峰谷在 0.3V 到 3.0V 范围内摆动,满足 ADC 输入要求。
注意一个细节:偏置电压的精度直接影响测量结果的零点偏移。如果分压电阻误差导致偏置漂到 1.62V,软件做 RMS 计算时如果仍按 1.65V 减掉偏置,就会产生一个恒定的直流分量误差。这个误差在低电压小电流时会被放大成很大的百分比误差。
更稳的做法是运放输出端加一个 1kΩ 电阻再加 100nF 电容到地,构成一个低通滤波器,截止频率约 1.6kHz,滤掉高频噪声和采样开关引入的毛刺,同时保护 ADC 引脚不过压。
2.3 电流通道信号调理:取样电阻决定量程
TA1005M 的副边额定输出电流为 2mA 对应初级 2A 穿心电流时。不对,这里要校准一下说法:TA1005M 的变比是 1000:1,副边输出电流等于原边电流除以 1000,原边 5A 时副边 5mA。如果副边取样电阻用 10Ω,则 5A 输入时取样电压为 50mVrms;用 33Ω 就是 165mVrms。
实际设计时先确定最大测量电流,再反推取样电阻。比如计划最大测 10A,副边电流 10mA,希望 ADC 输入端满幅约 1.5Vrms,那么取样电阻和放大倍数的组合可以这样计算:取样电阻取 20Ω,得到 0.2Vrms,再放大 6 倍就是 1.2Vrms。这个幅值在 12 位 ADC 下已经足够保证 0.1A 以下小电流的测量分辨率。
电流通道的运放电路与电压通道完全一致,偏置同样是 1.65V,放大倍数根据取样电阻单独调整。注意 TA1005M 的相位误差在中大电流段比较小,但在 1A 以下会明显增大,低功率因数场景下功率计算的误差会变大,这个后面在程序里可以通过软件校准修正。
电压通道: TV1005M -> 取样电阻100Ω -> 同相放大6.6倍 -> RC滤波 -> PA0(ADC1_IN0) 电流通道: TA1005M -> 取样电阻20Ω -> 同相放大6倍 -> RC滤波 -> PA1(ADC1_IN1)两个通道最好用同一路运放的同一个偏置源,这样共模抑制比更高,电源纹波对两路信号的影响方向一致,在软件里做差分计算时能抵消一部分。这个接法在模拟布局上很关键,很多自制电表在电源纹波大时读数跳动,就是因为两个通道偏置各走各的,纹波引入的方向不一致。
3. STM32 交流采样程序设计的关键:过零对齐与 RMS 算法选型
3.1 为什么不能用直流采样那套直接读 ADC 求平均
直流信号采样只需要读一次或者多次取平均,交流信号则完全不同。市电 50Hz,周期 20ms,如果只在一个随机时刻点采样一次,你读到的是正弦波上的某个瞬时值,完全不能代表有效值。即使多采几次取平均,只要采样点没有覆盖完整的整数个周期,平均结果里会残留与初相位相关的分量。
由此得到交流采样设计的第一原则:必须同步采样一个完整周期或多个完整周期的等间隔点,才能算出正确的 RMS 有效值。等间隔要求对 STM32 来说就是用定时器触发 ADC,而不是靠软件循环里ADC_GetFlagStatus轮询。
同时采样窗口必须是整周期。对 50Hz 信号,采样 20ms 就能覆盖一个整周期。如果采样点数是 N,采样频率就是 N / 20ms。常见选择是每周波采样 128 点或 256 点,对应采样频率 6.4kHz 或 12.8kHz,远低于 STM32 ADC 的最高采样率,所以 CPU 负担并不重。
3.2 软件过零检测做周期同步的问题与替代方案
有些方案通过检测交流信号的过零点来触发采样中断,实现每周波自动同步。具体做法是:比较器输出过零信号,STM32 的外部中断捕获过零时刻,然后启动定时器按固定间隔采样 N 个点。
这个思路理论可行,但工程上有两个难点:一是过零时刻容易受谐波和噪声干扰,信号在零点附近抖动超过比较器滞回窗口就会产生误触发,造成采样窗口错位,RMS 结果周期性跳变;二是过零触发后需要软件精确调整第一个采样点的相位,逻辑复杂且调试成本高。
更常用的做法是用固定频率采样,配合 1s 滑动窗口内的平均整周期对齐。但更简洁的做法是:用定时器固定频率连续采样,连续采集超过一个周期长度(例如 20ms 采 128 个点,实际上可以连续采 129 个点),取最后 128 个点计算 RMS。下一个周期重新取新的一组 128 点。由于采样时钟与市电频率存在微小偏差,每周波的采样点数会有轻微波动,但只要保证窗口长度覆盖足够的完整周期,RMS 结果的精度在 0.5% 以内是可以做到的。
3.3 RMS 有效值计算的两种实现:标准公式与逐点累加
标准交流有效值的定义是:对 u(t) 在一个周期内先平方、再求平均、最后开方。离散化到 N 个采样点:
U_rms = sqrt( (1/N) * Σ u[i]^2 )
这个公式在 STM32 上直接用浮点算,每周波 128 次平方和累加,循环次数很少,20ms 内完成计算完全没问题。关键是每个 u[i] 要先减掉直流偏置再平方,否则偏置的直流分量会被当成有效值算进去,导致结果虚高。拿上一章的电路参数来说,如果偏置是 1.65V,ADC 读到 1.65V 对应 2048 码,采样值减 2048 再按比例换算成实际电压,才是真正的交流瞬时值。
电流通道同理。但要注意互感器在大电流时会轻微饱和,导致有效值输出略偏低。校正的办法是在程序里做分段线性修正:电流在 0.5A 以下乘修正系数 1.02,0.5A 到 5A 乘 1.005,5A 以上乘 0.98。这种处理不能完全消除互感器的非线性,但对智能电表常见的 0.5 级计量要求已经足够。
// 伪代码: 128点RMS计算,忽略定时器配置细节 float adc_value, sum_sq = 0; for (i = 0; i < SAMPLES; i++) { adc_value = (float)adc_buf[i] / 4096.0f * 3.3f; // ADC码转实际电压 adc_value -= 1.65f; // 减去直流偏置 sum_sq += adc_value * adc_value; } float rms = sqrtf(sum_sq / SAMPLES);这段代码的关键在第二行减法。adc_buf[i]是 DMA 搬运到内存的原始采样值,转成电压后先减偏置再平方累加。这样求出的 rms 是纯交流有效值。如果偏置漂移了,最简单的方法是运行时定期采一个静态零点通道把偏置实时测出来,而不是用一个常量。
4. STM32 多通道 ADC 采样程序设计:定时器触发、DMA 双缓冲与任务调度
4.1 用定时器触发 ADC 实现精确等间隔采样
交流采样最忌讳用while(1)循环里软件延时来触发 ADC,因为中断响应延迟和代码执行时间抖动会直接变成采样间隔的相位噪声。正确的做法是用定时器的更新事件作为 ADC 的外部触发源。
STM32 系列里典型配置是:TIM2 的更新事件连接到 ADC 的触发输入,预分频器和自动重载寄存器把触发频率设置为 6.4kHz,即每周波 128 点。这样 ADC 的采样时刻由定时器硬件精确控制,软件不需要干预,CPU 可以在采样间隙做显示刷新、按键扫描和通讯任务。
配置时的注意点是:ADC 要工作在扫描模式并设置好通道采样时间。交流信号采样不需要太快的采样时间,把采样周期设定为 28 个 ADC 时钟周期左右即可,太短反而容易采到前一次采样保持电容上的残余电荷,导致相邻采样点之间出现通道串扰。
// ADC 和定时器初始化要点,以 STM32F103 为例 void ADC_TIM_Sample_Init(void) { TIM_TimeBaseInitTypeDef TIM_InitStructure; ADC_InitTypeDef ADC_InitStructure; // TIM2 触发频率 = 72MHz / (71+1) / (157+1) ≈ 6.4kHz TIM_InitStructure.TIM_Period = 157; // 自动重装载值 TIM_InitStructure.TIM_Prescaler = 71; // 预分频 TIM_TimeBaseInit(TIM2, &TIM_InitStructure); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 多通道扫描 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO; ADC_InitStructure.ADC_NbrOfChannel = 2; // 电压+电流两通道 ADC_Init(ADC1, &ADC_InitStructure); }定时器的 Period 和 Prescaler 计算方法很简单:72MHz 时钟先除以 72 得 1MHz,再除以 158 约等于 6329Hz,对应每周波 126.6 点,与 128 点目标略有偏差。要精确做到 6400Hz,需要预分频改大一些但保持整除关系,或者直接使用 64MHz 主频配合整除参数。对 RMS 计算来说,采样频率偏差在 1% 以内对结果影响非常小,因为算法本身对非整周期采样有平均效应,所以不用追求绝对精确。
4.2 DMA 双缓冲:避免采样与计算互相阻塞
单缓冲模式下,DMA 采样满 128 点会产生一次传输完成中断,CPU 在中断里计算 RMS,计算期间如果定时器继续触发 ADC,DMA 会覆盖正在使用的缓冲区,数据就乱了。解决方法是双缓冲机制。
双缓冲的逻辑是:开辟两个同样大小的数组,DMA 交替填充。CPU 处理 Buffer A 时,DMA 正在写 Buffer B;下一次 DMA 写满 Buffer B 后自动切回 Buffer A,同时 CPU 刚好处理完 Buffer B 的数据。两个环节乒乓交替,互不阻塞。
在 STM32F1 系列里没有硬件双缓冲寄存器,需要自己在 DMA 传输完成中断里切换缓冲区指针。F4 及以上系列有双缓冲模式寄存器,可以直接配置。以 F103 为例:
// 两个缓冲区, DMA 交替写入 volatile uint16_t adc_buf[2][128]; volatile uint8_t buf_index = 0; void DMA1_Channel1_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC1)) { DMA_ClearITPendingBit(DMA1_IT_TC1); buf_index ^= 1; // 切换缓冲区 Process_RMS((uint16_t *)adc_buf[buf_index ^ 1]); // 处理刚填满的那一半 DMA_SetCurrDataCounter(DMA1_Channel1, 128); DMA_Cmd(DMA1_Channel1, ENABLE); } }Process_RMS函数接收刚被 DMA 填满的缓冲区指针,计算电压、电流有效值。由于 DMA 需要关闭再重新打开才能重新装载传输计数器,在这个空档里定时器不会停止,所以会丢掉几微秒的采样,但 128 点的窗口偶尔少一个点对 RMS 精度影响可以忽略。
把计算放在中断里确实会增加中断服务的执行时间,但如果主循环还需要做其他事情,更好的做法是:中断里只设置一个data_ready标志位,主循环检测到标志后处理数据。不过要注意,两个周期的时间是 20ms,主循环只要在 20ms 内完成计算就不会丢数据。
4.3 多通道采样时的通道顺序与采样周期分配
ADC 扫描模式下,通道的转换顺序由ADC_RegularChannelConfig里的 Rank 参数决定。电压通道和电流通道要交替转换,避免两个通道在同一个扫描周期内相隔太远,导致电流采样时刻相对电压采样时刻有明显偏移。
假设 Rank1 是电压通道,Rank2 是电流通道,ADC 会先转换 PA0 再转换 PA1,两个通道转换完成的时间间隔约为 28 个 ADC 时钟周期,在 72MHz 下约 0.39us。这个时间差折算到 50Hz 信号上约为 0.007°,完全可以忽略。
更关键的一点是每通道的采样时间要分别配置。如果电压通道输出阻抗较高,需要更长的采样时间让采样电容充分充电;电流通道输出阻抗低则可以短一些。两种通道用同一个采样时间,如果阻抗特性差异太大,读数会有可见的幅度误差。
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_28Cycles5);通道配置函数最后那个参数就是采样周期,28Cycles5表示采样阶段占 28.5 个 ADC 时钟周期。如果前级运放的输出阻抗在 1kΩ 以下,这个采样时间足够;如果输出阻抗偏高,应改用55Cycles5甚至71Cycles5,换取更准确的采样值。判断是否够用,可以在输入一个直流电平时观察 ADC 转换结果是否稳定,如果相邻采样点之间有明显的阶梯波动,就要提高采样周期。
4.4 三相场景下的通道扩展问题
如果做三相四线电表,需要采集三路电压和三路电流,一共 6 个通道。STM32F103 的 ADC1 有 16 个外部通道,可以一次扫描 6 个通道,顺序配置成 Ua、Ia、Ub、Ib、Uc、Ic 交替排列。
但要注意两个问题。一是 ADC 扫描 6 个通道后,一路完整扫描的周期变长,在 28.5 周期采样时间和每种通道各 6 个通道的情况下,一次扫描约为 171 个 ADC 时钟周期,约 2.4us。这个时间对 50Hz 信号不是问题,但定时器每次触发只能完成一轮扫描,6 点一组,速率为定时器频率。要保证每周波每组有 128 点,触发频率依然是 6.4kHz,每组 6 个通道共需要 6.4k × 6 = 38.4k 次转换每秒,F103 的 ADC 最大约 1MHz 转换速度,完全能承受。
二是多通道时 DMA 缓冲区要相应扩大,adc_buf[6][128]或者线性数组adc_buf[768]。处理数据时注意每个通道的数据分布在一个扫描周期内的固定位置,也就是每 6 个点里第 0 号是 Ua,第 1 号是 Ia,依次类推。取第 k 个采样点时,实际数组下标是 k × 6 + channel_index。
5. 校准方法与误差修正技巧,用标准源把读数校准到 0.5% 以内
5.1 空载校零消除偏置电压漂移
在输入端不加任何信号(电压通道短接,电流通道不穿导线)的情况下,程序里读到的 RMS 值应该是零。但实际上由于偏置电路电阻误差、运放失调电压和 ADC 本身的零点偏移,计算出来的 RMS 通常不是零,而是一个几毫伏的小值。
解决方法是做软件校零:上电启动后延时 2 秒待电路稳定,连续采集 20 个周期计算平均 RMS 值,把这个值记录为零点基准,后续每次测量结果都减去这个基准。注意校零时一定要保证输入端确实没有信号,如果设备已经接在电路上,校零会把真实信号误当成零点减掉,造成严重测量误差。
5.2 用标准源做分段增益校正
增益校准的思路是:用一个已知精度的交流源输出标准电压,比如 220.0V,程序读取计算出的 RMS 原始值,得到增益系数 K = 标准值 / 测量值。把这个系数存进 Flash,运行测量时乘以 K 即可。
但一个系数不够用,因为 TV1005M 和 TA1005M 在小信号时线性度下降。所以要做分段增益校正:在满量程的 5%、20%、50%、80%、100% 五个点分别记录增益系数,存储在 Flash 里。程序运行时根据当前测量值所在的区间,用相邻两个点的系数做线性插值。
电压通道的分段校准相对简单,电流通道要特别注意相位差的影响。如果后续要做功率计算,电流和电压之间还有一个相位偏移,这个偏移来自互感器和运放的相位延迟。功率因数为 1.0 时相位误差影响不明显,但功率因数为 0.5 时,1° 的相位误差会造成约 3% 的功率误差。所以高精度智能电表一定要做相位校准。
5.3 相位误差修正:旋转矢量法
相位校准的方法是在标准源上设置一个纯阻性负载,即电压和电流同相位,功率因数为 1.0,记录此时计算出的功率值 P1。然后在相同电压电流幅值下,把负载换成纯感性或容性,功率因数为 0,功率计的示数应该接近零。不为零的部分就是相位误差导致的。
修正时需要把电压和电流的离散采样点分别做正余弦变换,用旋转矢量法把电流的采样点相对于电压做微小的时间偏移。具体做法是:对电流通道的 128 个采样点,在软件里按照偏移角度对相邻两个采样点做线性插值,等效于把电流波形沿时间轴平移。
相位校准系数用一个浮点数保存,范围通常是 -0.1° 到 +0.1°。计算功率时每周期把角度换算成采样点数,对电流采样序列做插值平移后再与电压相乘累加。这个操作在 F103 上占用约几百微秒,20ms 的周期内做一次完全可行。
5.4 用示波器验证采样时序是否符合预期
程序写完以后,先在输入端加载一个稳定的正弦信号,用示波器观察 PA0 和 PA1 的波形,确认运放输出没有削顶或者削底。然后加一个干路电阻,测量实际的电压电流数值,与标准万用表比对。
如果程序里配置了合适的调试引脚,可以在 DMA 传输完成中断里输出一个 10us 的高电平脉冲,用示波器测量脉冲间隔是否正好等于 156.25us(对应 6.4kHz),验证 ADC 触发频率是否正确。这个技巧在查采样频率配置错误时非常有效,不需要仿真器也能定位。
最后一招是用内置的 CRC 或串口打印方式输出原始采样值,在 PC 端用 Python 脚本做离线 FFT,观察频谱里的 50Hz 分量是否正常,如果频谱出现了明显的 100Hz 分量,说明偏置减法没有做干净,回去检查零点校准逻辑。这个验证方法不花成本,但能直接暴露很多隐蔽的问题。
本文还有配套的精品资源,点击获取