尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

RV1106 Linux GPIO控制全攻略:从Shell命令到C程序实现

发布时间:2026/9/2 23:36:12

资讯中心
01
ARTICLE

RV1106 Linux GPIO控制全攻略:从Shell命令到C程序实现

RV1106 Linux GPIO控制全攻略:从Shell命令到C程序实现
最近在调试 RV1106 开发板时需要快速验证 GPIO 功能比如点亮一个 LED。网上资料要么是纯驱动开发要么是零散的脚本片段对于想快速上手应用层控制的开发者来说不够友好。本文将系统梳理在 RV1106 Linux 系统下从命令行到应用程序两种控制 GPIO 输出的完整方法包含详细的步骤、可复现的代码以及避坑指南无论是嵌入式新手还是需要快速验证硬件的工程师都能直接套用。1. 背景与核心概念在嵌入式 Linux 开发中GPIOGeneral-Purpose Input/Output通用输入输出是最基础也是最常用的外设接口之一。它允许软件通过简单的电平高/低来控制硬件如点亮 LED或读取硬件状态如按键输入。对于 RV1106 这类集成了丰富外设的 SoC其 GPIO 通常通过内核的 GPIO 子系统进行管理。在应用层我们有两种主流方式来操作 GPIOShell 命令方式通过操作/sys/class/gpio目录下的虚拟文件系统sysfs。这是最直接、无需编译的方法适合快速测试、脚本控制或系统初始化阶段。应用程序方式在 C/C 程序中通过标准的文件 I/O 操作open,read,write,close来读写上述 sysfs 接口或者使用更专业的库如 libgpiod。这种方式适合集成到正式的产品软件中实现更复杂的逻辑控制。为什么需要掌握这两种方式效率与灵活度Shell 命令适合一次性操作和调试应用程序适合自动化、长期运行和复杂交互。理解层次通过 Shell 操作可以直观理解 Linux GPIO 子系统的工作模型这是编写应用程序的基础。开发阶段在驱动调试和硬件验证阶段Shell 命令无可替代在应用软件开发阶段则需要稳定的程序化控制。本文将围绕 RV1106详细讲解这两种方式的具体实现。2. 环境准备与版本说明在开始操作前请确保你的开发环境已就绪。硬件环境RV1106 开发板本文以某常见开发板为例原理通用。USB 转串口调试线用于连接板子的调试串口如 UART2。一个 LED 灯及限流电阻通常开发板已集成需确认其连接的 GPIO 引脚号。例如假设 LED 正极通过电阻连接到GPIO0_C5。软件环境主机用于交叉编译的 Ubuntu Linux 系统或 Windows WSL2。RV1106 系统已烧录好 Linux 系统的 SD 卡或 eMMC。内核需支持 GPIO Sysfs 接口默认已开启。串口终端工具如minicom,picocom,PuTTY或 MobaXterm。本文使用minicom。交叉编译工具链用于编译 C 应用程序。例如arm-rockchip830-linux-uclibcgnueabihf-gcc。请根据你的 SDK 确定具体名称。关键版本信息Linux 内核RV1106 通常使用 Linux 4.19 或 5.10 内核。GPIO Sysfs 接口是标准功能版本差异影响不大。工具链需与 SDK 和内核版本匹配。开发板 GPIO 映射这是最重要的部分。你必须查阅你的开发板原理图或硬件手册确定 LED 连接的具体 GPIO 编号。注意在 Linux Sysfs 中操作的不是GPIO0_C5这样的物理组编号而是计算后的全局 GPIO 编号。如何计算 GPIO 编号RV1106 的 GPIO 通常按 Bank 分组如 GPIO0, GPIO1, GPIO2, GPIO3。每个 Bank 有多个 Pin如 A0-A7, B0-B7, C0-C7, D0-D7。 计算公式一般为全局编号 bank_index * 32 group_index * 8 pin_index。bank_index: GPIO0 为 0, GPIO1 为 1以此类推。group_index: A组为 0, B组为 1, C组为 2, D组为 3。pin_index: 引脚在组内的序号如 C5 的 pin_index 是 5。例如GPIO0_C5bank_index 0 (GPIO0)group_index 2 (C组)pin_index 5全局编号 0 * 32 2 * 8 5 21。因此下文中所有需要[GPIO_NUM]的地方请替换为你根据自己板子计算出的正确编号。本文示例将以21为例。3. Shell 命令方式控制 GPIO这是最快捷的验证方法全部在开发板的 Linux Shell 中完成。3.1 Sysfs GPIO 接口简介Linux 内核提供了/sys/class/gpio目录通过文件操作来抽象 GPIO 控制。主要涉及以下文件/sys/class/gpio/export写入 GPIO 编号用于申请导出该 GPIO 的控制权。/sys/class/gpio/unexport写入 GPIO 编号用于释放该 GPIO。/sys/class/gpio/gpio[GPIO_NUM]/导出后生成的目录包含控制该 GPIO 的具体文件。direction设置方向in为输入out为输出。value读写值0为低电平1为高电平。对于输出模式的 GPIO写此文件即可控制电平。edge设置中断触发边沿如rising,falling,both输入模式且支持中断时使用。3.2 完整操作步骤假设我们要控制 GPIO 21对应GPIO0_C5输出高低电平从而控制 LED 亮灭。步骤 1连接串口登录系统使用串口终端工具连接开发板上电启动以 root 用户或具有 sudo 权限的用户登录。步骤 2导出 GPIO向export文件写入 GPIO 编号 21内核会为我们创建/sys/class/gpio/gpio21目录。echo 21 /sys/class/gpio/export执行后检查目录是否生成ls /sys/class/gpio/你应该能看到gpio21目录。步骤 3设置 GPIO 方向为输出进入该 GPIO 的控制目录并设置其方向为输出。echo out /sys/class/gpio/gpio21/direction可以读取direction文件确认cat /sys/class/gpio/gpio21/direction输出应为out。步骤 4控制 GPIO 输出电平点灯/灭灯输出高电平假设高电平点亮 LEDecho 1 /sys/class/gpio/gpio21/value观察开发板上的 LED 是否点亮。输出低电平熄灭 LEDecho 0 /sys/class/gpio/gpio21/value观察 LED 是否熄灭。步骤 5释放 GPIO可选操作完成后如果需要释放该 GPIO特别是在驱动中避免资源占用可以执行echo 21 /sys/class/gpio/unexport执行后gpio21目录会消失。3.3 封装成 Shell 脚本为了方便测试可以将上述命令写成一个 Shell 脚本led_control.sh。#!/bin/bash # led_control.sh - 控制 GPIO21 连接的 LED # 用法: ./led_control.sh [on|off|blink|status] GPIO_NUM21 GPIO_PATH/sys/class/gpio/gpio$GPIO_NUM SYSFS_GPIO/sys/class/gpio # 检查并导出 GPIO if [ ! -d $GPIO_PATH ]; then echo $GPIO_NUM $SYSFS_GPIO/export sleep 0.1 # 稍等片刻让内核创建目录 fi # 确保方向为输出 echo out $GPIO_PATH/direction case $1 in on) echo 1 $GPIO_PATH/value echo LED ON ;; off) echo 0 $GPIO_PATH/value echo LED OFF ;; blink) for i in {1..5}; do echo 1 $GPIO_PATH/value sleep 0.5 echo 0 $GPIO_PATH/value sleep 0.5 done echo Blink finished ;; status) VAL$(cat $GPIO_PATH/value) DIR$(cat $GPIO_PATH/direction) echo GPIO$GPIO_NUM: direction$DIR, value$VAL ;; *) echo Usage: $0 {on|off|blink|status} exit 1 ;; esac # 注意脚本结束后不会 unexport GPIO便于后续操作。给脚本添加执行权限并运行chmod x led_control.sh ./led_control.sh on ./led_control.sh blink ./led_control.sh status4. 应用程序方式控制 GPIOC语言对于嵌入式产品我们更需要一个编译好的可执行文件来集成到系统中。下面通过 C 语言程序实现同样的功能。4.1 程序设计思路程序的核心逻辑与 Shell 命令一致只是将echo、cat等命令替换为标准的文件 I/O 函数fopen,fprintf,fclose等。我们将编写一个健壮的程序包含错误处理。4.2 完整代码示例创建一个文件gpio_led.c。/** * gpio_led.c - 通过 Sysfs 控制 GPIO 输出示例 * 编译: arm-rockchip830-linux-uclibcgnueabihf-gcc -o gpio_led gpio_led.c * 运行: ./gpio_led [on|off|blink|status] */ #include stdio.h #include stdlib.h #include string.h #include unistd.h #include fcntl.h #include errno.h #define GPIO_NUM 21 #define SYSFS_GPIO_DIR /sys/class/gpio #define GPIO_EXPORT SYSFS_GPIO_DIR /export #define GPIO_UNEXPORT SYSFS_GPIO_DIR /unexport #define GPIO_DIR_FMT SYSFS_GPIO_DIR /gpio%d/direction #define GPIO_VAL_FMT SYSFS_GPIO_DIR /gpio%d/value /** * 向指定文件写入字符串 * param filename 文件路径 * param value 要写入的字符串 * return 成功返回0失败返回-1 */ int write_to_file(const char *filename, const char *value) { FILE *fp fopen(filename, w); if (fp NULL) { perror(Error opening file for writing); return -1; } if (fprintf(fp, %s, value) 0) { perror(Error writing to file); fclose(fp); return -1; } fclose(fp); return 0; } /** * 从指定文件读取字符串 * param filename 文件路径 * param buf 存储读取内容的缓冲区 * param buf_size 缓冲区大小 * return 成功返回0失败返回-1 */ int read_from_file(const char *filename, char *buf, size_t buf_size) { FILE *fp fopen(filename, r); if (fp NULL) { perror(Error opening file for reading); return -1; } if (fgets(buf, buf_size, fp) NULL) { perror(Error reading from file); fclose(fp); return -1; } // 去除可能的换行符 buf[strcspn(buf, \n)] 0; fclose(fp); return 0; } /** * 导出 GPIO */ int gpio_export(int gpio_num) { char export_path[128]; snprintf(export_path, sizeof(export_path), GPIO_EXPORT); char num_str[8]; snprintf(num_str, sizeof(num_str), %d, gpio_num); return write_to_file(export_path, num_str); } /** * 设置 GPIO 方向 */ int gpio_set_direction(int gpio_num, const char *direction) { char dir_path[128]; snprintf(dir_path, sizeof(dir_path), GPIO_DIR_FMT, gpio_num); return write_to_file(dir_path, direction); } /** * 设置 GPIO 输出值 */ int gpio_set_value(int gpio_num, int value) { char val_path[128]; snprintf(val_path, sizeof(val_path), GPIO_VAL_FMT, gpio_num); char val_str[2] { value 0, \0 }; // 将整数0/1转为字符0/1 return write_to_file(val_path, val_str); } /** * 读取 GPIO 当前值 */ int gpio_get_value(int gpio_num, int *value) { char val_path[128]; snprintf(val_path, sizeof(val_path), GPIO_VAL_FMT, gpio_num); char buf[8]; if (read_from_file(val_path, buf, sizeof(buf)) ! 0) { return -1; } *value atoi(buf); return 0; } /** * 取消导出 GPIO */ int gpio_unexport(int gpio_num) { char unexport_path[128]; snprintf(unexport_path, sizeof(unexport_path), GPIO_UNEXPORT); char num_str[8]; snprintf(num_str, sizeof(num_str), %d, gpio_num); return write_to_file(unexport_path, num_str); } int main(int argc, char *argv[]) { if (argc ! 2) { fprintf(stderr, Usage: %s [on|off|blink|status]\n, argv[0]); return EXIT_FAILURE; } const char *action argv[1]; int ret 0; // 1. 尝试导出 GPIO (如果尚未导出) // 在实际产品代码中导出操作可能由初始化脚本完成这里为了演示的完整性而保留。 // 注意重复导出可能会返回错误EBUSY我们可以忽略这个错误。 ret gpio_export(GPIO_NUM); if (ret ! 0 errno ! EBUSY) { // 如果不是“设备忙”错误则报错退出 fprintf(stderr, Failed to export GPIO %d\n, GPIO_NUM); // 继续执行可能GPIO已被其他进程导出 } // 2. 设置方向为输出 if (gpio_set_direction(GPIO_NUM, out) ! 0) { fprintf(stderr, Failed to set GPIO %d direction\n, GPIO_NUM); // 不立即退出尝试后续操作 } // 3. 根据参数执行动作 if (strcmp(action, on) 0) { ret gpio_set_value(GPIO_NUM, 1); printf(Set GPIO%d to HIGH (LED ON)\n, GPIO_NUM); } else if (strcmp(action, off) 0) { ret gpio_set_value(GPIO_NUM, 0); printf(Set GPIO%d to LOW (LED OFF)\n, GPIO_NUM); } else if (strcmp(action, blink) 0) { printf(Blinking GPIO%d (LED)...\n, GPIO_NUM); for (int i 0; i 10; i) { gpio_set_value(GPIO_NUM, 1); usleep(500000); // 500ms gpio_set_value(GPIO_NUM, 0); usleep(500000); // 500ms } printf(Blink finished.\n); } else if (strcmp(action, status) 0) { int value -1; char direction[16] {0}; char dir_path[128]; snprintf(dir_path, sizeof(dir_path), GPIO_DIR_FMT, GPIO_NUM); read_from_file(dir_path, direction, sizeof(direction)); if (gpio_get_value(GPIO_NUM, value) 0) { printf(GPIO%d: direction%s, value%d\n, GPIO_NUM, direction, value); } else { printf(Failed to read GPIO%d status.\n, GPIO_NUM); } } else { fprintf(stderr, Invalid action: %s\n, action); fprintf(stderr, Usage: %s [on|off|blink|status]\n, argv[0]); ret -1; } // 4. 注意在实际长期运行的程序中通常不会在这里 unexport。 // gpio_unexport(GPIO_NUM); return (ret 0) ? EXIT_SUCCESS : EXIT_FAILURE; }4.3 交叉编译与运行在主机上交叉编译# 请将交叉编译工具链路径加入PATH或使用绝对路径 arm-rockchip830-linux-uclibcgnueabihf-gcc -o gpio_led gpio_led.c -static-static选项进行静态链接确保可执行文件在目标板上无需额外的动态库也能运行。将可执行文件传输到 RV1106 开发板 可以通过scp、adb push或 SD 卡拷贝。scp gpio_led root[开发板IP]:/root/在开发板上运行# 登录开发板串口或ssh cd /root chmod x gpio_led ./gpio_led on ./gpio_led blink ./gpio_led status5. 常见问题与排查思路在操作 GPIO 时你可能会遇到以下问题问题现象可能原因排查思路与解决方案echo: write error: Device or resource busy1. GPIO 已被内核驱动占用如用作 SPI、I2C 功能。2. GPIO 已被其他进程导出。1. 检查设备树dts配置确认该 GPIO 未被复用为其他功能。2. 检查/sys/class/gpio/gpio[GPIO_NUM]目录是否存在若存在则无需再次导出。echo: write error: Invalid argument1. 写入的 GPIO 编号超出系统范围。2. 向direction文件写入的值不是in或out。1. 确认 GPIO 编号计算正确。RV1106 的 GPIO 总数可查阅芯片手册。2. 检查命令拼写确保是echo out direction。Permission denied当前用户没有/sys/class/gpio目录的写权限。1. 使用root用户执行命令。2. 或通过sudo提权。3. 检查/sys/class/gpio的权限通常应为root:root。LED 不亮/常亮1. GPIO 编号错误。2. LED 极性接反低电平点亮。3. 硬件电路问题如电阻过大、LED损坏。4. GPIO 驱动未加载或配置。1.双重检查 GPIO 编号这是最常见错误。2. 尝试输出相反电平写0看是否亮。3. 用万用表测量 GPIO 引脚电压是否随命令变化。4. 检查内核启动日志 dmesg应用程序编译失败1. 交叉编译工具链未安装或路径错误。2. 代码语法错误。1. 确认工具链名称和路径which arm-rockchip830-linux-uclibcgnueabihf-gcc。2. 先在主机上用gcc编译测试语法。程序运行无效果1. 程序逻辑错误文件路径拼接错误。2. 程序没有执行权限。3. 静态链接失败动态库缺失。1. 在程序中加入更多printf调试打印每一步操作的文件路径和返回值。2.chmod x your_program。3. 使用file your_program查看是否静态链接或使用ldd检查依赖在主机上检查交叉编译版本。通用排查流程确认硬件连接原理图、GPIO 编号。确认内核支持ls /sys/class/gpio查看是否有export文件。简化测试先用 Shell 命令手动操作排除硬件和基础驱动问题。分步验证导出 - 设置方向 - 设置值每一步都检查是否有错误输出并查看生成的文件内容cat direction,cat value。查看系统日志dmesg | tail查看内核是否有相关报错。6. 最佳实践与工程建议在实际项目中直接使用 Sysfs 操作 GPIO 虽然简单但在性能、稳定性和资源管理上存在局限。以下是一些进阶建议GPIO 资源管理谁导出谁释放在应用程序初始化时导出 GPIO在程序退出或清理阶段取消导出。避免 GPIO 资源泄漏。避免重复导出在导出前先检查/sys/class/gpio/gpioXX目录是否存在。如果存在可能已被系统脚本或其他进程管理不应重复操作。使用 libgpiod对于新项目强烈推荐使用libgpiod库。它提供了更规范、更高效的 API并且是 Linux 社区主推的 GPIO 用户空间控制库。它通过字符设备/dev/gpiochipX进行操作避免了 Sysfs 的一些性能和并发问题。错误处理应用程序中所有文件操作fopen,fprintf,fclose都必须检查返回值。区分错误类型权限错误、设备忙错误、参数错误等并给出明确的日志信息。考虑设置超时机制防止因 GPIO 状态异常导致程序卡死。性能考量Sysfs 方式每次操作都需要进行文件系统读写延迟较高毫秒级不适合用于高频翻转如模拟 PWM 信号。高频操作应使用内核驱动或硬件 PWM。如果需要控制多个 GPIO频繁地打开/关闭文件会带来较大开销。可以考虑在程序启动时一次性打开所有需要的value文件描述符open然后使用write和read进行读写最后统一关闭。配置化与可移植性不要将 GPIO 编号硬编码在代码中。应该通过配置文件、环境变量或命令行参数传入。示例改进// 从配置文件读取或命令行参数获取 int gpio_num get_gpio_num_from_config(); // 或者 // int gpio_num atoi(argv[1]);这样同一份代码可以轻松适配不同板卡或不同的 LED 连接。生产环境建议使用设备树Device Tree对于固定的硬件功能如电源使能、复位引脚最佳实践是在内核设备树中将该 GPIO 定义为gpio-leds、gpio-keys或简单的fixed-regulator等标准节点。这样内核会在启动时自动配置和管理无需用户空间干预。考虑使用内核驱动如果 GPIO 控制逻辑复杂或实时性要求高应编写一个简单的内核驱动模块通过ioctl或sysfs属性文件提供更高效的控制接口。权限与安全如果非 root 用户需要控制 GPIO可以通过 udev 规则修改/sys/class/gpio下相关文件的属组和权限但需谨慎评估安全风险。代码结构优化将 GPIO 操作封装成一个独立的模块或类在 C 中提供init(),set_value(),get_value(),deinit()等接口。这样主业务逻辑与硬件操作解耦便于测试和维护。掌握 Shell 命令和应用程序两种 GPIO 控制方式是进行 RV1106 乃至任何嵌入式 Linux 平台开发的基本功。从简单的点灯开始理解 Sysfs 的运作机制再逐步深入到更高效、更工程化的控制方法是嵌入式 Linux 应用开发者的典型成长路径。建议读者在理解本文示例的基础上尝试用libgpiod库重写控制程序并查阅 RV1106 的芯片手册了解其 GPIO 复用功能为更复杂的项目打下基础。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。