Linux驱动 SPI子系统

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);elsekfree(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.rhkb.cn/news/251524.html

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

相关文章

C语言——柔性数组

柔性数组概念&#xff1a; 这个概念你可能没听说过&#xff0c;但是这个概念确实存在&#xff0c;在C99中&#xff0c;结构体中最后一个成员允许是未知大小的数组&#xff0c;这就叫做【柔性数组】成员。 struct S {char c;int i;int arr[0];//未知大小的数组 - 柔性数组成员 }…

【Docker进阶】镜像制作-用Dockerfile制作镜像(一)

进阶一 docker镜像制作 文章目录 进阶一 docker镜像制作用dockerfile制作镜像dockerfile是什么dockerfile格式为什么需要dockerfileDockerfile指令集合FROMMAINTAINERLABELCOPYENVWORKDIR 用dockerfile制作镜像 用快照制作镜像的缺陷&#xff1a; 黑盒不可重复臃肿 docker…

【日常聊聊】开源软件影响力

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 方向一&#xff1a;开源软件如何推动技术创新 方向二&#xff1a;开源软件的商业模式 方向三&#xff1a;开源软件的安全风险 方…

基于EdgeWorkers的边缘应用如何进行单元测试?

随着各行各业数字化转型的持续深入&#xff0c;越来越多企业开始选择将一些应用程序放在距离最终用户更近的边缘位置来运行&#xff0c;借此降低延迟&#xff0c;提高应用程序响应速度&#xff0c;打造更出色的用户体验。 相比传统集中部署和运行的方式&#xff0c;这种边缘应…

[office] Excel2007在工作簿中创建区域名称 #职场发展#经验分享

Excel2007在工作簿中创建区域名称 Excel 提供了几种不同的方法来创建区域名称。但在开始之前&#xff0c;必须注意关于可接受内容的重要规则: 名称不能含有空格。可以用一个下划线字符来代替空格(如Annual Total ) 。 可以使用字母和数字的任意组合&#xff0c;但是名称必须以…

3 编辑器(Vim)

1.完成 vimtutor。备注&#xff1a;它在一个 80x24&#xff08;80 列&#xff0c;24 行&#xff09; 终端窗口看起来效果最好。 2.下载我们提供的 vimrc&#xff0c;然后把它保存到 ~/.vimrc。 通读这个注释详细的文件 &#xff08;用 Vim!&#xff09;&#xff0c; 然后观察 …

CDH6.3.2 多 Spark 版本共存

一 部署Spark客户端 1.1 部署spark3客户端 tar -zxvf spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz -C /opt/cloudera/parcels/CDH/lib cd /opt/cloudera/parcels/CDH/lib mv spark-3.3.1-bin-3.0.0-cdh6.3.2/ spark3将 CDH 集群的 spark-env.sh 复制到 /opt/cloudera/parcels/CDH/li…

Framework - ActivityThread 应用启动UI渲染流程

一、概念 ActivityThread拥有 main(String[] agrs) 方法&#xff0c;作为程序的入口&#xff0c;是应用程序的初始化类。&#xff08;ActivityThread不是主线程&#xff0c;它在 main() 方法中实例化&#xff0c;是运行在主线程中。&#xff09;ApplicationThread是 ActivityT…

Linux---信号

前言 到饭点了&#xff0c;我点了一份外卖&#xff0c;然后又开了一把网游&#xff0c;这个时候&#xff0c;我在打游戏的过程中&#xff0c;我始终记得外卖小哥会随时给我打电话&#xff0c;通知我我去取外卖&#xff0c;这个时候游戏还没有结束。我在打游戏的过程中需要把外…

Cannot resolve plugin org.apache.maven.plugins:maven-compiler-plugin:3.8.1

目录 【问题描述】maven环境报错 Cannot resolve plugin org.apache.maven.plugins:maven-compiler-plugin:3.8.1 【解决办法】 检查maven路径是否一致 路径一致的话&#xff0c;更改配置文件settings.xml的镜像源。 添加代码到 <mirrors> <!-- 阿里镜像 --> &l…

网络原理-TCP/IP(5)

TCP协议 延迟应答 它也是基于滑动窗口,提高效率的一种机制,结合滑动窗口以及流量控制,能够以延迟应答ACK的方式,把反馈的窗口,搞大.核心在于允许范围内,让窗口尽可能大. 如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小. 1.假设接收端缓冲区为1M.一次收到了5…

新手小白零基础一分钟自动部署搭建幻兽帕鲁服务器教程

对于初次接触游戏联机服务器搭建的新手玩家来说&#xff0c;部署和配置一个像“幻兽帕鲁”这样的网络游戏联机服务器可能会显得有些复杂。但别担心&#xff0c;本文将提供一种快速、简洁且适合零基础新手的一分钟自动部署搭建幻兽帕鲁游戏联机服务器的详细教程。 本文将为大家提…

Linux 驱动开发基础知识——内核对设备树的处理与使用(十)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…

Oracle闪回日志管理(flashbackup log)

Oracle闪回日志管理&#xff08;flashbackup log&#xff09; 1.开启闪回日志 shutdown immediate startup mount; alter database archivelog; alter database flashback on; alter database open; 2、查看闪回日志保留期限 show parameter flash 默认是1440分钟&#xff0c…

【LeetCode】刷题总结 - 15. 三数之和

15. 三数之和 | LeetCode 思路 首先对 nums 进行排序&#xff0c;然后设置三个指针&#xff1a;left、mid、right&#xff1a; left 从最左边开始&#xff0c;依次向后遍历每次固定住 left 后&#xff0c;就化为一个 2sum 问题&#xff1a; mid left 1&#xff0c;right …

python中[[]] * (n)和[[] for _ in range(n)]的区别

1、现象 刷leetcode207的时候碰到一个坑&#xff0c;用[[]] * (n)初始化二维数组&#xff0c;逻辑是正确的&#xff0c;但是结果始终不对。 2、原因 最后定位是初始化语句使用错误导致的&#xff0c;我使用的是[[]] * (n)&#xff0c;应该使用[[] for _ in range(n)] 3、解…

SpringBoot:实例一

一、实现的效果 在浏览器地址栏输入http://localhost:8080/hello&#xff0c;当前页面显示hello world 实例一代码&#xff1a;点击查看LearnSpringBoot01 点击查看更多的SpringBoot教程 二、 效果图 三、 pom.xml代码 <?xml version"1.0" encoding"UTF-…

【快速上手QT】01-QWidgetQMainWindow QT中的窗口

总所周知&#xff0c;QT是一个跨平台的C图形用户界面应用程序开发框架。它既可以开发GUI程序&#xff0c;也可用于开发非GUI程序&#xff0c;当然我们用到QT就是要做GUI的&#xff0c;所以我们快速上手QT的第一篇博文就讲QT的界面窗口。 我用的IDE是VS2019&#xff0c;使用QTc…

【小白学unity记录】使用unity播放声音

1. 示例 unity中播放声音涉及到两个组件。AudioSource和AudioClip。AudioSource可以理解为播放器&#xff0c;AudioClip可以理解为音频片段文件。AudioSource可以通过.clip属性切换音频片段。 using UnityEngine;public class PlayerController : MonoBehaviour {private int…

Windows11通过Hyper-V创建VM,然后通过vscode连接vm进行开发

这边需要在win11上建立vm来部署docker(这边不能用windows版本的docker destop)&#xff0c;学习了下&#xff0c;记录。 下载系统镜像 首先下载系统镜像&#xff1a;https://releases.ubuntu.com/focal/ 这边使用的是ubuntu20.04.6 LTS (Focal Fossa) &#xff0c;Server inst…