SPI通讯协议

1、SPI通讯协议说明

  SPI是Serial Peripheral Interface的缩写,即串行外围设备接口。SPI是一种高速全双工的通信总线。

  SPI通讯只需要4根线,也就是4线通讯,这4根线分别为:片选信号CS、时钟信号SCK、主机输出/从机输入MOSI、从机输出/主机输入MISO。

   SPI通讯设备之间常用的连接方式如下图所示:

   从图中可以看到主机的MOSI和从机的MOSI相连、主机的MISO和从机的MISO相连,主机的MOSI/MISO与从机的MOSI/MISO是相对应的,不需要交叉相连。

  由SPI设备连接图可以看到,除了片选信号,主机和多个从机的SCK线、MOSI线和MISO线是共用的。

  CS片选信号:

  从上图还可以看到每个从机的通讯线只有4跟,而主机却有6根通讯线,这是因为当总线上挂载多个从机设备的时候,主机需要通过不同的片选信号来区分到底操作哪个从机设备。在SPI通讯协议中,是没有设备地址的,它主要通过片选信号来寻址。主机通过不同片选信号来选择需要通信的从机设备,这样就不会造成多从机同时通讯。如果线路上只有一个从机,那只需要一根片选信号就可以了。

  SCK时钟信号:

  SPI通讯的时钟线SCK主要用于通讯数据同步。SCK时钟由主机产生,同时用来驱动主机和从机进行通信。

  SCK时钟决定了SPI通讯的速率,SCK时钟的频率越高,SPI通讯的速率也就越快。一般SPI通讯的速率可以达到MHZ级别,比IIC的通讯速率高出很多。

  需要注意的是不同的主机和从机设备所支持的最高时钟频率是有差别的,在选择SCK时钟频率是,一般不能超过通讯设备支持的最高时钟频率。时钟频率参数可以在具体使用的设备规格书中查询。    

  MOSI主输出/从输出数据线:

  MOSI是SPI通讯的主机数据输出、从机数据输入信号线,在SCK时钟的驱动下,主机通过MOSI输出数据,而从机通过MOSI读取数据。即主机通过MOSI将数据传输给从机。  

  MISO从输出/主输入数据线:

  MISO是SPI通讯的从机数据输出,主机数据输入信号新,在SCK时钟的驱动下,从机通过MISO输出数据,而主机通过MISO读取数据。即从机通过MISO将数据传输给从机。

  SPI通讯的内部工作机制如下图所示:

 

   从图中可以看到,SPI通讯主要由一个时钟生成器(SPI CLOK Generaor)和一个8位的移位寄存器(Shift Register)组成。

  从SPI通讯内部工作机制图中可以看到SPI通讯的SCKK时钟线由时钟成成器产生,而时钟生成器在主机(MASTER)内,也就是说SCK时钟线由主机生成。SCK时钟通过驱动主机和从机的移位寄存器,即主机和从机的移位寄存器是同步工作的。

  从SPI通讯内部工作机制图分析SPI通讯协议如下:

  在一个SCK时钟周期内,主机的移位寄存器在SCK时钟的驱动下通过MOSI移出一个bit的数据,同时主机的移位寄存器在SCK的驱动通过MISO读取一个bit数据。而从机的移位寄存器在SCK时钟的驱动下通过MISO移出一个bit的数据,同时从机的移位及除尘器在SCK的驱动下通过MOSI读取一个个bit的数据。由于主机和从机共用SCK时钟线,所以以上操作是同步进行的,也就是说在一个SCK时钟周期内,主机输出数据的同时从机读取数据,从机输出数据的同时主机读取书记。 

  在进行SPI数据传输时,可以选择先传输高位数据后传输低位数据,也可以选择先传输低位数据后传输高位数据。具体是先传输高位还是先传输低位,需要根据从机设备的接收方式来决定。   

2、SIP的通讯模式

  SPI通讯一共有4中模式,主要是根据时钟线SCK空闲时的电平状态和数据传输时采样的时刻有区分的。一般在SPI通讯中,用时钟极性CPOL来表示SCK时钟线空闲时的电平状态,用时钟相位CPHA来表示数据采样时刻。

  时钟极性CPOL是指SPI通讯设备处于空闲状态(无通讯)时,SCK时钟线的电平状态。若SCK时钟线在空闲状态时为低电平,则CPOL=0;若SCK时钟线在空闲状态时为高电平,则CPOL=1。

  时钟相位CPHA是指SPI通讯时数据采样的时刻,即数据移入和移出的时刻。

  当CPHA=0时,MOSI和MISO数据线上的电平状态(0或1)会在SCK时钟线上的第一个边沿(奇数边沿,这是从片选信号拉低开始算起的)被输出或读取(即采样数据)。

  当CPHA=1时,MOSI和MISO数据线上的电平状态(0或1)会在SCK时钟线上的第二个边沿(偶数边沿,同样从片选信号拉低开始算起)被输出或读取(即采样数据)。

  时钟极性CPOL和时钟相位CPHA的不同状态组合,可以产生4种SPI通讯模式,如下:

   下图是时钟相位CPHA=0时,在不同的时钟极性CPOL状态下的SPI通讯时序图:

  在上图中,MOSI数据线和MISO的数据线都是在SCK的第一个边沿采样的,具体是上升沿采样还是下降沿采样,要根据CPOL的状态才决定。若CPOL=0,则采样发生在上升沿;若CPOL=1,则采样发生在下降沿。

  下图是时钟相位CPHA=1时,在不同的时钟极性CPOL状态下的SPI通讯时序图:

 

  在上图中,MOSI数据线和MISO的数据线都是在SCK的第二个边沿采样的,具体是上升沿采样还是下降沿采样,要根据CPOL的状态才决定。若CPOL=0,则采样发生在下降沿;若CPOL=1,则采样发生在上升沿。

   在实际应用中,需要根据从机设备的通讯方式来决定使用哪种SPI通讯模式,若从机设备的数据采样发生在上升沿,则应该选用CPOL=0、CPHA=0或者CPOL=1、CPHA=1的模式,如果对空闲状态有要求,还需要根据从机设备的通讯要求选择CPOL的状态。

3、IO口模拟SPI通讯

  对于一些没有SPI串行总线接口的MCU来说,可以通过IO口来模拟SPI通讯协议来实现串行通信。通过使用MCU的IO来模拟SPI通讯的4种模式。

  SPI通讯模式1:CPOL=0、CPHA=0

  在这个模式下,SCK时钟线在空闲时(无通讯)的电平状态为低电平,并且在第一个边沿采样数据,由于SCK时钟线在空闲时的电平为低电平,所以在通讯时,第一个边沿是上升沿,即在该模式下,数据的采样发生在上升沿。

  在该模式下,要输出的数据应该在SCK为低电平的时候改变状态(即输出相应的电平状态),在SCK为高电平的时候保持数据的稳定。

  主机在SCK为低电平的时候输出MOSI电平,然后在SCK从低电平变为高电平的时候保持MOSI的电平状态并且同时读取MISO数据线上的电平。

  从机在SCK为低电平的时候输出MISO电平,然后在SCK从低电平变为高电平的时候保持MISO的电平状态并且同时读取MOSI数据线上的电平。

  传输一个字节总共产生8个高电平脉冲,退出传输一个字节后,SCK时钟应该保持为低电平。

  程序如下所示:

uint8_t SPI_WRITE_READ_BYTE(uint8_t TX_DAT)
{
    uint8_t i;
    uint8_t RX_DAT = 0;    
       
    for(i = 0;i < 8;i ++)
    {
        SPI_CLK(0);
        if(TX_DAT & 0x80)
        {
            SPI_MOSI(1);    
        }
        else
        {
            SPI_MOSI(0);       
        }     
        TX_DAT <<= 1;     
        SPI_DELAY(_SPI_DELAY_);           
        SPI_CLK(1);
        RX_DAT <<= 1;
    
        if(SPI_MISO != 0)
        {
            RX_DAT |= 0x01;    
        }       
        SPI_DELAY(_SPI_DELAY_);            
    }
    SPI_CLK(0);//空闲时SCK为低电平
    
    return RX_DAT;
}

  SPI通讯模式2:CPOL=0、CPHA=1

  在这个模式下,SCK时钟线在空闲时(无通讯)的电平状态为低电平,并且在第二个边沿采样数据,由于SCK时钟线在空闲时的电平为低电平,所以在通讯时,第二个边沿是下降沿,即在该模式下,数据的采样发生在下降沿。

  在该模式下,要输出的数据应该在SCK为高电平的时候改变状态(即输出相应的电平状态),在SCK为低电平的时候保持数据的稳定。

  主机在SCK为高电平的时候输出MOSI电平,然后在SCK从高电平变为低电平的时候保持MOSI的电平状态并且同时读取MISO数据线上的电平。

  从机在SCK为高电平的时候输出MISO电平,然后在SCK从高电平变为低电平的时候保持MISO的电平状态并且同时读取MOSI数据线上的电平。

  传输一个字节总共产生8个高电平脉冲,退出传输一个字节后,SCK时钟应该保持为低电平。

  程序如下所示:

uint8_t SPI_WRITE_READ_BYTE(uint8_t TX_DAT)
{
    uint8_t i;
    uint8_t RX_DAT = 0;    
       
    for(i = 0;i < 8;i ++)
    {
        SPI_CLK(1);
        if(TX_DAT & 0x80)
        {
            SPI_MOSI(1);    
        }
        else
        {
            SPI_MOSI(0);       
        }    
        TX_DAT <<= 1;    
        SPI_DELAY(_SPI_DELAY_);
        SPI_CLK(0);
        RX_DAT <<= 1;
        if(SPI_MISO != 0)
        {
            RX_DAT |= 0x01;    
        }    
        SPI_DELAY(_SPI_DELAY_);            
    }
    
    return RX_DAT;
}

  SPI通讯模式3:CPOL=1、CPHA=0

  在这个模式下,SCK时钟线在空闲时(无通讯)的电平状态为高电平,并且在第以个边沿采样数据,由于SCK时钟线在空闲时的电平为高电平,所以在通讯时,第一个边沿是下降沿,即在该模式下,数据的采样发生在下降沿。

  在该模式下,要输出的数据应该在SCK为高电平的时候改变状态(即输出相应的电平状态),在SCK为低电平的时候保持数据的稳定。

  主机在SCK为高电平的时候输出MOSI电平,然后在SCK从高电平变为低电平的时候保持MOSI的电平状态并且同时读取MISO数据线上的电平。

  从机在SCK为高电平的时候输出MISO电平,然后在SCK从高电平变为低电平的时候保持MISO的电平状态并且同时读取MOSI数据线上的电平。

  传输一个字节总共产生8个低电平脉冲,退出传输一个字节后,SCK时钟应该保持为高电平。

  程序如下所示:

uint8_t SPI_WRITE_READ_BYTE(uint8_t TX_DAT)
{
    uint8_t i;
    uint8_t RX_DAT = 0;    
       
    for(i = 0;i < 8;i ++)
    {
        SPI_CLK(1);
        if(TX_DAT & 0x80)
        {
            SPI_MOSI(1);    
        }
        else
        {
            SPI_MOSI(0);       
        }    
        TX_DAT <<= 1;    
        SPI_DELAY(_SPI_DELAY_);   
        SPI_CLK(0);   
        RX_DAT <<= 1;
        if(SPI_MISO != 0)
        {
            RX_DAT |= 0x01;              
        }       
        SPI_DELAY(_SPI_DELAY_);         
    }
    SPI_SCK(1);//空闲时SCK为高电平
    
    return RX_DAT;
}

  SPI通讯模式1:CPOL=1、CPHA=1

  在这个模式下,SCK时钟线在空闲时(无通讯)的电平状态为高电平,并且在第二个边沿采样数据,由于SCK时钟线在空闲时的电平为高电平,所以在通讯时,第二个边沿是上升沿,即在该模式下,数据的采样发生在上升沿。

  在该模式下,要输出的数据应该在SCK为低电平的时候改变状态(即输出相应的电平状态),在SCK为高电平的时候保持数据的稳定。

  主机在SCK为低电平的时候输出MOSI电平,然后在SCK从低电平变为高电平的时候保持MOSI的电平状态并且同时读取MISO数据线上的电平。

  从机在SCK为低电平的时候输出MISO电平,然后在SCK从低电平变为高电平的时候保持MISO的电平状态并且同时读取MOSI数据线上的电平。

  传输一个字节总共产生8个低电平脉冲,退出传输一个字节后,SCK时钟应该保持为低电平。

  程序如下所示:

uint8_t SPI_WRITE_READ_BYTE(uint8_t TX_DAT)
{
    uint8_t i;
    uint8_t RX_DAT = 0;    
       
    for(i = 0;i < 8;i ++)
    {
        SPI_CLK(1);
        if(TX_DAT & 0x80)
        {
            SPI_MOSI(1);    
        }
        else
        {
            SPI_MOSI(0);       
        }   
        TX_DAT <<= 1;    
        SPI_DELAY(_SPI_DELAY_);
        SPI_CLK(0);  
        RX_DAT <<= 1;
        if(SPI_MISO != 0)
        {
            RX_DAT |= 0x01;              
        }       
        SPI_DELAY(_SPI_DELAY_);         
    }
    SPI_SCK(1);//空闲时SCK为高电平
    
    return RX_DAT;
}

   完整的用STM32F103ZET6的GPIO口模拟SPI通讯的工程代码可以在https://github.com/h1019384803/STM32F103ZET6-GPIO_SPI下载。

posted @ 2019-09-08 11:50  Mars-King  阅读(3209)  评论(0编辑  收藏  举报