链接:https://pan.baidu.com/s/1g8jkENjO8v4eXq0bN0acEw?pwd=45c8
提取码:45c8
目录
一、 什么是SPI
1-1 SPI简介
SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器等芯片,还有数字信号处理器和数字信号解码器之间。
1-2 SPI的特点
SPI,是一种高速的,同步的,全双工,主从式接口通信总线,SPI接口可以是3线式或4线式,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。
1-2-1 同步和异步
同步:发送方和接收方拥有相同的时钟基准,在SPI里时钟基准是由主机提供的。比如IIC,SPI。
异步:发送方和接收方时钟独立,明显的特征是它的总线没有时钟线,像我们常用的串口通信。
SPI是同步通信,因为他具有时钟线。
1-2-2 单工和双工
单工:数据只能单向传输;比如数据可以从A传输到B,但是不能从B传输到A;
双工:数据允许双向传输;比如数据可以从A传输到B,也可以从B传输到A,双工又可以分为全双工和半双工;
全双工:同一时刻数据可以双向传输,也就是说有,在同一时刻数据既有从A发往B的,又有 从B发往A的,如SPI,UART;
半双工:同一时刻只能单向传输,如IIC,比如IIC只有两根线,一根时钟线先,一根数据线,一个设备要发送和接收数据时,只能等待发送完成或者接收完成,才能开始接收或者发送;
1-2-3 主从式结构
一般运用于一个设备挂载多个设备才有这个概念,下文会提到。
二、SPI硬件认识
SPI主从模式硬件连接如下图:
图1 含主机和从机的SPI配置
SPI的通信原理很简单,它以主从方式工作,产生时钟信号的器件称为主机。主机和从机之间传输的数据与主机产生的时钟同步。这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。SPI的四条通讯线是MOSI(数据输入),MISO(数据输出),SCLK(时钟),SS/CS(片选)。
四条逻辑线
SPI总线包括4条逻辑线,定义如下:
MISO:主机输入,从机输出(数据来自从机)
MOSI: 主机输出,从机输入(数据来自主机)
SCLK: 串行时钟信号,由主机产生发送给从机
SS/CS:从设备使能信号,由主设备控制。 片选信号,由主机发送,以控制与哪个从机通信,通常是低电平有效信号。
其他制造商可能会遵循其他命名规则,但是最终他们指的相同的含义。以下是一些常用术语;
MISO:也可以是SIMO,DOUT,DO,SDO或SO(在主机端);
MOSI:也可以是SOMI,DIN,DI,SDI或SI(在主机端);
SCLK:也可以是SCK,SCL;
SS/CS:也可以是CE,或SSEL;
注意:MISO 是主机的输入,从机的输出,MOSI是主机的输出,从机的输入。
其中,SS/CS是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个SPI设备成为可能,如图2:
一个主机连接了3个从机设备,而主机通过3路独立的SS片选线连接到3个从机设备,假设3个从机的片选信号为低电位使能,也就是当你要用到这个从机时,你要把它的SS置0,这样就可以向使能的从机设备发送或接收数据了。
如下:(符号-:表示低电平有效)
图2 一个主机多个从机的常规接法
要注意的是,SCLK(时钟信号)信号线只由主设备控制,从设备不能控制信号线。同样,在一个基于SPI的设备中,至少有一个主控设备。
这样传输的特点:
这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据,也就是说,主设备通过对SCK时钟线的控制可以完成对通讯的控制。
SPI还是一个数据交换协议,因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C系统要稍微复杂一些。当然了,因为SPI从设备没有地址区分,这样也存在了一个弊端,那就是没有指定的流控制,没有应答机制确认是否接收到数据。
三、SPI时序
3-1 时钟极性和时钟相位
在SPI中,主机可以选择时钟极性和时钟相位。在空闲状态期间,CPOL位设置时钟信号的极性。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。CPHA位选择时钟相位。根据CPHA位的状态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用。下表显示了这4种SPI模式。
CPOL | 时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平 |
CPHA | 时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样 |
表1
模式 | CPOL&CPHA | 介绍 |
Mode 0 | CPOL=0;CPHA=0 | SCLK 空闲是为低电平,数据在上升沿有效 |
Mode 1 | CPOL=0;CPHA=1 | SCLK 空闲是为低电平,数据在下降沿有效 |
Mode 2 | CPOL=1;CPHA=0 | SCLK 空闲是为高电平,数据在下降沿有效 |
Mode 3 | CPOL=1;CPHA=1 | SCLK 空闲是为高电平,数据在上升沿有效 |
表2
3-2 四种工作模式
4种工作模式波形时序如下图:
3-2-1 Mode 0:
图3 Mode 0
数据必须在第一个时钟信号上升之前可用。时钟空闲状态为零。当时钟处于高位时,MISO和MOSI线路上的数据必须稳定,当时钟处于低位时,数据可以更改。数据在时钟的低到高转换时捕获,并在高到低时钟转换时传播。在MODE0模式下,时钟在空闲时始终置0,每产生一次上升沿便会发送1 bit 数据。
3-2-2 Mode 1:
图4 Mode 1
第一个时钟信号上升可用于准备数据。时钟空闲状态为零。当时钟低位时,MISO和MOSI线路上的数据必须稳定,当时钟高位时,数据可以更改。数据在时钟的高-低转换上捕获,并在低-高时钟转换上传播。时钟在空闲时始终置0,每产生一次下降沿便会发送1 bit 数据。
3-2-3 Mode 2:
图5 Mode 2
数据必须在第一个时钟信号下降之前可用。时钟空闲状态为1。当时钟低位时,MISO和MOSI线路上的数据必须稳定,当时钟高位时,数据可以更改。数据在时钟的高-低转换上捕获,并在低-高时钟转换上传播。时钟在空闲时始终置1,每产生一次下降沿便会发送1 bit 数据。
3-2-4 Mode 3:
图6 Mode 3
第一个时钟信号下降可用于准备数据。时钟空闲状态为1。当时钟处于高位时,MISO和MOSI线路上的数据必须稳定,当时钟处于低位时,数据可以更改。数据在时钟的低到高转换时捕获,并在高到低时钟转换时传播。时钟在空闲时始终置1,每产生一次上升沿便会发送1 bit 数据。
比方说Mode0,时钟空闲状态为零,即SCLK=0;当SCLK=0时,MISO和MOSO的电位可改变,上面的内容说到:MISO是主机输入,从机输出(数据来自从机),可理解为主机读数据;MOSI是主机输出,从机输入(数据来自主机),可理解为主机向从机写命令;这样,在至少8次时钟信号的改变(上沿和下沿为一次),就可以完成8位数据的传输。
注意事项:
在主设备配置SPI接口时钟的时候一定要弄清楚从设备的时钟要求,因为主设备的时钟极性和相位都是以从设备为基准的。因此在时钟极性的配置上一定要搞清楚从设备是在时钟的上升沿还是下降沿接收数据,是在时钟的下降沿还是上升沿输出数据。
3-3 模式选择说明
SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。
SPI分主设备和从设备,两者通过SPI协议通讯。而设置SPI的模式,是从设备的模式,决定了主设备的模式。所以要先去搞懂从设备的SPI是何种模式,然后再将主设备的SPI的模式,设置和从设备相同的模式,即可正常通讯。
对于从设备的SPI是什么模式,有两种:
(1)固定的,有SPI从设备硬件决定的
SPI从设备,具体是什么模式,相关的datasheet中会有描述,需要自己去datasheet中找到相关的描述,即:
关于SPI从设备,在空闲的时候,是高电平还是低电平,即决定了CPOL是0还是1;
然后再找到关于设备是在上升沿还是下降沿去采样数据,这样就是,在定了CPOL的值的前提下,对应着可以推算出CPHA是0还是1了。
举例:
SSD1289 - 240 RGB x 320 TFT LCD Controller Driver的datasheet中提到:
“SDI is shifted into 8-bit shift register on every rising edge of SCK in the order of data bit 7, data bit 6 …… data bit 0.”
意思是,数据是在上升沿采样,所以可以断定是CPOL=0,CPHA=0,或者CPOL=1,CPHA=1的模式,但是至于是哪种模式,按理来说,接下来应该再去确定SCLK空闲时候是高电平还是低电平,用以确定CPOL是0还是1,但是datasheet中没有提到这点。
所以,此处,目前不太确定,是两种模式都支持,还是需要额外找证据却确定CPOL是0还是1.
(2)可配置的,由软件自己设定
从设备也是一个SPI控制器,4种模式都支持,此时只要自己设置为某种模式即可。
然后知道了从设备的模式后,再去将SPI主设备的模式,设置为和从设备模式一样,即可。
多数都是直接去写对应的SPI控制器中对应寄存器中的CPOL和CPHA那两位,写0或写1即可。SPI工作模式选择是根据外设的工作方式,如果外设使用上升沿采样,SPI就是用1和3工作模式
四、SPI协议软件模拟和撰写
对于不带SPI串行总线接口的AT89C52单片机来说,可以使用软件来模拟SPI的操作,接下来介绍利用单片机的I/O口通过软件模拟SPI总线驱动的实现方法。
下面我们通过软件模拟SPI来实现两个单片机的通信。
所需:
两个51单片机开发板(可以一样的开发板,也可以两个不同的开发板,我使用的两个开发板都是实验室的版子,也会用到仿真)、杜邦线
实现的基本功能:开发板1的按键控制开发板2的led等亮灭,实验功能很简单,但涉及两个单片机的通信,有助于理解和掌握SPI协议。
实验步骤:
4-1 首先定义好I/O口(可自定义)
sbit SDO = P2^0; //位定义,主机输入,从机输出(数据来自从机);
sbit SDI = P2^1; //位定义,主机输出,从机输入(数据来自主机);
sbit SCK = P2^2; //位定义时钟
sbit CS = P2^3; //位定义片选(使能,CS引脚为0时主从方可互相通信)
4-2 SPI部分发送数据/命令函数:
/*-----5微秒延时函数-----*/
void delay5us()
{
_nop_();
}
//CPHA=0;CPOL=1 模式2,时钟在空闲时始终置1,每产生一次下降沿便会发送1 bit 数据。
//SPI发送函数
//上升沿发送
void Spi_Send(unsigned char dat1)
{
unsigned char i;
for (i=0; i<8; ++i) //8bit,一位一位写
{
SCK = 0; //下降沿发送数据
if (dat1 & 0x80) //判断当前最高位为1还是0对应的把SDI拉高拉低
{
SDI=1;
}
else
{
SDI=0;
}
SCK = 1; //上升沿的时候开始可以改变数据
dat1 <<= 1;
delay5us();
}
}
大家写的时候要根据时序图来写,加以理解。
4-3 读数据函数:
//SPI接收函数
//下降沿接收
unsigned char SpiReceive()
{
unsigned char i, dat0;
dat0 = 0x00; //dat0初始化
for (i=0; i<8; ++i)//8bit,一位一位读
{
dat0 <<= 1;//第一个接收的对应发送过来的最高位
while(SCK == 1);//等待下降沿,下降沿读取数据
while(SCK == 0);//下降沿读取数据
dat0 |= SDO; //每次存入dat0的最低位
}
return (dat0);//收到数据(返回值)dat0
}
到目前为止,有关SPI协议的函数就写完了,无非就是发送和接收数据。在编写和理解的过程要根据上文讲到的模式二的时序图,空闲状态和高低电平的变化。
根据我们要实现的功能,我们要实现两个单片机的通信,那么就必须确认一个单片机为主机,另一个为从机。
首先编写主机的主函数,这个单片机要实现的功能是通过按键信号SPI通信发送数据:
4-4 主函数:
#include<reg52.h>
#include"SPI.h"
sbit s1=P3^4; //这个是我的按键
sbit led=P1^2; //led灯
//按键消抖延时函数
void delay (unsigned int ms)
{
unsigned char i,j;
while(ms--)
{
i=2;
j=239;
do
{
while(--j);
}while(--i);
}
}
//这个是主机的函数,功能是通过按键发送0xAA和0xA5
void main()
{
unsigned char n=1;
CS=0; //这步是关键的,如果未使能,不能通信,CS引脚为0时主从方可互相通信
while(1)
{
if(s1==0)
{
delay(10);
while(s1==0);
delay(10);
led=~led;//这个是验证按键功能是否实现的
n=~n;//按键按下会改变n值,达到发送两个数据的目的
if(n==1)
{
Spi_Send(0xAA);//向从机发送数据0xAA
}
else
{
Spi_Send(0xA5);//向从机发送数据0xA5
}
}
}
}
这样主机的功能就实现了,通过按键s1(P3^4口,每个开发板设计的按键不一定是这个,要根据每个板子的原理图选择IO口)按下发送0xAA/0xA5这两个命令给从机,从机可根据这两个命令的判断实现不同的功能,在这里,为了实验的简便,我设计了最简单的点灯/灭灯功能,当从机接收到0xAA时,led(P1^0)点亮,0xA5熄灭。如下:
#include<reg52.h>
#include"SPI.h"
sbit led2=P1^0;
void main()
{
unsigned char dat;
CS=0;//同样的,从机也要使能CS
while(1)
{
dat=SpiReceive();//读取接收到的数据
if(dat==0xAA)
{
led2=0;
}
if(dat==0xA5)
{
led2=1;
}
}
}
以上代码我是有封装好的,下面是放在一个main.c文件里的全部函数,可供复制粘贴,注意要根据每个实验开发板的原理图定义好IO口。
#include <reg52.h>
#include <intrins.h>
sbit s1=P3^4; //这个是我的按键
sbit led=P1^2; //主机led灯
sbit led2=P1^0;//从机led
sbit SDO = P2^0; //位定义,主机输入,从机输出(数据来自从机);
sbit SDI = P2^1; //位定义,主机输出,从机输入(数据来自主机);
sbit SCK = P2^2; //位定义时钟
sbit CS = P2^3; //位定义片选(使能,CS引脚为0时主从方可互相通信)
void delay5us()
{
_nop_();
}
//CPHA=0;CPOL=1 模式2,时钟在空闲时始终置1,每产生一次下降沿便会发送1 bit 数据。
//SPI发送函数
//上升沿发送
void Spi_Send(unsigned char dat1)
{
unsigned char i;
for (i=0; i<8; ++i) //8bit,一位一位写
{
SCK = 0; //下降沿发送数据
if (dat1 & 0x80) //判断当前最高位为1还是0对应的把SDI拉高拉低
{
SDI=1;
}
else
{
SDI=0;
}
SCK = 1; //上升沿的时候开始可以改变数据
dat1 <<= 1;
delay5us();
}
}
//SPI接收函数
//下降沿接收
unsigned char SpiReceive()
{
unsigned char i, dat0;
dat0 = 0x00; //dat0初始化
for (i=0; i<8; ++i)//8bit,一位一位读
{
dat0 <<= 1;//第一个接收的对应发送过来的最高位
while(SCK == 1);//等待下降沿,下降沿读取数据
while(SCK == 0);//下降沿读取数据
dat0 |= SDO; //每次存入dat0的最低位
}
return (dat0);//收到数据(返回值)dat0
}
//按键消抖延时函数
void delay (unsigned int ms)
{
unsigned char i,j;
while(ms--)
{
i=2;
j=239;
do
{
while(--j);
}while(--i);
}
}
//这个是主机的函数,功能是通过按键发送0xAA和0xA5
void main()
{
unsigned char n=1;
CS=0; //这步是关键的,如果未使能,不能通信,CS引脚为0时主从方可互相通信
while(1)
{
if(s1==0)
{
delay(10);
while(s1==0);
delay(10);
led=~led;//这个是验证按键功能是否实现的
n=~n;//按键按下会改变n值,达到发送两个数据的目的
if(n==1)
{
Spi_Send(0xAA);//向从机发送数据0xAA
}
else
{
Spi_Send(0xA5);//向从机发送数据0xA5
}
}
}
}
//从机主函数,烧入第二个单片机时将上一个注释掉
//void main()
//{
// unsigned char dat;
// CS=0;//同样的,从机也要使能CS
// while(1)
// {
// dat=SpiReceive();//读取接收到的数据
// if(dat==0xAA)
// {
// led2=0;
// }
// if(dat==0xA5)
// {
// led2=1;
// }
// }
//}
特别注意的,代码中有两个主函数,一个是主机的,一个是从机的,烧入对应的代码时,注释掉另一个主函数,运行警告是因为有函数未被调用。在这里片选数据一直被拉低,所以会一直写和读,为了不必要的写读,发送完一组数据,将片选拉高,停止写入 。
4-5 接线
图7 接线图
主机的SDO接从机的SDI(这点要注意)
主机的SCK接从机的SCK,CS接CS,在对两个单片机供电就好了。
达到的实验效果就是按下按键可以控制另一个单片机的亮灭。
4-6 效果图
按下主机按键前状态:
图8 主机按键未按下
按下后:
图9 主机按键按下
4-7 仿真效果图
图10 仿真效果图
到这里,SPI的内容就介绍完了,加上对代码的理解和功能的实现希望对SPI的学习和理解有所帮助。