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

在上一篇笔记中,学习编写了基于设备树的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.rhkb.cn/news/164556.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

快速了解服务器单CPU与双CPU

​  在当今快节奏的技术环境中&#xff0c;用户们对功能强大且高效的服务器配置需求不断增长。CPU作为构成任何计算基础设施的骨干&#xff0c;服务器的“大脑”&#xff0c;负责执行计算、控制数据流并协调各个组件之间的任务&#xff0c;是服务器选择硬件中的重要一环。因此…

Vue2之防抖_debounce封装函数v-debounce自定义指令(传参/不传)

目录 1、防抖 2、debounce - 封装函数 3、v-debounce 全局自定义指令 1、防抖 推荐文章 &#xff1a; https://blog.csdn.net/weixin_58099903/article/details/119902796 2、debounce - 封装函数 utils / tools.js /*** 函数防抖 是n秒后延迟执行&#xff0c;多用于页面scr…

Java虚拟机常见面试题总结

梳理Java虚拟机相关的面试题&#xff0c;主要参考《深入理解Java虚拟机 JVM高级特性与最佳实践》(第2版, 周志明 著)一书&#xff0c;其余部分整合网络相关内容。注意&#xff0c;关于Java并发编程的面试题因为内容较多&#xff0c;单独整理。Java基础相关的面试题可以参考Java…

海外调查问卷赚钱是真的吗?

海外问卷赚钱是真实的吗&#xff1f;我是橙河网络&#xff0c;一家问卷公司的老板&#xff0c;做这个行业已经2年时间了&#xff0c;首先给大家一个明确的回答&#xff1a;海外问卷调查赚钱是真实的&#xff01; 海外问卷调查项目&#xff0c;在国内已经存在一二十年的时间了&…

Kali Linux 安装搭建 hadoop 平台 详细教程

1&#xff09;前期环境准备&#xff1a;&#xff08;虚拟机、jdk、ssh&#xff09; 2&#xff09;SSH相关配置 安装SSH Server服务器&#xff1a;apt-get install openssh-server 更改默认的SSH密钥 cd /etc/ssh mkdir ssh_key_backup mv ssh_host_* ssh_key_backup 创建新…

AUTOSAR AP硬核知识点梳理(1)

一 什么是 Adaptive AUTOSAR? Adaptive AUTOSAR是一种新的汽车软件框架,旨在满足现代汽车行业中不断增长的技术需求。随着汽车变得越来越智能,对处理器的性能要求也在不断增长。 Adaptive AUTOSAR旨在通过提供高性能计算和通信机制以及灵活的软件配置来满足这些需求,为车…

利用特殊反序列化组件攻击原生反序列化入口

目录 前言 本文所述攻击的本质是将上述组件中的类拼接到反序列化利用利用链中&#xff0c;打的是Serilizable入口&#xff0c;而不是特殊反序列化入口 攻击原理 利用链分析 readObject()->任意类toString() HotSwappableTargetSource & XString BadAttributeValue…

静态路由与双线BFD热备份

✍ 路由具体是什么概念&#xff1f; ✍ 路由表和路由协议有什么关系&#xff1f; ✍ 电信联通双线如何做路由热备份&#xff1f; ---- 什么叫路由&#xff1f; ---- 路由器 - 网络设备 - 转发数据 - 要有一张地图 - 路由表 ---- 路由表 - 指明要到达某个目…

Apache Doris (四十五): Doris数据更新与删除 - Sequence 列

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. 基本原理

安装mmcv及GPU版本的pytorch及torchvision

一、先装GPU版本的pytorch和torchvision pip install torch1.9.1cu111 torchvision0.10.1cu111 torchaudio0.9.1 -f https://download.pytorch.org/whl/torch_stable.html注意&#xff1a;以上适用cuda11.1版本 如果想离线安装&#xff0c;就看这篇文章 二、安装mmcv 看这篇…

奥威BI数据可视化:让各层级管理快速获取信息

都说众口难调&#xff0c;企业各层级管理者想分析的维度不同&#xff0c;急需获取的数据信息不同&#xff0c;怎么才能第一时间满足企业各层级管理者的分析需求&#xff1f;奥威BI数据可视化软件的做法是利用多维动态自助分析实现随时随地按需分析。 多维动态分析功能支持用户…

MATLAB——神经网络参考代码

欢迎关注“电击小子程高兴的MATLAB小屋” %% I. 清空环境变量 clear all clc %% II. 训练集/测试集产生 %% % 1. 导入数据 load spectra_data.mat %% % 2. 随机产生训练集和测试集 temp randperm(size(NIR,1)); %打乱60个样本排序 % 训练集——50个样本 P_train NIR(…

Go实现CORS(跨域)

引言 很多时候&#xff0c;需要允许Web应用程序在不同域之间&#xff08;跨域&#xff09;实现共享资源。本文将简介跨域、CORS的概念&#xff0c;以及如何在Golang中如何实现CORS。 什么是跨域 如果两个 URL 的协议、端口&#xff08;如果有指定的话&#xff09;和主机都相…

【精华系列】跟着Token学习数据挖掘-1

Hello&#xff0c;大家好&#xff01;这里是Token的博客&#xff0c;欢迎您的到来 今天整理的笔记时数据挖掘方向的基础入门&#xff0c;了解数据分析使用的一些基础的Python库&#xff0c;为后面的数据处理做好准备 01-数据分析工具介绍 准备&#xff1a;Python的安装、平台搭…

【论文阅读】点云地图动态障碍物去除基准 A Dynamic Points Removal Benchmark in Point Cloud Maps

【论文阅读】点云地图动态障碍物去除基准 A Dynamic Points Removal Benchmark in Point Cloud Maps 终于一次轮到了讲自己的paper了 hahaha&#xff0c;写个中文的解读放在博客方便大家讨论 Title Picture Reference and prenotes paper: https://arxiv.org/abs/2307.07260 …

【python】文件和异常

文件和异常 实际开发中常常会遇到对数据进行持久化操作的场景&#xff0c;而实现数据持久化最直接简单的方式就是将数据保存到文件中。说到“文件”这个词&#xff0c;可能需要先科普一下关于文件系统的知识&#xff0c;但是这里我们并不浪费笔墨介绍这个概念&#xff0c;请大…

eltable el-tooltip__popper 换行、字体、颜色等调整

show-overflow-tooltip属性 element-ui表格 默认情况下若内容过多会折行显示&#xff0c;若需要单行显示可以使用show-overflow-tooltip属性&#xff0c;它接受一个Boolean&#xff0c;为true时多余的内容会在 hover 时以 tooltip 的形式显示出来。 默认情况 element-ui表格 sh…

为什么MySQL使用B+树索引,而不使用其他作为索引呢?

索引介绍 索引是一种用于快速查询和检索数据的数据结构&#xff0c;其本质可以看成一种排序号的数据结构。 索引的作用相当于书的目录。打个比方&#xff1a;在查字典的时候&#xff0c;如果没有目录&#xff0c;那我们就只能一页一页地去查&#xff0c;速度很慢。如果有目录…

从0到1,申请cos服务器并上传图片到cos文件服务器

目录 准备工作 Java代码编写 控制台打印 整理成工具类 编写接口 Postman测试 准备工作 1.进入网址腾讯云 产业智变云启未来 - 腾讯 (tencent.com) 2.搜索cos,点击立即使用&#xff0c;刚开始会免费赠送你 3.存储都是基于桶的&#xff0c;先创建桶&#xff0c;在桶里面创…

分类预测 | Matlab实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆神经网络的数据多输入分类预测

分类预测 | Matlab实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆神经网络的数据多输入分类预测 目录 分类预测 | Matlab实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆神经网络的数据多输入分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现WOA-BiLSTM鲸鱼算法…