LV530-gpio调试
在 linux 下,如何对 gpio 进行控制?怎么调试?若笔记中有错误或者不合适的地方,欢迎批评指正 😃。
一、准备工作
这里既然是做 GPIO 的调试,那肯定要先知道几个 GPIO,我这里用的是整点原子 alpha 开发板,我们准备三种 GPIO 用于测试。我们选择连接了外设的 GPIO,例如连接了 I2C 设备的 I2C1,LED 灯以及按键这些,用来测试的话现象比较直观。
1. LED 灯
LED 灯的硬件原理图如下:

搜一下原理图,就可以知道这个 LED 灯所连的 GPIO 为 GPIO1_IO03,相关的寄存器如下:
0X020E0068 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
0X020E02F4 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
0x0209C000 GPIO data register (GPIO1_DR)
0x0209C004 GPIO direction register (GPIO1_GDIR)
0X0209C008 GPIO pad status register (GPIO1_PSR)
0X0209C00C GPIO interrupt configuration register1 (GPIO1_ICR1)
0X0209C010 GPIO interrupt configuration register2 (GPIO1_ICR2)
0X0209C014 GPIO interrupt mask register (GPIO1_IMR)
0X0209C018 GPIO interrupt status register (GPIO1_ISR)
0X0209C01C GPIO edge select register (GPIO1_EDGE_SEL)2. KEY 按键
接下来是按键:

这个按键 key0 是使用的 GPIO 为 UART1_CTS,我们可以用它来作为输入或者中断,它可以复用为 GPIO1_IO18,相关的寄存器如下:
0x020E008C IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B
0x020E0318 IOMUXC_SW_PAD_CTL_PAD_UART1_CTS_B
0x0209C000 GPIO data register (GPIO1_DR)
0x0209C004 GPIO direction register (GPIO1_GDIR)
0X0209C008 GPIO pad status register (GPIO1_PSR)
0X0209C00C GPIO interrupt configuration register1 (GPIO1_ICR1)
0X0209C010 GPIO interrupt configuration register2 (GPIO1_ICR2)
0X0209C014 GPIO interrupt mask register (GPIO1_IMR)
0X0209C018 GPIO interrupt status register (GPIO1_ISR)
0X0209C01C GPIO edge select register (GPIO1_EDGE_SEL)3. I2C1——ap3216c
我们还有一个 I2C1,上面接了 ap3216c:

I2C1 其实是可以用由不同的 GPIO 复用而来的:

但是在正点原子 alpha 开发板上,其中 I2C1_SCL 使用的 UART4_TXD 这个 IO、 I2C1_SDA 使用的是 UART4_RXD 这个 IO。 所以我们也就要看 UART4_TX_DATA 和 UART4_RX_DATA 这两个引脚。前面学习 pinctrl 子系统的时候有分析过这个 UART4_TX_DATA 相关寄存器的取值情况:
0x020E00b4 IOMUXC_SW_MUX_CTL_PAD_UART4_TX_DATA (0x2)
0x020E0340 IOMUXC_SW_PAD_CTL_PAD_UART4_TX_DATA (0x4001b8b0)
0x020E05A4 IOMUXC_I2C1_SCL_SELECT_INPUT (0x1)4. 参考文档
linux 内核源码中有一些参考文档可以看:sysfs.txt - Documentation/gpio/sysfs.txt
二、GPIO 调试
先来看怎么调试把,后面控制的时候也会有一些问题,可能需要查看引脚情况。
1. debugfs 文件系统
1.1 debugfs 简介
debugfs 是 Linux 内核提供的一个调试文件系统, 可以用于查看和调试内核中的各种信息,包括 GPIO 的使用情况。 通过挂载 debugfs 文件系统, 并查看/sys/kernel/debug/目录下的相关文件, 可以获取 GPIO 的状态, 配置和其他调试信息。如下图:

1.2 debug 目录下没有文件?
我们打开这个目录,发现什么都没有:
这个时候怎么办?
1.2.1 内核配置
我们先看一下 linux 系统是否支持 debugfs 文件系统:
cat /proc/filesystems
发现是支持的,若是不支持的话,我们需要在内核打开对应的配置项,我们执行:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig按照以下路径,找到对应的菜单项:
Kernel hacking --->
Compile-time checks and compiler options --->
-*- Debug Filesystem
勾选后,我们把它编译进内核,然后重新编译内核并更新。
1.2.2 挂载 debugfs
我们已经支持了 debugfs 文件系统,但是 debug 目录还是空的,我们可以执行 mount 命令看一下是不是没挂载:
mount
发现确实没有挂载,然后我们可以手动挂载:
mount -t debugfs none /sys/kernel/debug/
然后就有对应的目录了,要是想开机自动挂载的话,可以修改 /etc/fstab 文件自动挂载到该目录:
debugfs /sys/kernel/debug debugfs defaults 0 0
1.3 /sys/kernel/debug/gpio
1.3.1 这个文件是什么?
在 Linux 系统中,/sys/kernel/debug/gpio 文件提供了关于系统 GPIO(通用输入输出)状态的调试信息。通过解析该文件,可以查看 GPIO 引脚的使用情况、配置和当前状态。
1.3.2 gpio 文件结构
文件内容通常分为两部分:GPIO 控制器信息 和 具体 GPIO 引脚状态,例如 imx6ull:

GPIO 控制器信息:
gpiochipx:看图中,x 的值为 0、1、2、3、4,这就是 imx6ull 的 5 组 GPIO。GPIOs x-y:该控制器管理的 GPIO 编号范围(全局编号)。parent:控制器所属的硬件设备(如芯片或外设地址)。platform/209c000.gpio:是 GPIO 控制器的名称或地址。
GPIO 引脚状态:
gpio-3、gpio-9和gpio-19表示这几个 gpio 已经被内核空间或者用户空间占用。
(1)in 表示引脚是输入模式,out 表示输出模式。
(2)hi 表示引脚当前为高电平,lo 表示低电平。
(3)有时候还会有 IRQ ,表示该引脚被配置为中断引脚。
1.3.2 总结一下
/sys/kernel/debug/gpio 是一个虚拟文件系统(debugfs)中的文件,用于显示当前系统中 GPIO(通用输入输出)引脚的状态和配置信息。它通常包含以下信息:
- GPIO 控制器信息:系统中每个 GPIO 控制器的名称和状态、控制器的基地址或标识符。
- GPIO 使用情况:哪些 GPIO 引脚已被占用(被内核或用户空间程序使用)、使用该引脚的驱动或模块的名称。
- GPIO 引脚状态:每个 GPIO 引脚的编号(如
gpiochipX中的引脚)、引脚的方向(输入或输出)、引脚的当前值(高电平或低电平)、引脚是否被配置为中断引脚。 - GPIO 中断信息:如果 GPIO 引脚被配置为中断引脚,可能会显示中断触发类型(如上升沿、下降沿等)。
1.4 /sys/kernel/debug/pinctrl
当进入/sys/kernel/debug/pinctrl 目录时, 我们可以获取有关 GPIO 控制器的调试信息。在该目录下, 通常会有以下文件和目录:
- (1)
/sys/kernel/debug/pinctrl/*/pinmux-pins: 这些文件列出了每个 GPIO 引脚的引脚复用配置。
可以查看每个引脚的功能模式、 引脚复用选择以及其他相关的配置信息。 我们进入下面的目录:
cd /sys/kernel/debug/pinctrl/20e0000.iomuxc/
- (2)
/sys/kernel/debug/pinctrl/*/pins:这些文件列出了 GPIO 的引脚编号, 可以查看 GPIO 编号。
我们进入下面的目录看一下:
ls /sys/kernel/debug/pinctrl/20e0000.iomuxc
cat /sys/kernel/debug/pinctrl/20e0000.iomuxc/pins
- (3)
/sys/kernel/debug/pinctrl/*/gpio-ranges:这些文件列出了每个 GPIO 控制器支持的 GPIO 范围。
cd /sys/kernel/debug/pinctrl/20e0000.iomuxc
cat cd /sys/kernel/debug/pinctrl/20e0000.iomuxc/gpio-ranges
- (4)
/sys/kernel/debug/pinctrl/*/pinmux-functions:这些文件列出了每个功能模式的名称以及与之关联的 GPIO 引脚。
ls /sys/kernel/debug/pinctrl/20e0000.iomuxc/
cat /sys/kernel/debug/pinctrl/20e0000.iomuxc/pinmux-functions
- (5)
/sys/kernel/debug/pinctrl/*/pingroups:该路径提供有关用于配置和控制系统上的 GPIO 引脚的引脚组的信息。 - (6)
/sys/kernel/debug/pinctrl/*/pinconf-pins:这些文件包含了 GPIO 引脚的配置信息, 如输入/输出模式、 上拉/下拉设置等。可以查看和修改 GPIO 的电气属性, 以便进行 GPIO 的调试和配置。
2. /dev/mem 设备
2.1 /dev/mem 简介
/dev/mem 是 Linux 系统中的一个虚拟设备, 通常与 mmap 结合使用, 可以将设备的物理内存映射到用户态, 以实现用户空间对内核态的直接访问。 无论是标准 Linux 系统还是嵌入式 Linux 系统, 都支持使用/dev/mem 设备。
/dev/mem 设备是内核所有物理地址空间的全映像,这些地址包括:
- 物理内存(RAM)空间
- 物理存储(ROM)空间
- cpu 总线地址
- cpu 寄存器地址
- 外设寄存器地址,GPIO、定时器、ADC
因为涉及访问内核空间,因此 只有 root 用户才有访问“/dev/mem”设备的权限。
2.2 支持/dev/mem
然而, 直接访问内核空间是一项潜在危险的操作, 因此只有 root 用户才能访问/dev/mem 设备。 此外有些系统可能需要单独启动/dev/mem 设备的功能。 我们执行:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig按照以下路径,找到对应的菜单项:
Device Drivers --->
Character devices --->
[*] /dev/mem virtual device support
打开之后,编译更新内核,当出现 /dev/mem 设备的时候说明开启成功:
ls /dev/mem2.3 /dev/mem 设备的使用
2.3.1 使用步骤
使用/dev/mem 设备需要具有 root 权限, 并且谨慎操作, 因为直接访问内核空间是一项潜在的危险操作。 以下是使用/dev/mem 设备的基本步骤:
- (1)使用 open 函数打开 "/dev/mem" 文件描述符, 并指定访问权限和阻塞方式。 访问权限可以是只读(O_RDONLY) 、 只写(O_WRONLY) 或读写(O_RDWR) 阻塞方式或非阻塞(O_NDELAY)。
int fd = 0;
fd = open("/dev/mem", O_RDWR | O_NDELAY); /* 读写权限, 非阻塞 */请注意, 这里使用 O_RDWR 表示读写权限, 并使用 O_NDELAY 表示非阻塞方式。 可以根据实际需求选择适当的访问权限和阻塞方式。
- (2)使用 mmap 函数将需要访问的物理地址与 "/dev/mem" 文件描述符建立映射。mmap 函数将返回一个指向映射内存区域的指针。
char *mmap_addr = NULL;
mmap_addr = (char *)mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MMAP_ADDR);在这里, 使用 mmap 函数将物理内存地址映射到 mmap_addr 指针所指向的内存区域。MMAP_SIZE 表示映射的大小, PROT_READ | PROT_WRITE 表示访问权限为读写, MAP_SHARED 表示共享映射, fd 是之前打开的 /dev/mem 文件描述符, MMAP_ADDR 是要映射的物理地址。
- (3)对映射的地址进行访问, 即对寄存器进行读写操作。
int a = 0;
*(int *)mmap_addr = 0xff; // 写地址
a = *(int *)mmap_addr; // 读地址在这里, 使用指针操作对 mmap_addr 指向的地址进行读写操作。 *(int *)mmap_addr 表示将 mmap_addr 解释为 int 类型的指针, 对于写操作, 将 0xff 写入该地址; 对于读操作, 将地址的值读取到变量 a 中。
2.3.2 mmap()
mmap 函数原型如下:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);函数参数:
- start: 指定文件应被映射到进程空间的起始地址, 一般被指定为一个空指针, 选择起始地址的任务留给内核来完成。 映射成功之后, 函数返回值为最后文件映射到进程空间的地址, 进程可直接操作起始地址为该值的有效地址。
- length: 是映射到调用进程地址空间的字节数。
- prot: 参数指定共享内存的访问权限。 可取如下几个值的或。 PROT_READ(映射区域可读)、PROT_EXEC(映射区域可执行)、PROT_WRITE(映射区域可写)、 PROT_NONE(映射区域不可访问)。
- flags: 由以下几个常值指定, MAP_SHARED, MAP_PRIVATE, MAP_FIXED, 其中 MAP_SHARED,MAP_PRIVATE 必选其一, MAP_FIXED 不推荐使用。
- fd: 有效的文件描述符。 一般是由 open()函数返回。
- offset: 文件映射的偏移量, offset 的大小必须是页的整数倍, 如果设备为 0 代表从文件最前方开始映射。
函数返回值: 成功执行时, mmap()返回被映射区的指针, 失败时, mmap()返回-1.
2.4 自己写一个 memdev
2.4.1 源码编写
我们会在有些地方看到 memdev 工具,它可以直接读写内存,方便我们调试,这个其实就是通过访问/dev/mem 实现的,网上也会有源码,例如 sources.buildroot.net/devmem2.c。这个其实就是个应用程序,我们可以自己实现的。
2.4.2 开发板测试
我们用这个带有 ap3216c 的 demo,加载相关驱动后,可以读一下 i2c 相关的寄存器:
./app_demo.out 0x020e0340
./app_demo.out 0x020e00b4
2.5 根文件系统支持 memdev
2.5.1 busybox 配置
其实 busybox 中是支持 memdev 命令的,只是,在 1.36.1 版本的 busybox 中叫 devmem,我之前用的是 buildroot 构建的根文件系统,它其实用到了 busybox,我们可以在 buildroot 源码顶层目录下执行以下命令配置 busybox:
sudo make busybox-menuconfig然后就会打开 busybox 的配置界面:

我们按以下路径找到对应的配置项:
Miscellaneous Utilities --->
[*] devmem (2.5 kb)
退出的时候保存,注意这个配置修改后,会修改 busybox 源码目录中的.config 文件,要是需要保存的话,这里需要注意一下。然后重新编译 buildroot:
sudo make编译完成后看一下 output/images 目录下 rootfs.tar 的创建时间是否为刚刚编译的,如果不是的话就删除掉 rootfs.tar,然后重新执行“sudo make”重新编译一下即可。我们可以把这个目录解压看一下:
tar xvf rootfs.tar
find ./ -name "devmem"
会发现有这个命令,我们进入开发板,使用以下命令:
which devmem可以看到确实有这个命令。
2.5.2 开发板测试
我们还是读这两个寄存器:
devmem 0x020e0340 32
devmem 0x020e00b4 32
会发现和前面读到的结果一致,其实都一样,都是通过 mmap 来映射内存,只是根文件系统这个直接做成命令了。