☰
STM32并网同步控制:从过零检测到软件锁相环的完整落地指南
2026/10/20 7:28:33 网站建设 项目流程

第一次把逆变器的输出接到电网之前,我盯着示波器上两个波形看了很久。一个是电网电压互感器引下来的正弦波,一个是我板子上SPWM滤波之后的输出,频闪看起来都在50Hz附近,幅值也调得差不多了,但我心里清楚,直接合闸大概率要出问题。因为做的是STM32并网同步控制,相位没有锁住,一切都是白搭。

那次的教训挺深刻,后来我把“并网同步”这件事从头到尾梳理了一遍,从过零检测到软件锁相环,从捕获中断到SPWM相位映射,一步步在STM32上落地。这篇就把整个链路掰开讲清楚:电网同步到底在同步什么、过零检测为什么不够用、软件PLL怎么和硬件配合、代码怎么组织、实测又会踩到什么坑。想做并网逆变器、UPS、或者任何需要和电网“对齐相位”的电源类项目,这份内容可以直接拿来当参考。

1. 为什么要“同步”才算并网:那笔冲击电流的账

很多刚接触并网的人以为,逆变器输出电压做到和电网一样的有效值,频率调到50Hz,就可以合闸了。这个想法很危险。交流系统里“一样”不是指有效值一样,而是指任意时刻的瞬时电压要尽可能一致。也就是说,电压波形不但要频率相同、幅值相同,相位也必须相同。

1.1 幅值、频率、相位三个条件差一个都不行

假设电网电压是Va = 311sin(ωt),逆变器输出电压是Vb = 311sin(ωt+θ)。合闸瞬间,两个电源被硬接在一起,开关回路里流过的是(Va - Vb)/ Z,Z是线路、开关、变压器漏感加起来的回路阻抗。如果相位差θ是10度,峰值电压差大约等于2311sin(5°)≈54V;如果相位差拉到90度,峰值电压差接近440V。这两个电压源的压差直接跨在很小的回路阻抗上,冲击电流有多大,不用我算你也知道。

幅值差同样不能忽略。相位一致但幅值差10V,相当于两个电压源之间串了一个微弱压差,同样会形成环流。所以并网前,输出侧的电压幅值、频率、相位都要往电网靠拢,三者缺一不可。硬件上,输出端一般会加预充回路或者软启动电路,先把母线电容和输出滤波电容充到接近电网电压;频率和相位的对齐,就要靠我们今天说的同步控制来完成。

1.2 频率偏差不是静态问题,是持续积累的问题

有人会说,差0.1Hz能怎么样,肉眼都看不出来。但0.1Hz的频率偏差,每个周期会让相位多跑0.72度(360*0.1/50),10个周期就是7.2度,相位差会一直累积下去。只要合闸瞬间不是恰好落在过零点附近,总会在某个时刻撞上一个不小的相位差。

所以并网同步的本质,是让逆变器的输出相位“跟着电网走”,而不是“差不多就行”。跟着走意味着每个周期都要测量电网频率,每个周期都要修正自己的相位,直到两者锁住。这也正是过零检测和软件锁相环要解决的核心问题。频率测量本身不算难,难的是相位持续跟踪和抗干扰。

2. 过零检测:能测频率,但撑不起严格并网

最早的方案当然是过零检测。电网电压每周期有两次过零点,检测到过零沿,MCU就算出周期,进而算出频率和相位起点。这个方案实现简单,在很多产品里也确实够用,但它有很明显的天花板。

2.1 硬件回路怎么搭,迟滞往哪加

常规做法是电阻分压把220V降下来,经过比较器整形成方波,再隔离进MCU的外部中断引脚。比较器可以选普通的比较器芯片,阈值设置在0V附近;为了抗干扰,必须加正反馈迟滞,否则过零附近电网噪声稍微抖一下,输出就会反复翻转,MCU一次过零能误触发好几次。

迟滞也不能太大。比如比较器迟滞设置成±5V(折算到初级),那实际触发点并不是电压等于0的点,而是电压穿越-5V或者+5V的时刻,会带来一个固定的相位偏移。这个偏移可以通过软件校准掉,但每次硬件参数变化都要重新标定,挺麻烦。比较器之后建议加一级数字隔离或者高速光耦,把强电侧和MCU侧彻底隔开,别让一次雷击浪涌顺着引脚打进单片机。

MCU侧我习惯用带有输入捕获的定时器,而不是普通外部中断加软件计时。输入捕获能记录精确到定时器时钟周期的边沿时刻,比外部中断里读SysTick或者定时器计数值靠谱得多,也省CPU。

2.2 误差主要来自哪里:过零抖动比计数器误差大得多

如果用84MHz定时器做输入捕获,电网50Hz对应一个周期计数值大约是1.68M,计数误差只有±1,对应的频率分辨率非常理想。但实测下来频率测量不会那么干净,误差大头全在过零抖动:电网谐波、整流负载、比较器迟滞、光耦传输延迟,都会让捕获到的边沿时刻偏离真实过零点。严重的时候,过零时刻抖动个几百微秒甚至一两毫秒都正常。

1毫秒在50Hz里是什么概念?一个周期20ms,1ms就是18度相位误差。带着18度的误差去合闸,冲击电流已经很难看了。所以过零检测适合做“粗略同步”和“频率测量”,不适合直接在工业现场那种谐波复杂的环境里当严格并网的相位依据。

2.3 它适合干什么,不适合干什么

过零检测的定位是给系统一个绝对相位参考,同时提供一个廉价的频率测量通道。配合软件锁相环之后,过零信号的作用是校正累积误差,而不是直接决定输出相位。开个表格看得更清楚:

项目过零检测软件锁相环
相位精度受硬件抖动影响大依靠连续累加,精度由采样率决定
抗谐波能力差,过零毛刺多好,误差经过滤波和PI收敛
频率跟踪每个周期更新一次每个采样点都在微调
实现复杂度低中
典型定位粗同步、频率测量、参考基准精细相位锁定、并网核心

可以看到,过零检测不能省,但只靠它也不行。真正让相位稳定锁住的,是软件锁相环。

3. 软件锁相环:把相位从“粗略对齐”做到“稳定锁定”

软件锁相环的思路是用一个相位累加器模拟自己的“虚拟正弦波相位”,每个采样周期累加一个固定步长,步长由电网频率决定。同时用过零信号作为绝对参考点,不断比较虚拟相位和真实电网相位的偏差,用PI调节器修正步长。这样既不依赖单次过零的精度,又能持续跟踪频率变化。

3.1 相位累加器与过零参考怎么配合

我用一个32位变量phase_acc表示相位,2^32对应360度。这样相位累加天然具备回绕能力,加减误差也不用担心溢出。假设ADC每个工频周期采样256点,采样频率就是50*256=12.8kHz,那每个采样点相位应该增加2^32/256=16777216。如果测得电网频率是50.2Hz,相位步长就需要相应上浮,否则虚拟相位就会慢慢跑偏。

过零信号在这里扮演“校表”的角色。每检测到一次过零,理论上电网相位是0度,对应phase_acc应该是0。如果实际phase_acc是+500万,说明虚拟相位走快了;如果是-300万,说明走慢了。这个差值就是相位误差,喂给PI调节器,让它去微调phase_step。

3.2 相位误差的PI修正:跟车不是急刹车

刚开始我做这个PLL的时候,想过一个偷懒做法:过零来了,直接把phase_acc清零,相位不就对齐了?实测发现这个思路不对。直接清零相当于在每个周期过零点把相位硬掰回0,但两个过零之间相位仍然可能漂移,而且过零本身有抖动,清零策略等于把抖动直接引入相位输出,PWM相位会一颤一颤的。

正确做法像开车跟车:前车刹车灯亮了,你不该一脚刹车踩死,而是轻收油门、观察车距变化,让车距慢慢恢复正常。也就是说,相位误差要通过PI调节器换算成频率修正量,叠加到50Hz基础频率上。误差大就多修正一点,误差小就少修正一点,误差被逐步收敛而不是被生硬打断。比例项负责快速响应,积分项负责消除稳态误差,积分项要做限幅,防止长时间失锁时积分饱和,等电网恢复后又震荡半天。

具体计算里还有一个坑,就是相位误差要处理回绕。phase_acc和期望值0的差值可能接近2^31,如果不把误差映射到-180度到+180度范围内,PI调节器会被一个大数吓到,输出瞬间饱和。所以每次取误差,要判断它是否超过2^31,超过就减去2^32,确保误差永远在一个周期内。

3.3 单相并网的两种PLL实现路线:查表法与SOGI法

单相系统没有天然的两相正交信号,要用锁相环一般走两条路。

一条是查表法,就是我上面说的相位累加器加过零修正,相位直接作为正弦表索引生成SPWM。优点是直观、运算量小,适合在STM32F103这种资源一般的芯片上跑,缺点是电压畸变严重时,过零参考本身带着误差,锁相精度会受影响。

另一条是SOGI法,用一个二阶广义积分器构造出和电网电压正交的虚拟信号,然后做dq变换,在同步旋转坐标系里锁相。这种方法抗谐波能力强,是工业变频器和并网逆变器里非常常见的方案,但计算量明显更大,每个采样周期要做几次浮点乘加,在Cortex-M3上跑到十几kHz的采样率会有点吃力,换成带FPU的M4或者M7芯片才合适。

做产品选型时,我的建议是:验证原理、做样机,用查表法就够了;如果目标是复杂电网环境下的产品,直接上SOGI-PLL,别在方案上省事。

4. STM32侧的外设分工与同步采样链路搭建

软件锁相环再漂亮,最后也要落到STM32的外设资源上。这里最忌讳的是“一个定时器干所有事”。我见过有人把PWM、输入捕获、软件计时全塞进一个定时器,最后中断优先级和资源共享把自己坑惨了。合理分工是第一步。

4.1 哪些外设各干什么是先决条件

外设职责说明
TIM1/HRTIM生成SPWM中心对齐模式,互补输出加死区,适合全桥逆变
TIM2触发ADC采样TRGO事件同步ADC注入通道,时机固定
ADC1+DMA采集电网电压/输出电压由TIM2触发,DMA搬运,不占CPU
TIM5输入捕获电网过零沿高精度记录边沿时间,计算电网频率
EXTI过零边沿中断可复用比较器输出,或者由捕获事件统一处理
GPIO继电器/接触器控制并网合闸输出

芯片选型上,F103系列能跑通整个方案,但要注意ADC采样率做不高,建议用G431这类带内置比较器、多路高级定时器的型号。G系列的内置比较器可以直接配合DAC阈值做电网过零检测,省掉外部比较器芯片,硬件精简很多。当然,如果手里只有F103,也可以做,只是模拟部分的坑要多花时间填。

4.2 将PWM中心对齐、ADC同步触发、DMA搬运串成一条链

这里有个细节值得单独说:ADC采样必须和PWM同步,不能想采就采。

全桥逆变输出经过滤波之后,电压相对干净,但PWM开关纹波还是会对采样造成干扰。如果在PWM边沿附近触发ADC,采到的值会带着很大的开关噪声;如果在中心对齐的计数零点触发,正好是开关纹波的谷底,采样值最稳定。所以用TIM1做主PWM,把它的TRGO事件配置成更新事件,再触发TIM2产生ADC启动信号,ADC采样完成之后DMA自动把数据搬到内存,整个过程CPU只在中途做一次PLL运算,效率非常高。

这个链路一旦搭好,后面调PLL的时候你会感谢这个决定——采样点固定了,相位计算的一致性才能保证。否则每个周期采样时刻漂移,PLL算出来的相位误差里混进了采样延迟误差,怎么调PI都不干净。

5. 核心代码落地:从过零捕获到SPWM相位映射

代码部分我按实际项目里的组织方式拆成三块:频率测量、相位累加和SPWM映射、过零误差修正与并网判定。只保留核心逻辑,工程化封装略去。

5.1 定时器输入捕获测量电网频率

用TIM5的通道1做输入捕获,捕获上升沿。两个上升沿之间的时间就是电网周期。

// TIM5_CH1 输入捕获初始化(HAL库) static void zero_cross_tim5_init(void) { TIM_IC_InitTypeDef sConfigIC = {0}; sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0x02; // 轻微数字滤波 HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1); }

捕获回调里连续取两次捕获值,相减得到一个周期对应的计数值。用无符号减法,不需要手动处理回绕,C语言自然会按模运算处理:

static uint32_t cap_old = 0; static float grid_freq_hz = 50.0f; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim != &htim5) return; uint32_t cap_new = HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1); uint32_t diff = cap_new - cap_old; // 自动处理32位回绕 cap_old = cap_new; if (diff > 0u) { grid_freq_hz = (float)TIM5_CLK_HZ / (float)diff; pll_set_freq(grid_freq_hz); } }

要注意输入捕获的计数器频率需要特别配置好,分频系数别乱加。比如定时器挂在84MHz上,如果预分频器设置成84,计数值会缩小84倍,频率分辨率变差,过零抖动的比例反而更大。我一般设置预分频为1,让计数分辨率达到最大。

5.2 相位累加器与正弦表查表的实现

相位累加器用32位无符号变量,2^32对应360度。假如ADC固定每工频周期采样256点,每个采样点相位步长为2^32/256=16777216,乘以频率相对值即可。

#define PHASE_2PI (1u << 32) #define SIN_TABLE_N 256u static uint32_t phase_acc = 0; static float phase_step = 0.0f; // 根据电网频率更新每采样点的相位步长 void pll_set_freq(float freq_hz) { phase_step = freq_hz * (float)PHASE_2PI / ADC_SAMPLE_HZ; } // 在ADC采样完成中断里更新相位并查表生成PWM比较值 const uint16_t sine_table[SIN_TABLE_N] = { /* 预生成:值域0~1024,sin表达式 */ }; void on_adc_sample_complete(void) { phase_acc += (uint32_t)phase_step; uint8_t idx = (uint8_t)(phase_acc >> 24); // 高8位正好对应256点表 uint16_t duty_x = ((uint32_t)sine_table[idx] * (uint32_t)modulation) >> 10u; uint16_t ocr = (uint16_t)((duty_x * (uint32_t)PWM_ARR) >> 10u); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, ocr); }

sine_table可以按单极性调制的形式生成,把0~1024映射到一个周期内的半波。实际项目里我一般把正弦表生成函数放上位机里算好,直接以const数组拷进工程,运行时不做三角函数运算。modulation是调制度,0到1024之间可调。这样修改调制度只需要改一个变量,不必动PWM。

如果觉得256点查表相位分辨率不够(每点1.4度),可以把采样点数提高到512,同时把表换成512点,用phase_acc的高9位做索引。实测下来,逆变并网对相位分辨率要求不算变态,256点足够,但如果你后面要做高次谐波补偿或者有源滤波,建议直接512点起步。

5.3 过零误差修正与并网允许判断

相位累加器负责“大步走”,过零信号负责“纠偏”。每次过零到来,读取phase_acc,期望值接近0,差值即相位误差,映射到±180度范围后交给PI修正:

void zero_cross_isr(void) { int32_t err = (int32_t)phase_acc; if (err > (int32_t)(PHASE_2PI / 2)) err -= (int32_t)PHASE_2PI; else if (err < (int32_t)(-(int32_t)PHASE_2PI / 2)) err += (int32_t)PHASE_2PI; // PI修正:比例项直接调整步长,积分项累积频率误差并限幅 pll_pi_update((float)err); // 并网允许判断:频率和相位误差同时满足条件 static uint8_t sync_count = 0; if (freq_within_range() && abs(pll_err_norm()) < 0.001f) { if (++sync_count > 20) { HAL_GPIO_WritePin(RELAY_PORT, RELAY_PIN, GPIO_PIN_SET); sync_count = 0; } } }

PI调节器的输出是频率修正量,加在基础50Hz上再重新计算phase_step。积分项需要限幅,否则长时间频率偏差较大时,积分会累积到很大,等相位锁住之后容易产生过冲。我习惯把积分项限幅到±0.5Hz,既能应对电网频率波动,又不会让系统震荡。

并网允许条件一定要有连续满足的计数,不能让单次相位误差过小就立刻合闸。过零抖动哪怕被滤波,单次读取仍有偶然性,连续20个周期满足条件,基本说明输出相位和电网相位确实稳定锁定了,这时候再给继电器信号才稳妥。

6. 实测阶段才暴露的坑,逐个说清楚

代码写完只是开始,真正让人头疼的是上电实测。这些坑我在调样机时基本都踩过,按频率从高到低给你过一遍。

6.1 过零毛刺:把一次过零变成三次过零

电网带整流性负载的时候,电压波形在过零点附近经常出现畸变。比较器逻辑稍不干净,一次过零会触发出三个沿,频率测量结果变成一堆乱数,PLL跟着乱跳。我的处理是三层:硬件上加施密特迟滞,MCU输入捕获的ICFilter数字滤波开到合适档位,软件里再做相邻沿间隔合理性判断——两个沿间隔小于最小周期的一半,直接丢弃,当作毛刺。三层下来,过零计数基本干净了。

6.2 固定相位延迟:光耦和比较器的“迟到”误差

光耦、比较器、数字隔离器全都有传输延迟。这些延迟看着只有几微秒到几十微秒,但在50Hz系统里,50微秒对应0.9度相位误差,多个器件串起来误差不小。而且这个延迟是固定的,不会自己消失。解决方法是加一个相位补偿参数:先在示波器上对比电网电压波形和PWM输出波形,测出实际滞后角度,然后在相位累加器里人为加一个偏置,让输出提前对应角度。这个参数一次标定,之后长时间稳定。

6.3 谐波、频率突变与相位跳变的处理策略

谐波对过零检测的影响前面说过,对PLL的影响则主要体现为相位误差里混入噪声。PI调节器带宽不能太高,否则谐波分量会被放大进相位步长,PWM频率跟着抖;带宽也不能太低,否则电网频率突变时PLL反应太慢。我常用的办法是把过零测量到的频率先做滑动平均,利用PLL的积分项平滑修正,别直接用最新值。这样谐波引起的相位误差会被平均掉,而真实的频率偏移又不会被挡在外面。

相位跳变则是另一类问题。电网大负载投切瞬间,电压相位可能会跳几度甚至十几度。如果PLL还是按小误差慢慢收敛,可能要好几个周期才能拉回来,中间这段时间逆变器输出和电网相位差偏大,存在安全隐患。我的处理策略是:把相位误差设置一个阈值,比如超过30度就认为发生了相位跳变,此时不再继续生成SPWM,而是进入重新同步状态,等PLL误差收敛、连续N个周期满足条件后再次允许并网。宁可多等两个周期,也不冒险在一个大相位差下合闸。

6.4 打样调试建议

第一次上电千万不要直接挂220V强电并网。我当时的做法是用一台可调交流源,先从低压比如30V开始,模拟电网电压,把PLL跑稳了,再逐步升压到220V并网。低压调试阶段可以把过零检测、频率测量、PLL锁定、继电器合闸全流程跑通,而且安全得多。

示波器上主要看三路:电网电压、逆变器输出、继电器驱动信号。当输出波形和电网波形完全重合,继电器驱动信号在重合稳定后拉高,说明同步链路是对的。然后故意把电网频率调偏0.2Hz,观察PLL能不能在几个周期内追回来,这个测试能快速验证PI参数是否合理。

产品化之前还要注意继电器动作时间。继电器从驱动到触点闭合通常有5到10毫秒延迟,所以在相位误差满足条件之后,要提前一个固定时间输出合闸信号,让继电器闭合瞬间恰好落在相位误差接近零的时刻。这个提前量可以通过示波器测出来,写进程序里作为常数,别忽略。

最后再说一句我个人做这类项目的体会:并网同步控制看着是个算法问题,实际是模拟电路、MCU外设、控制算法三者的配合。任何一个环节抖一下,锁相质量都会打折。不要迷信某一种花哨算法,先把过零链路的硬件底子打好,让PLL在一个“干净”的输入上工作,后面一切都顺了。如果样机预算允许,直接选带比较器的高端型号,省掉的PCB面积和自己的调试时间,算下来比省那几块钱芯片值得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询