STM32----------SPI(Serial Peripheral Interface)

SPI(Serial Peripheral Interface,串行外设接口)是Motorola公司提出的一种同步串行数据传输标准,在很多器件中被广泛应用。

1. 接口

SPI接口经常被称为4线串行总线,以主/从方式工作,数据传输过程由主机初始化。如图1所示,其使用的4条信号线分别为:

1) SCLK:串行时钟,用来同步数据传输,由主机输出;

2) MOSI:主机输出从机输入数据线;

3) MISO:主机输入从机输出数据线;

4) SS:片选线,低电平有效,由主机输出

在SPI总线上,某一时刻可以出现多个从机,但只能存在一个主机,主机通过片选线来确定要通信的从机。这就要求从机的MISO口具有三态特性,使得该口线在器件未被选通时表现为高阻抗。

                                                                                        

2. 数据传输

在一个SPI时钟周期内,会完成如下操作:

1) 主机通过MOSI线发送1位数据,从机通过该线读取这1位数据;

2) 从机通过MISO线发送1位数据,主机通过该线读取这1位数据。

这是通过移位寄存器来实现的。如图2所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。

                                                         

       1)SPI分为主从模式,主模式提供时钟和片选选择信号.

       2) 模式控制:CPOL用来控制时钟信号(clk)在空闲时候的状态;CPHA用来控制采样时刻时CLK的边缘动作。

     CPOL     CPHA        模式

        0              0            CLK空闲时为低电平,CLK上升沿采样数据。                              0              1            CLK空闲为低电平,CLK下降沿采样数据。

        1              0            CLK空闲时为高电平,CLK上升沿采样数据。                              1              1            CLK空闲时为高电平,CLK下降沿采样数据。


2. NSS片选详解

主设备和从设备在进行SPI通信的时候,从设备都有个CS片选信号,低电平有效,我们通常都要用这个NSS连到从设备的CS上。

先看输入输出模式.

对于每个SPI的NSS可以输入,也可以输出。所谓输入,就是NSS的电平信号给自己,所谓输出,就是将NSS的电平信号发送出去,给从机。配置为输出,还是不输出,我们可以通过SPI_CR2寄存器的SSOE位。当SSOE为1时,并且SPI处于主模式控制时,NSS就输出低电平,也就是拉低,因此当其他SPI设备的NSS引脚与它相连,必然接收到低电平,则片选成功,都成为从设备了。NSS的输出就介绍到这里

下面介绍NSS的输入。

我们都知道NSS输入又分为硬件输入和软件控制输入两种模式,那么就从这两种模式入手,来揭开它的面纱吧。

先说软件模式吧。

1对于SPI主机来说,需要设置SPI_CR1寄存器的SSM为1和SSI位为1,SSM为1是为了使能软件管理。,NSS有内部和外部引脚。这时候,外部引脚留作他用(可以用来作为GPIO驱动从设备的片选信号)。内部NSS引脚电平则通过SPI_CRL寄存器的SSI位来驱动。SSI位为1是为了使NSS内电平为高电平。这时候,不免产生疑问,为什么主设备的内部NSS电平要为1呢?

STM32手册上说,要保持MSTR和SPE位为1,也就是说要保持主机模式,只有NSS接到高电平信号时,这两位才能保持置1.也就是说对于STM32的SPI,要保持为主机状态,内部输入的NSS电平必须为高。当然这里在硬件模式下也是如此

2对于SPI从机来说

主机自己的内部NSS高电平解决了,那么SPI从机的NSS片选低电平也得解决啊。如果从机选择STM32的一个SPI,譬如主机选为SPI1,从机选为SPI2,则要按照以下操作手册说,NSS引脚在完成字节传输之前必须连接到一个低电平信号。在软件模式下,则需要设置SPI_CR1寄存器的SSM为1(软件管理使能)和SSI位为0.果然如此。SSI必须要为0,也就是SPI2的片选为低,则片选成功


SPI配置为hard模式后,通过检测NSS可以实现的是自身主机和从机模式的切换,而不是大多数人所认为的自动NSS。。。也就是说:在一个多SPI系统中,STM32 SPI通过NSS检测,一旦发现系统中无NSS低信号,自己就输出低,从而成为主机;当系统中有NSS低信号时(及已经有其它SPI宣布为主机),自己就配置为从机。所谓的hard模式的NSS实际就是为了实现多机间通信的。

3 Master 和Slaver

在STM32中,当要配置为slave时,只要将它的NSS引脚配置为低电平即可。那么如果要SPI配置为master时,也要配置这个NSS,但此时配置为SPI_NSS_Soft。究竟什么是SPI_NSS_Soft和SPI_NSS_Hard呢?我们来看一下数据手册的说明吧:在stm32的spi.h文件里面是这么定义的

#define SPI_NSS_Soft                    ((uint16_t)0x0200)
#define SPI_NSS_Hard                    ((uint16_t)0x0000)

它对应的SPI的控制寄存器的第9bit(从第0bit开始数)SSM(Software slave management),数据手册上是这么说的,当SSM为1时,NSS管脚的输入被SSI(空寄存器1的第8bit)值取代;当为0时,对应为外部NSS管脚的状态。


再来看看配置master:

#define SPI_Mode_Master                 ((uint16_t)0x0104)
#define SPI_Mode_Slave                  ((uint16_t)0x0000)

从#define SPI_Mode_Master                 ((uint16_t)0x0104)    这句可以看出配置了第2bit为1,第8bit位为1.在SPI的控制寄存器中,第2bit位是MSTR,当配置这个位为0时,配置SPI为slave模式,如果该位是1,则为master模式。第8bit是SSI(内部从机选择位),数据手册的说明是:仅当SSM位为1时,SSI位的值被强制对应到NSS引脚,而此时NSS引脚I/O寄存器的值被忽略。当SSI为1时,那么对应的NSS引脚为1,在它内部看来,它被使能了,被配置成了master。

可能有点迷糊,什么是NSS引脚,什么是NSS引脚I/O啊?我的理解是,NSS引脚是SPI通信的信号线之一,它可以不必要地连接到真实的I/O口上,可以软件置位和复位;而NSS引脚I/O是NSS这个信号线所连接的外部I/O口。例如上面第三段说的,NSS管脚的输入被SSI的值取代。

3举例

若从机为一个其他的SPI芯片,譬如我那个ADXL345加速度传感器。那么,我们可以有两种方法

一种方法,是把芯片的CS接到GND上,另一种方法是,用一个GPIO口去输出低电平来控制CS片选成功。这个GPIO可以是任何一个GPIO口,当然我们上面提到当SPI的主机配置为软件模式,外部NSS引脚留作他用了,它就是一个GPIO了,我们也可以用它。这时候,我们可以设置它推挽输出为低电平,然后用线跟从机的CS相连,那么就可以片选从芯片了。 再说说硬件模式。

对于主机,我们的NSS可以直接接到高电平,对于从机,NSS接低就可以。

当然我们上面提过当一个主机的SSOE为1时,主机工作在输出模式,而且NSS拉低了,我们要让从机片选,只要将CS接到主机的NSS上,CS自动拉低。

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

SPI_InitStructure.SPI_Mode = SPI_Mode_Master,//这里设置SPI1为主模式,设置SSI为1 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//这里设置SSM为1,软件管理

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_LSB;

SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStructure);

/* SPI2 Config -------------------------------------------------------------*/

SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;//这里设置SPI2为从模式SSI为0

//因为SPI2和SPI1用的是同一个SPI_InitStructure,所以SSM位已经为1

SPI_Init(SPI2, &SPI_InitStructure);

对于SPI2的配置,和SPI1用的是同一个结构体,只需要把模式和SSI一改就行,其他的譬如软件使能,还有时序什么的都不用改。这样SPI1和SPI2就配置好了。以后就可以传输数据了。

     1)SPI配置(3.01库):

       SPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex;   //双工模式
      SPI_InitStructure.SPI_Mode =SPI_Mode_Master;   //SPI主模式
      SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b; //8bit数据
      SPI_InitStructure.SPI_CPOL =SPI_CPOL_High;   //CLK空闲时为高电平          
      SPI_InitStructure.SPI_CPHA =SPI_CPHA_2Edge;    //CLK上升沿采样,因为上升沿是第二个边沿动作,所以也可以理解为第二个边沿采样
      SPI_InitStructure.SPI_NSS =SPI_NSS_Soft;       //片选用软件控制
      SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_4;  //SPI频率
      SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB;    //高位在前
      SPI_InitStructure.SPI_CRCPolynomial =7;     //crc7,stm32spi带硬件ecc
      SPI_Init(SPI1, &SPI_InitStructure);

      2)CS信号:

      主模式下要为从设备提供片选信号,值得注意的是STM32的主频相当较高,要提防数据没有完全发送前拉高CS信号。      


    3)SPI读写:(非中断模式)


    a)写一个字节:

       while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
      //确保发生前Buffer为空,也就是说上一次已经发生完成
      SPI_I2S_SendData(SPI1,Data);        //往寄存器中写入一个字节


       while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
       //等待接受到一个字节数据,为什么要这么做?加这一句的原因是为了确保这个字节已经发送出去,因为发生和接受是并行同步进行,那就是说你发生出去一个字节意味着你收到一个字节。所以这样判断完全没有问题,再说必要性,如果你不加这句你就会容易犯过早拉高CS信号的错误,你想想如果在SPI_I2S_SendData(SPI1, Data)后面立即拉高CS是什么后果。
     SPI_I2S_ReceiveData(SPI1);  //都会接收到的数据,看起来没什么必要,但以用stm32的经验推荐这样做,也许会有意想不到的收获。

    SPI_Writebyte(u8 data)

   {

          while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
          SPI_I2S_SendData(SPI1,Data);      


          while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);  


          SPI_I2S_ReceiveData(SPI1);  

  }


    b)读一个字节:

     读的时候要注意一个问题,因为从模式是没法提供时钟的,所以主模式下必须要在接收的同时提供时钟。办法就是发送一个字节来实现,因为还是上面说的,发送一个字节就意味着收到一个字节,代码和写完全一样,只要把读出来的字节保存即可。


   u8   SPI_Readbyte(u8 data)

   {     

          while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
          SPI_I2S_SendData(SPI1,Data);      

          while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);  

          returnSPI_I2S_ReceiveData(SPI1);  

  }

  总结:上面的程序是最求稳定而设定的,如果你对速度有要求,你可以做相应的精简,比如读写直接对寄存器进行操作,另外配置SPI前要对从模式的模式了解清楚,包括从设备支持的时钟范围和模式(CPOL,CPHA状态)。


4. 优缺点

SPI接口具有如下优点:

1) 支持全双工操作;

2) 操作简单;

3) 数据传输速率较高。

同时,它也具有如下缺点:

1) 需要占用主机较多的口线(每个从机都需要一根片选线);

2) 只支持单个主机。



  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值