通信协议——SPI总线
一、SPI总线基础概述
SPI系统是一种高性能的全双工同步通信总线,并仅使用四根管脚线路即可实现数据传输的具体包括
1)、MOSI——Master Output Slave Input主机输出从机输入
2)、MISO——Master Input Slave Output主机输入从机输出
3)SCLK——时钟信号,由主机产生
4)、CS——从机使能信号(片选信号),由主机控制
SPI通信协议的信息以逐位的方式进行发送/接收,并需要注意的是:SPI总线采用的是一个环形结构,在SCLK调控下两个双向移位寄存器进行数据交换。具体而言,在主机向从机发送一个数据包时也会同步接收到一个反馈数据包,并确保双方能够完成一次完整的移位操作;其环形通信过程如下所述:当主机向从机发送一位数据时会即时接收到从机的一位响应信号;同时在每次完整的循环往复中都能保证双方信息的有效同步与互换

二、SPI总线的四种工作方式
SPI模块用于与外设进行数据交换。根据外设的工作要求,在其输出端的串行同步时钟极性的设置以及相位设置可进行参数配置。具体配置如下:
CPOL用于控制SCK时钟信号在低电平状态下运行(即空闲阶段)。
1)、CPOL=0,空闲电平为低电平
2)、CPOL=1时,空闲电平为高电平
2.CPHA用来决定采样时刻的
1)、CPHA=0,在每个周期的第一个时钟沿采样
2)、CPHA=1,在每个周期的第二个时钟沿采样
通过上述CPOL和CPHA的两两搭配组合出了四种工作模式,可看下图所示

需要特别关注的是SPI主机与从机之间的信号线连接配置:主机的MOSI与从机的MOSI相连、MISO与从机的MISO相连,并需详细说明主机所使用的时钟波形(包括相位与极性)是如何根据从机的要求进行设置的。具体而言,在使用单片机芯片实现此类通信模式时,请明确以下几点:一是确定从机是通过时钟上升沿还是下降沿接收数据信号;二是了解在发送数据信号时应采用时钟下降沿还是上升沿的方式输出数据信号。这些配置细节均需参考芯片的数据手册中所描述的具体实现模式。
三、SPI四种模式下的发送接收程序
/******************************************************************************* * 函 数 名 : SPI_Send(unsigned char dat)
* 函数功能 : 主机发送一个数据
* 输 入 : 主机要发送的数据
* 输 出 : 无
* 备 注 : 模式0:CPL0 = 0 CPHA = 0
*******************************************************************************/
void SPI_Send(unsigned char dat)
{
unsigned char n;
for(n=0;n<8;n++)
{
SCK = 0;
if(dat&0x80)
MOSI = 1;
else
MOSI = 0;
dat<<=1;
SCK = 1;
}
SCK = 0;
}
/******************************************************************************* * 函 数 名 : unsigned char SPI_Receive(void)
* 函数功能 : 主机接收一个数据
* 输 入 : 无
* 输 出 : 主机接收的数据
* 备 注 : 模式0:CPL0 = 0 CPHA = 0
*******************************************************************************/
unsigned char SPI_Receive(void)
{
unsigned char n;
unsigned char dat;
for(n = 0; n < 8; n++)
{
SCK = 0;
dat <<= 1;
if(MISO)
dat = dat | 0x01;
else
dat = dat & 0xfe;
SCK = 1;
}
SCK = 0;
return dat;
}
/******************************************************************************* * 函 数 名 : SPI_Send(unsigned char dat)
* 函数功能 : 主机发送一个数据
* 输 入 : 主机要发送的数据
* 输 出 : 无
* 备 注 : 模式0:CPL0 = 0 CPHA = 1
*******************************************************************************/
void SPI_Send(unsigned char dat)
{
unsigned char n;
SCK = 0;
for(n=0;n<8;n++)
{
SCK = 1;
if(dat&0x80)
MOSI = 1;
else
MOSI = 0;
dat<<=1;
SCK = 0;
}
}
/******************************************************************************* * 函 数 名 : unsigned char SPI_Receive(void)
* 函数功能 : 主机接收一个数据
* 输 入 : 无
* 输 出 : 主机接收的数据
* 备 注 : 模式0:CPL0 = 0 CPHA = 1
*******************************************************************************/
unsigned char SPI_Receive(void)
{
unsigned char n;
unsigned char dat;
SCK = 0;
for(n = 0; n < 8; n++)
{
SCK = 1;
dat <<= 1;
if(MISO)
dat = dat | 0x01;
else
dat = dat & 0xfe;
SCK = 0;
}
return dat;
}
/******************************************************************************* * 函 数 名 : SPI_Send(unsigned char dat)
* 函数功能 : 主机发送一个数据
* 输 入 : 主机要发送的数据
* 输 出 : 无
* 备 注 : 模式0:CPL0 = 1 CPHA = 0
*******************************************************************************/
void SPI_Send(unsigned char dat)
{
unsigned char n;
for(n=0;n<8;n++)
{
SCK = 1;
if(dat&0x80)
MOSI = 1;
else
MOSI = 0;
dat<<=1;
SCK = 0;
}
SCK = 1;
}
/******************************************************************************* * 函 数 名 : unsigned char SPI_Receive(void)
* 函数功能 : 主机接收一个数据
* 输 入 : 无
* 输 出 : 主机接收的数据
* 备 注 : 模式0:CPL0 = 1 CPHA = 0
*******************************************************************************/
unsigned char SPI_Receive(void)
{
unsigned char n;
unsigned char dat;
for(n = 0; n < 8; n++)
{
SCK = 1;
dat <<= 1;
if(MISO)
dat = dat | 0x01;
else
dat = dat & 0xfe;
SCK = 0;
}
SCK = 1;
return dat;
}
/******************************************************************************* * 函 数 名 : SPI_Send(unsigned char dat)
* 函数功能 : 主机发送一个数据
* 输 入 : 主机要发送的数据
* 输 出 : 无
* 备 注 : 模式0:CPL0 = 1 CPHA = 1
*******************************************************************************/
void SPI_Send(unsigned char dat)
{
unsigned char n;
SCK = 1;
for(n=0;n<8;n++)
{
SCK = 0;
if(dat&0x80)
MOSI = 1;
else
MOSI = 0;
dat<<=1;
SCK = 1;
}
}
/******************************************************************************* * 函 数 名 : unsigned char SPI_Receive(void)
* 函数功能 : 主机接收一个数据
* 输 入 : 无
* 输 出 : 主机接收的数据
* 备 注 : 模式0:CPL0 = 0 CPHA = 0
*******************************************************************************/
unsigned char SPI_Receive(void)
{
unsigned char n;
unsigned char dat;
SCK = 1;
for(n = 0; n < 8; n++)
{
SCK = 0;
dat <<= 1;
if(MISO)
dat = dat | 0x01;
else
dat = dat & 0xfe;
SCK = 1;
}
return dat;
}
SPI工作过程:
SPI可以用全双工通信方式同时发送和接收8(16)位数据,过程如下:
在主机启动阶段发送过程进行时,在主移位寄存器与从移位寄存器之间通过SSO传输数据;与此同时,则通过SSD将主移位寄存器中的数据传递给从移位寄存器;在此过程中每隔8(16)个时钟周期就会暂停一次时钟信号;当完成该次数据转移后,则会自动将主(从)移位寄存器中的数据传输至相应的接收缓冲器中,并相应地设置接收缓冲器的溢出标志位(中断标志位)为"1";随后又会将从(主)移位寄存器中的数据传输至对方的接收缓冲器中,并同样设置对方接收缓冲器的溢出标志位(中断标志位)为"1";当主CPU检测到主接收缓冲器的溢出标志或者中断标志置"1"之后,则能够读取该接收缓冲器中的数据;同样地当从CPU检测到从接收缓冲器的溢出标志或者中断标志置"1"之后,则能够读取该接收缓冲器中的数据;如此就完成了双方之间的通信过程
根据上面的程序代码,下面有一些我个人对 SPI 主从机通信的理解:
另外,在SPI协议中实现 send 和 receive 同步工作的情况下,在当一条指令被主站发往设备站时必然是会收到一条来自设备站的数据。然而在此时收到的数据显然不属于主站所期望的信息因为设备站还未完成接收到完整的指令序列因此主站无法立即获取所需信息。为此请参考以下两段代码:
另外,在SPI协议中实现 send 和 receive 同步工作的情况下,在当一条指令被主站发往设备站时必然是会收到一条来自设备站的数据。然而在此时收到的数据显然不属于主站所期望的信息因为设备站还未完成接收到完整的指令序列因此主站无法立即获取所需信息。为此请参考以下两段代码:
SPI_Send(0x11) //假设主机发送的命令是0x11
Dat = SPI_Receive(); //主机从从机出接收数据
通过发送接收函数可以看出,在使用SPI.Send()函数向从机发送指令的过程中,必定会从从机那边接收了一个数据;正如前面所述这一数据并非是我们所需的;因此在下面这段代码中必须添加Dat = SPI.Receive()这条语句;在此之后接收的数据才会成为我们需要的数据;值得注意的是,在执行Dat = SPI.Receive()这条指令时不仅主机会接受来自从机的数据并将其存储在变量Dat中还会有另一个操作发生即主机将通过MISO口发送数据给从机;尽管如此我们并未指定一个具体的要发送的数据值这一操作完全取决于MISO口当前的状态;换句话说只要有发送就会有对应地收到的数据;但在这个特定的操作过程中我们只需要关注来自from端的信息而不必过于在意所发送的具体内容是什么
