通信协议——SPI总线

一、SPI总线基础概述

    SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,分别是:

    1)、MOSI——Master Output Slave Input主机输出从机输入

    2)、MISO——Master Input Slave Output主机输入从机输出

    3SCLK——时钟信号,由主机产生

    4)、CS——从机使能信号(片选信号),由主机控制

    SPI通信协议的数据同样是一位一位传输的,而需要特别注意的是,SPI是一个环形总线结构,即是在SCLK控制下,两个双向移位寄存器进行数据交换。例如,主机在发一位数据给从机的时候,也会接收到一位来自从机的数据,从而完成两个寄存器的数据交换,因此,主机发送一个数据给从机,也必然会从从机处接收到一个数据,而想要从从机处收一个数据,主机也就必须发送一个数据给从机,在其环形通信结构如下图所示

                         通信协议——SPI总线_第1张图片

二、SPI总线的四种工作方式

    SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性CPOL和相位CPHA可以进行配置,其具体配置如下:

    1.CPOL用来决定SCK时钟信号空闲时的电平

    1)CPOL=0,空闲电平为低电平

    2)CPOL=1时,空闲电平为高电平

    2.CPHA用来决定采样时刻的

    1)CPHA=0,在每个周期的第一个时钟沿采样

    2)CPHA=1,在每个周期的第二个时钟沿采样

    通过上述CPOLCPHA的两两搭配组合出了四种工作模式,可看下图所示

        通信协议——SPI总线_第2张图片

    而需要特别注意的是SPI主机的MOSI和从机的MOSI相连,主机的MISO和从机的MISO相连,而主机的时钟相位和极性是要看从机的时钟相位和极性来进行配置的,即要搞清楚从机是时钟的上升沿还是下降沿接收数据,是在时钟的下降沿还是上升沿输出数据,这里可以看芯片的数据手册采用4个模式中的哪个模式。


三、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)位数据,过程如下:

主机启动发送过程,送出时钟脉冲信号——>主移位寄存器的数据通过SDO移入到从移位寄存器,同时从移位寄存器中的数据通过SDI移人到主移位寄存器中——>8(16)个时钟脉冲过后,时钟停顿,主移位寄存器中的8(16)位数据全部移人到从移位寄存器中,随即又被自动装入从接收缓冲器中,从机接收缓冲器满标志位(BF)和中断标志位(SSPIF)“1”。同理,从移位寄存器中的8位数据全部移入到主寄存器中,随即又被自动装入到主接收缓冲器中.主接收缓冲器满标志位(BF)和中断标志位(SSPIF)“1——>CPU检测到主接收缓冲器的满标志位或者中断标志位置1后,就可以读取接收缓冲器中的数据。同样,从CPU检测到从接收缓冲器满标志位或中断标志位置1后,就可以读取接收缓冲器中的数据,这样就完成了一次相互通信过程。


根据上面的程序代码,下面有一些我个人对 SPI 主从机通信的理解:

另外,因为SPI是发送和接收同时进行的,那么在主机发送一个命令给从机的时候,必然也就会接收一个来自从机的数据,但很显然此时的这个数据应该不是主机要的,因为此时从机还没接收完命令,所以并不知道主机要的数据是什么,只有当主机发送完命令从机也接收了以后,从机才知道要发送什么给主机,那么主机如何才能获得想要的数据呢?先看下面两句代码:

SPI_Send(0x11)   //假设主机发送的命令是0x11

Dat = SPI_Receive();    //主机从从机出接收数据

从发送接收函数可以知道,SPI_Send()函数发送了命令给从机,此时也必然从从机那里接收了一个数据,但正如前面所说,这个数据不是主机要的,因此在下面就要加上一句代码也就是Dat = SPI_Receive(),此时接收来的数据Dat才是主机要的数据,另外,特别要注意的一点是,在Dat = SPI_Receive()这句代码中,不仅仅是主机从从机那里接收了数据然后赋值给变量Dat,同时主机也通过MISO口发送了数据给从机,只是我们没有明确一个具体的要发送的值,此时是任意的,全看MISO口自身的状态,所以这里同样是有发送就有接收,只不过此刻主机只需要接收来自从机的值,而无所谓发送什么值给从机。



你可能感兴趣的:(通信协议)