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 坏数据识别与处理
校验出坏数据了怎么办?不能直接扔掉,因为保护算法需要连续的采样序列。你得想办法把坏数据补上。
我常用的方法有三种:
- 保持法:用上一个有效采样值代替当前坏数据。简单粗暴,但误差小。
- 线性插值法:用坏数据前后两个有效值做线性插值。公式是:y = y1 + (y2 - y1) * (t - t1) / (t2 - t1)。精度比保持法高,但需要缓存后面的数据。
- 预测法:根据历史数据预测当前值。比如用前三个点的二次曲线拟合。精度最高,但计算量也最大。
我的建议:对于继电保护来说,实时性比精度更重要。我个人习惯用保持法。为什么?因为保护动作的判断依据是连续多个采样点的趋势,偶尔一个坏数据用保持法替代,对整体判断影响很小。但如果你连续出现3个以上的坏数据,那就别补了,直接闭锁保护,发告警信号。连续坏数据说明硬件可能出问题了,这时候保护动作反而不可靠。
我曾经在一个变电站调试时遇到过这种情况:CT二次回路接触不良,导致采样值每隔几个点就跳变一次。一开始我用线性插值法补数据,结果保护算法算出来的阻抗值忽大忽小,差点误动。后来改成连续3个坏数据就闭锁保护,问题就解决了。你想想看,数据都不可靠了,保护动作还有什么意义?
好了,采样值处理这块就聊到这儿。下一节我们讲采样值重采样技术,那是解决不同装置之间采样同步问题的关键。