电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
【i.MX6ULL】驱动开发6——Pinctrl子系统与GPIO子系统点亮LED
分 享
扫描二维码分享
【i.MX6ULL】驱动开发6——Pinctrl子系统与GPIO子系统点亮LED
i.MX6ULL
嵌入式
驱动
码农爱学习
关注
发布时间: 2021-11-05
丨
阅读: 328
前面的两篇文章([寄存器配置点亮LED](https://www.icxbk.com/article/detail/2378.html)与[设备树版的点亮LED](https://www.icxbk.com/article/detail/2392.html)),其本质都是通过寄存器配置,来控制LED的亮灭。 - 使用**直接操作寄存器**的方式,是将与LED有关的寄存器信息,直接写到了LED的驱动代码中,这也是一种比较常规的控制方式。但当芯片的寄存器发了变动,就要对底层的驱动进行重写。 - 使用**设备树**的方式,是将与LED有关的寄存器信息,写到了设备树文件中,这样,当设备的信息修改了,还可以通过设备树的接口函数,来获取设备信息,提高了驱动代码的复用能力。 - 本篇介绍的**Pinctrl子系统与GPIO子系统**的方式,不需要再直接操作寄存器了,因为这两个子系统已经替我们实现了对寄存器的操作,我们只需要操作这两个子系统提供的API函数即可。 [TOC] # 1 Pinctrl子系统 Pintrl子系统,顾名思义,就是管理pin引脚的一个系统,比如要点亮LED,即要控制LED对应引脚的高低电平,就要先通过Pintrl子系统将LED对应的引脚复用为GPIO功能(这一点是不是和之前**寄存器配置时使用的MUX寄存器**的功能有点像)。 ## 1.1 设备树中iomuxc节点 如何使用Pintrl子系统呢?其实它也是要依赖设备树的,先来了解一下设备树里的**iomuxc节点**,这个节点是IOMUXC外设对应的节点,负责IO功能的复用。 打开自己开发板对应的设备树文件(我的是imx6ull-myboard.dts),然后找到iomuxc节点,先来看一下其基本结构: ```c &iomuxc { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hog_1>; imx6ul-evk { pinctrl_hog_1: hoggrp-1 { fsl,pins = < MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /@@* SD1 CD */ MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /@@* SD1 VSELECT */ MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /@@* SD1 RESET */ >; }; pinctrl_csi1: csi1grp { fsl,pins = < MX6UL_PAD_CSI_MCLK__CSI_MCLK 0x1b088 MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK 0x1b088 MX6UL_PAD_CSI_VSYNC__CSI_VSYNC 0x1b088 MX6UL_PAD_CSI_HSYNC__CSI_HSYNC 0x1b088 MX6UL_PAD_CSI_DATA00__CSI_DATA02 0x1b088 MX6UL_PAD_CSI_DATA01__CSI_DATA03 0x1b088 MX6UL_PAD_CSI_DATA02__CSI_DATA04 0x1b088 MX6UL_PAD_CSI_DATA03__CSI_DATA05 0x1b088 MX6UL_PAD_CSI_DATA04__CSI_DATA06 0x1b088 MX6UL_PAD_CSI_DATA05__CSI_DATA07 0x1b088 MX6UL_PAD_CSI_DATA06__CSI_DATA08 0x1b088 MX6UL_PAD_CSI_DATA07__CSI_DATA09 0x1b088 >; }; //省略... ``` 这里以**pinctrl_hog_1插拔子节点**为例进行分析,它是和热插拔有关的Pin集合,比如USB OTG的ID引脚,pinctrl_csi1子节点是csi外设所使用的PIN,本篇需要控制LED的亮灭,就需要新建一个对应的节点,然后将这个自定义外设的所有Pin配置信息都放到这个子节点中。 ## 1.2 宏定义的含义解析 对于pinctrl_hog_1这个字节点,注意其中的: ``` MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /@@* SD1 CD */ ``` 这就是对Pin引脚的配置,配置包括两方面:一是设置Pin的**复用功能**,二是设置Pin的**电气特性**。 前面的`MX6UL_PAD_UART1_RTS_B__GPIO1_IO19`这个宏定义, 定义在arch/arm/boot/dts/imx6ul-pinfunc.h中(注意**imx6ull.dtsi**会引用**imx6ull-pinfunc.h**,而imx6ull-pinfunc.h又会引用**imx6ul-pinfunc.h**) ![](https://cf02.ickimg.com/bbsimages/202110/032bbba7e261299e2f31bcbb54d566cd.png) 这里一共有8 个以MX6UL_PAD_UART1_RTS_B开头的宏定义,分别代表这个IO的8种不同的功能。 另外,这个宏定义的值,被分为了5段,每段的值都有具体的含义: - **0x0090** mux_reg寄存器偏移地址 ![](https://cf02.ickimg.com/bbsimages/202110/5d5ac66dbfc63543ab57b73bb50cc957.png) - **0x031C **conf_reg寄存器偏移地址 ![](https://cf02.ickimg.com/bbsimages/202110/66dc082eae76595ed92f69c57f7ed429.png) - **0x0000** input_reg寄存器偏移地址(这里无效) - **0x5** mux_reg寄存器的值 ![](https://cf02.ickimg.com/bbsimages/202110/7e167a5ab7c2d2fe6173947cc659e005.png) - **0x0** input_reg寄存器值(这里无效) # 2 GPIO子系统 GPIO子系统,顾名思义,就是管理GPIO功能的一个系统,其作用是初始化配置GPIO(这一点是不是和之前**寄存器配置时使用的PAD寄存器**的功能有点像),并提供对外的API接口。使用GPIO子系统后,就不需要自己操作寄存器,通过调用GPIO子系统提供的API函数即可实现对GPIO的控制。 ## 2.1 设备树中gpio信息 仍以热插拔节点为例: ![](https://cf02.ickimg.com/bbsimages/202110/c39ee0f6734fb51764c47f77bec6aae3.png) UART1_RTS_B复用为GPIO1_IO19,通过读取其高低电平来判断SD卡有没有插入。 那SD卡驱动程序怎么知道CD引脚连接的GPIO1_IO19呢?还是需要设备树告诉驱动,在设备树中SD卡节点下添加一个属性来描述SD卡的 CD 引脚就行了: ![](https://cf02.ickimg.com/bbsimages/202110/967b46a076d58c3496a1a4ba3f1a4631.png) 属**cd-gpios**描述了SD卡的CD引脚使用的哪个IO,属性值一共有三个: - **&gpio1** 表示CD引脚所使用的IO属于GPIO1组 - **19** 表示GPIO1组的第19号IO - **GPIO_ACTIVE_LOW** 表示低电平有效 根据上面这些信息,SD卡驱动程序就可以使用GPIO1_IO19来检测SD卡的CD信号了 ## 2.2 gpio子系统API函数 ### 2.2.1 gpio_request/free - gpio_request 用于申请一个GPIO管脚 ```c /@@** * gpio: 要申请的gpio标号(使用of_get_named_gpio函数从设备树获取指定GPIO属性信息时返回的标号) * label: 给gpio设置个名字 * return: 0-申请成功 其他值-申请失败 */ int gpio_request(unsigned gpio, const char *label) ``` - gpio_free 用于释放一个GPIO管脚 ```c /@@** * gpio: 要释放的gpio标号 * return */ void gpio_free(unsigned gpio) ``` ### 2.2.2 gpio_direction_input/output - gpio_direction_input 用于设置某个GPIO为**输入** ```c /@@** * gpio: 要设置为输入的GPIO标号 * return: 0-设置成功 负值-设置失败 */ int gpio_direction_input(unsigned gpio) ``` - gpio_direction_output 此函数用于设置某个GPIO为**输出**,并且设置**默认输出值** ```c /@@** * gpio: 要设置为输出的GPIO标号 * value: GPIO默认输出值 * return 0-设置成功 负值-设置失败 */ int gpio_direction_output(unsigned gpio, int value) ``` ### 2.2.3 gpio_get_value/set_value - gpio_get_value 此函数用于**获取**某个GPIO的值(0 或 1) ```c #define gpio_get_value __gpio_get_value /@@** * gpio: 要获取的gpio标号 * return: 非负值-得到的gpio值 负值-获取失败 */ int __gpio_get_value(unsigned gpio) ``` - gpio_set_value 用于**设置**某个GPIO的值 ```c #define gpio_set_value __gpio_set_value /@@** * gpio: 要设置的gpio标号 * value: 要设置的值 * return */ void __gpio_set_value(unsigned gpio, int value) ``` ## 2.3 与gpio相关的OF函数 ### 2.3.1 of_gpio_named_count 用于获取设备树某个属性里面定义了几个GPIO信息 ```c /@@** * np: 设备节点 * propname: 要统计的gpio属性 * return: 正值-统计到的gpio数量 负值-失败 */ int of_gpio_named_count(struct device_node *np, const char *propname) ``` ### 2.3.2 of_gpio_count 统计“gpios”这个属性的gpio数量 ```c /@@** * np: 设备节点 * return: 正值-统计到的gpio数量 负值-失败 */ int of_gpio_count(struct device_node *np) ``` ### 2.3.3 of_get_named_gpio 获取GPIO编号 ```c /@@** * np: 设备节点 * propname: 包含要获取gpio信息的属性名 * index: gpio索引(一个属性里面可能包含多个gpio) * return: 正值-获取到的gpio编号 负值-失败 */ int of_get_named_gpio(struct device_node *np, const char *propname, int index) ``` # 3 Pinctr版LED驱动程序 上面介绍了Pinctrl子系统与GPIO子系统的基本情况,下面就来使用它们来实现LED的亮灭控制。 ## 3.1 修改设备树文件 修改imx6ull-myboard.dts,在iomuxc节点的imx6ull-evk字节点下创建一个名为pinctrl_led的子节点,节点内容如下: ```c pinctrl_gpioled: ledgrp{ fsl,pins = < MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x10b0 >; }; ``` - MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 表示将该io复用为GPIO - 0x10b0 表示对PAD寄存器的配置值,具体含义为如下,之前的文章([驱动开发4--点亮LED(寄存器版)](https://www.icxbk.com/article/detail/2378.html))介绍过。 ```c /@@*寄存器SW_PAD_SNVS_TAMPER3设置IO属性 *bit 16:0 HYS关闭 *bit [15:14]: 00 默认下拉 *bit [13]: 0 kepper功能 *bit [12]: 1 pull/keeper使能 *bit [11]: 0 关闭开路输出 *bit [7:6]: 10 速度100Mhz *bit [5:3]: 110 R0/6驱动能力 *bit [0]: 0 低转换率 */ ``` ![](https://cf02.ickimg.com/bbsimages/202110/8a9999d567062be82577819a8f770489.png) 在根节点下创建名为gpioled的LED节点,内容如下: ```c /@@*pinctrl led*/ gpioled { compatible = "myboard,gpioled"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpioled>; led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>; status = "okay"; }; ``` - **pinctrl-0** 设置 LED所使用的PIN对应的pinctrl节点 - **led-gpio** 指定了LED所使用的GPIO,这里是GPIO5的IO03,低电平有效 ![](https://cf02.ickimg.com/bbsimages/202110/16ab6a15c79a966592cb10a75dddc27c.png) ## 3.2 检查引脚是否使用冲突 因为我的开发板使用的设备树文件(imx6ull-myboard.dts)是从NXP官方提供的设备树文件(imx6ull-14x14-evk.dts)上修改而来的,可能某些引脚的配置与自己的开发板不一样,需要检查一下是否有使用冲突。 本次添加的这个`MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03`与文件中的其它引脚没有出现冲突,因此无需修改。 ## 3.3 修改LED驱动文件 在上一篇的设备树版的驱动文件上进行修改,主要修改内容如下。 头文件需要添加一个: ```c #include
``` 设备结构体改为gpio_led: ```c /@@* gpioled设备结构体 */ struct gpioled_dev{ dev_t devid; /@@* 设备号 */ struct cdev cdev; /@@* cdev */ struct class *class; /@@* 类 */ struct device *device; /@@* 设备 */ int major; /@@* 主设备号 */ int minor; /@@* 次设备号 */ struct device_node *nd; /@@* 设备节点 */ int led_gpio; /@@* led使用的GPIO编号*/ }; struct gpioled_dev gpioled; /@@* led设备 */ ``` **硬件初始化**部分是主要修改的内容,这次就不需要从设备树读取寄存器操作了,也不需要自己再进行I/O内存映射了,而实使用gpio子系统的API函数来对LED的GPIO进行配置: ```c static int gpioled_hardware_init(void) { int ret; /@@* 获取设备树中的属性数据 */ /@@* 1、获取设备节点:gpioled */ gpioled.nd = of_find_node_by_path("/gpioled"); if(gpioled.nd == NULL) { printk("gpioled node not find!\r\n"); return -EINVAL; } else { printk("gpioled node find!\r\n"); } /@@* 2、获取gpio属性, 得到LED编号 */ gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); if(gpioled.led_gpio < 0) { printk("can't get led-gpio!\r\n"); return -EINVAL; } else { printk("led-gpio num = %d\r\n", gpioled.led_gpio); } /@@* 3、设置GPIO为输出, 并默认关闭LED */ ret = gpio_direction_output(gpioled.led_gpio, 1); if(ret < 0) { printk("can't set led-gpio!\r\n"); } return 0; } ``` 开关LED时,也不需要再直接操作寄存器了,也是使用API函数来操作: ```c static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { //省略... if(ledstat == LEDON) { gpio_set_value(dev->led_gpio, 0); /@@* 打开LED灯 */ printk("led on!\n"); } else if(ledstat == LEDOFF) { gpio_set_value(dev->led_gpio, 1); /@@* 关闭LED灯 */ printk("led off!\n"); } return 0; } ``` # 4 实验测试 ## 4.1 编译程序 - 编译设备树文件(.dtb),和上篇设备树点亮LED的实验一样,先将设备树文件复制到nfs文件系统位置,再从网络启动开发板,串口中查看设备树中是否有添加的gpioled节点: ![](https://cf02.ickimg.com/bbsimages/202110/4279aa89bcdba83408b0ca778aa2350b.png) - 编译LED驱动文件(.ko),复制到rootfs/lib/modules/4.1.15目录中: ![](https://cf02.ickimg.com/bbsimages/202110/f2d4bbcdb51e9383b332ecae6de95a0a.png) - LED应用程序不需要改,仍使用之前寄存器版点亮LED实验时使用的程序即可。 ## 4.2 测试 测试方式与之前的一样,都是先加载驱动文件,然后调用应用程序来控制LED的亮灭: ![](https://cf02.ickimg.com/bbsimages/202110/7acc9b2d2fb60698f7486dc5341e8ed2.png) 效果和之前的**寄存器版点亮LED**与**设备树版点亮LED**的效果一样 ![](https://cf02.ickimg.com/bbsimages/202110/645dab9c23ab7050bbd7f178baef269f.jpg) # 5 总结 本篇介绍了使用**Pinctrl子系统与GPIO子系统**的方式来点亮LED,与之前的**寄存器版点亮LED**与**设备树版点亮LED**的最大区别在于**不需要直接操作寄存器**了,而是使用API函数来配置GPIO,具体操作寄存器在过程在API函数内部实现,我们无需在进行繁琐的寄存器操作。 本篇与上一篇的**设备树版点亮LED**的程序编写流程基本一致,因为都是要使用**设备树**,与上一篇的主要区别就在于,不需要将寄存器信息写入设备树,再从设备树获取出来手动配置寄存器了。
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
码农爱学习
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交