第4章 GPIO驱动基础
GPIO,说白了就是芯片上那些可以编程控制的引脚。你可以让它输出高电平或低电平,也可以读取它当前的电平状态。在嵌入式系统里,GPIO 是最基础也最常用的外设接口——控制 LED、读取按键、模拟时序,哪样都离不开它。
这一章,我带你从 Linux GPIO 子系统的整体框架入手,然后深入到寄存器层面,最后再聊聊怎么在用户空间通过 sysfs 接口直接操作 GPIO。嗯,这些都是我这些年做驱动开发天天打交道的玩意儿。
4.1 GPIO 子系统框架
Linux 内核的 GPIO 子系统,我习惯叫它 gpiolib。它把不同芯片的 GPIO 控制器抽象成统一的接口。你写驱动的时候,不用关心底层寄存器具体怎么配,调用 gpiolib 提供的 API 就行。
框架分三层:
- 用户空间接口层:sysfs 或 libgpiod,让你在应用层就能操作 GPIO。
- 内核 gpiolib 核心:管理 gpio_chip 的注册、引脚分配、方向控制、读写操作。
- 硬件驱动层:每个 GPIO 控制器(比如 HiSilicon 的 GPIO 模块)实现自己的 .get、.set、.direction_input 等回调函数。
我记得刚接触 gpiolib 时,总觉得它很绕。后来发现,你只要理解两个核心结构体就行:gpio_chip 和 gpio_desc。前者代表一个控制器,后者代表一个引脚。
struct gpio_chip {
const char *label;
int base; // 引脚编号起始
int ngpio; // 本控制器管理的引脚数
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
int (*get)(struct gpio_chip *chip, unsigned offset);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
// ... 还有中断、请求、释放等回调
};
你写一个 GPIO 控制器驱动,主要就是填充这个结构体,然后调用 gpiochip_add_data() 注册到内核。剩下的,gpiolib 帮你搞定。
4.2 GPIO 控制寄存器
说完了框架,咱们得落地到硬件。Atlas 200 DK 用的海思芯片,GPIO 模块的寄存器布局其实很典型。每个 GPIO 引脚通常对应这么几类寄存器:
| 寄存器类型 | 作用 | 位宽 |
|---|---|---|
| GPIO_SWPORTA_DR | 数据输出寄存器(写引脚电平) | 32位,每bit对应一个引脚 |
| GPIO_SWPORTA_DDR | 方向寄存器(0=输入,1=输出) | 32位,每bit对应一个引脚 |
| GPIO_EXT_PORTA | 数据输入寄存器(读引脚电平) | 32位,只读 |
| GPIO_INTEN | 中断使能寄存器 | 32位 |
| GPIO_INTTYPE_LEVEL | 中断触发类型(电平/边沿) | 32位 |
| GPIO_INT_POLARITY | 中断极性(高/低电平,上升/下降沿) | 32位 |
举个例子,你想让 GPIO12 输出高电平。先设置方向寄存器对应位为 1(输出),再写数据寄存器对应位为 1。就这么简单。
4.3 在用户空间操作 GPIO(sysfs 接口)
对于快速原型验证,我强烈推荐先用 sysfs 接口。它不需要写内核模块,直接在 shell 里 echo 和 cat 就能控制 GPIO。虽然现在内核社区更推荐 libgpiod,但 sysfs 在调试阶段依然很顺手。
基本步骤就四步:
- 导出引脚:
echo 12 > /sys/class/gpio/export - 设置方向:
echo out > /sys/class/gpio/gpio12/direction - 写值:
echo 1 > /sys/class/gpio/gpio12/value - 读值:
cat /sys/class/gpio/gpio12/value
用完之后记得 echo 12 > /sys/class/gpio/unexport 释放引脚。
下面是一个完整的 shell 示例,控制 GPIO12 闪烁 5 次:
#!/bin/sh
GPIO=12
# 导出
echo $GPIO > /sys/class/gpio/export
# 设为输出
echo out > /sys/class/gpio/gpio$GPIO/direction
for i in 1 2 3 4 5; do
echo 1 > /sys/class/gpio/gpio$GPIO/value
sleep 0.5
echo 0 > /sys/class/gpio/gpio$GPIO/value
sleep 0.5
done
# 清理
echo $GPIO > /sys/class/gpio/unexport
你想想看,就这么几行,就能让一个 LED 闪起来。对于硬件 bring-up 阶段,这种快速验证方式特别管用。
/sys/class/gpio/
├── export # 写引脚号导出
├── unexport # 写引脚号释放
├── gpiochip0/ # 第一个GPIO控制器
│ ├── base # 起始编号
│ ├── ngpio # 引脚数量
│ └── label # 控制器名称
├── gpiochip64/ # 第二个控制器
└── gpio12/ # 导出后出现的目录
├── direction # in / out / high / low
├── value # 0 或 1
├── edge # none / rising / falling / both
└── active_low # 0 或 1
我个人习惯在调试时先 cat 一下 /sys/kernel/debug/gpio,看看当前所有 GPIO 的状态。这个 debugfs 接口会列出每个引脚的申请者、方向、当前值,排查冲突特别好用。
嗯,这一章的内容差不多就这些。从框架到寄存器,再到用户空间操作,GPIO 驱动的基础脉络你应该清楚了。下一章我们会深入 GPIO 中断,那才是真正体现驱动工程师水平的地方。