3. GPIO驱动开发:从管脚配置到中断处理

GPIO,说白了就是芯片的“手脚”。传感器要跟主控通信,第一步往往就是通过GPIO来传递状态信号。比如一个简单的按键传感器,按下时电平变化,GPIO检测到跳变,系统就知道该干活了。

我在鸿蒙南向开发中,GPIO驱动这块踩过的坑不少。今天咱们就把它拆开揉碎了讲清楚——从控制器驱动、管脚配置,到上下拉、驱动能力,再到中断注册与处理。

3.1 GPIO控制器驱动:先让芯片“认识”管脚

每个芯片内部都有一个或多个GPIO控制器。控制器驱动的作用,就是把这些硬件寄存器暴露给上层。鸿蒙的HDF(硬件驱动框架)里,GPIO控制器驱动需要实现标准的操作接口。

核心接口清单:

  • GpioSetDir — 设置输入/输出方向
  • GpioGetDir — 读取当前方向
  • GpioWrite — 输出高低电平
  • GpioRead — 读取输入电平
  • GpioSetIrq — 配置中断触发方式
  • GpioUnsetIrq — 注销中断

我习惯先把控制器基地址、管脚总数这些参数写在设备树或HCS配置里。这样换芯片时,改配置就行,不用动代码。

// 伪代码:GPIO控制器驱动注册
static struct GpioMethod g_gpioMethod = {
    .setDir = HiGpioSetDir,
    .getDir = HiGpioGetDir,
    .write = HiGpioWrite,
    .read = HiGpioRead,
    .setIrq = HiGpioSetIrq,
    .unsetIrq = HiGpioUnsetIrq,
};

int32_t HiGpioBind(struct HdfDeviceObject *device) {
    // 绑定方法表
    return HDF_SUCCESS;
}

我的经验: 注册控制器时,记得检查管脚号是否越界。我曾经遇到过上层传了个-1进来,直接写寄存器导致系统挂死。加个边界判断,省心很多。

3.2 GPIO管脚配置:方向、模式与复用

管脚配置是GPIO开发里最琐碎的部分。一个管脚可能同时是GPIO、UART TX、I2C SCL。你得告诉芯片:我要用哪个功能。

配置流程一般分三步:

  1. 设置复用功能 — 把管脚切换到GPIO模式
  2. 设置方向 — 输入还是输出
  3. 设置初始电平 — 输出时确定默认高/低
// 示例:配置GPIO3_5为输出,初始高电平
int32_t GpioConfigOutput(uint16_t gpio, uint8_t initVal) {
    // 1. 复用配置(寄存器地址因芯片而异)
    GpioSetMux(gpio, GPIO_MUX_GPIO);
    
    // 2. 设置方向为输出
    GpioSetDir(gpio, GPIO_DIR_OUT);
    
    // 3. 设置初始电平
    GpioWrite(gpio, initVal);
    
    return HDF_SUCCESS;
}

注意: 复用配置必须在设置方向之前完成。顺序反了,管脚可能还停留在外设模式,写电平根本写不进去。我调试一个I2C转GPIO的传感器时,卡了整整半天,最后发现是复用没先配好。

3.3 上下拉与驱动能力设置

上下拉电阻,很多人觉得不重要。其实恰恰相反——传感器信号不稳,十有八九是上下拉没配对。

鸿蒙的GPIO驱动里,上下拉配置通常通过 GpioSetPull 接口实现。驱动能力则通过 GpioSetDriveStrength 来设置。

配置项 可选值 典型场景
上拉 PULL_UP / PULL_NONE 按键输入、开漏输出
下拉 PULL_DOWN / PULL_NONE 中断输入(低电平触发)
驱动能力 0~3 或 0~7(芯片相关) LED驱动用高驱动,普通信号用低驱动
// 示例:配置上拉输入
int32_t GpioConfigInputPullUp(uint16_t gpio) {
    GpioSetMux(gpio, GPIO_MUX_GPIO);
    GpioSetDir(gpio, GPIO_DIR_IN);
    GpioSetPull(gpio, GPIO_PULL_UP);   // 内部上拉
    GpioSetDriveStrength(gpio, 1);     // 驱动能力设为1级
    return HDF_SUCCESS;
}

避坑指南: 我曾经在驱动一个温湿度传感器时,发现数据线电平总是毛刺不断。查了半天,原来是内部上拉没使能,外部也没加上拉电阻。信号浮空,啥数据都读不准。记住:输入管脚,要么内部上拉,要么外部上拉,别让它悬空。

3.4 中断注册与处理:让CPU不再“空转”

传感器最常用的通信方式就是中断。比如运动传感器检测到动作,拉高一个GPIO,CPU收到中断后立刻读取数据。这比轮询省电得多。

鸿蒙里注册中断的流程:

  1. 申请中断号(从设备树或HCS获取)
  2. 设置触发方式(上升沿、下降沿、双边沿、高电平、低电平)
  3. 注册中断处理函数
  4. 使能中断
// 中断处理函数
static int32_t GpioIrqHandler(uint16_t gpio, void *data) {
    // 读取传感器数据
    uint8_t val = GpioRead(gpio);
    // 通知上层
    printf("GPIO %d interrupt triggered, value = %d\n", gpio, val);
    return HDF_SUCCESS;
}

// 注册中断
int32_t GpioRegisterIrq(uint16_t gpio) {
    // 配置为输入
    GpioSetDir(gpio, GPIO_DIR_IN);
    GpioSetPull(gpio, GPIO_PULL_UP);
    
    // 注册下降沿中断
    int32_t ret = GpioSetIrq(gpio, OSAL_IRQF_TRIGGER_FALLING, 
                             GpioIrqHandler, NULL);
    if (ret != HDF_SUCCESS) {
        printf("GpioSetIrq failed, ret = %d\n", ret);
        return ret;
    }
    return HDF_SUCCESS;
}

重要: 中断处理函数里不要做耗时操作。读寄存器、清标志位、发消息给任务队列就够了。千万别在中断里调用 printf 或 malloc——我见过有人这么干,系统直接死机。中断讲究“快进快出”。

3.5 知识结构总览

下面这张图把GPIO驱动开发的整体脉络画出来了。你可以对照着看,心里有个全局感。

GPIO驱动开发知识结构图 GPIO驱动开发知识结构 GPIO驱动开发 控制器驱动 管脚配置 上下拉与驱动能力 中断注册与处理 HDF接口 方法表注册 复用功能 方向设置 上拉/下拉 驱动强度 触发方式 处理函数 核心原则:先配复用,再设方向,最后配中断 中断处理要“快进快出”,避免耗时操作

3.6 实战要点总结

  • 配置顺序不能乱: 复用 → 方向 → 上下拉 → 中断。跳步容易出怪问题。
  • 中断触发方式选对: 传感器一般用边沿触发(上升沿或下降沿),电平触发容易产生重复中断。
  • 驱动能力别贪大: 不是越大越好。驱动能力过强,信号反射严重,反而影响通信质量。我一般先从最低档试起,够用就行。
  • 调试时加打印要克制: 中断里别加 printf,用 HDF_LOGE 或标志位代替。

个人习惯: 我每次写完GPIO驱动,都会先写一个简单的测试用例——循环读电平、写电平、触发中断。确认每个管脚都正常工作了,再往上挂传感器逻辑。这一步能省掉后面80%的调试时间。