Linux驱动开发:SPI子系统

news2024/9/25 9:34:05

1、SPI简介

1.1 四根线

MISO:主设备数据输入,从设备数据输出。
MOSI:主设备数据输出,从设备数据输入。
SCLK:时钟信号,由主设备产生。
CS:    从设备片选信号,由主设备控制。

1.2 四种模式

CPOL(时钟极性) :   0:时钟起始位低电平      1:时钟起始为高电平  
CPHA(时钟相位) :0:第一个时钟周期采样   1:第二个时钟周期采样

1、CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是 SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿

2、CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是 SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿

3、CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是 SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿

4、CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是 SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿

1.3 SPI数据传输

        

        从图中可以看出,主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

1.4 SPI驱动框架简介

        SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC 的 SPI 控制器接口。样。和 I2C适配器驱动一样,SPI主机驱动一般都是 SOC 厂商去编写的,所以我们作为 SOC的使用者,这一部分的驱动就不用操心了。

2、SPI设备驱动

2.1 SPI相关API

2.1.1 spi_driver

struct spi_driver {
	int			(*probe)(struct spi_device *spi);
	int			(*remove)(struct spi_device *spi);
	struct device_driver	driver;
};

struct device_driver {
	const char	*name;
	const struct of_device_id	*of_match_table;
}

2.1.2 注册:spi_register_driver

#define spi_register_driver(driver) 
	__spi_register_driver(THIS_MODULE, driver)

2.1.3 注销:spi_unregister_driver

static inline void spi_unregister_driver(struct spi_driver *sdrv)

2.1.4 module_spi_driver:一键注册,不需要以上注册注销的过程

#define module_spi_driver(__spi_driver) 
	module_driver(__spi_driver, spi_register_driver, 
			spi_unregister_driver)

#define module_driver(__driver, __register, __unregister, ...) 
static int __init __driver##_init(void) 
{ 
	return __register(&(__driver) , ##__VA_ARGS__); 
} 
module_init(__driver##_init); 
static void __exit __driver##_exit(void) 
{ 
	__unregister(&(__driver) , ##__VA_ARGS__); 
} 
module_exit(__driver##_exit);
module_spi_driver(myspi);

#define module_spi_driver(myspi) 
	module_driver(myspi, spi_register_driver, 
			spi_unregister_driver)

#define module_driver(myspi, spi_register_driver, spi_unregister_driver) 
static int __init myspi_init(void) 
{ 
	return spi_register_driver(&myspi); 
} 

static void __exit myspi_exit(void) 
{ 
	spi_unregister_driver(&myspi); 
} 
module_init(myspi_init); 
module_exit(myspi_exit);

2.1.5 spi_write

spi_write(struct spi_device *spi, const void *buf, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= buf,
			.len		= len,
		};

	return spi_sync_transfer(spi, &t, 1);
}

2.1.6 spi_read

spi_read(struct spi_device *spi, void *buf, size_t len)
{
	struct spi_transfer	t = {
			.rx_buf		= buf,
			.len		= len,
		};

	return spi_sync_transfer(spi, &t, 1);
}

2.1.7 spi_write_then_read 

extern int spi_write_then_read(struct spi_device *spi,
		const void *txbuf, unsigned n_tx,
		void *rxbuf, unsigned n_rx);

2.1.8 以上三种spi传输函数解析,实际接收发送只需spi_write、spi_read

struct spi_transfer {
	const void	*tx_buf;  //用于保存发送的数据
	void		*rx_buf;  //用于保存接收到的数据
	unsigned	len;      //是要进行传输的数据长度, SPI是全双工通信,
                          //因此在一次通信中发送和接收的字节数都是一样的,
                          //所以 spi_transfer中也就没有发送长度和接收长度之分。
};

spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,
	unsigned int num_xfers)
{
	struct spi_message msg;

	spi_message_init_with_transfers(&msg, xfers, num_xfers);

	return spi_sync(spi, &msg);
}

struct spi_message {
	struct list_head	transfers;
	struct spi_device	*spi;
};

spi_message_init_with_transfers(struct spi_message *m,
struct spi_transfer *xfers, unsigned int num_xfers)
{    
	unsigned int i;
	spi_message_init(m);
	for (i = 0; i < num_xfers; ++i)
		spi_message_add_tail(&xfers[i], m);
}

总体流程:

①、申请并初 始化 spi_transfer,设置 spi_transfer的 tx_buf成员变量, tx_buf为要发送的数
据。然后设置 rx_buf成员变量, rx_buf保存着接收到的数据。最后设置 len成员变量,也就是要进行数据通信的长度。
②、使用 spi_message_init函数初始化 spi_message。
③、使用 spi_message_add_tail函数将前面设置好的 spi_transfer添加到 spi_message队列中。
④、使用 spi_sync函数完成 SPI数据同步传输。

 2.1.9 spi_message_init

int spi_sync(struct spi_device *spi, struct spi_message *message) 
/*
功能:初始化spi_message
参数:
    @spi    :要进行数据传输的 spi_device
    @message:要传输的 spi_message
返回值:无
*/

2.1.10 spi_message_add_tail

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
/*
功能:初始化完成以后需要将 spi_transfer添加到 spi_message队列中
参数:
    @t:要添加到队列中的 spi_transfer
    @m:spi_transfer要加入的 spi_message
返回值: 无
*/

2.1.11 spi_sync:同步传输

int spi_sync(struct spi_device *spi, struct spi_message *message)
/*
功能:同步传输,会阻塞的等待 SPI 数据传输完成
参数:
    @spi:要进行数据传输的 spi_device
    @message:要传输的 spi_message
返回值:成功返回0,失败返回错误码
*/

2.1.12 spi_async:异步传输

int spi_async(struct spi_device *spi, struct spi_message *message)
/*
功能:异步传输不会阻塞的等到 SPI数据传输完成,异步传输需要设置 spi_message中的 complete成员变量,
      complete是一个回调函数,当 SPI异步传输完成以后此函数就会被调用。
参数:        
    @spi:要进行数据传输的 spi_device
    @message:要传输的 spi_message
返回值:成功返回0,失败返回错误码
*/

3、驱动程序

3.1 修改设备树

&spi4{
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&spi4_pins_b>;
    pinctrl-1 = <&spi4_sleep_pins_b>;
    cs-gpios = <&gpioe 11 0>;
    status = "okay";
    
    m74hc595@0{
        compatible = "aaa,m74hc595";
        reg = <0>;
        spi-max-frequency = <10000000>; //10Mhz
    };
};

3.2 驱动程序编写

#ifndef __M74HC595_H__
#define __M74HC595_H__

#define SEG_WHICH _IOW('k',0,int)
#define SEG_DAT  _IOW('k',1,int)
#endif
#define NAME "m74hc595"
int major = 0;
struct class *cls;
struct device *dev;
struct spi_device *gspi;
u8 code[] = {
	0x3f, 0x06, 0x5b, 0x4f, 0x6d, 0x7d, 0x07, 0x7f, 
	0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, 
};

u8 which[] = {
	0x1, 0x2, 0x4, 0x8, 
};

int m74hc595_open(struct inode *inode, struct file *file)
{
	return 0;
}
long m74hc595_ioctl(struct file *file, 
	unsigned int cmd, unsigned long args)
{	
	switch(cmd){
		case SEG_WHICH:
			spi_write(gspi,&which[args],1);
			break;
		case SEG_DAT:
			spi_write(gspi,&code[args],1);
			break;
		default: printk("ioctl error\n");break;
	}
	
	return 0;
}

int m74hc595_close(struct inode *inode, struct file *file)
{
	return 0;
}
struct file_operations fops = {
	.open = m74hc595_open,
	.unlocked_ioctl = m74hc595_ioctl,
	.release = m74hc595_close,
};

int	m74hc595_probe(struct spi_device *spi)
{
	u8 buf[2] = {0xf,0x0};
	gspi = spi;
	spi_write(gspi,buf,ARRAY_SIZE(buf));
	
	major = register_chrdev(0,NAME,&fops);
	cls = class_create(THIS_MODULE,NAME);
	dev = device_create(cls,NULL,MKDEV(major,0),NULL,NAME);

	return 0;
}

int	m74hc595_remove(struct spi_device *spi)
{
	device_destroy(cls,MKDEV(major,0));
	class_destroy(cls);
	unregister_chrdev(major,NAME);
	return 0;
}

const struct of_device_id of_match[] = {
	{.compatible = "aaa,m74hc595",},
	{},
};

struct spi_driver m74hc595 = {
	.probe = m74hc595_probe,
	.remove = m74hc595_remove,
	.driver = {
		.name = "bbb",
		.of_match_table = of_match,
	},	
};
module_spi_driver(m74hc595);

4、应用程序
 

int main(int argc, const char *argv[])
{
	int which=0;
	int data=0;
	int fd;
	fd = open("/dev/m74hc595",O_RDWR);
	if(fd < 0){
		perror("open error");
		return -1;
	}

	while(1){
		ioctl(fd,SEG_WHICH,which++);
		ioctl(fd,SEG_DAT,data++);
		if(which >= 4)which=0;
		if(data >= 16)data = 0;
		sleep(1);
	}

	close(fd);
	return 0;
}

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

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

相关文章

《string类的使用介绍》

本文主要介绍string的常见的接口的使用 文章目录 一、什么是string类二、string类的使用1、string类对象的常见构造2、string类对象的容量操作3、string类对象的访问及遍历操作①operator[ ]的用法②迭代器③范围for 4、string类对象的修改操作①push_back②append③operator&a…

刷题笔记8| 344.反转字符串, 541. 反转字符串II, 剑指Offer 05.替换空格

344.反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 输入&#xff1a;s ["h","e",…

索引—MySQL

文章目录 1.定义以及相关知识1.1定义1.2数据库保存数据的基本单位 2.MySQL中索引分类2.1B树和B树2.2主键索引&#xff08;聚簇索引&#xff09;2.3非聚簇索引2.4覆盖索引2.5复合索引&#xff08;联合索引&#xff09;2.6基于B树的索引2.7hash索引 1.定义以及相关知识 1.1定义 …

okio篇--总览

看源码第一步&#xff0c;先去看看官网对okio的介绍&#xff1a; Okio 首先第一段&#xff1a; okio对bytes&#xff0c;做了两种形式的封装&#xff1a;ByteString和Buffer。 其中ByteString&#xff0c;是针对字符类的数据&#xff0c;内部封装一个byte数组&#xff0c;封…

网络协议与攻击模拟-06-ICMP重定向

■0网络不可达 ■2协议不可达 类型4源抑制 类型5重定向 2、 ICMP 常见的报文 响应请求 使用 ping 请求&#xff08; type 0)响应&#xff08; type 8) 目标不可达 type 3 源抑制 源抑制则充当一个控制流量的角色&#xff0c;它通知主机减少数据报流量&#xff0c;由于 I…

Django初识

1、简介 Django&#xff0c;是用python语言写的开源web开发框架&#xff0c;并遵循MVC设计。劳伦斯出版集团为了开发以新闻内容为主的网站&#xff0c;而开发出来了这个框架&#xff0c;于2005年7月在BSD许可证下发布。这个名称来源于比利时的爵士音乐家DjangoReinhardt&#…

操作系统第二章——进程与线程(中)

和光同尘&#xff0c;与时舒卷 文章目录 2.2.1 调度的概念&#xff0c;层次知识总览调度的基本概念高级调度低级调度中级调度三层调度的联系&#xff0c;对比进程的挂起态和七状态模型知识回顾 2.2.2 进程调度的时机&#xff0c;切换与过程&#xff0c;方式知识总览进程调度的时…

【C++】第二站:类和对象(中)拷贝构造函数

文章目录 一、拷贝构造函数的概念二、拷贝构造函数的特性三、深度剖析拷贝构造函数不采用引用会无限递归的原因1.C对于传参的两个规定2.如何解开这个无穷递归 四、拷贝构造函数的其他特性五、拷贝构造的一些使用场景 一、拷贝构造函数的概念 拷贝构造函数&#xff1a;只有单个形…

2.2 Linux控制台访问CLI

系列文章目录 第1章 Linux Shell简介 第2章 Shell基础 <本章所在位置> 第3章 Bash Shell基础命令 第4章 Bash Shell命令进阶 第5章 Linux Shell深度理解 第6章 Linux环境变量 第7章 Linux文件权限 第8章 Linux文件系统的管理 第9章 Linux软件安装 第10章 Linux文本编辑器…

【MySQL】搭建出高可用性、高性能的MySQL集群要考虑的事是蛮多的,你看看会不会?

MySQL 架构设计数据同步负载均衡安全性监控和维护注意的点1. 确定节点数量和配置2. 选择合适的硬件和网络设备3. 避免单点故障4. 定期备份和恢复测试5. 定期更新和升级 Java工程师使用集群步骤最后 MySQL集群是一种高可用性、高性能的数据库解决方案&#xff0c;它可以通过多个…

基于Django实现的TMS物流管理系统(附源码下载)

基于Django实现的物流管理系统&#xff08;TMS&#xff0c;Transportation Management System&#xff09; 特点 前端基于Bootstrap 4框架和AdminLTE框架。使用MySQL作为数据库后端。实现了运单录入、发车出库、到货签收、客户签收等基本功能。拥有较为完善的报表功能和财务管…

Java—JDK8新特性—Lambda表达式【内含思维导图】

目录 JDK8新特性 2.Lambda表达式 思维导图 2.1 什么是Lambda表达式 2.2 为什么使用Lamdba表达式 2.3 Lambda表达式基本语法 2.4 类型推断 2.5 Lambda练习 2.6 Lambda常用场景 JDK8新特性 官网提供网址&#xff1a;JDK 8 Features 2.Lambda表达式 思维导图 2.1 什么是…

浅谈Dom和Bom(清晰易懂版)

DOM&#xff08;文档对象模型&#xff09; DOM 是浏览器提供的一种操作网页内容和结构的 API&#xff0c;它将 Web 页面表示为一个树形结构&#xff0c;其中每一个 HTML 元素都是一个节点&#xff0c;可以通过 DOM API 对其进行访问和操作。DOM API 包括了一系列方法和属性&am…

Shapes布局-文字环绕动画

文章目录 说明实现以及语法动画渐变裁切图形变换的动画效果 说明 Shapes也有形状、图形的意思&#xff0c;我们可以在页面中创建图形&#xff0c;并让内容环绕在定义的图形边上。 Shapes的官方文档&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Shapes/F…

YOLOv8 来了,快速上手实操

目录 YOLOv8的优点安装ultralytics使用YOLOv8n在图像上进行PredictTasks与 ModesModes - 模式分类Tasks - 任务分类 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;程序员半夏 , 一名全栈程序员&#xff0c;擅长使用各种编程语言和框架&#xff0c;如JavaScript、React、N…

SpringBoot集成Redis—缓存穿透解决方案与哨兵模式实战

目录 1、环境准备 1&#xff09;pom.xml引入Redis依赖 2) 演示业务场景 2、SpringBoot集成Redis单机模式 1&#xff09; 通过MyBatis逆向工程生成实体Bean和数据持久层 2) application.yml 中配置redis连接信息 3) 启动redis服务 4) XinTuProductRedisController类 5…

一图看懂 yarl 模块:为URL解析和更改提供了方便的URL类, 资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 yarl 模块&#xff1a;为URL解析和更改提供了方便的URL类, 资料整理笔记&#xff08;大全&#xff09; 摘要模块图类关系图模块全展开【yarl】统计常量模块1 yarl._quoting…

Python图形界面开发——系统资源监视器System-Monitor

Python图形界面程序怎么开发呢&#xff1f;很多人推荐python自带的tkinter自带库&#xff0c;还有pyqt这个这种拖拽式界面开发方案&#xff0c;但是他们开发界面比较难定制界面样式。现在web前端这么多框架用来开发python的图形界面其实不是很好&#xff1f;下面这么案例就是用…

Python爬虫 | 一文解决文章付费限制问题

本文概要 本篇文章主要介绍利用Python爬虫爬取付费文章&#xff0c;适合练习爬虫基础同学&#xff0c;文中描述和代码示例很详细&#xff0c;干货满满&#xff0c;感兴趣的小伙伴快来一起学习吧&#xff01; &#x1f31f;&#x1f31f;&#x1f31f;个人简介&#x1f31f;&…

项目内训(2023.5.6)

目录 Nacos是什么&#xff1f; 领域模型是什么&#xff1f; domain模块一般是干什么的&#xff1f; 在小乌龟中合并其他分支的作用是什么&#xff1f; nacos的配置文件 服务集群、服务提供、服务更加灵活庞大、消费服务、访问比较麻烦&#xff0c;A和B服务一起访问 系统结…