ESP32与Arduino非UART引脚实现小车机器人指令无损通信问询
问题背景
我开发了一台搭载Arduino和电机驱动盾的小车机器人,ESP32接收网络指令后需转发给Arduino,指令包含forward、backward、right、left、autonomous、manual六种。因Arduino的RX/TX引脚被电机驱动盾占用,尝试通过自定义脉冲时长的通信方式(使用cmd_pin与data_validator引脚)实现数据传输:ESP32发送不同时长的高低脉冲,Arduino通过pulseIn检测脉冲时长判断指令。但当前方案无法正常工作,现需排查问题原因,并寻求基于Arduino闲置引脚(2、13、A3、A4、A5)的无损数据传输最优方案。
现有代码
Arduino代码
// 原代码未声明变量,需补充以下内容 #define cmd_pin 2 #define data_validator 13 bool automatic = false; String command = ""; void setup() { Serial.begin(9600); Serial.println("started"); pinMode(cmd_pin, INPUT); pinMode(data_validator, OUTPUT); digitalWrite(data_validator, HIGH); } String check(){ int serial_data = digitalRead(cmd_pin); if(serial_data==LOW){ delay(10); digitalWrite(data_validator, LOW); unsigned long high_dur = pulseIn(cmd_pin, HIGH); unsigned long low_dur = pulseIn(cmd_pin, LOW); Serial.println("HIGH "); Serial.println(high_dur); Serial.println("low "); Serial.println(low_dur); if(high_dur<=5000 && low_dur>24000){ automatic = false; return "Forward"; } else if(high_dur<=10000 && low_dur>19000){ automatic = false; return "Backward"; } else if(high_dur<=15000 && low_dur>14000){ automatic = false; return "Turing right"; } else if(high_dur<=20000 && low_dur>9000){ automatic = false; return "Turing left"; } else if(high_dur<=25000 && low_dur>4000){ automatic = true; return "autonomous"; } else if(high_dur<=30000 && low_dur>1900){ automatic = false; return "manual"; } digitalWrite(data_validator, HIGH); } return ""; // 无匹配指令时返回空字符串 } void loop(){ command = check(); Serial.println(command); // 以下为指令执行逻辑(原代码注释) // if (command=="autonomous") // auton(); // else if (command=="manual") // moveStop(); // else if (command=="Turing left") // turnLeft(); // else if (command=="Turing right") // turnRight(); // else if (command=="Forward") // moveForward(); // else if (command=="Backward") // moveBackward(); // else{ // if (automatic) // auton(); // } }
ESP32代码
// 原代码未声明变量,需补充以下内容 #define cmd_pin 2 #define data_validator 13 bool serial_busy = false; void manual(){ serial_busy = true; digitalWrite(cmd_pin, LOW); while (digitalRead(data_validator)!=LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(30); digitalWrite(cmd_pin, LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(1); digitalWrite(cmd_pin,LOW); delay(2); digitalWrite(cmd_pin,HIGH); serial_busy = false; delay(10); } void forward(){ serial_busy = true; Serial.println("Forward"); digitalWrite(cmd_pin, LOW); while (digitalRead(data_validator)!=LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(5); digitalWrite(cmd_pin, LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(1); digitalWrite(cmd_pin,LOW); delay(25); digitalWrite(cmd_pin,HIGH); serial_busy = false; delay(10); } void backward(){ serial_busy = true; Serial.println("Backward"); digitalWrite(cmd_pin, LOW); while (digitalRead(data_validator)!=LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(10); digitalWrite(cmd_pin, LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(1); digitalWrite(cmd_pin,LOW); delay(20); digitalWrite(cmd_pin,HIGH); serial_busy = false; delay(10); } void right(){ serial_busy = true; Serial.println("Right"); digitalWrite(cmd_pin, LOW); while (digitalRead(data_validator)!=LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(15); digitalWrite(cmd_pin, LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(1); digitalWrite(cmd_pin,LOW); delay(15); digitalWrite(cmd_pin,HIGH); serial_busy = false; delay(10); } void left(){ serial_busy = true; Serial.println("Left"); digitalWrite(cmd_pin, LOW); while (digitalRead(data_validator)!=LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(20); digitalWrite(cmd_pin, LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(1); digitalWrite(cmd_pin,LOW); delay(10); digitalWrite(cmd_pin,HIGH); serial_busy = false; delay(10); } void auton(){ serial_busy = true; Serial.println("going autonomusly"); digitalWrite(cmd_pin, LOW); while (digitalRead(data_validator)!=LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(25); digitalWrite(cmd_pin, LOW); delay(1); digitalWrite(cmd_pin,HIGH); delay(1); digitalWrite(cmd_pin,LOW); delay(5); digitalWrite(cmd_pin,HIGH); serial_busy = false; delay(10); } void setup(){ Serial.begin(115200); pinMode(cmd_pin, OUTPUT); pinMode(data_validator, INPUT); digitalWrite(cmd_pin, HIGH); } void loop(){ // 调用指令函数,比如forward()、left()等 }
问题排查
1. 核心时序不匹配
Arduino代码预期的是单一高脉冲+单一低脉冲的组合(比如Forward对应高脉冲≤5000ms、低脉冲>24000ms),但ESP32的指令函数发送的是多段脉冲序列(比如forward函数发送HIGH 5ms→LOW 1ms→HIGH 1ms→LOW 25ms→HIGH),两者的脉冲格式完全不匹配。Arduino的pulseIn会读取第一个高脉冲的时长(5ms),但后续的低脉冲仅1ms,远不满足判断条件中的>24000ms。
2. pulseIn超时问题
pulseIn默认超时时间为1秒(1000000微秒),而Arduino判断逻辑中要求低脉冲时长>24000ms(24秒),远超默认超时时间,pulseIn会直接返回0,导致判断逻辑失效。
3. 同步逻辑漏洞
Arduino的check()函数在匹配到指令后会直接return,跳过末尾的digitalWrite(data_validator, HIGH)操作,导致data_validator一直处于LOW状态,后续ESP32的while (digitalRead(data_validator)!=LOW)会直接通过,无法实现有效同步。
4. 未声明变量
原代码中cmd_pin、data_validator、automatic、command(Arduino端)和cmd_pin、data_validator、serial_busy(ESP32端)均未声明,编译会直接报错。
最优传输方案推荐
结合闲置引脚(2、13、A3、A4、A5),推荐以下两种成熟可靠的方案:
方案1:软件串口(SoftwareSerial)
适用引脚:数字引脚2和13
利用Arduino的SoftwareSerial库模拟串口通信,仅需2根引脚(RX/TX),无需自定义协议,稳定性高。
- Arduino端实现:
#include <SoftwareSerial.h> SoftwareSerial espSerial(2, 13); // 2=RX, 13=TX String command = ""; void setup() { Serial.begin(9600); espSerial.begin(9600); } void loop() { if (espSerial.available()) { command = espSerial.readStringUntil('\n'); // 执行指令逻辑 Serial.println(command); } } - ESP32端实现:
#define tx_pin 13 // 对应Arduino的RX引脚2 #define rx_pin 2 // 对应Arduino的TX引脚13 HardwareSerial espSerial(1); // 用ESP32的硬件串口1,避免占用默认串口 void setup() { Serial.begin(115200); espSerial.begin(9600, SERIAL_8N1, rx_pin, tx_pin); } void forward() { espSerial.println("Forward"); }
方案2:I2C通信
适用引脚:A4(SDA)和A5(SCL)
Arduino的A4/A5是默认I2C引脚,适合主从通信(ESP32为主设备,Arduino为从设备),仅需2根引脚,支持多设备扩展。
- Arduino端(从设备):
#include <Wire.h> #define SLAVE_ADDRESS 0x08 String command = ""; void receiveEvent(int howMany) { while (Wire.available()) { command += (char)Wire.read(); } } void setup() { Wire.begin(SLAVE_ADDRESS); Wire.onReceive(receiveEvent); Serial.begin(9600); } void loop() { if (command.length() > 0) { Serial.println(command); command = ""; } } - ESP32端(主设备):
#include <Wire.h> #define SLAVE_ADDRESS 0x08 void setup() { Serial.begin(115200); Wire.begin(); // ESP32默认用SDA=21, SCL=22,若需改引脚可传参:Wire.begin(sda_pin, scl_pin) } void sendCommand(String cmd) { Wire.beginTransmission(SLAVE_ADDRESS); Wire.print(cmd); Wire.endTransmission(); } void forward() { sendCommand("Forward"); }
修复现有脉冲方案的建议(可选)
如果坚持使用自定义脉冲,需修正以下几点:
- 统一脉冲格式:ESP32每个指令仅发送一段高脉冲+一段低脉冲,比如Forward发送HIGH 5000ms→LOW 25000ms。
- 调整pulseIn超时:修改
pulseIn调用为pulseIn(cmd_pin, HIGH, 35000000)(35秒超时),确保能读取长脉冲。 - 修复同步逻辑:在
check()函数的return前添加digitalWrite(data_validator, HIGH);,或者将该语句放在函数末尾,确保无论是否匹配指令都恢复引脚状态。 - 补全变量声明:添加所有未定义的引脚宏和变量。
内容的提问来源于stack exchange,提问作者Deekshith Ranga Babu Tirumala

