文章目录
- 一、SPI总线协议简介
- 二、SPI子系统驱动
- (二)SPI子系统API
- (三)SPI设备树节点
- 三、代码示例
一、SPI总线协议简介
高速、同步、全双工、非差分、总线式
传输速度在几十M
差分总线和非差分总线
非差分总线:受压降影响,通信距离短,IIC、SPI、UART
差分总线:抗干扰能力较强,传输距离相对较远,
485总线理论上可以传输1200m
DB9就是串口,有9个引脚,
四根线
四种工作模式,
MODE0和MODE3常用,
spi总线特点
SPI 是串行外设接口(Serial Peripheral Interface)的缩写。它
是 Motorola 公司推出的一种同步串行接口技术,是一种高
速的,全双工,同步的通信总线。
SPI优点:
支持全双工通信,通信简单,数据传输速率快
1):高速、同步、全双工、非差分、总线式
2):主从机通信模式
缺点:
没有指定的流控制,没有应答机制确认是否接收到数据,
所以跟IIC总线协议比较在数据的可靠性上有一定的缺陷。
spi管脚及模式
可以一主机多从机,具体和那个从机通讯通过cs片选决定。
MISO :主机输入,从机输出
MOSI :主机输出,从机输入
SCK :时钟线(只能主机控制)
CS :片选线
数据传输的四种方式:
CPOL(时钟极性) : 0:时钟起始位低电平,1:时钟起始为高电平
CPHA(时钟相位) :0:第一个时钟周期采样,1:第二个时钟周期采样
spi协议解析
CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据
采样是在第1个边沿,也就是 SCLK由低电平到高电平的跳变,
所以数据采样是在上升沿,数据发送是在下降沿。
CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据
发送是在第1个边沿,也就是 SCLK由低电平到高电平的跳变,
所以数据采样是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据
采集是在第1个边沿,也就是 SCLK由高电平到低电平的跳变,
所以数据采集是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据
发送是在第1个边沿,也就是 SCLK由高电平到低电平的跳变,
所以数据采集是在上升沿,数据发送是在下降沿。
二、SPI子系统驱动
锁存器芯片:IO口扩展,可以锁存,稳定输出
(二)SPI子系统API
(三)SPI设备树节点
spi4: spi@44005000 {
#address-cells = <1>; //对子节点描述
#size-cells = <0>;
compatible = "st,stm32h7-spi";
reg = <0x44005000 0x400>;
interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&rcc SPI4_K>;
resets = <&rcc SPI4_R>;
dmas = <&dmamux1 83 0x400 0x01>,
<&dmamux1 84 0x400 0x01>;
dma-names = "rx", "tx";
power-domains = <&pd_core>;
status = "disabled"; //1.是否使能
};
问:如何将控制器驱动和核心层选配到内核中?
将stm32mp157a中spi控制器设备树和核心层选配到内核中
spi控制器驱动配置:(make menuconfig)
Device Drivers —>
[] SPI support —>
<> STMicroelectronics STM32 SPI controller
spi核心层配置:
Device Drivers —>
[*] SPI support —>
重新编译内核
make uImage LOADADDR=0xc2000000
将编译好的内核拷贝到tftpboot目录下
cp arch/arm/boot/uImage ~/tftpboot/
2.2spi子系统API
1.分配并初始化对象
struct spi_driver {
int (*probe)(struct spi_device *spi);
//匹配成功执行的函数
int (*remove)(struct spi_device *spi);
//分离的时候执行的函数
struct device_driver driver;
//父类
const struct spi_device_id *id_table;
//idtable匹配方式
};
struct device_driver {
const char *name;
const struct of_device_id of_match_table;
}
2.注册
#define spi_register_driver(driver)
__spi_register_driver(THIS_MODULE, driver)
3.注销
void spi_unregister_driver(struct spi_driver sdrv)
4.一键注册注销的宏
module_spi_driver(变量名)
2.3spi子系统驱动实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
int m74hc595_probe(struct spi_device spi)
{
printk(“%s:%s:%d\n”, FILE, func, LINE);
return 0;
}
int m74hc595_remove(struct spi_device spi)
{
printk(“%s:%s:%d\n”, FILE, func, LINE);
return 0;
}
struct of_device_id oftable[] = {
{.compatible = “hqyj,m74hc595”,},
{}
};
struct spi_driver m74hc595 = {
.probe = m74hc595_probe,
.remove = m74hc595_remove,
.driver = {
.name = “m74hc595”,
.of_match_table = oftable,
}
};
module_spi_driver(m74hc595);
MODULE_LICENSE(“GPL”);
spi收发数据的接口
int spi_write(struct spi_device *spi, const void *buf, size_t len)
//发数据
int spi_read(struct spi_device *spi, void *buf, size_t len)
//接收数据
int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx);
//同时收发
&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 = "hqyj,m74hc595";
reg = <0x0>; //片选的下标
spi-max-frequency = <10000000>; //10MHz
//spi-cpol; //mode0模式
//spi-cpha;
};
};
三、代码示例
数码管驱动
#include <linux/module.h>
#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/fs.h>
#include "m74hc595.h"
#define CHRNAME "m74hc595"
int major;
struct class *cls;
struct device *dev;
struct spi_device * spidriver;
int code[]={
0x3f,
0x06,
0x5b,
0x4f,
0x66,
0x6d,
0x7d,
0x07,
0x7f,
0x6f,
0x77,
0x7c,
0x39,
0x5e,
0x79,
0x71
};
int m74hc595_open(struct inode *inode, struct file *file){
printk("%s:%d\n",__func__,__LINE__);
return 0;
}
int m74hc595_close(struct inode *inode, struct file *file){
printk("%s:%d\n",__func__,__LINE__);
return 0;
}
long m74hc595_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
u8 data[2]={0};
int ret;
switch (cmd){
case SET_LIGHT_ONE:
data[0]=0x01;
data[1]=code[arg];
ret = spi_write(spidriver,data,2);
if(ret){
pr_err("spi_write error:%d\n",__LINE__);
return -ENAVAIL;
}
break;
case SET_LIGHT_TWO:
data[0]=0x2;
data[1]=code[arg]|0x80;
ret = spi_write(spidriver,data,2);
if(ret){
pr_err("spi_write error:%d\n",__LINE__);
return -ENAVAIL;
}
break;
case SET_LIGHT_THREE:
data[0]=0x4;
data[1]=code[arg];
ret = spi_write(spidriver,data,2);
if(ret){
pr_err("spi_write error:%d\n",__LINE__);
return -ENAVAIL;
}
break;
case SET_LIGHT_FOUR:
data[0]=0x8;
data[1]=code[arg];
ret = spi_write(spidriver,data,2);
if(ret){
pr_err("spi_write error:%d\n",__LINE__);
return -ENAVAIL;
}
break;
default:
pr_err("cmd error\n");
return -ENAVAIL;
}
return 0;
}
struct file_operations fops = {
.open=m74hc595_open,
.release=m74hc595_close,
.unlocked_ioctl=m74hc595_ioctl,
};
int m74hc595_probe(struct spi_device *spi){
printk("%s:%d\n",__func__,__LINE__);
spidriver = spi;
// 1.注册字符设备驱动
major = register_chrdev(0, CHRNAME, &fops);
if (major < 0)
{
pr_err("register_chrdev error\n");
return major;
}
// 2.自动创建设备节点
cls = class_create(THIS_MODULE, CHRNAME);
if (IS_ERR(cls))
{
pr_err("class_create error\n");
unregister_chrdev(major, CHRNAME);
return PTR_ERR(cls);
}
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CHRNAME);
if (IS_ERR(dev))
{
pr_err("device_create error\n");
class_destroy(cls);
unregister_chrdev(major, CHRNAME);
return PTR_ERR(dev);
}
return 0;
}
int m74hc595_remove(struct spi_device *spi){
printk("%s:%d\n",__func__,__LINE__);
device_destroy(cls,MKDEV(major,0));
class_destroy(cls);
unregister_chrdev(major, CHRNAME);
return 0;
}
struct of_device_id m74hc595_of_match_table[]={
{ .compatible="hqyj,m74hc595" },
{},
};
struct spi_driver m74hc595_driver={
.probe=m74hc595_probe,
.remove=m74hc595_remove,
.driver={
.name="m74hc595",
.of_match_table=m74hc595_of_match_table,
}
};
module_spi_driver(m74hc595_driver);
MODULE_LICENSE("GPL");