驱动开发基础知识——设备树

2023-05-16

BSP开发工程师【原来BSP就是那些被指臃肿的文件啊

BSP的出生

Linux经过不断的发展,原先嵌入式系统的三层结构逐步演化成为一种四层结构。 这个新增加的中间层次位于操作系统和硬件之间,包含了系统中与硬件相关的大部分功能。通过特定的上层接口与操作系统进行交互,向操作系统提供底层的硬件信息;并根据操作系统的要求完成对硬件的直接操作。 由于引入了一个中间层次,屏蔽了底层硬件的多样性,操作系统不再直接面对具体的硬件环境。而是面向由这个中间层次所代表的、逻辑上的硬件环境。 因此,把这个中间层次叫做硬件抽象层 HAL(Hardware Abstraction Layer)。在目前的嵌入式领域中通常也把HAL叫做板级支持包 BSP(Board Support Package)。 尽管BSP中包含硬件相关的设备驱动程序,但是这些设备驱动程序通常不直接由BSP使用,而是在系统初始化过程中由BSP把它们与操作系统中通用的设备驱动程序关联起来, 并在随后的应用中由通用的设备驱动程序调用,实现对硬件设备的操作。 BSP的引入大大推动了嵌入式实时操作系统的通用化,从而为嵌入式系统的广泛应用提供了可能。

嵌入式系统初始化

不同的嵌入式系统初始化所涉及的内容各不相同,复杂程度也不尽相同。 但是初始化过程总是可以抽象为三个主要环节,按照自底向上、从硬件到软件的次序依次为:片级初始化、板级初始化和系统级初始化。

(1)片级初始化:主要完成CPU的初始化,包括设置CPU的核心寄存器和控制寄存器,CPU核心工作模式以及CPU的局部总线模式等。片级初始化把CPU从上电时的缺省状态逐步设置成为系统所要求的工作状态。这是一个纯硬件的初始化过程。
(2)板级初始化:完成CPU以外的其他硬件设备的初始化。除此之外,还要设置某些软件的数据结构和参数【就是那些操作设备时要用的函数】,为随后的系统级初始化和应用程序的运行建立硬件和软件环境。这是一个同时包含软硬件两部分在内的初始化过程。
(3)系统级初始化:这是一个以软件初始化为主的过程,主要进行操作系统初始化。BSP将控制转交给操作系统,由操作系统进行余下的初始化操作。包括加载和初始化与硬件无关的设备驱动程序,建立系统内存区,加载并初始化其他系统软件模块,比如网络系统、文件系统等;最后,操作系统创建应用程序环境并将控 制转交给应用程序的入口。

经过以上三个层次的操作,嵌入式系统运行所需要的硬件和软件环境已经进行了正确设置,从这里开始,高层的实时应用程序可以运行了。

BSP的具体工作内容

因为BSP具有操作系统相关性,因此,不同的操作系统会使用不同的文件完成类似的初始化操作。 BSP中硬件相关的设备驱动程序随操作系统的不同而具有比较大的差异,设计过程中应参照操作系统相应的接口规范。
BSP的开发不仅需要具备一定的硬件知识,例如CPU的控制、中断控制器的设置、内存控制器的设置及有关的总线规范等;同时还要求掌握操作系统所定义的BSP接口。 另外,在BSP的初始化部分通常会包含一些汇编代码,因此还要求对所使用的CPU汇编指令有所了解,例如X86的汇编和PowerPC的汇编指令等;对于某些复杂的BSP还要了解所使用的开发工具,例 如GNU、Diab Data等。
所以,不要妄图自己从头写。
在设计BSP时,首先选择与应用硬件环境最为相似的参考设计,例如Motorola的ADS系列评估板等。针对这些评估板,不同的操作系统都会提供完整 的BSP,这些BSP是学习和开发自己BSP的最佳参考。
针对具体应用的特定环境对参考设计的BSP进行必要的修改和增加,就可以完成简单的BSP设计。
下面以设计pSOS操作系统的BSP初始化过程为例。pSOS系统初始化的层次非常清晰,与初始化过程相对应的是以下三个文件:
1)init.s :对应于片级初始化;完成CPU的初始化操作,设置CPU的工作状态;
2)board.c :对应于板级初始化;继续CPU初始化,并设置CPU以外的硬件设备;
3)sysinit.c :对应于系统级初始化;完成操作系统的初始化,并启动应用程序。
以参考BSP为切入点,针对初始化过程的具体环节,在对应的文件中进行某些参数的修改及功能的增加就可以实现BSP的系统初始化功能。

嵌入式BSP层介绍

BSP升级换代

Linux内核中有很多BSP(板级支持包),不同的BSP会包含着不同的描述设备的代码(.c或.h-文件)。
随着芯片的发展,Linux内核中就包含着越来越多这些描述设备的代码,导致Linux内核代码会很臃肿。
现在的BSP开发应该就是驱动开发了。肯定也要会设备树啦。

  1. 特点电路板驱动适配。当前已有所有通用驱动功能,包括SOC内部和总线外接芯片驱动,这个特定电路板其实只是当前已有驱动的子集,需要的工作就是确定哪些驱动需要调用哪些不调用,以及调用顺序(等价于不写代码只配置下设备树)。再有就是对电路板所有功能确定的接口进行完整详细的测试
  2. 缺少某几个总线驱动。相比于1 这里缺少几个总线驱动,需要编写相应代码。这里编写的驱动和体系结构及处理器型号无关,和使用的总线特性相关【应该是指波特率那些】,一般有驱动框架,在各种电路板上也是通用的。开发这类驱动首先要查找一下在其他电路板或项目中是否用到过,如果有且编写的比较规范可能直接拿来用就行,如果编写不太规范或是适配的不同操作系统等则也是有很大参考意义的,改造一下基本就行。如果没有参考的源码程序,则只能参考类似驱动代码并结合器件数据手册开发了。
  3. 缺少SOC中的某个驱动。一般这类驱动只和相同系列的处理器有关,需要参考同类型驱动以及处理器数据手册才行。
  4. 一款全新处理器系列。这里使用的CPU内核(core)是已支持的,arch和最小系统是可用的,需要适配的是SOC外设。注意同一芯片厂商用的外设一般比较接近,再有就是一些外设可能用的是一些标准IP核,所以参考这些已有的代码开发会很有价值。比如已有TI的A8核处理器的bsp,现在要开发NXP新出的A8核处理器bsp,那就可以参考TI的A8核处理器的最小系统和NXP的A9处理器外设进行开发。
    这里设计的驱动是不针对特定板卡的,比如本处理器或本处理器系列最多可能支持10路串口,这10路串口都是同一个驱动,只是寄存器基地址及中断号等不同,这里的串口驱动就应该是针对这10路串口的。但到了具体的板卡,可能只用了其中3路,另外7路是不具备或不可用的。
  5. 同一厂商处理器不同CPU内核。同一厂商相近处理器系列外设驱动基本相同,如果只是更换一个相近的CPU内核,且此内核之前也适配过,则工作量可能较小,只需适配下最小系统就行。
  6. 全新的体系结构。比如第一次适配RISC-V的处理器,首先要做的可能并不是编写代码,而是寻找或适配一套编译工具链,有了编译工具才能开发代码。开发驱动前需要先适配arch和最小系统。适配全新的体系结构,需要极为熟悉计算机原理和对应体系结构特性,能力要求极高。

Bsp开发的几个层次

ARM Linux设备树之三(由设备树引发的BSP和驱动变更)【BSP的代码与设备树代码的一一对比】

引入设备树后的变化

在这里插入图片描述
在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息(一般在平台文件中写),然后使用 platform_device_register将驱动注册到内核中
不再使用时可以通过platform_device_unregister注销掉对应的platform设备

引入设备树以后我们就不用写设备资源文件了,只需要在设备树中添加一个节点
platform_device代码所包含的resource现在都在设备树的.dts中设备节点的reg、interrupts属性里,会由内核自动读取。如下

gpioled {
		#address-cells = <1>; 
		#size-cells = <1>; 
		compatible = "atkalpha-gpioled"; 
		pinctrl-names = "default"; 
		pinctrl-0 = <&pinctrl_gpio_leds>; 
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; 
		status = "okay";
	};

我们添加了一个gpioled 节点,我们要注意它的compatible 属性"atkalpha-gpioled",在驱动中我们要设置匹配表

static const struct of_device_id led_of_match[] = {
    { .compatible = "atkalpha-gpioled"},
    { /**/ }
};

我们将匹配表设置只有一项设备,就是我们在设备树中定义的节点
然后定义platform_driver,将匹配表初始化到platform_driver

static struct platform_driver led_driver = {
    .driver = {
        .name = "im6ul-led",
        .of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};

这样当驱动加载到内核后,就会进行匹配,匹配成功的话才会执行probe函数,在probe函数中做设备、驱动的初始化。
Linux驱动开发(十):设备树下的platform平台设备驱动

设备树基础知识

Linux设备树相关操作
整理了一份Linux设备树基础知识!

linux官方教程

设备树的文档资料十分详尽,基本上看着文档就可以进行配置,设备树文档对每一个需要配置的地方都有详细的解释以及示例
Documentation/devicetree/usage-model.txt

dts和bingings

在这里插入图片描述
文件分为dts和bingings
bindings包含设备树用到的所有宏定义,都放到bindings目录下
dts分为dts和dtsi文件,dts是板级文件,dtsi是“平台文件”,另外还有使用文档在Documentation/devicetree
这里的平台文件是指支持的不止一块板子而是一类板子
.dts描述板级信息(有哪些IIC设备、SPI设备等)
.dtsi描述SOC级信息
DTS是设备树源码文件
DTB是将DTS编译后得到的二进制文件
将.dts编译为.dtb需要DTC文件 工具源码在scripts/dtc目录下
在源码文件夹中执行make dtbs就可以进行设备树的编译
4412开发板的设备树文件:arch/arm/boot/dts/exynos4412-itop-elite.dts
总之我们系统使用的设备树文件都存在目录/boot下
在这里插入图片描述

设备树dts的基本构造

随便截取的例子

/{
	compatible = "nvidia,harmony", "nvidia,tegra20";
	#address-cells = <1>;
	#size-cells = <1>;
	interrupt-parent = <&intc>;

	chosen { };
	aliases { };

	memory {  【内存也是一个节点
		device_type = "memory";
		reg = <0x00000000 0x40000000>;
	};

	soc {  【SOC 平台级别的设备信息【大概吧
		compatible = "nvidia,tegra20-soc", "simple-bus";【在内核里可以匹配上的"厂商,驱动名" 写在前面的会先被匹配
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		intc: interrupt-controller@50041000 {  【简称intc
			compatible = "nvidia,tegra20-gic";
			interrupt-controller;
			#interrupt-cells = <1>;
			reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >;
		};

		serial@70006300 {
			compatible = "nvidia,tegra20-uart";
			reg = <0x70006300 0x100>;
			interrupts = <122>;
		};

		i2s1: i2s@70002800 {
			compatible = "nvidia,tegra20-i2s";
			reg = <0x70002800 0x100>;
			interrupts = <77>;
			codec = <&wm8903>;
		};

		i2c@7000c000 { 【设备节点的名字@节点的寄存器地址
			compatible = "nvidia,tegra20-i2c";
			#address-cells = <1>;【指reg的地址信息的长度 单位是 32
			#size-cells = <0>;【诶,怎么会是0
			reg = <0x7000c000 0x100>;
			interrupts = <70>;
【i2c的子节点有codec
			wm8903: codec@1a {
				compatible = "wlf,wm8903";
				reg = <0x1a>;
				interrupts = <347>;
			};
		};
	};

	sound {
		compatible = "nvidia,harmony-sound";
		i2s-controller = <&i2s1>;
		i2s-codec = <&wm8903>;
	};
};

节点和根节点

{}框起来的,称为节点
/{}在dts的最开头,称为根节点

节点的标准结构是xxx@yyy{ … }
xxx是节点的名字,yyy则不是必须的,其值为节点的地址(寄存器地址或其他地址)
label:node-name@unit-address
引入label的目的是为了方便访问节点,可以直接通过&label来访问这个节点
节点可以包含属性和子节点 【也就是说,名称前面加了&的都是已经在设备树里定义过了的节点】
在这里插入图片描述

属性

设备树学习的主要部分:设备树文件中的属性的配置,驱动文件中调用设备树中的属性

属性赋值 所赋的val可以是各种类型的数据

在这里插入图片描述

compatible

类似设备名称,兼容性属性,字符串列表,用于将设备和驱动绑定起来
格式:“manufacturer,model”
其中manufacturer表示厂商,model一般是模块对应的驱动名字
一般的驱动程序文件都会有一个OF匹配表,此OF匹配表保存着一些compatible值,如果设备节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动
在这里插入图片描述

status

字符串,设备的状态

  • okey:可操作
  • disable:当前不可操作,但是在未来可以变为可操作,如热插拔设备插入后
  • fail:不可操作,检测到了一系列错误,也不大可能变得可操作

#address-cells和#size-cells

都是无符号32位整型,可以用在任何拥有子节点的设备中
用于描述子节点的地址信息
#address-cells决定子节点reg属性中地址信息所占用的字长(32位)
#size-cells决定了子节点reg属性中长度信息所占用的字长(32位)
一般这两个都是1
这两个属性表明了子节点应该如何编写reg属性值。一般reg属性都是和地址相关的内容

reg

reg=<address1 length1 address2 length2 address3 length3……>
用于描述设备地址空间资源信息
一般都是某个外设的寄存器地址范围信息

在这里插入图片描述

ranges

ranges是一个地址映射/转移表,可以为空,啥事没有。
或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵
这个表的每个项目由字地址、父地址和地址空间长度三部分组成
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells确定所占用字长
parent-bus-address:父总线空间的物理地址,同样由父节点的#address-cells确定所占用字长
length:子地址空间的长度,由父节点的#size-cells确定所占用字长

【可是哪些节点需要什么属性啊?完全不懂。而且什么时候要 在根目录外 再次引用某个外设啊?

设备树在系统中的体现

/proc/device-tree/ 目录下是根据节点名字创建的不同文件夹
就是将设备树分级存储
每个文件夹就是一个节点,里面包含这个节点的属性以及它所包含的子节点

驱动文件要调用设备树的信息,需要一系列以of开头的操作函数

of是open firmware的缩写,意为开放固件,是定义计算机固件系统接口的标准
ARM的设备树操作就遵守open firmware标准
OF操作函数是编写驱动获取设备树信息调用的函数
定义在include/linux/of.h文件中

查找节点的OF函数

1、of_find_node_by_name
通过子节点名字查找子节点
2、of_find_node_by_type
通过子节点类型查找子节点,device_type 【这个type好像被淘汰了,只有CPU和memory在用【大概】
3、of_find_compatible_node
根据device_type和compatible查找子节点,device_type可以设置为NULL
4、of_find_matching_node_and_match
通过of_device_id匹配表来查找指定的节点
5、of_find_node_by_path
通过路径来查找指定节点

查找父/子节点的OF函数

1、of_get_parent
父节点
2、of_get_next_child
迭代地查找子节点

提取属性值

节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要, Linux内核中使用结构体 property表示属性
下面的一系列函数大部分是读取设备树中保存的数值,格式是of_property_read_##数据类型

1 、of_find_property
用于查找指定的属性
2、of_property_count_elems_of_size
函数用于获取属性中元素的数量,比如 reg属性值是一个数组,那么使用此函数可以获取到这个数组的大小
3、of_property_read_u32_index
函数用于从属性中获取指定标号的 u32类型数据值 (无符号 32位 ),比如某个属性有多个 u32类型的值,那么就可以使用此函数来获取指定标号的数据值
4、读取数组数据的函数
of_property_read_u8_array
of_property_read_u16_array
of_property_read_u32_array
of_property_read_u64_array
这 4个函数分别是将属性中 u8、 u16、 u32和 u64类型的数组数据全部读取出来,比如大多数的 reg属性都是数组数据,可以使用这 4个函数一次读取出 reg属性中的所有数据。
5 、读取整型值属性的函数
of_property_read_u8
of_property_read_u16
of_property_read_u32
of_property_read_u64
6、of_property_read_string
用于读取属性中字符串值
7、of_n_addr_cells
函数用于获取 #address-cells属性值
8、 of_n_size_cells
函数用于获取 #size-cells属性值

其他常用的OF函数

1、of_device_is_compatible
函数用于查看节点的 compatible属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性【就是驱动匹配的时候查吧】
2、of_get_address
函数用于获取地址相关属性,主要是“ reg”或者 assigned-addresses”属性值
3、of_translate_address
函数负责将从设备树读取到的地址转换为物理地址
4、of_address_to_resource
函数是从设备树中提取资源值,本质上就是取reg属性值然后将其转换为resource结构体类型
IIC、 SPI、 GPIO等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间, Linux 内核使用 resource结构体来描述一段内存空间 , resource结构体定义在文件 include/linux/ioport.h中
对于 32位的 SOC来说, resource_size_t是 u32类型的。其中 start表示开始地址, end表示结束地址, name是这个资源的名字, flags是资源标志位,一般表示资源类型,可选的资源标志也定义在文件 include/linux/ioport.h中
一般最常见的资源标志是IORESOURCE_MEM、IORESOURCE_REG和IORESOURCE_IRQ等
5、of_iomap
函数用于直接内存映射,以前我们会通过 ioremap函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap函数来获取物理地址所对应的虚拟地址,不需要使用 ioremap函数了。
当然ioremap函数也是可以使用的,只是在采用了设备树后,大部分的驱动都使用了of_iomap函数

Linux设备树详解【有设备树的编写和获取资源的函数调用】

SPI驱动

本小节摘抄自下文教程
Linux驱动开发(十九):SPI驱动
**
进行Linux驱动开发
我们使用的模块是正点原子开发板上板载的icm20608六轴传感器模块
可以读到的数据为温度、3轴加速度、3轴角速度数据
设备驱动开发的流程分为 修改设备树、编写驱动程序、编写应用程序 三个部分
**

编写应用程序

应用程序就是从模块读取数据并打印

filename = argv[1];///指定设备路径 dev/SPI设备名 运行代码
fd = open(filename, O_RDWR);///这俩需要先挂载设备和驱动 匹配驱动


ret = read(fd, databuf, sizeof(databuf)); ///从内核取出设备的缓冲区里的内容
		if(ret == 0) { 			/* 数据读取成功 */
			gyro_x_adc = databuf[0];
			gyro_y_adc = databuf[1];
			gyro_z_adc = databuf[2];
			accel_x_adc = databuf[3];
			accel_y_adc = databuf[4];
			accel_z_adc = databuf[5];
			temp_adc = databuf[6];

close(fd);	/* 关闭文件 */

驱动代码分析

  • 在icm20608_init函数中我们调用了spi_register_driver【傀儡】来注册了一个SPI驱动,传入了一个icm20608_driver 参数【实权】
  • icm20608_driver 中定义了probe和remove函数以及设备和驱动的匹配规则,我们可以使用设备树和id_tables两种匹配方式
  • 在probe函数中主要完成字符设备的注册、GPIO的获取以及初始化以及SPI设备的初始化【从设备文件那边获取资源】
  • 设备驱动的实现关键就是提供给应用层接口,icm20608_ops就是该设备驱动的操作函数,我们实现了icm20608_open、icm20608_read和icm20608_release函数【功能:开,读,关
  • icm20608_read函数中我们就实现了从模块读取温度、角速度、加速度数据,我们可以在应用层调用read函数来读取

开,读,关 的实权函数

static int icm20608_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &icm20608dev;
    return 0;
}

static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    signed int data[7];
    long err = 0;
    struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
    ///获取设备里的数据

    icm20608_readdata(dev);///这个函数是在这个.c文件里层层封装的【而且这篇文章把很多寄存器地址写在了驱动文件里,不知道这样的操作规不规范。】,具体流程如下
    /*
SPI数据传输的步骤
1、申请并初始化 spi_transfer,设置 spi_transfer的 tx_buf成员变量, tx_buf为要发送的数据。然后设置 rx_buf成员变量, rx_buf保存着接收到的数据。最后设置 len成员变量,也就是要进行数据通信的长度。
2、使用 spi_message_init函数初始化 spi_message【不知道这个函数的内容】
3、使用 spi_message_add_tail函数将前面设置好的 spi_transfer添加到 spi_message队列中。
4、使用 spi_sync函数完成 SPI数据同步传输。
*/
    data[0] = dev->gyro_x_adc;///按自己的格式存好
    data[1] = dev->gyro_y_adc;
    data[2] = dev->gyro_z_adc;
    data[3] = dev->accel_x_adc;
    data[4] = dev->accel_y_adc;
    data[5] = dev->accel_z_adc;
    data[6] = dev->temp_adc;

    err = copy_to_user(buf, data, sizeof(data));///发到用户层,给应用程序读取使用
    return 0;
}

static int icm20608_release(struct inode *inode, struct file *filp)
{///卸载驱动、关闭class的代码在icm20608_remove函数里
    return 0;
}

结构体

struct icm20608_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int major;
    void *private_data;
    int cs_gpio;//SPI CS Pin
    signed int gyro_x_adc;
    signed int gyro_y_adc;
    signed int gyro_z_adc;
    signed int accel_x_adc;
    signed int accel_y_adc;
    signed int accel_z_adc;
    signed int temp_adc;
};

static struct icm20608_dev icm20608dev;

icm20608_driver 用于匹配的平台驱动句柄

static const struct spi_device_id icm20608_id[] = {
    {"alientek,icm20608", 0},
    {}
};

static const struct of_device_id icm20608_of_match[] = {
    {.compatible = "alientek,icm20608" },
    {}
};

static struct spi_driver icm20608_driver = {
    .probe = icm20608_peobe,
    .remove = icm20608_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "icm20608",
        .of_match_table = icm20608_of_match,
    },
    .id_table = icm20608_id,
};

probe 和 remove

static int icm20608_peobe(struct spi_device *spi)
{///太长了,去原文看
    /*1.get device id*/
    /*2.register device*/
    /*3.create class*/
    /*4.create device*/
    /*5.get cs from dts*/ 
    /*6.get gpio property from dts*/
    /*7.set gpio output and set high*/
    /*8.init spi_device*/
    /*9.init ICM20608 inside register*/
    return 0;
}

static int icm20608_remove(struct spi_device *spi)
{
    /*delete device*/
    cdev_del(&icm20608dev.cdev);
    unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);

    /*unregister class and device*/
    device_destroy(icm20608dev.class, icm20608dev.devid);
    class_destroy(icm20608dev.class);
    return 0;
}

修改设备树

pinctrl_ecspi3: ecspi3grp {
           fsl,pins = <
                   MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO        0x100b1  /* MISO*/
                   MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI        0x100b1  /* MOSI*/
                   MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK      0x100b1  /* CLK*/
                   MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20       0x100b0  /* CS*/
           >;
   		};

该传感器连接在SPI3上

&ecspi3 {
        fsl,spi-num-chipselects = <1>;
        cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_ecspi3>;
        status = "okay";

       spidev: icm20608@0 {
       compatible = "alientek,icm20608";  ///查找主机驱动
         spi-max-frequency = <8000000>;
         reg = <0>;
    };
};

fsl,spi-num-chipselects 属性为1,表示只有一个设备
cs-gpios 表示片选信号为gpio1 的某个引脚【本文没有配置cs-gpios而是用了一个自己定义的cs-gpio(不带s),因为我们要自己控制片选引脚,如果使用cs-gpios属性点额话SPI主机驱动就会控制片选引脚】
pinctrl-names就是SPI设备使用的IO名字
pinctrl-0 所使用的IO对应的pinctrl节点 【引用了上面定义的pinctrl 【就是这个SPI用的四个引脚】】
status 设置为okay
icm20608@0 设备为icm20608,0表示icm20608接到了ECSPI的通道0上
compatible SPI设备用于匹配驱动的标识
spi-max-frequency 设置SPI控制器的最高频率,要根据所使用的SPI设备来设置,icm20608的SPI口最大支持8M
reg 表示使用ECSPI的通道0

扩展阅读

C语言的宏的#的用法

两个## 分割出变量
#define A1(name, type) type name_##type##type
#define A2(name, type) type name##
##type##_type

A1(a1, int); /* 等价于: int name_int_type; /
A2(a1, int); /
等价于: int a1_int_type; */

单独一个#,则表示对这个变量替换后,再加双引号引起来。比如
#define __stringify_1(x) #x
那么
__stringify_1(linux) <==> “linux”
宏定义中的#,##

4412开发板学习之Linux驱动开发(九):中断控制及按键中断实现

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

驱动开发基础知识——设备树 的相关文章

  • 电路设计_阅读英文数据手册并没有想象的那么难

    说明书的正文究竟包含哪几部分 xff0c 应根据不同产品的具体情况来确定 xff08 1 xff09 Release Date 检查手册发布的日期 xff0c 是预备版还是修正版 xff1b xff08 2 xff09 General De
  • 用C语言实现卷积操作(包含单通道单卷积和多通道多卷积以及维度的转换)

    用C语言实现卷积 单通单卷积多通道多卷积维度转换 xff08 多维转一维 xff09 完整代码实现 xff1a 单通单卷积 卷积其实就是个点成相加的过程 原理图如下 xff1a 关键代码实现 xff1a span class token c
  • 基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息

    车牌识别项目之图像预处理一 xff1a C语言读取bmp图像信息 一 什么是bmp文件二 BMP格式结构1 文件信息头2 图像描述信息块3 BMP调色板4 BMP图像数据区 像素存储 像素数据 位图像素格式 三 原理实现 xff1a 1 打
  • 基于深度学习的车牌识别项目的APP部分之图像预处理(二):C语言实现bmp的二值化处理

    基于深度学习的车牌识别项目的APP部分之图像预处理 xff08 二 xff09 xff1a C语言实现bmp的二值化处理 一 二值化概念二 代码实现三 运行结果 xff1a 1 图像是8位深度的运行结果 xff1a 2 图像是16位深度的运
  • rviz和tf树报错修改

    跟着这个博主进行多机器人仿真 xff0c 一直出错 xff0c 有点崩溃了 ROS仿真笔记之 基于gazebo的ROS多机器人仿真 gwpscut的博客 CSDN博客 TF REPEATED DATA warnings ROS Answer
  • 港科大GVINS编译与运行教程

    GVINS GVINS是香港科技大学沈劭劼团队开源的基于因子图优化的GNSS 视觉 惯导紧耦合多传感器组合导航软件 GVINS是一个基于非线性优化的系统 xff0c 它将 GNSS 原始测量与视觉和惯性信息紧密融合 xff0c 以实现实时和
  • 硬件结构——(6) 软中断

    1 中断是什么 xff1f 在计算机中 xff0c 中断是 系统用来响应硬件设备请求 的一种机制 操作系统收到 硬件的中断请求后 xff0c 会打断正在执行的进程 xff0c 然后调用内核中的 中断处理程序 来响应请求 中断是 种 异步的事
  • C++将类序列化和反序列化到共享内存

    这里用模板泛化一下 template lt typename T gt int saveToShm const T amp obj key t key 61 gen shm key int shmId 61 shmget key sizeo
  • C++使用rapidjson进行类的序列化与反序列化

    目录 序列化 amp 反序列化单个对象序列化反序列化使用示例 序列化 amp 反序列化对象列表 C 43 43 中可以使用第三方库来实现将类序列化成JSON文件 xff0c 以及读取JSON文件内容反序列化为类对象 这里使用rapidJso
  • c++用vector实现定长队列

    目录 queue实现vector实现 我们可以用queue或vector实现定长队列 xff0c 但是如果我们有遍历定长队列的需求的话 xff0c 使用queue不是一个好的选择 xff0c 因为queue本身不支持直接访问元素 queue
  • c++判断文件是否存在

    可以使用C 43 43 的文件流来判断文件是否存在 以下是一个简单的函数 xff0c 可以通过文件名判断文件是否存在 xff1a span class token macro property span class token direct
  • 相机内外参数的意义

    1 内参数 xff1a 由于相机坐标系使用的是毫米制的单位 xff0c 而图像平面使用的像素为单位 内参数的作用就是在这两个坐标系之间进行线性的变化 相机光轴中心Z轴方向与像平面交点称为投影中心 xff0c 其坐标为 xff0c 其单位为像
  • C++实现读写分离的双缓冲buffer

    目录 1 双缓冲区 读写分离2 后台线程定时更新数据3 类设计完整代码cache cppcache hmain cppmakefile 读写分离的双缓冲buffer有以下好处 xff1a 提高了并发读写的效率 xff1a 在多线程环境下 x
  • 返回引用与返回值与返回std::move(obj)

    返回引用与返回值相比 返回引用与返回值相比有以下几个好处 xff1a 减少内存开销 xff1a 返回值需要在函数内部创建一个临时对象 xff0c 然后将该对象的副本返回给调用者 这个过程需要分配内存 复制数据等操作 xff0c 会增加内存开
  • error: non-member function ‘XXX::IsValid(const T&)’ cannot have cv-qualifier

    这个错误提示表明你定义的函数 IsValid 是一个非成员函数 xff0c 并且带有 const 限定符 在 C 43 43 中 xff0c 非成员函数不能带有 const 限定符 xff0c 因为它们没有隐式的 this 指针 xff0c
  • c++使用regex报错regex_error

    原本写了个同时识别IPv4和IPv6地址的C 43 43 函数 xff1a span class token macro property span class token directive hash span span class to
  • c++实现日志类(写入logfile)

    span class token macro property span class token directive hash span span class token directive keyword include span spa
  • c++中以类对象作为key用于unordered_map、map,以及std::tie技巧使用

    我有一个类 span class token keyword class span span class token class name UserRegion span span class token punctuation span
  • std::set_difference用法

    std set difference 是 C 43 43 STL 中的一个算法 xff0c 用于计算两个有序范围之间的差集 xff0c 并将结果存储到另一个有序范围中 std set difference 的函数签名如下 xff1a spa

随机推荐

  • __builtin_xxx指令学习【1】__builtin_expect

    builtin expect是GCC编译器提供的一个内置函数 xff0c 用于告诉编译器一个分支的执行概率 xff0c 以便编译器在生成机器码时进行优化 它的语法如下 xff1a span class token function buil
  • __builtin_xxx指令学习【2】__builtin_prefetch

    builtin prefetch是GCC编译器提供的一个内置函数 xff0c 用于预取数据到CPU的缓存中 xff0c 以便提高程序的执行效率 它的语法如下 xff1a builtin prefetch const void addr in
  • __builtin_xxx指令学习【3】__builtin_popcount & __builtin_popcountll

    builtin popcount是GCC和Clang编译器提供的一个内置函数 xff0c 用于计算一个整数中二进制位为1的个数 该函数的使用背景是在一些位运算和计算机视觉等领域中 xff0c 需要对二进制数据进行处理和分析 xff0c 而二
  • 当arduino遇到树莓派(usb串口)

    arduino与树莓派cm4通过usb串口连接 xff1a 问题描述 前两天尝试了很久arduino和树莓派连接 终于找到了连接的方法 xff01 可恶 这是在简书找到的过程 主要是先在树莓派端导serial包 pip install se
  • Linux的rc.local自启动服务

    Linux的rc local自启动服务 网址 xff1a http blog csdn net 21aspnet article details 6826659 Linux有自己一套完整的启动体系 xff0c 抓住了linux启动的脉络 x
  • __builtin_xxx指令学习【4】__builtin_clz&__builtin_ctz & __builtin_clzll & __builtin_ctzll

    builtin clz是GCC和Clang编译器提供的一个内置函数 xff0c 用于计算一个整数的二进制表示中 xff0c 从最高位开始连续的0的个数 该函数的使用背景是在一些位运算和计算机视觉等领域中 xff0c 需要对二进制数据进行处理
  • __builtin_xxx指令学习【5】__builtin_bswap16/32/64

    builtin bswap16 32 64是GCC和Clang编译器提供的内置函数 xff0c 用于交换一个整数的字节顺序 其中 xff0c builtin bswap16用于交换一个16位整数的字节顺序 xff0c builtin bsw
  • 【Python】@cache装饰器使用 (依赖cachetools)

    介绍 cachetools 是一个 Python 缓存库 xff0c 可以用于缓存函数的计算结果 xff0c 以提高程序的性能和响应速度 使用 cachetools 可以避免重复计算 减少网络请求 降低数据库负载等问题 xff0c 从而提高
  • Python常见装饰器使用(实用向)

    目录 1 96 64 staticmethod 96 2 96 64 classmethod 96 3 96 64 classmethod 96 与 96 64 staticmethod 96 比较4 96 64 property 96 5
  • 【C++】std::transform、std::back_inserter使用

    std transform函数可以用于对一个序列中的每个元素进行转换 xff0c 并将结果存储到另一个序列中 们需要传递三个参数 xff1a 源序列的起始迭代器 源序列的结束迭代器和目标序列的起始迭代器 此外 xff0c 我们还需要传递一个
  • std::inner_product与std::accumulate初始值设置踩坑

    std inner product函数可以用于计算两个序列的内积 在这个函数中 xff0c 我们需要传递四个参数 xff1a 两个源序列的起始迭代器 一个初始值和一个二元函数对象 std inner product函数将对两个源序列中的每个
  • 树莓派无法连接vnc,树莓派 vnc viewer 显示 cannot currently show the desktop 的解决方法

    最近新买了一块树莓派4B xff0c 正常启动ssh xff0c vnc服务 但vnc viewer上却显示黑屏 cannot currently show the desktop 在网上查找了许多解决方法 xff0c 都不能用 但后来调整
  • 'gbk' codec can't encode character '\xa0' XXX解决方法

    在运行py文件时 xff0c 出现 UnicodeEncodeError gbk codec can t encode character xa0 in position XXX 让人摸不着头脑 注意这三个方面 xff0c 一般都能解决 1
  • 基于stm32cubeIDE学习can通信

    can通信 TO DO 1 CAN FilterTypeDef 中的SlaveStartFilterBank 为从过滤器配置 xff0c 用来选择从过滤器的寄存器号 当选择双CAN模式的时候 xff0c 这个参数要跟CAN2的filtern
  • jetson nano 的GPIO使用

    依次输入以下指令 python span class token comment 进入python编程界面 span span class token operator gt gt span span class token operato
  • 多态的基础语法

    1 向上转型和向下转型的概念 向上转型 自动转型 xff1a 子类 gt 父类 当我们是用父类引用指向子类对象的时候 xff0c 系统会自动向上转型 向下转型 强制转型 xff1a 父类 gt 子类 当父类引用指向子类对象时 xff0c 父
  • qt画个灰度直方图

    步骤 导入图表库在ui界面新建一个wight窗口并提升为那个图表库的类型写函数 输入参数是 xff08 图表 xff0c 待处理图片 xff09 图表的横轴是像素值 从0 255 纵轴是此灰度的像素点数量或者占比所以要挨个判断图像里的每一个
  • 找特征点的算法 SIFT和SURF算法

    SIFT 特征点算法 SIFT算法是记录每个极值点附近的梯度方向 xff0c 并以一个主要梯度方向为基准来兼顾特征旋转的情况 它通过不同模糊程度的灰度图相减找极值 xff0c 筛选确定出真实关键点后按梯度和权重求出关键点的主方向 xff0c
  • nano的CAN通信

    我买的是微雪的树莓派用的SPI转CAN 现在用在jetson nano上 我看了一下俩模块的芯片用的都是一样的 xff0c 引脚也都对得上 xff0c 所以我就打算拿这个模块给nano做can通信 具备CAN功能 xff0c 使用SPI接口
  • 驱动开发基础知识——设备树

    BSP开发工程师 原来BSP就是那些被指臃肿的文件啊 BSP的出生 Linux经过不断的发展 原先嵌入式系统的三层结构逐步演化成为一种四层结构 这个新增加的中间层次位于操作系统和硬件之间 包含了系统中与硬件相关的大部分功能 通过特定的上层接