RMS不是单个采样点的绝对值,而是一个周期内所有采样点平方的平均值再开方,即RMS = √[(v[0]² + v[1]² + … + v[N−1]²)/N],且N必须为严格整数个工频周期,否则结果漂移超±2%。

为什么不能直接用 sqrt(voltage_samples[i] * voltage_samples[i]) 算电压 RMS?
这是新手最常踩的坑:RMS 不是单个采样点的绝对值,而是**一个周期内所有采样点平方的平均值再开方**。智能电表通常以 1–10 kHz 采样电网电压(比如 50 Hz 正弦波,每周期至少 200 点),必须先准确捕获完整周期,再计算:RMS = sqrt( (v[0]² + v[1]² + ... + v[N-1]²) / N )
关键在 N —— 必须是**严格整数个工频周期**。若用固定点数(如 1024 点)但实际只覆盖 1.98 个周期,结果会漂移 ±2% 以上。实践中建议用过零检测动态截取周期:记录相邻正向过零点间的所有采样点,取连续 M 个完整周期(M ≥ 4)求平均 RMS,抑制谐波和噪声干扰。
怎么用 C++ 实时算功率因数而不翻车?
功率因数 = active_power / apparent_power,但分母 apparent_power = V_rms × I_rms 容易算错。问题出在相位:电压与电流采样必须严格同步(同一 ADC 通道配对或硬件触发对齐),否则相位差误差 1° 就会导致功率因数在 0.95 附近偏差超 0.01。
实操要点:
- 电压、电流使用同一采样时钟,避免用两个独立定时器
- 不做 FFT,用滑动窗口做协方差法估算相位差:计算
sum(v[i] * i[i])和sum(v[i] * i[i-1]),比值反映滞后/超前趋势 - 对 50 Hz 系统,推荐 20 ms 窗口(即 1 个周期)滚动更新,避免长窗口响应迟钝
- 当
V_rms或I_rms低于阈值(如 5% 满量程),直接置功率因数为 0.0,防止噪声主导计算
定点运算下怎么避免 RMS 计算溢出?
电表 MCU 常用 Cortex-M3/M4,ADC 输出 16-bit,电压采样值范围 ±32768。平方后达 10⁹ 量级,32-bit int 装不下 200 个点累加——sum(v[i]²) 极易溢出。
安全做法:
- 用
int64_t累加平方和(ARM GCC 下无性能惩罚) - 或提前右移缩放:采样值先
>> 4,平方后再乘 2⁸ 补偿,控制中间值在 32-bit 安全区 - 禁用浮点
sqrtf():裸机环境无 FPU 时极慢;改用查表+牛顿迭代,或直接调用 CMSIS-DSP 的arm_sqrt_q31() - 注意:缩放后 RMS 单位变化,校准系数要同步调整,否则计量不准
电表国标(DL/T 645、JJG 596)对 RMS 和功率因数的精度要求怎么落地?
JJG 596 明确要求 1 级电表的电压 RMS 误差 ≤ ±0.5%,功率因数在 0.5–1.0 区间误差 ≤ ±0.015。这意味着:
- 采样率不能只满足奈奎斯特——50 Hz 信号需 ≥ 2 kHz,但实际要用 4–8 kHz 抑制抗混叠滤波器非理想性
- ADC 参考电压温漂必须 ≤ 20 ppm/°C,否则 10°C 温变就吃掉 0.02% RMS 误差预算
- 功率因数计算中,若电流通道增益误差比电压高 0.1%,在 PF=0.5 时直接引入 0.002 偏差,必须做通道增益校准(存入 Flash 校准系数)
- 所有计算路径(包括缩放、移位、查表)必须用 Q-format 定义并全程跟踪量化误差,不能依赖“后期校准抹平”
真正难的不是算法本身,是把数学公式塞进资源受限的实时系统里,还让每个字节的误差都可控。


















