STM32 SPI详解

一、SPI简介

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

 

二、SPI特色

2.一、SPI控制方式

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

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

 

2.二、SPI传输方式

采用同步方式(Synchronous)传输数据网络

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

2.三、SPI数据交换

SPI数据交换框图布局

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

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

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

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

 

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

 

上面的过程转为动画

初始状态

 

 

 

 主机读取一个bit过程

 

 

 

 

 

 总结:

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

 

2.四、SPI传输模式

上升沿、降低沿、前沿、后沿触发。固然也有MSB和LSB传输方式.

 

三、工做机制

3.一、相关缩写

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=边沿,即时钟电平变化的时刻,即上升沿(rising edge)或者降低沿(falling edge)

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

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

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

 

3.二、CPOL极性

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

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

再用中文详解:

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

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

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

 

3.三、CPHA相位

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

对于:

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

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

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

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

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

 

3.四、极性和相位图示

图例1

图例2

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便可。

 

四、STM32的SPI控制模块

SPI是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通讯总线,而且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今愈来愈多的芯片集成了这种通讯协议,STM32也有SPI接口。

SPI接口通常使用4条线通讯:

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

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

SCLK  时钟信号,由主设备产生。

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

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

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

 

关于SPI,从数据手册查到

STM32F207VCT6共有3个SPI。

从下表查出每一个SPI对应的管脚

STM32标准外设库SPI_InitTypeDef的定义

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

第一个参数SPI_Direction 是用来设置SPI的通讯方式,能够选择为半双工,全双工,以及串行发和串行收方式,这里咱们选择全双工模式SPI_Direction_2Lines_FullDuplex。

第二个参数SPI_Mode用来设置SPI的主从模式,这里咱们设置为主机模式 SPI_Mode_Master,固然有须要你也能够选择为从机模式 SPI_Mode_Slave。

第三个参数SPI_DataSiz为8位仍是16位帧格式选择项,这里咱们是8位传输,选择SPI_DataSize_8b。

第四个参数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便可。

示例代码:

void  SPIInit( void )
{
    SPI_InitTypeDef SPI_InitStructure;
    FLASH_GPIO_Init();
    /*!< Deselect the FLASH: Chip Select high */
    GPIO_SetBits( GPIOA, GPIO_Pin_4 );
    /*!< SPI configuration */
    SPI_InitStructure.SPI_Direction     = SPI_Direction_2Lines_FullDuplex;      /* 双线双向全双工 */
    SPI_InitStructure.SPI_Mode      = SPI_Mode_Master;                      /* 主 SPI */
    SPI_InitStructure.SPI_DataSize      = SPI_DataSize_8b;                      /* SPI 发送接收 8 位帧结构 */
    SPI_InitStructure.SPI_CPOL      = SPI_CPOL_High;                        /* 串行同步时钟的空闲状态为高电平 */
    SPI_InitStructure.SPI_CPHA      = SPI_CPHA_2Edge;                       /* 第二个跳变沿数据被采样 */
    SPI_InitStructure.SPI_NSS       = SPI_NSS_Soft;                         /* NSS 信号由软件控制 */
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;             /* 预分频 16 */
 
    SPI_InitStructure.SPI_FirstBit      = SPI_FirstBit_MSB;                     /* 数据传输从 MSB 位开始 */
    SPI_InitStructure.SPI_CRCPolynomial = 7;                                    /* CRC 值计算的多项式 */
    SPI_Init( SPI1, &SPI_InitStructure );
    /*!< Enable the sFLASH_SPI  */
    SPI_Cmd( SPI1, ENABLE );
}

看到这里,可能觉的前面讲原理并无太大的用处,由于STM32集成了SPI控制器,配置一下便可。

 

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

 

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

五、SPI的应用

SPI的经常使用应用NorFlash

从数据手册上看到,SPI传输:CKPOL=1 , CKPHA=1

因此STM32的SPI读取NorFlash的配置以下

抓取下面代码波形

抓取的波形以下

0100 1011 就是0X4B

 

其中看到:

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

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

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

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

本身的理解:在降低沿转换数据,在上升沿采样数据

 

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

六、code

读取norflash

使用STM32F207硬件SPI模块

/**
  * @brief  Initializes the peripherals used by the SPI FLASH driver.
  * @param  None
  * @retval None
  */
void FLASH_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  
  /*!< Enable the SPI clock */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  
  /*!< Enable GPIO clocks */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);  
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); 
  /*!< SPI pins configuration *************************************************/
  
  /*!< Connect SPI pins to AF5 */  
  GPIO_PinAFConfig(GPIOA, 5, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, 6, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOB, 5, GPIO_AF_SPI1);
  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;//GPIO_PuPd_DOWN;
  
  /*!< SPI SCK pin configuration */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  
  /*!< SPI MISO pin configuration */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  
  /*!< SPI MOSI pin configuration */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_5;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  
  /*!< Configure sFLASH Card CS pin in output pushpull mode */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOE, &GPIO_InitStructure);
}
 
/**
  * @brief  Initializes the peripherals used by the SPI FLASH driver.
  * @param  None
  * @retval None
  */
void  FLASH_SPIInit(void)
{  
  
  SPI_InitTypeDef  SPI_InitStructure;
  
  FLASH_GPIO_Init();
  
  /*!< Deselect the FLASH: Chip Select high */
  GPIO_SetBits(GPIOE,GPIO_Pin_12);
  
  /*!< SPI configuration */
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线双向全双工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主 SPI
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;// SPI 发送接收 8 位帧结构
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平 
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二个跳变沿数据被采样
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS 信号由软件控制
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;//预分频 16
  
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//数据传输从 MSB 位开始
  SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC 值计算的多项式
  SPI_Init(SPI1, &SPI_InitStructure);
  /*!< Enable the sFLASH_SPI  */
  SPI_Cmd(SPI1, ENABLE);
}

软件模拟SPI协议

/**
  * @brief  Sends a byte through the SPI interface and return the byte received
  *         from the SPI bus.
  * @param  byte: byte to send.
  * @retval The value of the received byte.
  */
uint8_t SPI_ReadWriteByte(uint8_t data)
{
  uint8_t i,data_read = 0;  
  if(data!=0xA5){
    for(i=0;i<8;i++){
      MSPI_CLK_L();                                                        
      if(data&0x80){                                                        
    MSPI_MOSI_H();
      }else{
    MSPI_MOSI_L();
      }
      MSPI_DELAY();
      data = data<<1;                                                        
      MSPI_CLK_H();                                                         
      MSPI_DELAY();                                                            
    }
    return data_read;
  }else{
    for(i=0;i<8;i++){
      MSPI_CLK_L();                                             
      MSPI_DELAY();                                             
      data_read = data_read<<1;                                           
      MSPI_CLK_H();                                            
      if(MSPI_READ_IN()){                                                    
    data_read |= 0x01;                                       
      }
      MSPI_DELAY();
    }
    return data_read;
  }                                              
}
 
/**
  * @brief  Initializes the peripherals used by the SPI FLASH driver.
  * @param  None
  * @retval None
  */
void FLASH_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
 
  /*!< Enable GPIO clocks */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);  
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
  
  /*!< Configure sFLASH Card CS pin in output pushpull mode */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOE, &GPIO_InitStructure);
  
  /*!< SPI SCK pin configuration */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  MSPI_CLK_H();
  
  /*!< SPI MOSI pin configuration */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_5;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  MSPI_MOSI_H();
  
  /*!< SPI MISO pin configuration */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
}
 
/**
  * @brief  Initializes the peripherals used by the SPI FLASH driver.
  * @param  None
  * @retval None
  */
void  FLASH_SPIInit(void)
{  
  
  FLASH_GPIO_Init();
  
  /*!< Deselect the FLASH: Chip Select high */
  GPIO_SetBits(GPIOE,GPIO_Pin_12);
}

开源地址:

https://github.com/strongercjd/STM32F207VCT6

 

点击查看本文所在的专辑,STM32F207教程