资讯详情

单相并网锁相环:双二阶广义积分器虚拟两相法C语言实现

📅 2026/10/22 11:37:23 | 华诺云谱 👁 阅读
单相并网锁相环:双二阶广义积分器虚拟两相法C语言实现
我们直接聊实现。单相电网锁相环有个老生常谈的痛点——没有天然的正交分量电压过零检测又扛不住谐波和频率跳变。我给一套单相并网逆变器做同步时用的就是题目里说的这个方案双二阶广义积分器虚拟两相法配合C语言实现的锁相环核心函数。说白了就是给单相电压硬生生造出一个虚拟的β轴信号然后照搬三相dq锁相那套成熟玩法。实测下来电网频率从49Hz跳到51Hz能在一个周波内重新锁定波形畸变率超过10%时相位输出依然干净这套代码帮我扛过了好几轮并网认证测试。我知道电气专业的同学早就精通SOGI那一套理论但真正动手用C写过一版、并且调试过关的人并不多。文档里的传递函数和代码里的差分方程之间隔着离散化方法、系数换算、环路带宽取舍、启动冲击处理这些实打实的坑。这篇文章不讲虚的把原理、代码、参数计算、调试验证串成一条线你照着撸一遍就能得到一份能跑的锁相环核心函数。1. 为什么非得用双二阶广义积分器造虚拟正交信号1.1 单相锁相和三相锁相的差距到底在哪三相系统锁相太舒服了。abc三相静止坐标一转Clarke就得到αβ两相静止坐标再转Park就得到dq旋转坐标d轴分量就是相位误差丢给PI调节器闭环一套标准的矢量锁相链路。整个过程没有任何观测器没有状态估计数学上干干净净。单相系统就尴尬了。你手上只有一个标量电压信号根本没有办法直接做Park变换。常见的做法是延迟90度构造虚拟正交信号也就是常说的T/4延时法。这个办法胜在简单但有两个硬伤第一延时的准确度依赖电网频率电网频率一旦偏离50Hz相位误差就出来了第二谐波会跟着一起延时谐波幅值完全不衰减后面PI调节器得额外扛谐波干扰动态响应和稳态精度两头都难受。还有一类做法是直接用微分器一阶差分近似微分。微分器天生放大高频噪声电网里的开关噪声、谐波毛刺会被放大锁出来的相位毛得没法看。双二阶广义积分器(DSOGI)方案把这两个问题同时解决掉它本身就是一个谐振型的带通滤波器只对基波频率敏感谐波进不来同时它天然输出两个相互正交的分量一个是滤波过后的原信号(α轴)另一个是滤波过后的滞后90度信号(β轴)。不需要你手动算延时频率自适应能力强谐波抑制能力也是结构自带的。1.2 DSOGI和普通SOGI的区别单SOGI只能构造一组正交信号DSOGI不过是把两个SOGI拼接起来。但这里有个关键区别值得说清楚两个SOGI不是简单地各管各的它们的谐振频率由一个公共的频率反馈环节统一控制这样无论电网频率怎么跳变两组正交信号都能同步跟踪。为什么单相系统要用DSOGI而不是单个SOGI因为DSOGI天然可以做正负序分离尤其在单相系统里电网故障时经常伴随直流偏置和负序分量。双二阶的结构允许你把正序、负序分量拆开锁相环拿正序分量去做同步就算电网波形畸变得乱七八糟锁出来的相位依然是正序基波的相位。这个特性在并网认证测试里非常值钱。我记得有次做低压穿越测试电网电压跌到20%还叠加了相位跳变普通锁相环当时就失锁了换了DSOGI这套结构虽然动态过程有点过冲但始终没有丢锁。2. 核心细节拆解SOGI正交信号发生器到底怎么工作2.1 传递函数与频域特性SOGI的s域结构由两个传递函数描述。输入信号为v经过谐振环节后产生两个输出一个是与输入同相位的带通输出v_alpha另一个是滞后90度的积分输出v_beta。带通输出传递函数为H_α(s) k·ω·s / (s² k·ω·s ω²)积分输出传递函数为H_β(s) k·ω² / (s² k·ω·s ω²)这里k是阻尼系数ω是谐振角频率。从频域看H_α是一个典型带通滤波器中心频率就是ωH_β是一个低通性质的积分输出在ω处相位精确滞后90度。k的取值直接决定滤波器带宽。k越大带宽越宽动态响应越快但对谐波的抑制能力越差k越小选择性越好但动态响应变慢。工程上最常用的取值是根号2也就是1.414。这个值是在临界阻尼附近兼顾了响应速度和滤波效果的典型工程折衷。这里有个容易忽略的细节H_β的分母和H_α完全相同所以两者的幅频特性在远离谐振频率的地方都会衰减但衰减速率不同。H_α是带通高低频都衰减H_β是低通性质高频衰减但直流分量增益不为零。这就意味着如果输入信号里混有直流偏置β轴输出会有一个直流误差。实际电网波形在故障瞬间经常带有直流偏置后面要专门处理这个问题。2.2 双线性变换离散化既然用C语言实现s域的连续传递函数必须离散化。这里用的方法是双线性变换也叫Tustin变换。代换关系是s (2/Ts) · (z - 1) / (z 1)其中Ts是采样周期。双线性变换的好处是稳定的模拟滤波器离散化后依然是稳定的数字滤波器不会出现频率混叠。代价是频率轴被压缩了数字域频率和模拟域频率之间存在非线性关系不过对于50Hz基波、采样频率10kHz这种比例畸变可以忽略。把代换关系代入H_α(s)经过整理可以得到直接II型结构的差分方程系数。以H_α为例最终系数如下b0 2·k·ω·Ts / (4 2·k·ω·Ts ω²·Ts²) b1 0 b2 -b0 a1 (2·ω²·Ts² - 8) / (4 2·k·ω·Ts ω²·Ts²) a2 (4 - 2·k·ω·Ts ω²·Ts²) / (4 2·k·ω·Ts ω²·Ts²)H_β的系数略有不同b0 k·ω²·Ts² / (4 2·k·ω·Ts ω²·Ts²) b1 2·b0 b2 b0a1、a2和H_α一致因为分母相同。这些系数是运行前预先算好的但ω会随频率反馈实时变化所以严格意义上系数要在ω更新时重新计算。如果主控芯片算力紧张可以做个折衷把整个系数计算控制在环路更新频率下执行也就是几十微秒算一次乘法加法现代MCU完全扛得住。2.3 正交信号发生器的代码骨架SOGI的C代码核心就是一个二阶IIR滤波器外加输出变量。下面的结构体定义了一个正交信号发生器实例typedef struct { float k; /* 阻尼系数通常取1.414 */ float omega; /* 谐振角频率 2*pi*f */ float Ts; /* 采样周期 */ /* 离散化系数 */ float b0_alpha, b1_alpha, b2_alpha; float b0_beta, b1_beta, b2_beta; float a1, a2; /* 滤波器历史状态 */ float x1, x2; /* 输出 */ float v_alpha; float v_beta; } sogi_osg_t;核心更新函数里就是标准IIR直接II型结构的写法void sogi_osg_update(sogi_osg_t *sogi, float vin) { float v0 vin; /* 差分方程 */ float y_alpha sogi-b0_alpha * v0 sogi-b1_alpha * sogi-x1 sogi-b2_alpha * sogi-x2 - sogi-a1 * sogi-v_alpha_prev - sogi-a2 * sogi-v_alpha_prev2; /* 更新状态变量 */ sogi-x2 sogi-x1; sogi-x1 v0; /* 存储历史输出 */ sogi-v_alpha_prev2 sogi-v_alpha_prev; sogi-v_alpha_prev y_alpha; sogi-v_alpha y_alpha; sogi-v_beta /* 类似方式计算 */; }这里有个细节要提醒如果按教材上的标准直接II型写必须区分输入历史状态和输出历史状态不能混用。很多初学者在这里翻车把x1、x2既用来存输入历史又用来存输出历史算出来的波形直接发散。2.4 频率自适应与系数重算SOGI的谐振频率必须跟着电网频率走否则就是固定50Hz的窄带滤波器电网频率一偏输出幅值和相位全错。实现频率自适应的方式是把PLL环路估算出的角频率ω反馈给SOGI每次更新时用它重新计算滤波器系数。void sogi_osg_update_coeffs(sogi_osg_t *sogi) { float denominator 4.0f 2.0f * sogi-k * sogi-omega * sogi-Ts sogi-omega * sogi-omega * sogi-Ts * sogi-Ts; sogi-b0_alpha 2.0f * sogi-k * sogi-omega * sogi-Ts / denominator; sogi-b2_alpha -sogi-b0_alpha; sogi-b0_beta sogi-k * sogi-omega * sogi-omega * sogi-Ts * sogi-Ts / denominator; sogi-b1_beta 2.0f * sogi-b0_beta; sogi-a1 (2.0f * sogi-omega * sogi-omega * sogi-Ts * sogi-Ts - 8.0f) / denominator; sogi-a2 (4.0f - 2.0f * sogi-k * sogi-omega * sogi-Ts sogi-omega * sogi-omega * sogi-Ts * sogi-Ts) / denominator; }系数重算函数不需要每个采样周期都执行。因为频率变化是相对缓慢的过程比如电网频率一分钟内最多变化几赫兹根本不需要在每个50微秒的采样周期内更新。实际操作时我是每1ms更新一次系数既保证了频率跟踪的动态性能又不增加太多CPU负担。不过这里有个排序问题要注意系数更新和信号滤波更新在同一个中断里执行时顺序不能反过来。必须先更新系数再用新系数做滤波。否则这一拍滤波器用的还是上一拍的老系数频率跟踪滞后一拍动态响应会劣化。3. 锁相环核心函数实现从SOGI输出到相位锁定3.1 完整环路结构有了v_alpha和v_beta这组正交信号后面就是标准的三相同步旋转坐标系锁相环。SOGI输出的αβ分量做Park变换得到d轴分量v_d和q轴分量v_q。当锁相环锁定时q轴分量为零d轴分量等于输入电压幅值。PI控制器以q轴为误差输入输出频率校正量频率积分得到相位角θ。整体链路是采样电压 → SOGI生成αβ → Park变换 → q轴误差 → PI调节 → 频率ω → 相位积分θ → 反馈给SOGI和控制系统这个环路和三相锁相环几乎一样唯一的区别就是αβ来源从Clarke变换变成了SOGI输出。3.2 Park变换与误差提取Park变换的公式很简单但工程写法有讲究float vd v_alpha * cosf(theta) v_beta * sinf(theta); float vq -v_alpha * sinf(theta) v_beta * cosf(theta);实际实现时如果MCU没有硬件浮点单元cosf和sinf的开销会非常大。工程师常用的优化方法是用查表法替代实时三角函数计算。查表步长可以做到0.1度以内精度完全够用查表时间只是三角函数运算的一个零头。更进阶的做法是直接用相位累加器输出正弦值和余弦值用正交振荡器替代三角函数计算。不过这会让代码的可读性下降不少我一般只在算力极度受限的平台上才这么做。3.3 PI控制器参数设计PI控制器是锁相环的灵魂。q轴误差经过PI调节后输出是角频率的校正量。PI参数的整定方法很多工程上最实用的是按带宽法定参数。把锁相环的闭环传递函数近似成一个二阶系统PI的参数可以由期望的带宽和阻尼比反推。设PI传递函数为C(s) Kp Ki/s锁相环开环增益中包含一个积分环节和SOGI的延时。工程简化时把SOGI在基波附近的响应近似为1那么闭环自然角频率和阻尼比与PI参数关系近似为Kp 2·ζ·ωn Ki ωn²其中ωn是闭环自然角频率ζ是阻尼比。对于50Hz电网锁相工程上ωn一般取20到40弧度每秒ζ取0.707。这时Kp大约在28到56之间Ki在400到1600之间。别照抄这个范围具体取多少要看你的采样频率和SOGI带宽。我的经验是把ωn取到30得到的动态响应大约在80ms左右收敛对于多数并网逆变器足够。如果你做的是动态响应要求极高的场合比如铁路牵引或军工电源ωn可以取到60以上但这时SOGI带宽也得跟着加大否则相位裕度不够环路容易振荡。3.4 频率积分与角度生成PI输出的频率ω经过积分就得到相位角θ。积分在离散域就是累加theta omega * Ts; if (theta 2.0f * PI) { theta - 2.0f * PI; }这里有个经典问题角度取模后如果直接用取模后的θ去做Park变换会有一个跳变但没关系因为sin和cos是周期函数跳变不会影响结果。真正需要小心的是theta的类型一定要用float或double千万别用int否则角度分辨率不够锁出的相位会有明显的抖动。还有一个细节频率ω的初始值一定要设置在50Hz对应的角频率附近不要从0开始让PI自己拉。否则每次上电要花几百毫秒去牵引频率严重的话甚至锁不到基波上直接锁到某个谐波上去。这类失锁问题后面还会详细讲。4. 完整工程实现与参数计算全过程4.1 模块化封装实际工程中我不会把SOGI和PLL写成一坨大函数而是拆成三层最底层是SOGI滤波器单元中间层是正交信号发生器和Park变换最上层是PI控制器和相位累加器。这样每个模块能独立测试出问题时定位快。模块头文件可以这样设计typedef struct { sogi_osg_t sogi_alpha; sogi_osg_t sogi_beta; float v_alpha; float v_beta; /* 正序分量 */ float v_alpha_pos; float v_beta_pos; } dsogi_osg_t; typedef struct { float vd; float vq; float theta; float omega; float omega_pll; float theta_pll; } pll_2nd_order_t;在DSOGI里两个SOGI分别处理α轴和β轴的输入信号。理论上单相系统只需要一个SOGI生成正交信号但如果你要提取正序分量就得上双SOGI交叉反馈结构。这个问题要展开说清楚因为很多方案图里画的DSOGI是交叉耦合的而单独一个SOGI构造的正交信号并不严格对应正序分量。4.2 单SOGI与DSOGI的结构选择我在标题里直接写了双二阶广义积分器但必须诚实地说明单相锁相其实只需要单SOGI就够。那为什么工程上很多人坚持用DSOGI答案在于不平衡和畸变工况。DSOGI的交叉反馈结构能实现对正序和负序分量的解耦提取。把SOGI输出的αβ分量经过正负序分离计算得到正序αβ分量和负序αβ分量。锁相环只需要正序分量负序分量天然被剔除。这个功能在电网电压不平衡时特别有价值。单相系统的电压不平衡本质上是幅值不对称会引入了很大的2次谐波分量普通单SOGI的锁相输出会出现明显的2倍频波动。用DSOGI提取正序后锁相输出的波动几乎可以消除。4.3 完整参数计算示例假设采样频率为10kHz电网额定频率50Hz。按照前面的公式手动计算一遍所有系数有助于加深理解。输入参数如下Ts 0.0001sf 50Hzω 2π×50 ≈ 314.159 rad/sk 1.414计算分母D 4 2×1.414×314.159×0.0001 314.159²×0.0001² 4 0.0888 0.00987 4.09867H_α的b0b0_alpha 2×1.414×314.159×0.0001 / 4.09867 0.0888 / 4.09867 ≈ 0.02167b2_alpha -0.02167a1 (2×314.159²×0.0001² - 8) / 4.09867 (0.01974 - 8) / 4.09867 ≈ -1.94765a2 (4 - 2×1.414×314.159×0.0001 0.00987) / 4.09867 (4 - 0.0888 0.00987) / 4.09867 ≈ 0.95579H_β的系数b0_beta 1.414×314.159²×0.0001² / 4.09867 1.414×0.00987 / 4.09867 ≈ 0.00341b1_beta 0.00682b2_beta 0.00341这几个数可以直接填进代码里。但请注意这是50Hz固定频率下的值实际运行中ω由PLL实时更新系数要跟着重算。如果追求极致性能每个采样周期重算系数也不是不行只是算力开销会大一些。4.4 上电启动与状态初始化滤波器初始状态必须清零PI积分器初始值设为0频率初始值设为2π×50。这里有个容易忽视的细节如果电网采样有直流偏置SOGI输出的β通道会在启动瞬间产生一个较大的瞬态过冲。解决的办法是在软件里加一个直流偏置消除环节简单做法是先对采样信号做一阶高通滤波截止频率设在5到10Hz把50Hz分量滤掉的同时不损伤基波。我的初始化代码如下void pll_init(pll_2nd_order_t *pll) { pll-theta 0.0f; pll-omega 2.0f * PI * 50.0f; pll-omega_pll 0.0f; pll-theta_pll 0.0f; /* PI积分器 */ pll-integral 0.0f; }启动时如果电网初相角和theta初始值相差太大锁相环会有一个短暂但是可能幅度不小的过冲。如果想优化启动性能可以在第一次过零检测时把theta的初始值直接设置为过零对应的相位值这样锁相环几乎是零冲击启动。不过这个优化不是必须的多数应用场景下几十毫秒的启动过程完全可以接受。5. 现场实录扛住频率波动和波形突变的调试过程5.1 频率阶跃测试我在实验平台上用可编程交流源做了几组测试。第一组是频率阶跃电网频率从49Hz瞬间跳到51Hz。对于锁相环来说这是比较苛刻的工况相当于频率直接跳变2Hz。我用的是单SOGI方案PI带宽ωn设到30。实测波形显示SOGI的αβ输出在频率跳变瞬间有一个明显的幅值扰动但一个周波之内就恢复稳定。PLL估计的频率从49Hz爬到51Hz花了大约50ms超调量约0.3Hz相位角度输出在这个过程中没有出现跳变和失锁整个切换过程平滑完成。这个测试暴露了一个问题如果PI带宽设置过高频率估计会产生明显超调甚至出现频率振荡。我把ωn从30调到60重新测试频率超调量上升到0.8Hz出现了接近1Hz的振荡尾巴。这个结果很好的验证了带宽折衷的必要性——并不是反应越快越好。5.2 波形突变与谐波畸变测试第二组测试是模拟电压跌落和波形畸变。我让交流源在某个相位点突然把电压幅值跌到30%同时叠加5%的5次谐波和3%的7次谐波。SOGI的带通特性在这里发挥了大作用。输入波形畸变得很厉害但SOGI输出的αβ波形依然很干净谐波成分被大幅衰减。锁相环输出的相位在跌落的瞬间有一个约10度的瞬态偏差但经过约20ms就恢复到原有精度。这个表现远优于纯过零检测方案——过零检测在谐波严重的场合根本没法看。如果只做单SOGI而不做DSOGI正序提取这个测试中相位输出会叠加一个100Hz的纹波峰峰值大约3度。对多数并网应用来说3度还能接受但对高精度同步或者故障录波来说必须上DSOGI做正序提取把纹波压到0.5度以内。5.3 调试中遇到的一个典型问题调试后期遇到一个怪问题系统运行一段时间后锁相环偶尔会突然失锁频率估计值跳到75Hz左右不再回来。排查了很长时间最终定位到问题是PI积分器饱和。当电网频率发生较大偏移时PI积分器输出持续累加一旦超过浮点数的安全范围积分器进入饱和状态恢复起来非常慢。后来给积分器加上了抗饱和限幅把输出限制在±10Hz范围内失锁问题彻底消失。这个坑值得单独记录PLL里的PI积分器一定要加限幅很多人做仿真时不会遇到这个情况但实际C代码在长期运行后浮点累加的稳定性问题一定会暴露。抗饱和限幅代码很简单pll-integral ki * vq * Ts; if (pll-integral LIMIT_MAX) { pll-integral LIMIT_MAX; } else if (pll-integral LIMIT_MIN) { pll-integral LIMIT_MIN; }限幅范围我设为±10Hz对应的角频率这个范围足够覆盖电网极端频率漂移场景又不会让积分器在故障时过度累积。6. 常见问题与排查技巧快查表6.1 现象与对策问题现象可能原因处理办法锁相输出相位有明显抖动PI带宽过高或SOGI的k值过大降低ωn到20k保持1.414启动时锁到谐波上初始频率设置不对或滤波带宽太窄初始ω设为2π×50适当增大k频率跳变时失锁积分器饱和或PI参数过激加积分限幅降低KP稳态相位误差偏大采样直流偏置或SOGI系数未实时更新加高通滤波确保系数随ω重算β轴输出幅值不等于α轴k值偏离1.414或离散化系数有误校准k值检查系数推导高频噪声明显k值偏大导致带宽过宽k降为1.2左右验证动态响应6.2 快速排查清单如果锁相环表现不正常按以下顺序排查会省很多时间第一步检查离散化系数。用固定50Hz频率代入计算如果实际输出的αβ波形幅值不一致或者有直流偏置系数计算基本有问题。第二步检查采样中断频率。SOGI和PLL代码必须跑在同一个定时中断里中断频率抖动过大会导致离散化模型失配稳态时αβ波形会出现毛刺。第三步用固定频率开环测试SOGI。断开PLL反馈把ω固定为2π×50输入一个标准正弦波检查SOGI输出幅值和相位是否准确。这一步能把SOGI的滤波性能和PLL环路性能分开排查。6.3 调试工具推荐手头有示波器就直接看αβ波形两个波形相位差90度、幅值一致说明SOGI工作正常。没有示波器的话用DAC输出内部变量把v_alpha、v_beta、theta分别送出来看。实际调PLL时最有价值的观察量是vq也就是Park变换后的q轴分量。vq的幅值直接反映锁相误差的瞬态大小vq稳定在零附近就说明锁住了。我在调试过程中会临时加一个调试模式把SOGI系数、PI输出、估计频率周期性地存到RAM里通过串口导出到上位机绘图。这个方法比单纯看波形更直观能看到内部变量的细微变化定位问题能快一个数量级。7. 后续还可以怎么扩展这套代码目前只覆盖了基本锁相功能。实际工程需求往往更多。比如并网逆变器需要的电流内环d轴电压前馈、电压跌落时的故障检测、反孤岛检测的频率变化率判据这些都可以基于这套SOGIPLL框架扩展不需要推翻重来。另一个值得考虑的方向是谐波提取。SOGI本身是窄带滤波器如果把谐振频率设置为特定谐波频率就能当单独的谐波提取器用。比如把ω设为250HzSOGI输出的就是5次谐波的正交分量。多组SOGI并联就能实现电网谐波分析这在电能质量分析仪里是很常规的做法。我个人在实际操作中的体会是这套方案的调试重心根本不是理论本身而是离散化实现细节和参数折衷。把k、ωn、Ts三者关系想透这套代码在绝大多数单相并网场景里都能稳定扛住。你先照这篇文章把基础版跑通再去应对谐波和不平衡的进阶需求路会顺很多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑