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源码修改,当然也要配置好相应的通用驱动: