在 C2000 上写一个 PWM 发波与中断程序(F28335 实战)
整理自《DSP 自学视频(顾老师)从入门到编写(C2000)》第 18 集《写个 PWM 发波与中断的程序》 (UP 主:Study-Charging)。
本篇把视频里的讲解流程与关键配置梳理成文字,方便对照代码复习。
一、这节课要实现什么
用 F28335 的 ePWM 模块写一个完整工程,达到下面几个目标:
- 使用 EPWM1 单元的两个引脚 EPWM1A 、EPWM1B ,输出一对互补的 PWM 波形;
- PWM 频率固定为 10 kHz;
- EPWM1A 与 EPWM1B 之间插入 1 µs 死区时间,用来安全驱动桥式电路(避免上下管直通);
- 启用 ePWM 的周期中断 ,在中断服务函数里实时改变占空比。

提示:视频里还顺带介绍了一个辅助工具「C2000 助手」(可在 hellodsp 官网下载),写程序时用来快速查寄存器,不必死记硬背。
二、先配置 GPIO 引脚复用
要让 EPWM1A / EPWM1B 真正从芯片引脚上发出波形,第一步是把对应的 GPIO 引脚配置成 ePWM 功能:
- EPWM1A → 对应 GPIO0;
- EPWM1B → 对应 GPIO1。
做法:把 GPAMUX 寄存器里相应的位设为 1,引脚就变成 EPWM1A / EPWM1B 功能;如果设成 0,它就是普通 GPIO。这一步做错,后面再怎么配都不会出波形。
三、时钟与频率:怎么算出 TBPRD = 3750
PWM 周期由两个因素决定:时钟 和计数模式 。本节课假设采用增减计数模式(up-down count)。
在增减计数模式下,一个 PWM 周期等于 2 × TBPRD (计数器从 0 数到 TBPRD,再减回 0)。计数器每记一次数所花的时间,取决于 TBCLK。
TBCLK 由 SYSCLKOUT 和 TBCTL 里的两个分频位决定:
CLKDIV(对应视频里的 c2kdiv);HSPCLKDIV(对应视频里的 hspclkdiv)。
本例设置:CLKDIV 效果为 1,HSPCLKDIV 效果为 2,而 SYSCLKOUT = 150 MHz,于是:
TBCLK = 150 MHz / (1 × 2) = 75 MHz
也就是说计数器每记一次数,耗时 1 / 75 MHz。一个 PWM 周期 = 2 × TBPRD / TBCLK。
我们要的是 10 kHz,所以:
TBPRD = TBCLK / (2 × f) = 75×10⁶ / (2 × 10⁴) = 3750
结论:在「75 MHz 时钟 + 增减计数」这套方案下,想输出 10 kHz ,就把周期寄存器 TBPRD 赋值为 3750。
四、动作限定(AQ):先让 A、B 输出相同的波形
通过动作限定控制寄存器 AQCTLA / AQCTLB,可以规定 EPWMxA / EPWMxB 在时间基准计数器(TBCTR)发生特定事件时做什么动作。
计数器能产生这些事件:CTR = 0、CTR = PRD、CTR = CMPA、CTR = CMPB,还可以叠加「递增 / 递减」方向。
本例只用 CMPA ,并让 EPWM1A 和 EPWM1B 做完全相同的动作:
- 当
CTR = CMPA且递增 时(CAU事件)→ 引脚输出高电平; - 当
CTR = CMPA且递减 时(CAD事件)→ 引脚输出低电平。
此时 A 和 B 是完全同相的波形。占空比关系为:
D = (TBPRD − CMPA) / TBPRD = 1 − CMPA / TBPRD


五、死区模块(DB):把同相波形变成互补 + 1 µs 死区
上一步得到的是两路一模一样的波形,而我们要的是互补且带死区 。这就交给**死区子模块(DB)**处理。

DB 模块的配置逻辑:
- 以 EPWM1A 的上升沿 和 EPWM1B 的下降沿作为死区延时的参考源;
- 经过 DB 后,EPWM1A 的上升沿被延迟、EPWM1B 的下降沿被延迟,二者错开,形成死区;
- 再把 EPWM1B 的极性取反。

这样一来,EPWM1A 保持原样,EPWM1B 变成「延迟 + 反相」的版本,A、B 就成互补关系,并且:
- EPWM1A 导通(开关 S1 导通)时,EPWM1B 已完全关断;
- EPWM1B 导通(开关 S2 导通)时,EPWM1A 已完全关断。
这正是驱动桥电路所需要的「死区」保护。如果反过来让两路同时为高,上下管就会直通 ,电路根本没法正常工作。

死区时间怎么算? DB 模块内部有一个计数器,每记一次数耗时同样是 1 / TBCLK = 1 / 75 MHz。要得到 1 µs 死区:
DBRED = DBFED = 1 µs × 75 MHz = 75
所以把 DBRED(上升沿延迟)和 DBFED(下降沿延迟)都赋值为 75,死区就是 1 µs。
六、ePWM 中断:周期中断 INT3.1
本例会用到 ePWM 中断(EPWMxINT) 。在中断向量表里,它位于 PIE 第 3 组第 1 个 ,即 INT3.1。
中断事件选择为 周期匹配事件(CTR = PRD) :每当计数器回到 PRD,就产生一次中断。这样每个 PWM 周期都会进一次中断,我们就在中断里实时调整占空比。

七、main 函数与 ISR 的整体流程
视频带着读了一遍工程代码,核心流程如下:
main 函数:
InitSysCtrl()初始化系统控制:配置时钟(PLL)、初始化外设时钟;- 关键:必须把 EPWM1 的外设时钟使能,否则 TBCLK 不工作,PWM 根本不出波形。
- 初始化 GPIO:把 GPIO0 / GPIO1 通过
GPAMUX配成 EPWM1A / EPWM1B。 - 初始化占空比变量
duty = 0.5(上电默认 50% 占空比)。 - 初始化 PIE 中断向量表、初始化 PIE 控制函数;
- 先
DINT关 CPU 中断,清除 IER / IFR 标志位,再初始化中断向量表; - 给中断源
EPWMxINT在向量表里填入入口地址(也可直接写在DSP2833x_DefaultIsr.c的默认中断里); - 由于 EPWMxINT 属于 CPU 中断 3、PIE 第 3 组第 1 个,对它使能;
- 重新
EINT打开中断; - 调用
PWM_Config()配置 ePWM。
c
#include "DSP2833x_Project.h"
#include <stdio.h>
#include <string.h>
// EPWM配置函数声明
void ConfigEPwm(void);
// EPWM1中断服务函数声明
interrupt void EPWM_INT(void);
Uint32 LoopCount;
float Duty; // 控制占空比的参数
void main(void)
{
// 初始化系统控制:PLL、看门狗、开启外设时钟
InitSysCtrl();
asm(" RPT #8 || NOP");
EALLOW; // 解锁受EALLOW保护的寄存器,允许写入
InitEPwm1Gpio(); // 初始化EPWM1对应的IO引脚
EDIS; // 上锁,禁止修改受保护寄存器
Duty=0.5; // 初始占空比 50%
DINT; // 关闭全局中断
InitPieCtrl(); // 初始化PIE中断控制器
// 关闭所有CPU中断,清除所有CPU中断标志位
IER = 0x0000;
IFR = 0x0000;
InitPieVectTable(); // 初始化PIE中断向量表
EALLOW; // 解锁
PieVectTable.EPWM1_INT = &EPWM_INT; // 将EPWM1中断绑定到自定义中断函数
EDIS; // 上锁
IER |= M_INT3; // 开启CPU第3组中断
PieCtrlRegs.PIEIER3.bit.INTx1 = 1; // 开启PIE3组第1路中断(EPWM1)
EINT; // 打开全局中断
ConfigEPwm(); // 配置EPWM模块时基、比较、中断等参数
while(1)
{
// 主循环空转,所有逻辑放在中断里面
}
}
PWM_Config() 配置要点(全部对应前面的原理):
CLKDIV、HSPCLKDIV配成 TBCLK = 75 MHz;- 计数模式设为增减计数;
TBPRD = 3750(得 10 kHz);- 只用
CMPA(CMPB用不到,注释掉即可); AQCTLA/AQCTLB:CTR=CMPA递增→高、CTR=CMPA递减→低;- 死区:A 上升沿延迟、B 下降沿延迟,
DBRED = DBFED = 75,并把 B 反相; - 使能 ePWM 中断,事件选
CTR = PRD。
注意时钟同步位:配置前会先禁止 ePWM 时钟(让所有 ePWM 模块用同一时刻的 TBCLK 同步),配置完成后再打开,PWM 才正式开始工作。
c
void ConfigEPwm()
{
EALLOW; // 解锁受保护寄存器,允许写入配置
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; // 禁止EPWM时钟同步,单独配置时基模块
EPwm1Regs.TBCTL.bit.CLKDIV=0; // 时基时钟分频:不分频
EPwm1Regs.TBCTL.bit.HSPCLKDIV=1; // 高速时基分频:二分频,TBCLK=150M/2=75M
EPwm1Regs.TBCTL.bit.CTRMODE=2; // 计数模式:增减计数(对称PWM)
EPwm1Regs.TBSTS.all=0; // 清零时基状态寄存器
EPwm1Regs.TBPHS.half.TBPHS=0; // 时基相位值设为0
EPwm1Regs.TBCTR=0; // 时基计数器清零
EPwm1Regs.TBPRD=3750; // 时基周期值,PWM频率10kHz
EPwm1Regs.CMPCTL.bit.SHDWAMODE=1; // CMPA比较寄存器:直接模式(立即生效)
EPwm1Regs.CMPCTL.bit.SHDWBMODE=1; // CMPB比较寄存器:直接模式(立即生效)
EPwm1Regs.CMPA.half.CMPA =1875; // CMPA初始值,占空比50%
//EPwm1Regs.CMPB=1875;
EPwm1Regs.AQCTLA.bit.CAU=2; // 增计数到CMPA时,EPWMA输出高电平
EPwm1Regs.AQCTLA.bit.CAD=1; // 减计数到CMPA时,EPWMA输出低电平
EPwm1Regs.AQCTLB.bit.CAU=2; // 增计数到CMPA时,EPWMB输出高电平
EPwm1Regs.AQCTLB.bit.CAD=1; // 减计数到CMPA时,EPWMB输出低电平
EPwm1Regs.AQSFR.all=0; // 清零动作限定软件强制寄存器
EPwm1Regs.AQCSFRC.all=0; // 清零连续软件强制寄存器
EPwm1Regs.DBCTL.bit.IN_MODE=2; // 死区输入模式:EPWMA上升沿延时,EPWMB下降沿延时
EPwm1Regs.DBCTL.bit.POLSEL=2; // 极性选择:EPWMB输出取反
EPwm1Regs.DBCTL.bit.OUT_MODE=3; // 死区输出模式:上升沿、下降沿死区都使能
EPwm1Regs.DBRED=75; // 上升沿延时75个TBCLK,死区1us
EPwm1Regs.DBFED=75; // 下降沿延时75个TBCLK,死区1us
EPwm1Regs.ETSEL.bit.INTSEL=1; // 中断触发事件:计数器等于TBPRD(周期点CTR=PRD)
EPwm1Regs.ETSEL.bit.INTEN=1; // 使能EPWM中断
EPwm1Regs.ETPS.bit.INTPRD=1; // 每发生1次事件,产生一次中断
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1; // 开启EPWM时钟同步,让时基时钟开始运行
EDIS; // 上锁,禁止修改受保护寄存器
}
ePWM 中断服务函数(ISR):
- 完成的功能:实时改变 EPWM1A / EPWM1B 的占空比;
- 退出外设中断必须做两件事 :
- 清除中断标志位;
- 响应同组(第 3 组)的中断(写
PIEACK)。
这两步是正确退出外设中断、保证下次还能进中断的前提。
c
interrupt void EPWM_INT(void)
{
EPwm1Regs.CMPA.half.CMPA = (1-Duty)*EPwm1Regs.TBPRD; // 更新比较寄存器,修改PWM占空比
EPwm1Regs.ETCLR.bit.INT = 1; // 清除EPWM事件中断标志位
PieCtrlRegs.PIEACK.all = PIEACK_GROUP3; // PIE应答,释放第3组中断,允许同组其他中断触发
}
八、编译、下载与示波器验证
工程编译通过,生成 .out 文件,下载到板子运行后,用示波器核对结果:
- 频率 :测得 PWM 频率 10 kHz,与设定一致;
- 波形 :通道 1 是 EPWM1A,通道 2 是 EPWM1B,二者互补输出;
- 死区 :上升沿延迟和下降沿延迟实测均为 1 µs,与分析一致;
- 中断:在 ISR 里打了断点,程序能停在断点,说明 ePWM 中断确实被触发、能正常进入中断函数。
整段效果与理论设想完全吻合。
九、小结与下集预告
这一集把前面学的 ePWM 各子模块串成了一个能跑的完整工程:
- GPIO 复用决定引脚功能;
- TB (时基)决定时钟与频率(TBPRD = 3750 → 10 kHz);
- AQ (动作限定)决定波形形状;
- DB (死区)把同相变互补并加 1 µs 死区;
- ET + PIE 把周期事件变成可响应的中断(INT3.1)。
下一集将进入 ADC 的采样模块,继续往「能采会控」的完整控制工程推进。