4. GPIO进阶:从按键消抖到中断响应
各位同学,欢迎来到GPIO的进阶篇。上一章我们学会了怎么让LED灯亮起来,说白了就是GPIO输出。但DSP如果只能输出,那跟一个普通灯泡有什么区别?真正有意思的是输入——让芯片感知外部世界的变化。
这一章,我会带着大家搞定三个核心技能:按键输入检测、按键消抖处理、GPIO中断配置。这三板斧学会了,你的C2000就算是真正入门了。
4.1 GPIO输入模式配置
先说说输入模式。其实配置输入比输出还简单——你只需要告诉DSP:「这个引脚我要用来读数据,不是写数据」。
我个人习惯把GPIO输入配置写成这样:
// 配置GPIO12为输入模式
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 0; // 设置为普通GPIO
GpioCtrlRegs.GPADIR.bit.GPIO12 = 0; // 0=输入,1=输出
GpioCtrlRegs.GPAQSEL1.bit.GPIO12 = 0; // 采样方式,0=仅同步,3=6次采样
这里有个细节我想强调一下:GPAQSEL这个寄存器。它控制输入信号的采样方式。我在项目中遇到过一个问题——按键明明按下了,程序就是检测不到。后来发现是采样次数设得太高,按键按下时间太短,DSP还没来得及采样就错过了。
4.2 按键消抖原理与实现
说到按键,就不得不提消抖。你想想看,一个机械按键按下时,金属触点会弹跳几次才稳定。这个弹跳时间大概5-20ms。如果不做处理,DSP可能会认为你按了十几次。
消抖说白了就两种方法:硬件消抖和软件消抖。
硬件消抖简单粗暴——加个RC滤波电路。但咱们做嵌入式开发的,能省则省,一个电阻一个电容也是成本啊。所以我一般用软件消抖。
软件消抖的核心思想就一句话:等信号稳定了再读取。具体实现有两种:
- 延时消抖:检测到电平变化后,延时20ms再读一次
- 连续采样消抖:连续读取N次,如果结果一致才认为有效
我个人更推荐第二种,因为它不会阻塞程序运行。来看代码:
// 连续采样消抖实现
#define DEBOUNCE_COUNT 5 // 连续采样5次
Uint16 ReadKeyDebounced(void)
{
Uint16 keyState = 0;
Uint16 stableCount = 0;
Uint16 i;
for(i = 0; i < DEBOUNCE_COUNT; i++)
{
if(GpioDataRegs.GPADAT.bit.GPIO12 == 0) // 按键按下(低电平有效)
{
stableCount++;
}
DELAY_US(1); // 每次采样间隔1us
}
if(stableCount == DEBOUNCE_COUNT)
{
keyState = 1; // 确认按下
}
return keyState;
}
for(i=0;i<100000;i++)这种空循环。结果按键响应慢得像蜗牛。后来改用定时器或者SysTick来做延时,程序流畅多了。
4.3 GPIO中断配置
好了,按键检测学会了。但有个问题——你总不能一直轮询按键吧?DSP还要干别的活呢。这时候就需要中断出场了。
中断的原理很简单:让硬件主动通知CPU,而不是CPU去轮询硬件。C2000的GPIO中断支持上升沿触发、下降沿触发、或者两者都触发。
配置GPIO中断的步骤,我总结为四步走:
- 配置GPIO为输入(刚才已经做了)
- 选择中断触发方式(上升沿/下降沿)
- 使能GPIO中断(打开对应的中断屏蔽位)
- 编写中断服务函数(ISR)
来看具体代码:
// 配置GPIO12为下降沿触发中断
// 步骤1:GPIO输入配置(同上,略)
// 步骤2:选择触发方式
GpioCtrlRegs.GPIOINTPRIMSEL.bit.GPIO12 = 0; // 0=使用GPIOINT1A
GpioIntRegs.GPIOINT1SEL.bit.GPIO12 = 12; // 选择GPIO12作为中断源
// 步骤3:配置触发边沿
GpioCtrlRegs.GPIOINTPOL.bit.GPIO12 = 0; // 0=下降沿触发,1=上升沿触发
// 步骤4:使能中断
GpioIntRegs.GPIOINT1EN.bit.GPIO12 = 1; // 使能GPIO12中断
PieCtrlRegs.PIEIER1.bit.INTx1 = 1; // 使能PIE第1组第1个中断
IER |= M_INT1; // 使能CPU中断1
这里有个容易搞混的地方——GPIO中断号和PIE中断号的关系。C2000的GPIO中断先映射到GPIOINT1~GPIOINT6这6个中断线上,然后再映射到PIE的对应组。说白了就是两级映射。
核心要点:GPIO12的下降沿触发中断,最终会触发PIE第1组第1个中断(INT1.1)。这个对应关系在TI的手册里有详细表格,我建议你打印出来贴在工位上。
4.4 中断服务函数编写
中断配置好了,最后一步就是写中断服务函数。这个函数就是中断发生时要执行的代码。
写ISR有几个铁律:
- 快进快出——不要在ISR里做复杂运算
- 清除中断标志——否则会一直进中断
- 保护现场——保存和恢复用到的寄存器
来看一个完整的按键中断服务函数:
// 中断服务函数
interrupt void GPIO_ISR(void)
{
// 清除中断标志
GpioIntRegs.GPIOINT1CLR.bit.GPIO12 = 1;
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
// 读取按键状态(已经消抖过的)
if(ReadKeyDebounced() == 1)
{
// 按键按下,执行对应操作
GpioDataRegs.GPATOGGLE.bit.GPIO0 = 1; // 翻转LED
}
}
嗯,这里要注意一点:中断服务函数里不要做延时消抖。为什么?因为中断讲究的就是快。你想想看,如果在ISR里延时20ms,其他中断就得排队等着,系统实时性就没了。
我的做法是:中断里只设置一个标志位,主循环里检测这个标志位再做消抖处理。这样既利用了中断的实时性,又避免了阻塞。
4.5 本章知识体系
说了这么多,我画了一张图帮你理清思路:
这张图把本章的三个核心内容串起来了。你从左到右看:先配好输入模式,再搞定按键消抖,最后用中断来提升效率。每一步都是下一步的基础。
4.6 避坑指南
最后,我把自己踩过的坑总结一下,你遇到了可以直接拿来用:
| 常见问题 | 现象 | 解决方案 |
|---|---|---|
| 中断触发多次 | 按一次按键,LED闪了好几下 | 检查是否在ISR里清除了中断标志 |
| 按键不响应 | 按了没反应 | 检查GPIO方向寄存器是否设为了输入 |
| 程序卡死 | 进中断后出不来 | 检查PIEACK是否清除,以及中断优先级配置 |
| 误触发 | 没按键也会进中断 | 增加硬件滤波或提高采样次数 |
我曾经在一个项目里,因为忘了清除PIEACK,导致中断只触发了一次就再也不响应了。查了两天才找到原因。所以你现在看到我写中断代码,第一件事就是清标志——这已经成肌肉记忆了。
好了,这一章的内容就到这里。GPIO输入和中断是嵌入式开发的基石,你花时间把它吃透了,后面学PWM、ADC、CAN这些外设会轻松很多。动手试试吧,把代码烧进去,按按键看看LED会不会听话地翻转。
公众号:蓝海资料掘金营,微信deep3321