4、GPIO基础编程:GPIO模块初始化、输入输出模式配置、中断触发与回调函数,实战:按键控制LED

好,咱们今天聊聊GPIO。这玩意儿可以说是嵌入式开发里最基础、也最常用的外设了。你想想看,一个芯片要跟外界打交道,总得有个“手脚”吧?GPIO就是干这个的。

我个人习惯,拿到一个新开发板,第一件事就是点灯。别笑,这真不是闹着玩的。点灯能帮你快速验证硬件连接、开发环境、下载调试链路是不是通的。要是灯都不亮,后面写再多代码也是白搭。

4.1 GPIO模块初始化

在TI的无线连接芯片上,GPIO的初始化其实挺讲究的。不像有些单片机,上电默认就是普通IO。TI的芯片,很多引脚是复用的——它可能同时是GPIO、也可能是I2C、SPI、UART,甚至是RF相关的引脚。

所以第一步,你得告诉芯片:“嘿,这个脚,我要当普通IO用。”

初始化流程大致分三步:

  1. 使能GPIO模块时钟——没电怎么干活?
  2. 配置引脚功能为GPIO——从复用功能里“抢”过来。
  3. 设置方向(输入/输出)——你是要听,还是要说?

代码大概长这样:

// 使能GPIO时钟
PRCMGPIOClockEnable();

// 配置引脚为GPIO功能
PinTypeGPIO(PIN_LED, PIN_MODE_0);

// 设置方向为输出
GPIODirModeSet(GPIOA_BASE, PIN_LED, GPIO_DIR_MODE_OUT);

嗯,这里要注意:PinTypeGPIO这个函数,不同型号的TI芯片名字可能不一样。有的叫IOCPinTypeGPIO,有的直接操作寄存器。我建议你养成好习惯,每次都用TI的驱动库函数,别自己手撕寄存器——除非你特别清楚自己在干什么。

警告: 千万别忘了使能时钟!我见过太多新手,代码写了一大堆,引脚就是不反应。查了半天,原来是时钟没开。这就像你给电灯开关接好了线,但总闸没合上——灯能亮才怪。

4.2 输入输出模式配置

GPIO的方向配置,说白了就是决定这个脚是“说话”还是“听话”。

输出模式:芯片往外送电平。高电平就是3.3V(或者1.8V,看你的VDD),低电平就是GND。用来控制LED、继电器、蜂鸣器这些。

输入模式:芯片读取外部电平。比如检测按键有没有按下、传感器输出是高是低。

配置代码很简单:

// 配置为输出
GPIODirModeSet(GPIOA_BASE, PIN_LED, GPIO_DIR_MODE_OUT);

// 配置为输入
GPIODirModeSet(GPIOA_BASE, PIN_KEY, GPIO_DIR_MODE_IN);

但这里有个坑——上拉和下拉。输入模式下,如果外部引脚悬空(什么都没接),电平是不确定的。你读到的值可能是0,也可能是1,完全看心情。

解决办法?加个上拉电阻或者下拉电阻。TI的芯片内部一般都集成了这个功能,不用你外接。

// 使能内部上拉
GPIOPadConfigSet(GPIOA_BASE, PIN_KEY, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);

我曾经在一个项目里,按键没配置上拉,结果按键没按下时,程序老是误触发。折腾了两天,最后发现是内部上拉没使能。你说冤不冤?

配置选项 说明 适用场景
GPIO_PIN_TYPE_STD 标准推挽输出 / 高阻输入 输出驱动LED,输入有外部上拉
GPIO_PIN_TYPE_STD_WPU 标准输出 / 带上拉输入 按键输入(最常用)
GPIO_PIN_TYPE_STD_WPD 标准输出 / 带下拉输入 某些传感器,默认低电平
GPIO_PIN_TYPE_OD 开漏输出 I2C总线、电平转换

4.3 中断触发与回调函数

轮询读按键?可以,但太浪费CPU了。你想想看,CPU一直在那问:“按键按了吗?按了吗?按了吗?”——啥正事都干不了。

更好的办法是中断。按键按下那一刻,硬件自动通知CPU:“嘿,别忙了,这边有事!”

TI芯片的GPIO中断配置,一般分这几步:

  1. 配置中断触发方式——上升沿、下降沿、还是双边沿?
  2. 注册中断服务函数——也就是回调函数,告诉CPU中断来了该干啥。
  3. 使能中断——打开总开关。

代码示例:

// 配置下降沿触发(按键按下时,电平从高变低)
GPIOIntTypeSet(GPIOA_BASE, PIN_KEY, GPIO_FALLING_EDGE);

// 注册中断回调函数
GPIOIntRegister(GPIOA_BASE, PIN_KEY, KeyPressCallback);

// 使能中断
GPIOIntEnable(GPIOA_BASE, PIN_KEY);

回调函数长这样:

void KeyPressCallback(void)
{
    // 清除中断标志——这一步千万别忘!
    GPIOIntClear(GPIOA_BASE, PIN_KEY);

    // 处理按键逻辑
    // 比如翻转LED
    GPIOPinWrite(GPIOA_BASE, PIN_LED, 
                 ~GPIOPinRead(GPIOA_BASE, PIN_LED));
}
重点: 中断服务函数里,第一件事就是清除中断标志。否则CPU会以为中断一直没处理完,然后不停地进中断——这叫“中断风暴”,系统直接卡死。我早期做项目时就吃过这个亏,当时还以为是芯片坏了。

另外,中断回调函数里不要做耗时操作。比如别在里面加延时、别做复杂的计算。原则是:快进快出。你可以在回调里设一个标志位,然后在主循环里处理具体逻辑。

4.4 实战:按键控制LED

好,理论说完了,咱们来点实际的。写一个完整的例子:按一下按键,LED状态翻转。

硬件连接:

  • LED接在PA0(输出)
  • 按键接在PA1(输入,内部上拉)

完整代码:

#include <ti/drivers/GPIO.h>
#include <ti/drivers/Power.h>

// 引脚定义
#define PIN_LED   0
#define PIN_KEY   1

// 全局标志
volatile uint8_t g_keyPressed = 0;

// 按键中断回调
void KeyPressCallback(uint_least8_t index)
{
    // 清除中断
    GPIOIntClear(PIN_KEY);
    // 设标志
    g_keyPressed = 1;
}

void main(void)
{
    // 初始化
    Power_init();
    GPIO_init();

    // 配置LED为输出,初始低电平
    GPIO_setConfig(PIN_LED, GPIO_CFG_OUTPUT);
    GPIO_write(PIN_LED, 0);

    // 配置按键为输入,内部上拉,下降沿中断
    GPIO_setConfig(PIN_KEY, GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_FALLING);
    GPIO_setCallback(PIN_KEY, KeyPressCallback);
    GPIO_enableInt(PIN_KEY);

    while(1)
    {
        if(g_keyPressed)
        {
            g_keyPressed = 0;

            // 翻转LED
            uint8_t state = GPIO_read(PIN_LED);
            GPIO_write(PIN_LED, !state);
        }

        // 其他任务...
        // 休眠或低功耗模式
        Power_sleep();
    }
}
小技巧: 实际项目中,按键会有“抖动”。机械按键按下和松开时,电平会跳变好几次。如果你不做消抖,一次按键可能会触发多次中断。最简单的消抖方法:在中断里加一个10~20ms的延时,或者用定时器做软件消抖。我一般用后者,因为延时太浪费CPU了。

好了,这一章的内容就这些。GPIO看似简单,但它是所有外设的基础。你把这个玩熟了,后面学I2C、SPI、UART都会轻松很多。下一章咱们聊聊定时器——那个东西更有意思。