如何使用ESP32DMASPI实现ESP32(从机)向Arduino(主机)传输数据
SPI通信逻辑澄清
首先你对SPI的工作逻辑理解有误,SPI是同步全双工通信总线,不存在「MISO数据等于MOSI数据加从机追加数据」的规则:
- 所有SPI传输完全由主机发起,主机提供时钟信号,每产生1个时钟脉冲,MOSI和MISO各传输1位数据,收发是同时完成的
- 主机发N个字节的同时,必然会收到从机返回的N个字节,不存在从机主动向主机发送数据的情况
- 你说的类似
on_request的效果,本质是通过自定义上层协议实现:主机先发送1个指定命令字节告知从机「我要读数据」,随后主机连续发送N个无意义的空字节(通常是0x00)来提供时钟,从机在收到读命令后,把要返回的数据提前放到MISO的发送缓冲区,在后续的时钟周期依次发出即可。
ESP32DMASPI实现从机回传的方法
ESP32DMASPI库本身支持同时配置DMA收发缓冲区,完全可以实现上述功能,具体实现步骤如下:
1. ESP32从机端修改
初始化时同时开辟RX和TX的DMA缓冲区,你现有的代码只处理了RX逻辑,需要补充TX缓冲的预填逻辑:
#include <ESP32DMASPISlave.h> #define BUFFER_SIZE 32 ESP32DMASPISlave spi(HSPI); // 也可以用VSPI,根据你的引脚配置调整 // 收发缓冲区 uint8_t spi_slave_rx_buf[BUFFER_SIZE] = {0}; uint8_t spi_slave_tx_buf[BUFFER_SIZE] = {0}; void setup() { spi.setDMAChannel(1); spi.setMOSI(13); spi.setMISO(12); spi.setSCLK(14); spi.setCS(15); // 同时配置收发缓冲大小 spi.begin(BUFFER_SIZE, BUFFER_SIZE); // 初始化时先把TX缓冲填默认值,避免返回随机数据 memset(spi_slave_tx_buf, 0xFF, BUFFER_SIZE); spi.queue(spi_slave_tx_buf, BUFFER_SIZE); } void loop() { while(spi.available()) { // 读取主机发过来的数据 uint8_t data[BUFFER_SIZE] = {0}; for (size_t i = 0; i < BUFFER_SIZE; ++i) { data[i] = spi_slave_rx_buf[i]; spi_slave_rx_buf[i] = 0; } // 处理主机命令 if(data[0] == 0x02) { // 自定义的读数据命令,可自行修改命令值 // 把要返回的数据填到TX缓冲里,比如替换为你的传感器采集值、状态值等 spi_slave_tx_buf[0] = 0xAA; // 自定义帧头 spi_slave_tx_buf[1] = 25; // 温度示例值 spi_slave_tx_buf[2] = 60; // 湿度示例值 spi_slave_tx_buf[3] = 0xBB; // 自定义帧尾 } // 把新的TX缓冲提交到队列,下次传输时自动通过MISO发出 spi.queue(spi_slave_tx_buf, BUFFER_SIZE); user_onReceive(data); spi.pop(); } }
2. Arduino Uno主机端逻辑
主机要按照约定的协议发起读写请求,读数据的示例代码如下:
#include <SPI.h> #define CS_PIN 10 void setup() { SPI.begin(); pinMode(CS_PIN, OUTPUT); digitalWrite(CS_PIN, HIGH); Serial.begin(9600); } void loop() { uint8_t read_buf[4] = {0}; digitalWrite(CS_PIN, LOW); // 先发读命令0x02,随后发4个空字节获取从机返回数据 SPI.transfer(0x02); read_buf[0] = SPI.transfer(0x00); read_buf[1] = SPI.transfer(0x00); read_buf[2] = SPI.transfer(0x00); read_buf[3] = SPI.transfer(0x00); digitalWrite(CS_PIN, HIGH); // 打印拿到的从机数据 Serial.print("从机返回:"); for(int i=0; i<4; i++) { Serial.print(read_buf[i], HEX); Serial.print(" "); } Serial.println(); delay(1000); }
注意事项
- 因为SPI是全双工同步传输,第一次发读命令时拿到的第一个字节,是从机上一次传输存在TX缓冲里的数据,所以如果需要严格对齐数据,要么在协议里预留1个字节的偏移,要么主机连续发两次读命令,取第二次的返回即可。
- 缓冲区大小要在主从机两端保持一致,避免DMA传输出现截断。
内容的提问来源于stack exchange,提问作者the-dstrp
相关产品推荐
相关产品推荐

