文章目录
- 一、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");