一、介绍
1.1 常用概念
概念 | 说明 |
GPIO | General-Purpose Input/Output,通用输入输出引脚,可由软件控制高低电平或读取外部电平状态 |
引脚复用(Pin Mux) | 同一物理引脚可被配置为 GPIO、UART、SPI、I2C 等不同功能,由 IOMUX 控制器选择 |
上拉 / 下拉(Pull-up / Pull-down) | 内部电阻将引脚默认拉高或拉低,防止浮空产生不确定电平 |
推挽 / 开漏(Push-Pull / Open-Drain) | 推挽可主动输出高低电平;开漏只能拉低,需外部上拉电阻实现高电平(常用于 I2C 总线) |
驱动强度(Drive Strength) | 引脚输出电流能力,影响信号的上升/下降速率和驱动负载能力 |
电平触发 / 边沿触发 | 中断检测方式:电平触发在持续满足条件时触发;边沿触发在电平跳变瞬间触发(上升沿/下降沿) |
主设备号 / 次设备号 | Linux 用主设备号区分驱动类型,次设备号区分同类设备的不同实例 |
file_operations | 内核结构体,定义字符设备的 open/read/write/ioctl 等操作接口 |
1.2 是什么
GPIO 驱动是 Linux 内核中控制通用输入输出引脚的软件模块。核心职责:
- 抽象硬件差异:将不同 SoC 的 GPIO 寄存器操作封装为统一的内核 API(
gpio_get_value、gpio_set_value等)
- 管理引脚资源:通过
gpio_request/gpio_free避免多个驱动同时操作同一引脚
- 提供中断支持:将 GPIO 电平变化转换为 Linux IRQ,供上层驱动注册中断处理函数
- 对接子系统:与 pinctrl 子系统协作完成引脚复用和电气属性配置,与 Device Tree 协作实现硬件描述与驱动解耦
GPIO 驱动在内核中的层次关系:
1.3 怎么编写驱动程序?
- 确定主设备号,也可以让内核分配
- 定义自己的
file_operations结构体
- 实现对应的
drv_open/drv_read/drv_write等函数,填入file_operations结构体
- 把
file_operations结构体告诉内核:register_chrdev
- 谁来注册驱动程序?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
- 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用
unregister_chrdev
- 其他完善:提供设备信息,自动创建设备节点:
class_create→device_create
关键数据结构:
include/linux/fs.h 中的 struct file 与用户空间每次 open() 调用一一对应。int open(const char *pathname, int flags, mode_t mode) 打开文件句柄后,内核为其创建一个 struct file 实例,驱动通过该结构体的 private_data 字段传递自定义上下文。二、常用功能 API
2.1 设置引脚状态
void eio_pin_set_status(eio_pin_t * const me, bool status)调用 HAL 库,设置 GPIO 状态。
参数 | 描述 |
me | this 指针,指定不同实例化对象 |
status | 引脚电平状态 |
支持的引脚状态如下:
2.2 读取引脚状态
bool eio_pin_get_status(eio_pin_t * const me)读取当前 GPIO 引脚的输入电平。
参数 | 描述 |
me | this 指针,指定不同实例化对象 |
返回值 | true = 高电平,false = 低电平 |
2.3 翻转引脚状态
void eio_pin_toggle(eio_pin_t * const me)翻转 GPIO 引脚当前输出电平(高→低 / 低→高),常用于 LED 闪烁、心跳指示等场景。
2.4 设置引脚方向
void eio_pin_set_direction(eio_pin_t * const me, eio_pin_dir_t dir)动态切换引脚方向,支持的方向:
GPIO 操作方法三、Linux GPIO 子系统
3.1 Legacy API vs Descriptor API
Linux 内核提供两套 GPIO 接口。新驱动必须使用 Descriptor API(
gpiod_*),Legacy API(gpio_*)已废弃。操作 | Legacy API(已废弃) | Descriptor API(推荐) |
获取引脚 | gpio_request(gpio, label) | gpiod_get(dev, con_id, flags) |
释放引脚 | gpio_free(gpio) | gpiod_put(desc) |
设为输入 | gpio_direction_input(gpio) | gpiod_direction_input(desc) |
设为输出 | gpio_direction_output(gpio, val) | gpiod_direction_output(desc, val) |
读取值 | gpio_get_value(gpio) | gpiod_get_value(desc) |
设置值 | gpio_set_value(gpio, val) | gpiod_set_value(desc, val) |
转中断号 | gpio_to_irq(gpio) | gpiod_to_irq(desc) |
使用
devm_gpiod_get() 系列函数可自动管理资源释放,避免在 remove 或错误路径中手动调用 gpiod_put()。3.2 Descriptor API 使用示例
对应设备树节点:
3.3 GPIO 与中断
GPIO 引脚可配置为中断源,典型流程:
- 通过
gpiod_to_irq(desc)获取虚拟中断号
- 调用
devm_request_threaded_irq()注册中断处理函数
- 指定触发类型:
IRQF_TRIGGER_RISING、IRQF_TRIGGER_FALLING、IRQF_TRIGGER_BOTH
四、设备树中的 GPIO 配置
4.1 GPIO 控制器节点
4.2 设备节点引用 GPIO
4.3 pinctrl 与 GPIO 的协作
pinctrl 子系统负责引脚复用和电气属性配置,GPIO 子系统负责逻辑电平控制。两者通过
gpio-ranges 建立映射:驱动中通过
pinctrl-names 和 pinctrl-0 自动应用 pinctrl 配置,无需手动调用 pinctrl API。五、寄存器直接操作(ioremap)
当需要绕过 GPIO 子系统直接操作硬件寄存器时(如 Bootloader 或裸机驱动),使用
ioremap 将物理地址映射到内核虚拟地址空间。5.1 函数原型
参数 | 说明 |
phys_addr | 要映射的物理地址(通常从芯片手册获取) |
size | 映射的内存大小(字节) |
返回值 | 映射后的虚拟地址指针,失败返回 NULL |
5.2 使用示例
在正式 Linux 驱动中应优先使用 GPIO 子系统 API 而非直接
ioremap 操作寄存器。直接操作寄存器绕过了内核的资源管理和并发保护,仅适用于底层调试、Bootloader 或无 GPIO 子系统支持的场景。





