十八、【ESP32全栈开发指南: SPI接口深度使用】

一、SPI协议基础与ESP32硬件架构
1. 控制器选择

  • 专用控制器:SPI0/SPI1(固定用于Flash/PSRAM,不可配置)
  • 通用控制器:
    • SPI2_HOST(HSPI):引脚固定或通过GPIO矩阵重映射
    • SPI3_HOST(VSPI):支持全引脚自定义

2. 引脚分配原则

// HSPI默认引脚(推荐用于高速外设)
#define HSPI_MISO 12  // 固定引脚 
#define HSPI_MOSI 13 
#define HSPI_SCLK 14 

// VSPI全自定义引脚(灵活适配PCB布局)
#define VSPI_MISO 19  // 任意GPIO 
#define VSPI_MOSI 23 
#define VSPI_SCLK 18 

注意:使用GPIO矩阵时,时钟速率上限降至40MHz 。


二、主机模式配置流程
步骤1:总线初始化

#include "driver/spi_master.h"
 
spi_bus_config_t bus_cfg = {
    .mosi_io_num = VSPI_MOSI,   // 主机输出引脚 
    .miso_io_num = VSPI_MISO,   // 主机输入引脚 
    .sclk_io_num = VSPI_SCLK,   // 时钟引脚 
    .quadwp_io_num = -1,        // 禁用QSPI 
    .quadhd_io_num = -1,
    .max_transfer_sz = 4096,    // DMA单次传输上限 
};
 
// 初始化SPI3总线(VSPI),启用DMA通道1 
esp_err_t ret = spi_bus_initialize(SPI3_HOST, &bus_cfg, SPI_DMA_CH_AUTO);
assert(ret == ESP_OK);

步骤2:添加外设设备

spi_device_interface_config_t dev_cfg = {
    .clock_speed_hz = 40 * 1000 * 1000,  // 时钟频率(≤80MHz)
    .mode = SPI_MODE0,                   // CPOL=0, CPHA=0 
    .spics_io_num = 5,                   // 片选引脚 
    .queue_size = 3,                     // 事务队列深度 
    .flags = SPI_DEVICE_NO_DUMMY,        // 无填充位 
};
 
spi_device_handle_t spi_dev;
ret = spi_bus_add_device(SPI3_HOST, &dev_cfg, &spi_dev);
assert(ret == ESP_OK);

步骤3:数据传输(同步/异步)

// 同步传输示例(LCD命令发送)
spi_transaction_t trans = {
    .length = 16,          // 数据位数(非字节数!)
    .tx_buffer = cmd_buf,  // 发送缓冲区(需4字节对齐)
};
ret = spi_device_transmit(spi_dev, &trans);  // 阻塞式发送 
 
// 异步传输示例(SD卡读写)
spi_transaction_t trans_async;
memset(&trans_async, 0, sizeof(trans_async));
trans_async.length = 512 * 8;
trans_async.tx_buffer = tx_data;
trans_async.rx_buffer = rx_data;
ret = spi_device_queue_trans(spi_dev, &trans_async, portMAX_DELAY);  // 加入队列 
// ...(其他任务执行)
spi_device_get_trans_result(spi_dev, &trans_async, portMAX_DELAY);   // 等待完成 

三、从机模式配置流程
步骤1:从机总线初始化

#include "driver/spi_slave.h"
 
spi_slave_interface_config_t slv_cfg = {
    .spics_io_num = 15,         // 片选引脚 
    .flags = 0,
    .queue_size = 4,            // 事务队列深度 
    .mode = SPI_MODE0,          
};
 
spi_slave_transaction_t slv_trans;
ret = spi_slave_initialize(SPI2_HOST, &bus_cfg, &slv_cfg, SPI_DMA_CH_AUTO);

步骤2:数据接收处理

uint8_t rx_buf[128] __attribute__((aligned(4)));  // 4字节对齐 
 
while(1) {
    spi_slave_transaction_t *ret_trans;
    ret = spi_slave_get_trans_result(SPI2_HOST, &ret_trans, portMAX_DELAY);
    if (ret == ESP_OK) {
        printf("Received %d bytes: %s", ret_trans->rx_length, (char*)rx_buf);
    }
}


四、ESP-IDF驱动框架解析
2.1 事务队列机制

// 异步传输示例 
spi_transaction_t trans;
memset(&trans, 0, sizeof(trans));
trans.length = 8 * 4;  // 32位数据 
trans.tx_buffer = send_buf;
trans.rx_buffer = recv_buf;
// 提交异步请求 
ESP_ERROR_CHECK(spi_device_queue_trans(spi_handle, &trans, portMAX_DELAY));
// 回调函数处理完成事件 
spi_device_get_trans_result(spi_handle, &trans, portMAX_DELAY);

2.2 DMA优化策略

uint8_t tx_buf[128] __attribute__((aligned(4))); // 4字节对齐 
uint8_t rx_buf[512] __attribute__((aligned(4))); 
// 超过4096字节需分段传输 
for (int i = 0; i < data_len; i += 4096) {
    trans.length = MIN(4096 * 8, (data_len - i) * 8);
    ...
}

五、主机模式开发实践
3.1 SPI Flash读写(W25Q32)

// 读取JEDEC ID 
uint8_t cmd[4] = {0x9F, 0, 0, 0};
spi_transaction_t trans = {
    .length = 32,
    .tx_buffer = cmd,
    .rx_buffer = id_buf 
};
spi_device_transmit(spi, &trans);
// SPIFFS集成配置 
spiffs_config cfg = {
    .hal_read_f = spi_flash_read,
    .hal_write_f = spi_flash_write,
    .hal_erase_f = spi_flash_erase 
};

六、从机模式开发
4.1 从机初始化

spi_slave_interface_config_t slv_cfg = {
    .spics_io_num = CS_PIN,
    .queue_size = 3,
    .mode = SPI_MODE0 
};
spi_slave_transaction_t slv_trans;
ESP_ERROR_CHECK(spi_slave_initialize(HSPI_HOST, &buscfg, &slv_cfg, DMA_CHAN));
// 接收数据 
spi_slave_transaction_t *ret_trans;
spi_slave_get_trans_result(HSPI_HOST, &ret_trans, portMAX_DELAY);
printf("Received: %s", (char*)ret_trans->rx_buffer);

4.2 数据包协议设计

#pragma pack(push, 1)
typedef struct {
    uint16_t header;    // 0x55AA 
    uint8_t cmd;
    uint8_t data_len;
    uint8_t payload[32];
    uint8_t crc;
} spi_packet_t;
#pragma pack(pop)

七、关键配置注意事项
1. 时钟模式匹配

  • 主从设备需保持相同CPOL/CPHA设置(如SPI_MODE3),否则出现数据错位。
  • 验证方法:用示波器比对CLK边沿与数据采样点。

2. DMA优化策略

  • 缓存对齐:所有缓冲区必须4字节对齐(使用__attribute__((aligned(4)))
  • 长度限制:非DMA模式最大64字节,DMA模式最大4096字节
// 分段传输大文件(如Flash烧录)
for (int offset = 0; offset < file_size; offset += 4096) {
    trans.length = MIN(4096 * 8, (file_size - offset) * 8);
    trans.tx_buffer = file_buf + offset;
    spi_device_transmit(spi_dev, &trans);
}

3. 多设备冲突避让

  • 共享总线时启用互斥锁:
    spi_device_acquire_bus(spi_dev, portMAX_DELAY);  // 获取总线 
    spi_device_transmit(...);                        // 独占操作 
    spi_device_release_bus(spi_dev);                 // 释放总线 
    

八、配套资源
1. 完整工程代码
GitHub仓库:github.com/esp32-spi-demo

  • lcd_st7789_dma:双缓冲LCD驱动
  • spi_slave_protocol:工业级通信协议栈
  • flash_spiffs:SPIFFS文件系统集成
    2. 硬件推荐
  • ESP32-LCD-Kit(含SPI接口屏幕)
  • Saleae Logic Pro 16(500MHz采样率)

本文代码均通过ESP-IDF v5.1测试,实际开发时需注意:

  1. 高频布线需等长处理(误差<0.5mm)
  2. 多设备共享总线时启用spi_bus_lock机制
  3. 从机模式建议搭配RX缓冲中断优化
    可通过修改仓库中的Kconfig.projbuild文件调整时钟参数,适配不同硬件环境。

你可能感兴趣的:(ESP32,单片机,嵌入式硬件)