3、采样值处理:采样值滤波算法、数字低通滤波器设计、采样值有效性校验、坏数据识别与处理

采样值拿到手了,是不是就能直接用?

当然不是。我干继保这些年,见过太多因为采样值没处理好导致的误动或拒动。说白了,ADC采回来的原始数据,就像刚从河里捞上来的鱼,你得先刮鳞、去内脏、洗干净,才能下锅。这个处理过程,就是我们今天要聊的采样值处理。

3.1 采样值滤波算法

滤波,是采样值处理的第一步。为什么要滤波?因为现场干扰太多了。电磁场、谐波、开关操作产生的暂态分量,都会叠加到采样值上。你不把它滤掉,保护算法算出来的结果就是错的。

3.1.1 算术平均滤波

这是最朴素的方法。连续采N个点,加起来除以N。比如你每周波采24点,那就把连续24个点平均一下,得到一个滤波后的值。

// 算术平均滤波示例
#define N 24
uint16_t samples[N];
uint16_t filter_average(void) {
    uint32_t sum = 0;
    for (int i = 0; i < N; i++) {
        sum += samples[i];
    }
    return (uint16_t)(sum / N);
}

这个方法的好处是简单,对随机噪声抑制效果不错。但缺点也很明显——它对脉冲干扰特别敏感。你想想看,如果24个点里有一个点因为雷击跳到了满量程,平均值就会被拉高一大截。我在现场遇到过这种情况,一个雷雨天,线路没故障,保护却差点动作,查了半天,就是算术平均滤波惹的祸。

3.1.2 中值滤波

中值滤波就是专门对付脉冲干扰的。做法是:把连续N个采样值排序,取中间那个值作为输出。

// 中值滤波示例(冒泡排序取中值)
#define N 5
uint16_t filter_median(uint16_t *buf) {
    uint16_t temp[N];
    uint16_t t;
    // 复制数据
    for (int i = 0; i < N; i++) temp[i] = buf[i];
    // 冒泡排序
    for (int i = 0; i < N-1; i++) {
        for (int j = 0; j < N-1-i; j++) {
            if (temp[j] > temp[j+1]) {
                t = temp[j];
                temp[j] = temp[j+1];
                temp[j+1] = t;
            }
        }
    }
    return temp[N/2];  // 取中值
}

我的经验:中值滤波的窗口N一般取奇数,3、5、7都行。N越大,对脉冲抑制越强,但响应也越慢。我个人习惯在电流采样上用N=5,电压采样上用N=3。为什么?电流回路更容易受CT饱和影响产生尖峰,需要更强的滤波。

3.2 数字低通滤波器设计

算术平均和中值滤波都是时域方法。但有些场合,我们需要在频域上做文章。比如,你要滤除50Hz基波以上的谐波分量,只保留基波。这时候就要用数字低通滤波器。

数字低通滤波器,说白了就是一个差分方程。最常用的一阶低通滤波器,公式长这样:

y(n) = α * x(n) + (1-α) * y(n-1)

其中,α是滤波系数,范围0到1。α越小,滤波越强,但响应越慢。α越大,滤波越弱,响应越快。

α怎么选?这取决于你的截止频率和采样频率。举个例子:

采样频率 (Hz) 截止频率 (Hz) α 推荐值 应用场景
1200 100 0.25 滤除5次以上谐波
2400 150 0.15 保留基波,滤除高频噪声
4800 200 0.10 高精度测量场合

注意:数字低通滤波器会引入相位延迟。你想想看,滤波后的信号比原始信号滞后了。对于保护算法来说,相位延迟可能导致动作时间变慢。我曾经在一个距离保护项目里,因为低通滤波器相位延迟没算好,导致保护动作慢了半个周波。嗯,从那以后我设计滤波器时一定会把相位补偿考虑进去。

3.3 采样值有效性校验

滤波完了,数据就能用了吗?还不行。你得先确认这个采样值是不是有效的。什么叫有效?就是它真实反映了被测量的状态,没有因为硬件故障、通信错误等原因变成垃圾数据。

我一般做三件事:

  • 幅值范围校验:采样值不能超过ADC的满量程,也不能低于噪声底限。比如12位ADC,满量程是4095,正常信号范围应该在500-3500之间。超出这个范围,要么是过载,要么是硬件坏了。
  • 变化率校验:相邻两个采样点的差值不能太大。电力系统是惯性系统,电流电压不会突变。如果相邻两点差值超过额定值的20%,基本可以判定是坏数据。
  • 三相一致性校验:对于三相系统,A、B、C三相的采样值应该满足一定的对称关系。如果某一相突然和其他两相差异很大,那这一相大概率有问题。
// 采样值有效性校验示例
typedef struct {
    uint16_t value;
    uint8_t  valid;  // 0:无效, 1:有效
} Sample_t;

Sample_t check_sample(uint16_t raw, uint16_t prev, uint16_t rated) {
    Sample_t result;
    result.value = raw;
    result.valid = 1;
    
    // 1. 幅值范围校验
    if (raw < 100 || raw > 4000) {
        result.valid = 0;
        return result;
    }
    
    // 2. 变化率校验(额定值的20%)
    uint16_t diff = (raw > prev) ? (raw - prev) : (prev - raw);
    if (diff > (rated * 20 / 100)) {
        result.valid = 0;
    }
    
    return result;
}

3.4 坏数据识别与处理

校验出坏数据了怎么办?不能直接扔掉,因为保护算法需要连续的采样序列。你得想办法把坏数据补上。

我常用的方法有三种:

  1. 保持法:用上一个有效采样值代替当前坏数据。简单粗暴,但误差小。
  2. 线性插值法:用坏数据前后两个有效值做线性插值。公式是:y = y1 + (y2 - y1) * (t - t1) / (t2 - t1)。精度比保持法高,但需要缓存后面的数据。
  3. 预测法:根据历史数据预测当前值。比如用前三个点的二次曲线拟合。精度最高,但计算量也最大。

我的建议:对于继电保护来说,实时性比精度更重要。我个人习惯用保持法。为什么?因为保护动作的判断依据是连续多个采样点的趋势,偶尔一个坏数据用保持法替代,对整体判断影响很小。但如果你连续出现3个以上的坏数据,那就别补了,直接闭锁保护,发告警信号。连续坏数据说明硬件可能出问题了,这时候保护动作反而不可靠。

我曾经在一个变电站调试时遇到过这种情况:CT二次回路接触不良,导致采样值每隔几个点就跳变一次。一开始我用线性插值法补数据,结果保护算法算出来的阻抗值忽大忽小,差点误动。后来改成连续3个坏数据就闭锁保护,问题就解决了。你想想看,数据都不可靠了,保护动作还有什么意义?

好了,采样值处理这块就聊到这儿。下一节我们讲采样值重采样技术,那是解决不同装置之间采样同步问题的关键。