news 2026/5/25 13:49:22

ESP32 Arduino模拟SPI实战指南:主机与从机通信实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32 Arduino模拟SPI实战指南:主机与从机通信实现

目录

一、模拟SPI核心原理(模式0)

二、前期准备

1. 硬件材料

2. 软件环境

三、核心实现:代码编写与解析

1. 通用引脚定义(主机与从机一致)

2. 主机代码实现

3. 从机代码实现

四、硬件连接步骤

五、测试与调试

1. 程序上传

2. 查看通信结果

六、常见问题与解决方案

1. 时序同步错位(核心问题)

2. 引脚选择不当

3. 缺少共地或接触不良

4. 从机进入死循环


在嵌入式开发中,SPI(串行外设接口)是常用的同步通信协议,广泛用于传感器、存储芯片、显示屏等外设的通信。ESP32自带硬件SPI外设,但在某些场景下(如硬件SPI引脚被占用、需要自定义通信引脚),模拟SPI(软件SPI)就成为了刚需。本文将详细讲解在Arduino环境下,如何实现ESP32模拟SPI的主机与从机通信,包含完整代码、硬件连接、时序分析及常见问题解决。


一、模拟SPI核心原理(模式0)

SPI通信有4种模式,由时钟极性(CPOL)和时钟相位(CPHA)定义,本文选择最常用的模式0(CPOL=0,CPHA=0),其核心时序规则如下:

  • 时钟空闲状态:SCLK为低电平(CPOL=0);

  • 数据采样时机:SCLK的上升沿(第一个时钟沿)采样数据;

  • 数据更新时机:SCLK的下降沿(第二个时钟沿)更新数据;

  • 通信逻辑:主机产生SCLK时钟和CS片选信号,通过MOSI发送数据,从机同步通过MISO返回数据(全双工通信)。

模拟SPI的本质是通过软件控制GPIO口的电平变化,模拟上述时序,无需依赖硬件SPI外设,灵活性更高,但通信速度低于硬件SPI。


二、前期准备

1. 硬件材料

  • ESP32开发板 2块(1块作为主机,1块作为从机);

  • 杜邦线 若干(至少4根,用于连接SCLK、MOSI、MISO、CS);

  • USB数据线 2根(用于给开发板供电和上传程序);

  • 面包板 1块(可选,用于规范接线,避免接触不良)。

2. 软件环境

Arduino IDE(已安装ESP32开发板支持包,若未安装,可参考官方指南:https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html)。


三、核心实现:代码编写与解析

本次实现“主机发送固定数据0x54,从机接收后返回固定数据0x77”的全双工通信,引脚定义优先选择ESP32通用GPIO(避免使用SDIO复用引脚,减少电平干扰)。

1. 通用引脚定义(主机与从机一致)

选择4个通用GPIO口,对应关系如下(可根据实际需求调整,但需保证主机与从机一一对应):

#define SPI_SCLK 9 // 串行时钟引脚 #define SPI_MOSI 10 // 主机输出从机输入引脚 #define SPI_MISO 11 // 主机输入从机输出引脚 #define SPI_CS 12 // 片选引脚(低有效)

2. 主机代码实现

主机核心功能:初始化GPIO口、产生SCLK时钟和CS信号、通过MOSI发送数据、通过MISO接收从机数据。

/* * ESP32 模拟 SPI 主机(模式0:CPOL=0,CPHA=0) */ #define SPI_SCLK 9 // 串行时钟引脚 #define SPI_MOSI 10 // 主机输出从机输入引脚 #define SPI_MISO 11 // 主机输入从机输出引脚 #define SPI_CS 12 // 片选引脚(低有效) #define CPOL 0 // 时钟极性:空闲低 #define CPHA 0 // 时钟相位:上升沿采样 void spi_soft_init(); uint8_t spi_soft_transfer(uint8_t tx_data); void setup() { Serial.begin(115200); spi_soft_init(); Serial.println("SPI主机初始化完成!"); } void loop() { uint8_t send_data = 0x01; // 测试数据 uint8_t recv_data = spi_soft_transfer(send_data); Serial.printf("主机发送:0x%02X,主机接收(从机返回):0x%02X\n", send_data, recv_data); delay(1000); } // 主机SPI初始化 void spi_soft_init() { pinMode(SPI_SCLK, OUTPUT); pinMode(SPI_MOSI, OUTPUT); pinMode(SPI_MISO, INPUT); pinMode(SPI_CS, OUTPUT); digitalWrite(SPI_CS, HIGH); digitalWrite(SPI_SCLK, CPOL); digitalWrite(SPI_MOSI, LOW); } // 主机全双工传输一个字节 uint8_t spi_soft_transfer(uint8_t tx_data) { uint8_t rx_data = 0; digitalWrite(SPI_CS, LOW); // 选中从机 // 增加短暂延时,确保CS拉低后从机有时间响应 delayMicroseconds(1); for (int i = 7; i >= 0; i--) { // 【关键】先输出MOSI数据,等待电平稳定(提前于SCLK上升沿) digitalWrite(SPI_MOSI, (tx_data >> i) & 0x01); delayMicroseconds(1); // 电平稳定延时 // 模式0:上升沿采样,先拉SCLK高 digitalWrite(SPI_SCLK, HIGH); delayMicroseconds(1); // 等待从机响应并稳定MISO电平 // 【关键】延时后再采样MISO,避免时序错位 rx_data |= (digitalRead(SPI_MISO) << i); // 拉低SCLK,恢复空闲电平 digitalWrite(SPI_SCLK, LOW); delayMicroseconds(1); // 可选:增加延时降低时钟速度 } digitalWrite(SPI_CS, HIGH); // 取消选中从机 return rx_data; }

3. 从机代码实现

从机核心功能:初始化GPIO口、持续监听CS信号(等待主机选中)、同步主机SCLK时钟、采样MOSI数据(接收主机数据)、通过MISO返回数据。

/* * ESP32 模拟 SPI 从机(模式0:CPOL=0,CPHA=0) * 引脚需与主机一一对应:SCLK/MOSI/CS 为输入,MISO 为输出 */ #define SPI_SCLK 9 // 主机产生的时钟,从机作为输入 #define SPI_MOSI 10 // 主机输出的数据,从机作为输入 #define SPI_MISO 11 // 从机输出的数据,主机作为输入 #define SPI_CS 12 // 主机的片选,从机作为输入 #define CPOL 0 #define CPHA 0 uint8_t slave_recv_data = 0; // 从机接收的主机数据 uint8_t slave_send_data = 0x02; // 从机要返回给主机的数据 void spi_slave_init(); void spi_slave_listen(); // 从机监听主机的通信请求 void setup() { Serial.begin(115200); spi_slave_init(); Serial.println("SPI从机初始化完成!"); } void loop() { spi_slave_listen(); // 从机持续监听主机的通信 } // 从机SPI初始化 void spi_slave_init() { pinMode(SPI_SCLK, INPUT); pinMode(SPI_MOSI, INPUT); pinMode(SPI_MISO, OUTPUT); pinMode(SPI_CS, INPUT); digitalWrite(SPI_MISO, LOW); // 初始化MISO电平 } // 从机核心:监听主机的CS和SCLK,完成数据接收和发送 void spi_slave_listen() { // 等待主机拉低CS(选中当前从机) if (digitalRead(SPI_CS) != LOW) { slave_recv_data = 0; digitalWrite(SPI_MISO, LOW); // 空闲时MISO拉低 return; } uint8_t recv_data = 0; uint8_t send_data = slave_send_data; // 从机要发送的预存数据 // 逐位处理主机的时钟和数据 for (int i = 7; i >= 0; i--) { // 【关键】在等待SCLK上升沿前,先输出当前位到MISO(提前准备数据) digitalWrite(SPI_MISO, (send_data >> i) & 0x01); delayMicroseconds(1); // 电平稳定延时 // 等待主机产生SCLK上升沿(模式0:上升沿采样) while (digitalRead(SPI_SCLK) != HIGH) { if (digitalRead(SPI_CS) == HIGH) { // 防止CS意外拉高导致死循环 return; } } // 上升沿:从机采样MOSI(接收主机的数据) recv_data |= (digitalRead(SPI_MOSI) << i); delayMicroseconds(1); // 可选:稳定延时 // 等待主机拉低SCLK(恢复空闲电平) while (digitalRead(SPI_SCLK) != LOW) { if (digitalRead(SPI_CS) == HIGH) { // 防止CS意外拉高导致死循环 return; } } } // 通信完成:更新从机的接收数据,并打印调试 slave_recv_data = recv_data; Serial.printf("从机接收主机数据:0x%02X,从机发送给主机数据:0x%02X\n", slave_recv_data, send_data); // 可选:从机发送数据自增,用于测试连续通信 // slave_send_data++; }

四、硬件连接步骤

按照以下对应关系连接主机和从机的GPIO口,确保接线牢固(面包板连接需注意杜邦线接触良好):

ESP32主机

ESP32从机

信号说明

GPIO18(SCLK)

GPIO9(SCLK)

时钟信号(主机→从机)

GPIO23(MOSI)

GPIO10(MOSI)

主机发送→从机接收

GPIO19(MISO)

GPIO11(MISO)

从机发送→主机接收

GPIO5(CS)

GPIO12(CS)

片选信号(主机→从机)

GND

GND

共地(必须连接,否则电平不稳定)

注意:主机和从机必须共地,否则会因电平参考点不一致导致通信失败!

五、测试与调试

1. 程序上传

  • 将主机代码上传到其中一块ESP32开发板;

  • 将从机代码上传到另一块ESP32开发板;

  • 上传完成后,给两块开发板通电。

2. 查看通信结果

打开Arduino IDE的“串口监视器”,分别选择两块开发板的串口(波特率设为115200),正常通信时应显示以下日志:

主机日志:

从机日志:


六、常见问题与解决方案

在实际测试中,最常见的问题是“从机能接收主机数据,但主机接收从机数据错误(如0xBB、0xFF)”,以下是核心问题及解决方法:

1. 时序同步错位(核心问题)

问题原因:主机在SCLK上升沿瞬间采样MISO,但从机未及时输出数据(从机在上升沿后才更新MISO电平),导致主机采样到旧电平。

解决方案:从机需在SCLK上升沿前输出MISO数据,主机采样前增加短暂延时(如delayMicroseconds(1)),确保电平稳定。本文提供的代码已优化此问题。

2. 引脚选择不当

问题原因:ESP32的GPIO9、10、11、12等属于SDIO复用引脚,若用于模拟SPI,可能因硬件复用导致电平异常。

解决方案:优先选择通用GPIO(如18、19、23、5、4等),避免使用SDIO、UART等复用引脚。

3. 缺少共地或接触不良

问题原因:主机与从机未共地,电平参考点不一致;面包板或杜邦线接触不良,导致信号丢失。

解决方案:确保主机和从机的GND相连;检查杜邦线是否插紧,必要时更换杜邦线或面包板。

4. 从机进入死循环

问题原因:从机的while循环(等待SCLK沿)中,若CS意外拉高,会一直循环无法退出。

解决方案:在从机的while循环中增加CS状态判断,若CS拉高则直接返回(本文代码已添加此保护)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/25 11:51:01

阿布昔替尼:特应性皮炎患者的创新口服治疗新选择【海得康】

特应性皮炎&#xff08;Atopic Dermatitis&#xff0c;AD&#xff09;作为一种慢性、复发性、炎症性皮肤病&#xff0c;长期困扰着众多患者。传统治疗手段如外用糖皮质激素、免疫抑制剂等虽有一定效果&#xff0c;但存在局部刺激、长期使用副作用大等问题&#xff0c;且对于中重…

作者头像 李华
网站建设 2026/5/26 1:31:33

气象 AI Agent 模型评测全记录(罕见真实数据集验证)

第一章&#xff1a;气象 AI Agent 模型评测全记录&#xff08;罕见真实数据集验证&#xff09;在本次评测中&#xff0c;我们采用来自国家气象信息中心的高分辨率历史观测数据集&#xff0c;涵盖2015至2023年全国2400余个气象站点的逐小时温湿度、气压、风速与降水记录。该数据…

作者头像 李华
网站建设 2026/5/25 10:12:31

高并发下的 Token 存储策略: Redis 与 MySQL 的一致性

一、 背景&#xff1a;一个 Token 的两难境地 在微服务或前后端分离架构中&#xff0c;Access Token 是用户身份的唯一凭证。关于它的存储&#xff0c;我们面临两个看似矛盾的需求&#xff1a; 极速验证&#xff1a;每个接口请求&#xff08;QPS 可能高达数万&#xff09;都要验…

作者头像 李华
网站建设 2026/5/26 7:31:21

开发人员一些实用的工具

网页免费翻译的插件&#xff1f;好用实用的翻译软件&#xff1f;YouTube开启中文字幕&#xff1f;作为技术开发人员&#xff0c;避免不了会闯进一些英文的网页&#xff0c;查询资料或者拓展新技术的时候&#xff0c;所以这个沉浸式翻译的插件是非常好用的。1、下载插件&#xf…

作者头像 李华
网站建设 2026/5/25 15:14:15

飞书知识库备份

目录 1、创建应用&#xff0c;添加机器人能力&#xff0c;添加应用为知识库管理员。方法如图&#xff1a; 2、获取到知识空间ID。 3、Java代码&#xff08;递归调用&#xff09; 1、创建应用&#xff0c;添加机器人能力&#xff0c;添加应用为知识库管理员。方法如图&#x…

作者头像 李华