12.1SPI驱动框架

SPI硬件基础

总线拓扑结构

在这里插入图片描述

引脚含义

DO(MOSI):Master Output, Slave Input, SPI主控用来发出数据,SPI从设备用来接收数据
DI(MISO) :Master Input, Slave Output, SPI主控用来发出数据,SPI从设备用来接收数据
SCK: Serial Clock,时钟
CS:Chip Select,芯片选择引脚

SPI模式

在SPI协议中,有两个值来确定SPI的模式,分别是:
CPOL(时钟极性)表示SPI CLK的初始电平,0为电平,1为高电平
CPHA (时钟相位)即第一个还是第二个时钟沿采样数据,0为第一个时钟沿,1为第二个时钟沿

CPOL	CPHA	模式	含义
0		0		0		SPI CLK初始电平为低电平,在第一个时钟沿采样数据
0		1		1		SPI CLK初始电平为低电平,在第二个时钟沿采样数据
1		0		2		SPI CLK初始电平为高电平,在第一个时钟沿采样数据
1		1		3		SPI CLK初始电平为高电平,在第二个时钟沿采样数据

此外部分SPI控制器还可以配置数据流顺序,分别是MSB(高位在前)、LSB(低位在前)

SPI 驱动框架组成

SPI驱动框架包括以下几个部分:

  1. SPI 核心;管理 SPI 控制器驱动驱动、 SPI 设备驱动、 SPI 设备,此部分由linux提供
  2. SPI 控制器驱动:用于驱 SOC 上的SPI控制器,此部分由主控芯片厂家提供
  3. SPI 设备:用于描述 SPI 设备信息和此设备对SPI总线的配置,一般在设备树中编写。
  4. SPI 设备驱动:用于驱动 SPI 总线上的设备,一移植厂家驱动或自己编写
  5. spidev :一个通用的SPI设备驱动,提供一种用户空间访问SPI总线的功能(需要设备树支持)

SPI 控制器驱动

对象 struct spi_controller 表示一个 SPI 控制器,其核心成员如下:

	//表示继承于struct device,其中的of_node需要指定,否则无法解析设备树
	struct device dev;
	//总线编号
	s16 bus_num;
	//片选数量,从设备的片选号不能大于这个数量
	u16 num_chipselect;
	//控制器所支持的模式
	u32 mode_bits;
	//最小传输速率
	u32 min_speed_hz;
	//最大传输速率
	u32 max_speed_hz;
	//标志,表示控制器的一些特性
	u16 flags;
	//指示此控制器是否时从设备
	bool slave;
	//配置SPI控制器
	int (*setup)(struct spi_device *spi);
	//设置CS的时序
	void (*set_cs_timing)(struct spi_device *spi, u8 setup_clk_cycles, 
		u8 hold_clk_cycles, u8 inactive_clk_cycles);
	//SPI数据包传输
	int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
	int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer);
	//维护 queue 的自旋锁
	spinlock_t queue_lock;
	//管理 SPI 控制器需要传输的 spi_message,一个 spi_message 表示一系列需要传输的数据
	struct list_head queue;
	//SPI 控制器正在传输的 spi_message
	struct spi_message *cur_msg;
	//片选引脚编号列表
	int *cs_gpios;
	//片选引脚描述符列表
	struct gpio_desc **cs_gpiods;
	//是否使用gpio描述符接口控制控制片选引脚
	bool use_gpio_descriptors;

注册、注销SPI控制器驱动

SPI 控制器驱动的核心就是完成对 struct spi_controller 的分配和初始化,然后将其添加到系统中,如下是分配 SPI 控制器并向系统添加和删除 SPI 控制器驱动的函数:

	//分配SPI控制器
	struct spi_controller *spi_alloc_master(struct device *host, unsigned int size)
	//释放SPI控制器
	void spi_controller_put(struct spi_controller *ctlr)
	#define spi_master_put(_ctlr) spi_controller_put(_ctlr)
	//注册SPI控制器
	int spi_register_master(struct spi_controller *ctlr)
	int spi_register_controller(struct spi_controller *ctlr)
	int devm_spi_register_master(struct device *dev, struct spi_controller *ctlr);
	int devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr);
	//注销SPI控制器
	void spi_unregister_master(struct spi_controller *ctlr)

SPI 设备和驱动

SPI 设备用 struct spi_device 表示,它用于描述一个 SPI 设备, SPI 设备的驱动用 struct spi_driver 表示,它用于描述 SPI 设备的驱动。
struct spi_device 的核心成员如下:

	//继承的device对象
	struct device dev;
	//所属控制器
	struct spi_controller *controller;
	//最大总线频率
	u32 max_speed_hz;
	//片选号,与控制器的片选列表对应
	u8 chip_select;
	//总线模式
	u32 mode;
	//片选引脚,不使用时为-ENOENT
	int cs_gpio;
	//驱动程序的名称或别名
	char modalias[SPI_NAME_SIZE];
	//强制匹配字符串
	const char *driver_override;

struct spi_driver 的核心成员如下:

	//ID匹配表
	const struct spi_device_id *id_table;
	//设备和驱动匹配成功执行
	int (*probe)(struct spi_device *spi);
	//设备或驱动卸载执行
	int (*remove)(struct spi_device *spi);
	//继承的device_driver
	struct device_driver driver;

注册/注销 SPI 设备和驱动

可以使用如下函数注册/注销 SPI 设备:

	//注册PSI设备
	int spi_add_device(struct spi_device *spi);
	struct spi_device *spi_new_device(struct spi_controller *, struct spi_board_info *);
	//注销SPI设备
	void spi_unregister_device(struct spi_device *spi);

可以使用如下函数注册/注销 SPI 设备驱动:

	//注册SPI设备驱动
	int spi_register_driver(struct spi_driver *sdrv)
	//注销SPI设备驱动
	void spi_unregister_driver(struct spi_driver *sdrv)

SPI 设备和驱动匹配过程

SPI 注销基于总线设备驱动模型框架,在向 SPI 添加设备或驱动时会执行 SPI 总线的设备去匹配函数,其函数内容如下:

	static int spi_match_device(struct device *dev, struct device_driver *drv)
	{
		const struct spi_device	*spi = to_spi_device(dev);
		const struct spi_driver	*sdrv = to_spi_driver(drv);

		//采用driver_override进行强制匹配
		if (spi->driver_override)
			return strcmp(spi->driver_override, drv->name) == 0;

		//设备树匹配
		if (of_driver_match_device(dev, drv))
			return 1;

		//ACPI匹配
		if (acpi_driver_match_device(dev, drv))
			return 1;

		//id_table匹配
		if (sdrv->id_table)
			return !!spi_match_id(sdrv->id_table, spi);

		//采用驱动程序的名称匹配
		return strcmp(spi->modalias, drv->name) == 0;
	}

SPI 控制器注册过程

构造并初始化一个 struct spi_controller 对象
	调用 spi_register_controller 注册 SPI 控制器
		使用 spi_controller_check_ops 检查必要参数是否配置正确
		通过 idr_alloc 将 SPI 控制器放入到一个 idr 对象中(如果 bus_num 无效会尝试从设备树中获取)
		初始化 struct spi_controller 各种资源
		从设备树中解析片选引脚,这里根据配置选择gpio编号模式和gpio描述符模式,若选择个屁哦编号模式在调用 spi_register_controller 函数前对相应引脚进行请求操作
		使用 device_add 将 struct spi_controller 的 dev 添加到内核
		若未提供 transfer 函数,但是提供了 transfer_one 函数或者 transfer_one_message 函数则调用 spi_controller_initialize_queue 函数使能队列参数模式
			将 struct spi_controller 对象的 transfer 设置为 spi_queued_transfer
			初始化 SPI 队列模式相关资源
		将 struct spi_controller 放入 spi_controller_list 链表中
		调用 of_register_spi_devices 解析 SPI 设备树
			遍历 spi 控制器的设备树子节点,并调用 of_register_spi_device 完成一个子节点的解析
				使用 spi_alloc_device 分配一个 SPI 设备
				使用 of_modalias_node 读取设备树的 compatible 来配置 SPI 设备的 modalias 参数
				使用 of_spi_parse_dt 解析设备树,并根据设备树配置 SPI 设备
				使用 spi_add_device 将设备注册到 SPI 总线
					检查片选是否合法
					调用 spi_dev_set_name ,利用总线名称和片选编号配置设备名称
					调用 bus_for_each_dev 遍历 SPI 设备,检查有设备是否存再片选相同问题(通过回调检查)
					绑定片选引脚
					调用 spi_setup 设置 SPI 设备
					调用 device_add 将设备加到系统(如果指定了总线会注册到对应总线,并进行设备驱动匹配,这里应该是 SPI 总线)

SPI 驱动注册过程

构造并初始化 struct spi_driver 对象
	调用 spi_register_driver  注册 SPI 驱动
		再次配置 struct spi_driver 对象,主要是内部 driver 成员的 bus 、 probe 、 remove 等
		调用 driver_register 注册驱动,这里指定的 bus 为 spi_bus_type 所以会注册到 spi_bus_type 总线中,然后执行设备驱动匹配操作

SPI 驱动传输数据

SPI 驱动采用 struct spi_transfer 描述一个数据包,其核心成员如下:

	//发送缓存
	const void *tx_buf;
	//接收缓存
	void *rx_buf;
	//缓存长度
	unsigned len;
	//此次传输完成后是否重新获取片选并配置
	unsigned cs_change;
	//片选无效时间
	u16 cs_change_delay;
	//cs_change_delay的单位,由us、ns、clk3种选择
	u8 cs_change_delay_unit;
	//字长
	u8 bits_per_word;
	//每个字传输完成后延迟多少us
	u8 word_delay_usecs;
	//每个字传输完成后延迟多少个时钟周期
	u16 word_delay;
	//在此传输后更改片选状态前之前延迟多少us
	u16 delay_usecs;
	//时钟速率
	u32 speed_hz;
	//spi_transfer链表节点
	struct list_head transfer_list;

struct spi_transfer 描述的是单个数据包,在 SPI 框架中还需要利用 struct spi_message 将一个或多个数据包组合成一个 message 后才能进行发送, struct spi_message 的核心成员如下:

	//要传输的数据包链表
	struct list_head transfers;
	//所属的 SPI 设备,其中包含了 SPI 设备的各种信息,如SPI控制器、片选、模式、时钟速率等
	struct spi_device *spi;
	//传输完成回调函数,用于异步传输
	void (*complete)(void *context);
	//用于再 SPI 控制器中形成一个队列,方便控制器管理需要发送的数据
	struct list_head queue;

SPI 传输数据相关的函数如下:

	//初始化一个SPI message
	void spi_message_init(struct spi_message *m)
	//向SPI message中添加一个数据包
	void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
	//启动SPI,进行同步传输
	int spi_sync(struct spi_device *spi, struct spi_message *message)
	//启动SPI,进行异步传输,传输完成后调用void (*complete)(void *context)函数
	int spi_async(struct spi_device *spi, struct spi_message *message)

SPI 传输数据的流程

以 spi_sync 为例, SPI 驱动框架提供了两种 spi_message 传输方案,分别如下:

  1. 阻塞模式
    在注册 SPI 控制器时如果提供了 transfer 函数即工作在阻塞模式。
构造 spi_message (发送过程可能会使用DMA)
	调用 spi_sync 进行传输
		再 spi_sync 中加锁后直接调用 __spi_sync (这里采用的互斥量加锁)
			调用 __spi_validate 检查 spi_message
			为 spi_message 绑定传输完成回调函数和 SPI 控制器
			检查 spi_controller 的 transfer 是否等于 spi_queued_transfer 函数,若不等于则调用 spi_async_locked 函数
				然后在 spi_async_locked 中加锁后调用 __spi_async (这里采用的自旋锁加锁)
					通过 spi_controller 控制器中的 transfer 指针调用驱动层的传输函数(应该是一个非阻塞函数,在控制器参数完成后调用 spi_message 传输完成回调函数)
			调用 wait_for_completion 等待传输完成
			获取传输结果并返回
  1. 队列模式
    在注册 SPI 控制器时不提供 transfer 函数,但提供了 transfer_one 函数,即工作在队列模式。
构造 spi_message ,因为发送过程可能会使用DMA,所以内存最好使用 kmalloc 分配
	调用 spi_sync 进行传输
		再 spi_sync 中加锁后直接调用 __spi_sync (这里采用的互斥量加锁)
			调用 __spi_validate 检查 spi_message
			为 spi_message 绑定传输完成回调函数和 SPI 控制器
			检查 spi_controller 的 transfer 是否等于 spi_queued_transfer ,等于则利用自旋锁加锁,然后调用 __spi_queued_transfer 将 spi_message 放入 spi_controller 的队列中
			调用 __spi_pump_messages 启动传输
				通过 spi_controller 的 cur_msg 判断是否正在传输,若正在传输则直接返回
				从 spi_controller 中取出的一个 spi_message ,并将其从队列中删除
				调用 spi_controller 控制器中的 transfer_one_message 传输一个 spi_message (默认的传输函数是 spi_transfer_one_message )
					在 spi_transfer_one_message 函数中多次调用 spi_controller 的 transfer_one 去完成一个 spi_message 的传输(每次调用 transfer_one 后还需要调用 spi_transfer_wait 去等待transfer_one 传输完成)
					传输完成后再调用 spi_finalize_current_message
						在 spi_finalize_current_message 中启动一个内核线程传输剩余的 spi_message ,然后执行当前 spi_message 的传输完成回调函数
			调用 wait_for_completion 函数等待 spi_message 传输完成
			获取传输结果并返回

SPI 设备树节点编写

在那条 SPI 总线下挂载设备就在那条总线的设备树节点下添加对应设备的子节点,节点命名规则 [标签:]名称[@地址],节点内容必须包含 reg 属性、 compatible 属性、 spi-max-frequency 属性, reg 属性用于描述片选索引, compatible 属性用于设备和驱动的匹配, spi-max-frequency 用于描述设备可支持的最大 SPI 总线频率,如下是在 SPI1 中添加一个 icm20608 设备节点的示例:

&spi1 {
	//描述SPI控制器引脚和片选引脚,并使能I2C控制器
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&spi1_pins_a>;
	pinctrl-1 = <&spi1_sleep_pins_a>;
	cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>;
	status = "okay";

	//描述icm20608设备
	spidev: icm20608@0 {
		compatible = "alientek,icm20608";
	 	reg = <0>;						/* CS #0 */
	 	spi-max-frequency = <8000000>;	/* 最大时钟频率 */
		spi-cpha;						/* cpha=1 */
		spi-cpol;						/* cpol=1 */
		spi-cs-high;					/* 片选高电平有效 */
		spi-lsb-first;					/* 低位在前 */
		spi-rx-bus-width = <1>;			/* 接收数据线宽度为1B */
		spi-tx-bus-width = <1>;			/* 发送数据线宽度为1B */
	};
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IPv6路由协议---IPv6动态路由(OSPFv3-4)

OSPFv3的链路状态通告LSA类型 链路状态通告是OSPFv3进行路由计算的关键依据,链路状态通告包含链路状态类型、链路状态ID、通告路由器三元组唯一地标识了一个LSA。 OSPFv3的LSA头仍然保持20字节,但是内容变化了。在LSA头中,OSPFv2的LS age、Advertising Router、LS Sequence…

Java中输入和输出处理(三)二进制篇

叮咚&#xff01;加油&#xff01;马上学完 读写二进制文件Data DataInputStream类 FilFeInputStream的子类 与FileInputStream类结合使用读取二进制文件 DataOutputStream类 FileOutputStream的子类 与FileOutputStream类结合使用写二进制文件 读写二进制代码 package 面…

DAPP和APP的区别在哪?

随着科技的飞速发展&#xff0c;我们每天都在与各种应用程序打交道。然而&#xff0c;你是否真正了解DAPP和APP之间的区别呢&#xff1f;本文将为你揭示这两者的核心差异&#xff0c;让你在自媒体平台上脱颖而出。 一、定义与起源 APP&#xff0c;即应用程序&#xff0c;通常指…

使用KubeSphere轻松部署Bookinfo应用

Bookinfo 应用 这个示例部署了一个用于演示多种 Istio 特性的应用&#xff0c;该应用由四个单独的微服务构成。 如安装了 Istio&#xff0c;说明已安装 Bookinfo。 这个应用模仿在线书店的一个分类&#xff0c;显示一本书的信息。 页面上会显示一本书的描述&#xff0c;书籍…

C/C++ 位段

目录 什么是位段&#xff1f; 位段的内存分配 位段的跨平台问题 什么是位段&#xff1f; 位段的声明与结构是类似的&#xff0c;但是有两个不同&#xff1a; 位段的成员必须是 int、unsigned int 或signed int 等整型家族。位段的成员名后边有一个冒号和一个数字 这是一个…

深度学习笔记(二)——Tensorflow环境的安装

本篇文章只做基本的流程概述&#xff0c;不阐述具体每个软件的详细安装流程&#xff0c;具体的流程网上教程已经非常丰富。主要是给出完整的安装流程&#xff0c;以供参考 环境很重要 一个好的算法环境往往能够帮助开发者事半功倍&#xff0c;入门学习的时候往往搭建好环境就已…

【ELISA检测】酶联免疫吸附实验概述-卡梅德生物

酶联免疫吸附实验&#xff08;Enzyme linked immunosorbent assay&#xff0c;ELISA&#xff09;是将抗原或抗体结合在固相载体表面&#xff0c;利用抗原抗体的特异性结合以及抗体或者抗原上标记的酶催化特定底物发生显色反应&#xff0c;实现目标物检测的免疫分析方法&#xf…

24/1/10 qt work

1. 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&…

11 个 Python全栈开发工具集

前言 以下是专注于全栈开发不同方面的 Python 库;有些专注于 Web 应用程序开发&#xff0c;有些专注于后端&#xff0c;而另一些则两者兼而有之。 1. Taipy Taipy 是一个开源的 Python 库&#xff0c;用于构建生产就绪的应用程序前端和后端。 它旨在加快应用程序开发&#xf…

Appium + ios环境搭建过程Mac

前提&#xff1a; 已经搭建好NodeJavaPythonAppium...环境 见下面的文章&#xff1a; ok的话按照下面的步骤搭建IOs的自动化 1. 安装Xcode 官方下载 (Downloads and Resources - Xcode - Apple Developer 1)AppStore 下载安装最新版本 2. 依赖工具 工具名描述libimobile…

Springboot的配置文件详解:从入门到精通,解读配置文件的奇妙世界

目录 1、前言 2、介绍 2.1 Springboot配置文件的作用 2.2 Springboot支持的配置文件类型 2.3 Springboot配置文件的加载顺序 3、YAML配置文件 3.1 YAML基本语法介绍 3.2 YAML中的基本数据类型 3.3 YAML中的复合数据类型 3.4 YAML中的配置属性 3.5 YAML中的多环境配置…

从0开始学Git指令(2)

从0开始学Git指令 因为网上的git文章优劣难评&#xff0c;大部分没有实操展示&#xff0c;所以打算自己从头整理一份完整的git实战教程&#xff0c;希望对大家能够起到帮助&#xff01; 工作区&#xff08;Working Directory&#xff09; 就是你在电脑里能看到的目录&#x…

还不会python 实现常用的数据编码和对称加密?看这篇文章就够啦~

相信很多使用 python 的小伙伴在工作中都遇到过&#xff0c;对数据进行相关编码或加密的需求&#xff0c;今天这篇文章主要给大家介绍对于一些常用的数据编码和数据加密的方式&#xff0c;如何使用 python 去实现。话不多说&#xff0c;接下来直接进入主题&#xff1a; 前言 1…

Windows VSCode 使用Python

一、vscode中安装python 二、下载python.exe&#xff08;即vscode中需要的python解释器&#xff09; 下载地址&#xff1a;https://www.python.org/downloads/ 三、安装第三方代码规范工具 参考网址&#xff1a;https://www.python.org/downloads/ 工具介绍 flake8 &#xf…

文心一言API调用,保姆级案例分享

分享一个调用文心一言API的案例。今天自己用程序去过去文心一言模型中获取结果。 文心一言API调用如何收费&#xff1f; 官方给送了20块钱的体验券&#xff01; 后续收费规则如下 如何开通所需要要的 API key 和 Secret key&#xff1f; api调用需要先在千帆平台开通API key 。…

大模型PEFT技术原理(一):BitFit、Prefix Tuning、Prompt Tuning

随着预训练模型的参数越来越大&#xff0c;尤其是175B参数大小的GPT3发布以来&#xff0c;让很多中小公司和个人研究员对于大模型的全量微调望而却步&#xff0c;近年来研究者们提出了各种各样的参数高效迁移学习方法&#xff08;Parameter-efficient Transfer Learning&#x…

TurboDesign安装包及安装教程

下载链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1I_jMid-z186GgvyH3ZExGQ 提取码&#xff1a;z936 1.解压下载好的压缩包。 2.进入解压后的文件夹并点击进入“ADT TURBODesign Suite 6.4.0”。 3.点击“安装包”进入文件夹。 4.找到“setup.exe”并右键点…

Docker入门介绍

【一】从 dotCloud 到 Docker——低调奢华有内涵 1、追根溯源:dotCloud 时间倒回到两年前&#xff0c;有一个名不见经传的小公司&#xff0c;他的名字叫做:dotCloud。 dotCloud 公司主要提供的是基于 PaaS(Platform as a Service&#xff0c;平台及服务) 平台为开发者或开发商…

训练自己的GPT2

训练自己的GPT2 1.预训练与微调2.准备工作2.在自己的数据上进行微调 1.预训练与微调 所谓的预训练&#xff0c;就是在海量的通用数据上训练大模型。比如&#xff0c;我把全世界所有的网页上的文本内容都整理出来&#xff0c;把全人类所有的书籍、论文都整理出来&#xff0c;然…

从零学Java 集合概述

Java 集合概述 文章目录 Java 集合概述1 什么是集合?2 Collection体系集合2.1 Collection父接口2.1.1 常用方法2.1.2 Iterator 接口 1 什么是集合? 概念&#xff1a;对象的容器&#xff0c;定义了对多个对象进行操作的常用方法&#xff1b;可实现数组的功能。 和数组区别&…