I.MX6ULL内核开发13:pinctrl子系统和gpio子系统-led实验
目录
一、pinctrl子系统
1.1 pinctrl子系统编写格式以及引脚属性介绍
1.1.1 iomux节点介绍
1.1.2 pinctrl子节点编写格式
1.1.3 引脚配置信息介绍
1.2 将RGB灯引脚添加到pinctrl子系统
1.2.1 查找RGB灯使用的引脚
1.2.2找到引脚宏定义
1.2.3 设置引脚属性
1.2.4 在iomuxc节点中添加pinctrl子节点
二、GPIO子系统
2.1 在设备树种添加RGB灯的设备树节点
2.2 编译、下载设备树验证修改结果
2.3 GPIO子系统常用的API函数
三、实验
3.1 实验代码
3.1.1 驱动程序
3.1.2 应用程序
3.2 实验准备
3.2.1 Makefile修改
3.3 下载验证
一、pinctrl子系统
pinctrl子系统用于管理芯片的引脚。imx6ull芯片上拥有众多的片上外设,大多数外设需要通过芯片的引脚与外部涉笔(器件)相连实现相对应的控制,例如I2C、SPI、LCD、USDHC等等。而芯片的可用引脚(除去电源引脚和特定功能引脚)数量是有限的,芯片的设计厂商为了提高硬件设计的灵活性,一个芯片引脚往往可以作为多个片上外设的功能引脚。
在驱动程序中需要手动的设置每个引脚的复用功能,不仅增加工作量,编写的驱动程序不方便移植,可重用性差。更糟糕的时缺乏对引脚的统一管理,容易出现引脚的重复定义。且这种重定义引起的错误是很难被发现的。
pinctrl子系统时由芯片厂商实现,用于帮助管理芯片引脚并自动完成引脚的初始化,所以编写驱动代码的时候知识在设备树中按照规定的格式写出现想要的配置参数即可。
1.1 pinctrl子系统编写格式以及引脚属性介绍
1.1.1 iomux节点介绍
文件位置:/ebf_linux_kernel/arch/arm/boot/dts/imx6ull/dtsi文件中查找iomux节点,可以看到如下定义
iomuxc: iomuxc@20e0000 {compatible = "fsl,imx6ul-iomuxc";reg = <0x20e0000 0x4000>;};
- compatiable:修饰的是与平台驱动做匹配的名字,这里则是pinctrl子系统的平台驱动做匹配。
- reg:表示的是引脚配置寄存器的基地址。
imx6ull.dtsi这个文件是芯片厂商官方将芯片的通用部分单独提出来的一些设备树配置。在iomux节点中汇总了所需要引脚的配置信息,pinctrl子系统存储使用者iomux节点信息。
设备树主要的配置文件主要在/arch/arm/boot/dts/imx6ull-mmc-npi.dts中,打开imx6ull-mmc-npi.dts,在文件中搜索“&iomux”找到设备树中引用“iomux”节点的位置如下所示。
&iomuxc {pinctrl-names = "default";pinctrl-0 = <&pinctrl_hog_1>;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_enet1: enet1grp {fsl,pins = <MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b031>;};pinctrl_enet2: enet2grp {fsl,pins = <MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b031>;};pinctrl_uart1: uart1grp {fsl,pins = <MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1>;};
/*--------------部分省略--------------------------------*/
- 第2-3行:“pinctrl-names”标识,指定PIN的状态列表,默认设置为“default”。“pinctrl-0=<&pinctrl_hog_1>”的意思的是在默认状态下,将使用pinctrl_hog_1这个设备点来设置GPIO状态。一个引脚可能有多种状态,以串口为例,在正常使用的时候将引脚设置为发送引脚、接收引脚,而在系统进入休眠模式下,为了节省功耗,可以将这两个引脚设置为其他模式。
- 其余源码都是pinctrl的子节点,它们都是按照一定的格式来编写。
&iomuxc {pinctrl-names = "default","sleep","init";pinctrl-0 = <&pinctrl_uart1>;pinctrl-1=<&xxx>;pinctrl-2=<&yyy>;/*-----------------------省略--------------------------------*/pinctrl_uart1: uart1grp {fsl,pins = <MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1>;};xxx:xxx_grp{...这里设置将引脚设置为其他模式}yyy:yyy_grp{...这里设置将引脚设置为其他模式}...
}
- pinctrl-names:定义引脚状态
- pinctrl-0:定义第0种状态需要使用到的引脚配置,可饮用其他节点表示
- pinctrl-1:定义第1种状态需要使用到的引脚配置。
- pinctrl-2:定义第2种状态需要使用到的引脚配置。
1.1.2 pinctrl子节点编写格式
以“pinctrl-uart1”节点源码为例介绍pinctrl子节点格式规范编写:
pinctrl子节点格式规范,格式框架如下:
pinctrl_自定义名字: 自定义名字 {fsl,pins = <引脚复用宏定义 PAD(引脚)属性引脚复用宏定义 PAD(引脚)属性>;};
这里需要注意的是每个芯片厂商的pinctrl子节点的编写格式并不相同,这里不属于设备树的规范,是芯片厂商自定义的。如果想添加自己的pinctrl子节点,只需要照着上面的格式编写即可。
1.1.3 引脚配置信息介绍
即上图中标号3处的内容,也是编写的主要内容-提娜佳引脚配置信息。引脚配置信息有两部分组成,一个宏定义和一个16进制数组成。这实际上定义已经配置控制引脚所需要用到的各个寄存器及应写入寄存器的i西南西,以上面第一条配置信息为例。
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 是定义在“./arch/arm/boot/dts/imx6ul-pinfunc.h”文件内的一个宏定义。
这里面关于“MX6UL_PAD_UART1_TX_DATA__xxx”命名的宏定义一共8个,表示关于引脚复用寄存器,8个宏是用来定义UART1_TX_DATA引脚的8个复用功能。每个宏定义后面有5个参数,名词依次是mug_reg、conf_reg、input_reg、mux_mod、input_val。
mug_reg conf_reg input_reg mux_mod input_val
0x0084 0x0310 0x0000 0x0 0x0
如果将宏定义展开则在设备树中每条配置信息实际是6个参数,由于第6个参数设置比较复杂需要根据实际需要设置因此并没有把它放到宏定义里面。以MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 为例,宏定义里面的5个参数介绍如下:
1、 mux_reg和mux_mode:mux_reg 是引脚复用选择寄存器偏移地址,mux_mode是引脚复用选择寄存器模式选择位的值,UART1_TX 引脚复用选择寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA定义如下所示。
mux_reg = 0x0084 与IM6ULL用户手册偏移地址移植,mux_mode = 0 。 设置复用选择寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA[MUX_MODE] = 0,将其复用位UART1_TX功能。
2、config_reg,引脚(PAD)属性控制控制寄存器偏移地址。与引脚复用选择寄存器不同,引脚属性寄存器应当根据实际需要灵活的配置,所以它的值不包含在宏定义中。
3、input_reg和input_val,input_reg暂且称为输入选择寄存器的偏移地址,input_val是输入选择寄存器的值。这个寄存器只有某些用作输入的引脚才有,正如本例所示,UART1_TX用作输出,所以这两个参数都是0。
1.2 将RGB灯引脚添加到pinctrl子系统
1.2.1 查找RGB灯使用的引脚
1.2.2找到引脚宏定义
这些引脚都将被复用为GPIO,用作驱动LED灯。首先要在“./arch/arm/boot/dts/imx6ul-pinfunc.h”文件内找到对应的宏定义,以CSI_HSYNC引脚为例,在imx6ul-pinfunc.h中直接搜索“CSI_HSYNC”找到如下结果,
同一个引脚的可选复用功能是连续排布的,要将其复用为GPIO,所以选择“MUX6UL_PAD_CSI_HSYNC_GPIO4_IO20”即可。
1.2.3 设置引脚属性
要写入到设备树中的引脚属性就是引脚属性设置寄存器的值,引脚属性配置项很多,从GPIO1_IO04为例如下所示。
实际编程中很少手动设置每一个配置项然后将其再组合成一个16进制数,通常情况下,按照官方的设置,如果有需要在对个别参数进行修改。通常情况下用作GOIO的引脚的PAD引脚属性设置为“0x000010B1”。
1.2.4 在iomuxc节点中添加pinctrl子节点
添加子节点,只需要将前面选择好的配置信息按照之前的格式写入到设备树中即可。
&iomuxc {pinctrl-names = "default";pinctrl-0 = <&pinctrl_hog_1>;/*新增加的内容*/pinctrl_rgb_led:rgb_led{fsl,pins = <MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x000010B1MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x000010B1MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x000010B1>;};
新增加的节点名为“rgb_led”,名字任意选取,长度不要超过32个字符。“pinctrl_rgb_led”节点标签,“pinctrl_”是固定的格式,后面的内容自定义的,通过这个标签引用这个节点。在添加完pinctrl子节点后,系统会根据添加的配置信息将引脚初始化为GPIO子系统相关的内容。
二、GPIO子系统
在没有使用GPIO子系统之前,如过想点亮一个LED,首先要得到led相关的配置寄存器,再手动地读、改、写这些配置寄存器实现控制LED地目的。有了GPIO子系统后这部分工作由GPIO子系统完成。只需要调用GPIO子系统提供地API函数即可完成GPIO地控制动作。
再imx6ull.dtsi文件中地GPIO子节点记录着GPIO控制器地寄存器地址,下面以GPIO4为例介绍GPIO子节点地相关内容
gpio4: gpio@20a8000 {compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";reg = <0x20a8000 0x4000>;interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_GPIO4>;gpio-controller;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <2>;gpio-ranges = <&iomuxc 0 94 17>, <&iomuxc 17 117 12>;
};
- compatiable:与GPIO子系统地平台驱动做匹配
- reg:GPIO寄存器地基地址,GPIO4地寄存器组地映射地址是0x20a8000~0x20ABFFF
- interrupts:描述中断相关地信息
- clocks:初始化GPIO外设时钟信息
- gpio-controller:表示gpio4是一个GPIO控制器
- #gpio-cells:表示有多少个cells来描述GPIO引脚
- interrupt-controller:表示gpio4也是一个中断控制器
- #nterrupt-cells:表示用多少个cells来描述一个中断
- gpio-ranges:将gpio编号转化为pin引脚,<&iomux 0 94 17>,表示将gpio4的第0个引脚映射为97,17表示的是引脚的个数。
gpio4这个节点对整个gpio4进行了描述。使用GPIO子系统时需要往设备树中添加很多设备节点,在驱动程序中使用GPIO子系统提供的API实现GPIO的效果。
2.1 在设备树种添加RGB灯的设备树节点
相比之前led灯的设备节点(没有使用GPIO子系统),这里只需要增加GPIO属性定义,基于GPIO子系统的rgb_led设备树节点添加到“./arch/arm/boot/dts/imx6ull-mmc-npi.dtb”设备树的根节点内。添加完成后的设备树如下表示。
/* 添加 rgb_led 节点*/rgb_led{#address-cells = <1>;#size-cells = <1>;pinctrl_names = "default";compatible = "fire,reg_led";pinctrl-0 = <&pinctrl_rgb_led>;regb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW>; regb_led_green = <&gpio4 20 GPIO_ACTIVE_LOW>;regb_led_blue = <&gpio4 19 GPIO_ACTIVE_LOW>;status = "okay";};
- 第6行:设置“compatiable”属性值,与led的平台驱动做匹配。
- 第7行:指定RGB灯的引脚pinctrl信息,上一小节定义了pinctrl节点,并且标签设置为“pinctrl_rgb_led”,在这里引用或者pinctrl信息。
- 第8-10行,指定引脚使用的哪个GPIO,编写格式如下
- 标号1:设置引脚名字,如果使用GPIO子系统提供的API操作GPIO,在驱动程序种会用到这个名字,名字是自定义的。
- 标号2:指定GPIO组
- 标号3:指定GPIO编号
- 标号4:这是一个宏定义,指定有效电平,低电平有效选择“GPIO_ACTIVE_LOW”,高电平有效选择“GPIO_ACTIVE_HIGH”
2.2 编译、下载设备树验证修改结果
编译内核时会自动编译设备树,我们可以重新编译内核,这样做的缺点是编译时间过长,在内核目录下执行如下命令,只编译设备树:
make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
如果执行了“make distclean”清理了内核,那么就需要在内核目录下执行如下命令重新配置内核(如果编译设备树出错也可以先清理内核然后执行如下命令尝试重新编译)
命令:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfig
make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
编译成功后会在“./arch/arm/boot/dts”目录下生成“imx6ull-mmc-npi.dtb”文件,将其替换掉板子/usr/lib/linux-image-4.19.35-imx6/目录下的imx6ull-mmc-npi.dtb文件并输入sudo reboot重启开发板。
重启之后正常情况下会在开发板的“/proc/device-tree”目录下生成“rgb_led”设备树节点,如下所示
2.3 GPIO子系统常用的API函数
1、获取 GPIO 编号函数 of_get_named_gpio
static inline int of_get_gpio(struct device_node *np, int index)
{return of_get_gpio_flags(np, index, NULL);
}
参数:
- np:指定设备节点。
- propname:GPIO属性名,与设备树中定义的属性名对应
- index:引脚索引值,在设备树中一条引脚属性可以包含很多个引脚,该参数用于指定获取哪个引脚
返回值:
- 成功:获取的GPIO编号(这里的GPIO编号是根据引脚属性生成一个非负整数)
- 失败:返回负数。
2、GPIO申请函数gpio_request
文件位置:内核源码/drivers/gpio/gpiolib-legacy.c
int gpio_request(unsigned gpio, const char *label)
参数:
- gpio:要申请的GPIO编号,该值是函数of_get_named_gpio的返回值
- label:引脚名子,相当于为申请得到的引脚取了个别名。
返回值:
- 成功:返回0
- 失败:返回负数
3、GPIO释放函数
void gpio_free(unsigned gpio)
gpio_free函数与gpio_request是一对相反的函数,一个申请,一个释放。一个GPIO只能申请一次,当不再使用某一个引脚时记得将其释放掉。
参数:
- gpio:要释放的GPIO编号
返回值:
无。
4、GPIO输出设置函数gpio_direction_output
用于将引脚设置为输出模式
文件位置:内核源码/include/asm-generic/gpio.h
static inline int gpio_direction_output(unsigned gpio, int value)
函数参数:
- gpio:要设置的GPIO的编号
- value:输出值,1,表示高电平。0,表示低电平。
返回值:
- 成功:返回0
- 失败:返回负数
5、GPIO输入设置函数gpio_direction_input
用于将引脚设置为输入模式。
文件位置:内核源码/include/asmgeneric/gpio.h
static inline int gpio_direction_input(unsigned gpio)
函数参数:
- gpio:要设置的GPIO的编号
返回值:
- 成功:返回0
- 失败:返回负数
6、获取GPIO引脚值函数gpio_get_value
用于获取引脚的当前状态。无论引脚被设置为输出或者输入都可以用该函数获取引脚的当前状态。
文件位置:内核源码/include/asmgeneric/gpio.h
static inline int gpio_get_value_cansleep(unsigned gpio)
函数参数:
- gpio:要获取的GPIO的编号
返回值:
- 成功:获取得到的引脚状态‘
- 失败:返回负数
7、设置GPIO输出值goio_set_value
该函数只用于那些设置为输出模式的GPIO
文件位置:内核源码/include/asmgeneric/gpio.h
static inline int gpio_direction_output(unsigned gpio, int value)
函数参数:
- gpio:设置的GPIO的编号
- value:设置的输出值,为1输出高电平,为0输出低电平
返回值:
- 成功:返回0
- 失败:返回负数
根据上面这些函数就可以在驱动程序中控制GPIO了。
三、实验
3.1 实验代码
程序包含两个C语言文件,一个是驱动程序,驱动程序在平台总线基础上编写。另一个是测试程序,用于测试驱动是否正常。
3.1.1 驱动程序
驱动程序大致分为3个部分,第一部分,编写平台设备驱动的入口和出口函数。第二部分,编写平台设备的.probe函数,在probe函数中实现字符设备的注册和RGB灯的初始化。第三部分,编写字符设备函数集,实现open和write函数。
平台驱动入口和出口函数实现
源码如下:
/*-------------------------第一部分---------------------------*/
static const struct of_device_id rgb_led[] = {
{ .compatible = "fire,rgb-led"},{ /* sentinel */ }
};/*定义平台设备结构体*/
struct platform_driver led_platform_driver = {.probe = led_probe,.remove =led_remove,.driver = {.name = "rgb-leds-platform",.owner = THIS_MODULE,.of_match_table = rgb_led,}
};/*-------------------------第二部分---------------------------*//*
*驱动初始化函数
*/
static int __init led_platform_driver_init(void)
{int DriverState;DriverState = platform_driver_register(&led_platform_driver);printk(KERN_EMERG "\tDriverState is %d\n",DriverState);return 0;
}/*-------------------------第三部分---------------------------*/
/*
*驱动注销函数
*/
static void __exit led_platform_driver_exit(void)
{printk(KERN_EMERG "HELLO WORLD exit!\n");platform_driver_unregister(&led_platform_driver);
}module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);MODULE_LICENSE("GPL");
- 第一部分:仅实现.probe函数和.driver,当驱动和设备匹配成功后会执行该函数,这个函数的实现后面介绍。.driver描述这个驱动的属性,包括.name驱动的名字,.owner驱动的所有者,.of_match_table驱动匹配表,用于匹配驱动和设备。驱动设备匹配表定义为“rgb_led”在这个表只有一个匹配值为“.compatible = “fire,rgb-led””这个值要与我们在设备树节点的“compatible”属性相同。
- 第二、三部分:是平台设备的入口和出口函数,函数实现即在入口函数注册平台驱动,在出口函数中注销平台驱动。
平台驱动.probe函数实现
当驱动和设备匹配后首先会执行probe函数,我们在probe函数中实现RGB的初始化、注册一个字符设备。后面将会在字符设备操作函数(open、write)中实现对RGB灯的控制。函数源码如下。
*----------------平台驱动函数集-----------------*/
static int led_probe(struct platform_device *pdv)
{int ret = 0; //用于保存申请设备号的结果printk(KERN_EMERG "\t match successed \n");/*-------------------------第一部分----------------------------------------*//*获取RGB的设备树节点*/rgb_led_device_node = of_find_node_by_path("/rgb_led");if(rgb_led_device_node == NULL){printk(KERN_EMERG "\t get rgb_led failed! \n");}/*-------------------------第二部分----------------------------------------*/rgb_led_red = of_get_named_gpio(rgb_led_device_node, "rgb_led_red", 0);rgb_led_green = of_get_named_gpio(rgb_led_device_node, "rgb_led_green", 0);rgb_led_blue = of_get_named_gpio(rgb_led_device_node, "rgb_led_blue", 0);printk("rgb_led_red = %d,\n rgb_led_green = %d,\n rgb_led_blue = %d,\n", rgb_led_red,\rgb_led_green,rgb_led_blue);/*-------------------------第三部分----------------------------------------*/gpio_direction_output(rgb_led_red, 1);gpio_direction_output(rgb_led_green, 1);gpio_direction_output(rgb_led_blue, 1);/*-------------------------第四部分----------------------------------------*//*---------------------注册 字符设备部分-----------------*///第一步//采用动态分配的方式,获取设备编号,次设备号为0,//设备名称为rgb-leds,可通过命令cat /proc/devices查看//DEV_CNT为1,当前只申请一个设备编号ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);if(ret < 0){printk("fail to alloc led_devno\n");goto alloc_err;}//第二步//关联字符设备结构体cdev与文件操作结构体file_operationsled_chr_dev.owner = THIS_MODULE;cdev_init(&led_chr_dev, &led_chr_dev_fops);//第三步//添加设备至cdev_map散列表中ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);if(ret < 0){printk("fail to add cdev\n");goto add_err;}//第四步/*创建类 */class_led = class_create(THIS_MODULE, DEV_NAME);/*创建设备*/device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);return 0;add_err://添加设备失败时,需要注销设备号unregister_chrdev_region(led_devno, DEV_CNT);printk("\n error! \n");
alloc_err:return -1;}
- 第一部分:使用of_find_node_by_path函数找到并获取rgb_led在设备树种的设备节点。参数“/rgb_led”是要获取的设备树节点在设备树中的路径,如果要获取的节点嵌套在其他子节点中需要写出节点所在的完美路径。
- 第二部分:使用函数of_get_named_gpio函数获取GPIO号,读取成功则返回读取得到的GPIO号。“rgb_led_red”指定GPIO的名字,这个参数要与rgb_led设备树节点中GPIO属性名对应,参数“0”指定引脚索引,设备树中一条属性只定义一个引脚,只有一个所以设置为0.
- 第三部分:将GPIO设置为输出模式,默认输出电平为高电平。
- 第四部分:字符设备相关内容。
字符设备函数
字符设备函数只需要实现open函数和write函数。函数源码如下
/*--------------------------第一部分-------------------------*/
/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
{.owner = THIS_MODULE,.open = led_chr_dev_open,.write = led_chr_dev_write,
};/*---------------------------第二部分--------------------------*/
/*字符设备操作函数集,open函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{printk("\n open form driver \n");return 0;
}/*----------------------------第三部分--------------------------*/
/*字符设备操作函数集,write函数*/
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{unsigned char write_data; //用于保存接收到的数据int error = copy_from_user(&write_data, buf, cnt);if(error < 0) {return -1;}/*设置 GPIO1_04 输出电平*/if(write_data & 0x04){gpio_direction_output(rgb_led_red, 0); // GPIO1_04引脚输出低电平,红灯亮}else{gpio_direction_output(rgb_led_red, 1); // GPIO1_04引脚输出高电平,红灯灭}/*设置 GPIO4_20 输出电平*/if(write_data & 0x02){gpio_direction_output(rgb_led_green, 0); // GPIO4_20引脚输出低电平,绿灯亮}else{gpio_direction_output(rgb_led_green, 1); // GPIO4_20引脚输出高电平,绿灯灭}/*设置 GPIO4_19 输出电平*/if(write_data & 0x01){gpio_direction_output(rgb_led_blue, 0); // GPIO4_19引脚输出低电平,蓝灯亮}else{gpio_direction_output(rgb_led_blue, 1); // GPIO4_19引脚输出高电平,蓝灯灭}return 0;
}
- 第一部分:定义字符设备操作函数集,这里主要实现open函数和write函数即可
- 第二部分:实现open函数,在平台驱动的probe函数中已经初始化了GPIO。
- 第三部分:实现write函数,首先使用“copy_from_user”函数将来自应用层的数据“拷贝”打破内核层。得到命令后依此检查后三位,根据命令值使用“gpio_direction_output”函数控制RGB灯的亮灭。
3.1.2 应用程序
应用程序则只需要打开设备节点文件,写入命令后然后关闭设备节点文件即可。
int main(int argc, char *argv[])
{printf("led_tiny test\n");
/*----------------第一部分------------------------------------------------*//*判断输入的命令是否合法*/if(argc != 2){printf(" commend error ! \n");return -1;}/*----------------第二部分------------------------------------------------*//*打开文件*/int fd = open("/dev/rgb-leds", O_RDWR);if(fd < 0){printf("open file : %s failed !\n", argv[0]);return -1;}/*----------------第三部分------------------------------------------------*/unsigned char commend = atoi(argv[1]); //将受到的命令值转化为数字;/*判断命令的有效性*//*写入命令*/int error = write(fd,&commend,sizeof(commend));if(error < 0){printf("write file error! \n");close(fd);/*判断是否关闭成功*/}/*关闭文件*/error = close(fd);if(error < 0){printf("close file error! \n");}return 0;
}
- 第一部分:判断命令是否有效。再运行应用程序时要传递一个控制命令,所以参数长度是2.
- 第二部分:打开设备文件。参数“/dev/rgb-leds”用于指定设备节点文件,设备节点文件名是在驱动程序中设置的,这里保证与驱动一致即可。
- 第三部分:由于从main函数中获取的参数是字符串,这里首先要将其转化为数字。最后用write函数写入命令后然后关闭文件即可。
3.2 实验准备
3.2.1 Makefile修改
修改Makefile并编译生成驱动程序
KERNEL_DIR=/home/geralt/linux_driver/kernel/ebf_linux_kernel_6ull_depth1/build_image/buildARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILEobj-m := rgb-leds.oapp_in = rgb_leds_app.c
app_out = rgb_leds_appall:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules$(CROSS_COMPILE)gcc -o $(app_out) $(app_in).PHONY:clean
clean:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) cleanrm $(app_out)
- 第一行:变量“KERNEL_DIR”保存的是内核所在路径,这个需要根据自己内核所在位置设定
- 第七行:“obj-m:rhb-led.o”中的“rgb-led.o”要与驱动源码名对应。Makefile文件修改完执行如下命令编译驱动。
- 第9行:“rgb_leds_app.c"是需要编译的应用程序。
- 第10行:“rgb_leds_app”是编译应用程序后输出的应用程序可执行文件。
命令:
make
正常情况下会在当前目录生成.ko驱动文件和rgb_leds_app应用程序可执行文件。
3.3 下载验证
(1)将前面编译出的.ko驱动和应用程序,添加到开发板中。
(2)执行如下命令加载驱动:
命令:
insmod ./rgb-leds.ko
(3)在驱动程序中,由于在.probe函数中注册字符设备并创建了设备文件,设备和驱动匹配成功后.probe函数执行,所以在"/dev"目录下已经生成了"rgb-leds"设备节点,如下所示。
注意:这里没有生成设备节点,需要取/boot/uEnv.txtx 取消led的设备树插件
驱动加载后直接运行应用程序如下所示。
命令:
./rgb_leds_app <命令>
执行结果如下:
命令是一个“unsigned char”型数据,只有后三位有效,每一位代表一个灯,从高到低依次是红、绿、蓝,1代表亮,0代表灭。
相关文章:
I.MX6ULL内核开发13:pinctrl子系统和gpio子系统-led实验
目录 一、pinctrl子系统 1.1 pinctrl子系统编写格式以及引脚属性介绍 1.1.1 iomux节点介绍 1.1.2 pinctrl子节点编写格式 1.1.3 引脚配置信息介绍 1.2 将RGB灯引脚添加到pinctrl子系统 1.2.1 查找RGB灯使用的引脚 1.2.2找到引脚宏定义 1.2.3 设置引脚属性 1.2.4 在…...
Linux系列 使用vi文本编辑器
作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.vi文本编辑器 1.使用vi文本编辑器 2.vi编辑器的工作模式 3.命令模式中的…...
【java基础】接口(interface)
文章目录基础介绍接口的定义关于接口字段和方法的说明使用接口抽象类和接口接口方法冲突的一些说明方法相同名称和参数,返回值相同方法名称相同,参数不同,返回值相同方法返回值不同,名称参数相同方法完全相同,一个有默…...
ChatGPT(GPT3.5) OpenAI官方API正式发布
OpenAI社区今天凌晨4点多发送的邮件,介绍了ChatGPT官方API的发布。官方介绍文档地址为“OpenAI API”和“OpenAI API”。 ChatGPT(GPT3.5)官方API模型名称为“gpt-3.5-turbo”和“gpt-3.5-turbo-0301”。API调用价格比GPT text-davinci-003模型便宜10倍。调用费用为…...
CAD中如何将图形对象转换为三维实体?
有些小伙伴在CAD绘制完图纸后,想要将图纸中的某些图形对象转换成三维实体,但却不知道该如何操作,其实很简单,本节CAD绘图教程就和小编一起来了解一下浩辰CAD软件中将符合条件的对象转换为三维实体的相关操作步骤吧! 将…...
【K8S笔记】Kubernetes 集群架构与组件介绍
K8S 官方文档 https://kubernetes.io/zh/docs/home ##注重关注 概念和任务 板块。 K8S 集群架构 K8S也是运用了分布式集群架构: 管理节点/Master 整个集群的管理,任务协作。工作节点/Node 容器运行、删除。 K8S 组件介绍 管理节点/Master 相关组件 …...
9 怎么登录VNC
1)首先在ssh登录后启动vncserver。登陆后输入下面的指令来创建自己的VNC。 命令vncserver :16 –geometry 1900x1000 其中:16是分配的端口号,1900x1000是分辨率。如果没有响应,建议执行下面操作后再次重复上面操作。 命令…...
MPI ubuntu安装,mpicc,mpicxx,mpif90的区别
介绍 MPI是并行计算的一个支持库,支持对C、C、fortran语言进行并行计算。 安装基础环境 ubuntu进行gcc/g/gfortran的安装: gcc: ubuntu下自带gcc编译器。可以通过gcc -v命令来查看是否安装。 g: sudo apt-get install buil…...
移动端笔记
目录 一、移动端基础 二、视口 三、二倍图/多倍图 四、移动端开发 (一)开发选择 (二)常见布局 (三)移动端技术解决方案 五、移动WEB开发之flex布局 六、移动WEB开发之rem适配布局 #END(…...
操作系统笔记、面试八股(一)—— 进程、线程、协程
文章目录1. 进程、线程、协程1.1 进程1.1.1 进程间的通信方式1.1.2 进程同步方式1.1.3 进程的调度算法1.1.4 优先级反转1.1.5 进程状态1.1.6 PCB进程控制块1.1.7 进程的创建和撤销过程1.1.8 为什么要有进程1.2 线程1.2.1 为什么要有线程1.2.2 线程间的同步方式1.3 协程1.3.1 什…...
Python每日一练(20230302)
目录 1. 字符串统计 2. 合并两个有序链表 3. 下一个排列 附录 Python字典内置方法 增 删 改 查 其它 1. 字符串统计 从键盘输入一个包含有英文字母、数字、空格和其它字符的字符串,并分别实现下面的功能:统计字符串中出现2次的英文字母&#…...
Numpy课后练习
Numpy课后练习 文章目录 Numpy课后练习一、前言二、题目及答案一、前言 答案仅供参考,谢谢大家! 二、题目及答案 导入Numpy包并设置随机数种子为666 import numpy as np np.random.seed(666)创建并输出一个包含12个元素的随机整数数组r1,元素的取值范围在[30,100)之间 r1 …...
动态规划dp中的子序列、子数组问题总结
目录 定义dp数组 初始化dp数组 状态转移方程 最终结果 题目 定义dp数组 这类问题的共性是会提供两个数组,寻找他们共同的子序列、子数组。设第一个数组为s,第二个数组为t。则可以设二维dp数组,其大小为len(s + 1)*len(t + 1) dp[i][j]表示 s 前 i 个长度,...
Zookeeper3.5.7版本——Zookeeper的概述、工作机制、特点、数据结构及应用场景
目录一、Zookeeper的概述二、Zookeeper的工作机制三、Zookeeper的特点四、Zookeeper的数据结构五、Zookeeper的应用场景5.1、统一命名服务5.2、统一配置管理5.3、统一集群管理5.4、服务器动态上下线5.5、软负载均衡一、Zookeeper的概述 Zookeeper 是一个开源的分布式的&#x…...
安卓逆向学习及APK抓包(二)--Google Pixel一代手机的ROOT刷入面具
注意:本文仅作参考勿跟操作,root需谨慎,本次测试用的N手Pixel,因参考本文将真机刷成板砖造成的损失与本人无关 1 Google Pixel介绍 1.1手机 google Pixel 在手机选择上,优先选择谷歌系列手机,Nexus和Pixel系列&…...
线程池的基本认识与使用
线程池的基本认识与使用线程池线程池工作原理:优点:传统的创建线程方式线程池创建线程使用线程池 池化思想:线程池、字符串常量池、数据库连接池可以提高资源的利用率 线程池工作原理: 预先创建多个线程对象 放入线程池种&#…...
小家电品牌私域增长解决方案来了
小家电品牌的私域优势 01、行业线上化发展程度高 相对于大家电动辄上千上万元的价格,小家电的客单价较低。而且与大家电偏刚需属性不同的是,小家电的消费需求侧重场景化,用户希望通过购买小家电来提高自身的生活品质。这就决定了用户的决策…...
什么是让ChatGPT爆火的大语言模型(LLM)
什么是让ChatGPT爆火的大语言模型(LLM) 更多精彩内容: https://www.nvidia.cn/gtc-global/?ncidref-dev-876561 文章目录什么是让ChatGPT爆火的大语言模型(LLM)大型语言模型有什么用?大型语言模型如何工作?大型语言模型的热门应用在哪里可以找到大型语言…...
【监控】Linux部署postgres_exporter及PG配置(非Docker)
目录一、下载及部署二、postgres_exporter配置1. 停止脚本stop.sh2. 启动脚本start.sh3. queries.yaml三、PostgreSQL数据库配置1. 修改postgresql.conf配置文件2. 创建用户、表、扩展等四、参考一、下载及部署 下载地址 选一个amd64下载 上传至服务器,解压 tax…...
基于Java+SpringBoot+Vue+Uniapp(有教程)前后端分离健身预约系统设计与实现
博主介绍:✌全网粉丝3W,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战✌ 博主作品:《微服务实战》专栏是本人的实战经验总结,《Spring家族及…...
【2023】DevOps、SRE、运维开发面试宝典之Redis相关面试题
文章目录 1、redis主从复制原理2、redis哨兵模式的原理3、reids集群原理4、Redis 哈希表进行的触发时机是什么?5、Redis 的 RDB 和 AOF 机制各自的优缺点是什么?这两种机制是否可以混合使用?6、Redis 经常被称为单线程的系统,你如何理解 Redis 的单线程模型7、redis 的事务…...
十五、MyBatis使用PageHelper
1.limit分页 limit分页原理 mysql的limit后面两个数字: 第一个数字:startIndex(起始下标。下标从0开始。) 第二个数字:pageSize(每页显示的记录条数) 假设已知页码pageNum,还有每页…...
【MySQL】B+ 树索引
一、索引是什么 ? 为什么需要索引 ? 索引就是目录,目录就是索引。 索引从 InnoDB 存储引擎数据存储结构上来看,就是为各个页建立的目录。保证我们在查询时,可以通过二分法快速定位到页,再在页内通过二分法…...
Android Gradle Plugin Version 和 Gradle Version 的对应关系
官网参考 以下是插件版本和Gradle 版本对应关系: 插件版本所需的最低 Gradle 版本Android Gradle Plugin VersionGradle Version1.0.0 - 1.1.32.2.1 - 2.31.2.0 - 1.3.12.2.1 - 2.91.5.02.2.1 - 2.132.0.0 - 2.1.22.10 - 2.132.1.3 - 2.2.32.14.1 - 3.52.3.03.33.0…...
更多单词/词组/短语补充和总结(二)
auto 美 /[ˈɔːtoʊ] n.汽车adj.与汽车有关的,汽车的。不要记成“自动的” mobile 美 /[ˈmoʊbl] adj.可移动的;流动的;不要记成“手机”,手机是mobile phone automobile 美 /[ˈɔːtəməbiːl] n.汽车adj.自动的 automatic 美 /[ˌɔːtəˈmtɪk]…...
HEC-HMS和HEC-RAS快速入门、防洪评价报告编制及洪水建模、洪水危险性评价等应用
目录 ①HEC-RAS一维、二维建模方法及实践技术应用 ②HEC-HMS水文模型实践技术应用 ③新导则下的防洪评价报告编制方法及洪水建模实践技术应用 ④基于ArcGIS水文分析、HEC-RAS模拟技术在洪水危险性及风险评估 ⑤山洪径流过程模拟及洪水危险性评价 ①HEC-RAS一维、二维建模方…...
全面了解 B 端产品设计 — 基础扫盲篇
在今天,互联网的影响力与作用与日俱增,除了我们日常生活领域的改变以外,对于商业领域的渗透也见效颇丰。 越来越多的企业开始使用数字化的解决方案来助力企业发展,包括日常管理、运营、统计等等。或者通过互联网的方式开发出新的业务形态,进行产业升级,如这几年风头正劲的…...
顺序表(增删查改)
目录一、什么是顺序表二、顺序表的增删查改2.1 结构体的声明2.2 顺序表的初始化2.3 顺序表检查容量2.4 顺序表尾部插入数据2.5 顺序表头部插入数据2.6 顺序表尾部删除数据2.7 顺序表头部删除数据2.8 顺序表查找数据2.9 顺序表任意位置插入数据2.10 顺序表任意位置删除数据2.11 …...
一款优秀的低代码开发平台是什么样的?
目录 一、一款优秀的低代码平台应该是什么样的? 二、低代码核心能力 01、全栈可视化编程: 02、全生命周期管理: 03、低代码扩展能力: 三、小结 一、一款优秀的低代码平台应该是什么样的? 从企业角度来说&#x…...
ElasticSearch 学习笔记总结(四)
文章目录一、ES继承 Spring Data 框架二、SpringData 功能集成三、ES SpringData 文档搜索四、ES 优化 硬件选择五、ES 优化 分片策略六、ES 优化 路由选择七、ES 优化 写入速度优化七、ES 优化 内存设置八、ES 优化 重要配置一、ES继承 Spring Data 框架 Spring Data 是一个用…...
豫icp郑州网站建设/百度地图关键词排名优化
网络的核心设备并不在应用层上起作用,而仅在较底层起作用,特别是在网络层及下面的层次起作用;特别是在网络层及下面层次起作用。这种基本设计,即将应用层软件限制在端系统的方法,促进了大量的网络应用程序的迅速研发和…...
重庆江北营销型网站建设公司推荐/谷歌浏览器搜索入口
统计表格是实验数据、统计结果或事物分类的一种有效表达形式,是科技论文中经常使用的一种特殊信息语言,是描述科技文献的重要工具和手段。在撰写科技论文的过程中,通过正确使用统计表格,对获取到的资料数据进行归纳、整理、统计学…...
展览设计制作公司/北京百度搜索排名优化
最常用的Eclipse的快捷键,各个实用,由Java攀登网提供。 虽然现在IDEA非常的好用,但是Eclipse还是有一定的受众用户,包括我现在的公司,领导就是让用Eclipse,因此喜欢Eclipse的朋友可以看下。 后面可以再讲…...
企业网站报价方案模板/今日要闻 最新热点
最大子段和: 给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]a[i1]…a[j]的子段和的最大值当所给的整数均为负数时定义子段和为0,依此定义,所求的最优值为: Max{0,a[i]a[i1]…a[j…...
名片式网站模板/制作网页的流程
VRRP工作原理 简介:VRRP:(Virtual Router Redundancy Protocol)虚拟路由器冗余协议主用路由器master:负责转发发给虚拟路由器的数据包,并响应ARP请求的路由器。若一台拥有与虚拟路由器有相同IP地址的VRRP路…...
wordpress swf 上传/惠州seo排名收费
什么是存储过程:存储过程一般用于处理比较复杂的任务,基础ms这个平台,可以大大降低耗时,其编译机制也提高了数据库执行速度。 当然在系统控制方便方面,例如当系统进行调整时,这是只需要将后台存储过程进行更…...