无FPU资源受限MCU中工频正弦波整数域RMS计算问询
先明确你的硬件映射关系:ADC读数01023对应原始市电的-368V+368V(260VRMS对应峰峰值736V),所以每个ADC单位对应的原始电压为:V_actual = (sample - 512) * 736 / 1023
其中512是ADC中点,对应原始电压0V。
一、现有rms_filter()函数的问题
你的核心思路(用方差公式计算AC分量的RMS:RMS_ac = sqrt( (sum(x²)/N) - (sum(x)/N)² ))是正确的,但实现上有两个关键问题:
1. 滑动窗口实现错误
你用sum_squares -= sum_squares / SAMPLES和sum -= sum / SAMPLES更新总和,这不是固定长度为SAMPLES的滑动窗口求和,而是指数加权移动平均(EWMA)。这种方式会给旧数据赋予(SAMPLES-1)/SAMPLES的权重,新数据赋予1/SAMPLES的权重,并非严格保留最近的SAMPLES个样本。
如果目标是固定窗口的RMS计算,正确的滑动窗口需要维护循环缓冲区,每次移除最早样本并加入新样本:
#define SAMPLES 1024 static uint16_t sample_buf[SAMPLES]; static uint32_t sum = 0; static uint32_t sum_squares = 0; static uint8_t buf_idx = 0; uint16_t rms_filter(uint16_t sample) { // 移除最旧样本 sum -= sample_buf[buf_idx]; sum_squares -= (uint32_t)sample_buf[buf_idx] * sample_buf[buf_idx]; // 添加新样本 sample_buf[buf_idx] = sample; sum += sample; sum_squares += (uint32_t)sample * sample; // 更新缓冲区索引 buf_idx = (buf_idx + 1) % SAMPLES; // 计算方差的平方根(AC的RMS) uint32_t avg_sq = sum_squares / SAMPLES; uint32_t sq_avg = (sum / SAMPLES) * (sum / SAMPLES); uint32_t var = avg_sq - sq_avg; if (var == 0) return 0; // 牛顿迭代求整数平方根 uint16_t rms = var >> 1; // 初始值 while (1) { uint16_t new_rms = (rms + var / rms) / 2; if (new_rms >= rms) break; // 收敛终止 rms = new_rms; } return rms; }
如果MCU RAM紧张,EWMA方式也可以用,但要明确它的特性:响应速度慢于固定滑动窗口,但RAM占用更小。
2. 初始值的过渡问题
你初始化sum_squares = SAMPLES * INITIAL * INITIAL和sum = SAMPLES * INITIAL,相当于假设前SAMPLES个样本都是512(对应0V),这在启动阶段会有一段过渡时间,直到缓冲区被真实样本填满——这个是可接受的,但用固定滑动窗口时,最好把缓冲区初始值全部设为512,或者启动阶段跳过前SAMPLES个样本的计算。
二、整数域还原原始RMS值的正确步骤
你最终的vrms计算存在映射错误,需要调整:
ADC域的AC RMS值(即rms变量)对应的原始电压RMS,推导如下:
AC分量的ADC范围是-512~+511(中点512),对应原始电压峰值368V,所以AC的RMS(原始)= rms_adc * 368 / (512 * sqrt(2))。
用整数运算优化,把系数转换为可移位的整数:sqrt(2)≈1.4142,所以368/(512*1.4142)≈0.509,乘以2^16=65536得到约33358,因此修正K值后,计算代码应为:
#define K 33358 uint16_t vrms = (uint32_t)(rms * K) >> 16;
这样得到的vrms就是整数形式的原始市电RMS值。
三、样本量SAMPLES与采样率、工频的关联
要得到有效的RMS值,样本量需要满足以下条件:
1. 最小样本量N
- 至少覆盖一个完整市电周期:50Hz市电周期为20ms,若采样率为X sps,最小样本数
N_min = ceil(X * 0.02)。比如采样率10k sps时,N_min=200;采样率5k sps时,N_min=100。 - 优先选择整数个周期的样本数:AC分量在整数个周期内的总和趋近于0,这样
sum(x_i)/N会更接近DC偏移512,减少RMS计算的误差。
2. 样本量选择原则
- 追求精度:选择2~10个周期的样本数,平滑采样噪声和量化误差。
- 追求响应速度:选择1~2个周期的样本数,精度会略有下降。
- 若用EWMA方式(你的原始函数):
SAMPLES参数对应时间常数的倒数,需确保时间常数远大于市电周期(20ms),否则RMS值波动大。比如采样率10k sps时,SAMPLES=1024对应的时间常数为0.1024秒(约5个周期),平滑效果合理。
总结
- 修正滑动窗口实现(用循环缓冲区的固定窗口,或明确使用EWMA并接受其特性)。
- 调整
K值,确保ADC域RMS正确映射到原始电压RMS。 - 样本量至少覆盖一个完整周期,优先选择整数个周期的样本数,平衡精度与响应速度。
内容的提问来源于stack exchange,提问作者NStorm

