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模式1。 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硬件SPI2.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 模式,则不需要此步骤。
- SPE 位置 1 使能SPI(MSTR 位清零表示从模式,置位为主模式)
2.4 事件标志STM32F4/L0均有TI帧格式错误标志,F1则没有 2.4.1 状态标志 此标志置 1 时,表示接收缓冲区中存在有效的已接收数据。读取 SPI_DR 时,将清零该标志。 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信号 基本参数 - Frame Format帧格式:Motorola摩托罗拉 或 TI
- Data size数据大小:8位 或 16位
- First Bit:MSB/LSB先行
时钟参数 - 波特率分配因子:
- CPOL时钟极性:low or high
- CPHA时钟相位:1 edge or 2 edge
高级参数 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方式也均有此类函数。
|