已移植好PCA9535的Linux系统上,PCA9535的GPIO基地址是gpio496,后续操作9535gpio只要操作496 + x,和主机内核普通GPIO一样操作。但是有多个PCA9535存在或者其他IO扩张芯片,驱动挂载顺序可能不同,这个GPIO序号可能是变化的。
gpiochip496
是一个用来标识 GPIO 控制器的名称,它是由 Linux 内核在初始化过程中根据 GPIO 控制器的配置动态生成的。当一个 GPIO 控制器被内核识别并初始化之后,内核会在 /sys/class/gpio
下创建对应的 gpiochipX
目录,这里的 X
是一个整数,用来区分不同的 GPIO 控制器。这个整数通常是按照 GPIO 控制器被加载到内核的顺序来分配的,因此 gpiochip496
表示的是第 497 个(因为计数是从 0 开始的)被内核初始化的 GPIO 控制器。
当一个 GPIO 芯片如 PCA9535 被驱动程序加载时,内核会给它分配一个唯一的 ID,并创建相应的 gpiochipX
目录。这个目录包含了一系列的文件,如 gpiochipX/label
、gpiochipX/base
等,用来提供关于 GPIO 芯片的一些基本信息。其中 base
文件记录了 GPIO 芯片所管理的 GPIO 引脚的起始编号,而 label
则包含了 GPIO 芯片的一个描述性标签。
对于 gpiochip496
,这意味着它是内核加载的第 497 个 GPIO 控制器,并且它的 GPIO 引脚编号将从某个基础值开始递增。例如,如果 gpiochip496/base
的值是 496,那么该控制器下的第一个 GPIO 引脚就会被命名为 gpio496
,第二个为 gpio497
,以此类推。
总结来说,gpiochip496
的确定和生成过程涉及到内核对 GPIO 控制器的初始化,并为每个控制器分配一个唯一的 ID,这个 ID 用于标识控制器,并帮助用户空间的应用程序定位和控制具体的 GPIO 引脚。这个 ID 是基于 GPIO 控制器被内核加载的顺序来决定的。
gpiochipX
的编号在每次启动 Linux 系统时可能会发生变化,这取决于系统上 GPIO 控制器的初始化顺序以及系统中 GPIO 控制器的数量。这些编号是在系统启动期间由内核动态分配的,通常是根据 GPIO 控制器被内核识别和初始化的顺序来决定的。
如果设备树(Device Tree)中的配置没有改变,并且硬件和驱动程序的加载顺序保持一致,那么 GPIO 控制器的编号通常会保持不变。然而,在实际应用中,由于各种因素(如模块加载顺序的变化、硬件检测的不确定性等),同一个 GPIO 控制器在不同启动之间可能会有不同的编号。
为了确保应用程序能够在不同的启动中正确地访问 GPIO 引脚,最好使用 GPIO 控制器的标签(label
)而不是其编号来引用它。在 /sys/class/gpio/gpiochipX/
目录下,有一个 label
文件,它包含了 GPIO 控制器的描述,这个描述通常是稳定的,并且可以用来唯一地标识一个 GPIO 控制器。
此外,现代 Linux 系统通常会为 GPIO 引脚提供 sysfs 接口,通过这个接口,用户空间程序可以查询 GPIO 控制器的信息,并且可以通过 label
或者其他元数据来查找和使用 GPIO 引脚,而不是依赖于可能变化的 gpiochipX
编号。
总之,虽然 gpiochipX
的编号可能会随启动而改变,但内核提供了机制来确保通过稳定的标识符(如 label
)来访问 GPIO 控制器及其引脚。因此,开发人员应该避免硬编码 GPIO 控制器的编号,而是使用更稳定的方式来引用它们。
使用 GPIO 控制器的标签(label
)是一种更加健壮的方法,可以在不同的启动之间可靠地引用 GPIO 引脚。下面是一个使用 C 语言编写的示例代码,展示了如何通过标签来查找和控制 GPIO 引脚。
示例代码
假设 PCA9535 的 GPIO 控制器标签为 pca9535
,我们将通过这个标签来查找并控制 GPIO 引脚。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#define PCA9535_LABEL "pca9535"
#define PCA9535_GPIO_COUNT 16
// 函数声明
int find_gpiochip_by_label(const char *label);
void setup_gpio(int gpiochip_fd, int gpio);
void write_gpio_value(int gpiochip_fd, int gpio, int value);
int main() {
int gpiochip_fd;
int gpio;
// 查找 GPIO 控制器
gpiochip_fd = find_gpiochip_by_label(PCA9535_LABEL);
if (gpiochip_fd == -1) {
fprintf(stderr, "Failed to find GPIO controller with label '%s'\n", PCA9535_LABEL);
return 1;
}
// 设置所有 GPIO 引脚为输出模式
for (gpio = 0; gpio < PCA9535_GPIO_COUNT; ++gpio) {
setup_gpio(gpiochip_fd, gpio);
}
// 写入 GPIO 值
for (gpio = 0; gpio < PCA9535_GPIO_COUNT; ++gpio) {
write_gpio_value(gpiochip_fd, gpio, 1); // 设置为高电平
usleep(100000); // 等待 100 ms
write_gpio_value(gpiochip_fd, gpio, 0); // 设置为低电平
usleep(100000); // 等待 100 ms
}
return 0;
}
int find_gpiochip_by_label(const char *label) {
DIR *dir;
struct dirent *entry;
char path[256];
int fd;
char buf[64];
snprintf(path, sizeof(path), "/sys/class/gpio/");
dir = opendir(path);
if (!dir) {
perror("Failed to open /sys/class/gpio");
return -1;
}
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR && strstr(entry->d_name, "gpiochip")) {
snprintf(path, sizeof(path), "/sys/class/gpio/%s/label", entry->d_name);
fd = open(path, O_RDONLY);
if (fd == -1) {
perror("Failed to open label file");
closedir(dir);
return -1;
}
read(fd, buf, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
close(fd);
if (strcmp(buf, label) == 0) {
closedir(dir);
return open(path, O_RDONLY);
}
}
}
closedir(dir);
return -1;
}
void setup_gpio(int gpiochip_fd, int gpio) {
char export_path[256];
char direction_path[256];
char direction[10] = "out";
int export_fd, direction_fd;
snprintf(export_path, sizeof(export_path), "/sys/class/gpio/export");
export_fd = open(export_path, O_WRONLY);
if (export_fd == -1) {
perror("Failed to open export file");
return;
}
write(export_fd, &gpio, sizeof(gpio));
close(export_fd);
snprintf(direction_path, sizeof(direction_path), "/sys/class/gpio/gpio%d/direction", gpio);
direction_fd = open(direction_path, O_WRONLY);
if (direction_fd == -1) {
perror("Failed to open direction file");
return;
}
write(direction_fd, direction, strlen(direction) + 1);
close(direction_fd);
}
void write_gpio_value(int gpiochip_fd, int gpio, int value) {
char value_path[256];
char val[3];
int value_fd;
snprintf(value_path, sizeof(value_path), "/sys/class/gpio/gpio%d/value", gpio);
value_fd = open(value_path, O_WRONLY);
if (value_fd == -1) {
perror("Failed to open value file");
return;
}
val[0] = value ? '1' : '0';
val[1] = '\0';
write(value_fd, val, 2);
close(value_fd);
}
代码解释
-
find_gpiochip_by_label
函数:- 该函数遍历
/sys/class/gpio
目录,查找带有指定标签(label
)的 GPIO 控制器。 - 如果找到匹配的标签,则返回对应的 GPIO 控制器的文件描述符。
- 该函数遍历
-
setup_gpio
函数:- 该函数负责导出 GPIO 引脚并将其设置为输出模式。
- 首先打开
/sys/class/gpio/export
文件并写入 GPIO 编号以导出引脚。 - 然后打开
/sys/class/gpio/gpioX/direction
文件并写入"out"
以设置引脚方向为输出。
-
write_gpio_value
函数:- 该函数用于设置 GPIO 引脚的值。
- 打开
/sys/class/gpio/gpioX/value
文件并写入'1'
或'0'
以设置引脚值为高电平或低电平。
注意事项
- 该示例代码假设 PCA9535 的 GPIO 控制器标签为
pca9535
。你需要根据实际情况调整标签名称。 - 该代码没有处理错误情况,例如文件打开失败等。在实际应用中,应该添加适当的错误处理逻辑。
- 如果你的系统中有多个 PCA9535 芯片,可能需要为每个芯片指定不同的标签,并且在代码中进行相应的处理。
通过这种方式,即使 GPIO 控制器的编号在不同启动之间发生变化,你仍然可以通过稳定的标签来引用和控制 GPIO 引脚。