Skip to content

SPI通信协议

在ESP32-C3上使用SPI连接外设芯片时,需按照以下步骤操作,并调用ESP-IDF的核心函数:

​1. 配置SPI总线参数

首先定义并初始化SPI总线配置结构体:

c
#include "driver/spi_master.h"

// 定义SPI引脚(根据实际连接调整)
#define MOSI_PIN 7
#define MISO_PIN 2
#define CLK_PIN  6
#define CS_PIN   10  // 外设的片选引脚

// 配置SPI总线参数
spi_bus_config_t buscfg = {
    .mosi_io_num = MOSI_PIN,
    .miso_io_num = MISO_PIN,
    .sclk_io_num = CLK_PIN,
    .quadwp_io_num = -1,  // 未使用
    .quadhd_io_num = -1,  // 未使用
    .max_transfer_sz = 4094, // 最大传输大小
};

​2. 初始化SPI总线

调用 spi_bus_initialize 初始化SPI主机:

这个函数用于初始化SPI总线,配置主机模式,设置相关的GPIO引脚,分配DMA资源,设置时钟频率等。可能还会涉及中断的配置,或者检查引脚是否冲突。

c
// 初始化SPI2_HOST(ESP32-C3可用SPI2_HOST)
esp_err_t ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
    // 错误处理(如打印日志)
}

​3. 配置并添加SPI设备

设置设备参数并添加到总线:

c
spi_device_interface_config_t devcfg = {
    .mode = 0,                 // SPI模式(0-3,根据外设要求)
    .clock_speed_hz = 10 * 1000 * 1000, // 时钟频率(10MHz)
    .spics_io_num = CS_PIN,    // 片选引脚
    .queue_size = 7,           // 传输队列大小
    .flags = SPI_DEVICE_NO_DUMMY, // 可选标志(如无dummy位)
};

spi_device_handle_t spi;
ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi);
if (ret != ESP_OK) {
    // 错误处理
}

​4. 执行SPI数据传输

构造事务结构体并发送数据:

c
// 定义发送和接收缓冲区
uint8_t tx_data[4] = {0x01, 0x02, 0x03, 0x04};
uint8_t rx_data[4] = {0};

spi_transaction_t t = {
    .length = 8 * 4,           // 数据位数(4字节×8位)
    .tx_buffer = tx_data,
    .rx_buffer = rx_data,
};

// 阻塞式传输
ret = spi_device_transmit(spi, &t);
if (ret != ESP_OK) {
    // 错误处理
}

​5. 清理资源

释放SPI设备和总线:

c
spi_bus_remove_device(spi);    // 移除设备
spi_bus_free(SPI2_HOST);       // 释放总线资源

​核心函数列表

  • ​总线初始化与释放:
    • spi_bus_initialize():初始化SPI总线。
    • spi_bus_free():释放总线资源。
  • ​设备管理:
    • spi_bus_add_device():添加设备到总线。
    • spi_bus_remove_device():移除设备。
  • ​数据传输:
    • spi_device_transmit():同步传输数据。
    • spi_device_queue_trans() 和 spi_device_get_trans_result():异步传输(需队列管理)。

​关键注意事项

  • ​SPI模式:需与外设的CPOL和CPHA设置一致。
  • ​GPIO分配:确保引脚无冲突,且支持SPI功能。
  • ​时钟频率:不超过外设支持的最大频率。
  • ​DMA配置:使用SPI_DMA_CH_AUTO自动选择通道。
  • ​错误处理:检查所有API调用的返回值。

通过以上步骤和函数,即可在ESP32-C3上实现SPI通信。具体参数需根据外设芯片手册调整。

说明:

关键步骤1:

在ESP-IDF中,使用SPI2_HOST时,通常无需通过 idf.py menuconfig 进行额外配置。

如果SPI传输数据量极大(如大块DMA传输),可能需要调整 ​DMA缓冲区大小: 进入 menuconfig → Component config → Driver configurations → SPI configuration → SPI master DMA Max Buffer Size。 增大此值可支持更大的单次传输(默认4094字节)。

在第二步中,在ESP32-C3中调用 spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO) 初始化SPI总线时,其底层操作可分为以下

​1. 硬件SPI控制器配置
  • ​选择SPI主机:ESP32-C3有两个SPI控制器(SPI1_HOST和SPI2_HOST),此处选择SPI2_HOST(通常用于通用SPI通信)。
  • ​时钟源设置:默认使用APB总线时钟(通常为80MHz),后续设备使用时可以分频。
  • ​DMA通道分配:通过SPI_DMA_CH_AUTO自动选择可用DMA通道,用于高效传输数据块。
​2. GPIO引脚复用
  • ​引脚功能重映射:将mosi_io_num、miso_io_num、sclk_io_num等配置的物理GPIO引脚连接到SPI控制器的信号线。
  • ​冲突检查:确保这些GPIO未被其他功能(如UART、I2C)占用。
​3. SPI总线全局参数配置
  • ​信号极性设置:根据后续设备的SPI模式(CPOL/CPHA)动态调整,但此时仅初始化默认状态。
  • ​位序配置:默认MSB(高位先传),除非在buscfg中指定flags修改为LSB。
  • ​最大传输长度:根据max_transfer_sz设置单次DMA传输的最大字节数(默认4094字节)。
​4. DMA引擎初始化
  • ​缓冲区分配:为DMA传输分配内存缓冲区,用于临时存储SPI数据。
  • ​中断注册:注册DMA传输完成的中断服务程序(ISR),但用户无需直接处理。
​5. 资源互斥锁创建
  • ​总线锁初始化:防止多个设备或任务同时访问SPI总线造成冲突。
  • ​线程安全机制:确保后续的spi_device_transmit()等函数可安全调用。
​6. 错误检查与返回
  • ​参数合法性校验:检查buscfg中引脚是否有效、DMA通道是否可用等。
  • ​返回状态码:若成功返回ESP_OK,否则返回错误码(如ESP_ERR_INVALID_ARG)。

​典型错误原因

若初始化失败(ret != ESP_OK),可能原因包括:

  1. ​GPIO冲突:配置的MOSI/MISO/CLK引脚已被其他外设占用。
  2. ​DMA资源不足:系统内其他组件(如I2S、SDMMC)已占用所有DMA通道。
  3. ​无效主机号:ESP32-C3仅支持SPI1_HOST(保留用于Flash)和SPI2_HOST。
  4. ​内存不足:无法为DMA缓冲区分配内存。

​初始化后的总线状态

  1. PI控制器:处于空闲状态,未产生任何时钟信号。
  2. ​GPIO电平:MOSI和CLK引脚默认为低电平,MISO由外设控制。
  3. ​后续操作:需通过spi_bus_add_device()添加具体设备并设置通信参数(如SPI模式、时钟分频)。

​调试建议

若初始化失败,可通过以下方式定位问题:

c
if (ret == ESP_ERR_INVALID_ARG) {
    ESP_LOGE("SPI", "无效参数(如引脚号错误)");
} else if (ret == ESP_ERR_NOT_FOUND) {
    ESP_LOGE("SPI", "无可用DMA通道");
} else if (ret == ESP_ERR_NO_MEM) {
    ESP_LOGE("SPI", "内存不足");
}

通过上述步骤,spi_bus_initialize()为SPI总线提供了硬件底层支持,但尚未与外设通信。实际数据传输需在添加设备后通过spi_device_transmit()实现。

关键步骤2:

当调用 spi_bus_add_device(SPI2_HOST, &devcfg, &spi) 时,函数内部会执行以下操作:

(1) 配置设备参数

根据传入的 spi_device_interface_config_t 结构体(devcfg)设置设备属性:

  • ​SPI模式​(.mode):CPOL(时钟极性)和CPHA(时钟相位)。
  • ​时钟频率​(.clock_speed_hz):通过分频器将总线时钟(如80MHz)分频为设备所需频率。
  • ​片选引脚​(.spics_io_num):指定该设备的CS引脚。
  • ​传输队列大小​(.queue_size):异步传输时的队列容量。
  • ​特殊标志​(.flags):如SPI_DEVICE_HALFDUPLEX(半双工模式)、SPI_DEVICE_NO_DUMMY(无虚位传输)等。
(2) 分配设备句柄

为设备分配一个 spi_device_handle_t 句柄(即代码中的spi变量),后续通过此句柄操作设备(如调用spi_device_transmit())。

(3) 计算时钟分频值

根据 .clock_speed_hz 计算SPI控制器的分频系数,使实际时钟频率尽可能接近用户设定的值(例如10MHz)。

(4) 注册设备到总线

将设备添加到总线的设备链表中,确保多个设备可以共享同一个SPI控制器。 初始化设备的CS引脚为GPIO输出模式,并默认设置为高电平(非激活状态)。 通过spi_bus_add_device()添加的设备是 逻辑实体,绑定到总线上,通过CS引脚区分。

(5) 配置DMA(如果启用)​

如果使用DMA(通过spi_bus_initialize()的dma_chan参数指定),会为设备分配DMA通道缓冲区。

(6) 返回状态

返回ESP_OK表示成功,否则返回错误码(如引脚冲突、内存不足等)。

总线与设备的关系

  • ​物理总线:如SPI2_HOST是硬件资源,负责底层信号生成和传输。
  • ​逻辑设备:通过spi_bus_add_device()添加的设备是逻辑实体,绑定到总线上,通过CS引脚区分。
  • ​多设备共存:同一总线可以添加多个设备,例如:
c
// 添加设备1(CS引脚为GPIO10)
spi_bus_add_device(SPI2_HOST, &devcfg1, &spi_dev1);
// 添加设备2(CS引脚为GPIO11)
spi_bus_add_device(SPI2_HOST, &devcfg2, &spi_dev2);

关键步骤3:

通过 spi_device_transmit() 函数进行数据传输,该函数会根据设备句柄(spi_device_handle_t)和传输参数(spi_transaction_t)执行以下操作:

c

uint8_t tx_data[4] = {0x01, 0x02, 0x03, 0x04};  // 发送数据
uint8_t rx_data[4] = {0};                       // 接收数据缓冲区

spi_transaction_t t = {
    .length = 8 * 4,       // 传输总位数(4字节 × 8位 = 32位)
    .rxlength = 8 * 2,   // 实际接收数据位数(2字节)
    .tx_buffer = tx_data,  // 发送数据指针
    .rx_buffer = rx_data,  // 接收数据指针(数据将存储在此处)
};

// 执行阻塞式传输
ret = spi_device_transmit(spi, &t);
if (ret != ESP_OK) {
    // 错误处理
}

//获取接收到的数据
ESP_LOGI("SPI", "Received data: 0x%02X 0x%02X 0x%02X 0x%02X", 
         rx_data[0], rx_data[1], rx_data[2], rx_data[3]);

如果接收数据小于等于 32 字节,可以直接使用 rx_data 字段(无需动态分配缓冲区):

c
spi_transaction_t t = {
    .length = 8 * 4,
    .tx_buffer = tx_data,
    .flags = SPI_TRANS_USE_RXDATA, // 启用内置rx_data
};

// 传输完成后,数据在 t.rx_data 中
ESP_LOGI("SPI", "Received: 0x%02X", t.rx_data[0]);

SPI模式说明

SPI模式由 ​时钟极性(CPOL, Clock Polarity)​​ 和 ​时钟相位(CPHA, Clock Phase)​​ 的组合定义,共有4种模式(0-3)。以下是每个模式的具体含义及对应的时序特征:

​SPI模式定义

模式CPOLCPHA描述
000时钟空闲时为低电平,数据在上升沿采样。
101时钟空闲时为低电平,数据在下降沿采样。
210时钟空闲时为高电平,数据在下降沿采样。
311时钟空闲时为高电平,数据在上升沿采样。

​SPI模式时序特征

模式时钟空闲数据采样
0低电平上升沿
1低电平下降沿
2高电平下降沿
3高电平上升沿

常见外设模式示例

外设类型典型模式
SD卡0
多数SPI Flash0 或 3
BME280温湿度传感器0 或 3
MAX31855热电偶模块1
ST7735 LCD屏3