SPI工作原理总结
硬件上为4根线。
主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
串行移位寄存器通过MOSI信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
SPI接口简介
SPI:Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
STM32F4的 SPI 功能很强大, SPI 时钟最高可以到 37.5Mhz,支持 DMA。
SPI内部结构简明图
SPI接口一般使用4条线通信:
MISO 主设备数据输入,从设备数据输出。
MOSI 主设备数据输出,从设备数据输入。
SCLK时钟信号,由主设备产生。
CS从设备片选信号,由主设备控制。
SPI接口框图
STM32 SPI接口可配置为支持SPI协议或者支持I2S音频协议,默认是SPI模式。可以通过软件切换到I2S方式。
27.2.1 SPI 特性
● 基于三条线的全双工同步传输
● 基于双线的单工同步传输,其中一条可作为双向数据线
● 8 位或 16 位传输帧格式选择
● 主模式或从模式操作
● 多主模式功能
● 8 个主模式波特率预分频器(最大值为 fPCLK/2)
● 从模式频率(最大值为 fPCLK/2)
● 对于主模式和从模式都可实现更快的通信
● 对于主模式和从模式都可通过硬件或软件进行 NSS 管理:动态切换主/从操作
● 可编程的时钟极性和相位
● 可编程的数据顺序,最先移位MSB或LSB
● 可触发中断的专用发送和接收标志
● SPI 总线忙状态标志
● SPI TI 模式
● 用于确保可靠通信的硬件 CRC 功能:
— 在发送模式下可将 CRC 值作为最后一个字节发送
— 根据收到的最后一个字节自动进行 CRC 错误校验
● 可触发中断的主模式故障、上溢和 CRC 错误标志
● 具有 DMA 功能的 1字节发送和接收缓冲器:发送和接收请求时钟相位和时钟极性
通过 SPI_CR1 寄存器中的 CPOL 和 CPHA 位,可以用软件选择四种可能的时序关系。CPOL(时钟极性)位控制不传任何数据时的时钟电平状态。此位对主器件和从 器件都有作用。如果复位 CPOL, SCK 引脚在空闲状态处于低电平。如果将 CPOL 置 1, SCK 引脚在空闲状态处于高电平。
如果将 CPHA(时钟相位)位置 1,则 SCK 引脚上的第二个边沿(如果复位 CPOL 位,则
为下降沿;如果将 CPOL 位置 1,则为上升沿)对 MSBit 采样。即,在第二个时钟边沿锁存
数据。如果复位 CPHA 位,则 SCK 引脚上的第一个边沿(如果将 CPOL 位置 1,则为下降
沿;如果复位 CPOL 位,则为上升沿)对 MSBit 采样。即,在第一个时钟边沿锁存数据。CPOL(时钟极性)和 CPHA(时钟相位)位的组合用于选择数据捕获时钟边沿。数据帧格式
移出数据时MSB在前还是LSB在前取决于SPI_CR1寄存器中LSBFIRST位的值。每个数据帧的长度均为8位或16位,具体取决于使用SPI_CR1寄存器中的DFF位。所选的数据帧格式适用于发送和/或接收。从器件选择(NSS) 引脚管理
可以使用 SPI_CR1 寄存器中的 SSM 位设置硬件或软件管理从器件选择。
● 软件管理 NSS (SSM = 1)
从器件选择信息在内部由 SPI_CR1寄存器中的 SSI 位的值驱动。外部 NSS 引脚空闲,可供其它应用使用。
● 硬件管理 NSS (SSM = 0)
根据 NSS 输出配置( SPI_CR1 寄存器中的 SSOE 位),硬件管理 NSS 有两种模式。— NSS 输出使能( SSM = 0, SSOE = 1)
仅当器件在主模式下工作时才使用此配置。当主器件开始通信时, NSS 信号驱动为低电平,并保持到 SPI 被关闭为止。
— NSS 输出禁止( SSM = 0, SSOE = 0)
对于在主模式下工作的器件,此配置允许多主模式功能。对于设置为从模式的器件, NSS 引脚用作传统 NSS 输入:在 NSS 为低电平时片选该从器件,在 NSS 为高电平时取消对它的片选。状态标志
应用可通过三种状态标志监视 SPI 总线的状态。
发送缓冲区为空 (TXE)
此标志置 1 时,表示发送缓冲区为空,可以将待发送的下一个数据加载到缓冲区中。对SPI_DR 寄存器执行写操作时,将清零 TXE 标志。
接收缓冲区非空 (RXNE)
此标志置 1 时,表示接收缓冲区中存在有效的已接收数据。读取 SPI_DR 时,将清零该标志。
BUSY
BSY标志由硬件置1和清零(对此标志执行写操作没有任何作用)。BSY标志用于指示SPI通信的状态。
BSY 置 1 时,表示 SPI 正忙于通信。在主模式下的双向通信接收模式( MSTR=1 且 BDM=1
且 BDOE=0)有一个例外情况, BSY 标志在接收过程中保持低电平。
如果软件要关闭 SPI 并进入停止模式(或关闭外设时钟),可使用 BSY 标志检测传输是否
结束以避免破坏最后一个数据的传输。为此,必须严格遵循下述步骤。
BSY 标志还可用于避免在多主模式系统中发生写冲突。
传输开始时, BSY 标志将置 1,但在主模式下的双向通信接收模式( MSTR=1 且 BDM=1 且
BDOE=0)下例外。在以下情况硬件将清零该标志:
● 传输完成时(主模式下的连续通信除外)
● 关闭 SPI 时
● 发生主模式故障时 (MODF=1)
当通信不连续时, BSY 标志在各通信之间处于低电平。
当通信连续时:
● 在主模式下, BSY 标志在所有传输期间均保持高电平
● 在从模式下, BSY 标志在各传输之间的一个 SPI 时钟周期内变为低电平
注意: 请勿使用 BSY标志处理每次数据发送或接收,最好改用 TXE 标志和 RXNE 标志。SPI 中断
常用寄存器
SPI控制寄存器1(SPI_CR1)
SPI控制寄存器1 (SPI_CR1)(不用于I2S模式)SPI control register 1
偏移地址: 0x00 复位值: 0x0000
位 7
LSBFIRST: 帧格式 (Frame format)
0:先发送 MSB 1:先发送 LSB
注意: 正在通信时不应更改此位。
不适用于I2S 模式和SPI TI模式
位 6
SPE:SPI使能(SPI enable)
0:关闭外设 1:使能外设
注意: 1- 不适用于 I2S 模式。
注意: 2- 关闭 SPI 时,请按照第 27.3.8 节:关闭 SPI中所述的步骤操作。
位 5:3
BR[2:0]: 波特率控制 (Baud rate control)
000: fPCLK/2 100: fPCLK/32
001: fPCLK/4 101: fPCLK/64
010: fPCLK/8 110: fPCLK/128
011: fPCLK/16 111: fPCLK/256
注意: 正在通信时不应更改这些位。
不适用于 I2S 模式
SPI控制寄存器2(SPI_CR2)
SPI状态寄存器(SPI_SR)
SPI数据寄存器(SPI_DR)
SPI_I2S配置寄存器(SPI_I2S_CFGR)
SPI_I2S预分频寄存器(SPI_I2SPR)
SPI相关库函数:
stm32f4xx_spi.c/stm32f4xx_spi.h
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
void SPI_Init(SPI_TypeDef* SPIx,SPI_InitTypeDef* SPI_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx,FunctionalState NewState);
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx,uint8_t SPI_I2S_IT, FunctionalState NewState);
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx,uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
void SPI_I2S_SendData(SPI_TypeDef* SPIx,uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef*SPIx);
void SPI_DataSizeConfig(SPI_TypeDef* SPIx,uint16_t SPI_DataSize);
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef*SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx,uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef*SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef*SPIx, uint8_t SPI_I2S_IT);
程序配置过程:
①使能SPIx和IO口时钟
RCC_AHBxPeriphClockCmd() / RCC_APBxPeriphClockCmd();
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //使能SPI1时钟
②初始化IO口为复用功能
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
③设置引脚复用映射:
GPIO_PinAFConfig();
②初始化SPIx,设置SPIx工作模式
voidSPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
这一步全部是通过SPI1_CR1来设置,我们设置SPI1为主机模式,设置数据格式为8位,然后通过CPOL和CPHA位来设置SCK时钟极性及采样方式。并设置SPI1的时钟频率(最大37.5Mhz),以及数据的格式(MSB在前还是LSB在前)。
SPI_InitTypeDef 的定义:
typedef struct
{uint16_t SPI_Direction;
uint16_t SPI_Mode;
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;
uint16_t SPI_CPHA;
uint16_t SPI_NSS;
uint16_t SPI_BaudRatePrescaler;
uint16_t SPI_FirstBit;
uint16_t SPI_CRCPolynomial;}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的时钟的参数,从2分频到256分频8个可选值,初始化的时候我们选择256分频值
SPI_BaudRatePrescaler_256, 传输速度为84M/256=328.125KHz。
第八个参数SPI_FirstBit:设置数据传输顺序是MSB位在前还 LSB位在前,这里我们选择SPI_FirstBit_MSB高位在前。
第九个参数SPI_CRCPolynomial:是用来设置CRC校验多项式,提高通信可靠性,大于1即可。
SPI_InitTypeDef SPI_InitStructure;
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_256; //预分频 256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据指定的参数初始化外设 SPIx 寄存器③使能SPIx
voidSPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
④SPI传输数据
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_tSPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);
SPI 初始化函数的最后有一个启动传输,这句话最大的作用就是维持MOSI为高电平,而且这句话也不是必须的,可以去掉。