Linux驱动 SPI子系统

news2024/9/21 20:32:46

1、SPI协议

SPI(Serial Peripheral Interface)是一种同步串行数据通信协议,通常用于连接微控制器和外部设备,如传感器、存储器、显示器等。SPI协议使用四根线进行通信,包括时钟线(SCLK)、数据输入线(MISO)、数据输出线(MOSI)和片选线(SS)。

SPI通信的基本过程如下:

  • 主设备通过片选线选择要与之通信的从设备
  • 主设备通过时钟线产生时钟信号,控制数据传输的时序
  • 主设备通过数据输出线(MOSI)发送数据到从设备
  • 从设备通过数据输入线(MISO)返回响应数据到主设备

时钟线在空闲时可以有高低电平两种状态,数据在采样可以在时钟线的奇数沿或者偶数沿,前者为极性,后者为相位,一共四种组合构成了SPI通信的4种模式:

模式极性相位说明
000clk空闲为低电平,在奇数沿采样
101clk空闲为低电平,在偶数沿采样
210clk空闲为高电平,在奇数沿采样
311clk空闲为高电平,在偶数沿采样

以模式0为例,clk空闲为低电平,第一个沿为奇数沿,是从低电平变高电平的上升沿,在此处进行数据采样:

2、SPI驱动子系统

2.1 主机驱动

主机驱动一般由SOC厂商编写,本文示例使用的全志H616的内核代码,源码位于drivers/spi/spi-sunxi.c,主机驱动使用了platform总线驱动模型, platform总线的详细匹配过程可以参考之前的文章:Linux驱动(四)platform总线匹配过程,在设备树中配置好对应节点后,能够与内核配置的of_match_table匹配成功就会调用probe函数:

probe函数主要完成两个任务,一个是根据设备树的配置获取spi通信的相关参数,申请-->初始化-->注册spi_master;二是将设备树配置的子节点转化为spi_device数据结构,供后续的设备驱动使用:

2.2 设备驱动

linux内核中spi设备驱动代码位置:/drivers/spi/spidev.c。驱动的入口函数如下,正常的字符设备驱动需要经过三个步骤:register_chrdev-->class_create-->device_create;但是在入口函数中只进行了前面两步,然后调用spi_register_driver向spi总线进行了驱动注册。

static int __init spidev_init(void)
{
	int status;

	BUILD_BUG_ON(N_SPI_MINORS > 256);
	status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
	if (status < 0)
		return status;

	spidev_class = class_create(THIS_MODULE, "spidev");
	if (IS_ERR(spidev_class)) {
		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
		return PTR_ERR(spidev_class);
	}

	status = spi_register_driver(&spidev_spi_driver);
	if (status < 0) {
		class_destroy(spidev_class);
		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
	}
	return status;
}

spi_register_driver注册驱动,其本质也是使用总线设备驱动模型,不过其使用的是spi总线,设备树中的节点转换为spi_device与驱动进行匹配,匹配成功后则会调用驱动的probe函数,其具体的匹配过程如下:

设备和驱动通过spi总线匹配成功后执行probe函数,即spidev_probe函数,其内容如下,在31行使用device_create进行字符设备创建,在前面的入口函数中已经完成register_chrdev和class_create,在此处调用device_create创建了spidevxx设备,其中第一个x表示的事第几路SPI,第二个x表示的是该SPI下的第几个片选设备(设备树<reg>属性):

static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	int			status;
	unsigned long		minor;

	if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {
		dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n");
		WARN_ON(spi->dev.of_node &&
			!of_match_device(spidev_dt_ids, &spi->dev));
	}

	spidev_probe_acpi(spi);

	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	if (!spidev)
		return -ENOMEM;

	spidev->spi = spi;
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);

	INIT_LIST_HEAD(&spidev->device_entry);

	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) {
		struct device *dev;

		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
		dev = device_create(spidev_class, &spi->dev, spidev->devt,
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);
		status = PTR_ERR_OR_ZERO(dev);
	} else {
		dev_dbg(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors);
		list_add(&spidev->device_entry, &device_list);
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;

	if (status == 0)
		spi_set_drvdata(spi, spidev);
	else
		kfree(spidev);

	return status;
}

在上述代码中定义了struct spidev_data    *spidev,其存储了spi设备对应的master结构体和设备本身的设备号,因此当使用open函数打开该设备节点的时候就可以反向查找到该设备对应的master结构体,进而可以使用其中的收发函数实现数据传输。

2.3 应用测试

目前测试的设备中设备树配置了两路SPI,每一路配置了一个设备:

根据上述分析,会生成两个字符设备,查看/dev下相关文件:

内核中spi驱动程序使用示例在/tools/spi/spidev_fdx.c中,重点查看do_msg接口:

static void do_msg(int fd, int len)
{
	struct spi_ioc_transfer	xfer[2];
	unsigned char		buf[32], *bp;
	int			status;

	memset(xfer, 0, sizeof xfer);
	memset(buf, 0, sizeof buf);

	if (len > sizeof buf)
		len = sizeof buf;

	buf[0] = 0xaa;
	xfer[0].tx_buf = (unsigned long)buf;
	xfer[0].len = 1;

	xfer[1].rx_buf = (unsigned long) buf;
	xfer[1].len = len;

	status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
	if (status < 0) {
		perror("SPI_IOC_MESSAGE");
		return;
	}
	printf("response(%2d, %2d): ", len, status);
	for (bp = buf; len; len--)
		printf(" %02x", *bp++);
	printf("\n");
}

根据上述示例编写一个简单的测试用例,将SPI的输入和输出进行短接,测试数据发送与数据接收是否一致,测试代码如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/types.h>
#include <linux/spi/spidev.h>



int main(int argc, char **argv)
{
    int fd, ret = 0;
    struct spi_ioc_transfer xfer = {0};
    unsigned char sendbuf[6] = "hello";
    unsigned char recvbuf[6] = {0};
    
    fd = open("/dev/spidev1.1", O_RDWR);
    if (fd < 0)
    {
        printf("error, can't open /dev/spidev1.1\n");
        return 0;
    }
    
    xfer.tx_buf =  (unsigned long)sendbuf;
    xfer.rx_buf =  (unsigned long)recvbuf;
    xfer.len =  6;

    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &xfer);

    if(ret < 0)
    {
        printf("err\n");
    }
    else
    {
        printf("send data : %s\n", sendbuf);
        printf("recv data : %s\n", recvbuf);
        
    }
    return 0;
}

编译程序进行测试,测试结果正常:

3、总结

文章简单阐述了spi设备的协议和特性,结合内核代码总结了SPI驱动子系统的架构,最后编写了简单测试用例进行测试,其中设备树的详细配置项和数据收发中内核的详细处理后续再另起篇幅阐述。

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

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

相关文章

第十二篇【传奇开心果系列】Python的OpenCV技术点案例示例:视频流处理

传奇开心果短博文系列 系列短博文目录Python的OpenCV技术点案例示例短博文系列短博文目录一、前言二、视频流处理介绍三、实时视频流处理示例代码四、视频流分析示例代码五、归纳总结系列短博文目录 Python的OpenCV技术点案例示例短博文系列 短博文目录 一、前言 OpenCV视频…

【面试官问】Redis 持久化

目录 【面试官问】Redis 持久化 Redis 持久化的方式RDB(Redis DataBase)AOF(Append Only File)混合持久化:RDB + AOF 混合方式的持久化持久化最佳方式控制持久化开关主从部署使用混合持久化使用配置更高的机器参考文章所属专区

OfficeWeb365 Readfile 任意文件读取漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

Qt|实现时间选择小功能

在软件开发过程中&#xff0c;QtDesigner系统给出的控件很多时候都无法满足炫酷的效果&#xff0c;前一段时间需要用Qt实现选择时间的小功能&#xff0c;今天为大家分享一下&#xff01; 首先看一下时间效果吧&#xff01; 如果有需要继续往下看下去哟~ 功能 1&#xff1a;开…

池化技术的总结

文章目录 1.什么是池化技术2.池化技术的应用一、连接池二、线程池三、内存池 3.池化技术的总结 1.什么是池化技术 池化技术指的是提前准备一些资源&#xff0c;在需要时可以重复使用这些预先准备的资源。 在系统开发过程中&#xff0c;我们经常会用到池化技术。通俗的讲&am…

xlsx xlsx-style 使用和坑记录

1 安装之后报错 npm install xlsx --savenpm install xlsx-style --save Umi运行会报错 自己代码 import XLSX from "xlsx"; import XLSXStyle from "xlsx-style";const data [["demo1","demo2","demo3","demo4&quo…

电路设计(9)——八路智力抢答器的proteus仿真

1.设计要求 运用模拟电路、数字电路知识&#xff0c;设计、制作一个8路智力竞赛抢答器&#xff0c;要求有优先锁存、数显、声响及复位电路。 主要元器件&#xff1a;CD4511&#xff0c;IN4148&#xff0c;共阴数码管&#xff0c;NPN三极管9013&#xff0c;NE555&#xff0c;喇叭…

在工业制造方面,如何更好地实现数字化转型?

实现工业制造的数字化转型涉及利用数字技术来增强流程、提高效率并推动创新。以下是工业制造领域更好实现数字化转型的几个关键步骤&#xff1a; 1.定义明确的目标&#xff1a; 清楚地概述您的数字化转型目标。确定需要改进的领域&#xff0c;例如运营效率、产品质量或供应链…

[SWPUCTF 2021 新生赛]Do_you_know_http

我们看到它让我们用WLLM浏览器登录 那我们修改User-Agent的值即可 发现有一个a.php的我们进入该目录 它提示我们不在本地服务器上 发现有一个/secretttt.php的目录 我进入即可获得flag

Netflix Mac(奈飞mac客户端) v2.13.0激活版

Clicker for Netflix Mac版是一款适用于Mac的最佳独立Netflix播放器&#xff0c;具有直接从从Dock启动Netflix&#xff0c;从触摸栏控制Netflix&#xff0c;支持画中画等多种功能&#xff0c;让你拥有更好的观看体验。 软件特色 •直接从Dock启动Netflix •从触摸栏控制Netflix…

何以穿越产业周期?解读蓝思科技2023年增长密码

1月30日晚&#xff0c;蓝思科技发布了2023年业绩预告&#xff0c;2023年预计实现归母净利润29.38亿元-30.60亿元&#xff0c;同比增长20%-25%。 松果财经注意到&#xff0c;蓝思科技通过垂直整合&#xff0c;构筑了更具竞争力的产业链条。一方面&#xff0c;公司打造了包含ODM…

[349. 两个数组的交集](C语言)(两种解法:双指针+排序,哈希)

✨欢迎来到脑子不好的小菜鸟的文章✨ &#x1f388;创作不易&#xff0c;麻烦点点赞哦&#x1f388; 所属专栏&#xff1a;刷题 我的主页&#xff1a;脑子不好的小菜鸟 文章特点&#xff1a;关键点和步骤讲解放在 代码相应位置 前提&#xff1a; 看本文章之前&#xff0c;建…

php微信退款:订单金额或退款金额与之前请求不一致,请核实后再试问题

bug出现情况&#xff1a; 微信支付退款 解决方案 php将float转换成int类型&#xff0c;在要转换的变量之前加上用括号括起来的目标类型 <?php $a 16.90; echo (int)($a * 100); echo "<br/>"; echo (int)strval($a * 100);

2024年土木工程与环境科学国际会议(ICCEES2024)

2024年土木工程与环境科学国际会议(ICCEES2024) 会议简介 土木工程与环境科学是国民经济中的基础性、先导性、战略性产业&#xff0c;本次会议主要围绕土木工程与环保科学等研究领域&#xff0c;旨在吸引土木工程与生态环境工程专家学者、科技人员、&#xff0c;为广大工程师…

SSL证书的验证过程

HTTPS是工作于SSL层之上的HTTP协议&#xff0c;SSL&#xff08;安全套接层&#xff09;工作于TCP层之上&#xff0c;向应用层提供了两个基本安全服务&#xff1a;认证和保密。SSL有三个子协议&#xff1a;握手协议&#xff0c;记录协议和警报协议。其中握手协议实现服务器与客户…

嵌入式学习第十六天!(Linux文件查看、查找命令、标准IO)

Linux软件编程 1. Linux&#xff1a; 操作系统的内核&#xff1a; 1. 管理CPU 2. 管理内存 3. 管理硬件设备 4. 管理文件系统 5. 任务调度 2. Shell&#xff1a; 1. 保护Linux内核&#xff08;用户和Linux内核不直接操作&#xff0c;通过操作Shell&#xff0c;Shell和内核交互…

AES加密原理

AES是一个迭代的、分组密码加密方式&#xff0c;可以使用128 、192和256位密钥。与 公共密钥密码使用密钥对不同&#xff0c;对称密钥密码使用相同的密钥加密和解密数据。 通过分组密码返回的加密数据的位数与输入数据相同。迭代加密使用一个循环结 构&#xff0c;在该循环中重…

小程序中封装下拉选择框

小程序中没有现成的下拉选择组件&#xff0c;有个picker组件&#xff0c;但是是底部弹出的&#xff0c;不满足我的需求&#xff0c;所以重新封装了一个。 封装的下拉组件 html部分&#xff1a; <view class"select_all_view"><!-- 内容说明&#xff0c;可…

ref和reactive, toRefs的使用

看尤雨溪说&#xff1a;为什么Vue3 中应该使用 Ref 而不是 Reactive&#xff1f; toRefs import { ref, toRefs } from vue;// 定义一个响应式对象 const state ref({count: 0,name: Vue });// 使用toRefs转换为响应式引用对象 const reactiveState toRefs(state);// 现在你…

vue3 之 组合式API—reactive和ref函数

ref&#xff08;&#xff09; 作用&#xff1a;接收简单类型或者对象类型的数据传入并返回一个响应式的对象 核心步骤&#xff1a; 1️⃣ 从 vue 包中导入 ref 函数 2️⃣在 <script setup>// 导入import { ref } from vue// 执行函数 传入参数 变量接收const count …