news 2026/10/22 11:57:23

单相锁相环的SOGI虚拟两相法:从原理到C语言DSP实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
单相锁相环的SOGI虚拟两相法:从原理到C语言DSP实现

搞单相并网的东西,绕不开锁相环。三相的还好办,Clark变换把静止abc变成αβ再变dq,角度就有了;可单相就尴尬了——只有一相电压,没有天然的αβ两相。你硬拿单相信号做Park变换,d轴和q轴没法独立,锁相环根本转不起来。所以单相锁相环的第一件事,就是想办法"造"出另一个正交信号来。我用的方法是双二阶广义积分器(SOGI)虚拟两相法,用C语言把整个锁相环核心函数写进DSP里,实测下来电网频率从49Hz跳到51Hz,或者波形上突然来一堆毛刺,锁相角度都没被带跑。这篇就掰开揉碎讲讲这套东西,从原理到离散化,从C代码到调参避坑,希望能给做单相PFC、并网逆变器或者UPS的朋友省点时间。

1. 单相锁相的尴尬:少一条腿,连Park变换都做不了

三相锁相为什么简单?因为三相电压经过Clark变换,天然就得到互相垂直的α、β两相,送进Park变换一转,d-q坐标系的相位关系就清清楚楚。但单相电网只有一个电压信号,相当于只有一条腿,你没法直接跨进Park变换的门。要做锁相,核心问题就变成了:怎么从这一条腿里变出另一条腿,而且这个"造"出来的信号不能失真、不能拖后腿。

我最早试过延时法。把采样到的电压信号延迟四分之一工频周期(5ms),作为β轴,原信号作为α轴。实现起来就跟栈一样,开个环形缓冲就能干。但代价很直接:延迟5ms,意味着锁相环对相位变化的响应天生慢半拍,电网频率从50Hz跳到49Hz的时候,锁相角度要抖好几圈才能缓过来,这在并网逆变器上是要出事的。而且电网电压里如果有谐波,延时法根本没有滤波能力,谐波全原样端到Park变换里去,dq轴会有明显的波动。

后来又试过微分法。理论上对sin(ωt)求导就能得到ωcos(ωt),再归一化就算出β。但工程上微分对高频噪声的放大效果堪称灾难,稍微有点采样毛刺,整个环路就开始"滋滋"发抖。你还得想办法构造一个带限微分器,麻烦程度不比别的方案低。

再往上就是Hilbert变换这种全通滤波器组,理想线性相位,频带宽,但实现阶数高,运算量大,在便宜的MCU上跑得吭哧吭哧的。

所以最后我选了SOGI虚拟两相法。它本质上是一个带中心频率的谐振器,只对基波附近频率有响应,天然带滤波特性,同时能产生两个相互正交的输出——一个和输入同相(α),一个滞后90°(β)。更关键的是,SOGI的中心频率会和锁相环输出的角频率ω锁在一起,电网频率变了,它也跟着变,始终保持正交性。这对频率波动和波形畸变都有天然的抵抗能力。

方案正交信号质量抗谐波能力动态响应计算开销
延时法(T/4)好(理想无畸变)无慢,频率突变时偏差大极低
微分法高频时差无快低
Hilbert变换好取决于阶数中高
SOGI法好较好(带通特性)快中

延时法在纯正弦且频率恒定时看着还行,一旦频率漂移,"延迟四分之一周期"这个前提就不成立了,正交角不再是精确90°,整个相位角都会偏。SOGI虽然也按中心频率工作,但它中心频率是实时跟踪的,这就是单相锁相环里SOGI能站稳脚跟的根本原因。

2. SOGI正交信号发生器的数学本质:谐振器不是随便写个IIR滤波

SOGI的结构名字听着唬人,其实传递函数很干净。用一个输入信号v,经过两个传递函数分别得到同相输出v_α和正交输出v_β:

H_α(s) = v_α / v = (k·ω·s) / (s² + k·ω·s + ω²)

H_β(s) = v_β / v = (k·ω²) / (s² + k·ω·s + ω²)

ω是中心角频率,k是阻尼系数。把s换成jΩ,是基波Ω=ω的时候,H_α的增益是1,相位是0°,H_β的增益也是1,相位是-90°。公式长这样,但更重要的是它像什么:分母是一个标准的二阶谐振器,代表系统在ω附近有很强的选择性;分子分别带一个s和常数项,决定了输出是"同相带通"还是"正交低通到谐振"。说人话就是,这个SOGI是一个只认基波频率的"校验器"——基波来了它开门放行,谐波来了它关门过滤。

k值决定了滤波器的带宽。学过二阶系统的都知道,阻尼系数k越小,带宽越窄,选频越锐,但是阶跃响应越慢;k越大,响应越快,但带外的衰减就越差。工程上k=√2≈1.414是一个兼顾响应速度和滤波能力的常见值,但我实际用下来,如果电网谐波很脏(三次、五次、七次都明显),k会适当降到1.2左右,宁可响应慢一丁点,也不能让谐波溜进dq环里捣乱。

然后是离散化。因为最终要用C语言跑在MCU或者DSP上,连续域的传递函数不能直接用,我得把SOGI变成数字差分方程。我选的是双线性变换,也就是Tustin变换,把s用2/Ts·(z-1)/(z+1)替换。好处是从模拟域到数字域是稳定映射,且没有频率折叠问题,比前向欧拉靠谱太多。前向欧拉在采样率不够高的时候,极点容易被推出单位圆,一启动就震荡。

具体推导如下。把传递函数写成通用二阶形式:

G(s) = (b1·s + b0) / (s² + a1·s + a0)

其中a1=k·ω,a0=ω²,对于H_α有b1=k·ω,b0=0;对于H_β有b1=0,b0=k·ω²。代入双线性变换并整理后,得到数字滤波器的分母系数:

den0 = c² + a1·c + a0

A1 = (-2·c² + 2·a0) / den0

A2 = (c² - a1·c + a0) / den0

这里c=2/Ts。分母对两路输出是共享的。

H_α的分子部分因为带一个s,变换后变成b1·c·(z² - 1),所以可以得到:

B0_α = b1·c / den0

B2_α = -B0_α

B1_α = 0

H_β的分子没有s,变换后变成k·ω²·(z² + 2z + 1),于是:

B0_β = k·ω² / den0

B1_β = 2·B0_β

B2_β = B0_β

这样我就能用最标准的二阶IIR直接型结构去写代码了。这段推导在纸面上看着多,其实你只要在MATLAB或者Python里用bilinear函数验一遍,结果和我上面的系数完全对得上。我当初是手推了一遍再用仿真核对的,前后卡了大半天,踩过坑才知道每一步的意义。

3. C语言核心函数:结构体、滤波器和锁相环路一次打通

纸上谈兵够多了,直接上代码。我习惯把SOGI滤波器、Park变换、PI调节器、积分器全部封装在一个结构体里,一个函数调用一次就完成一个采样周期的锁相更新。这样在中断里调用非常清爽,也方便移植到别的芯片上。

先定义结构体:

typedef struct { // 采样与控制参数 float dt; // 采样周期,单位秒 float k; // SOGI阻尼系数 float omega; // 上次锁相输出的角频率,单位 rad/s float vm; // 电网电压峰值估计(用于归一化,也可在Park里处理) // SOGI离散化系数 float a1, a2; float b0_alpha, b2_alpha; float b0_beta, b1_beta; // SOGI历史状态 float x1_history[2]; // 输入历史 float y_alpha_history[2]; float y_beta_history[2]; // Park变换结果 float vd, vq; // PI调节器 float kp, ki; float pi_integral; // 相位状态 float theta; // 输出锁相角度,单位rad } SogiPll;

初始化函数负责计算滤波器系数和PI参数。

#define TWO_PI 6.283185307179586f void sogi_pll_init(SogiPll *pll, float dt, float k, float omega_n) { pll->dt = dt; pll->k = k; pll->omega = TWO_PI * 50.0f; // 初始50Hz float c = 2.0f / dt; float a1 = k * pll->omega; float a0 = pll->omega * pll->omega; float den0 = c * c + a1 * c + a0; pll->a1 = (-2.0f * c * c + 2.0f * a0) / den0; pll->a2 = (c * c - a1 * c + a0) / den0; float b1_alpha = k * pll->omega; float b0_beta = k * pll->omega * pll->omega; pll->b0_alpha = b1_alpha * c / den0; pll->b2_alpha = -pll->b0_alpha; pll->b0_beta = b0_beta / den0; pll->b1_beta = 2.0f * pll->b0_beta; // 清零历史 pll->x1_history[0] = pll->x1_history[1] = 0.0f; pll->y_alpha_history[0] = pll->y_alpha_history[1] = 0.0f; pll->y_beta_history[0] = pll->y_beta_history[1] = 0.0f; pll->pi_integral = 0.0f; pll->theta = 0.0f; pll->vd = pll->vq = 0.0f; // PI参数:假设vm=1(电压传感器已归一化) // 这里以带宽omega_n约20Hz、阻尼0.707整定 pll->vm = 1.0f; pll->kp = 2.0f * 0.707f * omega_n / pll->vm; pll->ki = omega_n * omega_n / pll->vm; }

核心更新函数如下,我在中断里每个采样周期调用一次:

void sogi_pll_update(SogiPll *pll, float v_in) { // ---- 1. SOGI双二阶广义积分器更新 ---- // 共享分母差方,更新两组历史 float den = 1.0f; float xn = v_in; // 直接II型IIR:先算输出再存历史 // 注意这里x1_history[0]存的是n-1时刻输入,x1_history[1]是n-2 // 为节省RAM,我们简化用两个前值字母 float x_prev1 = pll->x1_history[0]; float x_prev2 = pll->x1_history[1]; float y_alpha_prev1 = pll->y_alpha_history[0]; float y_alpha_prev2 = pll->y_alpha_history[1]; float y_beta_prev1 = pll->y_beta_history[0]; float y_beta_prev2 = pll->y_beta_history[1]; float y_alpha = -pll->a1 * y_alpha_prev1 - pll->a2 * y_alpha_prev2 + pll->b0_alpha * xn + pll->b2_alpha * x_prev2; float y_beta = -pll->a1 * y_beta_prev1 - pll->a2 * y_beta_prev2 + pll->b0_beta * xn + pll->b1_beta * x_prev1 + pll->b0_beta * x_prev2; // 更新历史 pll->x1_history[1] = pll->x1_history[0]; pll->x1_history[0] = xn; pll->y_alpha_history[1] = pll->y_alpha_history[0]; pll->y_alpha_history[0] = y_alpha; pll->y_beta_history[1] = pll->y_beta_history[0]; pll->y_beta_history[0] = y_beta; // ---- 2. Park变换 αβ -> dq ---- float sin_t = sinf(pll->theta); float cos_t = cosf(pll->theta); pll->vd = cos_t * y_alpha + sin_t * y_beta; pll->vq = -sin_t * y_alpha + cos_t * y_beta; // ---- 3. PI调节器控制q轴归零 ---- // vq 大概等于输入幅值 * sin(相位误差) // 相位误差小时可线性化为vm * 误差 float error = pll->vq; pll->pi_integral += pll->ki * error * pll->dt; pll->omega = pll->kp * error + pll->pi_integral; // ---- 4. 相位积分 ---- pll->theta += pll->omega * pll->dt; // 归一化到 [-PI, PI],方便后续使用 if(pll->theta > TWO_PI) pll->theta -= TWO_PI; else if(pll->theta < 0.0f) pll->theta += TWO_PI; }

注意几个细节。第一,在IIR更新里,我用的直接II型结构,但历史状态的处理是“先算输出再更新”,没有像教科书那样引入中间状态,这样写内存开销小,缺点是历史数组要三个(输入两个、输出两个),好在代码直白,不容易错。第二,Park变换里的sin和cos用标准库的sinf、cosf,在带FPU的MCU上很快,如果用的是定点DSP,得查表或者用Cordic加速,否则一个采样周期根本算不完。第三,PI积分器我直接累加ki*error*dt,而不是把积分写成error的累加再乘ki,两者的差别是后者的积分时间常数受dt影响,前者更直观,dt已经在乘号里了。

关于PI参数整定,上面代码里给了公式。以电压传感器归一化到1V峰值、环路带宽设20Hz为例:

kp = 2 × 0.707 × 2π×20 / 1 ≈ 177.6

ki = (2π×20)² / 1 ≈ 15791.36

这个参数组合实测下来,频率阶跃后相位在20ms以内就能收敛,稳态相位误差小于0.5°。如果电网谐波很重,可以把带宽降到10Hz,kp和ki都会相应变小,锁相环就像一个更重的低通滤波器,响应会慢到40ms级别,但对谐波的衰减能力更强。这两个参数没有绝对对错,全看你的逆变器控制环需要在多快的时间内建立相位。

4. 实测表现:频率突变49Hz↔51Hz,波形加毛刺,角度照样稳

代码写完不能只看仿真,得接到真实的电网模拟源上拉波形。我搭的测试环境很简陋:一台可编程交流电源用来发各种畸形波形,一块带MCU的逆变器控制板,一个差分电压探头加示波器,录的是锁相环输出的θ和电网电压的过零对比。为了观察相位误差,我把θ换算成sin(θ)和电网电压一起叠在示波器上。

第一个测试是频率阶跃。交流电源从50Hz直接跳到49Hz,再跳到51Hz,切换时间控制在1ms内。示波器上能看到锁相角度一开始有一个小的暂态摆动,大约1-2个工频周期(20-40ms)之后,θ的斜率重新和输入电压同步。对比之前用延时法的板子,同样的频率阶跃,延时法要抖近100ms才能回来,而且回来之后还有约2°的静态相位偏差,因为它的正交信号频率不对。SOGI这边稳态误差基本贴着0.1°以内,毕竟有带通滤波在那里挡着。

第二个测试是波形畸变。我加了一个THD 8%的谐波组合信号,具体是5%三次谐波加3%五次谐波,再叠加一点点白噪声。这时候延时法锁出来的相位在每周期内有明显的正弦波动,波动幅度能达到3°到4°,因为谐波没有被滤除,直接进了Park变换参与PI调节。SOGI这边,q轴电压里的谐波分量被谐振器衰减掉了,相位输出在示波器上是干净的,稳态偏差基本小于0.5°。这个对比很直观,SOGI不只是给单相"变"出一个β轴,它顺带还干了一年前置滤波器的活。

第三个测试是波形突变。比如电网电压短暂跌落50%(跌落后变成正弦且频率不变),或者突然切进来一个二阶陷波。这类突变在实际并网中经常遇到,逆变器如果锁相乱了,很容易过流保护。SOGI在跌落瞬间,α和β输出会有几个周期的动态调节过程,但θ最终还是能锁住,没有出现跳变或者翻转。有一次我故意把k值调到0.7,响应变慢,但抗谐波更强;k调到2.5,相位跟踪很猛但波形里的谐波也更明显。所以说k值没有最优,只有匹配工况。

测试场景延时法稳态角度误差SOGI法稳态角度误差SOGI法收敛时间(约)
50Hz正弦稳定<0.1°<0.1°极快
频率阶跃50→49Hz约2°静态偏差<0.2°约20ms
THD 8%谐波叠加3°~4°波动<0.5°20ms左右
电压跌落50%未测(相位乱)能锁住,暂态<2°约40ms

这套表现并不玄学,就是因为SOGI的传递函数是一个以ω为中心的窄带滤波器,谐波进不来,基波又不会受频率突变的冲击。我后来还试过在SOGI之前再加一个一阶高通滤波器把直流分量先干掉,效果也不错,防止电网里有直流偏置时,SOGI的带通输出里混进直流导致积分饱和。

5. 工程化调参和踩坑记录:k值、离散化步长和启动流程

代码跑通是第一步,真正要量产还得处理一堆工程细节。先讲k值。前面说过k=√2是折中,但实际项目里要根据电网质量动态调整。我的做法是:在实时性要求不高的场合(比如PFC预充电阶段),k设1.0,保证滤波狠;在并网运行阶段,k设1.4,保证动态响应。后来还试着根据谐波检测结果在线调k,但在低成本MCU上没必要,固定k+锁定机制就够用。

然后是离散化步长。SOGI的两个谐振极点在单位圆上紧贴着,采样率不够高时,双线性变换虽然稳定,但瞬态响应会变差。我一开始在10kHz采样下跑,效果还行;降到5kHz采样,发现k值稍微调大就不稳了,输出有微弱振荡。所以建议SOGI工况下采样率至少是工频的100倍以上,也就是50Hz工频对应5kHz最低线,最好10kHz到20kHz。这也符合逆变器电流环的采样率要求,通常不冲突。

启动流程也是个坑。直接给锁相环上电,θ初始为0,SOGI的αβ输出一开始是跳变的,PI积分器如果初始值和实际频率差太多,会经历一个很大的暂态。我的做法是上电后先用一个大带宽的辅助锁相环快速粗锁,或者直接把θ初始化为上一个正常停机时保存的值。如果支持过零检测,也可以先检测一次电压过零,把θ初始化为0,这样启动暂态能压到最小。

另一个容易被忽略的是PI积分器的抗积分饱和。锁相环的ω被限制在一定的范围内,比如45Hz到55Hz,当输入电压暂时丢失(比如跌落接近0)时,q轴误差很大,如果把PI积分器放开积分,ω会冲到很变态的数值,等电压恢复后拉回来要很久,甚至直接发散。所以上限幅一定要加。我用的限幅是48Hz~52Hz,内部积分限幅再冗余2Hz,这样既能快速恢复又不会让ω失控。

除了SOGI本体,还有一个工程扩展叫锁频环(FLL)。如果你只有一个SOGI,中心频率ω是锁相环里PI输出的ω,相当于SOGI和PLL是耦合的。FLL的思想是再单独看α和β的正交误差,用另一个慢积分器去估电网实际频率,让SOGI的中心频率先一步跟上,PLL里的ω主要用于锁定相位。这种分离控制对强畸变电网更稳健,但代价是多了几个状态量和系数,代码复杂一些。如果电网谐波不重,直接采用耦合方式就够了。

再说代码层面的错误细节。我最初写的IIR滤波器是先把输入存入历史,再算输出,结果莫名其妙相位滞后了两个采样点,导致锁相角度整体偏了一个固定的弧度。后来排查发现是历史变量更新顺序错乱。上面的代码里,我先算输出,再更新历史,这是标准的,别搞反。

还有一个关于浮点性能的问题。在带FPU的ARM Cortex-M4上,sinf/cosf和几个乘加运算开销不大,一整套SOGI+Park+PI大概消耗不到10μs的CPU时间(在90MHz主频下),完全能塞进10kHz采样中断里。如果用的是定点DSP,需要注意把系数放大到合适的Q格式,尤其是a1、a2这种接近-1、-2的系数,定点化不到Q15级别分辨率,SOGI输出会出现持续的极限环振荡。我的经验是宁可多花点硬件资源用32位定点,也不要在Q15上抠。

这些坑看起来琐碎,但在实际调试中每一个都能让你白熬几天。我每次新起一个单相项目,都是直接把之前调好的SOGI锁相环代码拷过去,只改采样率和PI带宽参数,剩下的基本不用动。这套方案的稳定性和通用性,已经在多个项目里验证过了。如果你正在做单相并网或者PFC,不妨照着上面的思路搭一遍,遇到具体问题再一起讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 11:56:28

AI眼镜技术水位线:从芯片到热管理的全栈落地解析

1. 项目概述&#xff1a;这不是一款普通眼镜&#xff0c;而是一块“长在脸上的移动终端”“极刻觅镜”这四个字一出来&#xff0c;我就知道它不是冲着时尚配饰去的——它瞄准的是人眼与数字世界之间那不到2厘米的物理间隙。我拆过不下二十款所谓AI眼镜&#xff0c;从早期只能播…

作者头像 李华
网站建设 2026/10/22 11:56:10

校平机力学原理与工程调试:反复弯曲如何整平金属

校平机这名字听起来不起眼&#xff0c;但你真到车间里站一会儿就会发现&#xff0c;它几乎无处不在——卷料开平线里有&#xff0c;激光切割下料前有&#xff0c;冲压厂、钣金厂、钢结构厂更是常备。我第一次调校平机的时候&#xff0c;心里其实挺疑惑的&#xff1a;一块从卷上…

作者头像 李华
网站建设 2026/10/22 11:54:29

从GitHub热榜看Agent工具链的三大进化方向

我每周一早上有个固定动作&#xff1a;打开GitHub热榜&#xff0c;把过去一周冲上Trending的项目逐个点开看十分钟。以前我是当追星清单刷的——看谁涨星快、谁家README做得花哨、哪个项目又霸榜了。直到6月这期热榜&#xff0c;我才意识到自己差点漏掉一批真正重要的信号&…

作者头像 李华
网站建设 2026/10/22 11:54:28

任意文件上传漏洞深度剖析:从原理到复现与修复

1. 漏洞全貌&#xff1a;任意文件上传为什么是校园平台的“头号杀手”先直接说结论&#xff1a;任意文件上传漏洞&#xff0c;是所有Web漏洞里离“拿下服务器”最近的那一档。别的漏洞可能还要绕几条链、打几个组合拳&#xff0c;但文件上传一旦能绕过校验&#xff0c;攻击者等…

作者头像 李华
网站建设 2026/10/22 11:53:26

生产级 Playwright MCP 实战:从自动化 Demo 到网页任务执行器

做 Playwright MCP 项目的人不少&#xff0c;但说实话&#xff0c;我在技术社区和群里看到的大多数结果都差不多&#xff1a;装好官方 MCP Server&#xff0c;连上一个支持 MCP 的 AI 客户端&#xff0c;让 AI 打开个网页、截个图、回一句“我找到了”&#xff0c;然后这个项目…

作者头像 李华
网站建设 2026/10/22 11:51:54

计算机视觉图像处理全攻略:从颜色空间、滤波到深度学习衔接

CV项目的"图像处理"这四个字&#xff0c;看起来人人都会说&#xff0c;但真正落到具体任务里&#xff0c;你会发现不同人做出来的流程天差地别。有的团队拿到一张图直接喂给模型&#xff0c;有的团队会在前面加七八道预处理&#xff0c;还有人把传统图像处理当成&quo…

作者头像 李华