一、SPI简介
1.SPI总线特点
(1)四条通信线
SPI需要SCK、MISO、MOSI、NSS四条通信线来完成数据传输 ,每增加一个从机,多一条NSS通信线。
(2)多主多从
SPI总线允许有多个主机和多个从机。
(3)传输速率
比常见的I2C协议要快,通常为Mbit级别。
(4)通信方式
是同步全双工串行通信,每次交换一个字节(8bit)或者两个字节(16bit),可选择高位先行或者低位先行。
2.SPI总线术语
-
SCK:串行时钟线,作为主设备的输出、从设备的输入。
-
MISO:主机输入/从机输出数据线,该引脚在主模式下接收数据,在从模式下发送数据。
-
MOSI:主机输出/从机输入数据线,该引脚在主模式下发送数据,在从模式下接收数据。
-
NSS:低电平有效的从机选择线。NSS引脚是可选的,一般选择用软件管理。
3.SPI硬件构成
所有SPI设备的SCK、MOSI、MISO分别连在一起,主机另外引出多条NSS控制线,分别接到各从机的NSS引脚,输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
4.位传输
(1)起始信号
SS引脚从高电平切换到低电平 。
(2)终止信号
SS引脚从低电平切换到高电平 。
5.数据传输模式
在使用STM32的SPI外设时,需要设置时钟相位(CPHA)和时钟极性(CPOL)。
(1)模式0
- CPOL=0:空闲状态时,SCK为低电平
- CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
(2)模式1
- CPOL=0:空闲状态时,SCK为低电平
- CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
(3)模式2
- CPOL=1:空闲状态时,SCK为高电平
- CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
(4)模式3
- CPOL=1:空闲状态时,SCK为高电平
- CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
二、STM32的SPI外设
1.STM32的SPI外设的特性:
- 8位或16位传输帧格式
- 可选的时钟频率、高/低位先行
- 支持多主模式
- 可触发中断的专用发送和接收标志、主模式故障、过载以及CRC错误标志
- SPI总线忙状态标志
- 支持DMA功能的1字节发送和接收缓冲器,产生发送和接收请求
- 兼容I2S协议
2.STM32 SPI外设的基本结构:
3.状态标志
应用程序可以通过TXE、RXNE和BUSY三个状态标志来完全监控SPI总线的状态。
(1)发送缓冲器空闲标志(TXE)
此标志为1时表示发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。
(2)接收缓冲器非空标志(RXNE)
此标志为1时表示接收缓冲器中包含有效的接收数据。
(3)忙标志(BUSY)
此标志为1时表示SPI正忙于通信,但有一个例外:在主模式下的双向接收模式下,在接收期间BUSY标志保持为低。
三、STM32 SPI外设的通信方式
1.主模式
在SPI主模式下,MOSI引脚是数据输出,而MISO是数据输入,SCK引脚产生串行时钟。
2.从模式
在 SPI从模式下,MOSI引脚是数据输入,而MISO是数据输出,SCK引脚用于接收来自主机的串行时钟。
四、STM32 SPI外设使用流程
虽然不同器件实现的功能不同,但是只要遵循SPI协议,其通信方式都是一样的,配置流程也基本相同。对于STM32,首先要对SPI进行配置,使其能够正常工作,再结合不同器件的驱动程序,完成STM32与不同器件的数据传输。这里只介绍STM32作为主机的使用流程,使用流程和参考代码如下:
- 配置GPIO
- 配置SPI
- 写一个产生起始条件的函数
- 写一个产生终止条件的函数
- 写一个交换数据的函数
void SPI_W_SS(uint8_t Bit) //根据Bit设置SS引脚的电平
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)Bit);
}
void SPI_Init(void) //配置GPIO和SPI
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//将PA4引脚初始化为推挽输出,作为NSS片选引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//将PA5和PA7引脚初始化为复用推挽输出,即配置SCK和MOSI
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//将PA6引脚初始化为上拉输入,即配置为MISO
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //模式,选择为SPI主模式
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
//方向,选择2线全双工
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据宽度,选择为8位
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //先行位,选择高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
//波特率分频,选择128分频
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //SPI极性,选择低极性
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS,选择由软件控制
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多项式,暂不用,给默认值7
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE); //使能SPI1,开始运行
MySPI_W_SS(1); //NSS默认高电平
}
void MySPI_Start(void) //产生起始信号
{
MySPI_W_SS(0); //拉低SS
}
void MySPI_Stop(void) //产生终止信号
{
MySPI_W_SS(1); //拉高SS
}
uint8_t MySPI_SwapByte(uint8_t ByteSend) //交换一个字节数据
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
//等待发送数据寄存器空
SPI_I2S_SendData(SPI1, ByteSend);
//写入数据到发送数据寄存器,开始产生时序
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
//等待接收数据寄存器非空
return SPI_I2S_ReceiveData(SPI1);
//读取接收到的数据并返回
}
代码仅供参考,具体使用需要结合相关从机器件的开发文档。