尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

嵌入式Linux C应用编程——GPIO

嵌入式Linux C应用编程——GPIO
📅 发布时间:2026/7/17 19:50:07

GPIO应用编程

概述

我们可以在 Linux 应用层通过 sysfs 文件系统操控 GPIO 引脚的方法。sysfs 挂载于 /sys 目录,内核将 GPIO 控制器及每个引脚以文件和目录的形式导出,应用程序可以通过读写这些文件来配置引脚方向、读取电平、触发中断等。这种方式是嵌入式 Linux 中最常用的 GPIO 用户态操作方法,适用于 LED 控制、按键检测、传感器读取等场景。

操控GPIO的方法

1. GPIO 在 sysfs 中的组织

进入 /sys/class/gpio 目录,可以看到两类内容:

  • gpiochipX 目录:每个 gpiochipX 对应 SoC 的一个 GPIO 控制器(如 I.MX6UL 的 GPIO1~GPIO5,分别对应 gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128)。每个目录下有三个只读属性文件:

    • base:该控制器管理的 GPIO 编号最小值。
    • label:控制器名称。
    • ngpio:该控制器管理的引脚数量。
      通过这些文件可以计算出任意引脚对应的编号,例如 GPIO4_IO16 对应编号 96 + 16 = 112。96表示的是这个组里最小的标号值。
  • export 和 unexport 文件:均为只写文件。

    • 向 export 写入一个编号,可以导出对应的 GPIO 引脚,成功后会在当前目录下生成一个名为 gpioX 的目录(X 为编号)。
    • 向 unexport 写入该编号,可以删除该目录,释放引脚。

img

注意:并非所有 GPIO 都能成功导出。如果该引脚已被内核中的其他驱动占用(例如作为 LCD 背光、SD 卡检测等),则导出会失败,echo 操作会提示 Device or resource busy。

2. 导出后生成的 gpioX 目录

该目录下包含多个属性文件,用于控制引脚:

  • direction:可读可写。写入 "in" 或 "out" 配置为输入或输出;读取可查看当前模式。
  • value:可读可写。
    • 输出模式:写入 "1" 输出高电平,"0" 输出低电平。
    • 输入模式:读取获取当前电平("0" 或 "1")。
  • active_low:可读可写,默认 "0"。当设置为 "1" 时,电平极性反转(即 value 写入 1 实际输出低电平,写入 0 输出高电平;输入同理)。
  • edge:可读可写,用于配置中断触发模式。可选值:
    • "none":无中断
    • "rising":上升沿触发
    • "falling":下降沿触发
    • "both":双边沿触发
      配置中断前需先将 direction 设为 "in"。

img

3. 常用操作示例

# 导出 GPIO 编号 0(即 GPIO1_IO0)
echo 0 > /sys/class/gpio/export# 配置为输出并设置高电平
echo out > /sys/class/gpio/gpio0/direction
echo 1 > /sys/class/gpio/gpio0/value# 配置为输入并读取电平
echo in > /sys/class/gpio/gpio0/direction
cat /sys/class/gpio/gpio0/value# 配置边沿触发中断
echo in > /sys/class/gpio/gpio0/direction
echo both > /sys/class/gpio/gpio0/edge# 删除导出
echo 0 > /sys/class/gpio/unexport

4. /sys/class/gpio 目录的来源

该目录下的文件并非由开发板厂商“提供”,而是由 Linux 内核在启动时动态创建。其生成过程如下:

  • 内核中的 GPIO 驱动(如 gpiolib)在初始化时调用 class_create 和 device_create 接口,在 /sys/class/ 下创建 gpio 目录及其子项。
  • 设备树(.dtb)描述了 SoC 中的 GPIO 控制器信息(如寄存器基地址、中断号等),内核解析设备树后匹配相应驱动,进而创建对应的 gpiochipX 目录。
  • export 和 unexport 文件由 gpiolib-sysfs 模块提供,当用户写入编号时,该模块负责动态生成 gpioX 目录。

5. gpiochipX 符号链接的实际指向

从上面的图可以看出,这些 gpiochip文件实际上是链接文件,它指向 /sys/devices/platform/soc/.../gpio/gpiochip0 等路径,那里是内核设备模型中该 GPIO 控制器的真实位置。/sys/class/ 下的目录为了方便应用层按功能查找,使用符号链接指向物理设备树。

如果查看芯片手册,就可以知道实际上209c000是gpio1_0的起始地址。
img

GPIO输出

1. 实现思路

操作 GPIO 输出电平的基本步骤:

  1. 确定 GPIO 编号。
  2. 检查 /sys/class/gpio/gpioX 目录是否存在,若不存在则通过 export 导出。
  3. 向 direction 文件写入 "out" 配置为输出模式。
  4. 向 value 文件写入 "1" 或 "0" 控制高低电平。

2. 示例程序

通过命令行参数控制可以指定 GPIO 输出电平的功能。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>static char gpio_path[100];static int gpio_config(const char *attr, const char *val)
{char file_path[100];int len;int fd;sprintf(file_path, "%s/%s", gpio_path, attr);if (0 > (fd = open(file_path, O_WRONLY))) {perror("open error");return fd;}len = strlen(val);if (len != write(fd, val, len)) {perror("write error");close(fd);return -1;}close(fd);return 0;
}int main(int argc, char *argv[])
{/* 校验传参 */if (3 != argc) {fprintf(stderr, "usage: %s <gpio> <value>\n", argv[0]);exit(-1);}/* 判断指定编号的 GPIO 是否导出 */sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);if (access(gpio_path, F_OK)) {  // 目录不存在则需要导出int fd;int len;if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {perror("open error");exit(-1);}len = strlen(argv[1]);if (len != write(fd, argv[1], len)) {  // 导出 gpioperror("write error");close(fd);exit(-1);}close(fd);}/* 配置为输出模式 */if (gpio_config("direction", "out"))exit(-1);/* 极性设置(默认 0) */if (gpio_config("active_low", "0"))exit(-1);/* 控制 GPIO 输出高低电平 */if (gpio_config("value", argv[2]))exit(-1);exit(0);
}

代码解释

程序执行时需要传入两个参数:argv[1] 为 GPIO 编号,argv[2] 为电平值("0" 或 "1")。运行过程如下:

  1. 通过 sprintf 构造 gpio_path,格式为 /sys/class/gpio/gpioX。
  2. 调用 access(gpio_path, F_OK) 判断该目录是否存在。若不存在(返回 -1),则打开 /sys/class/gpio/export 文件,将编号写入以完成导出。
  3. 调用 gpio_config("direction", "out"),该函数构造 gpio_path/direction 文件路径,打开并写入 "out"。
  4. 调用 gpio_config("active_low", "0"),设置极性为默认值(可选,默认即为 0)。
  5. 调用 gpio_config("value", argv[2]),向 value 文件写入 "1" 或 "0" 控制电平。

gpio_config 函数封装了打开属性文件 → 写入字符串 → 关闭的通用操作,简化了代码。

注意:写入 value 的内容必须是字符串形式,即 "1" 或 "0",不能是二进制整数 1 或 0。同样,direction 也要写入字符串 "out" 而非二进制值。

注意事项

程序中使用 access(gpio_path, F_OK) 判断目录是否存在,这是一种安全的做法。如果直接尝试导出已经导出的引脚,向 export 写入会返回错误,因为该引脚已被占用。

在输出模式下,虽然可以读取 value 文件,但读到的是最后写入的值,并非引脚实际电平(例如引脚被外部电路拉低时,读到的仍可能是 "1")。要获取实际电平,需配置为输入模式。

GPIO 输入

读取 GPIO 输入电平的流程与输出控制基本一致,主要区别在于将 direction 设置为 "in",然后通过读取 value 文件获取当前电平。该操作常用于检测按键状态、传感器数字信号等。

示例程序 gpio_in.c 与 gpio_out.c 结构非常相似,仅改动了配置和读取部分。下面是 main 函数中关键代码段。

/* 配置为输入模式 */
if (gpio_config("direction", "in"))exit(-1);/* 极性设置 */
if (gpio_config("active_low", "0"))exit(-1);/* 配置为非中断方式 */
if (gpio_config("edge", "none"))exit(-1);/* 读取 GPIO 电平状态 */
sprintf(file_path, "%s/%s", gpio_path, "value");if (0 > (fd = open(file_path, O_RDONLY))) {perror("open error");exit(-1);
}if (0 > read(fd, &val, 1)) {perror("read error");close(fd);exit(-1);
}printf("value: %c\n", val);

代码执行过程:

  1. 通过 gpio_config("direction", "in") 将引脚设为输入模式。
  2. active_low 保持默认 0,若需要极性反转可置 1,此时 value 读到的电平与实际物理电平相反。
  3. 向 edge 写入 "none" 明确关闭中断功能(默认即无中断,此步可省略,但显式写出可避免后续误配置)。
  4. 构造 value 文件的完整路径并打开(只读)。
  5. 调用 read 读取 1 个字符,存入 val,该字符为 '0' 或 '1'。
  6. 打印该字符。

读取 value 文件时,返回的是 ASCII 字符 '0' 或 '1',而非二进制数值 0 或 1。比较时应使用字符常量,如 if (val == '1')。

GPIO 中断

当需要检测引脚电平变化(比如按键按下、传感器触发)时,用轮询(死循环读 value)会白白消耗 CPU 资源,实际项目中更常见的做法是使用中断。GPIO 的中断也是通过 sysfs 来配置的:往 edge 文件写入 "rising"、"falling" 或 "both" 来设定触发边沿,然后用 poll 函数阻塞等待中断事件。

示例程序 gpio_intr.c 跟前面输入输出的代码非常相似,导出引脚和设置方向的代码几乎一样,关键的不同在于配置 edge 以及后续的 poll 等待逻辑。下面只给出 main 函数中与中断相关的核心代码段。

/* 配置为输入模式 */
if (gpio_config("direction", "in"))exit(-1);/* 极性设置 */
if (gpio_config("active_low", "0"))exit(-1);/* 配置中断触发方式: 上升沿和下降沿 */
if (gpio_config("edge", "both"))exit(-1);/* 打开 value 属性文件 */
sprintf(file_path, "%s/%s", gpio_path, "value");
if (0 > (pfd.fd = open(file_path, O_RDONLY))) {perror("open error");exit(-1);
}/* 调用 poll */
pfd.events = POLLPRI;  // 只关心高优先级数据可读(中断)read(pfd.fd, &val, 1); // 先读取一次清除状态
for ( ; ; ) {ret = poll(&pfd, 1, -1);  // 阻塞等待中断if (0 > ret) {perror("poll error");exit(-1);} else if (0 == ret) {fprintf(stderr, "poll timeout.\n");continue;}/* 校验高优先级数据是否可读 */if (pfd.revents & POLLPRI) {if (0 > lseek(pfd.fd, 0, SEEK_SET)) {perror("lseek error");exit(-1);}if (0 > read(pfd.fd, &val, 1)) {perror("read error");exit(-1);}printf("GPIO 中断触发<value=%c>\n", val);}
}

代码的执行逻辑可以分为几个步骤:

  1. 将引脚设为输入模式后,向 edge 文件写入 "both",表示上升沿和下降沿都会触发中断。你也可以根据需要改成 "rising" 或 "falling"。
  2. 打开 value 文件,获得文件描述符。注意这里只打开一次,后续 poll 和读写都复用这个描述符。
  3. 在进入主循环之前,先调用一次 read 把当前状态消费掉,避免之前的残留状态干扰后续的中断等待。
  4. poll 的 events 字段指定为 POLLPRI(高优先级数据可读),而不是普通的 POLLIN。GPIO 中断在内核里被归类为高优先级事件,用 POLLPRI 才能抓到。
  5. poll 返回后检查 revents 是否包含 POLLPRI,如果是则说明中断发生了。此时必须先 lseek 把文件偏移移到开头,然后再 read。这两个操作配合才能把内核里的中断状态清除掉,否则下次 poll 可能会直接返回,或者无法正确等待下一次中断。

在驱动中,读操作会让内核自动清理状态,第二次之所以需要用Iseek把读位置移动到头部正是因为第一次的读取使读位置后移了。

实际跑起来之后,用杜邦线把引脚在 3.3V 和 GND 之间来回碰一下,终端就会打印出相应的触发信息。

poll 函数在之前有学习过,具体参考我之前的笔记:https://juejin.cn/post/7660712622824718390#heading-7

它本来是用来同时监视多个文件描述符的,但在这里只监视一个 GPIO 的 value 文件,也能很好地完成阻塞等待中断的工作,比轮询节省 CPU 得多。

每次 poll 返回后,必须先 lseek 再 read,这一步是必须做的。其次,必须先读取一次清除状态,如果没有这一步,下一次 poll 可能会因为状态没有被清除而表现异常。

poll 的 -1 超时参数表示无限等待,直到中断发生。如果想让程序在超时后做点别的事情,可以设置一个正数超时值(单位毫秒)。

相关新闻

  • 长沙全区域 LV 回收,Neverfull 水桶包磨损款正规高价回收 - 讯息早知道
  • 2026山东郯城素法武术学校招生简章出炉,官方招生电话、小学初中职高学费全公示 - 全国文武学校招生
  • 智能体测开Day27

最新新闻

  • 官方|武汉助产学校招生简章(2026版) - 武汉中职最新信息发布
  • 【深度解析】PDF凭什么是macOS原生格式?一文吃透开放标准与Quartz图形系统的渊源,附跨平台避坑手册
  • Nuxt4 架构解析与基础语法篇
  • 地平线HSD V2.0端到端智驾深度解析|复现单基座一段式架构、世界模型训练、OCC统一安全底座、强化学习迭代、助力城市高速泊车全场景智驾有效涨点
  • 2026年7月最新河源防水补漏权威指南:卫生间/屋面/外墙/地下室正规施工+透明报价+避坑全攻略 - 吉林同城获客
  • 专网信噪比是什么?东北山林盲区通信,信噪比优化实操方案

日新闻

  • 佛山青少年训练营推荐:军博营地实力顶尖 - 秋山寄远
  • 如何快速上手PvZ2 Gardendless:免费Web版植物大战僵尸2完整指南
  • jiuwen-deepsearch核心功能详解:规划-检索-反思三合一智能工作流

周新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号