SPI Slave Sender 示例
示例说明
本示例展示了在 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 主机的初始化、数据传输和任务调度提供支持。
各头文件按功能分类如下:
FreeRTOS 任务调度 :提供任务创建与调度、信号量和队列功能,用于并发任务管理和多任务间的通信。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
系统工具 :提供标准输入输出、数据类型定义以及日志输出功能。
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_log.h"
SPI 从机驱动 :提供 SPI 主机模式的初始化、设备配置及数据传输功能。
#include "driver/spi_master.h"
GPIO 控制 :提供 GPIO 引脚配置及电平控制功能。
#include "driver/gpio.h"
定时器:提供基于微秒级精度的定时和回调功能,用于任务延时或周期性操作。
#include "esp_timer.h"
宏定义说明
本示例中涉及的宏定义主要用于配置 SPI 外设的引脚、控制器选择以及握手信号引脚,便于后续 SPI 数据传输和任务调度。
宏定义按功能分类如下:
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 中,提高中断响应速度。函数传参为用户自定义参数指针,常用于传递触发源或上下文信息。在本示例中,该函数并未对传参内容进行访问或修改,仅用于满足回调函数的函数原型要求。
该函数的执行逻辑为:
中断抖动过滤:为了避免握手 GPIO 上的干扰或信号反弹导致重复触发中断,函数通过记录上次中断时间并与当前中断时间进行比较,如果两次中断间隔小于设定阈值(1 ms),则忽略此次中断。
定义变量
lasthandshaketime_us
用于记录上次中断的时间。调用
esp_timer_get_time()
获取当前时间(微秒级)并保存至变量currtime_us
。进一步介绍及参数说明可参考 ESP 定时器。通过以上两个变量获取两次中断触发的时间间隔
diff
。判断时间间隔是否小于设定阈值(1 ms),如果小于,则直接返回,忽略此次中断;否则,更新上次中断的时间为当前时间,并继续执行后续操作。
发出同步信号:为了通知等待的任务从机已准备好,可以开始 SPI 数据传输,函数使用 FreeRTOS 信号量机制,从 ISR 安全地释放信号量,并在必要时触发任务切换。
定义标志变量
mustYield
,用于指示是否需要在中断结束后立即切换任务,初始化为false
。调用
xSemaphoreGiveFromISR()
释放信号量。进一步介绍及参数说明可参考 释放信号量资源。判断
mustYield
是否为true
,如果是则调用portYIELD_FROM_ISR()
切换到高优先级任务执行。
主函数说明
本示例演示了 SPI 主机的初始化、与从机设备建立通信并传输数据,以及资源释放的步骤。
定义变量:
定义返回值变量
ret
用于保存 SPI 外设相关函数的返回状态,判断操作是否成功,并可根据返回值进行错误处理或打印提示信息。定义设备句柄变量
handle
,用于标识和管理 SPI 总线上注册的从设备。
配置握手信号 GPIO:在 SPI 从机与主机通信中,为了实现可靠的数据传输,通常会使用一个握手信号(handshake line)来通知主机从机已准备好发送或接收数据。
配置握手线 中断服务。
安装并绑定中断服务,以便握手信号变化时能够触发对应处理。
在绑定中断服务前,再次显式设置引脚的中断触发模式为
GPIO_INTR_POSEDGE
,防止硬件初始化或驱动库内部修改中断配置。备注
再次设置中断触发模式属于冗余但保险的操作,确保握手 GPIO 的中断触发类型确实为上升沿触发,从而保证主从通信的可靠性。
SPI 主机发送/接收事务的准备工作:
定义计数变量
n
并将其初始化为 0,用于记录或标识当前传输的次数,方便调试或生成发送数据。定义数据缓冲区数组,为 SPI 主机的数据发送和接收提供存储空间,保证每次传输有确定的存储区域:
定义变量
sendbuf
并初始化为 0,用作发送数据缓冲区。定义变量
recvbuf
并初始化为 0,用作接收数据缓冲区。数组长度设置为 128,表示缓冲区可以存放最多 128 个字节 的数据,可根据实际应用需求调整字节容量。
定义 SPI 事务结构体
t
。调用
xSemaphoreCreateBinary()
创建一个信号量,并保存其句柄至变量rdySem
。进一步介绍及参数说明可参考 创建二值信号量。调用
xSemaphoreGive()
向信号量rdySem
发送一个“可用”信号,用于通知等待的任务从机已准备好或数据已经到达,可以继续执行后续操作。进一步介绍及参数说明可参考 释放信号量资源。
构建需要发送给 SPI 从机的数据,并在数据中记录本次传输编号以及上一次从机返回的数据。
调用
snprintf()
将格式化字符串写入发送缓冲区sendbuf
,保证不会越界。检查返回值,若发送数据超出缓冲区大小,则打印
Data truncated
,防止溢出。
执行事务前需要调用
xSemaphoreTake()
阻塞等待信号量,直到从机通过握手信号表明可以进行下一次传输。超时时间设置为portMAX_DELAY
表示无限等待。进一步介绍及参数说明可参考 获取信号量资源。
打印从机返回的数据,同时更新本次传输计数。
循环结束后调用
spi_bus_remove_device()
释放 SPI 总线资源,断开主机与从机的关联,并检查返回值确保成功。进一步介绍及参数说明可参考 SPI 主机驱动。
备注
在实际应用中,SPI 数据传输任务通常为长期运行或常驻任务,因此该步骤一般不被执行,但可以用于防止潜在的程序异常或内存泄漏。