简介:这是一份基于STM32F103C8T6单片机实现交流电压有效值检测的完整工程资源,面向嵌入式开发者、电子竞赛备赛者以及需要掌握电力参数采集的工程技术人员。资源围绕交流电压测量这一典型应用,覆盖STM32F103C8T6的ADC多通道采集、连续转换模式、DMA数据传输以及均方根(RMS)算法实现等关键技术点,解决了从硬件分压/整流电路到软件滤波与数值计算的全流程设计问题。包内共201个文件,以C源文件、头文件、编译中间文件(.o/.d)、HAL库及工程配置为主,另含hex烧录文件、Keil工程文件与调试描述文件,便于直接打开工程阅读、编译和烧录验证,压缩包大小6.54MB。已有4034人学习下载。通过该工程,读者可快速获取一套可运行的交流电压测量程序框架,重点理解DMA配合ADC实现高频采样与RMS换算的软件写法,也可参考其硬件连接与安全处理思路,用于课程设计、项目移植或竞赛改造。 把220V交流电直接接到STM32F103C8T6的ADC引脚上会发生什么?大概率是芯片当场冒烟。别笑,我在新手群里见过不止一次这种问题:拿了块最小系统板就想测市电,结果不仅没测到电压还赔了一块板子。检测交流电压这个需求其实非常常见,智能家居里的过压欠压保护、充电桩的输入监控、配电箱的电压采集,都用得上。但交流电真不是拿ADC“怼”一下就能采的,这篇文章就把完整的原理、硬件电路和基于HAL库的软件程序一次讲清楚,适合正在做电压检测项目的开发者和嵌入式爱好者参考。
1. 交流电压不是拿ADC直接怼:采样前的三个现实难题
做这个项目之前,得先想明白一个问题:为什么不能把交流电压直接送到STM32F103C8T6的ADC采样引脚?答案不只是一个“会烧芯片”这么简单,这里面藏着三个绕不开的现实约束。
1.1 幅度与安全:311V的峰值不是闹着玩的
我们常说的220V交流电,指的是有效值。市电波形是50Hz的正弦波,它的峰值电压是220 × √2 ≈ 311V,正半周最高冲到+311V,负半周跌到-311V。而STM32F103C8T6的ADC输入范围是0~3.3V,超出这个范围哪怕一点点,芯片IO引脚内部的保护二极管就会导通,轻则采样值被钳位,重则直接烧掉引脚甚至整个芯片。
更重要的是安全隔离问题。220V市电是致命电压,如果板和电网之间没有隔离,一旦接线错误、绝缘破损或者地线处理不当,单片机、调试器、电脑都会带上危险电压。我见过有人用电阻分压直接采样市电,板子倒是没烧,但MCU的GND和市电零线是共地的,调试器连上去的一瞬间电脑USB口直接保护。做交流电压检测,隔离是底线,不是可选项。
1.2 双极性信号:ADC根本采不到负半周
就算你用一个精密电阻分压网络把311V降压到3.3V以内,第二个问题马上暴露出来:正弦波有负半周。ST的ADC是单极性输入,只能识别0到3.3V之间的正电压,负电压送进去要么被钳位到0,要么损坏引脚。
所以必须在硬件上做一个“电平抬升”,把以0V为中心的双极性信号,整体平移到以1.65V为中心的单极性信号。这样正半周在1.65V以上,负半周在1.65V以下,ADC才能完整采到一个周期的波形。这就是后面要讲的偏置电路的核心作用。
1.3 目标值问题:峰峰值除以根号2不靠谱
我们最终要得到的是交流电压的有效值,也就是RMS。很多新手会觉得:ADC采到峰值,然后除以√2不就行了?这在波形是完美正弦波时确实成立,但现实中的电网电压经常有谐波畸变、毛刺噪声,波形根本不是标准正弦。峰值法在波形畸变时误差会非常明显,而RMS计算是对所有采样点做平方、平均、开方,它天然地包含了波形畸变的影响,测出来的才是真有效值。
总结一下,要做一个能用的交流电压检测程序,信号链路必须是:高压交流 → 隔离降压 → 电平抬升到单极性 → ADC采样 → 软件计算RMS。前面两步靠硬件,后面两步靠程序,这篇文章两个部分都会覆盖。
2. 硬件方案:电压互感器加偏置电路的设计要点
硬件上可选方案不少,但真正适合STM32F103C8T6这种低成本项目的,其实就一两种。我这部分把方案对比和参考电路参数都列出来,你照着搭就行。
2.1 三种主流方案横向对比
交流电压采样硬件方案,市面上常见的有三种,各有各的适用场景:
| 方案 | 隔离能力 | 精度 | 成本 | 适用场景 |
|---|---|---|---|---|
| 电阻分压 | 无隔离,危险 | 高 | 极低 | 仅限低压交流测量或实验室浮地环境 |
| 电压互感器 | 完全隔离 | 中高 | 低 | 市电电压检测、过压欠压保护 |
| 霍尔传感器 | 完全隔离 | 高 | 高 | 高频交流、大动态范围测量 |
我的建议非常明确:做220V市电检测,直接选电压互感器方案。霍尔传感器精度确实高,但一个传感器几十块甚至上百块,做产品根本没必要;电阻分压虽然便宜到可以忽略,但安全风险太大,工程上不该用。ZMPT101B这类微型电压互感器十几块钱一个,隔离耐压几千伏,精度对于电压监控来说完全够用。
2.2 一个可用的参考电路及参数计算
我用的是ZMPT101B电压互感器,配合一个单运放做电平抬升。这个方案的典型连接方式是:互感器初级串两个100kΩ电阻,次级并联一个采样电阻,然后把次级差分信号送到运放做偏置加法。
以220V为例,两个100kΩ串联后总限流电阻是200kΩ,初级电流约为220V / 200kΩ ≈ 1.1mA。ZMPT101B变比是1:1,次级电流也是1.1mA。如果次级并联820Ω采样电阻,得到的电压是1.1mA × 820Ω ≈ 0.9V RMS,峰值约1.27V。叠加1.65V偏置后,波形范围在1.65 - 1.27 = 0.38V到1.65 + 1.27 = 2.92V之间,离ADC的0V和3.3V都有余量。当市电升到250V时,峰值约1.44V,最高会到3.09V,仍然不削顶。这个参数设计是经过计算留足余量的,你直接照用即可。
运放部分我用的是经典加法电路:正输入端通过10kΩ接互感器输出信号,同时通过10kΩ接1.65V偏置电压;反相输入端直接接输出构成跟随。输出就是信号 + 1.65V,偏置电平的稳定性直接决定后续零点是否漂移,所以1.65V不要从电位器取,最好用两个1%精度的10kΩ电阻从3.3V分压后,再接一个运放做缓冲输出,这样带载能力也够。
2.3 模块化方案和使用注意事项
如果你不想自己搭运放电路,市面上有现成的ZMPT101B交流电压检测模块,模块上集成了互感器、运放和可调电位器,输出端直接就是叠加好偏置的交流信号,接到STM32F103C8T6的PA0引脚就能采。但用模块有一个坑:很多模块默认是按5V供电、2.5V偏置设计的,输出信号范围是0~5V。如果直接接在3.3V的ADC引脚上,正半周超过3.3V的部分会被削掉,采样值满量程,测出来数据完全错误。
用这种模块接STM32F103C8T6,必须改一下:把模块供电改到3.3V,让偏置电压相应降到1.65V左右;或者把模块输出串一个电阻再接ADC,同时用两个二极管做钳位保护。选模块时优先选标称支持3.3V供电的版本,能省掉很多麻烦。另外无论自己搭还是用模块,建议在ADC引脚对地并联一个0.1μF电容,做高频滤波,能明显降低采样抖动。
3. 软件链路:从ADC原始值到有效值的完整实现
硬件把交流信号处理成1.65V为中心的0~3.3V单极性信号之后,剩下的就是软件的事了。整个程序的核心链路是:定时器触发ADC采样 → DMA把数据搬到内存 → 主循环或中断里算RMS和频率。我是用STM32CubeMX配合HAL库实现的,代码结构清晰,也方便移植。
3.1 CubeMX里这几项配置不能错
先在CubeMX里创建STM32F103C8T6工程,打开ADC1、DMA1、TIM2和USART1,关键配置如下:
- ADC1:开启通道IN0(对应PA0引脚),分辨率12位,扫描模式关闭,连续转换关闭,外部触发源选择
Timer 2 Trigger Out event,采样时间选55.5周期,开启DMA Continuous Request。 - DMA1:选择ADC1,方向PeripheralToMemory,模式Circular循环模式,数据宽度Half Word,内存地址自增。
- TIM2:预分频
72-1,计数周期100-1,触发输出TRGO选择Update Event。这样72MHz主频经过72×100分频后,恰好产生10kHz的更新事件,也就是每100μs触发一次ADC采样。 - USART1:115200波特率,用于把计算结果打印出来。
这里最容易出错的是忘记开启DMA的Continuous Request,或者ADC外部触发源选错。配置好之后,CubeMX生成的代码里会主动调用HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, SAMPLE_SIZE),这个启动函数必须放在main函数里初始化之后,不能漏。
3.2 采样率与缓冲区长度怎么定
10kHz采样率对应的采样间隔是100μs,一个50Hz工频周期20ms,正好采200个点。我建议缓冲区设置成400个点,也就是40ms,覆盖连续两个完整周期。为什么要两个周期?因为RMS计算理想情况下是对整数个周期求平均,如果只采一个周期,万一缓冲区起始位置不在过零点,计算结果的波动会比较大;采两个周期再做平均,误差明显减小。
采样率的确定也要讲一下道理。理论上根据奈奎斯特定理,采样率必须大于信号最高频率的两倍,但实际工程上建议至少10倍以上。市电里常见的谐波到5次(250Hz)已经很显著了,10kHz采样率能覆盖到100次谐波都不成问题,而且每周期200个采样点对RMS计算的精度来说也足够了。除非你要做高次谐波分析,否则10kHz是性价比最高的选择。
3.3 RMS计算与直流偏置消除
软件计算RMS之前,必须先把直流偏置消掉,这是最容易算错的地方。ADC采到的原始值包含了1.65V偏置对应的数字量(3.3V对应4096,1.65V对应约2048),如果直接对原始采样值做平方平均,这个直流分量会被当成交流有效值的一部分算进去,导致结果大得离谱。
正确做法是分两步:
void ProcessACVoltage(uint16_t *buf, uint16_t len) { uint32_t raw_sum = 0; float offset, sum_sq = 0.0f, rms_lsb, rms_volt; uint16_t i; /* 第一步:求平均值,得到直流偏置对应的ADC值 */ for (i = 0; i < len; i++) { raw_sum += buf[i]; } offset = (float)raw_sum / len; /* 第二步:每个采样点减去直流偏置后,平方累加 */ for (i = 0; i < len; i++) { float v = (float)buf[i] - offset; sum_sq += v * v; } /* 开方求RMS,单位是LSB */ rms_lsb = sqrtf(sum_sq / len); /* LSB转换为电压,再乘以互感器变比校准系数 */ rms_volt = rms_lsb * 3.3f / 4096.0f; voltage_rms = rms_volt * g_ratio; }这里的g_ratio就是整个采样链路的电压变比校准系数,它包含了互感器匝比、采样电阻、运放增益等所有硬件环节的总比例。比如前面那个参考电路里,220V对应互感器次级0.9V RMS,ADC端也是0.9V RMS(运放增益为1),那么g_ratio = 220 / 0.9 ≈ 244.4。具体数值最好通过实际校准来确定,下一章细说。
3.4 过零检测与频率测量
除了有效值,很多项目还需要测量交流电频率,判断电网是否异常。基于已有采样数据,用软件过零检测就能实现,不需要额外硬件。思路是:先把采样值和直流偏置比较,找从负变正的上升穿越点;为了防抖,加一个滞回区间,只有跨过偏置值一定范围(比如正负20个LSB)才认为是一次有效过零。
#define HYST_LSB 20 uint16_t DetectFrequency(uint16_t *buf, uint16_t len, float offset) { uint16_t i; uint16_t rising_crossing = 0; float prev = (float)buf[0] - offset; for (i = 1; i < len; i++) { float v = (float)buf[i] - offset; if (prev < -HYST_LSB && v > HYST_LSB) { rising_crossing++; } prev = v; } /* 采样总时长 = len / 采样率,每个工频周期有1个上升沿过零 */ float elapsed = (float)len / SAMPLE_RATE_HZ; return (uint16_t)(rising_crossing / elapsed); }注意这种测频方法有个适用限制:缓冲区越长,测频分辨率越高,但实时性越差。400个点、40ms的缓冲区只能得到约±0.5Hz的精度,用来判断50Hz电网是否正常足够了——电网频率漂移超过0.5Hz本身就是重大异常。如果你要求高精度频率,比如做电能表那种±0.01Hz级别的测量,就得改用定时器输入捕获,在硬件的上升沿记录时间戳,原理上仍然是过零检测,只是时间基准从软件循环变成了硬件定时器。
4. 校准流程与踩坑记录:实测数据和教训
程序写完了不代表能直接交付,校准这一步跳不过去。我这部分把自己实测时遇到的现象和解决办法都列出来,有的是硬件问题,有的是软件问题,照着排查能省很多时间。
4.1 单点校准:两步让测量值逼近万用表
校准分成“零点校准”和“增益校准”两步。零点校准是把互感器输入端短路,ADC采到的平均值理论上就是偏置电压对应的数字量。如果你发现短路时平均值偏离2048很多,说明1.65V偏置电源精度不够或者运放有失调,这时候不要硬调软件,先检查硬件分压电阻和运放电路。
增益校准是用一个已知电压源,比如隔离变压器输出的可调交流电,同时用万用表读取真实有效值,跟程序算出来的值做对比,然后调整g_ratio。假设万用表测到220.0V,程序在当前系数下算出218.5V,那么修正后的系数就是g_ratio_new = g_ratio × 220.0 / 218.5。一般单点校准就够用了,因为电压互感器在100V到250V范围内的线性度都不错;如果你要求更高,可以在110V、180V、220V、250V各采一组,拟合一条直线,用斜率和截距做两点校正。
4.2 我踩过的四个坑,你可能也会踩
| 现象 | 根因 | 解决办法 |
|---|---|---|
| ADC值恒定不动或全为0 | DMA未开启循环模式,或定时器触发配置失败 | 检查CubeMX里DMA模式是Circular,确认ADC外部触发源是Timer2 |
| 计算出的电压值明显偏大 | 直接对含偏置的原始值做RMS | 必须先求平均值并减掉,再平方平均 |
| RMS值每隔一段时间跳变一次 | 采样缓冲区非整周期,起始点不在过零点 | 缓冲区长度设为20ms整数倍,建议400点/40ms |
| 数值和万用表差3%~5% | 互感器输出幅度偏差或模块电位器没调准 | 用已知电压重校g_ratio,模块版先调电位器 |
第一个坑在CubeMX配置阶段就会遇到,现象是ADC中断和DMA回调都不触发。我当时查了很久,最后发现是ADC的Continuous Request没有使能,导致DMA只搬运一轮就停了。第二个坑是很多人容易忽略的:拿了原始采样值直接套RMS公式,测出来数值高得离谱,像220V测出600V,就是因为没减直流偏置。
第三个坑藏得比较深,需要强调一下。假如你采样率是10kHz,缓冲区只有100个点,对应10ms,也就是半个工频周期。这样算出来的“RMS”在波形有偶次谐波或直流分量时会周期性跳变,因为积分区间不是完整周期。解决方法就是让采样窗口覆盖整数个周波,这也解释了为什么我建议400点而不是300点——200点是1个周期,但400点是2个周期,平均效果更好。
第四个坑主要是模块方案特有的。ZMPT101B模块上的蓝白电位器可以调输出幅度,出厂位置不一定适合3.3V系统。我第一次用这个模块时没调电位器,输出饱和,ADC读数满量程4096,拿万用表一测模块输出发现波形顶部削平了。用螺丝刀微调电位器,让220V输入时的输出峰值不超过2.9V,波形才恢复正常。
4.3 实测数据参考
这是我用上面整套方案在一台隔离变压器上测到的数据,万用表是Fluke 15B+,程序跑在STM32F103C8T6上,供你评估这个方案的精度水平:
| 万用表读数(V) | 程序计算值(V) | 误差(%) |
|---|---|---|
| 90.2 | 90.8 | +0.67 |
| 120.5 | 121.2 | +0.58 |
| 150.3 | 150.9 | +0.40 |
| 180.1 | 180.5 | +0.22 |
| 220.0 | 220.6 | +0.27 |
| 245.3 | 246.1 | +0.33 |
误差在正负0.7%以内,对于电压监控类的应用完全够用。这个结果的前提是采样电路参数准确、偏置电源稳定、校准系数调到了合适值。如果你测出来的误差在1%以下就不要再折腾了,再往高精度走就要上精密参考源和更高位数的ADC,成本和复杂度完全不同。
最后再分享一个小经验:做了几次这个项目之后,我习惯在程序里把voltage_rms加一个一阶低通滤波,filtered = filtered * 0.9 + current * 0.1,这样串口输出和显示屏上的数值就不会在个位上有轻微跳动,观感好很多。计算量几乎可以忽略,但对用户体验的提升非常明显。如果你要做的设备需要显示电压、判断过压欠压,这个细节值得直接抄走。
本文还有配套的精品资源,点击获取