ESP32-S3 双 SPI 总线设备协同:arduino-esp32 中一次配通 HSPI 与 VSPI
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
arduino-esp32 的 SPI 库在 ESP32-S3 上把 SPI2(HSPI)和 SPI3(VSPI)暴露为两个独立控制器。这篇实战笔记只讲一件事:如何用new SPIClass()把屏幕、SD 卡、传感器分到不同总线上并行工作,避开同引脚抢信号导致的丢字节和花屏。
先选你的路线
哪条路线适合你,取决于总线上挂几路设备、各自跑多快。先对号入座,再往下读。
| 你的场景 | 推荐路线 | 说明 |
|---|---|---|
| 1-2 个低速传感器,速度 ≤1 MHz | 默认 SPI 单总线多片选 | 用全局SPI实例,每设备一个 CS,靠SPISettings切换频率即可 |
| 屏幕 + SD 卡这类一快一慢的设备 | VSPI + HSPI 双总线分离 | S3 上两条总线引脚互不牵连,互不等待,本文主体内容 |
| 引脚已用完,只能再挤一个慢速设备 | 软件位模拟 | 用任意 GPIO 手写时序,速率受限,只做最后手段 |
最小可跑通配置
直接落地的一组 ESP32-S3 引脚分配如下:VSPI 走芯片默认引脚,HSPI 取 SPI_Multiple_Buses 官方示例中的固定组合。
| 总线 | 用途 | SCK | MISO | MOSI | CS |
|---|---|---|---|---|---|
| VSPI (SPI3) | SD 卡等存储设备 | 18 | 19 | 23 | 5 |
| HSPI (SPI2) | TFT 显示屏 | 14 | 12 | 13 | 15 |
默认引脚定义见 variants/esp32s3/pins_arduino.h。下面这段代码负责初始化两条总线并执行一次事务化传输,改好引脚就能编译运行:
#include <SPI.h> SPIClass *vspi = new SPIClass(VSPI); // SD 卡 SPIClass *hspi = new SPIClass(HSPI); // TFT void setup() { vspi->begin(18, 19, 23, 5); // SCK, MISO, MOSI, CS hspi->begin(14, 12, 13, 15); } void loop() { uint8_t frame[256]; hspi->beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0)); digitalWrite(hspi->pinSS(), LOW); hspi->transferBytes(frame, nullptr, sizeof(frame)); digitalWrite(hspi->pinSS(), HIGH); hspi->endTransaction(); }注意:Arduino SPI API 不会自动拉低片选,CS 必须由你的代码拉低和拉高,且初始化后保持在高电平。
为什么这样就行
卡住的根源不是设备数量,而是两条信号线被两组设备同时驱动。ESP32-S3 对外提供两条独立 SPI 控制器:SPI2(HSPI)和 SPI3(VSPI),每条总线各有自己的时钟分频器、MOSI/MISO 和硬件 CS 通道;SPI1 被 Flash/PSRAM 占用,C3/C6/H2 系列则只有一条,S3 恰好是双通道。默认的全局SPI对象只初始化 VSPI,而 SPIClass 构造函数接收总线编号作为参数,new SPIClass(HSPI)就是把第二条车道的硬件真正打开。硬件层依据见 cores/esp32/esp32-hal-spi.c,其中 ESP32-S3 的SPI_COUNT为 2。另外,HAL 内部为每条总线挂了一把 FreeRTOS 互斥锁,两个 FreeRTOS 任务同时操作同一条总线时会排队而不是交错写入。
瓶颈出现后的升级路径
- 提高事务时钟:在
SPISettings里按设备降频或升频,例如屏幕 40 MHz、SD 卡 20 MHz。换来单字节传输时间缩短;代价是超出器件耐受后花屏、CRC 错误,需逐档验证。 - 启用硬件片选:调用
setHwCs(true)(可配setSSInvert()),省去手写digitalWrite拉低/拉高 CS 的样板代码;代价是 CS 由硬件接管,动态切换 CS 策略时不如软控灵活。 - 并发下放宽对 CPU 的占用:多任务共享一条总线时无需自建互斥锁——HAL 锁已在驱动内部;把跨总线操作分到不同任务并行即可。换来主循环不再空等;代价是任务切换本身有开销,高频短事务反而更差。
交付前自查
- 两条总线的 SCK/MOSI/MISO 引脚没有任何交集
- 每个片选引脚初始为高电平,事务期间才拉低
- 每个设备的时钟频率与 SPI 模式(
SPI_MODE0~3)已按其数据手册核对 - 同总线多设备时,
SPISettings在每次beginTransaction时按设备匹配 - 多任务共线场景跑一轮压力循环,确认无字节错位、无死锁
避坑速查
| 现象 | 可能原因 | 解法 |
|---|---|---|
| 屏幕偶发花屏、串行字节错位 | 两个设备的引脚撞在同一条物理线上 | 按上表把 CS 以外的信号也分到不同总线 |
| 第二个总线初始化后无波形 | 误用全局SPI对象,或begin()参数顺序写成 SCK/MOSI/MISO | 检查参数顺序为 SCK、MISO、MOSI、CS,且确认用的是独立SPIClass实例 |
| 多任务读写时偶发数据错乱 | 跨总线共用一个锁、或手动 CS 时序在任务间交叉 | 依赖 HAL 内置互斥锁,不要对同一条总线另建软件锁 |
| SD 卡识别失败但屏幕正常 | SD 卡对时钟更敏感,频率给满 | SD 事务降频到 10~20 MHz,SPI_MODE0重试 |
配法本身很薄:两条独立控制器加事务封装就是全部。继续深入看 libraries/SPI/examples/、SPIClass 接口 与 驱动实现。
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考