你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32 SPI详解

[复制链接]
STMCU-管管 发布时间:2020-9-8 13:47
01 SPI简介
SPI 规定了两个SPI 设备之间通信必须由主设备(Master) 来控制次设备(Slave). 一个Master 设备可以通过提供Clock 以及对Slave 设备进行片选(Slave Select) 来控制多个Slave 设备,SPI 协议还规定Slave 设备的Clock 由Master 设备通过SCK 管脚提供给Slave 设备,Slave 设备本身不能产生或控制Clock, 没有Clock 则Slave 设备不能正常工作。

02 SPI特点

2.1SPI控制方式
采用主-从模式(Master-Slave)的控制方式。

SPI 规定了两个SPI 设备之间通信必须由主设备(Master) 来控制次设备(Slave). 一个Master 设备可以通过提供Clock 以及对Slave 设备进行片选(Slave Select) 来控制多个Slave 设备,SPI 协议还规定Slave 设备的Clock 由Master 设备通过SCK 管脚提供给Slave 设备,Slave 设备本身不能产生或控制Clock, 没有Clock 则Slave 设备不能正常工作。

2.2SPI传输方式
采用同步方式(Synchronous)传输数据。

Master 设备会根据将要交换的数据来产生相应的时钟脉冲(ClockPulse), 时钟脉冲组成了时钟信号(ClockSignal) , 时钟信号通过时钟极性(CPOL) 和时钟相位 (CPHA) 控制着两个SPI 设备间何时数据交换以及何时对接收到的数据进行采样,来保证数据在两个设备之间是同步传输的。

1.png
2.3SPI数据交换
SPI数据交换框图

2.png

上图只是对SPI 设备间通信的一个简单的描述,下面就来解释一下图中所示的几个组件(Module):

SSPBUF,Synchronous Serial Port Buffer, 泛指SPI 设备里面的内部缓冲区,一般在物理上是以FIFO 的形式,保存传输过程中的临时数据;

SSPSR, Synchronous Serial Port Register, 泛指SPI 设备里面的移位寄存器(ShiftRegitser), 它的作用是根据设置好的数据位宽(bit-width)把数据移入或者移出SSPBUF;

Controller, 泛指SPI 设备里面的控制寄存器,可以通过配置它们来设置SPI 总线的传输模式。

SPI 设备间的数据传输之所以又被称为数据交换,是因为SPI 协议规定一个SPI 设备不能在数据通信过程中仅仅只充当一个"发送者(Transmitter)"或者"接收者(Receiver)".在每个Clock 周期内,SPI 设备都会发送并接收一个bit 大小的数据,相当于该设备有一个bit 大小的数据被交换了.一个Slave 设备要想能够接收到Master 发过来的控制信号,必须在此之前能够被Master 设备进行访问(Access). 所以,Master 设备必须首先通过SS/CS pin 对Slave 设备进行片选,把想要访问的Slave 设备选上.在数据传输的过程中,每次接收到的数据必须在下一次数据传输之前被采样.如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能会被丢弃,导致SPI 物理模块最终失效.因此,在程序中一般都会在SPI 传输完数据后,去读取SPI 设备里的数据,即使这些数据(DummyData)在我们的程序里是无用的。

上面的过程转为动画

初始状态

3.png
主机读取一个bit过程

640.gif

当读取7次后,也就是读取7位后

5.png


总结:
没有读和写的说法,因为实质上每次SPI是主从设备在交换数据。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。

2.4SPI传输模式
上升沿、下降沿、前沿、后沿触发。当然也有MSB和LSB传输方式.

6.png

03 工作机制
3.1、相关缩写
SPI的极性Polarity和相位Phase,最常见的写法是CPOL和CPHA,不过也有一些其他写法,简单总结如下:

(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性

(2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (时钟)相位

(3) SCK=SCLK=SPI的时钟

(4) Edge=边沿,即时钟电平变化的时刻,即上升沿(risingedge)或者下降沿(fallingedge)

对于一个时钟周期内,有两个edge,分别称为:

Leading edge=前一个边沿=第一个边沿,对于开始电压是1,那么就是1变成0的时候,对于开始电压是0,那么就是0变成1的时候;

Trailingedge=后一个边沿=第二个边沿,对于开始电压是1,那么就是0变成1的时候(即在第一次1变成0之后,才可能有后面的0变成1),对于开始电压是0,那么就是1变成0的时候;

3.2CPOL极性
先说什么是SCLK时钟的空闲时刻,其就是当SCLK在数发送8个bit比特数据之前和之后的状态,于此对应的,SCLK在发送数据的时候,就是正常的工作的时候,有效active的时刻了。

先说英文,其精简解释为:ClockPolarity = IDLE state of SCK。

再用中文详解:

SPI的CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:

CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high;

CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low;

3.3CPHA相位
首先说明一点,capturestrobe = latch = read =sample,都是表示数据采样,数据有效的时刻。相位,对应着数据采样是在第几个边沿(edge),是第一个边沿还是第二个边沿,0对应着第一个边沿,1对应着第二个边沿。

对于:

CPHA=0,表示第一个边沿:

对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;

对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;

CPHA=1,表示第二个边沿:

对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;

3.4、极性和相位图示
图例1
7.png
图例2
8.png

3.5、软件设置极性和相位
SPI分主设备和从设备,两者通过SPI协议通讯。

而设置SPI的模式,是从设备的模式,决定了主设备的模式。

所以要先去搞懂从设备的SPI是何种模式,然后再将主设备的SPI的模式,设置和从设备相同的模式,即可正常通讯。

对于从设备的SPI是什么模式,有两种:

1.固定的,有SPI从设备硬件决定的

SPI从设备,具体是什么模式,相关的datasheet中会有描述,需要自己去datasheet中找到相关的描述,即:

关于SPI从设备,在空闲的时候,是高电平还是低电平,即决定了CPOL是0还是1;

然后再找到关于设备是在上升沿还是下降沿去采样数据,这样就是,在定了CPOL的值的前提下,对应着可以推算出CPHA是0还是1了。

2.可配置的,由软件自己设定

从设备也是一个SPI控制器,4种模式都支持,此时只要自己设置为某种模式即可。

然后知道了从设备的模式后,再去将SPI主设备的模式,设置为和从设备模式一样,即可。

对于如何配置SPI的CPOL和CPHA的话,不多细说,多数都是直接去写对应的SPI控制器中对应寄存器中的CPOL和CPHA那两位,写0或写1即可

04 STM32的SPI控制模块
SPI是英语SerialPeripheralinterface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。

SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32也有SPI接口。

SPI接口一般使用4条线通信:

1 MISO

主设备数据输入,从设备数据输出。

2 MOSI

主设备数据输出,从设备数据输入。

3 SCLK

时钟信号,由主设备产生

4 CS

从设备片选信号,由主设备控制。

SPI主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。

STM32的SPI功能很强大,SPI时钟最多可以到18Mhz,支持DMA,可以配置为SPI协议或者I2S协议

关于SPI,从数据手册查到
9.png

STM32F207VCT6共有3个SPI。

从下表查出每个SPI对应的管脚
10.png

STM32标准外设库SPI_InitTypeDef的定义


  1. typedef struct
  2. {
  3.     uint16_t SPI_Direction; // 设置SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式
  4.    uint16_t SPI_Mode; // 设置SPI 的主从模式
  5.    uint16_t SPI_DataSize; // 为8 位还是16 位帧格式选择项
  6.    uint16_t SPI_CPOL; // 设置时钟极性
  7.    uint16_t SPI_CPHA; // 设置时钟相位
  8.    uint16_t SPI_NSS;   //设置NSS 信号由硬件(NSS管脚)还是软件控制
  9.    uint16_t SPI_BaudRatePrescaler;  //设置SPI 波特率预分频值
  10.    uint16_t SPI_FirstBit;    //设置数据传输顺序是MSB 位在前还是LSB 位在前
  11.    uint16_t SPI_CRCPolynomial; //设置CRC 校验多项式,提高通信可靠性,大于1 即可
  12. }SPI_InitTypeDef;

复制代码


参数

  • SPI_Direction
用来设置SPI的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式,这里我们选择全双工模式SPI_Direction_2Lines_FullDuplex。

  • SPI_Mode
来设置SPI的主从模式,这里我们设置为主机模式 SPI_Mode_Master,当然有需要你也可以选择为从机模式 SPI_Mode_Slave。

  • SPI_CPOL
用来设置时钟极性,我们设置串行同步时钟的空闲状态为高电平所以我们选择SPI_CPOL_High。

  • SPI_CPHA
来设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样,可以为第一个或者第二个条边沿采集,这里我们选择第二个跳变沿,所以选择 SPI_CPHA_2Edge

  • SPI_NSS
设置NSS信号由硬件(NSS管脚)还是软件控制,这里我们通过软件控制NSS关键,而不是硬件自动控制,所以选择 SPI_NSS_Soft。

  • SPI_BaudRatePrescaler
很关键,就是设置SPI波特率预分频值也就是决定 SPI 的时钟的参数,从不分频道256分频8个可选值,初始化的时候我们选择256分频值SPI_BaudRatePrescaler_256,传输速度为36M/256=140.625KHz。

  • SPI_FirstBit
设置数据传输顺序是MSB位在前还是LSB位在前,这里我们选择SPI_FirstBit_MSB高位在前。

  • SPI_CRCPolynomial
用来设置CRC校验多项式,提高通信可靠性,大于1即可。

示例代码:

  1. void  SPIInit( void )
  2. {
  3.     SPI_InitTypeDef SPI_InitStructure;
  4.     FLASH_GPIO_Init();
  5.     /*!< Deselect the FLASH: Chip Select high */
  6.     GPIO_SetBits( GPIOA, GPIO_Pin_4 );
  7.     /*!< SPI configuration */
  8.     SPI_InitStructure.SPI_Direction    = SPI_Direction_2Lines_FullDuplex;      /* 双线双向全双工 */
  9.     SPI_InitStructure.SPI_Mode     = SPI_Mode_Master;                      /* 主 SPI */
  10.     SPI_InitStructure.SPI_DataSize     = SPI_DataSize_8b;                      /* SPI 发送接收 8 位帧结构 */
  11.     SPI_InitStructure.SPI_CPOL     = SPI_CPOL_High;                        /* 串行同步时钟的空闲状态为高电平 */
  12.     SPI_InitStructure.SPI_CPHA     = SPI_CPHA_2Edge;                       /* 第二个跳变沿数据被采样 */
  13.     SPI_InitStructure.SPI_NSS      = SPI_NSS_Soft;                         /* NSS 信号由软件控制 */
  14.     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;             /* 预分频 16 */

  15.     SPI_InitStructure.SPI_FirstBit     = SPI_FirstBit_MSB;                     /* 数据传输从 MSB 位开始 */
  16.     SPI_InitStructure.SPI_CRCPolynomial= 7;                                    /* CRC 值计算的多项式 */
  17.     SPI_Init( SPI1, &SPI_InitStructure );
  18.     /*!< Enable the sFLASH_SPI  */
  19.     SPI_Cmd( SPI1, ENABLE );
  20. }
复制代码

看到这里,可能觉的前面讲原理并没有太大的用处,因为STM32集成了SPI控制器,配置一下即可。

一方面我们学习原理是为了更好的理解SPI,用于对接不同的SPI设备,像norflash的spi驱动网上有大量的例子,不容易出错。但并不是特别常见的,spi驱动SD卡,SPI驱动网络PHY,SPI驱动ESP8266,甚至在设计两个IC通信时,由于没有过多GPIO,又觉的IIC通信速度慢的话,可以设计两个IC之间使用SPI通信,显然这些场景就需要了解SPI的原理

另外一方面,实际应用中,有可能因为芯片其他管脚用于特殊功能,留下的管脚没有硬件SPI功能,只能模拟实现,这个时候学习SPI原理就很有必要了。


05 SPI的应用

SPI的常用应用NorFlash
从数据手册上看到,SPI传输:CKPOL=1,CKPHA=1

11.png

所以STM32的SPI读取NorFlash的配置如下
12.png

抓取波形
13.png

波形如下:
14.png

0100 1011  就是0X4B

其中看到:

起始电平是高电平,也就是CKPOL=1

第二个边沿采样,也就是CKPHA=1

其实说成类似IIC的高电平有效也是没有问题的

下面这句话是写模拟SPI的核心

自己的理解:在下降沿转换数据,在上升沿采样数据

除了抓取波形,在华邦Flash也看到了时序图
15.png

06 code

读取norflash两种驱动实现方式:

使用STM32F207硬件SPI模块





  1. /**

  2.   * @brief  Initializes the peripherals used by the SPIFLASH driver.
  3.   * @param  None
  4.   * @retval None
  5. */
  6. void FLASH_GPIO_Init(void)
  7. {
  8.   GPIO_InitTypeDefGPIO_InitStructure;
  9.   
  10.   /*!< Enable the SPI clock */
  11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  12.   
  13. /*!< Enable GPIO clocks */
  14. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  15. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);  
  16. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
  17.   /*!<SPI pins configuration*************************************************/
  18.   
  19. /*!< Connect SPI pins to AF5 */  
  20.   GPIO_PinAFConfig(GPIOA,5, GPIO_AF_SPI1);
  21.   GPIO_PinAFConfig(GPIOA, 6, GPIO_AF_SPI1);
  22. GPIO_PinAFConfig(GPIOB, 5, GPIO_AF_SPI1);
  23.   
  24. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  25. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  26. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  27. GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;//GPIO_PuPd_DOWN;

  28.   /*!< SPI SCK pin configuration */
  29. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  30.   GPIO_Init(GPIOA,&GPIO_InitStructure);
  31.   
  32.   /*!< SPI MISO pinconfiguration */
  33.   GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;
  34. GPIO_Init(GPIOA, &GPIO_InitStructure);
  35.   
  36.   /*!<SPI MOSI pin configuration */
  37.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  38.   GPIO_Init(GPIOB, &GPIO_InitStructure);

  39.   /*!< Configure sFLASH Card CS pin in output pushpull mode*/
  40.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  41. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  42. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  43. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  44. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  45.   GPIO_Init(GPIOE,&GPIO_InitStructure);
  46. }

  47. /**
  48.   * @brief Initializes the peripherals used by the SPI FLASH driver.
  49.   *@param  None
  50.   * @retval None
  51.   */
  52. void FLASH_SPIInit(void)
  53. {  
  54.   
  55.   SPI_InitTypeDef SPI_InitStructure;
  56.   
  57.   FLASH_GPIO_Init();
  58.   
  59. /*!< Deselect the FLASH: Chip Select high */
  60. GPIO_SetBits(GPIOE,GPIO_Pin_12);
  61.   
  62.   /*!< SPIconfiguration */
  63.   SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex;//双线双向全双工
  64. SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主SPI
  65.   SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b;// SPI 发送接收8 位帧结构
  66. SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平
  67.   SPI_InitStructure.SPI_CPHA =SPI_CPHA_2Edge;//第二个跳变沿数据被采样
  68. SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS 信号由软件控制
  69. SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_16;//预分频16
  70.   
  71.   SPI_InitStructure.SPI_FirstBit= SPI_FirstBit_MSB;//数据传输从MSB 位开始
  72. SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC 值计算的多项式
  73. SPI_Init(SPI1, &SPI_InitStructure);
  74.   /*!< Enable thesFLASH_SPI  */
  75.   SPI_Cmd(SPI1, ENABLE);
复制代码


软件模拟SPI协议



  1. /**

  2.   * @brief  Sends a byte through the SPI interface andreturn the byte received
  3.   *         from the SPI bus.
  4.   *@param  byte: byte to send.
  5.   * @retval The value of thereceived byte.
  6.   */
  7. uint8_t SPI_ReadWriteByte(uint8_tdata)
  8. {
  9.   uint8_t i,data_read = 0;  
  10. if(data!=0xA5){
  11.     for(i=0;i<8;i++){
  12.      MSPI_CLK_L();                                                   
  13.      if(data&0x80){                                                  
  14.   MSPI_MOSI_H();
  15.       }else{
  16.   MSPI_MOSI_L();
  17.      }
  18.       MSPI_DELAY();
  19.       data = data<<1;                                                  
  20.       MSPI_CLK_H();                                                   
  21.      MSPI_DELAY();                                                        
  22.     }
  23.     return data_read;
  24.   }else{
  25.    for(i=0;i<8;i++){
  26.       MSPI_CLK_L();                                      
  27.       MSPI_DELAY();                                      
  28.       data_read = data_read<<1;                                    
  29.       MSPI_CLK_H();                                    
  30.       if(MSPI_READ_IN()){                                                
  31.   data_read |= 0x01;                                 
  32.      }
  33.       MSPI_DELAY();
  34.     }
  35.     returndata_read;
  36.   }                                            
  37. }

  38. /**
  39.   * @brief  Initializes the peripheralsused by the SPI FLASH driver.
  40.   * @param  None
  41.   * @retvalNone
  42.   */
  43. void FLASH_GPIO_Init(void)
  44. {
  45. GPIO_InitTypeDef GPIO_InitStructure;

  46.   /*!< Enable GPIOclocks */
  47.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
  48.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  49.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

  50.   /*!< Configure sFLASH Card CS pin in output pushpull mode*/
  51.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  52. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  53. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  54. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  55. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  56.   GPIO_Init(GPIOE,&GPIO_InitStructure);
  57.   
  58.   /*!< SPI SCK pinconfiguration */
  59.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  60. GPIO_Init(GPIOA, &GPIO_InitStructure);
  61.   MSPI_CLK_H();

  62.   /*!< SPI MOSI pin configuration */
  63. GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_5;
  64.   GPIO_Init(GPIOB,&GPIO_InitStructure);
  65.   MSPI_MOSI_H();
  66.   
  67.   /*!<SPI MISO pin configuration */
  68.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  69.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  70. GPIO_Init(GPIOA, &GPIO_InitStructure);
  71. }

  72. /**
  73. * @brief  Initializes the peripherals used by the SPI FLASH driver.
  74. * @param  None
  75.   * @retval None
  76.   */
  77. void FLASH_SPIInit(void)
  78. {  
  79.   
  80.   FLASH_GPIO_Init();

  81.   /*!< Deselect the FLASH: Chip Select high */
  82. GPIO_SetBits(GPIOE,GPIO_Pin_12);
  83. }
复制代码



4.png
16.png
2 收藏 7 评论1 发布时间:2020-9-8 13:47

举报

1个回答
qhq8001 回答时间:2020-9-11 09:58:14
真形象,

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版