i.MX6ULL移植内核6.6(二)GPIO子系统和LED子系统
本篇主要内容:
- 一、应用层操作GPIO的几种方式
- 1. 通过sysfs系统操作GPIO
- 2. 通过/dev/mem内存映射直接操作GPIO
- 3. 通过/dev/gpiochipx操作GPIO(linux4.8新增)
- 4. 设备树分析
- 二、LED子系统
- 1.设备树添加LED节点
- 2.应用层控制LED
- 3.内核LED驱动
一、应用层操作GPIO的几种方式
1. 通过sysfs系统操作GPIO
📌以i.IMX6ULL为例,在前一篇移植的6.6内核,GPIO相关的引脚定义和声明均在设备树中已写好,驱动也默认使能。可以直接通过sysfs操作GPIO,在阿尔法开发板上,GPIO1_IO3接了LED,这里可以通过以下命令操作GPIO。
echo 3 > /sys/class/gpio/export # 导出GPIO1_IO03
echo "out" > /sys/class/gpio/gpio3/direction # 设置GPIO1_IO03为输出
echo 0 > /sys/class/gpio/gpio3/value # GPIO1_IO03输出低电平(此时LED亮)
echo 1 > /sys/class/gpio/gpio3/value # GPIO1_IO03输出高电平(此时LED灭)
2. 通过/dev/mem内存映射直接操作GPIO
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>// IMX6ULL GPIO1寄存器基地址
#define GPIO1_BASE 0x0209C000// GPIO寄存器偏移量
#define GPIO_DR 0x00 // 数据寄存器
#define GPIO_GDIR 0x04 // 方向寄存器
#define GPIO_PSR 0x08 // 引脚状态寄存器
#define GPIO_ICR1 0x0C // 中断配置寄存器1
#define GPIO_ICR2 0x10 // 中断配置寄存器2
#define GPIO_IMR 0x14 // 中断屏蔽寄存器
#define GPIO_ISR 0x18 // 中断状态寄存器
#define GPIO_EDGE_SEL 0x1C // 边沿选择寄存器// 内存映射大小
#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1) //0xFFF,4K对齐用int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s <0|1>\n", argv[0]);fprintf(stderr, " 0: set GPIO1_IO03 low\n");fprintf(stderr, " 1: set GPIO1_IO03 high\n");return -1;}int value = atoi(argv[1]);if (value != 0 && value != 1) {fprintf(stderr, "Invalid value: %s. Must be 0 or 1.\n", argv[1]);return EXIT_FAILURE;}int fd;void *map_base;volatile unsigned long *gpio_dr;volatile unsigned long *gpio_gdir;// 打开/dev/mem 设备if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) {perror("open /dev/mem");return EXIT_FAILURE;}// 对齐后的GPIO1基地址映射到用户空间map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_BASE & ~MAP_MASK);if (map_base == (void *)-1) {perror("mmap");close(fd);return EXIT_FAILURE;}// 计算寄存器地址off_t gpio_addr = GPIO1_BASE & MAP_MASK; // 0x0209C000 & 0xfff得到页偏移值,这里偏移是0gpio_gdir = (volatile unsigned long *)((char *)map_base + gpio_addr + GPIO_GDIR);gpio_dr = (volatile unsigned long *)((char *)map_base + gpio_addr + GPIO_DR);// 设置 GPIO1_IO03 为输出模式 (bit3 = 1)*gpio_gdir |= (1 << 3);// 设置输出电平if (value) {*gpio_dr |= (1 << 3); // 设置高电平printf("Set GPIO1_IO03 HIGH\n");} else {*gpio_dr &= ~(1 << 3); // 设置低电平printf("Set GPIO1_IO03 LOW\n");}// 释放内存、关闭设备if (munmap(map_base, MAP_SIZE) == -1) {perror("munmap");}close(fd);return 0;
}
3. 通过/dev/gpiochipx操作GPIO(linux4.8新增)
📌这种方式是linux4.8以后新增的方式,也是GPIO子系统一部分,是当前比较新和推荐的方式,毕竟sysfs在2020年被标记为deprecated(废弃),同时,也引入了libgpiod这个库供应用层来操作GPIO,以及相应的命令行测试工具。上一篇使用的根文件系统是不带gpiod相关的命令,这里重新使用buildroot制作了一个根文件系统,并添加了gpiod相关的命令。
📌命令操作GPIO如下(其它命令略):
root@ATK-IMX6ULL:~# gpioset /dev/gpiochip0 3=1 # 操作GPIO1_IO3输出高电平
root@ATK-IMX6ULL:~# gpioset /dev/gpiochip0 3=0 # 操作GPIO1_IO3输出低电平
📌libgpiod操作GPIO代码如下:
#include <gpiod.h>
#include <stdio.h>
#include <unistd.h>int main() {const char *chipname = "gpiochip0";struct gpiod_chip *chip;struct gpiod_line *line;int ret;// 打开GPIO控制器chip = gpiod_chip_open_by_name(chipname);if (!chip) {perror("Open chip failed");return 1;}// 获取GPIO3(IMX6ULL GPIO1_IO03 = 偏移量 3)line = gpiod_chip_get_line(chip, 3);if (!line) {perror("Get line failed");gpiod_chip_close(chip);return 1;}// 配置为输出模式,初始低电平ret = gpiod_line_request_output(line, "example", 0);if (ret < 0) {perror("Request line as output failed");goto cleanup;}// 设置高电平gpiod_line_set_value(line, 1);printf("GPIO3 has been set HIGH\n");sleep(1);// 设置低电平gpiod_line_set_value(line, 0);printf("GPIO3 has been set LOW\n");cleanup:// 释放资源gpiod_line_release(line);gpiod_chip_close(chip);return 0;
}
📌编译命令如图所示,需要提前交叉编译好对应的库,最后的运行结果,LED先熄灭1s后亮起
4. 设备树分析
gpio1: gpio@209c000 { /* GPIO1_IOX基地址0x209c000 *//* 匹配该GPIO控制器兼容的驱动:*/compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";reg = <0x0209c000 0x4000>; /* <寄存器基地址 大小16KB>*/interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, /* <中断控制器 中断号 触发方式> */<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_GPIO1>; /* 时钟信号 */gpio-controller; /* gpio控制器 */#gpio-cells = <2>; /* <&gpio1 3 0> */interrupt-controller;#interrupt-cells = <2>; /* <引脚复用控制器 GPIO起始编号 iomux控制器起始编号 GPIO数量> *//* 0-9、10-15、16-31 一共32个GPIO */gpio-ranges = <&iomuxc 0 23 10>, <&iomuxc 10 17 6>,<&iomuxc 16 33 16>;
};
📌源码中默认已经开启了GPIO子系统,如果没有可以添加以下配置到.config
CONFIG_GPIOLIB=y //启用GPIO核心库
CONFIG_OF_GPIO=y //允许通过设备树配置GPIO
CONFIG_GPIOLIB_IRQCHIP=y //允许GPIO引脚用于中断请求
CONFIG_GPIO_CDEV=y //启用字符设备(cdev)接口来访问GPIO
CONFIG_GPIO_CDEV_V1=y //使用GPIO字符设备接口的版本V1
CONFIG_GPIO_GENERIC=y //启用通用GPIO框架,提供了标准的GPIO实现
二、LED子系统
1.设备树添加LED节点
gpio-leds {compatible = "gpio-leds";status = "okay";led0 {function = LED_FUNCTION_HEARTBEAT;color = <LED_COLOR_ID_RED>;gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;linux,default-trigger = "heartbeat";};
};
📌关于LED内核所提供的驱动配置路径如下,在设备树中添加以上节点,替换到开发板重启,可以看到LED闪烁
2.应用层控制LED
root@ATK-IMX6ULL:~# ls /sys/class/leds/*
mmc0::/ mmc1::/ red:heartbeat/
root@ATK-IMX6ULL:~# echo none > /sys/class/leds/red:heartbeat/trigger
root@ATK-IMX6ULL:~# echo 1 > /sys/class/leds/red:heartbeat/brightness
root@ATK-IMX6ULL:~# echo 0 > /sys/class/leds/red:heartbeat/brightness
3.内核LED驱动
📌源码路径:
📂linux-imx-lf-6.6.y/drivers/leds/led-core.c
📂linux-imx-lf-6.6.y/drivers/leds/led-class.c
📂linux-imx-lf-6.6.y/drivers/leds/leds-gpio.c
📂linux-imx-lf-6.6.y/drivers/leds/leds-pwm.c
📂linux-imx-lf-6.6.y/drivers/leds/leds.h
📌楼了一眼代码,有点小复杂看不懂,此处略过。大致的关系如下
源文件 | 层次 | 主要功能 |
---|---|---|
led-class.c | 核心抽象层 | 定义核心数据结构 struct led_classdev,通过它注册具体的LED设备 |
led-core.c | 核心基础实现层 | 提供了LED子系统的核心实现,如sysfs 接口、全局初始化等 |
leds-gpio.c | 硬件驱动层 | 控制GPIO引脚上的LED,实现probe函数初始化GPIO并注册LED设备 |
leds-pwm.c | 其它功能 | 控制PWM信号驱动的LED,通过调节PWM占空比实现亮度控制 |
led-triggers.c | 其它功能 | 实现了LED触发器功能(如 heartbeat、timer) |