Lazy loaded image
Words 0Read Time 1 min
Invalid Date

一、介绍

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_valuegpio_set_value 等)
  • 管理引脚资源:通过 gpio_request / gpio_free 避免多个驱动同时操作同一引脚
  • 提供中断支持:将 GPIO 电平变化转换为 Linux IRQ,供上层驱动注册中断处理函数
  • 对接子系统:与 pinctrl 子系统协作完成引脚复用和电气属性配置,与 Device Tree 协作实现硬件描述与驱动解耦
GPIO 驱动在内核中的层次关系:

1.3 怎么编写驱动程序?

  1. 确定主设备号,也可以让内核分配
  1. 定义自己的 file_operations 结构体
  1. 实现对应的 drv_open / drv_read / drv_write 等函数,填入 file_operations 结构体
  1. file_operations 结构体告诉内核:register_chrdev
  1. 谁来注册驱动程序?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
  1. 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev
  1. 其他完善:提供设备信息,自动创建设备节点:class_createdevice_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 APIgpiod_*),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 引脚可配置为中断源,典型流程:
  1. 通过 gpiod_to_irq(desc) 获取虚拟中断号
  1. 调用 devm_request_threaded_irq() 注册中断处理函数
  1. 指定触发类型:IRQF_TRIGGER_RISINGIRQF_TRIGGER_FALLINGIRQF_TRIGGER_BOTH

四、设备树中的 GPIO 配置

4.1 GPIO 控制器节点

4.2 设备节点引用 GPIO

4.3 pinctrl 与 GPIO 的协作

pinctrl 子系统负责引脚复用和电气属性配置,GPIO 子系统负责逻辑电平控制。两者通过 gpio-ranges 建立映射:
驱动中通过 pinctrl-namespinctrl-0 自动应用 pinctrl 配置,无需手动调用 pinctrl API。

五、寄存器直接操作(ioremap)

当需要绕过 GPIO 子系统直接操作硬件寄存器时(如 Bootloader 或裸机驱动),使用 ioremap 将物理地址映射到内核虚拟地址空间。

5.1 函数原型

参数
说明
phys_addr
要映射的物理地址(通常从芯片手册获取)
size
映射的内存大小(字节)
返回值
映射后的虚拟地址指针,失败返回 NULL

5.2 使用示例

⚠️
在正式 Linux 驱动中应优先使用 GPIO 子系统 API 而非直接 ioremap 操作寄存器。直接操作寄存器绕过了内核的资源管理和并发保护,仅适用于底层调试、Bootloader 或无 GPIO 子系统支持的场景。

六、调试技巧

6.1 sysfs(Legacy,仅用于快速验证)

6.2 libgpiod 工具集(推荐)

6.3 内核调试信息

上一篇
Data Structure and Algorithm
下一篇
用面试拷问嵌入式技术栈

Comments
Loading...