Linux系统GPIO子系统概述
1. GPIO子系统简介
GPIO(General Purpose Input/Output)子系统是Linux内核中负责管理通用输入输出引脚的子系统。它提供了统一的接口来访问和控制SoC(片上系统)的GPIO引脚,使设备驱动和应用程序能够方便地使用这些引脚进行输入输出操作。
1.1 主要功能
- 引脚控制:设置GPIO引脚的方向(输入/输出)
- 电平操作:读取和设置GPIO引脚的电平状态(高/低)
- 中断处理:配置GPIO中断触发方式(上升沿、下降沿、双边沿等)
- 设备树集成:通过设备树描述GPIO硬件信息和配置
- 用户空间访问:提供sysfs接口,允许用户空间程序访问GPIO
2. GPIO子系统架构
GPIO子系统采用分层架构设计,从上到下依次为:
2.1 架构层次
| 层次 | 描述 | 主要组件 |
|---|---|---|
| 应用层 | 用户空间应用和设备驱动 | 应用程序、设备驱动、sysfs接口 |
| 核心层 | GPIO核心 | gpiolib模块 |
| 驱动层 | 平台特定的GPIO驱动 | 各SoC厂商的GPIO驱动 |
| 硬件层 | 实际的GPIO硬件 | SoC GPIO控制器、物理引脚 |
2.2 核心模块
- gpiolib:实现GPIO子系统的核心功能,提供统一的API接口
- gpiochip:表示一个GPIO控制器,管理一组GPIO引脚
- gpiodevice:表示一个GPIO设备,由gpiochip管理
- irqchip:与中断子系统的集成,处理GPIO中断
3. GPIO子系统的关键概念
3.1 基本概念
- GPIO控制器(GPIO Chip):管理一组GPIO引脚的硬件模块
- GPIO引脚(GPIO Pin):物理引脚,可配置为输入或输出
- GPIO编号(GPIO Number):系统范围内唯一的GPIO标识符
- GPIO方向(GPIO Direction):引脚的输入或输出方向
- GPIO值(GPIO Value):引脚的电平状态(0或1)
- GPIO中断(GPIO Interrupt):由GPIO状态变化触发的中断
3.2 数据结构
- struct gpio_chip:表示一个GPIO控制器
- struct gpio_desc:表示一个GPIO引脚的描述符
- struct gpio_device:表示一个GPIO设备
- struct gpio_irq_chip:表示GPIO中断控制器
4. GPIO子系统与设备树
4.1 设备树绑定
GPIO子系统通过设备树来描述GPIO硬件信息和配置,主要包括以下部分:
- GPIO控制器节点:描述GPIO控制器的硬件特性和可用引脚
- GPIO引脚配置:描述引脚的使用方式和属性
- 设备节点引用:设备通过GPIO属性引用特定的GPIO引脚
4.2 设备树示例
4.2.1 GPIO控制器节点
gpio: gpio@1c20800 {
compatible = "allwinner,sun8i-h3-gpio";
reg = <0x01c20800 0x400>;
interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
#gpio-cells = <3>;
gpio-controller;
interrupt-controller;
#interrupt-cells = <2>;
clocks = <&ccu CLK_BUS_GPIO>;
resets = <&ccu RST_BUS_GPIO>;
};
4.2.2 设备节点引用GPIO
leds {
compatible = "gpio-leds";
led0 {
label = "status";
gpios = <&gpio 1 0 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
led1 {
label = "user";
gpios = <&gpio 2 0 GPIO_ACTIVE_LOW>;
default-state = "off";
};
};
button {
compatible = "gpio-keys";
power {
label = "Power Button";
linux,code = <KEY_POWER>;
gpios = <&gpio 3 0 GPIO_ACTIVE_LOW>;
debounce-interval = <100>;
};
};
4.3 GPIO设备树属性说明
- #gpio-cells:指定GPIO引用所需的单元格数量,通常为2或3
- gpio-controller:标记该节点为GPIO控制器
- interrupt-controller:标记该节点为中断控制器
- #interrupt-cells:指定中断引用所需的单元格数量
- gpios:引用GPIO引脚,格式为
<&gpio-controller pin-number flags> - flags:GPIO标志,如GPIO_ACTIVE_HIGH、GPIO_ACTIVE_LOW等
5. GPIO子系统API
5.1 内核空间API
5.1.1 基本操作API
- gpio_request():请求一个GPIO引脚
- gpio_free():释放一个GPIO引脚
- gpio_direction_input():设置GPIO为输入方向
- gpio_direction_output():设置GPIO为输出方向并设置初始值
- gpio_get_value():读取GPIO的值
- gpio_set_value():设置GPIO的值
- gpio_to_irq():将GPIO编号转换为中断编号
5.1.2 设备树相关API
- of_get_gpio():从设备树中获取GPIO编号
- of_get_gpio_flags():从设备树中获取GPIO编号和标志
- gpiod_get():获取GPIO描述符
- gpiod_put():释放GPIO描述符
- gpiod_direction_input():设置GPIO为输入方向
- gpiod_direction_output():设置GPIO为输出方向并设置初始值
- gpiod_get_value():读取GPIO的值
- gpiod_set_value():设置GPIO的值
5.1.3 内核驱动使用示例
#include <linux/gpio/consumer.h>
struct gpio_desc *gpio_led;
static int my_driver_probe(struct platform_device *pdev)
{
int ret;
/* 从设备树获取GPIO */
gpio_led = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(gpio_led)) {
dev_err(&pdev->dev, "Failed to get LED GPIO: %ld\n", PTR_ERR(gpio_led));
return PTR_ERR(gpio_led);
}
/* 设置GPIO为高电平 */
gpiod_set_value(gpio_led, 1);
return 0;
}
static int my_driver_remove(struct platform_device *pdev)
{
/* 释放GPIO */
gpiod_put(gpio_led);
return 0;
}
5.2 用户空间API
5.2.1 sysfs接口
GPIO子系统通过sysfs提供了用户空间访问接口,位于/sys/class/gpio/目录下。
- /sys/class/gpio/export:导出GPIO到用户空间
- /sys/class/gpio/unexport:从用户空间取消导出GPIO
- /sys/class/gpio/gpioX/:导出的GPIO目录,X为GPIO编号
- direction:设置GPIO方向(in/out)
- value:读取或设置GPIO值(0/1)
- edge:设置中断触发方式(none/rising/falling/both)
- active_low:设置是否为低电平有效
5.2.2 用户空间使用示例
# 导出GPIO 18
echo 18 > /sys/class/gpio/export
# 设置为输出方向
echo out > /sys/class/gpio/gpio18/direction
# 设置为高电平
echo 1 > /sys/class/gpio/gpio18/value
# 读取GPIO值
cat /sys/class/gpio/gpio18/value
# 设置为输入方向
echo in > /sys/class/gpio/gpio18/direction
# 设置中断触发方式为上升沿
echo rising > /sys/class/gpio/gpio18/edge
# 取消导出GPIO 18
echo 18 > /sys/class/gpio/unexport
6. 应用层测试接口和方法
6.1 sysfs接口测试
6.1.1 基本测试
# 查看系统中的GPIO控制器
ls /sys/class/gpio/
# 导出GPIO
# 注意:不同平台的GPIO编号可能不同,需要根据实际情况调整
echo 1 > /sys/class/gpio/export
# 测试输出功能
echo out > /sys/class/gpio/gpio1/direction
echo 1 > /sys/class/gpio/gpio1/value
echo 0 > /sys/class/gpio/gpio1/value
# 测试输入功能
echo in > /sys/class/gpio/gpio1/direction
cat /sys/class/gpio/gpio1/value
# 测试中断触发方式
echo rising > /sys/class/gpio/gpio1/edge
# 取消导出
echo 1 > /sys/class/gpio/unexport
6.1.2 批量测试脚本
#!/bin/bash
# GPIO测试脚本
GPIO_NUM=1
# 导出GPIO
echo $GPIO_NUM > /sys/class/gpio/export
# 测试输出
echo "Testing GPIO $GPIO_NUM as output..."
echo out > /sys/class/gpio/gpio$GPIO_NUM/direction
for i in {1..5}
do
echo 1 > /sys/class/gpio/gpio$GPIO_NUM/value
echo "GPIO $GPIO_NUM set to HIGH"
sleep 1
echo 0 > /sys/class/gpio/gpio$GPIO_NUM/value
echo "GPIO $GPIO_NUM set to LOW"
sleep 1
done
# 测试输入
echo "Testing GPIO $GPIO_NUM as input..."
echo in > /sys/class/gpio/gpio$GPIO_NUM/direction
for i in {1..5}
do
VALUE=$(cat /sys/class/gpio/gpio$GPIO_NUM/value)
echo "GPIO $GPIO_NUM input value: $VALUE"
sleep 1
done
# 取消导出
echo $GPIO_NUM > /sys/class/gpio/unexport
echo "Test completed"
6.2 libgpiod库
libgpiod是一个用户空间库,提供了更高级的GPIO操作接口,替代了传统的sysfs接口。
6.2.1 安装libgpiod
# Debian/Ubuntu
sudo apt-get install libgpiod-dev libgpiod-tools
# CentOS/RHEL
sudo yum install libgpiod-devel libgpiod-utils
6.2.2 使用gpiod工具
# 列出所有GPIO芯片
gpiodetect
# 列出特定芯片的GPIO
gpioinfo gpiochip0
# 设置GPIO 1为输出并设置为高电平
gpioget gpiochip0 1
# 设置GPIO 1为输出并设置为高电平
gpioset gpiochip0 1=1
# 设置GPIO 1为输出并设置为低电平
gpioset gpiochip0 1=0
# 监控GPIO 1的状态变化
gpiomon -n 10 gpiochip0 1
6.2.3 编程示例
#include <gpiod.h>
#include <stdio.h>
int main(void)
{
struct gpiod_chip *chip;
struct gpiod_line *line;
int value;
int ret;
// 打开GPIO芯片
chip = gpiod_chip_open("/dev/gpiochip0");
if (!chip) {
perror("gpiod_chip_open");
return 1;
}
// 获取GPIO线
line = gpiod_chip_get_line(chip, 1);
if (!line) {
perror("gpiod_chip_get_line");
gpiod_chip_close(chip);
return 1;
}
// 设置为输出方向
ret = gpiod_line_request_output(line, "test", 0);
if (ret < 0) {
perror("gpiod_line_request_output");
gpiod_line_release(line);
gpiod_chip_close(chip);
return 1;
}
// 设置为高电平
ret = gpiod_line_set_value(line, 1);
if (ret < 0) {
perror("gpiod_line_set_value");
gpiod_line_release(line);
gpiod_chip_close(chip);
return 1;
}
printf("GPIO set to HIGH\n");
// 释放资源
gpiod_line_release(line);
gpiod_chip_close(chip);
return 0;
}
7. 平台特定的GPIO驱动
7.1 常见平台的GPIO驱动
- Allwinner:sunxi-gpio
- Qualcomm:msm-gpio
- NXP:imx-gpio
- TI:omap-gpio
- Rockchip:rk-gpio
- Intel:gpio-ich
7.2 驱动实现要点
- GPIO控制器初始化:识别硬件特性,初始化寄存器
- 操作函数实现:实现GPIO的读写、方向设置等操作
- 中断处理:实现GPIO中断的配置和处理
- 设备树解析:解析设备树中的GPIO配置
8. GPIO子系统的优势
- 统一管理:集中管理所有GPIO的配置和操作
- 标准化接口:提供统一的API,隐藏硬件差异
- 设备树集成:通过设备树描述硬件配置,实现配置与代码分离
- 用户空间访问:提供sysfs接口,方便用户空间程序访问
- 中断支持:内置中断处理机制,支持各种触发方式
9. 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| GPIO请求失败 | GPIO被其他设备占用 | 检查GPIO使用情况,确保没有冲突 |
| 中断不触发 | 中断配置错误 | 检查中断触发方式和GPIO方向设置 |
| 电平读取错误 | 电气连接问题或方向设置错误 | 检查硬件连接,确保GPIO方向正确 |
| 用户空间访问权限 | 权限不足 | 使用root权限或正确设置文件权限 |
| 设备树解析失败 | 设备树语法错误或节点引用错误 | 检查设备树语法,确保节点路径正确 |
10. 总结
GPIO子系统为Linux内核提供了统一的GPIO管理框架,通过分层架构和设备树集成,实现了GPIO配置的标准化和集中管理。它不仅简化了驱动开发,还提供了用户空间访问接口,方便应用程序使用GPIO功能。
在实际应用中,正确使用GPIO子系统可以实现各种输入输出功能,如LED控制、按键检测、传感器接口等。通过sysfs接口和libgpiod库,开发人员可以方便地测试和使用GPIO功能。
随着嵌入式系统的不断发展,GPIO子系统的重要性也将日益凸显,它为各种嵌入式设备提供了基础的输入输出能力,是嵌入式系统开发中不可或缺的一部分。

5096

被折叠的 条评论
为什么被折叠?



