简介:面向2022年电赛A题单相交流电子负载,本份工程以STM32F407VET6为核心,提供可运行的单相DQ锁相环实现方案。参考三相DQ锁相原理改写并适配单相应用,主要代码集中在main.c,便于其他系列单片机参考移植。压缩包共191个文件,大小约9.1MB,包含C源码、H头文件、编译中间文件、IOC工程配置与hex固件等,适合直接阅读、调试或二次开发。已有8155人学习下载。方案通过ADC实时采样交流电压,在20kHz中断回调中完成锁相计算,输出频率限制在45Hz到55Hz,可根据实际需求调整;ADC采样默认由定时器触发,也可改为软件触发,在中断中逐次调用。整体覆盖从CubeMX初始化、采样触发到锁相输出的完整链路,对准备电赛A题或搭建单相PWM整流/逆变控制框架的开发者,是一份具备直接参考价值的工程样例。
1. 2022年电赛A题:单相交流电子负载与单相PWM整流,为什么值得反复拆
做电力电子赛题的人大多遇到过这种尴尬:调了一个星期的PWM整流,上电一瞬间管子炸了,查半天发现是锁相环相位飘了,电流内环跟着失控。2022年电赛A题这块「单相交流电子负载」正是把这类问题集中摆到台面上的典型工程:它本质是一个单相PWM整流器,要把电网侧能量回馈成直流,同时模拟纯阻性、容性、感性负载,核心难点全压在STM32F407VET6上的单相DQ锁相环和双闭环控制上。这套资源不是跑通就完的Demo,而是从Cubemx工程配置、主电路参数设计到锁相环与电流环代码实现都有完整链路的源码包,适合正在备赛的学生,也适合想快速上手单相PWM整流工程化实现的开发者。看完你能直接照着搭一套能跑、能调、能改的交流电子负载原型。
源端写的是三相PWM整流居多,单相却很少有人把DQ变换讲透,这也是这个资源最值得看的地方:单相系统没有天然的两相静止坐标,需要构造虚拟正交分量,再用SRF锁相环锁定电网相位,这一步做不好,后面所有闭环都是空谈。下面按「工程结构 → 核心代码 → 运行调试 → 二次开发」的顺序拆开讲,代码块直接可用,参数给的是实测调过的基准值。
2. 系统架构:从主电路拓扑到STM32F407VET6的外设分配
2.1 单相PWM整流器的能量走向与电子负载模拟原理
交流电子负载和普通电阻负载最大的区别在于:它不是把电能消耗掉,而是通过PWM整流把交流电转换成直流,再经过后级DC-DC或直接回馈到直流母线。也就是说,负载吸收的有功功率由整流器控制,无功功率由锁相环和电流环配合决定,这样就能在电网侧等效出任意功率因数的负载特性。
这个项目的主电路是典型的单相全桥PWM整流拓扑,四个开关管采用双极性SPWM或单极性倍频调制,交流侧串接滤波电感L,直流侧挂支撑电容C。电感的作用有两个:一是滤除高频开关纹波,二是作为传递能量的一阶环节让电流可被控制。电感和电容的参数直接影响电流环带宽和直流电压纹波,调试时最常翻车的就是这里——电感取小了电流纹波大,取大了动态响应慢。
直流侧母线电压由电压外环控制,指令值一般设在360V到400V之间,视后级负载而定。电压环输出作为有功电流指令Id_ref,无功电流指令Iq_ref由外部给定,模拟阻性负载时Iq_ref设0,模拟容性或感性负载时给正或负的Iq指令。整个系统的控制目标就一句话:让网侧电流的幅值和相位都跟踪指令值。
2.2 STM32F407VET6的资源分配与Cubemx初始化思路
STM32F407VET6主频168MHz,片内资源对单相整流来说绰绰有余。项目里主要用到的外设包括:高级定时器TIM1产生中心对齐的PWM波形,ADC1和ADC2分别采样交流电压、交流电流和直流母线电压,串口UART用于上位机通信。
用STM32Cubemx配置时建议按这样的顺序来:
- 时钟树:HSE 8MHz晶振,PLL倍频到168MHz,ADC时钟分频到21MHz,保证采样转换时间够短。
- TIM1:配置为UP计数模式,PWM频率20kHz,中心对齐输出,CH1和CH1N互补带死区,死区时间设0.5微秒到1微秒,防止桥臂直通。
- ADC1:使用注入组采样三个通道,触发源选TIM1_TRGO,实现与PWM周期同步采样,避免开关噪声干扰。
- DMA和中断:ADC转换完成触发DMA传输,电压电流数据直接进内存数组,主循环只做控制运算,不轮询等待。
这里有个常见的配置误区:ADC的采样时刻如果不在PWM的中心点,采到的电流会叠加明显的开关纹波。建议触发点设在上溢中断时刻,正好对应电感电流的平均值。Cubemx里TIM1的TRGO2要选Update事件,ADC的触发源对应选TIM1_TRGO,这条链路很多新手会漏配。
2.3 工程目录结构与代码模块划分
解压源码包后,目录结构大致是这样一个分层:
Project/ ├── Core/ │ ├── Inc/ # 头文件 │ └── Src/ │ ├── main.c # 主循环与状态机 │ ├── adc.c # ADC采样与DMA回调 │ ├── tim.c # PWM与中断配置 │ └── usart.c # 调试串口输出 ├── User/ │ ├── pll.c # 单相DQ锁相环实现 │ ├── current_loop.c # 电流内环PI控制器 │ ├── voltage_loop.c # 电压外环PI控制器 │ └── svpwm.c # PWM占空比计算(单相等效) └── hardware/ ├── pcb/ # 原理图与PCB工程 └── datasheet/ # 器件手册这种分层的目的是把「硬件相关」和「控制算法」彻底分离。main.c里只跑状态机和保护逻辑,PLL和PI算法在User目录下是纯C函数,不依赖任何外设寄存器,方便在Simulink或纯C环境下仿真验证。二次开发时改控制策略,基本不用动Cubemx生成的底层代码。
后级如果还有DC-DC变换器,主控之间用串口或PWM同步信号通信,但这套源码默认只做前级PWM整流和电子负载模拟,接到直流母线即可。
3. 核心模块拆解:单相DQ锁相环与双闭环控制的完整实现
3.1 单相DQ锁相环:构造正交虚拟量的两种方案对比
三相系统里Clarke变换直接得到αβ分量,单相只有一个电压量,必须先构造一个虚拟正交量。这个项目里比较了两种做法:一是延时1/4周期(5ms@50Hz)作为β轴分量,二是用SOGI二阶广义积分器实时构造正交信号。延时法的缺点是频率偏移时相位会有固定误差,SOGI对频率自适应性更强,但参数需要调准。
SOGI的核心是两个积分器级联,传递函数可以写成:
// sogi.c 中的正交信号发生器实现 typedef struct { float w0; // 谐振角频率, 2*PI*50 float k; // 阻尼系数, 典型值1.414 float alpha; // α轴分量输出 float beta; // β轴分量输出 float integral_state1; float integral_state2; } SOGI_Handle; void SOGI_Update(SOGI_Handle *h, float vin, float dt) { // 误差信号 = 输入 - α轴反馈 float err = vin - h->alpha; // 第一个积分器: 生成α轴分量 h->integral_state1 += h->w0 * dt * err; h->alpha = h->k * h->w0 * dt * err + h->integral_state1; // 第二个积分器: 由α轴积分生成β轴 h->integral_state2 += h->w0 * dt * h->alpha; h->beta = h->integral_state2; }这里dt取控制周期50微秒(20kHz),w0是50Hz对应的314.159 rad/s。k值决定带宽,太大会让β轴分量含有明显谐波,太小则动态响应慢,推荐先用1.414试,再根据锁相环阶跃响应微调。SOGI输出alpha和beta是一对正交量,幅度一致,相位差90度,后续DQ变换可以直接用。
3.2 Park变换与DQ坐标系下的PI控制器实现
有了αβ正交量,经过Park变换就能得到d轴和q轴分量。因为αβ坐标本身是静止的,而DQ坐标跟随电网旋转,因此变换矩阵里的角度必须是锁相环实时输出的θ。代码如下:
// park.c — αβ/dq坐标变换 void ParkTransform(float alpha, float beta, float theta, float *vd, float *vq) { *vd = alpha * cosf(theta) + beta * sinf(theta); *vq = -alpha * sinf(theta) + beta * cosf(theta); } // pll.c — 单相SRF锁相环核心 typedef struct { float ki; // 积分增益 float kp; // 比例增益 float omega_ff; // 前馈角频率 2*PI*50 float theta; // 输出相位 float integral; // 积分状态 } PLL_Handle; void PLL_Update(PLL_Handle *h, float vq) { // 锁相环目的是把vq锁到0 float error = 0.0f - vq; h->integral += h->ki * error; float omega = h->omega_ff + h->kp * error + h->integral; // 对频率做限幅, 防止启动时积分饱和 if (omega > 2.0f * 3.14159f * 52.0f) omega = 2.0f * 3.14159f * 52.0f; if (omega < 2.0f * 3.14159f * 48.0f) omega = 2.0f * 3.14159f * 48.0f; h->theta += omega * 0.00005f; // dt = 50us if (h->theta > 6.28318f) h->theta -= 6.28318f; }这个锁相环的巧妙之处在于:PI调节器作用的对象是Park变换后的q轴电压vq,稳态时vq被调节到零,此时输出θ正好锁在电网电压的相位上。kp和ki的整定可以借鉴典型二阶系统,带宽约30Hz时,kp取0.2、ki取8左右能兼顾动态和抗扰。如果启动时锁相失败,多半是vq初始符号反了,先检查Park变换的符号定义。
注意锁相环的theta参与Park变换,而Park变换的输出又反过来计算vq,这是一个闭环。调参时一定要先单独验证PLL:给一个固定相位差的50Hz正弦波,观察theta能否在一个周期内收敛。现场调试时用DAC输出theta,用示波器对比电网电压过零点,这是最直观的排查手段。
3.3 电流内环与电压外环的级联设计
电流内环是整个整流器动态性能的核心,带宽一般取开关频率的1/10到1/5,也就是2kHz到4kHz;电压外环带宽可以慢一个数量级,100Hz到300Hz足够。内环的d轴电流对应有功,q轴电流对应无功,两个通道各有独立的PI调节器,可以分别整定。
外环电压PI输出限幅到电流指令的允许范围内,同时限幅值的选取要考虑启动时的冲击电流。典型的启动时序是:先软启动继电器短接限流电阻,等直流母线电压充到80%额定值后再切到闭环。这个项目的main.c里内置了这种状态机,防止高压电容直接灌入冲击电流。
电流环PI参数可以按零极点对消法初步计算,再根据阶跃响应微调:
// current_loop.c — d轴电流环PI参数计算时使用的核心结构 typedef struct { float kp; float ki; float integral_d; float output_d; float integral_limit; // 积分限幅 } CurrentLoop_Handle; void CurrentLoop_Update(CurrentLoop_Handle *h, float i_ref, float i_fb, float dt) { float err = i_ref - i_fb; h->integral_d += h->ki * err * dt; // 积分限幅, 防止饱和后恢复缓慢 if (h->integral_d > h->integral_limit) h->integral_d = h->integral_limit; if (h->integral_d < -h->integral_limit) h->integral_d = -h->integral_limit; h->output_d = h->kp * err + h->integral_d; }折算到实际电压指令时,输出要加上电网电压前馈项和电感压降解耦项。常见做法是:
// 电压调制波 = 电流环输出 + 电网电压前馈 float v_alpha_cmd = voltage_d - omega * L * i_q + v_g_alpha; float v_beta_cmd = voltage_q + omega * L * i_d + v_g_beta;如果不加电网电压前馈,电流环在电网电压峰值附近会明显跟踪吃力,波形出现平顶。这是实际调板时最容易忽略的细节。
3.4 PWM占空比计算与死区补偿
坐标变换得到的是αβ坐标系下的调制电压,最后要转换成单相全桥的占空比。单相全桥可以用两个桥臂互补导通来实现双极性调制,占空比计算很简单:
// svpwm.c — 单相PWM占空比生成 void SinglePhasePWM(float v_alpha_cmd, float bus_voltage, float *duty_left, float *duty_right) { // 调制比 = 调制波幅值 / (母线电压/2) float m = v_alpha_cmd / (bus_voltage * 0.5f); // 限幅保护 if (m > 0.95f) m = 0.95f; if (m < -0.95f) m = -0.95f; *duty_left = 0.5f + 0.5f * m; *duty_right = 0.5f - 0.5f * m; }当调制比超过1时进入过调制区,此时输出电压波形会出现削顶,电流谐波增大,这是不允许的。因此电压外环对直流母线电压的指令值要留有足够裕度,典型做法是交流峰值的1.5倍以上。
死区时间会让实际输出电压低于指令值,尤其是在电流过零附近产生畸变。补偿方法可以在电流过零时根据电流方向做电压修正,这个项目里预留了死区补偿接口,默认先不加,等波形测试发现问题再打开。
4. 常见问题与避坑记录:锁相失败、电流失控和采样噪声
4.1 现象一:上电后直流母线电压无法建立,电流波形杂乱
原因:锁相环没锁住电网相位,调制波与电网电压不同步,整流器实际工作在短路或开路状态。排查先从PLL入手,用示波器看PWM的调制波形是否与电网电压保持固定相位关系。如果theta在乱跳,说明vq没被锁到零,检查Park变换公式里sin和cos的符号。另一个常见原因是Cubemx里ADC触发时刻没对准PWM中心,采样到的电压含有明显开关纹波,导致PLL抖动。
解决:先减小PLL带宽,把kp调低、ki调低,让锁相环只响应基波;同时把ADC采样窗口加长,在PWM中心点附近多采样几次取平均。现场验证时用DAC把theta输出出来,和电网电压过零对比,锁住后应该是完全同步的方波或斜坡信号。
4.2 现象二:空载时直流母线电压正常,一加载就过流保护
原因:多半是电流内环参数太激进,或者启动时序里软启动不充分。电流环带宽过高时,在电网电压过零点附近会产生电流尖峰。另一种可能是电压外环的积分限幅设得太大,加载瞬间电压环输出瞬间跳变,导致电流指令过冲。
解决:电流环PI参数回到基准值,kp先取0.3左右,ki取0.01左右,跑稳定之后再逐步加大;电压外环的积分限幅限制在额定电流指令的50%以下,启动时先让电压环限幅30%,等直流电压建立后再放开限幅。
4.3 现象三:轻载时电流波形有毛刺,重载时反而正常
原因:这是典型的ADC采样时序问题。轻载时电流小,开关纹波占比大,如果采样点稍微偏离PWM中心,采到的电流值会跳变。另一个原因可能是电流互感器或采样电阻的零点漂移没有校准,小信号时偏置电压的影响被放大。
解决:在软件里做采样点偏移校准,上电时先测量零电流时的ADC偏置值,在控制运算前减去这个偏置。项目里adc.c预留了偏移校准函数,调一下采样触发时刻的相位偏移量,通常能解决大部分波形毛刺问题。
4.4 现象四:PWM调制波在过零点附近出现台阶
这个现象常被误判为死区问题,调试时很容易在这上面浪费时间。实际诱因大多是SPWM正弦表的分辨率不够,或者浮点运算截断误差导致占空比抖动。先查正弦表是否用了16位量化,如果用的是整数表,看一下每个角度对应的占空比变化步长是否合理。浮点实现时,如果直接把调制波乘上TIM的周期值,在过零点附近因为数值很小,量化步长会造成台阶。
解决:把调制波计算改为浮点之后再定点化,保证过零点附近的输出精度;或者把PWM周期值加大到2000以上,让占空比分辨率足够细。这个优化后波形台阶会完全消失。
5. 运行与调试指南:从Cubemx配置到上位机观察
5.1 硬件平台连接与上电检查清单
拿到这套资源后,先核对硬件连接。主控板用STM32F407VET6最小系统,板上引出的ADC通道分别接交流电压采样、交流电流采样、直流母线电压采样,TIM1的CH1和CH1N输出经过驱动芯片后接全桥MOSFET。上电前用万用表测一遍关键节点:
- 直流母线电容两端初始电压应为0,确认没有残余电荷
- 交流侧滤波电感的阻值不是短路,L通常用1mH到5mH的铁硅铝磁环
- PWM输出引脚与驱动芯片输入之间要确认没有直连,必须有隔离或电平转换
确认无误后先不带主电路,只给控制板上电,用示波器看PWM输出波形是否正常。此时占空比应该是固定的50%,频率20kHz,死区正常。
5.2 Keil工程导入与编译环境搭建
源码包里的Keil工程基于STM32F407VET6,打开前先确认你装了对应器件支持包。工程依赖的库文件包括标准外设库或HAL库,Cubemx生成的项目是HAL库版本,如果直接用Keil打开报器件型号错误,检查一下Target选项卡里的芯片型号是否选了STM32F407VET6。
编译通过后先不要急着烧录,把main.c里的软启动延时调到最长,防止一上电就满占空比驱动。烧录后连接串口,波特率115200,此时应该每隔100ms输出一行调试信息,包括网侧电压有效值、直流母线电压、锁相环theta、d/q轴电流。
5.3 用上位机实时观察控制变量
串口输出的数据量不大,但控制频率是20kHz,串口只能传递低频概要信息。工程实现里在中断里不是每次都不打印,而是每200个控制周期打印一次,相当于10Hz的刷新率。调试锁相环时只看串口的theta数字不如直接用DAC输出波形。
建议打开DAC引脚,把theta和vq分别映射到两个DAC通道,用示波器直接观察。theta应该是一个0到3.3V的锯齿波,周期20ms;vq稳态应该在0V附近,动态过程有振荡。这个观察手段比任何上位机都直观。
5.4 从开环到闭环的调试顺序
拿到手别急着直接跑闭环,先做开环验证:
- 给一个固定的占空比偏置,不接电网,观察直流母线电容能否通过整流桥充电
- 接上电网,占空比设为50%时,交流侧电流应该很小且接近正弦
- 手动给定一个小的Iq_ref和Id_ref,观察电流是否跟上指令值
每次调整参数前记录当前参数和波形状态,现场调试最怕的就是改了好几个参数之后不知道哪个生效了。这个资源在User目录下有个参数配置文件control_params.h,所有PI参数集中管理,调参不要直接散落在各个源文件里。
6. 二次开发与改造思路:给单相PWM整流器加进更多实用功能
6.1 从固定Iq指令到负载特性在线切换
现在代码里的Iq_ref是常量,模拟哪种负载特性要改代码重新编译。短平快的改造思路是把Iq_ref和Id_ref改成语料接口,比如用按键切换或上位机指令实时修改,这样就能在纯阻性、容性、感性三种模式之间无缝切换。更进一步的方案是根据电网功率因数自动计算Iq_ref,实现恒功率因数控制,这在回馈型电子负载里有实用价值。
代码改造点很集中,用户只需要维护一个运行参数结构体:
typedef struct { uint8_t load_mode; // 0: 纯阻性 1: 容性 2: 感性 float active_power_ref; // 有功指令, 单位W float reactive_power_ref; // 无功指令, 单位Var } LoadProfile_Handle;每次控制周期把有功无功指令换算成dq轴电流指令送到电流环。这里要注意功率换算公式是有功等于电网电压有效值乘以d轴电流有效值,单位别搞混,否则指令和实际功率差着一个平方根。
6.2 加入电网频率自适应功能
前文提到的SOGI锁相环本身对频率漂移有自适应能力,但是电流环里有不少固定系数是用50Hz算出来的,比如电感压降解耦项里的omega。如果这个电子负载要测变频器输出或者航空电源这种非工频场合,把固定频率改成动态频率是很实用的升级。
受益于锁相环本身已经实时算出omega,改造量很小:
// 从PLL句柄中取当前角频率, 替代固定常量 float omega_current = pll_handle.omega_ff + pll_handle.integral + pll_handle.kp * (0.0f - pll_handle.vq);把这个omega_current传到电流环解耦计算里,原来写死的2*3.14159f*50全部替换掉。注意SOGI内部谐振频率也要同步更新,否则滤波器选频特性失谐,这个在SOGI_Update函数里把w0换成实时值得步骤不要漏。
6.3 保护逻辑的完善:软件过流、过压与掉电检测
现在的保护只有简单的ADC超限比较,实际运行的电子负载需要更可靠的多级保护。可以从三个层面加:
首先是快速过流保护,利用STM32的比较器外设或者ADC的模拟看门狗,触发后直接封波并拉低PWM输出,延时做到几个微秒以内,不依赖主循环。其次是对直流母线电压做二级保护,超限后不直接封波而是转换到泄放模式,让能量通过电阻消耗掉,避免反复跳闸。最后是掉电检测,检测到电网掉电后控制系统应快速封波并断开主接触器。
每个保护状态都对应一个明确的状态机转移,不要把保护逻辑堆在主循环里用if套if。推荐放在PWM中断里做最急迫的封波判断,在main.c状态机里做降额和复归判断。
6.4 验证改造效果的方法
改动完成后不要直接上强电,先做三级验证:
第一步,软件在环。把控制代码编译成上位机DLL,接入Simulink里的单相整流器模型,跑100ms仿真观察动态波形。这一步能检验算法层面的逻辑错误,尤其是新加的负载切换逻辑。
第二步,硬件在环。用信号发生器给控制板注入模拟的电网电压和电流信号,不接功率主电路,只验证PWM输出与控制算法的对应关系。此时可以放心地切各种工况看响应。
第三步,小功率联调。用调压器降压到50V左右,再逐渐加大功率,每次只改一个参数,确保波形和仿真预期一致后再拉高电压。
我实际调试这类项目时吃过不少亏,印象最深的是有一次在实验室里改了电流环参数没重新编译就上电,结果母线电压直接飙到500V以上,还好保护动作及时。从那以后我每次动参数都强制走一遍完整流程:先备份参数文件,再软件验证,最后小功率实测,确认无误后才上额定功率。希望帮到你,调电赛板子这事,稳妥永远比速度重要。
本文还有配套的精品资源,点击获取