1 SPI协议
SPI(Serial Peripheral interface)串行外围设备接口是同步全双工的通信总线,在芯片的管脚上只占用四根线。
1.1 物理层
-
SS/NSS/CS
:从设备选择信号线(片选信号线)。由主设备控制,选择指定的从设备。当主机要选择从设备时,把该从设备的SS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以SS线置低电平为开始信号,以SS线被拉高作为结束信号。
-
SCK
(Serial Clock):时钟信号线。用于通讯数据同步,只能由主设备产生,两个设备之间通讯时,通讯速率受限于低速设备 -
MOSI
(Master Output, Slave Input):主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。 -
MISO
(Master Input, Slave Output):主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
1.2 协议层
SPI总线内部结构:
结构抽象图:
在时钟信号控制下,主机将要发送的数据写到数据缓存区(Memory),缓存区经过8位移位寄存器shift register,串口移位寄存器通过MOSI信号线将数据一位一位的移到从机,从机将MISO接口收到的数据经过移位寄存器一位一位的移到数据缓存区(Memory)。同时从机也将自己移位寄存器数据通过MOSI发送给主机,两个移位寄存器数据完成交互,读写同时进行。
因此,SPI读写操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;若主机要读取从机的一个字节,就必须发送一个空字节引发从机传输。
1.2.1 时钟极性与时钟相位
- 时钟极性
CPOL
:指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前、 CS片选线为高电平时SCK的状态)。CPOL
= 0:SCK在空闲状态时为低电平CPOL
= 1:SCK在空闲状态时为高电平
- 时钟相位
CPHA
:指数据的采样的时刻CPHA
= 0:MOSI/MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样(第一个跳变沿开始)CPHA
= 1:MOSI/MISO数据线上的信号将会在SCK时钟线的“偶数边沿”被采样(第二个跳变沿开始)
SPI模式 | 时钟极性CPOL | 时钟相位CPHA | 空闲时SCK时钟 | 采样时刻 |
---|---|---|---|---|
0 | 0 | 0 | 低电平 | 第一个边沿(奇) |
1 | 0 | 1 | 低电平 | 第二个边沿(偶) |
2 | 1 | 0 | 高电平 | 第一个边沿(奇) |
3 | 1 | 1 | 高电平 | 第二个边沿(偶) |
由CPOL
及CPHA
的不同状态,SPI分成了四种模式,主机与从机必须工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式0”与“模式3”。
1.2.2 通信协议
SPI相对于IIC,没有规定最大传输速率、设备地址、通信应答机制、流控制规则;只要四根线连接正确,SPI模式一致,将设备的CS片选线拉低,即可与其直接通信,且读写数据同时进行。
由图知:SCK空闲时为低电平,则CPOL = 0;第二个边沿开始采样,则CPHA = 1;即SPI模式2。
-
1 NSS片选线由高变低,是SPI通讯的起始信号
-
2/3/4/5奇数边沿触发、偶数边沿采样
-
6 NSS片选线由低变高,是SPI通讯的停止信号
1.3 软件模拟SPI通信
初始化代码:
void SPI_Init(void)
{
/*##-1- Enable peripherals and GPIO Clocks #########################*/
/* Enable GPIO TX/RX clock */
SPI_SCK_GPIO_CLK_ENABLE();
SPI_MISO_GPIO_CLK_ENABLE();
SPI_MOSI_GPIO_CLK_ENABLE();
SPI_NSS_GPIO_CLK_ENABLE();
/*##-2- Configure peripheral GPIO #######################*/
/* SPI SCK GPIO pin configuration */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = SPI_SCK_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET); // CLK 初始化低
/* SPI MISO GPIO pin configuration */
GPIO_InitStruct.Pin = SPI_MISO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
/* SPI MOSI GPIO pin configuration */
GPIO_InitStruct.Pin = SPI_MOSI_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = SPI_NSS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET); // NSS 初始化高
}
SCK NSS MOSI 为输出,MISO为输入;CLK 初始化低,NSS 初始化高
模拟 SPI 通信(CPOL=0,CPHA=0)
- 读/写单个字节
void SPI_WriteByte(uint8_t data)
{
uint8_t i = 0;
uint8_t temp = 0;
for(i = 0; i < 8; i++)
{
temp = ((data & 0x80) == 0x80) ? 1 : 0;
data = data << 1;
SPI_CLK(0); // CPOL=0
SPI_MOSI(temp);
SPI_Delay();
SPI_CLK(1); // CPHA=0 第一个边沿采样
SPI_Delay();
}
SPI_CLK(0); // CPOL=0 SCK空闲电平为低
}
uint8_t SPI_ReadByte(void)
{
uint8_t i = 0;
uint8_t data = 0;
for(i = 0; i < 8; i++)
{
data = data << 1;
SPI_CLK(0);
SPI_Delay();
SPI_CLK(1);
SPI_Delay();
if (SPI_MISO())
data++;
}
SPI_CLK(0);
return data;
}
- 读写同时进行
uint8_t SPI_WriteReadByte(uint8_t data)
{
uint8_t i = 0;
uint8_t temp = 0;
uint8_t read_data = 0;
for(i = 0; i < 8; i++)
{
temp = ((data & 0x80) == 0x80) ? 1 : 0;
data = data << 1;
read_data = read_data << 1;
SPI_CLK(0);
SPI_MOSI(temp);
SPI_Delay();
SPI_CLK(1);
SPI_Delay();
if (SPI_MISO())
read_data++;
}
SPI_CLK(0);
return data;
}
2 STM32硬件SPI
2.1 硬件SPI框架
2.2 SPI模式
2.3 SPI配置步骤
- 设置 BR[2:0] 位以定义串行时钟波特率(主模式需要,从模式时钟频率由其主机决定)
- 选择 CPOL 和 CPHA 位
- 设置 DFF 位,以定义 8 或 16 位数据帧格式
- 配置 SPI_CR1 寄存器中的 LSBFIRST 位以定义帧格式(先发MSB还是LSB)。(默认为
Motorola
模式,如果选择TI
模式,则不需要此步骤) - NSS引脚配置
- 主模式:如果 NSS 引脚配置成输入,在 NSS 硬件模式下,NSS 引脚在整个字节发送序列期间都连接到高电平信号;在 NSS 软件模式下,将
SPI_CR1
寄存器中的SSM
和SSI
位置1。如果 NSS 引脚配置成输出,只应将SSOE
位置 1。如果选择TI
模式,则不需要此 步骤。 - 从模式:在硬件模式下,NSS 引脚在整个字节 发送序列期间都必须连接到低电平。在 NSS 软件模式下,将
SPI_CR1
寄存器中的SSM
位置 1,将SSI
位清零。如果选择TI
模式,则不需要此步骤。
- 主模式:如果 NSS 引脚配置成输入,在 NSS 硬件模式下,NSS 引脚在整个字节发送序列期间都连接到高电平信号;在 NSS 软件模式下,将
- SPE 位置 1 使能SPI(MSTR 位清零表示从模式,置位为主模式)
2.4 事件标志
STM32F4/L0均有TI帧格式错误标志,F1则没有
2.4.1 状态标志
-
发送缓冲区为空 (TXE)
此标志置 1 时,表示发送缓冲区为空,可以将待发送的下一个数据加载到缓冲区中。对 SPI_DR 寄存器执行写操作时,将清零 TXE 标志。
-
接收缓冲区非空 (RXNE)
此标志置 1 时,表示接收缓冲区中存在有效的已接收数据。读取 SPI_DR 时,将清零该标志。
-
BUSY
BSY 标志由硬件置 1 和清零,用于指示 SPI 通信的状态,BSY = 1:表明SPI正忙于通信。但有个例外:在主模式的双向接收模式下(MSTR=1、BDM=1并且BDOE=0),在接收期间BSY标志保持为低。以下情况硬件将清零该标志:
- 传输完成时<主模式连续通信除外>
- 关闭SPI
- 发生主模式故障时 <MODF=1>
- 当通信不连续时,BSY 标志在各通信之间处于低电平。
- 当通信连续时:
- 在主模式下,BSY 标志在所有传输期间均保持高电平
- 在从模式下,BSY 标志在各传输之间的一个 SPI 时钟周期内变为低电平
2.4.2 错误标志
主模式故障 (MODF)
- 错误原因:当主器件的 NSS 引脚拉低(NSS 硬件模式下)或 SSI 位为 0(NSS 软件模式下)时,会发生主模式故障,自动将 MODF 位置 1。
- 对SPI外设的影响:
- 如果 ERRIE 位置 1,MODF 位将置 1,并生成 SPI 中断。
- SPE 位清零。这将关闭器件的所有输出,并关闭 SPI 接口。
- MSTR 位清零,从而强制器件进入从模式。
- 清除错误标志:在 MODF 位置 1 时,对 SPI_SR 寄存器执行读或写访问,然后对 SPI_CR1 寄存器执行写操作。
为避免包含多个 MCU 的系统中发生多从模式冲突,必须在 MODF 位清零序列期间将 NSS 引脚拉高。在该清零序列后,可以将 SPE 和 MSTR 位恢复到原始状态。
溢出错误(OVR)
- 错误原因:当主器件发送完数据字节,而从器件尚未将上一个收到的数据所产生的 RXNE 位清零时,将 出现溢出情况。
- 对SPI外设的影响:OVR 位置 1 并在 ERRIE 位置 1 时生成一个中断。此时接收器缓冲区内容不会更新,主器件后续发送的数据均丢失
- 清除错误标志:依次读取 SPI_DR 寄存器和 SPI_SR 寄存器可将 OVR 清除。
CRC错误
- 错误原因:移位寄存器中接收的值与 SPI_RXCRCR 的值不匹配
对SPI外设的影响及如何清除错误标志 手册未说明。
TI 模式帧格式错误
- 错误原因:SPI从模式下,且配置为符合 TI 模式协议,则在持续通信期间出现 NSS 脉冲 时,将检测到 TI 模式帧格式错误。
- 对SPI外设的影响:SPI_SR 寄存器中的 FRE 标志将置 1,发生错误时不会关闭 SPI,但会忽略 NSS 脉冲,并且 SPI 会等待至下一个 NSS 脉冲,然后 再开始新的传输。由于错误检测可能导致丢失两个数据字节,因此数据可能会损坏。
- 清除错误标志:读取 SPI_SR 寄存器。
2.5 主要寄存器
2.6 CubeMX配置
模式设置
- 全双工主机/从机模式
- 办双工主机/从机模式
- 只接收主机/从机模式
- 只发送主机/从机模式
硬件NSS信号
- 不使能
- NSS输入信号
- NSS输出信号
基本参数
- Frame Format帧格式:Motorola摩托罗拉 或 TI
- Data size数据大小:8位 或 16位
- First Bit:MSB/LSB先行
时钟参数
- 波特率分配因子:
- CPOL时钟极性:low or high
- CPHA时钟相位:1 edge or 2 edge
高级参数
- CRC循环校验:使能/失能
- NSS信号类型:软件
2.7 HAL库SPI函数
MX SPI 初始化参数:
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; //主模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 双向全双工
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据长度
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0, CLK极性为低电平
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0, CLK相位为第一个边沿采样
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件NSS硬件
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; // 波特率 = fpclk / 2
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // 最高位先发送
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // 不使用TI模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 不使用CRC校验
hspi1.Init.CRCPolynomial = 7; // CRC多项式为7
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
轮询方式的SPI发送、接收、收发函数:
HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
uint32_t Timeout);
中断、DMA方式也均有此类函数。
参考:
END