You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无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个周期),平滑效果合理。

总结

  1. 修正滑动窗口实现(用循环缓冲区的固定窗口,或明确使用EWMA并接受其特性)。
  2. 调整K值,确保ADC域RMS正确映射到原始电压RMS。
  3. 样本量至少覆盖一个完整周期,优先选择整数个周期的样本数,平衡精度与响应速度。

内容的提问来源于stack exchange,提问作者NStorm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 15:27:40