ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

全周傅氏算法:原理、Matlab实现及继电保护应用

全周傅氏算法:原理、Matlab实现及继电保护应用 简介本资源系统讲解全周傅氏算法的数学原理与工程实现面向计算机、电子信息工程及数学等专业的本科生适用于课程设计、期末大作业及毕业设计中信号处理与电力系统谐波分析类课题。压缩包共3个文件862KB含1份理论详述PDF文档、1个Simulink仿真模型.mdl用于算法动态验证、1个结构清晰的MATLAB主程序.m代码采用参数化设计关键变量如采样频率、信号周期、谐波阶数等均可便捷修改注释覆盖推导逻辑与函数功能便于理解算法本质与调试优化。目前已有123人学习下载配套案例数据可直接运行无需额外配置显著降低初学者复现门槛助力快速掌握傅里叶级数在周期信号频谱分析中的实际应用方法。 电力系统继电保护里只要涉及到工频电气量的测量与计算全周傅氏算法就是绕不开的一个名字。不管是距离保护、差动保护还是故障录波分析提取基波分量这件事做得好不好直接决定保护装置会不会误动、拒动。最近整理了一份全周傅氏算法的理论笔记和Matlab实现代码放在一个压缩包里分享给同行这里把核心内容和代码思路完整梳理一遍。无论你是刚接触保护的在校学生还是正在做保护算法仿真的工程师这篇文章应该都能帮你省下不少翻书和调试的时间。1. 为什么距离保护非要提取基波从故障信号说起先从一个实际问题出发。输电线路发生短路故障时流过保护安装处的电流并不是一个干净的50Hz正弦波。故障瞬间会叠加衰减直流分量还会伴随大量的高次谐波如果是经过过渡电阻接地波形更是畸变得厉害。保护装置要判断故障类型、计算故障距离靠的是故障分量的工频量不把基波从这一堆杂讯里挑出来后面的阻抗计算和方向判别全都无从谈起。全周傅氏算法做的事情本质上就是一个带通滤波器它用一个完整工频周期20ms的数据窗把输入信号中与基波频率相同的分量提取出来同时理论上能完全滤除整数次谐波。这个特性让它成为微机保护中最经典的基波提取算法之一和半周傅氏算法、差分傅氏算法一起构成了保护算法的基础框架。那么问题来了为什么用傅氏算法而不是直接用低通滤波器原因在于低通滤波器虽然能滤掉高频分量但对衰减直流分量的抑制能力有限而且滤波器的过渡带会带来时间延迟。傅氏算法在频域上有着天然的选频能力它不需要迭代收敛计算量固定一个数据窗出一次结果非常适合保护装置的实时性要求。用生活化的方式理解假如你在嘈杂的菜市场里想听清远处朋友喊你的名字人耳会不自觉地调谐到朋友声音的频率范围忽略掉周围的高频噪音和低频嗡鸣。全周傅氏算法就是对电气量做同样的事——它只关心50Hz这个频率上的分量有多大、相位是多少其他频率的成分一概当作噪声丢弃。2. 从傅氏级数到离散算法全周傅氏的数学推导全周傅氏算法的理论基础是周期信号的傅里叶级数展开。任意一个周期为T的信号x(t)都可以分解成直流分量、基波分量和各次谐波分量的叠加x(t) a0/2 Σ[an·cos(nωt) bn·sin(nωt)]其中ω 2π/T是基波角频率。保护算法关心的是n1这一项也就是基波分量。如果能求出a1和b1基波的幅值和相位就全部确定了。根据傅里叶级数的正交性a1和b1可以通过在一个完整周期内对信号做积分得到a1 (2/T)·∫x(t)·cos(ωt)dtb1 (2/T)·∫x(t)·sin(ωt)dt在微机保护中信号被采样成离散序列积分变成求和。假设一个工频周期内采样N个点采样间隔Ts T/N那么离散化的计算公式变成a1 (2/N)·Σx(k)·cos(2πk/N)b1 (2/N)·Σx(k)·sin(2πk/N)这里的k从0取到N-1正好覆盖一个完整的数据窗。求出a1和b1之后基波分量的幅值和相角分别是幅值A √(a1² b1²)相角θ arctan(b1/a1)需要特别注意的是幅值A是基波分量的峰值还是有效值取决于公式里的系数。有些教材把(2/N)换成(√2/N)得到的直接就是有效值有些算法输出的是峰值需要再除以√2。这个细节在工程对接时最容易出问题后面会专门讲。2.1 关于计算方法的细究你可能已经注意到上面这个计算方法和全周傅氏这个名字之间的关联它必须用满一个完整周期的采样数据。如果只用半个周期10ms的数据来计算那就是半周傅氏算法。半周傅氏最大的优势是响应速度快故障后10ms就能出结果但它对偶次谐波的抑制能力差而且对频率偏移更敏感。全周傅氏虽然需要20ms才能出结果但滤波性能更好在强调可靠性的场合比如距离保护的后备段、变压器差动保护更受青睐。还有一个变种叫差分傅氏算法它先把相邻两个采样点做差分再套用傅氏公式。差分运算天然滤掉了纯直流分量所以差分傅氏对衰减直流分量的抑制能力比全周傅氏更强但它会放大高频噪声。三种算法各有适用场景实际装置中往往会组合使用。2.2 复数表达视角电气工程领域习惯用相量来描述正弦量。把a1和b1组合成复数形式I a1 - j·b1这个复数就是基波分量的相量表示它的模对应幅值辐角对应相位。在Matlab里可以直接用complex(a1, -b1)来构造这个相量后续的距离保护阻抗计算、功率方向判别都是基于这个相量做的。用复数来思考还有一个好处傅氏算法的线性性质一目了然——两个信号的傅氏结果相加等于各自傅氏结果之和这在分析故障分量时非常有用。3. Matlab实现一版能直接跑起来的全周傅氏代码压缩包里的Matlab代码核心就是一个函数全周傅氏算法提取基波相量。代码不长但把它写对、写稳涉及到采样率、数据窗对齐、边界处理等一堆细节。先把核心函数贴出来然后逐段解释。function [amp, phase] fullCycleFourier(signal, fs, f0) % fullCycleFourier 全周傅氏算法提取基波分量 % 输入 % signal - 采样信号序列可以是电流或电压 % fs - 采样频率Hz % f0 - 基波频率通常为50Hz % 输出 % amp - 基波幅值峰值 % phase - 基波相角弧度 N round(fs / f0); % 一个工频周期内的采样点数 n 0:N-1; % 采样点序号 % 构造正交参考序列cos和sin cosSeq cos(2*pi*f0*n/fs); sinSeq sin(2*pi*f0*n/fs); a1 0; b1 0; for k 1:N a1 a1 signal(k) * cosSeq(k); b1 b1 signal(k) * sinSeq(k); end a1 a1 * 2 / N; b1 b1 * 2 / N; % 幅值和相角 amp sqrt(a1^2 b1^2); phase atan2(b1, a1); end这个函数的核心逻辑和第二节的公式完全对应。有几个地方需要重点说明N的计算是round(fs/f0)不是直接取整。如果采样率是1000Hzf0是50HzN 20如果采样率是1200HzN 24。用round是为了适应非整数倍的情况虽然非整数倍采样下全周会有微小偏差但工程上问题不大。cosSeq和sinSeq是预先生成的参考序列不需要在每次采样时实时计算cos和sin省掉了大量三角函数运算。实际装置里这些系数是固化在程序里的常量表。atan2而不是atan。atan的返回值范围是[-π/2, π/2]无法区分相角落在哪个象限而atan2能返回[-π, π]的完整范围相角判别的准确性全靠它。3.1 滑动数据窗每个采样点都要出一次结果上面这个函数只是一次性的计算输入一个长度N的向量输出一个幅值和一个相角。但实际保护装置需要每个采样点都刷新一次计算结果也就是滑动数据窗。function [ampArray, phaseArray] slidingFullCycleFourier(signal, fs, f0) % 滑动全周傅氏对信号逐点滑动数据窗输出每个采样点的基波幅值和相角 N round(fs / f0); L length(signal); ampArray zeros(1, L-N1); phaseArray zeros(1, L-N1); for idx 1:L-N1 window signal(idx:idxN-1); [ampArray(idx), phaseArray(idx)] fullCycleFourier(window, fs, f0); end end这里有个性能问题每个数据窗都要重新计算a1和b1时间复杂度是O(L×N)。如果采样率是2400Hz数据长度是1秒那就是2400×48 ≈ 11.5万次乘加运算对现代CPU来说毫无压力但对老一代DSP芯片来说就不那么轻松了。有一种优化思路是递推算法。注意到滑动数据窗每次只移出一个旧点、移入一个新点傅氏结果可以增量更新。设当前窗口的和为S移出的点是x(0)移入的点是x(N)则S_new S_old - x(0)·ref(0) x(N)·ref(N)这里的ref是参考序列。这样每次更新只需要2次乘法复杂度从O(N)降到O(1)。但是递推算法对累积误差更敏感如果信号包含较大的直流偏置长时间运行后面临数值漂移问题。如果只是做仿真分析直接滑动窗就够用了如果要移植到嵌入式平台建议用递推版本并定期做全量重算。3.2 输出格式与数据对齐还有一个容易忽略的坑输出结果的时刻对齐。假设数据窗从第1个采样点开始计算出的幅值应该对应窗口内哪个时刻严格来说傅氏结果代表的是窗口中心时刻的电气量。窗口从索引1到索引N中心是(N1)/2。如果后续要做故障测距、行波分析这类对时间精度要求高的计算必须把时间轴对齐到窗口中心否则会引入系统性偏差。在Matlab里这个对齐体现为输出数组的索引和原始信号索引之间的偏移。比如原始信号长度L输出长度L-N1第i个输出对应原始信号的第(iN/2)个采样点。很多做仿真的人忽略了这个细节把幅值波形直接画在原始时间轴上看起来相位对了实际在故障测距时误差会放大。4. 用仿真故障数据验证算法的实际表现代码写完了总得验证一下它对不对。最直接的办法是构造一个已知各分量参数的信号让算法去提取基波再和真实值对比。我在压缩包里放了一个测试脚本生成了包含基波、3次谐波、5次谐波和衰减直流分量的故障电流信号验证结果很有代表性。% 测试信号基波50Hz幅值100A峰值叠加3次谐波30A5次谐波10A % 以及时间常数0.05s的衰减直流分量20A fs 1000; % 采样率1000Hz f0 50; % 基波频率50Hz t (0:0.4*fs-1) / fs; % 仿真时长0.4s signal 100*sin(2*pi*f0*t pi/6) ... % 基波初相角30度 30*sin(2*pi*3*f0*t) ... % 3次谐波 10*sin(2*pi*5*f0*t) ... % 5次谐波 20*exp(-t/0.05); % 衰减直流分量 [amp, phase] slidingFullCycleFourier(signal, fs, f0);这段代码生成的信号基波幅值100A初相角30度。运行全周傅氏算法后稳态段的幅值输出稳定在100左右相位输出在0.5236弧度也就是30度附近。3次、5次谐波被滤除得干干净净衰减直流分量虽然造成了一定的暂态扰动但大约两个周波之后输出就收敛到正确值。算法的收敛过程非常能说明问题第一个数据窗t0到t20ms里由于衰减直流分量还很强计算出的幅值会偏低或偏高取决于直流分量的初始相位和衰减速度。随着窗口不断滑动直流分量逐渐衰减幅值输出迅速逼近真实值。这个过程大约需要40-60ms恰好对应2-3个工频周期和理论分析的收敛时间一致。4.1 误差来源一衰减直流分量的影响衰减直流分量是全周傅氏算法的天敌。理论上纯直流分量能被完全滤除但衰减直流分量的频谱是连续谱它会泄漏到包括基波在内的所有频率分量上。泄漏量的大小取决于两个因素直流分量的初始幅值和时间常数。时间常数越小衰减越快频谱越宽对基波的干扰反而越大。实际系统中线路阻抗角决定了衰减时间常数通常在10ms到100ms之间。故障发生在电压过零点附近时衰减直流分量最大对傅氏算法的影响也最严重。缓解方法有几种一是前置差分滤波先对信号做差分再进傅氏二是增加一个衰减直流分量的补偿项用最小二乘法估算直流分量参数后从原信号中剔除三是采用变窗长策略通过调整数据窗长度来避开最恶劣的时段。工程装置中最常见的是前两种的组合单纯靠全周傅氏硬扛衰减直流是不现实的。4.2 误差来源二频率偏移问题另外一个让全周傅氏算法剧烈恶化的条件是系统频率偏移。电网正常情况下频率在50Hz附近波动但在孤网运行、大型机组跳闸等暂态过程中频率偏移可能达到±0.5Hz甚至更大。全周傅氏算法假设信号频率严格等于f0参考序列也是按f0构造的。如果实际频率偏离f0数据窗覆盖的时间不再是真正的一个周期频谱会发生泄漏计算出的基波幅值会周期性波动相角也会出现漂移。仿真测试中频率偏移0.5Hz时幅值误差可达5%以上这在保护判断中已经相当可观了。解决频率偏移的思路是跟踪频率再计算先通过测频算法得到当前实际频率再用实际频率重新计算参考序列和窗口长度。这个方案在数字式保护中已经普及但测频本身又引入了一个时间延迟需要在整个保护方案的时序设计上做权衡。4.3 仿真验证的具体结论我在测试脚本里做了几组对照实验结果整理成表格测试条件基波幅值输出基波相角输出稳态误差收敛时间纯基波信号100.00A0.5236rad0%20ms基波3/5次谐波100.00A0.5236rad0%20ms基波谐波衰减直流99.2A~100.8A0.51~0.54rad0.8%40ms基波谐波衰减直流频率偏移0.5Hz95.6A~104.6A0.48~0.57rad4.6%60ms从表格可以清晰看出整数次谐波对全周傅氏算法完全无影响衰减直流造成暂时的幅值波动但能收敛频率偏移则是诱发持续误差的主要因素。这个结论直接告诉我们算法选型和配套策略的优先级谐波问题基本不用操心重点要对付的是直流分量和频率偏移。5. 工程落地中的几个细节数据窗、直流抑制与系数表代码能在Matlab里跑通离真正在保护装置里运行还有很长一段路。工程实现中有一堆细节每一条都是前人踩坑踩出来的经验。5.1 采样率的选择全周傅氏算法要求N每周波采样点数不能太少。如果采样率太低高频分量会混叠到低频段傅氏算法的谐波抑制能力就失效了。工程上最低要求是每周波12点采样即720Hz这个数字源自奈奎斯特采样定理和6次谐波的考虑——要滤除5次以下谐波至少需要能表示10次以上的频率分量。现在主流保护装置的采样率至少是每周波24点1440Hz很多已经到了每周波48点2880Hz甚至更高。采样率提高的好处是频率分辨率和抗混叠能力更强代价是CPU负担和数据存储量增加。在做算法选型时需要先确认装置的采样率再确定参考序列的长度。不要想当然地认为每周波20点是标配——不同厂家、不同型号的装置采样率差异很大。5.2 参考序列的存储方式保护装置的采样率确定后cosSeq和sinSeq就完全确定了。工程实现时不会在每次计算时调用cos/sin函数数学库函数指令周期长且存在平台差异而是把N个cos值和N个sin值提前算好固化成常量数组放在程序里。有的装置为了节省存储空间只存1/4周期或1/2周期的系数通过象限对称关系推导出全部值——省空间但费CPU怎么选取决于具体的芯片资源。在做Matlab仿真时也不需要每次运行都现场算cos/sin。可以在脚本开始处生成系数表放到工作区里复用。特别是做批量仿真时这一小步能省掉大量重复计算。5.3 与半周傅氏的组合使用实际保护装置很少只用一个算法。比较常见的组合是启动元件用差分算法响应快故障测距用全周傅氏精度高快速距离保护用半周傅氏速度优先各段保护配置不同的算法和延时配合。所以在设计算法模块时建议把接口做成通用的输入是一段采样序列输出是基波相量算法内部怎么算不影响外部调用。我在压缩包里的代码也是按这个思路组织的fullCycleFourier、halfCycleFourier、differentialFourier三个函数接口完全一致方便对照测试和组合调用。5.4 抗混叠滤波器的位置还有一个工程细节全周傅氏算法虽然能滤除整数次谐波但它无法处理采样前的混叠问题。如果模拟信号中存在超过采样频率一半的高频分量例如开关操作产生的陡波前过电压这些分量会通过采样混叠到低频段傅氏算法对此无计可施。因此硬件上必须在采样保持电路之前设置抗混叠低通滤波器。这个滤波器的截止频率通常设为采样频率的40%左右兼顾衰减特性和相移影响。很多刚接触保护算法的人忽视了这个硬件前置条件直接在理想采样信号上验证算法结果到了实际装置上发现波形对不上怎么调参数都不行。这不是算法错了而是采样前端的问题。6. 代码压缩包内容说明与使用建议这个压缩包里除了刚才展示的核心函数还包含了完整的测试脚本、说明文档和几个典型故障场景的仿真数据。说明一下文件结构和每个文件的用途方便拿到代码后快速上手。压缩包内文件清单fullCycleFourier.m全周傅氏算法核心函数输入信号序列、采样率、基波频率输出基波幅值和相角。slidingFullCycleFourier.m滑动数据窗版本输出每个采样点对应的基波幅值序列和相角序列。halfCycleFourier.m半周傅氏算法用于和全周傅氏进行对比实验。differentialFourier.m差分傅氏算法用于观察直流抑制效果。test_fullCycleFourier.m主测试脚本生成包含各次谐波和衰减直流的故障信号调用上述函数绘图对比。generateFaultSignal.m故障信号生成函数可自定义各分量幅值、相位和时间常数。README.md代码说明和使用注意事项。建议使用顺序是先运行test_fullCycleFourier.m观察算法对纯基波信号的输出再逐项叠加谐波和直流分量体会算法在每个阶段的响应特性。多动手改一改参数比死记公式有用得多。有一点要提醒代码里的采样率、基波频率、仿真时长这些参数都不是固定的做实际项目时一定要先确认自己信号的具体参数再调整代码。比如你拿到的录波数据采样率是4000Hz那就要把fs改成4000如果是60Hz系统把f0改成60。不要直接套默认值否则结果肯定对不上。关于Matlab版本代码里只用了基本语法和内置函数不涉及任何工具箱R2016a及以上的版本应该都能直接运行。如果遇到函数名冲突或者警告优先检查是不是工作区变量被覆盖了。最后分享一点个人经验傅氏算法的理论门槛其实不高难的是把它放进整个保护方案里考虑。采样率怎么选、算法输出怎么用、和相邻算法怎么配合每一样都影响最终的保护性能。写这份代码和笔记的过程中我又翻了一遍经典教材把离散傅氏变换的推导过程重新手推了一遍才敢说把几个细节真正想明白了。建议你也试试不看教材、自己推一遍公式再对照代码看每一步的实现收获会比直接跑代码大得多。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进