简介:TMS320F2812 DSP外部中断扩展应用资源包,面向开发者重点呈现XINT1、XINT2、XINT13可屏蔽中断与XNMI不可屏蔽中断的配置方法,以及通过CPLD将按键信号扩展为EXINT5中断的实现思路,对工业控制、电力电子等实时系统设计有直接参考价值。压缩包共80个文件、约235KB,以17个C源码、17个头文件为主体,并包含CCS工程文件(.pjt/.cmd)、编译输出(.out/.map/.obj)及调试日志等,便于直接打开工程对照学习。已有268人学习,适合熟悉基础外设但希望深入中断嵌套、外部触发与CPLD协同设计的入门及进阶开发者。通过阅读示例工程,可掌握中断使能与优先级配置、中断服务程序编写,以及CPLD外部扩展中断的完整处理流程,能有效缩短相关项目的底层驱动开发时间。
1. DSP2812 的 XINT2 外部中断入门:先分清引脚和使能链
从网上下到的 DSP2812-Ext_int 例程包解出来,烧进 F2812 之后最常见的结局有三种:灯完全不亮、只闪一次就再也不进、按一下按钮进好几次。前两种十有八九不是引脚接错,而是 2812 外部中断这套中断链没走通——GPIO 复用、XINT2CR 的使能位、PIE 通道、CPU 的 IER 和 INTM,任何一环断了,XINT2 都进不了中断服务函数;第三种则多半是触发极性和输入限定没设对。这篇文章把 TMS320F2812 的 XINT1/XINT2 外部中断从头捋到尾,直接落到寄存器配置和可编译的 C 代码上,适合刚把 2812 工程跑起来、正要接编码器 Z 相信号、霍尔传感器或者外部按键开关的工程师参考。
2. 从 GPIOB0 到 PIE:DSP2812 外部中断的硬管线与关键寄存器
2.1 信号从 GPIOB0 到 CPU 中断线经过哪几道门
TMS320F2812 上叫得上"外部中断"的输入有三路:XINT1、XINT2 和 XINT13(XNMI)。绝大多数工程只用前两个,XINT13 一般接 NMI 走不可屏蔽路径,当普通外部事件触发用会给自己找麻烦。XINT2 的输入引脚在 GPIOB0(数据手册里也叫 IOPB0),它和 ADCSOC 输出共用同一个封装管脚,所以软件第一件事是让 GPBMUX 的 bit0 选择外设功能而不是 GPIO。
从引脚到 CPU 中断线,信号依次穿过:输入缓冲与两拍同步器、可选的采样限定逻辑、XINT2 边沿检测器、PIE 第 1 组第 4 通道、CPU 的 INT1 引脚,最后被 INTM 位放行。这条链上有四个软件使能点:GPBMUX 的复用选择、XINT2CR 的 EN 位、PIEIER1.INTx4、IER 的 M_INT1。任何一级没打开,现象都一样——万用表能测到电平变化,GPIO 读引脚也有反应,但中断就是不进。
提示:281x 的 GPxMUX 位写 0 表示外设功能,写 1 才是 GPIO。这个定义和 C2000 后来新系列的 GPIO 寄存器恰好相反,从 28335 转过来的人最容易在这里翻车。
2.2 XINT2CR、XINT2CTR、PIEACK:三个决定中断行为的寄存器
XINT2 相关的外设寄存器不多,DSP281x 头文件里都集中在 XIntruptRegs 结构体中,PIE 相关的则在 PieCtrlRegs 里。先看这张表,后面代码就是对着这四样东西操作:
| 寄存器 | 地址 | 有效位 | 作用 |
|---|---|---|---|
| XINT2CR | 0x0000 7076 | bit0 POLARITY、bit1 EN | 触发沿选择与总使能 |
| XINT2CTR | 0x0000 7078 | 16 位只读计数 | 有效电平持续时间计数 |
| PIEIER1 | PIE 模块内 | bit INTx4 | 第 1 组第 4 通道使能 |
| PIEACK | PIE 模块内 | bit0 | 第 1 组中断应答 |
XINT2CR 只有两个有效位。bit0 是 POLARITY,写 0 表示下降沿触发,写 1 表示上升沿触发;bit1 是 EN,写 1 才把 XINT2 真正接入 PIE 仲裁。XINT2CTR 是 16 位只读计数器,引脚处于有效电平期间每个 SYSCLKOUT 时钟加 1,回到无效电平后自动清零。它不是普通定时器,不能靠写寄存器清零,它的价值集中在调试期:读出这个值,就能知道当前引脚是不是长时间停在有效电平上。
PIEACK 是 PIE 第 1 组的应答位。任何一个第 1 组中断被 CPU 响应后的下一个周期,硬件会把 PIEACK 的 bit0 置 1,此后第 1 组里所有通道的中断请求全部被 PIE 挡住,直到软件向 PIEACK 写入 1 清除。这个机制解释了"中断只进一次"的经典故障:服务函数里忘了清 PIEACK,第二脚永远进不来。XINT1、XINT2 都挂在第 1 组,所以这两个中断互相也会被 PIEACK 影响。
2.3 极性选错和电平型误触发怎么区分
外部中断是边沿触发的,2812 的 XINT 模块内部没有锁存整个电平的能力,它根据 POLARITY 位检测跳变并向 PIE 发一个请求脉冲。如果按键一端接 GPIOB0、另一端接地,平时引脚被上拉为高,按下变低,应该配下降沿;反过来按键接电源、引脚靠下拉保持低,那配上升沿。极性反了之后,边沿检测只在错误的那半周跳变上动作,表现出"按下去没反应、松开反而触发"。
调试时把 XINT2CTR 读出来看:如果它持续大于 0 并且在增长,说明引脚正长时间停留在有效电平,结合 POLARITY 反推信号逻辑,基本可以认定是极性配反而不是脉冲问题。如果 XINT2CTR 始终是 0,中断却进得来,说明来的信号是窄脉冲,宽度没有超过输入限定窗口,这一类问题放到第四章展开。
3. 最小可复现代码:DSP2812 XINT2 外部中断三级使能一次配齐
3.1 先搭好骨架:头文件、PIE 初始化和向量声明
常见做法是直接在 TI 官方 Example_281xExtInt 基础上改,这类例程包的工程结构基本一致:DSP281x_Device.h 定义外设寄存器结构体,DSP281x_Examples.h 提供 InitPieCtrl、InitPieVectTable 等函数声明。PIE 向量表落在数据 RAM 的 0x000D00 起始区域,由 DSP281x_GlobalVariableDefs.c 实例化,链接脚本不动,这部分不需要自己写。
#include "DSP281x_Device.h" #include "DSP281x_Examples.h" // InitPieCtrl / InitPieVectTable 声明 interrupt void XINT2_ISR(void); // 中断服务函数声明 volatile Uint16 Int2Flag = 0; // 置位给主循环,ISR 里不做业务 volatile Uint32 Int2Count = 0; // 中断次数统计 void main(void) { InitSysCtrl(); // 关看门狗、PLL 配到 150MHz 并等稳定 DINT; // 先关全局中断,配置期间不允许进中断 InitPieCtrl(); // PIE 控制寄存器复位到默认态,使能 PIE IER = 0x0000; // 清 CPU 级中断使能 IFR = 0x0000; // 清 CPU 级中断标志 InitPieVectTable(); // 把默认中断向量表装载到 RAM 0x000D00 ... }这段代码的逻辑顺序有讲究:InitPieCtrl 会打开 PIE 模块的总开关并清空 PIEACK,IER 和 IFR 清零保证没有旧的中断挂起,InitPieVectTable 把复位默认的服务函数全部装进 RAM 向量表。如果跳过 InitPieCtrl 直接写自己的向量,PIEIER 里那些残留值会让中断在一个无法预料的通道上乱跳。DINT 放在最前面的原因是,PIE 已经使能后,任何未初始化的中断源都可能触发,先关全局中断才能保证配置过程不被中断打断。
3.2 GPIOB0 复用成 XINT2 的完整写法
引脚方向必须设成输入,且把采样限定关掉。281x 的 GPxMUX 每一位对应一个引脚,写 0 是外设功能,写 1 是 GPIO;GPxDIR 写 0 是输入、写 1 是输出。GPBQUAL 是对应引脚的输入限定开关,外部中断建议直接用仅同步模式,避免采样窗口滤掉短脉冲。
EALLOW; // 受保护的寄存器写使能 GpioMuxRegs.GPBMUX.all &= 0xFFFE; // bit0 清 0:GPIOB0 走 XINT2 GpioMuxRegs.GPBDIR.bit.GPIOB0 = 0; // 0 = 输入方向 GpioMuxRegs.GPBCTRL.bit.QUALPRD0 = 0; // 采样周期置 0,窗口最窄 GpioMuxRegs.GPBQUAL.bit.QUAL0 = 0; // 0 = 仅同步,不做 3 采样限定 EDIS;这里用&=而不是整体赋值,是为了只改 bit0,保留其它 GPIO 引脚已经配置好的状态,很多例程包喜欢写GPBMUX.all = 0x0000,会把其它外设的复用位全部冲掉。GPBCTRL 的 QUALPRD0 管理 GPIOB0 到 GPIOB3 这一组,设 0 表示采样周期最短;GPBQUAL 的 QUAL0 才是针对单个引脚的开关,主从配合才能把限定彻底关掉。如果只清 GPBQUAL 不清 QUALPRD,采样窗口仍然存在,只是变短而已。
3.3 挂中断向量、开 PIE 和 IER 的正确顺序
向量表在 RAM 里,受 EALLOW 保护,必须先挂好向量再开各级使能。顺序一旦反过来,中断可能在向量还没指向你的函数时就到达,执行的是默认 ISR,表现为主循环看起来一切正常、但计数不动。
EALLOW; PieVectTable.XINT2 = &XINT2_ISR; // 覆盖默认向量,挂上自己的函数 EDIS; XIntruptRegs.XINT2CR.bit.POLARITY = 1; // 1 = 上升沿触发(0 为下降沿) XIntruptRegs.XINT2CR.bit.EN = 1; // 使能 XINT2 到 PIE 的通路 PieCtrlRegs.PIEIER1.bit.INTx4 = 1; // PIE 第 1 组第 4 通道 = XINT2 IER |= M_INT1; // CPU 级使能 INT1,即 PIE 第 1 组 EINT; // 打开全局中断开关,等效 CLRC INTMPIEIER1 的 INTx4 对应 XINT2,INTx3 才是 XINT1,两个通道写错位是例程移植时的高频错误。IER 用M_INT1而不是M_INT2,因为 XINT1/XINT2 都挂在 INT1 上,也就是 PIE 第 1 组的 CPU 入口,这正好解释了为什么这两个外中断会抢同一个 PIEACK。EINT 是宏,展开成asm(" CLRC INTM"),它必须在 XINT2CR 和 PIEIER1 之后,道理和 DINT 放最前一样:先保证环境完整,再放开总闸。
3.4 服务函数里 PIEACK 不能忘
ISR 内部第一原则是短。外部中断只负责置一个标志,具体业务放在主循环的轮询里处理,这样既减少中断时间,也避免在 ISR 里做协议解析时被新中断重入。唯一必须做的额外动作是清 PIEACK。
interrupt void XINT2_ISR(void) { Int2Flag = 1; // 只置标志,具体业务留给主循环 Int2Count++; // 32 位自增,稍后说明注意事项 PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;// 清第 1 组应答,否则不再进中断 }PIEACK_GROUP1 的值是 0x0001,写all而不是写bit是因为 PIEACK 各 bit 都是写 1 清除的语义,整体赋值最稳妥。清 PIEACK 的位置建议放在 ISR 最后一条,前面的操作是为了保证本次中断的处理不被打断。如果 ISR 里还有可能需要嵌套开启其它中断组,也要先清 PIEACK 再开 EINT,否则同组的后续中断会被封死。
4. DSP2812 外部中断不进的排查:限定、极性和共享变量
4.1 短脉冲被输入限定滤掉是最隐蔽的丢中断原因
把 2812 的外部中断接到一个输出宽度在 1 微秒量级的传感器上,发现完全没反应,但用示波器量引脚确实有跳变——这种情况九成是输入限定没关。281x 的 GPIO 输入路径默认允许做 3 采样限定,只有当信号在连续多个采样周期内保持稳定,内部逻辑才认为电平有效。脉冲宽度小于限定窗口时,边沿检测器根本没见到有效跳变,XINT2CR 的 EN 再开也没用。
配置采样限定就三个寄存器位,顺序很重要:
EALLOW; GpioMuxRegs.GPBCTRL.bit.QUALPRD0 = 0; // 采样周期最短,按 SYSCLKOUT 数 GpioMuxRegs.GPBQUAL.bit.QUAL0 = 0; // 0 = 仅同步;1 = 3 采样限定 EDIS;QUALPRD0 控制的是采样间隔,QUAL0 控制是否启用限定。只改其中一个都会留下隐患:QUAL0 保持 1 时,即使 QUALPRD0 为 0,信号依旧要走三采样逻辑,脉冲宽度要覆盖至少两个采样间隔才能通过;QUAL0 清 0 之后,输入只经过两级同步器,内部延迟大约 2 个 SYSCLKOUT,在 150MHz 主频下约 13ns,这个量级对外部事件来说基本可以忽略。
4.2 中断标志和主循环共享变量的处理
外部中断的 ISR 和主循环天然共享变量,最常见的问题有三个:忘记加 volatile、在 ISR 里做耗时操作、主循环读 32 位变量时被打断。volatile 的作用是让编译器每次访问都从内存读,而不是缓存在寄存器里,缺了它,主循环可能永远看到旧值。
volatile Uint16 Int2Flag = 0; volatile Uint32 Int2Count = 0; interrupt void XINT2_ISR(void) { Int2Flag = 1; Int2Count++; // 读取-自加-写回,非原子操作 } Uint32 ReadInt2Count(void) { Uint32 tmp; DINT; // 临时关全局中断,保证读完不被改写 tmp = Int2Count; EINT; return tmp; }C28x 对 32 位变量的自增不是单指令,中断执行到一半时主循环读到的高 16 位和低 16 位可能来自两次不同的自增结果。读 32 位计数时临时关一下全局中断,代价是几十个时钟,换来的是数据一致性。如果只是在主循环里查询 Int2Flag,16 位读写是单指令的,不用关中断,但一定要记得读完后立刻给它清零,否则会重复处理同一次触发。
4.3 从 rar 例程包移植时最容易踩的向量表坑
下载的 Ext_int 例程包解压后,工程里通常带一份 DSP281x_PieVect.c 和一份原有的 main.c。直接往里面加代码时,最常见的坑是重复调用 InitPieVectTable:第一次调用已经把默认向量装进 RAM,第二次调用会把你自己挂到 PieVectTable.XINT2 的地址重新覆盖回默认 ISR。另一个坑是工程里存在两份 DSP281x_GlobalVariableDefs.c,链接器报重复符号还好办,怕的是两份都编进去、PieVectTable 被分配到两个不同的 RAM 地址,挂向量挂到了一份,PIE 响应的是另一份。
遇到烧进去没反应,第一步不是在 main 里加打印,而是确认链接器映射文件里 PieVectTable 的地址。在 CCS 的 map 文件里搜 PieVectTable,地址应是 0x000D00。如果不是,检查是否漏了--ram_model选项,或者 cmd 文件里的 PIE_VECT 段被意外挪走。向量表地址不对,后面所有的使能配置都是白做。
4.4 用 XINT2CTR 做信号诊断的三步法
XINT2CTR 是只读计数器,引脚处于有效电平期间每个 SYSCLKOUT 加 1,无效时自动清零。拿它做诊断,可以在主循环里直接读寄存器,不需要示波器也能定位问题:
| 现象 | 计数器表现 | 结论 |
|---|---|---|
| 中断偶尔进 | 计数器始终为 0 | 脉冲太窄,输入限定把边沿滤掉了 |
| 中断完全不进 | 计数器一直增长 | 极性配反,或引脚被电平钳在有效态 |
| 按键按下有反应 | 按下期间计数,松开归零 | 硬件和软件都正常,问题在消抖策略 |
Uint16 cnt = XIntruptRegs.XINT2CTR; // 直接读,任意时刻都安全 if (cnt > 0) { // 引脚此刻处于有效电平,且已经持续 cnt 个 SYSCLKOUT }这个寄存器不要尝试在 ISR 里写它清零,它是只读的,而且边沿触发模式下中断到达时电平往往已经翻回无效态,计数器早就归零。正确的用法是放在主循环里轮询,用来判断引脚电平状态和脉冲形状,属于调试辅助手段,不参与正常业务逻辑。
5. 把 XINT2 外部中断延迟压到最小的寄存器技巧
5.1 延迟由三段构成,先知道能省哪段
从引脚边沿到 ISR 第一条指令执行,延迟分三段。第一段是输入路径:仅同步模式约 2 个 SYSCLKOUT,开了采样限定则是整个采样窗口,这段由 GPBQUAL 和 GPBCTRL 决定,是唯一能明显压缩的部分。第二段是 PIE 仲裁和 CPU 响应,包括组内优先级判定、流水线清空、INTM 判定,这部分是硬件行为,只能通过把 XINT2 放到高优先级通道来间接优化,在 281x 上 XINT1/XINT2 固定是 INT1.3 和 INT1.4,没有重映射空间。第三段是现场保护,编译器在进入 ISR 时压栈,与该中断无关,无法优化。
5.2 ISR 最短化和 PIEACK 位置的影响
ISR 里只做两件事:置 flag、清 PIEACK。PIEACK 清得越早,同组接下来的中断就能越早被响应,但这个"早"不能以牺牲当前 ISR 的完整性为代价。实践里我一般把 PIEACK 放在 ISR 最后一条,因为置 flag 是单周期操作,早一个周期清毫无意义。真正影响延迟的是 ISR 里别调用延时函数、别做长除法、别访问慢速外设,这些才是延迟和抖动的大头。
5.3 示波器验证:把中断延迟量出来
用一个测试 GPIO 把中断延迟变成波形,是验证上述配置是否生效的最终手段。选 GPIOA2 当测试口,ISR 入口拉高、出口拉低,示波器同时接 XINT2 输入和 GPIOA2,直接读两个上升沿的时间差。
EALLOW; GpioMuxRegs.GPAMUX.all &= 0xFFFB; // bit2 清 0:GPIOA2 用 GPIO 功能 GpioMuxRegs.GPADIR.bit.GPIOA2 = 1; // 1 = 输出 EDIS; interrupt void XINT2_ISR(void) { GpioDataRegs.GPASET.bit.GPIOA2 = 1; // 入口拉高,标记中断已进入 Int2Flag = 1; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; GpioDataRegs.GPACLEAR.bit.GPIOA2 = 1; // 出口拉低 }GPASET 和 GPACLEAR 是独立置位/清零寄存器,比读改写 GPADAT 更安全,不会破坏同端口其它引脚的电平。量出来的时间除以 6.67ns(150MHz 时钟周期),就能反推是几拍的开销。如果测出来的延迟明显大于理论值,回去查 GPBCTRL 的 QUALPRD0 是不是被初始化库改成了非 0,这是我在各种 2812 例程包里见过最多的隐藏配置项。
本文还有配套的精品资源,点击获取