嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十六)GPIO和Pinctrl子系统的使用(下)

简介: 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十六)GPIO和Pinctrl子系统的使用

1.2.4 sysfs中的访问方法


在sysfs中访问GPIO,实际上用的就是引脚号,老的方法。

a. 先确定某个GPIO Controller的基准引脚号(base number),再计算出某个引脚的号码。

方法如下:

① 先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录:

1670921715090.jpg

② 然后进入某个gpiochip目录,查看文件label的内容

③ 根据label的内容对比设备树

label内容来自设备树,比如它的寄存器基地址。用来跟设备树(dtsi文件)比较,就可以知道这对应哪一个GPIO Controller。

下图是在100asK_imx6ull上运行的结果,通过对比设备树可知gpiochip96对应gpio4:

1670921722736.jpg

所以gpio4这组引脚的基准引脚号就是96,这也可以“cat base”来再次确认。


b. 基于sysfs操作引脚:

以100ask_imx6ull为例,它有一个按键,原理图如下:

1670921731770.jpg

那么GPIO4_14的号码是96+14=110,可以如下操作读取按键值:

echo  110 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio110/direction
cat /sys/class/gpio/gpio110/value
echo  110 > /sys/class/gpio/unexport


注意:如果驱动程序已经使用了该引脚,那么将会export失败,会提示下面的错误:

1670921749837.jpg

对于输出引脚,假设引脚号为N,可以用下面的方法设置它的值为1:

echo  N > /sys/class/gpio/export
echo out > /sys/class/gpio/gpioN/direction
echo 1 > /sys/class/gpio/gpioN/value
echo  N > /sys/class/gpio/unexport


1.3 基于GPIO子系统的LED驱动程序


1.3.1 编写思路


GPIO的地位跟其他模块,比如I2C、UART的地方是一样的,要使用某个引脚,需要先把引脚配置为GPIO功能,这要使用Pinctrl子系统,只需要在设备树里指定就可以。在驱动代码上不需要我们做任何事情。


GPIO本身需要确定引脚,这也需要在设备树里指定。 设备树节点会被内核转换为platform_device。

对应的,驱动代码中要注册一个platform_driver,在probe函数中:获得引脚、注册file_operations。

在file_operations中:设置方向、读值/写值。

1670921769257.jpg

下图就是一个设备树的例子:

1670921778430.jpg


1.3.2 在设备树中添加Pinctrl信息


有些芯片提供了设备树生成工具,在GUI界面中选择引脚功能和配置信息,就可以自动生成Pinctrl子结点。把它复制到你的设备树文件中,再在client device结点中引用就可以。

有些芯片只提供文档,那就去阅读文档,一般在内核源码目录Documentation\devicetree\bindings\pinctrl下面,保存有该厂家的文档。

如果连文档都没有,那只能参考内核源码中的设备树文件,在内核源码目录arch/arm/boot/dts目录下。 最后一步,网络搜索。

Pinctrl子节点的样式如下:

1670921788564.jpg


1.3.3 在设备树中添加GPIO信息


先查看电路原理图确定所用引脚,再在设备树中指定:添加”[name]-gpios”属性,指定使用的是哪一个GPIO Controller里的哪一个引脚,还有其他Flag信息,比如GPIO_ACTIVE_LOW等。具体需要多少个cell来描述一个引脚,需要查看设备树中这个GPIO Controller节点里的“#gpio-cells”属性值,也可以查看内核文档。 示例如下:

1670921800375.jpg


1.3.4 编程示例


在实际操作过程中也许会碰到意外的问题,现场演示如何解决。

a. 定义、注册一个platform_driver

b. 在它的probe函数里:

b.1 根据platform_device的设备树信息确定GPIO:gpiod_get

b.2 定义、注册一个file_operations结构体

b.3 在file_operarions中使用GPIO子系统的函数操作GPIO: gpiod_direction_output、gpiod_set_value


好处:这些代码对所有的板子都是完全一样的!

使用GIT命令载后,源码leddrv.c位于这个目录下:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\source\
05_gpio_and_pinctrl\
    01_led


摘录重点内容:


a. 注册platform_driver


注意下面第122行的"100ask,leddrv",它会跟设备树中节点的compatible对应:

121 static const struct of_device_id ask100_leds[] = {
122     { .compatible = "100ask,leddrv" },
123     { },
124 };
125
126 /* 1. 定义platform_driver */
127 static struct platform_driver chip_demo_gpio_driver = {
128     .probe      = chip_demo_gpio_probe,
129     .remove     = chip_demo_gpio_remove,
130     .driver     = {
131         .name   = "100ask_led",
132         .of_match_table = ask100_leds,
133     },
134 };
135
136 /* 2. 在入口函数注册platform_driver */
137 static int __init led_init(void)
138 {
139     int err;
140
141     printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
142
143     err = platform_driver_register(&chip_demo_gpio_driver);
144
145     return err;
146 }


b. 在probe函数中获得GPIO


核心代码是第87行,它从该设备(对应设备树中的设备节点)获取名为“led”的引脚。在设备树中,必定有一属性名为“led-gpios”或“led-gpio”。

77 /* 4. 从platform_device获得GPIO
78  *    把file_operations结构体告诉内核:注册驱动程序
79  */
80 static int chip_demo_gpio_probe(struct platform_device *pdev)
81 {
82      //int err;
83
84      printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
85
86      /* 4.1 设备树中定义有: led-gpios=<...>; */
87     led_gpio = gpiod_get(&pdev->dev, "led", 0);
88      if (IS_ERR(led_gpio)) {
89              dev_err(&pdev->dev, "Failed to get GPIO for led\n");
90              return PTR_ERR(led_gpio);
91      }
92


c. 注册file_operations结构体:


这是老套路了:

93      /* 4.2 注册file_operations      */
94      major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
95
96      led_class = class_create(THIS_MODULE, "100ask_led_class");
97      if (IS_ERR(led_class)) {
98              printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
99              unregister_chrdev(major, "led");
100             gpiod_put(led_gpio);
101             return PTR_ERR(led_class);
102     }
103
104     device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
105


d. 在open函数中调用GPIO函数设置引脚方向:

51 static int led_drv_open (struct inode *node, struct file *file)
52 {
53      //int minor = iminor(node);
54
55      printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
56      /* 根据次设备号初始化LED */
57      gpiod_direction_output(led_gpio, 0);
58
59      return 0;
60 }


e. 在write函数中调用GPIO函数设置引脚值:

34 /* write(fd, &val, 1); */
35 static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
36 {
37      int err;
38      char status;
39      //struct inode *inode = file_inode(file);
40      //int minor = iminor(inode);
41
42      printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
43      err = copy_from_user(&status, buf, 1);
44
45      /* 根据次设备号和status控制LED */
46      gpiod_set_value(led_gpio, status);
47
48      return 1;
49 }


f. 释放GPIO:

gpiod_put(led_gpio);


1.4 在100ASK_IMX6ULL上机实验


1.4.1 确定引脚并生成设备树节点


NXP公司对于IMX6ULL芯片,有设备树生成工具。我们也把它上传到GIT去了,使用GIT命令载后,在这个目录下:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\source\
05_gpio_and_pinctrl\
tools\
imx\


安装“Pins_Tool_for_i.MX_Processors_v6_x64.exe”后运行,打开IMX6ULL的配置文件“MCIMX6Y2xxx08.mex”,就可以在GUI界面中选择引脚,配置它的功能,这就可以自动生成Pinctrl的子节点信息。


100ASK_IMX6ULL使用的LED原理图如下,可知引脚是GPIO5_3:

1670921898277.jpg

在设备树工具中,如下图操作:

1670921906675.jpg

把自动生成的设备树信息,放到内核源码arch/arm/boot/dts/100ask_imx6ull-14x14.dts中,代码如下:


a. Pinctrl信息:

&iomuxc_snvs {
……
        myled_for_gpio_subsys: myled_for_gpio_subsys{ 
            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;
        };


b. 设备节点信息(放在根节点下):

myled {
        compatible = "100ask,leddrv";
        pinctrl-names = "default";
        pinctrl-0 = <&myled_for_gpio_subsys>;
        led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
    };


1.4.2 编译程序


编译设备树后,要更新设备树。

编译驱动程序时,“leddrv_未测试的原始版本.c”是有错误信息的,“leddrv.c”是修改过的。

测试方法,在板子上执行命令:

# insmod  leddrv.ko
# ls /dev/100ask_led0
# ./ledtest /dev/100ask_led0 on
# ./ledtest /dev/100ask_led0 off
相关文章
|
30天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
82 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
43 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
49 5
|
3月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
|
3月前
|
编解码 安全 Linux
基于arm64架构国产操作系统|Linux下的RTMP|RTSP低延时直播播放器开发探究
这段内容讲述了国产操作系统背景下,大牛直播SDK针对国产操作系统与Linux平台发布的RTMP/RTSP直播播放SDK。此SDK支持arm64架构,基于X协议输出视频,采用PulseAudio和Alsa Lib处理音频,具备实时静音、快照、缓冲时间设定等功能,并支持H.265编码格式。此外,提供了示例代码展示如何实现多实例播放器的创建与管理,包括窗口布局调整、事件监听、视频分辨率变化和实时快照回调等关键功能。这一技术实现有助于提高直播服务的稳定性和响应速度,适应国产操作系统在各行业中的应用需求。
109 3
|
3月前
|
传感器 人工智能 网络协议
:嵌入式 Linux 及其用途
【8月更文挑战第24天】
169 0
|
4月前
|
Ubuntu 算法 Linux
嵌入式Linux的学习误区
**嵌入式Linux学习误区摘要** 1. **过度聚焦桌面Linux** - 许多学习者误将大量时间用于精通桌面Linux系统(如RedHat、Fedora、Ubuntu),认为这是嵌入式Linux开发的基石。 - 实际上,桌面Linux仅作为开发工具和环境,目标不应是成为Linux服务器专家,而应专注于嵌入式开发工具和流程。 2. **盲目阅读Linux内核源码** - 初学者在不了解Linux基本知识时试图直接研读内核源码,这往往导致困惑和挫败感。 - 在具备一定嵌入式Linux开发经验后再有针对性地阅读源码,才能有效提升技能。
|
4月前
|
Ubuntu Linux Docker
Java演进问题之Alpine Linux创建更小的Docker镜像如何解决
Java演进问题之Alpine Linux创建更小的Docker镜像如何解决
|
4天前
|
Android开发
鸿蒙开发:自定义一个简单的标题栏
本身就是一个很简单的标题栏组件,没有什么过多的技术含量,有一点需要注意,当使用沉浸式的时候,注意标题栏的位置,需要避让状态栏。
鸿蒙开发:自定义一个简单的标题栏