本次我们学习一下STM32的一个基本外设 --- SPI,全程参考手册讲解,讲述SPI的工作模式和作用,让大家快速掌握和了解SPI通讯协议。本篇博客大部分是自己收集和整理,借鉴了很多大佬的图片和知识点整理,如有侵权请联系我删除。(@小麦大叔,@Aspirant-GQ)
本次实验板子使用的是正点原子精英版,芯片是STM32F103ZET6,需要资料可以@我拿取。
本博客内容原创,创作不易,转载请注明
SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口,是一种高速的,全双工,同步的通信总线。
可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。
应用场所:SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。
支持最高的 SCK 时钟频率为 fpclk/2:
STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。
就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。
在点对点的通信中,SPI接口不需要进行寻址操作,在多个从器件的系统中,每个从器件需要独立的使能信号。
五线制:
MOSI(DO) | 作为主器件时为数据输出,作为从器件时为数据输入 |
MISO(DI) | 作为主器件时为数据输入,作为从器件是为数据输出 |
SCLK | 时钟信号线,由主器件产生 |
NSS(CS) | 从器件使能信号,由主器件控制,低电平有效 |
GND | 公共地线 |
四线制:
MOSI | 作为主器件时为数据输出,作为从器件时为数据输入 |
SCLK | 时钟信号线,由主器件产生 |
NSS(CS) | 从器件使能信号,由主器件控制,低电平有效 |
GND | 公共地线 |
1)MOSI 主设备数据输出,从设备数据输入:MOSI专门负责主机向从机传输数据
不同器件引脚的MOSI也可以是:SOMI,DIN,DI,SDI或SI(在主机端);
MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。
数据输出通过MOSI发出,数据在时钟的上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。
2)MISO 主设备数据输入,从设备数据输出:MISO专门负责从机向主机传输数据
不同器件引脚的MOSI也可以是:MISO也可以是SIMO,DOUT,DO,SDO或SO(在主机端);
3)SCLK 时钟信号,由主设备产生:
SCK提供时钟脉冲,MOSI则基于这个时钟脉冲完成数据的传输。
统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据。
不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。
4)NSS(CS) 从设备片选信号,由主设备控制。
不同于IIC,如果和其他从设备通讯,不需要寻址操作,只需要通过片选信号就能和从机通信。
NSS是控制芯片是否被选中,也就是说只有片选信号为预先规定的使能信号(高电平或低电平),对芯片操作才有效。
● 软件NSS模式:可以通过设置SPI_CR1寄存器的SSM位来使能这种模式(见图211)。在这种 模式下NSS引脚可以用作它用,而内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动
● 硬件NSS模式,分两种情况:
─ NSS输出被使能:当STM32F10xxx工作为主SPI,并且NSS输出已经通过SPI_CR2寄存 器的SSOE位使能,这时NSS引脚被拉低,所有NSS引脚与这个主SPI的NSS引脚相连并 配置为硬件NSS的SPI设备,将自动变成从SPI设备。
─当一个SPI设备需要发送广播数据,它必须拉低NSS信号,以通知所有其它的设备它是主 设备;如果它不能拉低NSS,这意味着总线上有另外一个主设备在通信,这时将产生一个 硬件失败错误(Hard Fault)。
─ NSS输出被关闭:允许操作于多主环境。
SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式。
所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。
1)起始信号和停止信号:
SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号
2)数据有效性:
SPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。
SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。
3)通讯模式:主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。
4)SPI总线工作方式:
SPI有四种传输方式:上升沿、下降沿、前沿、后沿。SPI只有主模式和从模式之分,没有读和写的说法。
5)时钟极性CPOL和时钟相位CPHA:
SPI_CR寄存器的CPOL和CPHA位,能够组合成四种可能的时序关系。
时钟极性CPOL
时钟极性(CPOL):决定空闲状态时时钟线电平状态 |
时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号 |
CPOL = 0:串行时钟线空闲状态为低电平,下降沿采集数据 |
CPOL = 1:串行时钟线空闲状态为高电平,上升沿采集数据 |
如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0 |
时钟相位CPHA
时钟相位(CPHA):决定第一个数据的采集时刻 |
CPHA是指数据的采样时刻,SCK的信号可以看作方波 |
CPHA = 0:在串行时钟线的第一个跳变数据被采集 |
CPHA = 1:在串行时钟线的第二个跳变数据被采集 |
CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。 |
从上面的表格我们可以看到,时钟极性CPOL是控制空闲的时候SCK的电平变化的,时钟相位CPHA是控制我们在那个电平边沿采集数据的,由此我们可以得到时钟不同的搭配来采集数据。
CPOL = 0,CPHA = 0 | SCK串行时钟线空闲状态为低电平,在SCK的第一个跳变数据(上降沿)被采集 |
CPOL = 0,CPHA = 1 | SCK串行时钟线空闲状态为低电平,在SCK的第一个跳变数据(下降沿)被采集 |
CPOL = 1,CPHA = 0 | SCK串行时钟线空闲状态为高电平,在SCK的第二个跳变数据(下降沿)被采集 |
CPOL = 1,CPHA = 1 | SCK串行时钟线空闲状态为高电平,在SCK的第二个跳变数据(上降沿)被采集 |
注意:
1. 在改变CPOL/CPHA位之前,必须清除SPE位将SPI禁止。
2. 主和从必须配置成相同的时序模式。
3. SCK的空闲状态必须和SPI_CR1寄存器指定的极性一致(CPOL为’1’时,空闲时应上拉SCK为 高电平;CPOL为’0’时,空闲时应下拉SCK为低电平)。
SPI通讯协议规定传输的数据为8位,传输顺序是高位在前,低位在后。
SPI是一个数据交换协议,主机给从机发送一个信息,那么从机就会返回一个信息给主机。也就是说,当主机读从机的时候,需要先发一个位的数据给从机,从机才会发一个位的数据回来给主机。
SPI是一个同步的数据总线,也就是说它是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的完美同步。
时钟的作用就是告诉接收端在确切的时机对数据线上的信号进行采样。
1)主机先将NSS
信号拉低,这样保证开始接收数据;
2)当接收端检测到时钟的边沿信号时,它将立即读取数据线上的信号,这样就得到了一位数据(1bit
);
3)主机发送到从机时:主机产生相应的时钟信号,然后数据一位一位地将从MOSI
信号线上进行发送到从机;
4)主机接收从机数据:如果从机需要将数据发送回主机,则主机将继续生成预定数量的时钟信号,并且从机会将数据通过MISO
信号线发送;
注意:SPI是“全双工”(具有单独的发送和接收线路),因此可以在同一时间发送和接收数据,另外SPI的接收硬件可以是一个简单的移位寄存器。这比异步串行通信所需的完整UART要简单得多,并且更加便宜;
1)主机发送数据
当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE ”标志位”会被置 1,表示传输完一帧,发送缓冲区已空 |
地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。 |
2)主机接收数据
当接收完一帧数据的时候,“ RXNE“标志位”会被置 1,表示传输完一帧,接收缓冲区非空 |
接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。 |
3)数据帧格式
通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小 |
4)先行位
通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行 |
主机和从机通信都是通过NSS片选信号线来通信的,每一台从机都是单独和主机相连,每一条NSS线都是独立的,主机想要和那一台从机通信,就把相应的NSS数据线拉低电平,保持其他从机的为高电平,就能和对应的从机进行数据通信了。
如果同时将两个NSS
信号线拉低,则可能会出现乱码,因为从机可能都试图在同一条MISO
线上传输数据,最终导致接收数据乱码,所以最好一次只能和一个从机通信。
在数字通信世界中,在设备信号(总线信号或中断信号)以串行的方式从一 个设备依次传到下一个设备,不断循环直到数据到达目标设备的方式被称为菊花链。
1.菊花链的最大缺点是因为是信号串行传输,所以一旦数据链路中的某设备发生故障的时候,它下面优先级较低的设备就不可能得到服务了;
2.另一方面,距离主机越远的从机,获得服务的优先级越低,所以需要安排好从机的优先级,并且设置总线检测器,如果某个从机超时,则对该从机进行短路,防止单个从机损坏造成整个链路崩溃的情况;
非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同).
因为速度函数没有被封装,需要我们用寄存器来给他封装一个函数。
要初始化 SPI2,设置 SPI2 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟极性及采样方式。并设置 SPI2 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是LSB 在前)。
#include "spi.h"
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
SPI2_ReadWriteByte(0xff);//启动传输
}
//SPI 速度设置函数
//SpeedSet:
//SPI_BaudRatePrescaler_2 2分频
//SPI_BaudRatePrescaler_8 8分频
//SPI_BaudRatePrescaler_16 16分频
//SPI_BaudRatePrescaler_256 256分频
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
SPI2->CR1&=0XFFC7;
SPI2->CR1|=SPI_BaudRatePrescaler; //设置SPI2速度
SPI_Cmd(SPI2,ENABLE);
}
这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
1)发送数据前要等待发送缓冲区为空,靠TXE标志判断
2)开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回
3)由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
SPI的知识点实在太多了,不过配置的时候不是很麻烦,初始化相对来说还是比较简单的,我们不用纠结他底层是什么样的,我们保证我们可以使用就行,后期我会出OLED和flash的芯片SPI使用教程,大家如果对我的博客有疑问或者错误,可以@我修改,大家相互交流。
点赞收藏关注博主,不定期分享单片机知识,互相学习交流。