1. GPIO基础与GPIOLIB框架概述
GPIO(General Purpose Input/Output)是嵌入式系统和单片机开发中最基础也最重要的外设接口之一。它允许开发者通过软件控制引脚的电平状态(高/低)或读取外部信号,实现与各种传感器、执行器的交互。在Linux内核中,GPIOLIB框架为GPIO操作提供了统一的抽象层,使得不同硬件平台的GPIO控制器能够以一致的方式被访问。
提示:现代Linux内核(4.8+版本)已全面转向基于描述符的GPIO接口,旧版基于整数的API已被标记为废弃。新开发应优先使用
gpiod_系列函数。
GPIO在嵌入式系统中的典型应用场景包括:
- LED控制(输出模式)
- 按键检测(输入模式)
- 硬件复位信号生成
- 简单的设备间通信(如模拟I2C/SPI)
- 外设使能/禁用控制
GPIOLIB框架的核心价值在于:
- 硬件抽象:屏蔽不同SoC厂商GPIO控制器的差异
- 资源管理:防止GPIO引脚被多个驱动同时占用
- 安全访问:提供睡眠安全(sleep-safe)的访问接口
- 调试支持:通过sysfs提供用户空间调试接口
2. GPIO驱动开发环境准备
2.1 硬件需求分析
开发GPIO驱动前,需明确目标硬件平台的以下信息:
- SoC型号及数据手册(如STM32MP157、RK3588等)
- GPIO控制器章节的寄存器映射
- 引脚复用(Pin Mux)配置方式
- 电气特性(驱动能力、上下拉配置)
以常见的树莓派为例,其GPIO引脚分布如下:
| 物理引脚 | BCM编号 | 功能 | 电压 |
|---|---|---|---|
| 1 | - | 3.3V | 3.3V |
| 2 | - | 5V | 5V |
| 3 | 2 | SDA | 3.3V |
| ... | ... | ... | ... |
| 11 | 17 | GPIO | 3.3V |
2.2 软件环境配置
开发Linux GPIO驱动需要:
- 目标平台的内核源码树
- 交叉编译工具链
- GPIO子系统头文件:
#include <linux/gpio/consumer.h> // 推荐新接口 #include <linux/gpio.h> // 传统接口
内核配置需确保以下选项启用:
CONFIG_GPIOLIB=y CONFIG_GPIO_SYSFS=y CONFIG_DEBUG_FS=y3. GPIOLIB框架核心数据结构
3.1 gpio_chip结构体
GPIO控制器的抽象,驱动开发者需要实现其中的关键操作:
struct gpio_chip { const char *label; struct device *parent; int (*request)(struct gpio_chip *chip, unsigned offset); void (*free)(struct gpio_chip *chip, unsigned offset); int (*get_direction)(struct gpio_chip *chip, unsigned offset); 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); int (*set_config)(struct gpio_chip *chip, unsigned offset, unsigned long config); // ...其他成员 };3.2 gpio_desc结构体
新API中的核心描述符,代表一个GPIO引脚:
struct gpio_desc { struct gpio_device *gdev; unsigned long flags; // ...内部成员 };4. GPIO驱动实现详解
4.1 驱动初始化流程
典型GPIO驱动初始化代码框架:
static int my_gpio_probe(struct platform_device *pdev) { struct gpio_chip *chip; struct device *dev = &pdev->dev; chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->label = "my-gpio-chip"; chip->parent = dev; chip->owner = THIS_MODULE; chip->base = -1; // 动态分配 chip->ngpio = 16; // 16个GPIO引脚 chip->direction_input = my_gpio_direction_input; chip->direction_output = my_gpio_direction_output; chip->get = my_gpio_get; chip->set = my_gpio_set; return devm_gpiochip_add_data(dev, chip, NULL); }4.2 引脚方向控制实现
输入模式配置示例:
static int my_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct my_private_data *data = gpiochip_get_data(chip); u32 reg; reg = readl(data->base + GPIO_DIR_REG); reg |= BIT(offset); // 设置为输入 writel(reg,>static int my_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct my_private_data *data = gpiochip_get_data(chip); u32 reg; // 先设置电平 reg = readl(data->base + GPIO_DATA_REG); if (value) reg |= BIT(offset); else reg &= ~BIT(offset); writel(reg,>static int my_gpio_get(struct gpio_chip *chip, unsigned offset) { struct my_private_data *data = gpiochip_get_data(chip); u32 reg = readl(data->base + GPIO_DATA_REG); return !!(reg & BIT(offset)); }设置引脚电平:
static void my_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct my_private_data *data = gpiochip_get_data(chip); u32 reg = readl(data->base + GPIO_DATA_REG); if (value) reg |= BIT(offset); else reg &= ~BIT(offset); writel(reg,>desc = gpiod_to_irq(gpio_desc); ret = request_irq(desc, handler, IRQF_TRIGGER_RISING, "my-gpio-irq", NULL);实现中断处理函数:
static irqreturn_t my_gpio_irq_handler(int irq, void *dev_id) { // 处理中断 return IRQ_HANDLED; }注意:在中断上下文中不能调用可能休眠的GPIO操作函数,如
gpiod_set_value_cansleep()。
5.2 设备树配置示例
现代Linux驱动推荐使用设备树描述硬件资源:
gpio-controller@40020000 { compatible = "my-company,my-gpio-controller"; reg = <0x40020000 0x1000>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; user-device { compatible = "my-company,user-device"; en-gpios = <&gpio-controller 5 GPIO_ACTIVE_HIGH>; irq-gpios = <&gpio-controller 7 GPIO_ACTIVE_LOW>; };驱动中解析设备树节点:
struct gpio_desc *en_gpio; en_gpio = devm_gpiod_get(dev, "en", GPIOD_OUT_HIGH);5.3 性能优化技巧
批量操作:对于需要同时操作多个GPIO的情况,使用
gpiod_set_array()等批量接口减少IO访问次数。缓存配置:频繁切换方向时,可缓存当前方向状态避免冗余寄存器写入。
原子操作:在中断上下文中使用
gpiod_get_value()而非可能休眠的变体。电源管理:在
pm_ops中实现GPIO状态的保存与恢复:static int my_gpio_suspend(struct device *dev) { struct my_data *data = dev_get_drvdata(dev); >#include <linux/module.h> #include <linux/gpio/consumer.h> #include <linux/platform_device.h> struct led_controller { struct gpio_desc *led_gpio; }; static int led_set(struct led_controller *ctrl, bool on) { gpiod_set_value(ctrl->led_gpio, on); return 0; } static int led_probe(struct platform_device *pdev) { struct led_controller *ctrl; ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); if (!ctrl) return -ENOMEM; ctrl->led_gpio = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW); if (IS_ERR(ctrl->led_gpio)) return PTR_ERR(ctrl->led_gpio); platform_set_drvdata(pdev, ctrl); led_set(ctrl, true); // 点亮LED return 0; } static const struct of_device_id led_dt_ids[] = { { .compatible = "my-company,led-controller" }, { } }; MODULE_DEVICE_TABLE(of, led_dt_ids); static struct platform_driver led_driver = { .driver = { .name = "led-controller", .of_match_table = led_dt_ids, }, .probe = led_probe, }; module_platform_driver(led_driver);对应设备树节点:
led-controller { compatible = "my-company,led-controller"; led-gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>; };8. 用户空间GPIO访问
除了内核驱动,用户空间也可通过以下方式访问GPIO:
sysfs接口(传统方式):
# 导出GPIO echo 12 > /sys/class/gpio/export # 设置方向 echo out > /sys/class/gpio/gpio12/direction # 设置电平 echo 1 > /sys/class/gpio/gpio12/value字符设备接口(推荐新方式):
int fd = open("/dev/gpiochip0", O_RDWR); struct gpiohandle_request req; req.lineoffsets[0] = 12; req.flags = GPIOHANDLE_REQUEST_OUTPUT; strcpy(req.consumer_label, "my-app"); ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);libgpiod库:
struct gpiod_chip *chip = gpiod_chip_open("/dev/gpiochip0"); struct gpiod_line *line = gpiod_chip_get_line(chip, 12); gpiod_line_request_output(line, "my-app", 0); gpiod_line_set_value(line, 1);
重要:生产环境中应优先考虑内核驱动方案,用户空间直接操作GPIO可能导致资源冲突和系统不稳定。