一、SPI协议基础与ESP32硬件架构
1. 控制器选择
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),否则出现数据错位。2. DMA优化策略
__attribute__((aligned(4)))
)// 分段传输大文件(如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文件系统集成本文代码均通过ESP-IDF v5.1测试,实际开发时需注意:
- 高频布线需等长处理(误差<0.5mm)
- 多设备共享总线时启用
spi_bus_lock
机制- 从机模式建议搭配RX缓冲中断优化
可通过修改仓库中的Kconfig.projbuild
文件调整时钟参数,适配不同硬件环境。