2. GPIO控制

GPIO是General Purpose I/O的缩写,即通用输入输出端口,简单来说就是MCU/CPU可控制的引脚, 这些引脚通常有多种功能,最基本的是高低电平输入检测和输出,部分引脚还会与主控器的片上外设绑定, 如作为串口、I2C、网络、电压检测的通讯引脚。

安卓使用Linux内核,而Linux内核提供了GPIO子系统驱动框架,使用该驱动框架即可灵活地控制板子上的GPIO。

2.1. GPIO命名

Rockchip Pin的ID按照 控制器(bank)+端口(port)+索引序号(pin) 组成。

  • 控制器和GPIO控制器数量⼀致

  • 端口固定 A、B、C和D,每个端口仅有8个索引号,(a=0,b=1,c=2,d=3)

  • 索引序号固定 0、1、2、3、4、5、6、7

rk芯片具有多个GPIO控制器,每个控制器可以控制32个IO,作为GPIO功能时,端口⾏为由GPIO控制器寄存器配置。

GPIO1_C4表达的意思为第1组控制器,端口号为C,索引号为4。该引脚号的计算公式为32 x 1 + 2 x 8 + 4 = 52

部分板卡除了主控芯片原生IO,也有使用i2c扩展gpio,例如鲁班猫5,使用两颗XL9535芯片,每个扩展芯片可以扩展16路gpio,使用与原生gpio无异。

2.2. 使用GPIO sysfs接口控制IO

在Linux中,最常见的读写GPIO方式就是用GPIO sysfs interface, 是通过操作 /sys/class/gpio 目录下的 exportunexportgpio{N}/direction, gpio{N} /value (用实际引脚号替代{N})等文件实现的,在安卓中同样可以使用该方式控制GPIO。

GPIO举例计算

引脚

控制器

端口号

索引号

计算结果

GPIO1_C4

1

C

4

52 (32 x 1 + 8 x 2 + 4)

GPIO3_B2

3

B

2

106 (32 x 3 + 8 x 1 + 2)

GPIO0_C6

0

C

6

22 (32 x 0 + 8 x 2 + 6)

40pin引脚对照图章节已经列出引出排针的GPIO具体名称,并且“编号”一列已经计算好GPIO引脚对应的控制编号,例如LubanCat-3的GPIO0_C6,对应控制编号为22。以下以控制LubanCat-3的GPIO0_C6为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#连接adb shell
adb shell

#以下所有操作均需要打开管理者权限使用
#切换root用户
su root

#使能引脚GPIO0_C6
echo 22 > /sys/class/gpio/export

#设置引脚为输入模式
echo in > /sys/class/gpio/gpio22/direction
#读取引脚的值
cat /sys/class/gpio/gpio22/value

#设置引脚为输出模式
echo out > /sys/class/gpio/gpio22/direction
#设置引脚为低电平
echo 0 > /sys/class/gpio/gpio22/value
#设置引脚为高电平
echo 1 > /sys/class/gpio/gpio22/value

#复位引脚
echo 22 > /sys/class/gpio/unexport

2.3. 使用安卓应用控制IO

野火提供的安卓综合测试应用能很方便的控制板卡GPIO,包括控制引脚输入输出模式、高低电平、读取电平。

2.3.1. 应用测试

可参考“野火安卓综合测试应用”章节的“GPIO控制”小节进行测试,如下图:

使用安卓应用控制IO

2.3.2. 实现方式

GPIO控制对应的jni接口同样通过sysfs接口的方式实现,参考野火应用源码ebf_android_app/app/src/main/cpp/gpio.cpp

在Activity中声明JNI方法进行使用:

ebf_android_app/app/src/main/java/com/example/ebf_android_app/GpioActivity.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 导出GPIO
public native boolean exportGpio(int controller, char port, int index);
// 设置GPIO高电平
public native boolean setGpioHigh(int controller, char port, int index);
// 设置GPIO低电平
public native boolean setGpioLow(int controller, char port, int index);
// 设置GPIO输入模式
public native boolean setGpioInput(int controller, char port, int index);
// 设置GPIO输出模式
public native boolean setGpioOutput(int controller, char port, int index);
// 读取GPIO电平
public native int readGpioValue(int controller, char port, int index);

值得注意的是导出GPIO后会在/sys/class/gpio/目录下生成对应gpio[编号]的新目录,里面的文件普通用户默认是没有写权限的, 因此在Activity中使用exportGpio导出GPIO后要给gpio[编号]目录下的文件添加权限, 在野火应用源码的GpioActivity.java中提供了executeRootCommand函数用来执行root命令, 通过executeRootCommand函数执行chmod命令为gpio[编号]目录下的文件添加权限。

ebf_android_app/app/src/main/java/com/example/ebf_android_app/GpioActivity.java
1
private boolean executeRootCommand(String command)

那么控制GPIO为输出模式并设置高电平的流程为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 初始化GPIO参数
int controller = 1;         // 控制器编号
char port = 'A';            // 端口
int index = 0;              // 引脚索引

// 设置export权限
boolean exportResult = executeRootCommand("chmod 777 /sys/class/gpio/export");

// 导出GPIO
boolean exportResult = exportGpio(controller, port, index);

// 计算GPIO编号并设置权限
int gpioNum = controller * 32 + (port - 'A') * 8 + index;
boolean permResult = executeRootCommand("chmod 777 /sys/class/gpio/gpio" + gpioNum + "/*");

// 配置为输出模式
boolean setOutputResult = setGpioOutput(controller, port, index);

// 设置高电平
boolean setHighResult = setGpioHigh(controller, port, index);

由于野火测试应用是不确定用户选择控制的引脚是哪个,那么必须每次新导出引脚都要及时添加权限, 如果自己的项目中明确使用哪些GPIO,也可以在系统shell脚本中提前导出和添加权限,在Activity中省去添加权限步骤。

系统自启动的shell脚本位于/system/bin/android_shell.sh,由/vendor/etc/init/init.rk3588.rc调用执行(如果是其他芯片则是[对应芯片名字].rc),以提前导出GPIO4_A0并添加权限为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#重新挂载系统
adb root && adb remount

#拉取系统中的android_shell.sh
adb pull system/bin/android_shell.sh



#电脑文本软件打开android_shell.sh,在其末尾添加导出和添加权限命令,GPIO4_A0编号为128
echo 128 > /sys/class/gpio/export
chmod 777 /sys/class/gpio/gpio128/*



#将修改后的android_shell.sh的覆盖到系统system/bin/
adb push android_shell.sh system/bin/

#重启系统
adb reboot

#查看目录权限
adb shell ls -l /sys/class/gpio/gpio128/*
#信息输出如下
-rwxrwxrwx 1 root root 4096 2025-07-29 09:46 /sys/class/gpio/gpio128/active_low
lrwxrwxrwx 1 root root    0 2025-07-29 09:46 /sys/class/gpio/gpio128/device -> ../../../gpiochip4
-rwxrwxrwx 1 root root 4096 2025-07-29 09:46 /sys/class/gpio/gpio128/direction
-rwxrwxrwx 1 root root 4096 2025-07-29 09:46 /sys/class/gpio/gpio128/edge
lrwxrwxrwx 1 root root    0 2025-07-29 09:46 /sys/class/gpio/gpio128/subsystem -> ../../../../../../../class/gpio
-rwxrwxrwx 1 root root 4096 2025-07-29 09:46 /sys/class/gpio/gpio128/uevent
-rwxrwxrwx 1 root root 4096 2025-07-29 09:46 /sys/class/gpio/gpio128/value

最终可以看到重启系统后,默认生成gpio128目录,并且gpio128目录下的文件普通用户也具有读写权限。

如需修改SDK源码来修改android_shell.sh,该文件位于SDK源码/device/rockchip/rk[具体芯片]/android_shell.sh,由SDK源码/device/rockchip/rk[具体芯片]/init.rk[具体芯片].rc中调用脚本, 由SDK源码/device/rockchip/rk[具体芯片]/device.mk中拷贝脚本进系统。

2.4. FAQs

Q1:当使用GPIO时出现 gpioset: error setting the GPIO line values: Device or resource busy 或者 -bash: echo: 写错误: 设备或资源忙

A1:说明GPIO被占用了,占用的原因可能是设备树里把该引脚作为gpio或者其他复用功能被使用了。