问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

SPI 主从双机通讯的实现

创作时间:
作者:
@小白创作中心

SPI 主从双机通讯的实现

引用
1
来源
1.
https://club.rt-thread.org/ask/article/c91146d03f664cf5.html

前言

在嵌入式学习的路上,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 传输的过程

  1. 轮询模式

轮询模式顾名思义不断的轮训检查

while((initial_TxXferCount >0UL)||(initial_RxXferCount >0UL))
{
    ...
}

检查要接收的数据和要发送的数据是否为 0 ,如果不为 0 ,就是不断检查收发寄存器的状态标志位,直到接收到预期的数据或者直至超时退出 。

一般场景主机使用轮询模式即可,因为从机需要接收到主机的 CLK 之后才能收发数据,从机使用轮询模式,那么就需要从机一直守在这,一直检查寄存器,那从机就干不了其他事情了,因此从机不适合使用轮询模式。

  1. 中断模式

中断模式下会根据对 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 的情况下,那么就是每收发一个字节都会产生一个中断,虽然可以用,大数据高速率传输会导致中断响应太频繁,导致实时性下降。

  1. DMA 模式

DMA 模式又分为循环模式和普通模式。

  • 循环模式就是 DMA 自己不停的从 RAM 与 SPI DR 寄存器交换数据,每次收发数据完成都会产生一个完成中断,在考虑主从之间协议交互的时候,不能确定从机什么时候数据已经准备好了。如果主机发起的 CLK 读取的数据少于 DMA BUFF 的缓存区则会出现数据会继续在 RAM 地址之后放置。
  • 普通模式每次使能 DMA 一次传输完成之后,需要用户再次使能 DMA 传输。从机可以在数据已经准备妥当之后,再激活 DMA 的传输,同时也可以通过其他的方式去通知到主机。再次启动 DMA 传输之后,从机接收的数据会从 DMA 缓存区的起始位置开始存放数据,方便数据的解析。

综上所述,本次实验主机使用轮询模式,从机使用 DMA 普通模式。

三、SPI 的配置

本次实验借助 CUBEMX 工具来进行配置。

  1. 使能 SPI 的时钟

将 SPI2 和 SPI4 的时钟源都配置为 100M

  1. 对主机进行配置

设置 SPI 为全双工,使用软件 CS,8 个数据位,MSB,做了 256 分频是考虑到实验使用的是杜邦线,CPOL 和 CPHA 怎么配置无所谓,确保从机和主机一致即可。

  1. SPI 双机配置

配置为从机模式,使用硬件 CS,其他配置保持和主机一致,因为从机的时钟由主机人提供,所以这里可以看到从机不需要配置时钟。

收发模式都设置为 DMA 普通传输模式。

使能外设中断和 DMA 中断。

  1. 关闭 CACHE

为了保证 CACHE 对本次实验的影响,默认关闭 CACHE

实验代码

在编写实验代码之前,先梳理一下应该如何测试:

  1. 使能外设(CUBEMX 配置会自动完成);
  2. 启动 SPI 从机的 DMA 传输;
  3. 主机读取数据;
  4. 从机接收收据,接收完成后执行接收完成回调函数;
  5. 校验数据。
/* 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 主从双机协议栈实现的需要考虑的问题点,希望有大佬能指点一下。

  1. 从机如何获取 CS 的状态

主机在发起之前会拉低 CS,从机使用了硬件 CS,这个硬件 CS 不会产生中断通知到用户,时候需要从机单独设计一个引脚和 CS 连在一起来捕获这个下降沿。捕获这个下降沿有什么作用。

  1. 主机获取从机的状态

从机数据是否准备就绪,从机提供一个 BUSY 引脚,主机通过读取 BUSY 引脚的状态来确定从机的状态,在高速传输过程中,从机也可以主动的通知到主机数据已经准备就绪,可以来读数据了。

  1. 全双工模型的实现

SPI 是全双工的,可以同时收发数据,常见的场景如读写 FLASH,这些都是主从式的半双工模型,哪些场景下会真正的用到 SPI 的全双工,全双工是如何实现的。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号