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 数据传输任务通常为长期运行或常驻任务,因此该步骤一般不被执行,但可以用于防止潜在的程序异常或内存泄漏。