第四章 GPIO与基本外设:从寄存器到中断响应

各位同学,欢迎来到第四章。这一章我们聊聊DSP最基础、也最常用的外设——GPIO。说白了,就是那些能输出高低电平、能读取外部信号的引脚。你想想看,任何一块控制板,总得跟外界打交道吧?LED亮不亮、按键按没按、外部信号来了没有……这些全得靠GPIO。

我个人习惯,拿到一块新DSP芯片,第一件事就是先把GPIO调通。为什么?因为这是最直接的“芯片活着”的证据。灯亮了,心就定了。

4.1 GPIO寄存器配置——别小看这步

DSP的GPIO,不像单片机那么简单。它通常有多个功能复用。比如一个引脚,既能当普通IO,又能当PWM输出,还能当CAP捕获输入。所以,第一步就是配置引脚功能。

以TI的C2000系列为例,GPIO配置主要涉及三个寄存器组:

  • GPxMUX:功能选择寄存器。0是普通IO,1或2是外设功能。
  • GPxDIR:方向寄存器。0是输入,1是输出。
  • GPxDAT:数据寄存器。读引脚电平或写输出电平。

嗯,这里要注意:配置顺序很重要。我建议先配MUX,再配DIR,最后操作DAT。为什么?因为如果先配DIR为输出,但MUX还没切到IO模式,引脚可能被外设驱动,产生意外电平。

核心要点: 配置GPIO时,务必先锁定引脚功能(MUX),再设定方向(DIR)。这是我在项目中吃过亏才记住的。

来看一个实际代码片段。假设我们要把GPIO0配置为输出,驱动一个LED:

// 配置GPIO0为普通IO输出
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0;   // 设置为普通IO
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;    // 设置为输出方向
GpioDataRegs.GPADAT.bit.GPIO0 = 1;    // 输出高电平,点亮LED

你看,三行代码,清清楚楚。但实际项目中,我见过有人把顺序写反了,结果LED死活不亮,查了半天才发现是MUX没配对。

4.2 LED与按键驱动——实战入门

LED驱动,说白了就是控制GPIO的高低电平。但这里有个小坑:LED的极性。有的板子LED是低电平点亮,有的是高电平点亮。我曾经接手过一个项目,前任工程师把极性搞反了,灯在待机时一直亮着,功耗超标。

所以,写驱动前,先看原理图。确认LED是接VCC还是GND。

按键驱动稍微复杂点。因为机械按键有抖动。你按下去,电平会跳好几下。如果不做消抖,一次按键可能被误判成十次。

我常用的消抖方法有两种:

方法原理适用场景
硬件消抖RC滤波电路对成本不敏感、要求高可靠性
软件消抖延时10-20ms后再次读取大多数嵌入式项目

我个人偏爱软件消抖,因为省成本、改起来方便。来看一个典型的按键扫描代码:

// 按键扫描函数,返回1表示按下
Uint16 ReadKey(void)
{
    if(GpioDataRegs.GPADAT.bit.GPIO1 == 0)  // 检测到低电平(假设按键按下为低)
    {
        DELAY_US(10000);  // 延时10ms
        if(GpioDataRegs.GPADAT.bit.GPIO1 == 0)  // 再次确认
        {
            while(GpioDataRegs.GPADAT.bit.GPIO1 == 0);  // 等待按键释放
            return 1;  // 确认按下
        }
    }
    return 0;
}

小技巧: 延时函数DELAY_US()的精度取决于系统时钟。我习惯用定时器来做精确延时,而不是空循环。空循环在优化等级不同时,时间会变。

4.3 外部中断(XINT)配置与响应——别让CPU空等

前面讲的按键扫描,CPU得一直轮询。这太浪费了。你想想看,CPU主频几百兆,按键按下去才几毫秒一次,轮询期间CPU啥正事都干不了。

所以,真正实用的做法是用外部中断。按键一按,CPU立刻响应。平时CPU该干嘛干嘛。

DSP的外部中断配置,主要分三步:

  1. 配置GPIO为中断输入:设置MUX为IO模式,DIR为输入。
  2. 选择中断触发方式:上升沿、下降沿、或者双边沿。
  3. 编写中断服务函数(ISR):响应中断后要做什么。

我曾经遇到过一个坑:中断触发方式选错了。项目要求按键按下时触发,我配成了上升沿触发。结果按键按下是低电平,松开才是上升沿。每次松开按键才触发,逻辑完全反了。

来看一个标准的外部中断配置代码:

// 配置GPIO12为外部中断输入(下降沿触发)
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 0;   // 普通IO
GpioCtrlRegs.GPADIR.bit.GPIO12 = 0;    // 输入方向

// 配置外部中断XINT1
XIntruptRegs.XINT1CR.bit.POLARITY = 0; // 下降沿触发(0=下降沿,1=上升沿)
XIntruptRegs.XINT1CR.bit.ENABLE = 1;   // 使能中断

// 使能PIE中断(以C2000为例)
PieCtrlRegs.PIEIER1.bit.INTx4 = 1;     // XINT1对应PIE组1的第4个中断
IER |= M_INT1;                          // 使能CPU中断组1
EINT;                                   // 全局中断使能

警告: 中断服务函数一定要短!短!短!不要在ISR里做延时、打印、复杂计算。我见过有人把串口打印放在中断里,结果系统卡死。正确的做法是:ISR里只置一个标志位,主循环里处理具体逻辑。

中断服务函数的典型写法:

// 外部中断1服务函数
interrupt void XINT1_ISR(void)
{
    KeyPressFlag = 1;  // 置标志位,告诉主循环按键被按下
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;  // 清除中断应答
}

主循环里再根据标志位做具体处理:

void main(void)
{
    // 初始化代码...
    while(1)
    {
        if(KeyPressFlag)
        {
            KeyPressFlag = 0;  // 清除标志
            // 处理按键逻辑,比如切换LED状态
            GpioDataRegs.GPADAT.bit.GPIO0 ^= 1;  // 翻转LED
        }
        // 其他任务...
    }
}

4.4 避坑指南——我踩过的那些雷

做GPIO和中断开发,有几个坑我印象特别深:

  • 引脚复用冲突:同一个引脚被两个外设同时配置了。查原理图,确认引脚独占。
  • 中断优先级:多个中断同时触发时,高优先级的中断会打断低优先级的。如果处理不当,低优先级中断可能永远得不到响应。
  • 中断标志位未清除:ISR里忘了清中断标志,会导致反复进入中断。系统直接卡死。

嗯,这些坑我当年都踩过。特别是中断标志位那个,查了整整一天才找到原因。从那以后,我写ISR的第一件事就是清标志。

4.5 小结

这一章我们聊了GPIO的寄存器配置、LED和按键的驱动方法、以及外部中断的使用。说白了,这些都是DSP开发的基本功。你把这些玩熟了,后面的PWM、ADC、通信接口,上手会快很多。

下一章我们讲PWM模块。那可是控制电机、驱动负载的核心。到时候我会分享一个我做太阳翼驱动时遇到的PWM死区问题,挺有意思的。

好,今天就到这里。动手试试吧,把板子上的LED点亮,把按键中断调通。有问题随时交流。