简介:移相全桥控制是电力电子领域广泛应用的技术,通过改变PWM信号的占空比来调整全桥开关管的导通时序,从而提升电源转换效率并抑制电磁干扰。这套基于TI DSP2833X微控制器的完整工程代码,面向电力电子与电机驱动方向的开发者,解决全桥变换器中移相控制策略的实现问题。工程包含主程序HVPSFB-Main.c、PWM移相配置源文件、DPL-ISR中断服务程序及DSP2802x外设头文件,覆盖初始化设置、PWM生成、死区时间配置、故障检测与闭环调节等关键模块。资源共45个文件,以C语言源码、汇编程序、CCS工程配置、编译脚本及输出文件为主,压缩包仅266KB,结构清晰紧凑。目前已有1864人学习/下载,适合想快速上手DSP2833X移相全桥控制的工程师或研究者;通过研读代码可掌握边沿对齐/中心对齐PWM配置、上下桥臂锁存器管理、过流保护等实用技巧,并便于在CCS工程中直接阅读与调试。
1. 移相全桥控制为什么值得用 DSP2833X 写
移相全桥代码里真正值钱的只有一行:EPwm2Regs.TBPHS 的赋值。其余 PWM 初始化、死区配置、中断服务程序,都是在为这行赋值搭台子。移相全桥(Phase-Shifted Full Bridge,PSFB)在通信电源、充电桩 DC-DC 和车载 OBC 里几乎是标配拓扑,原边四个管子实现 ZVS,副边整流后等效开关频率翻倍,效率曲线在 40%~60% 负载附近尤其好看。但代码层面真正有信息量的不是「全桥」——那只是两路互补 PWM,而是「移相」:两个 50% 占空比的桥臂之间那几十度相位差被用来调节输出能量,这才是 DSP 比 UCC28950 这类模拟控制器值得用的地方。DSP2833X 有浮点核,算占空比不需要查表;ePWM 模块原生支持主从同步、相位寄存器和互补死区,四路 PWM 让两个模块级联就够。你在 Gitee 或 GitHub 搜「移相全桥代码」时,常能看到带 jungleysm 这类个人标识的 DSP2833X 仓库,里面大多是裸机寄存器工程,没有 RTOS,核心就是几十行寄存器配置加一个周期中断。下面按参数换算、寄存器初始化、闭环更新、验证排错四个层次展开,新手能从最小系统跟到波形,老手可以重点看相位限幅和中断时序。
2. PWM 移相在 DSP2833X 上的时序实现与参数换算
2.1 移相角与输出占空比的关系先算清楚
移相全桥的四个开关中,Q1/Q2 组成超前桥臂(A 点),Q3/Q4 组成滞后桥臂(B 点)。两个桥臂都是接近 50% 的占空比,滞后桥臂相对超前桥臂滞后一个相位角 δ。原边电压 V_AB 在 Q1、Q4 同时开通时为正,在 Q2、Q3 同时开通时为负;两个桥臂完全同相时正负抵消,原边没有电压输出,滞后 180° 时变成普通硬开关全桥,副边得到近乎满幅的方波电压。
数值关系可以写成非常干净的形式:D_eff = 1 - δ / 180°(δ 用度表示)。D_eff 是折算到副边的有效占空比,理想输出电压约为 Vin × (Ns/Np) × D_eff。比如 400V 输入、变压器匝比 4:1、目标 48V 输出时,需要 D_eff = 0.48,对应 δ ≈ 93.6°。这个公式决定了闭环程序里电压误差到相位角的变换方向:误差为正,说明需要更大的有效占空比,也就是减小 δ;误差为负,则要增大 δ。方向弄反的直接结果是环路变成正反馈,输出电压一下顶到保护值。
实际工程里不能把 0.48 直接当成最终占空比,因为原边电流在漏感 Lr(变压器漏感加外置谐振电感)上换流会吃掉一部分相位。CCM 模式下副边占空比丢失的近似公式是 D_loss ≈ 4·Lr·Ip / (Vin·Tsw)。举一组常见参数:Lr = 12μH,Ip = 6A,Vin = 400V,Tsw = 10μs,算出来大约 7.2%,换成角度就是 13° 左右。这也是纯查表开环方案容易出问题的地方,换流时间随负载电流变化,开环表很难覆盖每条负载线。
提示:上面公式里的 Ip 是换流前的原边峰值电流,不是副边折算电流,估算时用满载参数算一次相位补偿,再留出 10° 左右的软件裕量。
2.2 ePWM 主从模块怎么搭出四路 PWM
DSP2833X 的 ePWM 模块结构决定了实现移相全桥最顺手的方案:ePWM1 作主模块驱动超前桥臂,ePWM2 作从模块驱动滞后桥臂,两个模块共享 EPWMCLK,用同步信号把从模块的计数器对齐到主模块的周期起点。两个模块之间唯一的差异就是 TBPHS 寄存器,它决定从模块计数器在同步事件到来时的初始相位。
寄存器层面的行为是:主模块在 TBCTR 计到 0 时通过 EPWM1SYNCO 输出同步脉冲;从模块配置 PHSEN = 1 后,收到同步脉冲就把 TBPHS 里的值装载进 TBCTR。换句话说,任意时刻改写 TBPHS,下一个开关周期开始就会生效。这个机制天然支持在线调相,不需要额外的软件定时器去对齐换相时刻。四路 PWM 映射为:EPWM1A → Q1,EPWM1B → Q2,EPWM2A → Q3,EPWM2B → Q4。EPWM1A/B 靠 DB 模块生成互补关系并插入死区,EPWM2 的 A/B 两路同样处理。
这里要提醒一个容易漏的配置:如果只盯着 ePWM1A 和 ePWM2A 之间的相位差,忘记给 EPWM2B 配置同样的死区参数,滞后桥臂上下管会在换相时直通。DB 模块的寄存器是每个 ePWM 模块独立的,主模块配好了不等于从模块也配好。
2.3 死区与最小移相角的工程约束
死区时间按开关管关断特性估算,常见的 CoolMOS 和 IGBT 在硬开关条件下需要 150~300ns。EPWMCLK 为 150MHz 时一个计数周期约 6.67ns,30 个计数就是 200ns。推荐把 ePWM1、ePWM2 的 DBRED 和 DBFED 设成相同值,先按 200ns 起调,上电后观察原边电压尖峰再微调,不要一上来就追求最大 ZVS 范围而把死区拉满。
死区还约束着最小移相角。δ/360° × Tsw 如果小于死区时间,副边正负脉冲会窄到被死区完全覆盖,输出电压出现不可控跳变。以 100kHz、200ns 死区为例,对应的最小角度约 7.2°。软件里要给相位角加下限幅,不能只依赖硬件过流保护,这是我见过移相角没有下限制导致轻载失控的常见原因。
下面是一个顺手可用的换算表,按 100kHz、EPWMCLK = 150MHz、TBPRD = 1500 计算,TBPHS 计数值 = 1500 × δ / 180°。表里没扣除占空比丢失,实际调试时输出电压会比表格低 3%~8%,看到偏差先算 Lr 的贡献,别急着怀疑 PID。
| 移相角 δ | 有效占空比 D_eff | 折算输出电压(400V / 4:1) | TBPHS 计数值 |
|---|---|---|---|
| 60° | 0.667 | 66.7V | 500 |
| 72° | 0.600 | 60.0V | 600 |
| 90° | 0.500 | 50.0V | 750 |
| 108° | 0.400 | 40.0V | 900 |
| 120° | 0.333 | 33.3V | 1000 |
3. DSP2833X 移相全桥 PWM 的初始化代码与寄存器配置
3.1 GPIO 复用与 ePWM1/2 时钟使能
DSP28335 的 ePWM1A/B 默认映射在 GPIO0/GPIO1,ePWM2A/B 在 GPIO2/GPIO3。GPIO 配置只需要把 GPAMUX1 对应位置成 1,不需要单独设置方向寄存器,复用功能会接管引脚控制。时钟方面有一个容易忽视的点:EPWMCLK 默认跟随 SYSCLKOUT,但如果所有 ePWM 模块先跑起来再逐个初始化,两个模块的计数器会处在未知相位关系上。标准做法是先通过 TBCLKSYNC = 0 冻结全部时基,等两个模块都配置完成后再统一放开。
void InitPsfbGpio(void) { EALLOW; // GPIO0-3 复用为 EPWM1A / EPWM1B / EPWM2A / EPWM2B GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 1; GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 1; GpioCtrlRegs.GPAMUX1.bit.GPIO2 = 1; GpioCtrlRegs.GPAMUX1.bit.GPIO3 = 1; // 先停掉所有 TBCLK,主从计数器保持静止状态 SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; EDIS; }这段代码里 GPIO0-3 的四行是 28335 的固定映射,换用 2837x 或其它系列时引脚编号会变,需要重新查数据手册的引脚复用表。TBCLKSYNC 是系统控制寄存器,必须放在 EALLOW/EDIS 保护区内,漏掉 EDIS 会导致后续寄存器写入失败,而且这种问题在仿真器里不容易直接定位到原因。
3.2 主模块 ePWM1 的寄存器配置
主模块负责超前桥臂,自身不加载外部相位,但要负责给从模块发同步脉冲。配置分三块:时基(TB)、动作限定(AQ)、死区(DB)。时基决定开关频率和计数模式,动作限定决定 EPWM1A 什么时候拉高拉低,死区模块从 A 信号生成互补的 B 信号。
void InitPsfbEpmw1(void) { EALLOW; // 时基:up-count,150MHz 不分频,TBPRD=1500 对应 100kHz EPwm1Regs.TBCTL.bit.PRDLD = 0; // TBPRD 立即装载 EPwm1Regs.TBCTL.bit.CTRMODE = 0; // 0 = up-count EPwm1Regs.TBCTL.bit.CLKDIV = 0; // TBCLK = EPWMCLK / 1 EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0; // 高速时钟分频 = 1 EPwm1Regs.TBCTL.bit.PHSEN = 0; // 主模块不从同步输入装相位 EPwm1Regs.TBCTL.bit.SYNCOSEL = 1; // CTR=0 时输出同步脉冲 EPwm1Regs.TBPRD = 1500; EPwm1Regs.CMPA.bit.CMPA = 750; // 50% 占空比 // 动作:CTR=0 输出低,向上计数碰到 CMPA 输出高 EPwm1Regs.AQCTLA.bit.ZRO = 1; // 清除输出(拉低) EPwm1Regs.AQCTLA.bit.CAU = 2; // 置位输出(拉高) // 死区:上升沿和下降沿都延时 30 计数 = 200ns EPwm1Regs.DBCTL.bit.OUT_MODE = 3; // A/B 都由 DB 模块输出 EPwm1Regs.DBCTL.bit.POLSEL = 2; // A 不反相,B 反相,构成互补 EPwm1Regs.DBRED.bit.DBRED = 30; EPwm1Regs.DBFED.bit.DBFED = 30; // 周期中断:每次 CTR=0 触发一次中断 EPwm1Regs.ETSEL.bit.INTEN = 1; EPwm1Regs.ETSEL.bit.INTSEL = 1; // 1 = CTR=0 事件 EPwm1Regs.ETPS.bit.INTPRD = 1; // 每个周期事件都响应 EDIS; }逐项说明。SYNCOSEL = 1 让主模块在 TBCTR 归零时输出同步脉冲,这是从模块对齐相位的前提。AQCTLA 里 ZRO = 1 表示计数到 0 时清除输出,CAU = 2 表示向上计数碰到 CMPA 时置位输出,所以 EPWM1A 高电平区间是 0 到 750,占空比正好 50%。注意 AQ 模块里没有配置 AQCTLB,EPWM1B 由 DB 模块根据 A 信号反相生成,再去操作 AQCTLB 属于重复配置,会让输出动作不确定。死区的 OUT_MODE = 3 配合 POLSEL = 2 是互补 PWM 的标准写法:EPWM1A 保持原极性,EPWM1B 反相,DBRED 和 DBFED 分别延时 200ns,另一路从信号不会和原信号发生重叠导通。
3.3 从模块 ePWM2 的相位加载与同步配置
从模块的寄存器配置和主模块大部分一致,多出的关键位是 PHSEN = 1,以及初始相位寄存器 TBPHS。同步事件到来时,TBPHS 的值被装载进 TBCTR,ePWM2 的周期起点因此落后 ePWM1 一个可编程角度。启动时把 TBPHS 设为 0,意味着两个桥臂完全同相,原边电压为零,这是相对安全的软启动起点。
void InitPsfbEpmw2(void) { EALLOW; EPwm2Regs.TBCTL.bit.PRDLD = 0; EPwm2Regs.TBCTL.bit.CTRMODE = 0; EPwm2Regs.TBCTL.bit.CLKDIV = 0; EPwm2Regs.TBCTL.bit.HSPCLKDIV = 0; EPwm2Regs.TBCTL.bit.PHSEN = 1; // 从模式:同步事件装载 TBPHS EPwm2Regs.TBCTL.bit.SYNCOSEL = 0; // 同步输入来自 EPWM2SYNCI EPwm2Regs.TBPRD = 1500; EPwm2Regs.CMPA.bit.CMPA = 750; EPwm2Regs.AQCTLA.bit.ZRO = 1; EPwm2Regs.AQCTLA.bit.CAU = 2; EPwm2Regs.DBCTL.bit.OUT_MODE = 3; EPwm2Regs.DBCTL.bit.POLSEL = 2; EPwm2Regs.DBRED.bit.DBRED = 30; EPwm2Regs.DBFED.bit.DBFED = 30; EPwm2Regs.TBPHS.bit.TBPHS = 0; // 初始相位 0,原边无输出 EDIS; // 两个模块配置完成,统一启动时基 EALLOW; SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1; EDIS; }TBPHS 的更新时机值得说清楚。周期中断里直接写这个寄存器不会立刻改变输出,而是等下一个同步事件到来才生效,因此不会出现半周期乱脉冲。但如果在中断里同时改动 CMPA 和 TBPHS,两者生效点不同步:CMPA 改动要等到下一个周期边界,TBPHS 改动要等到同步事件。好在 100kHz 下 TBPRD 为 1500 个计数,完成两个 16 位寄存器写操作的时间裕量非常充足。下表把关键寄存器整理出来,调试时对照看:
| 寄存器/位域 | 作用 | 本文配置值 |
|---|---|---|
| TBCTL.CTRMODE | 计数模式,up-count 便于周期中断固定相位 | 0 |
| TBCTL.PHSEN | 从模块使能相位装载 | ePWM2 = 1 |
| TBCTL.SYNCOSEL | 同步输出源选择 | ePWM1 = 1,ePWM2 = 0 |
| TBPRD | 周期计数 | 1500 |
| CMPA | 占空比比较值 | 750 |
| AQCTLA.CAU / ZRO | 动作限定,决定高电平区间 | 2 / 1 |
| DBRED / DBFED | 死区上升/下降延时 | 30 |
| TBPHS | 移相计数值 | 0~1500 |
4. 移相全桥闭环的 PID 更新与软启动实现
4.1 控制变量的选择:用有效占空比算移相角
写移相全桥闭环时,有一种常见做法是让 PI 直接输出移相角,这样不是不能用,但增益的物理意义不直观。我习惯让 PI 输出一个「指令有效占空比」D_cmd,限幅在 0.05~0.90 之间,再用 D_eff = 1 - δ/180° 反算相位角。理由是 D_cmd 可以直接和副边电压、匝比去比对:目标 48V、Vin = 400V、匝比 4:1 时 D_cmd ≈ 0.48,调 PI 时看这个量比看角度更容易判断是超调还是静差。
反算角度时要考虑占空比丢失。补偿值可以按满载工况估算,例如第 2.1 节算出的 13°,写成常量 D_LOSS_DEG 放进程序。负载变化时补偿不会完全精确,剩余误差交给 PI 积分器修正。这个策略实现简单,不用实时估算 Lr 上的换流时间,工程上最稳。
4.2 周期中断里的增量式 PI 与限幅
控制循环放在 ePWM1 周期中断里。中断频率等于开关频率 100kHz,在 150MHz 主频下开销偏高,实际项目里我推荐把 ETPS.INTPRD 设成 2 或 4,控制频率降到 50kHz 或 25kHz。对 48V 输出的电源,25kHz 控制已经足够,还能给 ADC 转换和浮点运算留足裕量。下面是一段可以直接放进中断服务函数的增量式 PI:
__interrupt void epwm1_isr(void) { // 读 ADC 结果并按实际分压比换算成电压值(单位 V) float32 v_fb = (float32)AdcRegs.ADCRESULT0 * V_SCALE; // 增量式 PI:u 是有效占空比指令 float32 err = v_ref - v_fb; u += kp * (err - err_prev) + ki * err; err_prev = err; // 幅值限制,D_MAX 留出死区和换流裕量 if (u > D_MAX) u = D_MAX; if (u < D_MIN) u = D_MIN; // 有效占空比换算到移相角,再换算到 TBPHS 计数值 float32 delta = 180.0f * (1.0f - u) - D_LOSS_DEG; if (delta < MIN_DEG) delta = MIN_DEG; EPwm2Regs.TBPHS.bit.TBPHS = (uint16_t)(TBPRD * delta / 180.0f); // 清中断标志 EPwm1Regs.ETCLR.bit.INT = 1; PieCtrlRegs.PIEACK.all = PIEACK_GROUP3; }增量式 PI 的公式里,kp 控制误差变化的响应速度,ki 控制误差积分速度。增益的起步值按电压单位估算:48V 电源上 kp 取 0.003~0.008,即 1V 误差产生 0.3%~0.8% 的占空比修正量;ki 取 kp 的 1/10 到 1/5。下面给一组按控制频率区分的参考起点值,实际整定时按阶跃响应方向微调。注意这些数值依赖 ADC 采样比例和电压基准,换一套硬件需要重新标定。
| 控制频率 | kp 起点 | ki 起点 | D_MAX | D_MIN |
|---|---|---|---|---|
| 50kHz | 0.005 | 0.001 | 0.90 | 0.05 |
| 25kHz | 0.008 | 0.002 | 0.90 | 0.05 |
| 10kHz | 0.012 | 0.004 | 0.88 | 0.05 |
增量式 PI 自带一部分抗饱和能力,因为没有单独的积分累加器,但输出持续顶在限幅值时误差累积仍会造成恢复迟滞。更严格的做法是饱和期间冻结 u 的更新,也就是检测到 u 达到 D_MAX 且误差仍为正时,不执行 u += 这一段。
4.3 软启动和中断响应时间的实际处理
移相全桥启动时最怕直接给满载阶跃,变压器磁化电流和输出电容充电电流叠加会让原边电流瞬间冲高。我在项目里通常把 v_ref 做成软件斜坡,每次中断只增加额定目标值的一小段。以 25kHz 控制频率为例,每帧加 0.08V,48V 目标约 2.4 秒爬升完毕,比反复整定 PI 省事得多。斜坡结束后按额定电压闭环运行,切换点放在电压误差小于阈值的那一帧。
ADC 采样时刻对环路品质的影响比很多人想象的大。副边电感电流在开关周期起点和终点处于谷底,中点附近处于峰值,如果在任意相位启动 ADC 采样,采样值叠加周期纹波,环路容易在高频抖动。推荐把 ADC 触发对齐到 CMPA 事件,也就是副边电压脉冲的中点附近采样,或者对齐到 CTR=0 后再做两次平均。
中断函数执行时间是另一个关键点。代码放在 Flash 里运行时有取指等待,中断响应时间会随流水线状态波动。TI 的 C28x 编译器支持把中断函数放到 RAM 执行,写法是:
__attribute__((ramfunc)) __interrupt void epwm1_isr(void)也可以等价的用#pragma CODE_SECTION(epwm1_isr, ".TI.ramfunc")把函数放进可初始化 RAM 段。对 25kHz 或更高的控制频率,这一步能让 ISR 抖动小一个数量级,代价只是几十字节 RAM。需要注意链接脚本里.TI.ramfunc段必须被分配到 RAM,并且有对应的初始化拷贝。
5. 验证移相全桥代码的示波器技巧与常见踩坑
5.1 先观察滞后桥臂的 ZVS,再谈效率
示波器探头接在滞后桥臂下管 Q4 的 Vds 和 Vgs 上,重点看开通瞬间:如果 Vds 降到零之后栅极才拉高,ZVS 成立;如果栅极拉高时 Vds 上还有几十伏电压,说明死区太短或谐振电感能量不足。轻载下滞后桥臂会先丢失 ZVS,这是移相全桥的固有特性,不是 PID 能解决的,但至少要先确认满载时波形是正确的,再继续调环路。
原边电压 V_AB 的波形也要一起看。它不是干净方波,在每个半周期开始处会有一块被吞掉的平台,平台宽度等于原边电流换流时间,随负载电流增大而加宽。这就是占空比丢失的直观表现。如果平台宽度明显超过按 D_loss ≈ 4·Lr·Ip / (Vin·Tsw) 估算的数值,说明 Lr 偏大或变压器漏感超出预期,需要回到磁件参数上调整。
5.2 CCS 仿真器驱动加载失败的排查路径
换新电脑跑老工程时,常会遇到仿真器驱动加载失败,日志里出现 unable to load C:\ti\ccsv6\ccs_base\emulation\drivers\tixds560icepick_d.dvr 这类信息。这条报错通常和移相全桥代码无关,而是 CCS 版本与 XDS560 驱动不匹配,或者安装目录权限不够。先确认 CCS 是完整安装而不是只拷贝了工程,再看仿真器驱动服务有没有被安全软件拦截。如果只是为了验证代码逻辑,换 XDS100v2 这类低成本仿真器往往更快,不必在 XDS560 驱动上耗太多时间。
5.3 用 LTspice 把 PWM 策略先验证一遍
LTspice 不能直接编译 DSP 程序,但不妨碍做联合验证:把 DSP 中断里计算出的移相角和 TBPHS 值导出成时间序列,在 LTspice 里转成 PWL 电压源作为理想开关管的驱动信号。死区时间、移相角变化速率都做成参数,可以快速模拟轻载到满载的相位跨度,以及死区边界处原边电压的畸变情况。TI 的 DSP 模型在 LTspice 里确实不常见,但移相全桥只需要理想开关、二极管和耦合电感,用 LTspice 自带器件就能搭出可用的功率级模型。这部分验证在真实硬件上要多轮试错,在仿真里十几分钟就能跑完。调完这一轮,再回到硬件上看原边电流波形和副边二极管反向恢复特性,剩下的就是死区时间与磁件参数的微调。
本文还有配套的精品资源,点击获取