SPI 主从双机通讯的实现
SPI 主从双机通讯的实现
前言
在嵌入式学习的路上,SPI 是一个非常常见的外设,但是大部分使用的场景是作为主机,关于从机的实现可以参考的例程比较少见,本着学习理解的态度,对 SPI 的双机通讯做了一些试验,发现一个好的 SPI 从机实现需要考虑的方方面面非常多,本文仅实现通选,未实现协议层的传输。
本文使用 ART-Pi ,单板完成 SPI 双机通讯试验,主机是 SPI2 ,从机使用 SPI4.
一、SPI 的模式
SPI是串行外设接口(Serial Peripheral Interface)的缩写。是 Motorola 公司推出的一种同步串行接口技术,是一种高速的,全双工,同步的通信总线。
SPI 的主从双方必须设置一致的通讯模式才能完成正常的通讯,有部分厂商的模块可能会同时支持两种模式,但是在 MCU 之间的通讯只能配置为双方一致的模式。
SPI 通过 CPOL(时钟极性) 和 CPHA(时钟相位) 来确定数据的传输模式,因此有以下四种模式;
模式 | CPOL(时钟极性) | CPHA(时钟相位) |
---|---|---|
模式0 | CPOL = 0 | CPHA = 0 |
模式1 | CPOL = 0 | CPHA = 1 |
模式2 | CPOL = 1 | CPHA = 0 |
模式3 | CPOL = 1 | CPHA = 1 |
CPOL(时钟极性):表示 CLK 处于空闲时的状态,可以是高电平也可以是低电平
CPHA(时钟相位):表示数据采样的边沿以及发送的边沿。
从设备的时钟由主设备通过 SCLK 提供,MOSI、MISO 则基于此脉冲完成数据传输。
接线方式如下图所示:
二、SPI 的工作模式
SPI 有 3 种工作模式,分别是轮询,中断和 DMA,因为 SPI 是全双工,发送数据的同时也会收到数据,也就是在 SPI 的 CLK 产生之后,发送和接收数据寄存器里面保存的就是实际的数据。本次实验主机使用轮询模式,从机使用什么模式接下来对不同的模式进行分析。下图描述了 SPI 传输的过程
- 轮询模式
轮询模式顾名思义不断的轮训检查
while((initial_TxXferCount >0UL)||(initial_RxXferCount >0UL))
{
...
}
检查要接收的数据和要发送的数据是否为 0 ,如果不为 0 ,就是不断检查收发寄存器的状态标志位,直到接收到预期的数据或者直至超时退出 。
一般场景主机使用轮询模式即可,因为从机需要接收到主机的 CLK 之后才能收发数据,从机使用轮询模式,那么就需要从机一直守在这,一直检查寄存器,那从机就干不了其他事情了,因此从机不适合使用轮询模式。
- 中断模式
中断模式下会根据对 SPI 的配置来使能对应的中断位。
/* Enable EOT, DXP, UDR, OVR, FRE, MODF and TSERF interrupts */
__HAL_SPI_ENABLE_IT(hspi,(SPI_IT_EOT | SPI_IT_DXP | SPI_IT_UDR | SPI_IT_OVR | SPI_IT_FRE | SPI_IT_MODF | SPI_IT_TSERF));
中断类型 | 描述 |
---|---|
EOT | 传输结束 |
DXP | TXP(TXFIFO 中有空间可以放数据) 和 RXP(RXFIFO 中有空间可以放数据) 都有效 |
UDR | 下溢 |
OVR | 上溢 |
FRE | 帧格式错误 |
MODF | 模式故障 |
TSERF | 可重新装载 TSER |
通过上面的配置可以看到,如果 SPI 的 FIFO 设置为1 的情况下,那么就是每收发一个字节都会产生一个中断,虽然可以用,大数据高速率传输会导致中断响应太频繁,导致实时性下降。
- DMA 模式
DMA 模式又分为循环模式和普通模式。
- 循环模式就是 DMA 自己不停的从 RAM 与 SPI DR 寄存器交换数据,每次收发数据完成都会产生一个完成中断,在考虑主从之间协议交互的时候,不能确定从机什么时候数据已经准备好了。如果主机发起的 CLK 读取的数据少于 DMA BUFF 的缓存区则会出现数据会继续在 RAM 地址之后放置。
- 普通模式每次使能 DMA 一次传输完成之后,需要用户再次使能 DMA 传输。从机可以在数据已经准备妥当之后,再激活 DMA 的传输,同时也可以通过其他的方式去通知到主机。再次启动 DMA 传输之后,从机接收的数据会从 DMA 缓存区的起始位置开始存放数据,方便数据的解析。
综上所述,本次实验主机使用轮询模式,从机使用 DMA 普通模式。
三、SPI 的配置
本次实验借助 CUBEMX 工具来进行配置。
- 使能 SPI 的时钟
将 SPI2 和 SPI4 的时钟源都配置为 100M
- 对主机进行配置
设置 SPI 为全双工,使用软件 CS,8 个数据位,MSB,做了 256 分频是考虑到实验使用的是杜邦线,CPOL 和 CPHA 怎么配置无所谓,确保从机和主机一致即可。
- SPI 双机配置
配置为从机模式,使用硬件 CS,其他配置保持和主机一致,因为从机的时钟由主机人提供,所以这里可以看到从机不需要配置时钟。
收发模式都设置为 DMA 普通传输模式。
使能外设中断和 DMA 中断。
- 关闭 CACHE
为了保证 CACHE 对本次实验的影响,默认关闭 CACHE
实验代码
在编写实验代码之前,先梳理一下应该如何测试:
- 使能外设(CUBEMX 配置会自动完成);
- 启动 SPI 从机的 DMA 传输;
- 主机读取数据;
- 从机接收收据,接收完成后执行接收完成回调函数;
- 校验数据。
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_SPI2_Init();
MX_SPI4_Init();
MX_UART4_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* USER CODE BEGIN 2 */
/* 拉高主机的 CS */
HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_SET);
/* 设置从机的收发数据缓存区,长度为2 */
HAL_SPI_TransmitReceive_DMA(&hspi4, spi4_txbuff, spi4_rxbuff,2);
while(1)
{
/* 在从机 DMA 收发完成回调中对该标志位置 1 */
if(spi_flag)
{
spi_flag =0;
/* 对比 SP2 主机接收的数据与 SPI4 从机发送的数据是否一致 */
if(Buffercmp(spi2_rxbuff, spi4_txbuff,2))
{
master_error_num++;
printf("SPI_MASTER error is %d\r\n",master_error_num);
}
/* 修改 SPI 从机发送缓存区中的数据 */
spi4_txbuff[0]++;
spi4_txbuff[1]+=2;
/* 开启一轮新的传输 */
HAL_SPI_TransmitReceive_DMA(&hspi4, spi4_txbuff, spi4_rxbuff,2);
}
else
{
/* 修改 SPI 主机发送数据 */
spi2_txbuff[0]++;
spi2_txbuff[1]+=2;
/* 拉低片选 等待状态稳定后开始传输*/
HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_RESET);
HAL_Delay(2);
/* SPI 主机产生 CLK ,数据开始传输 */
HAL_SPI_TransmitReceive(&hspi2, spi2_txbuff, spi2_rxbuff,2,1000);
/* 释放片选 */
HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_SET);
}
}
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi ==&hspi4)
{
/* 对比 SPI2 主机发送的消息与 SPI4 从机接收的消息是否一致 */
if(Buffercmp(spi2_txbuff, spi4_rxbuff,2))
{
slave_error_num++;
printf("SPI_SLAVE error is %d\r\n",slave_error_num);
}
spi_flag =1;
}
}
注意事项
- 实验使用了 DMA,确保数据的缓存区是在 AXI RAM 区域段,也就是既地址是(0x2400 0000) 之后
- 在主机传输数据前,确保从机已经准备就绪
总结
本文仅仅是双机通信的简单实现,只能验证 SPI 从机的功能正常,假想了一下 SPI 主从双机协议栈实现的需要考虑的问题点,希望有大佬能指点一下。
- 从机如何获取 CS 的状态
主机在发起之前会拉低 CS,从机使用了硬件 CS,这个硬件 CS 不会产生中断通知到用户,时候需要从机单独设计一个引脚和 CS 连在一起来捕获这个下降沿。捕获这个下降沿有什么作用。
- 主机获取从机的状态
从机数据是否准备就绪,从机提供一个 BUSY 引脚,主机通过读取 BUSY 引脚的状态来确定从机的状态,在高速传输过程中,从机也可以主动的通知到主机数据已经准备就绪,可以来读数据了。
- 全双工模型的实现
SPI 是全双工的,可以同时收发数据,常见的场景如读写 FLASH,这些都是主从式的半双工模型,哪些场景下会真正的用到 SPI 的全双工,全双工是如何实现的。