当前位置: 首页 > news >正文

Linux驱动学习-spi接口

SPI驱动

SPI全称更是serial Peripheral interface的缩写,由美国摩托罗拉公司推出的一种同步串行传输规范。

1、特点

(1)SPI是一种高速,全双工,同步串行总线。

(2)SPI由主从两种模式,通常由一个主设备或者多个从设备组从。SPI不支持多主控i,

(3)SPI通信至少需要四根线,分别是MISO(主设备数据输入,从设备输出),MOSI(主设备数据输出从设备输入),SCLK(时钟信号),CS/SS(片选信号)

2、硬件连接

3、SPI通信原理

SPI主设备和从设备都有一个串行移位寄存器,主设备通过向SPI串行寄存器写入一个字节来发起一次数据传输。

举例:(可设置,默认先传输高位)

4、通信过程

5、SPI极性和相位

6、SPI子系统框架

绝大多数情况下,你只需要使用SPI核心层提供的API函数来编写你的具体外设驱动,而SPI控制器设备驱动层(你所说的“芯片原厂实现的部分”)通常已经由芯片厂商或主线Linux内核提供了。

当然,设备驱动层也可以学习一下怎么写的

6.1 spi外设代码框架编写:device部分

在没有设备树之前,是要用c代码的方式实现device部分的,非常麻烦,有设备树之后直接写就行:

6.2 spi外设代码框架编写:driver部分

#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
​
int mcp2515_probe(struct spi_device *spi)
{printk("This is mcp2515_probe\n");​return 0;
​
}
​
int mcp2515_remove(struct spi_device *spi)
{printk("This is mcp2515_remove\n");​return 0;
​
}
​
const struct of_device_id mcp2515_of_match_table[] = {{.compatible = "my-mcp2515"),{}
};
​
const struct spi_device_id mcp2515_id_table[] = {{"mcp2515"),{}
};
​
struct spi_driver spi_mcp2515 = {.probe = mcp2515_probe,.remove = mcp2515_remove,.driver = {.name = "mcp2515",.owner = THIS_MODULE,.of_match_table = mcp2515_of_match_table,},.id_table = mcp2515_id_table
};
​
static int __init mcp2515_init(void)
{int ret;ret = spi_register_driver(&spi_mcp2515);if (ret < 0) {printk("spi_register_driver error\n");return ret;}return ret;
}
​
static int __exit mcp2515_exit(void)
{int ret;ret = spi_unregister_driver(&spi_mcp2515);if (ret < 0) {printk("spi_unregister_driver error\n");return ret;}return ret;
}
​
module_init(mcp2515_init);
module_exit(mcp2515_exit);
MODULE_LICENSE("GPL");

下面我们通过追踪一下代码来看一下为什么没有进probe函数:

1、当spi的设备和驱动匹配成功会执行下面这个prboe函数

2、在这个prboe函数中,这段会注册一个spi控制器。

3、在spi_register_controller函数中调用了下面这个,会对设备树的属性进行提取

4、在of_register_spi_device函数中,看到下面函数:

5、在of_spi_parse_dt函数中,可以看到如果子节点一些属性没写,就会报错返回,所以就进不了probe函数了:

6、总结

编译后没有进probe函数,是因为dts没有添加相应属性,return掉了

所以dts应该配置如下:

7、实验硬件:mcp2515介绍

先了解一下mcp2515。

8、mcp2515驱动编写:注册字符设备

前面已经可以匹配进probe函数了,接下面就是完善驱动程序,可以在驱动程序注册一个字符设备,创建相应的设备节点

#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
​
dev_t dev_num;
struct cdev mcp2515_cdev;
struct class *mcp2515_class;
struct device *mcp2515_device;
​
int mcp2515_open(strct inode, struct file *file)
{return 0;
}
​
ssize_t mcp2515_read(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{return 0;
}
​
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{return 0;
}
​
ssize_t mcp2515_relese(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{return 0;
}
​
struct file_operations mcp2515_fops = {.open = mcp2515_open,.read = mcp2515_read,.write = mcp2515_write,.relese = mcp2515_relese,
};
​
int mcp2515_probe(struct spi_device *spi)
{int ret;printk("This is mcp2515_probe\n");ret = alloc_chrdev_region(&dev_num, 0, 1, "mcp2515");if (ret < 0) {printk("alloc_chrdev_region\n");return -1;}cdev_init(&mcp2515_cdev, &mcp2515_fops);mcp2515_cdev.owner = THIS_MODULE;ret = cdev_add(&mcp2515_cdev, dev_num, 1);if (ret < 0) {printk("cdev_add error\n");return -1;}mcp2515_class = class_create(THIS_MODULE, "spi_to_can");if (IS_ERR(mcp2515_class)) {printk("class_create error\n");return PTR_ERR(mcp2515_class);}mcp2515_device = device_create(mcp2515_class, NULL, dev_num, NULL, "mcp2515");if (IS_ERR(mcp2515_device)) {printk("device_create error\n");return PTR_ERR(mcp2515_device);}return 0;
}
​
int mcp2515_remove(struct spi_device *spi)
{printk("This is mcp2515_remove\n");return 0;
}
​
const struct of_device_id mcp2515_of_match_table[] = {{.compatible = "my-mcp2515"),{}
};
​
const struct spi_device_id mcp2515_id_table[] = {{"mcp2515"),{}
};
​
struct spi_driver spi_mcp2515 = {.probe = mcp2515_probe,.remove = mcp2515_remove,.driver = {.name = "mcp2515",.owner = THIS_MODULE,.of_match_table = mcp2515_of_match_table,},.id_table = mcp2515_id_table
};
​
static int __init mcp2515_init(void)
{int ret;ret = spi_register_driver(&spi_mcp2515);if (ret < 0) {printk("spi_register_driver error\n");return ret;}return ret;
}
​
static void __exit mcp2515_exit(void)
{device_destory(mcp2515_class, dev_num);class_destory(mcp2515_class);cdev_del(&mcp2515_dev);unregister_chrdev_region(dev_num, 1);spi_unregister_driver(&spi_mcp2515);
​
}
​
module_init(mcp2515_init);
module_exit(mcp2515_exit);
MODULE_LICENSE("GPL");

​
​

9、mcp2515驱动编写:复位函数

下面继续对上面的驱动进行完善:

9.1 配置dts:

上图红线内就是配置spi的模式,如果两个都没有注释,就是1 1,否则注释了就是0 0

上图红框内如果没有注释则代表spi从低位开始传输,注释了则代表从高位开始传输。

上图红框内如果没有注释则代表spi的cs片选脚是高电平选中,注释了则代表spi的cs片选脚是低电平选中。

最终配置:

2、添加复位函数

10、探究:SPI的通信流程

11、mcp2515驱动编写:读寄存器函数

#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
​
dev_t dev_num;
struct cdev mcp2515_cdev;
struct class *mcp2515_class;
struct device *mcp2515_device;
​
struct spi_device *spi_dev;
​
int mcp2515_open(strct inode, struct file *file)
{return 0;
}
​
ssize_t mcp2515_read(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{return 0;
}
​
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{return 0;
}
​
ssize_t mcp2515_relese(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{return 0;
}
​
struct file_operations mcp2515_fops = {.open = mcp2515_open,.read = mcp2515_read,.write = mcp2515_write,.relese = mcp2515_relese,
};
​
void mcp2515_reset(void)
{int ret;char write_buf[] = {0xc0};ret = spi_write(spi_dev, write_buf, sizeof(write_buf));if (ret < 0) {printk("spi_write is error\n");}
}
​
void mcp2515_read_reg(char reg) {char write_buf[] = {0x03, reg};char read_buf;int ret;ret = spi_write_then_read(spi_dev, write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf));if (ret < 0) {printk("spi_write_then_read error\n");return ret;}return read_buf;
}
​
int mcp2515_probe(struct spi_device *spi)
{int ret;char value;printk("This is mcp2515_probe\n");spi_dev = spi;ret = alloc_chrdev_region(&dev_num, 0, 1, "mcp2515");if (ret < 0) {printk("alloc_chrdev_region\n");return -1;}cdev_init(&mcp2515_cdev, &mcp2515_fops);mcp2515_cdev.owner = THIS_MODULE;ret = cdev_add(&mcp2515_cdev, dev_num, 1);if (ret < 0) {printk("cdev_add error\n");return -1;}mcp2515_class = class_create(THIS_MODULE, "spi_to_can");if (IS_ERR(mcp2515_class)) {printk("class_create error\n");return PTR_ERR(mcp2515_class);}mcp2515_device = device_create(mcp2515_class, NULL, dev_num, NULL, "mcp2515");if (IS_ERR(mcp2515_device)) {printk("device_create error\n");return PTR_ERR(mcp2515_device);}mcp2515_reset();value = mcp2515_read_reg(0x0e);printk("value is %x\n", vlaue);return 0;
}
​
int mcp2515_remove(struct spi_device *spi)
{printk("This is mcp2515_remove\n");return 0;
}
​
const struct of_device_id mcp2515_of_match_table[] = {{.compatible = "my-mcp2515"),{}
};
​
const struct spi_device_id mcp2515_id_table[] = {{"mcp2515"),{}
};
​
struct spi_driver spi_mcp2515 = {.probe = mcp2515_probe,.remove = mcp2515_remove,.driver = {.name = "mcp2515",.owner = THIS_MODULE,.of_match_table = mcp2515_of_match_table,},.id_table = mcp2515_id_table
};
​
static int __init mcp2515_init(void)
{int ret;ret = spi_register_driver(&spi_mcp2515);if (ret < 0) {printk("spi_register_driver error\n");return ret;}return ret;
}
​
static void __exit mcp2515_exit(void)
{device_destory(mcp2515_class, dev_num);class_destory(mcp2515_class);cdev_del(&mcp2515_dev);unregister_chrdev_region(dev_num, 1);spi_unregister_driver(&spi_mcp2515);
​
}
​
module_init(mcp2515_init);
module_exit(mcp2515_exit);
MODULE_LICENSE("GPL");
0x80就是处于配置模式。

12、配置cnf1 cnf2 cnf3等寄存器

12.1 分析

12.2 驱动代码

13、配置RXB0CTRL CANINTE寄存器(使用位修改指令和屏蔽字节)

14、mcp2515驱动编写:修改工作模式

让其进入回环模式。

0x40就是进入回环模式:

15、mcp2515驱动编写:编写write函数

16、mcp2515驱动编写:编写read函数

17、编写测试APP

18、Linux中通用SPI驱动以及spidev的使用

19、应用程序中如何使用SPI

参考18小节的工具spi_test源码修改,当然也要配置好相应的通用驱动:

20、如何在Linux中使用模拟SPI

21、实践:移植官方mcp2515驱动

http://www.dtcms.com/a/356974.html

相关文章:

  • 手写一个Spring框架
  • 【树论】树上启发式合并
  • ansible的playbook练习题
  • 短剧小程序系统开发:助力影视行业数字化转型
  • 算法---字符串
  • Speculation Rules API
  • PDF转图片工具实现
  • 天气查询系统
  • 2025_WSL2_Ubuntu20.04_C++20_concept 环境配置
  • el-select多选下拉框出现了e611
  • MySQL 中ORDER BY排序规则
  • 物联网平台中的Swagger(二)安全认证与生产实践
  • Socket编程核心API与结构解析
  • 【C++】掌握类模板:多参数实战技巧
  • 构筑沉浸式3D世界:渲染、资源与体验的协同之道
  • 云计算学习笔记——逻辑卷管理、进程管理、用户提权RAID篇
  • N32G43x Flash 驱动移植与封装实践
  • DBeaver 的 PostgreSQL 驱动包默认存储位置
  • 序列化和反序列的学习
  • 移动社交时代电商流量获取新路径:基于社群与开源AI智能名片链动2+1模式S2B2C商城小程序的探索
  • 【基础-单选】关于Button组件,下面哪个样式是胶囊型按钮
  • 大模型之RAG, 检索增强生成
  • 【若依】RuoYi-Vue-springboot3分离版
  • RS485、RS232、RS422协议
  • 浔川代码编辑器v2.1.0公测版上线时间公告
  • 基于FPGA的DDR3读写实验学习
  • LeetCode算法日记 - Day 26: 归并排序、交易逆序对的总数
  • 河南葱香鸡蛋,嫩滑香浓超棒!
  • 企业微信如何设置长期有效的获客二维码?3步生成!
  • 机器人视觉检测