备注

当前文档对应的版本为 ESP-IDF v5.5.0

SPI Slave Sender 示例

[English]

示例说明

本示例展示了在 ESP-IDF 中如何使用 SPI 主机驱动与另一台设备进行数据通信,主机在发送数据的同时接收从机返回的信息,并通过握手信号确保仅在从机准备就绪时才发起事务。有关 SPI 主机模式的说明可参考 SPI 主机驱动程序。学习该示例前,建议先掌握 FreeRTOS 信号量和队列的相关内容,其基础说明及 API 使用示例可参考 信号量和互斥锁 和 队列管理 文档。

运行方法

该示例需要与 Receiver 示例 配合使用,两者分别烧录到两块 ESP32 系列芯片,并正确连接信号线,从而实现完整的数据交换。虽然示例名称中包含 Slave,但 Sender 实际上配置为 SPI 主机,在通信中承担产生时钟和控制片选的角色,负责驱动 Receiver(SPI 从机)完成收发。

示例完整代码见 SPI Slave Sender 示例。运行前的配置说明、对应 GPIO 引脚、构建与烧录流程详见 spi_slave 目录下的 README.md 文件。

如需自定义配置项及查看默认值,可参考 宏定义说明 部分。

头文件说明

本示例所使用的头文件涵盖了 FreeRTOS 任务管理与同步、日志输出、SPI 主机驱动以及 GPIO 与定时器控制等模块,为 SPI 主机的初始化、数据传输和任务调度提供支持。

各头文件按功能分类如下:

  1. FreeRTOS 任务调度 :提供任务创建与调度、信号量和队列功能,用于并发任务管理和多任务间的通信。

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
  1. 系统工具 :提供标准输入输出、数据类型定义以及日志输出功能。

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_log.h"
  1. SPI 从机驱动 :提供 SPI 主机模式的初始化、设备配置及数据传输功能。

#include "driver/spi_master.h"
  1. GPIO 控制 :提供 GPIO 引脚配置及电平控制功能。

#include "driver/gpio.h"
  1. 定时器:提供基于微秒级精度的定时和回调功能,用于任务延时或周期性操作。

#include "esp_timer.h"

宏定义说明

本示例中涉及的宏定义主要用于配置 SPI 外设的引脚、控制器选择以及握手信号引脚,便于后续 SPI 数据传输和任务调度。

宏定义按功能分类如下:

  1. SPI 主机配置

  • 定义 SPI 主机所使用的控制器,本示例固定使用 SPI2_HOST。

#define RCV_HOST    SPI2_HOST
  • 定义 SPI 的关键引脚,包括握手、数据输入/输出、时钟以及片选引脚。

#define GPIO_HANDSHAKE      2    // 握手信号
#define GPIO_MOSI           12   // 主机输出,从机输入
#define GPIO_MISO           13   // 主机输入,从机输出
#define GPIO_SCLK           15   // SPI 时钟
#define GPIO_CS             14   // 片选信号

备注

  • 握手引脚用于主机与从机的数据同步控制,可根据硬件设计选择合适 GPIO。

  • 片选引脚用于选择 SPI 从机,保证总线上多从机的正确通信。

任务函数说明

本示例定义了一个中断服务函数(ISR),用于响应握手 GPIO 触发的中断。当从机通过握手引脚发出就绪信号时,该中断会被触发,主机通过该函数获知从机已准备好,可以进行数据传输。

static void IRAM_ATTR gpio_handshake_isr_handler( void* arg );

备注

  • 函数使用 IRAM_ATTR 修饰,保证函数放在内部 RAM 中,提高中断响应速度。

  • 函数传参为用户自定义参数指针,常用于传递触发源或上下文信息。在本示例中,该函数并未对传参内容进行访问或修改,仅用于满足回调函数的函数原型要求。

该函数的执行逻辑为:

  1. 中断抖动过滤:为了避免握手 GPIO 上的干扰或信号反弹导致重复触发中断,函数通过记录上次中断时间并与当前中断时间进行比较,如果两次中断间隔小于设定阈值(1 ms),则忽略此次中断。

  • 定义变量 lasthandshaketime_us 用于记录上次中断的时间。

  • 调用 esp_timer_get_time() 获取当前时间(微秒级)并保存至变量 currtime_us。进一步介绍及参数说明可参考 ESP 定时器。

  • 通过以上两个变量获取两次中断触发的时间间隔 diff。

  • 判断时间间隔是否小于设定阈值(1 ms),如果小于,则直接返回,忽略此次中断;否则,更新上次中断的时间为当前时间,并继续执行后续操作。

  1. 发出同步信号:为了通知等待的任务从机已准备好,可以开始 SPI 数据传输,函数使用 FreeRTOS 信号量机制,从 ISR 安全地释放信号量,并在必要时触发任务切换。

  • 定义标志变量 mustYield,用于指示是否需要在中断结束后立即切换任务,初始化为 false。

  • 调用 xSemaphoreGiveFromISR() 释放信号量。进一步介绍及参数说明可参考 释放信号量资源。

  • 判断 mustYield 是否为 true,如果是则调用 portYIELD_FROM_ISR() 切换到高优先级任务执行。

主函数说明

本示例演示了 SPI 主机的初始化、与从机设备建立通信并传输数据,以及资源释放的步骤。

  1. 定义变量:

  • 定义返回值变量 ret 用于保存 SPI 外设相关函数的返回状态,判断操作是否成功,并可根据返回值进行错误处理或打印提示信息。

  • 定义设备句柄变量 handle,用于标识和管理 SPI 总线上注册的从设备。

  1. 配置/初始化 SPI 总线

  2. 配置/初始化 SPI 从机接口

  3. 配置握手信号 GPIO:在 SPI 从机与主机通信中,为了实现可靠的数据传输,通常会使用一个握手信号(handshake line)来通知主机从机已准备好发送或接收数据。

  1. 配置握手线 中断服务。

  • 安装并绑定中断服务,以便握手信号变化时能够触发对应处理。

  • 在绑定中断服务前,再次显式设置引脚的中断触发模式为 GPIO_INTR_POSEDGE,防止硬件初始化或驱动库内部修改中断配置。

备注

再次设置中断触发模式属于冗余但保险的操作,确保握手 GPIO 的中断触发类型确实为上升沿触发,从而保证主从通信的可靠性。

  1. SPI 主机发送/接收事务的准备工作:

  • 定义计数变量 n 并将其初始化为 0,用于记录或标识当前传输的次数,方便调试或生成发送数据。

  • 定义数据缓冲区数组,为 SPI 主机的数据发送和接收提供存储空间,保证每次传输有确定的存储区域:

    • 定义变量 sendbuf 并初始化为 0,用作发送数据缓冲区。

    • 定义变量 recvbuf 并初始化为 0,用作接收数据缓冲区。

    • 数组长度设置为 128,表示缓冲区可以存放最多 128 个字节 的数据,可根据实际应用需求调整字节容量。

  • 定义 SPI 事务结构体 t。

  • 调用 xSemaphoreCreateBinary() 创建一个信号量,并保存其句柄至变量 rdySem。进一步介绍及参数说明可参考 创建二值信号量。

  • 调用 xSemaphoreGive() 向信号量 rdySem 发送一个“可用”信号,用于通知等待的任务从机已准备好或数据已经到达,可以继续执行后续操作。进一步介绍及参数说明可参考 释放信号量资源。

  1. 构建需要发送给 SPI 从机的数据,并在数据中记录本次传输编号以及上一次从机返回的数据。

  • 调用 snprintf() 将格式化字符串写入发送缓冲区 sendbuf,保证不会越界。

  • 检查返回值,若发送数据超出缓冲区大小,则打印 Data truncated,防止溢出。

  1. 设置事务信息并执行:

  • 执行事务前需要调用 xSemaphoreTake() 阻塞等待信号量,直到从机通过握手信号表明可以进行下一次传输。超时时间设置为 portMAX_DELAY 表示无限等待。进一步介绍及参数说明可参考 获取信号量资源。

  1. 打印从机返回的数据,同时更新本次传输计数。

  2. 循环结束后调用 spi_bus_remove_device() 释放 SPI 总线资源,断开主机与从机的关联,并检查返回值确保成功。进一步介绍及参数说明可参考 SPI 主机驱动。

备注

在实际应用中,SPI 数据传输任务通常为长期运行或常驻任务,因此该步骤一般不被执行,但可以用于防止潜在的程序异常或内存泄漏。