大家好!在前面的教程中,我们的 main 函数总是在一个 while(1) 循环里忙碌地查询(轮询)按键状态。这种方式虽然可行,但 CPU 就像一个不停打电话问“你好了吗?”的人,既浪费了大量的计算资源,也无法保证响应的实时性。
今天,我们将学习一种革命性的工作方式——外部中断 (External Interrupt, XINT)。我们将教会 DSP 在没有事件发生时“休息”(执行主循环任务),只在外部引脚电平发生跳变(如按键被按下)的瞬间,才被唤醒去执行特定任务。这是构建高效、低功耗、高响应速度系统的核心技术。
一、硬件基础:外部中断的信号通路
[图1 & 图2: F28335 外部中断逻辑结构图]
DSP28335 提供了一个专门用来处理外部引脚电平变化的硬件中断系统。
-
7+1 个外部中断通道: F28335 共有 7 个可屏蔽外部中断 (XINT1 ~ XINT7) 和 1 个不可屏蔽外部中断 (XNMI),其中 XNMI 和 XINT13 共用中断源。
-
灵活的 GPIO 映射: 并非所有 GPIO 都能触发所有外部中断。它们的分工如下:
-
XINT1, XINT2: 只能由 GPIO0 ~ GPIO31 中的任意一个引脚来触发。
-
XINT3 ~ XINT7: 只能由 GPIO32 ~ GPIO63 中的任意一个引脚来触发。
-
XNMI/XINT13: 可由 GPIO0 ~ GPIO31 中的任意一个引幕脚来触发。
-
信号的完整旅程是:GPIO 引脚 -> GPIO 中断选择器 (GPIOXINTnSEL) -> XINTn 中断控制器 -> PIE 模块 -> C28x 内核。我们软件配置的核心,就是打通这条路径上的所有关卡。
二、核心寄存器:配置中断的“扳机”
[图3: 外部中断控制寄存器 XINTnCR 位定义]
每个外部中断通道(XINTn)都有一个对应的控制寄存器 XINTnCR,它有两大关键设置:
Polarity (位 3~2):触发极性
-
00 或 10:下降沿触发。非常适合按键按下(电平从高到低)的场景。
-
01:上升沿触发。
-
11:上升沿和下降沿均触发。
Enable (位 0):中断使能
-
0:禁止该外部中断通道。
-
1:使能该外部中断通道。这是打开 XINTn 模块自身功能的总开关。
三、软件配置八步法:让中断跑起来
配置一个外部中断,虽然涉及多个模块,但遵循一个清晰、固定的流程。下面我们以“配置 GPIO12 作为 XINT1 的触发源”为例,拆解这八个步骤。
(1) 初始化 PIE 模块
这是所有中断配置的起点。我们需要初始化 PIE 控制器和向量表,为中断系统搭建好基础框架。
InitPieCtrl(); // 初始化PIE控制器寄存器
IER = 0x0000; // 清零CPU级中断使能寄存器
IFR = 0x0000; // 清零CPU级中断标志寄存器
InitPieVectTable(); // 初始化PIE中断向量表,将其所有入口指向一个默认ISR
(2) 配置 GPIO 为输入模式
既然要接收外部信号,那么对应的 GPIO 引脚必须被配置为输入模式。
EALLOW;
// 使能GPIO时钟…
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 0; // 设为通用GPIO
GpioCtrlRegs.GPADIR.bit.GPIO12 = 0; // 设为输入
EDIS;
(3) 映射 GPIO 到中断线
告诉系统,XINT1 这条中断专线应该去“监听”哪个 GPIO 引脚。
EALLOW;
// XINT1 的源选择寄存器,.GPIOSEL 字段写入引脚号
GpioIntRegs.GPIOXINT1SEL.bit.GPIOSEL = 12; // XINT1 由 GPIO12 触发
EDIS;
(4) 注册中断服务函数 (ISR)
将我们自己编写的 ISR 函数地址,写入到 PIE 向量表中 XINT1 对应的位置。
EALLOW;
PieVectTable.XINT1 = &EXTI1_IRQn; // EXTI1_IRQn 是我们函数的函数名
EDIS;
(5) 使能 PIE 级中断
根据 PIE 中断向量表,XINT1 属于 PIE 第 1 组的第 4 个中断 (INT1.4)。我们需要打开这个通道。
PieCtrlRegs.PIEIER1.bit.INTx4 = 1; // 使能 PIE 组1 的 INT4
(6) 配置 XINTn 的触发方式并使能
设置 XINTnCR 寄存器,定义我们需要的触发边沿,并正式启用 XINT1 模块。
XIntruptRegs.XINT1CR.bit.POLARITY = 0; // 0: 下降沿触发
XIntruptRegs.XINT1CR.bit.ENABLE = 1; // 1: 使能 XINT1
(7) 使能 CPU 级中断
打开 CPU 级的总开关。因为 XINT1 最终汇集到 CPU 的 INT1 总线上,所以我们要使能 INT1,并打开全局中断。
IER |= M_INT1; // 使能CPU的INT1
EINT; // 使能全局中断(Clear INTM bit)
ERTM; // 使能调试事件
(8) 编写中断服务函数 (ISR)
这是中断发生后,CPU 最终要执行的代码。
// 使用 'interrupt' 关键字声明这是一个中断服务函数
interrupt void EXTI1_IRQn(void)
{
// … 在这里编写处理事件的代码 …
// !! 关键:向 PIE 发送“确认”信号,表示中断已处理
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}
PIEACK 寄存器非常重要,它告诉 PIE 控制器,该组的中断已经得到响应,可以清除标志,以便下一次中断能正常触发。
四、代码实战:按键触发 LED 翻转
下面我们通过完整的代码,实现用 K1 键 (连接 GPIO12) 触发 XINT1 中断来翻转 LED2,用另一个按键 (连接 GPIO13) 触发 XINT2 中断来翻转 LED3。
1. exti.h (头文件)
#ifndef EXTI_H_
#define EXTI_H_
#include "DSP2833x_Device.h" // DSP2833x 头文件
#include "DSP2833x_Examples.h" // DSP2833x 例子相关头文件
void EXTI1_Init(void);
interrupt void EXTI1_IRQn(void);
void EXTI2_Init(void);
interrupt void EXTI2_IRQn(void);
#endif /* EXTI_H_ */
2. exti.c (源文件)
#include "exti.h"
#include "leds.h"
#include "key.h"
void EXTI1_Init(void)
{
EALLOW;
SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1; // GPIO input clock
EDIS;
EALLOW;
//KEY端口配置
GpioCtrlRegs.GPAMUX1.bit.GPIO12=0;
GpioCtrlRegs.GPADIR.bit.GPIO12=0;
GpioCtrlRegs.GPAPUD.bit.GPIO12=0;
GpioCtrlRegs.GPAQSEL1.bit.GPIO12 = 0; // 外部中断1(XINT1)与系统时钟SYSCLKOUT同步
GpioCtrlRegs.GPBMUX2.bit.GPIO48=0;
GpioCtrlRegs.GPBDIR.bit.GPIO48=1;
GpioCtrlRegs.GPBPUD.bit.GPIO48=0;
GpioDataRegs.GPBCLEAR.bit.GPIO48=1;
EDIS;
EALLOW;
GpioIntRegs.GPIOXINT1SEL.bit.GPIOSEL = 12; // XINT1是GPIO12
EDIS;
EALLOW;// 修改被保护的寄存器,修改前应添加EALLOW语句
PieVectTable.XINT1 = &EXTI1_IRQn;
EDIS; // EDIS的意思是不允许修改被保护的寄存器
PieCtrlRegs.PIEIER1.bit.INTx4 = 1; // 使能PIE组1的INT4
XIntruptRegs.XINT1CR.bit.POLARITY = 0; // 下降沿触发中断
XIntruptRegs.XINT1CR.bit.ENABLE= 1; // 使能XINT1
IER |= M_INT1; // 使能CPU中断1(INT1)
EINT; // 开全局中断
ERTM;
}
interrupt void EXTI1_IRQn(void)
{
Uint32 i;
for(i=0;i<10000;i++); //键盘消抖动
while(!KEY_H1);
LED2_TOGGLE;
PieCtrlRegs.PIEACK.bit.ACK1=1;
}
void EXTI2_Init(void)
{
EALLOW;
SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1; // GPIO input clock
EDIS;
EALLOW;
//KEY端口配置
GpioCtrlRegs.GPAMUX1.bit.GPIO13=0;
GpioCtrlRegs.GPADIR.bit.GPIO13=0;
GpioCtrlRegs.GPAPUD.bit.GPIO13=0;
GpioCtrlRegs.GPAQSEL1.bit.GPIO13 = 2; // 外部中断2(XINT2)输入限定6个采样窗口
GpioCtrlRegs.GPACTRL.bit.QUALPRD1 = 0xFF; // 每个采样窗口的周期为510*SYSCLKOUT
GpioCtrlRegs.GPBMUX2.bit.GPIO48=0;
GpioCtrlRegs.GPBDIR.bit.GPIO48=1;
GpioCtrlRegs.GPBPUD.bit.GPIO48=0;
GpioDataRegs.GPBCLEAR.bit.GPIO48=1;
EDIS;
EALLOW;
GpioIntRegs.GPIOXINT2SEL.bit.GPIOSEL = 13; // XINT2是GPIO13
EDIS;
EALLOW;// 修改被保护的寄存器,修改前应添加EALLOW语句
PieVectTable.XINT2 = &EXTI2_IRQn;
EDIS; // EDIS的意思是不允许修改被保护的寄存器
PieCtrlRegs.PIEIER1.bit.INTx5 = 1; // 使能PIE组1的INT5
XIntruptRegs.XINT2CR.bit.POLARITY = 0; // 下降沿触发中断
XIntruptRegs.XINT2CR.bit.ENABLE = 1; // 使能XINT2
IER |= M_INT1; // 使能CPU中断1(INT1)
EINT; // 开全局中断
ERTM;
}
interrupt void EXTI2_IRQn(void)
{
Uint32 i;
for(i=0;i<10000;i++); //键盘消抖动
while(!KEY_H2);
LED3_TOGGLE;
PieCtrlRegs.PIEACK.bit.ACK1=1;
}
3. mian.c (源文件)
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "DSP2833x_Examples.h" // DSP2833x Examples Include File
#include "leds.h"
#include "exti.h"
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
int i=0;
InitSysCtrl();
InitPieCtrl();
IER = 0x0000;
IFR = 0x0000;
InitPieVectTable();
LED_Init();
EXTI1_Init();
EXTI2_Init();
while(1)
{
i++;
if(i%2000==0)
{
LED1_TOGGLE;
}
DELAY_US(100);
}
}
可以看到,主循环 while(1) 里不再有 KEY_Scan() 函数了。CPU 只是在悠闲地闪烁着 LED1,完全不用关心按键何时按下。一旦按键按下,硬件会自动打断 CPU,去执行对应的 ISR,执行完毕后再无缝返回,这正是中断的魅力所在。
总结
通过本次实战,我们不仅将中断理论落地,还掌握了配置和使用外部中断的全流程。你现在应该深刻理解了:
-
外部中断信号从 GPIO 到 CPU 的完整硬件路径。
-
软件配置外部中断的 8 大关键步骤,缺一不可。
-
如何编写一个规范的中断服务函数,尤其是 PIEACK 的重要性。
-
中断驱动的编程思想,如何将 CPU 从繁忙的轮询中解放出来。
外部中断是 DSP 与外部世界实时交互的桥梁。掌握了它,你就拥有了构建复杂、高效、可靠嵌入式系统的关键能力。
评论前必须登录!
注册