正点原子嵌入式linux驱动开发——pinctrl和gpio子系统

news2025/1/17 1:13:26

在上一篇笔记中,学习编写了基于设备树的LED驱动,但是驱动的本质还是没变,都是配置LED灯
所使用的GPIO寄存器,驱动开发方式和裸机基本没区别。Linux是一个庞大而完善的系统,尤其是驱动框架,像GPIO这种最基本的驱动不可能采用裸机驱动开发方式。Linux内核提供了pinctrl和gpio子系统用于GPIO驱动,本章就学习一下如何借助pinctrl和gpio子系统来简化GPIO驱动开发。

pinctrl子系统

pinctrl子系统简介

Linux驱动讲究驱动分离与分层,pinctrl和gpio子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架

先来回顾一下上一篇笔记中是怎么初始化LED灯所使用的GPIO,步骤如下:

  1. 修改设备树,添加相应的节点,节点里面重点是设置reg属性,reg属性包括了GPIO相关寄存器。
  2. 获取 reg属性中GPIOI_MODER、GPIOI_OTYPER、GPIOI_OSPEEDR、GPIOI_PUPDR和GPIOI_BSRR这些寄存器的地址,并且初始化它们,这些寄存器用于设置PI0这个PIN的复用功能、上下拉、速度等。
  3. 在2里面将PI0这个PIN设置为通用输出功能,因此需要设置PI0这个GPIO相关的寄存器,也就是设置GPIOI_MODER寄存器。
  4. 在2里面将PI0这个PIN设置为高速、上拉和推挽模式,就要需要设置PI0的GPIOI_OTYPER、GPIOI_OSPEEDR和GPIOI_PUPDR这些寄存器。

对于大多数的32位 SOC而言,引脚的设置基本都是这两方面,因此Linux内核针对PIN的复用配置推出了pinctrl子系统,对于GPIO的电气属性配置推出了gpio子系统

大多数SOC的pin都是支持复用的,比如STM32MP1的PI0既可以作为普通的GPIO使
用,也可以作为SPI2的NSS引脚、TIM5的CH4引脚等等。此外还需要配置pin的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置pin的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如pin功能冲突)。pinctrl子系统就是为了解决这个问题而引入的,pinctrl子系统主要工作内容如下:

  1. 获取设备树中pin信息。
  2. 根据获取到的pin信息来设置pin的复用功能。
  3. 根据获取到的pin信息来设置pin的电气特性,比如上/下拉、速度、驱动能力等。

对于使用者来讲,只需要在设备树里面设置好某个pin的相关属性即可,其他的初始化工作均由pinctrl子系统来完成,pinctrl子系统源码目录为drivers/pinctrl

STM32MP1的pinctrl子系统驱动

PIN配置信息详解

要使用pinctrl子系统,需要在设备树里面设置PIN的配置信息,一般会在设备树里面创建一个节点来描述PIN的配置信息。打开stm32mp151.dtsi文件,找到一个叫做pinctrl的节点,如下所示:
pinctrl节点
第1815-1816行,#address-cells属性值为1和#size-cells属性值为1,也就是说pinctrl下的所有子节点的reg第一位是起始地址,第二位为长度

第1818行,ranges属性表示STM32MP1的GPIO相关寄存器起始地址,STM32MP1系列芯片最多拥有176个通用 GPIO,分为12组,分别为:PA0-PA15、PB0-PB15、PC0-PC15、PD0-PD15、PE0-PE15、PF0-PF15、PG0-PG15、PH0-PH15、PI0-PI15、PJ0-PJ15、PK0-PK7、PZ0-PZ7。

其中PA-PK这11组 GPIO的寄存器都在一起,起始地址为0X50002000,终止地址为0X5000C3FF。PZ组寄存器起始地址为0X54004000,终止地址为0X540043FF,所以stm32mp151.dtsi文件里面还有个名为“pinctrl_z”的子节点来描述PZ组 IO。pinctrl节点用来描述PA-PK这11组IO,因此ranges属性中的0x50002000表示起始地址, 0xa400表示寄存器地址范围

第1819行,interrupt-parent属性值为“&exti”,父中断为exti。

后面的gpiox子节点先不分析,这些子节点都是gpio子系统的内容,到后面在去分析。打开stm32mp15-pinctrl.dtsi文件,能找到如下内容:
pinctrl节点
示例代码25.1.2.2就是向pinctrl节点追加数据,不同的外设使用的PIN不同、其配置也不同,将某个外设所使用的所有PIN都组织在一个子节点里面。示例代码25.1.2.2中m_can1_pins_a子节点就是CAN1的所有相关IO的PIN集合,pwm1_pins_a子节点就是PWM1相关IO的PIN集合。绑定文档 Documentation/devicetree/bindings/pinctrl/st,stm32-pinctrl.yaml描述了如何在设备树中设置STM32的PIN信息

集合里面存放当前外设用到哪些引脚(PIN)、这些引脚应该怎么配置、复用相关的配置、上下拉、
默认输出高电平还是低电平。一般这个存放pincrtl集的子节点名字是“pins”,如果某个外设用到多种配置不同的引脚那么就需要多个pins子节点,比如示例代码25.1.2.2中第535行和541行的pins1和pins2这两个子节点分别描述PH13和PI9的配置方法,由于PH13和PI9这两个IO的配置不同,因此需要两个pins子节点来分别描述。第555-562行的pins子节点是描述PWM1的相关引脚,包括PE9、PE11、PE14,由于这三个引脚的配置是一模一样的,因此只需要一个pins子节点就可以了

上面讲了,在pins子节点里面存放外设的引脚描述信息,这些信息包括:

1、pinmux属性

此属性用来存放外设所要使用的所有IO,通过STM32_PINMUX宏来配置引脚和引脚的复用功能,定义在include/dt-bindings/pinctrl/stm32-pinfunc.h文件里面,内容如下:
STM32_PINMUX宏定义
可看出有3个参数,含义如下:

  • port:表示用哪一组 GPIO(例:H表示为GPIO第H组,也就是GPIOH)。
  • line:表示这组GPIO的第几个引脚 (例:13表示为GPIOH_13,也就是PH13)。
  • mode:表示当前引脚要做那种复用功能 (例:AF9表示为用第9个复用功能 ),这个需要查阅STM32MP1数据手册来确定使用哪个复用功能。

这里要注意,比如PH13做了FDCAN1的TX功能,还能做UART4的TX功能,这是不会冲突的,因为pinctrl驱动只会把设备树pinctrl节点解析出来的数据存储到一个链表里,只有当外设调用这个pinctrl节点的时候才会真的使用。但是,如果要同时使用FDCAN1和UART4的话就会出问题,因此PH13同一时间只能用于一个外设,所以为了方便开发,还是建议一个PIN最好只能被一个外设使用。

2、电气属性配置

接下来了解一下PIN的电气特性如何设置,电气特性在pinctrl子系统里不是必须的,可以不配置,但是pinmux属性是必须要设置的。stm32-pinctrl.yaml文件里面也描述了如何设置STM32的电气属性,如下图所示:
pinctrl电气特性
上图中bootlean类型表示了在pinctrl子系统只要定义这个电气属性就行了,例如:要禁用内部电压,只要在PIN的配置集里添加“bias-disable”即可,这个时候bias-pull-down和bias-pull-up都不能使用了,因为已经禁用了内部电压,所以不能配置上下拉。enum类型使用方法更简单跟C语言的一样,比如要设置PIN速度为最低就可以使用“slew-rate=<0>”。

PIN驱动程序讲解

所有的东西都已经准备好了,包括寄存器地址和寄存器值,Linux内核相应的驱动文件就会根据这些值来做相应的初始化。接下来就找一下哪个驱动文件来做这一件事情,pinctrl节点中compatible属性的值为“st,stm32mp157-pinctrl”,在Linux内核中全局搜索“pinctrl”字符串就会找到对应的驱动文件。在文件drivers/pinctrl/stm32/pinctrl-stm32mp157.c中有如下内容:
pinctrl-stm32mp157.c文件代码段
第2334-2344行,of_device_id结构体类型的数组,在讲解设备树的时候说过,of_device_id里面保存着这个驱动文件的兼容性值,设备树中的compatible属性值会和of_device_id中的所有兼容性字符串比较,查看是否可以使用此驱动。stm32mp157_pctrl_match结构体数组一共有两个兼容,分别为“st,stm32mp157-pinctrl”和 st,stm32mp157-z-pinctrl”,设备树也定义了这两个兼容性值,因此pinctrl和pinctrl_z节点都会和此驱动匹配,所以pinctrl-stm32mp157.c会完成STM32MP1的PIN配置工作

第2350-2357行,platform_driver是平台设备驱动,这个是后面章节要讲解的内容,platform_driver是个结构体,有个probe成员变量。在这里只需要知道,当设备和驱动匹配成功以后platform_driver的probe成员变量所代表的函数就会执行,在2351行设置probe成员变量为stm32_pctl_probe函数,因此在本章实验中stm32_pctl_probe这个函数就会执行,可以认为stm32_pctl_probe函数就是STM32MP157这个 SOC的PIN配置入口函数

第2359-2362行,就是一个简单的驱动入口函数,platform_driver_register函数是一个标准的平台设备驱动注册函数,用于向Linux内核注册一个platform_driver,这里就是将stm32mp157_pinctrl_driver注册到Linux内核总线,关于平台设备驱动后面章节会详细讲解。

重点来分析一下stm32_pctl_probe函数,函数定义在drivers/pinctrl/stm32/pinctrl-stm32.c里面,函数内容如下所示:
stm32_pctl_probe代码段
第1458行,看它的结构体的名字就知道是ST官方自定义的一个结构体类型,用于存放STM32相关PIN属性集合。要想驱动能通用,就要用结构体来保存数据和驱动里面尽量不要使用全局变量(在pinctrl驱动里就没有一个全局变量,全部使用结构体来描述一个物体,物体的所有属性都作为结构体成员变量)。接着去看下stm32_pinctrl结构体是如何定义的,如下图所示:
stm32_pinctrl结构体代码段
第103行,pinctrl_desc结构体用来描述PIN控制器,PIN控制器用于配置SOC的PIN复用功能和电气特性

第107行,这个stm32_gpio_bank结构体,是用来注册GPIO驱动。到后面GPIO子系统再说。

pinctrl_desc结构体内容如下所示:
pinctrl_desc结构体
第134-136行,这三个“_ops”结构体指针非常重要!因为这三个结构体就是PIN控制器的“工具”,这三个结构体里面包含了很多操作函数,Linux内核初始化PIN最终使用的就是这些操作函数。因此编写一个SOC的PIN控制器驱动的核心就是实现pinctrl_desc里面的pctlops、pmxops和confops,pinctrl_desc结构体需要由用户提供,结构体里面的成员变量也是用户编写的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的Linux内核源码中已经把这些工作做完了

示例代码 25.1.2.8里,第1536-1538行,给这三个结构体赋值分别对应stm32_pconf_ops、stm32_prtrl_ops和stm32_pmx_ops,三个结构体如下:
stm32_pconf_ops、stm32_prtrl_ops、stm32_pmx_ops结构体
pinctrl_desc结构体初始化完成以后,需要调用pinctrl_register或者devm_pinctrl_register函
数就能够向Linux内核注册一个PIN控制器
,示例代码25.1.2.8中的1542行就是向Linux内核注册PIN控制器。

总结一下,pinctrl驱动流程如下:

  1. 定义pinctrl_desc结构体。
  2. 初始化结构体, 重点是pinconf_ops、pinmux_ops和pinctrl_ops这三个结构体成员变量,但是这部分半导体厂商帮我们搞定。
  3. 调用devm_pinctrl_register函数完成PIN控制器注册。

设备树中添加pinctrl节点模板

接下来学习一下如何在设备树中添加某个外设的PIN信息 。比如需要将PG11这个PIN复用为UART4_TX引脚,pinctrl节点添加过程如下:

  1. 创建对应节点:在pinctrl节点下添加“uart4_pins”节点。
  2. 添加“pins”属性:这个子节点是真正来描述PIN配置的,同一个pins子节点需要所有PIN电气属性一样。
  3. 在“pins”节点中添加PIN配置信息。

最终结果如下:

示例代码25.1.3.3完整的uart4_pins设备pinctrl子节点 
1 &pinctrl { 
2     uart4_pins: uart4-0 { 
3         pins1{ 
4             pinmux = <STM32_PINMUX('G', 11, AF6)>; /* UART4_TX */ 
5             bias-disable; 
6             drive-push-pull; 
7         }; 
8     }; 
9 };

按理来说,将IO用作GPIO功能也需要创建对应pinctrl节点,并将IO复用为GPIO,但是对于STM32MP1而言,如果IO用作GPIO功能,就可以不需要创建对应的pinctrl节点。

gpio子系统

gpio子系统简介

上一小节讲解了pinctrl子系统,pinctrl子系统重点是设置PIN(有的SOC叫做PAD)的复用和电气属性,如果pinctrl子系统将一个PIN复用为GPIO的话,那么接下来就要用到gpio子系统了。gpio子系统顾名思义,就是用于初始化GPIO并且提供相应的API函数,比如设置GPIO为输入输出,读取GPIO的值等。gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用GPIO

STM32MP1的gpio子系统驱动

设备树中的gpio信息

以PI0引脚所在的GPIOI为例,打开stm32mp151.dtsi,找到如下内容:
gpioi控制器节点
第1912-1921行就是GPIOI的控制器信息,属于pincrtl的子节点,因此对于STM32MP1而言,pinctrl和gpio这两个子系统的驱动文件是一样的,都为pinctrl-stm32mp157.c,所以在注册pinctrl驱动顺便会把gpio驱动程序一起注册。绑定文档
Documentation/devicetree/bindings/gpio/gpio.txt详细描述了gpio控制器节点各个属性信息

第1913行,“gpio-controller”表示gpioi节点是个GPIO控制器,每个GPIO控制器节点必须包含“gpio-controller”属性。

第1914行,“#gpio-cells”属性和“#address-cells”类似,#gpio-cells应该为2,表示一共有两个cell,第一个cell为GPIO编号,比如“&gpioi 0”就表示PI0。第二个cell表示GPIO极性,如果为0(GPIO_ACTIVE_HIGH)的话表示高电平有效,如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。

第1917行,reg属性设置了GPIOI控制器的寄存器基地址偏移为0X800,因此GPIOI寄存器地址为0X50002000+0X800=0X5000A000,可以打《STM32MP157参考手册》,就可以找到GPIOI控制器的基地址就是0X5000A000。

第1918行,clocks属性指定这个GPIOI控制器的时钟。

示例代码25.2.2.1中的是GPIOI控制器节点,当某个具体的引脚作为GPIO使用的时候还需要进一步设置。ST官方EVK开发板将PG1用作SD卡的检测(CD)引脚, PG1复用为GPIO功能,通过读取这个 GPIO的高低电平就可以知道SD卡有没有插入。这里肯定需要设备树来告诉驱动,在设备树中的SD卡节点下添加一个属性来描述SD卡的CD引脚就行了,SD卡驱动直接读取这个属性值就知道SD卡的CD引脚使用的是哪个GPIO了。 ST官方EVK开饭的SD卡连接在STM32MP157的sdmmc1接口上,在stm32mp15xx-edx.dtsi中找到名为“sdmmc1”的节点,这个节点就是SD卡设备节点,如下所示:
设备树中SD卡节点
第338行,**属性“cd-gpios”描述了SD卡的CD引脚使用的哪个IO。**属性值一共有三个,来看一下这三个属性值的含义,“&gpiog”表示CD引脚所使用的IO属于GPIOG组,“1”表示 GPIOG组的第1号 IO,通过这两个值SD卡驱动程序就知道CD引脚使用了PG1这个GPIO。最后一个是“GPIO_ACTIVE_LOW | GPIO_PULL_UP”,Linux内核定义在include/linux/gpio/machine.h文件中定义了枚举类型gpio_lookup_flags,内容如下:gpio_lookup_flag枚举类型

可以通过或运算组合不同的配置内容,示例代码25.2.2.2中的338行,“GPIO_ACTIVE_LOW”表示低电平有效,“GPIO_PULL_UP”表示上拉,所以PG1引脚默认上拉,而且低电平有效(当 PG1被拉低的时候表示SD卡插入)。

GPIO驱动程序简介

前面一小节说过了STM32MP1的pinctrl驱动和gpio驱动是同一个驱动文件,都为pinctrl-stm32mp157.c,所以入口函数都是stm32_pctl_probe,找到如下代码所示:
stm32_pctl_probe代码段
第1586行,判断设备树节点,是否有gpio-controller。如果存在,那么这个节点就是一个
GPIO控制器节点。

第1587行,stm32_gpiolib_register_bank函数用来注册GPIO驱动,包括生成回调函数,注册的过程是跟pinctrl驱动注册是一样的。都是创建自己的结构体,然后初始化结构体,调用内核的注册函数,这样把自己的结构体注册到内核。

gpio子系统API函数

对于驱动开发人员,设置好设备树以后就可以使用gpio子系统提供的API函数来操作指定的GPIO,gpio子系统向驱动开发人员屏蔽了具体的读写寄存器过程。gpio子系统提供的常用API有如下几个。

gpio_request函数

该函数用于申请一个GPIO管脚,在使用一个GPIO之前一定要使用gpio_request进行申请,函数原型如下:

int gpio_request(unsigned gpio, const char *label) 

函数参数和返回值含义如下:

  • gpio:要申请的gpio标号,使用of_get_named_gpio函数从设备树获取指定GPIO属性信息,此函数会返回这个GPIO的标号。
  • label:给gpio设置个名字。
  • 返回值:0,申请成功;其他值,申请失败。

gpio_free函数

如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放。函数原型如下:

void gpio_free(unsigned gpio)

函数参数和返回值含义如下:

  • gpio:要释放的gpio标号。
  • 返回值:无。

gpio_direction_input函数

此函数用于设置某个GPIO为输入,函数原型如
下所示:

int gpio_direction_input(unsigned gpio)

函数参数和返回值含义如下:

  • gpio:要设置为输入的GPIO标号。
  • 返回值:0,设置成功;负值,设置失败。

gpio_direction_output函数

此函数用于设置某个GPIO为输出,并且设置默认输出值,函数原型如下:

int gpio_direction_output(unsigned gpio, int value)

函数参数和返回值含义如下:

  • gpio:要设置为输出的GPIO标号。
  • value:GPIO默认输出值。
  • 返回值:0,设置成功;负值,设置失败。

gpio_get_value函数

此函数用于获取某个GPIO的值 (0或1),此函数是个宏,定义如下所示:

#define gpio_get_value __gpio_get_value 
int __gpio_get_value(unsigned gpio)

函数参数和返回值含义如下:

  • gpio:要获取的GPIO标号。
  • 返回值:非负值,得到的GPIO值;负值,获取失败。

gpoio_set_value函数

此函数用于设置某个GPIO的值,此函数是个宏,定义如下:

#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

函数参数和返回值含义如下:

  1. gpio:要设置的GPIO标号。
  2. value:要设置的值。
  3. 返回值:无。

设备树中添加gpio节点模板

以正点原子STM32MP157开发板的LED0为例,学习创建GPIO节点。LED0连到PI0引脚上:

  1. 创建led设备节点。
  2. 添加GPIO属性信息。

完成后如下所示:

示例代码25.2.4.2 向led节点添加gpio属性 
1 led { 
2     compatible = "atk,led"; 
3     gpio = <&gpioi 0 GPIO_ACTIVE_LOW>; 
4     status = "okay"; 
5 }; 

gpio相关OF函数

在驱动程序中需要读取gpio属性内容, Linux内核提供了几个与GPIO有关的OF函数,常用的几个OF函数如下所示:

of_gpio_named_count函数

该函数用于获取设备树某个属性里面定义了几个 GPIO信息,要注意的是空的GPIO信息也会被统计到。此函数原型如下:

int of_gpio_named_count(struct device_node *np, const char *propname)

函数参数和返回值含义如下:

  • np:设备节点。
  • propname:要统计的GPIO属性。
  • 返回值:正值,统计到的GPIO数量;负值,失败。

of_gpio_count函数

此函数统计的是“gpios”这个属性的GPIO数量,而of_gpio_named_count函数可以统计任意属性的GPIO信息,函数原型如下所示:

int of_gpio_count(struct device_node *np)

函数参数和返回值含义如下:

  • np:设备节点。
  • 返回值:正值,统计到的GPIO数量;负值,失败。

of_get_named_gpio函数

此函数获取GPIO编号,因为Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备树中类似<&gpioi 0(GPIO_ACTIVE_LOW | GPIO_PULL_UP)>的属性信息转换为对应的GPIO编号,此函数在驱动中使用很频繁!函数原型如下:

int of_get_named_gpio(struct device_node *np, const char *propname, int index)

函数参数和返回值含义如下:

  • np:设备节点。
  • propname:包含要获取GPIO信息的属性名。
  • index:GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0。
  • 返回值:正值,获取到的GPIO编号;负值,失败。

硬件原理图

就是之前的LED原理图。

实验程序编写

修改设备树文件

在stm32mp157d-atk.dts文件的根节点“/”下创建LED灯节点,节点名为“gpioled”,节点内容如下:

示例代码25.4.1.1 创建LED灯节点 
1 gpioled { 
2     compatible = "alientek,led"; 
3     status = "okay"; 
4     led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>; 
5 };

第4行,led-gpio属性指定了LED灯所使用的GPIO,在这里就是GPIOI的0号,低电平有效。稍后编写驱动程序的时候会获取led-gpio属性的内容来得到GPIO编号,因为gpio子系统的API操作函数需要GPIO编号。设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的stm32mp157d-atk.dtb文件启动Linux系统。启动成功以后进入“/proc/device-tree”目录中查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证)。

LED灯驱动程序编写

在之前设备树类似裸机开发的LED驱动基础上进行改写。

主要的区别在于:
需要再色河北结构体gpioled_dev中添加led_gpio这个成员,保存LED使用的GPIO编号;然后将gpioled这个设备结构体变量设为filp的私有数据private_data;之后读取private_data得到设备结构体变量gpioled;然后就可以直接在led_write中使用gpio_set_value来完成LED开关。

之后在led_init中,通过of_find_node_by_path(“/gpioled”)来获取设备节点并保存到gpioled.nd中;之后通过of_property_read_string读取status属性;通过of_property_read_string获取compatible属性;通过of_get_named_gpio读取LED编号,存入gpioled.led_gpio中;之后通过gpio_request申请使用GPIO,再由gpio_direction_output输出高电平来默认关闭LED。

编写测试APP

可以直接用之前的ledApp.c文件。

运行测试

编译驱动程序和测试APP

驱动只需要把Makefile中obj-m的值改为gpioled.o然后“make -j8”即可,APP可以用如下命令编译:

arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp

运行测试

将之前编译得到的gpioled.ko和ledApp拷贝到rootfs/lib/modules/5.4.31目录中,然后重启开发板,进入/lib/modules/5.4.31目录,输入如下命令加载gpioled.ko:

depmod //第一次加载驱动的时候需要运行此命令
modprobe gpioled //加载驱动

加载成功后可以通过如下命令打开和关闭LED:

./ledApp /dev/gpioled 1 //打开LED灯
./ledApp /dev/gpioled 0 //关闭LED灯

可以通过如下命令卸载驱动:

rmmod dtsled.ko

总结

本章通过Linux最常用的pinctrl和gpio子系统,结合设备树来控制LED灯。主要是通过修改设备树stm32mp157d-atk.dts添加gpioled节点;然后在驱动程序中,在gpioled_dev结构体中添加int led_gpio记录GPIO编号并申请结构体gpioled代表led设备;led_write中,就可以通过private_data获取设备信息,然后由gpio_set_value来控制LED;led_init中就通过OF函数找到节点之后,获取status和compatible属性来匹配,最终由of_get_named_gpio读取LED编号,并由gpio_request申请对LED的使用并通过gpio_direction_output输出高电平。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1107816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++11 正则表达式详解

目录 1 正则表达式语法1.1 字符和特殊字符1.2 限定符1.3 定位符1.4 选择和反向引用 2 C正则表达式标准库常用接口3 C正则表达式模板的使用3.1 匹配&#xff08;Match&#xff09;3.2 搜索&#xff08;Search&#xff09;3.3 分词&#xff08;Tokenize&#xff09;3.4 替换&…

【前端学习】—ES6新增的方法有哪些(十五)

【前端学习】—ES6新增的方法有哪些&#xff08;十五&#xff09; 一 、ES6中新增的方法 &#xff08;一&#xff09;、Object.is() //用于判断两个值/数据类型是否相等/* 特点&#xff1a;不仅可以对值类型进行正常处理&#xff0c;对象类型的值也可以处理对于特殊的值NaN 也…

分享一下怎么做多门店小程序

近年来&#xff0c;随着互联网的普及和移动支付的兴起&#xff0c;越来越多的商家开始涉足线上业务&#xff0c;开发自己的小程序。对于拥有多家门店的连锁品牌来说&#xff0c;开发多门店小程序是一个非常不错的选择。本文将围绕多门店小程序的制作展开讨论&#xff0c;希望能…

阿里8年经验之谈 —— 如何选择合适的自动化测试工具?

自动化测试是高质量软件交付领域中最重要的实践之一。在今天的敏捷开发方法中&#xff0c;几乎任一软件开发过程都需要在开发阶段的某个时候进行自动化测试&#xff0c;以加速回归测试的工作。自动化测试工具可以帮助测试人员以及整个团队专注于自动化工具无法处理的各自任务&a…

【网络安全 --- XSS绕过】xss绕过手法及思路你了解吗?常见及常用的绕过手法了解一下吧

本次博客以pikachu靶场为例&#xff0c;需要安装靶场可以参考以下博客&#xff08;都包含工具&#xff0c;镜像下载地址&#xff09; 一&#xff0c;工具资源下载 1-1 VMware虚拟机的安装 请参考以下博客&#xff0c;包含资源下载地址&#xff08;若已安装请忽略&#xff09;…

2023年知名国产数据库厂家汇总

随着信创国产化的崛起&#xff0c;大家纷纷在寻找可替代的国产数据库厂家。这里小编就给大家汇总了一些国内知名数据库厂家&#xff0c;仅供参考哦&#xff01; 2023年知名国产数据库厂家汇总 1、人大金仓 2、瀚高 3、高斯 4、阿里云 5、华为云 6、浪潮 7、达梦 8、南大…

基于java网上书城系统的设计与实现

1 绪 论 网上书城系统采用了一种ssm的开发框架。讨论该系统的需求分析&#xff0c;将系统分为两大模块。前台模块由五部分功能组成&#xff0c;而后台模块则由八部分功能组成。作为网上书城系统&#xff0c;它的设计目的是改变传统的图书销售模式&#xff0c;迎合当代主流需…

acwing算法基础之数据结构--KMP算法

目录 1 知识点2 模板 1 知识点 KMP算法已经集成到string类型的find()方法了&#xff0c; 但这里我们不用这个&#xff0c;我们自己来实现这个方法。 KMP算法的关键步骤&#xff1a; p[N]表示输入模式串&#xff0c;求取该模式串的ne[]数组。ne[i]表示前缀等于后缀的长度&…

电压放大器在电子实验中有哪些作用

电压放大器在电子实验中扮演着重要的角色&#xff0c;它可以实现对电压信号的放大&#xff0c;为实验提供所需的电压级别。下面是电压放大器在电子实验中的几个常见作用&#xff1a; 信号放大&#xff1a;电压放大器的主要作用是将输入信号的幅度放大&#xff0c;以便进行更准确…

AXI总线协议

总线&#xff1a;总线是传输数据的通道&#xff0c;由各种逻辑器件构成&#xff0c;一般由数据线、地址线、控制线等构成 接口&#xff1a;连接标准&#xff0c;又称之为物理接口i 协议&#xff1a;传输数据的规则 什么是AXI AXI(Advanced Extensible Interfece)是高级可扩展…

ModelCenter—多学科设计优化软件

产品概述 Ansys ModelCenter是美国Ansys公司旗下的一款产品&#xff0c;用于赋能工程师创建和自动化多工具工作流&#xff0c;优化产品设计。ModelCenter是一个创新的软件框架&#xff0c;可以灵活地满足基于模型的需求工程。在ModelCenter框架内工作&#xff0c;工程师能够将…

GDPU 数据结构 天码行空5

一、实验目的 1&#xff0e;掌握队列的顺序存储结构 2&#xff0e;掌握队列先进先出运算原则在解决实际问题中的应用 二、实验内容 仿照教材顺序循环队列的例子&#xff0c;设计一个只使用队头指针和计数器的顺序循环队列抽象数据类型。其中操作包括&#xff1a;初始化、入队…

安全典型配置(四)使用自反ACL实现单向访问控制案例

【微|信|公|众|号&#xff1a;厦门微思网络】 安全典型配置&#xff08;一&#xff09;使用ACL限制FTP访问权限案例_厦门微思网络的博客-CSDN博客 安全典型配置&#xff08;二&#xff09;使用ACL限制用户在特定时间访问特定服务器的权限-CSDN博客 安全典型配置&#xff0…

【Note】CNN与现代卷积神经网络part2(附Pytorch代码)

文章目录 1.4 多输入多输出通道1.4.1 多输入通道1.4.2 多输出通道1.4.3 11卷积层1.4.4 Summary 1.5 汇聚层1.5.1 最大汇聚层和平均汇聚层1.5.2 填充和步幅1.5.3 多个通道1.5.4 Summary 1.6 卷积神经网络&#xff08;LeNet&#xff09;1.6.1 LeNet1.6.2 模型训练1.6.3 Summary 本…

一篇文章让你两种方式调用星火大模型,搭建属于自己的“chatgpt”

申请 网址&#xff1a;星火大模型api注册链接 选择零元购 获取你专属的key、密钥、appid 方法1&#xff1a;使用jquery直接调用 html: //应用插件-此插件用于对url编码加密&#xff0c;资源包已经上传&#xff0c;审核通过后&#xff0c;会在顶部显示<script src"…

三级分类部分三级目录无法加载,后端接口能在前端返回所有数据

项目场景&#xff1a; 实现ElementUI中三级分类的功能&#xff0c;发现没有前端三级目录的二级目录可以新建三级目录&#xff0c;数据库中也有数据&#xff0c;但是无法在前端显示&#xff01;后端的接口没有返回数据库的数据。 问题描述 提示&#xff1a;这里描述项目中遇到…

MySQL数据库varchar字段求和出现精度丢失

问题描述 在MySQL数据库中&#xff0c;将varchar字段用于数值运算时&#xff0c;会将其转换为数值类型进行计算。然而&#xff0c;由于varchar字段的可变长度特性&#xff0c;可能存在数值精度丢失的问题。 我用varchar类型存储学生的分数&#xff0c;分数有两位小数&#xff…

新业务场景如何个性化配置验证码?

验证码作为人机交互界面经常出现的关键要素&#xff0c;是身份核验、防范风险、数据反爬的重要组成部分&#xff0c;广泛应用网站、App上&#xff0c;在注册、登录、交易、交互等各类场景中发挥着巨大作用&#xff0c;具有真人识别、身份核验的功能&#xff0c;在保障账户安全方…

Cron表达式每隔两小时执行一次

Cron表达式每隔两小时执行一次 0 0 */2 * * ?验证正确性

7.自定义凸多边形

愿你出走半生,归来仍是少年&#xff01; 在Gis中常用的地物表达包含点线面&#xff0c;三维场景中可通过立方体的形式表达面妆物体&#xff0c;例如房屋等等。在Unity中为提供多边形的构建对象&#xff0c;此处通过wkt构建出在三维场景中的基于面的立方体。 通过传入wkt、底部高…