简介:这是一份基于TI DSP2833X的PWM移相全桥控制完整源码工程,面向电力电子与数字电源方向的开发者,主要解决移相全桥电路的数字控制、PWM波形生成、死区管理以及故障保护等问题。压缩包为zip格式,共45个文件,包含C源文件、头文件、汇编启动代码、CCS工程配置、链接命令文件、makefile以及编译生成的out和map文件等,覆盖从源码编辑、工程编译到烧录调试的完整链路,整体约266KB,可在CCS环境直接导入查看。代码内含HVPSFB主程序、DPL中断服务程序、设备初始化、移相PWM配置、ADC采样与SCI通信等模块,涉及初始化设置、占空比动态调整、死区与锁存器管理、过流过压故障检测等关键环节。通过研读这份代码,可以较快掌握DSP2833X的PWM模块配置和移相控制软件实现思路,理解全桥开关元件的驱动与保护逻辑;工程中同时提供VMC和PCMC两种控制框架的启动配置,便于对照不同调制策略。目前已有1864人学习,适合正在做电源变换器、电机驱动或全桥拓扑控制的工程师参考。
1. 移相全桥为什么在DSP2833X上绕不开ePWM移相
在DSP2833X上做移相全桥,最核心的控制量不是占空比,而是PWM移相。标题里那几个词——TI移相全桥代码、jungleysm、dps2833X移相控制——搜来搜去,最后落地的方案几乎都集中在ePWM模块的主从同步上。一句话说清:先让ePWM1输出固定频率、固定占空比的方波,再让ePWM2在同一个开关周期里延迟一个相位角,副边能量跟着这个相位角走。变压器绕得再好,相位控制不到,桥臂只会空发热。这里用TMS320F28335做例子,把寄存器配置、电压闭环和示波器验证这一段路径讲透,适合正在调移相全桥的电源工程师。
2. 移相全桥的PWM移相原理与死区设置
全桥的四个开关管分成超前桥臂和滞后桥臂。每个桥臂上下两只管子由ePWMxA和ePWMxB输出互补驱动,占空比固定接近50%。输出电压靠两个桥臂之间的相位差来调节。设超前桥臂先动作,滞后桥臂延迟角φ再动作,原边一个开关周期内先给变压器施加正向电压脉冲,持续时间为(φ/π)*Tsw,然后换向。副边全波整流后得到的等效占空比就是φ/π。
实际系统还要扣除占空比丢失。原边电流换向需要时间,变压器漏感在原边电压反向瞬间会抵消掉一段电压时间面积。有效占空比可以粗算为:
D_eff = φ/π - D_loss
D_loss ≈ 4 * L_k * f_sw * Io / V_in
其中 L_k 是折算到原边的漏感,f_sw 是开关频率,Io 是输出电流。漏感大的电源,D_loss 在满载时可能吃掉 10% 到 15% 的有效占空比,这是后面调 PID 时经常发现输出到不了设定值的根源之一。
2.1 两个桥臂的PWM时序与相位角的物理意义
先看时间顺序。在一个开关周期内,Q1/Q4先导通,原边流过正向电流;等进度到相移角φ所对应的时间,Q2/Q3接管导通,原边电流和电压换向;剩余时间励磁电流回收。这里Q1/Q4一组、Q2/Q3一组分别布置在两个桥臂上,彼此之间没有同一个桥臂的直通风险,真正的风险在同一个桥臂的上下管之间。因此PWM移相比单纯调占空比要多关心一点:固定50%的方波,死区必须在每个周期内稳定重复。DSP2833X的ePWM死区模块做的正是这件事。
2.2 死区参数与软开关的关系
死区时间看似只是安全措施,实际直接影响ZVS。希望Q1关闭后,变压器电流给Q2的输出电容放电,让Q2的反并联二极管先导通,Q2再打开。这个过程要求死区足够长,给电流一个窗口去完成容性充放电;死区过长又会丢掉相位精度,而且变压器电流变小后会失去续流能力。经验做法是先取开关管数据手册里关断延时的2到3倍,比如200ns起步,然后重点看满载时滞后桥臂的Vds波形。没有软开关时桥臂中点会有一个很陡的高压振铃。
2.3 主从ePWM同步结构与移相单位
DSP2833X的ePWM1到ePWM6有同步输入输出。把ePWM1当作主模块,TBCTL.SYNCOSEL设为TB_CTR_ZERO,在计数回零时产生同步脉冲;把ePWM2当作从模块,PHSEN置1,收到同步脉冲后把TBCTR强制设置为TBPHS。从机计数器从TBPHS开始跑,输出自然比主机滞后一个相位角。注意计数器是向上向下计数,一个完整开关周期等于2*TBPRD个TBCLK。用150MHz时钟、100kHz开关频率时,周期参数如下表:
| 参数 | 设定值 | 说明 |
|---|---|---|
| TBCLK | 150 MHz | F28335 EPWM时钟 |
| 开关频率 | 100 kHz | PWM周期 = 2 * TBPRD / TBCLK |
| TBPRD | 749 | 与100kHz对应的时基周期值 |
| TBPHS = TBPRD | 180度 | 对应最大移相 |
| TBPHS/度 | 约4.16 | 每度对应的计数值 |
所以移相角与TBPHS的关系为 TBPHS = (φ / 180°) * TBPRD,最大移相可以设到TBPRD,对应180电角度。不要设超过TBPRD,否则从机计数器会先走完一个递减边沿,移相逻辑直接乱掉。
3. DSP2833X上ePWM移相代码的初始化与动态更新
把主从同步、50%占空比和死区放到一个初始化函数里,是移相全桥代码里最值得先写清楚的部分。下面这段基于TMS320F28335,时钟150MHz,目标开关频率100kHz,死区200ns。
3.1 ePWM1主模块与ePWM2从模块的初始化代码
#include "DSP2833x_Device.h" #include "DSP2833x_Examples.h" #define CPU_FREQ 150.0e6 #define PWM_FSW 100.0e3 #define TBPRD_VAL (Uint16)(CPU_FREQ / (2.0 * PWM_FSW) - 1.0) #define DEADBAND_NS 200.0 #define DEADBAND_VAL (Uint16)(DEADBAND_NS * 150.0 / 1000.0) void PSFB_InitEPwm(void) { // 关闭所有ePWM时基时钟,避免初始化过程中输出乱态 EALLOW; SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; EDIS; // ePWM1:主模块,输出桥臂1的上下管 EPwm1Regs.TBPRD = TBPRD_VAL; EPwm1Regs.TBPHS.bit.TBPHS = 0; EPwm1Regs.TBCTR = 0; EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; EPwm1Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1; EPwm1Regs.TBCTL.bit.CLKDIV = TB_DIV1; EPwm1Regs.TBCTL.bit.PHSEN = 0; // 主模块不接收同步 EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_CTR_ZERO; // 计数回零时发出同步脉冲 EPwm1Regs.CMPA.bit.CMPA = TBPRD_VAL >> 1; // 50%占空比 EPwm1Regs.AQCTLA.bit.CAU = AQ_SET; // 向上计数到CMPA时置高 EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR; // 向下计数到CMPA时置低 // ePWM2:从模块,输出桥臂2的上下管 EPwm2Regs.TBPRD = TBPRD_VAL; EPwm2Regs.TBPHS.bit.TBPHS = 0; // 初始相位0 EPwm2Regs.TBCTR = 0; EPwm2Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; EPwm2Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1; EPwm2Regs.TBCTL.bit.CLKDIV = TB_DIV1; EPwm2Regs.TBCTL.bit.PHSEN = 1; // 从模块使能相位同步 EPwm2Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN; // 只接收同步,不继续转发 EPwm2Regs.CMPA.bit.CMPA = TBPRD_VAL >> 1; EPwm2Regs.AQCTLA.bit.CAU = AQ_SET; EPwm2Regs.AQCTLA.bit.CAD = AQ_CLEAR; // 重新开启时基时钟,所有ePWM同时起振 EALLOW; SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1; EDIS; }初始化前先把TBCLKSYNC关掉,所有ePWM时基配置完成后再打开,这样才能保证ePWM1和ePWM2从第一个周期就相位对齐。ePWM1的PHSEN保持0,它不去接受任何同步;ePWM2的PHSEN设为1,SYNCOSEL设为TB_SYNC_IN,停止继续转发。动作限定符用CAU置高、CAD置低,配合向上向下计数器刚好是一个对称方波。这里CMPA取TBPRD_VAL >> 1是50%占空比,不是CMPA等于TBPRD。
3.2 死区模块配置与互补PWM输出
死区配置会把ePWMxA单独提供的原始信号拆成两路:一路是原始信号,另一路取反并延迟。IN_MODE选择DBA_ALL,表示只有A路输入;POLSEL选择DB_ACTV_HIC,让A路驱动高边而且是主动高,B路自动变成低边互补信号。上升沿延迟DBRED和下降沿延迟DBFED要同时设置。死区计数值 = 实际死区秒数 / TBCLK周期。150MHz时1个TBCLK约6.67ns,200ns死区对应30个计数。
void PSFB_InitDeadBand(void) { // ePWM1 死区:桥臂1上下管 EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; EPwm1Regs.DBCTL.bit.IN_MODE = DBA_ALL; // 死区模块只使用ePWM1A输入 EPwm1Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC; // A路主动高,B路互补 EPwm1Regs.DBRED.bit.DBRED = DEADBAND_VAL; // 上升沿延迟 EPwm1Regs.DBFED.bit.DBFED = DEADBAND_VAL; // 下降沿延迟 // ePWM2 死区:桥臂2上下管 EPwm2Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; EPwm2Regs.DBCTL.bit.IN_MODE = DBA_ALL; EPwm2Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC; EPwm2Regs.DBRED.bit.DBRED = DEADBAND_VAL; EPwm2Regs.DBFED.bit.DBFED = DEADBAND_VAL; }实际调试时,如果发现同桥臂上下管有交叠,不要只加大死区,还要确认当前探头地线是否可靠。死区过大时,移相角在轻载会丢很多相位,波形上表现为输出纹波明显变大,成本比炸管低,但同样不能忽视。
3.3 运行中更新移相角的正确做法
闭环程序里更新相位只需要一行:
void SetPsfbPhase(Uint16 phase) { if (phase > TBPRD_VAL) phase = TBPRD_VAL; EPwm2Regs.TBPHS.bit.TBPHS = phase; }写进寄存器后这个值不会立刻生效。下一次ePWM1计数回零,同步脉冲到来,ePWM2才把新值装进TBCTR。所以写相位和PWM输出之间会延迟最多一个开关周期,这是正常现象。相位更新要在ISR中集中处理,不要在普通循环里频繁改。建议在同一个ISR里完成ADC读取、PID计算和TBPHS写入,避免读到一半被中断打断。需要做反向功率时,把相位值换算成TBPHS = TBPRD - phase,或交换两路PWM的驱动连接,不要在TBPHS里写入负值,因为TBPHS字段是无符号的。
4. 移相全桥电压环:ADC采样、数字PID和相位限幅
移相全桥的闭环回路里,输入是输出电压反馈,输出是相位角。相位角不是线性对象,所以PID参数必须按实际有效占空比来观察,而不是只看误差电压。
4.1 ADC触发时机与采样值换算
输出电压采样点放在变压器副边滤波电感之后,注意主功率地和控制地单点接入。在ePWM1的ZERO事件中触发ADC,这时原边刚刚完成一次换流,副边整流二极管处于导通状态,采样值最接近输出电压平均值。触发源配置如下:
EPwm1Regs.ETSEL.bit.SOCASEL = TB_CTR_ZERO; // 在ZERO事件触发ADC EPwm1Regs.ETSEL.bit.SOCAEN = 1; EPwm1Regs.ETPS.bit.SOCAPRD = 1; // 每个周期触发一次ADC转换完成后触发ADCINT,在ADC中断里做控制运算,相位更新会在下一次同步脉冲时写入,整个控制周期和PWM周期严格同步。采样值用12位分辨率时,直接右移3位得到0到4095。实际电压换算系数由分压电阻决定,不要用理论电压硬算,上电后用万用表标定一次。
4.2 增量式PID的C代码实现
增量式PID在移相全桥里比位置式PID更省心,因为相位限幅后不会出现积分项单独发散的问题。实现如下:
typedef struct { float kp, ki, kd; float Ts; float err_1, err_2; float out; } PsfbPid; void PsfbPidInit(PsfbPid *p, float kp, float ki, float kd, float Ts) { p->kp = kp; p->ki = ki; p->kd = kd; p->Ts = Ts; p->err_1 = 0.0f; p->err_2 = 0.0f; p->out = 0.0f; } float PsfbPidCalc(PsfbPid *p, float ref, float fb) { float err = ref - fb; float a0 = p->kp + p->ki * p->Ts + p->kd / p->Ts; float a1 = -(p->kp + 2.0f * p->kd / p->Ts); float a2 = p->kd / p->Ts; float dout = a0 * err + a1 * p->err_1 + a2 * p->err_2; p->err_2 = p->err_1; p->err_1 = err; p->out += dout; return p->out; }a0/a1/a2是增量式PID的差分系数。a0包含比例、积分和微分项,a1由比例和微分组合,a2是纯微分。输出带符号,但移相全桥正常单向工作时的相位输出应限制在0到TBPRD之间。如果反馈方向不对,先把反馈系数调正,不要靠负PID参数硬拉。
4.3 PID参数整定与相位边界
先只用比例,跑到稳态后再加积分。下表是100kHz、高压输入到低压大电流输出场景的经验起点:
| 负载条件 | kp | ki | kd | 相位范围 |
|---|---|---|---|---|
| 轻载 | 0.02 | 0.005 | 0.0002 | 0 ~ 0.6*TBPRD |
| 半载 | 0.05 | 0.012 | 0.0005 | 0 ~ TBPRD |
| 满载 | 0.08 | 0.020 | 0.0010 | 0 ~ TBPRD |
注意ki项不要把相位推过180度限制。相位输出必须在闭环代码里做一次限幅,避免变压器工作点进入占空比丢失严重区。限幅后的相位值写入TBPHS,不要绕过限幅直接操作寄存器。
5. 移相全桥调试技巧:示波器验证移相与软开关
5.1 用两张波形图确认移相是否生效
第一张测ePWM1A和ePWM2A,两路应相差TBPHS / TBCLK秒。连续改变设定phase,示波器上沿间隔要跟随变化。如果间隔固定不动,检查ePWM2的PHSEN和SYNCOSEL。第二张测同一桥臂上下管驱动Vgs,确认两者死区时间稳定。死区时间用寄存器换算:deadtime_ns = DBRED * 1000 / (TBCLK / 1e6)。这个换算式经常用来核对配置值。
5.2 通过开关管Vds找ZVS边界
把探头跨在滞后桥臂下管两端,看死区开始到结束这一段Vds。若Vds在死区结束前已经降到0,是软开关;若仍是母线电压或出现高频振铃,说明移相角太小或死区太短、漏感偏大。满载能软开关不稀奇,关键是30%负载以下是否仍然能维持。调试时逐步增大最小相位限幅值,直到轻载Vds的振铃消失。最小phase值会牺牲轻载效率,要与待机功耗要求一起权衡。
5.3 占空比丢失造成的控制偏差修正
在满载和空载分别记录输出电压和实际TBPHS,可以反推D_loss。比如空载时180度附近对应100%有效占空比,满载时同样输出电压可能需要把相位上限推到178度,甚至更高。出现这种情况不要盲目加大kp,应该在控制代码里引入负载前馈或根据输入电压做相位补偿。常见做法是把D_loss预估值换算成TBPHS,加到PID输出项之前:
float DlossLlk = 4.0f * Llk * fsw / Vin; float phase_comp = DlossLlk * out_current * TBPRD_VAL / 180.0f;该补偿量只应在重载方向生效,空载时强制置零,否则轻载会出现固定偏置相位,导致输出纹波和变压器噪音变大。用这个方法记录不同负载下的补偿表,再和PID输出叠加,比单纯把kp调大要稳得多。
本文还有配套的精品资源,点击获取