Arduino与树莓派串口通信故障:单独指令有效组合后无响应
问题分析与解决方案
核心问题定位
从代码和现象来看,导致电机无动作、中断失效的主要原因有以下几点:
1. Loop函数的阻塞与干扰代码
- Arduino的
loop()末尾存在无条件执行的MoveReverse调用,这会导致串口指令触发的电机动作刚执行就被反转操作打断,看起来像是电机没动。 - 代码中残留了无效的注释行
Put whatever you want here!,会导致编译错误(如果未修正的话)。
2. 串口接收的冗余字符干扰
Python的input()函数会将用户输入的内容(包括回车换行符)发送给Arduino,Serial.readString()会读取到包含换行/回车的字符串,虽然toInt()能忽略这些字符,但可能导致后续串口缓冲区残留数据,影响指令判断。
3. 中断变量读取的原子性问题
counter_A和counter_B是volatile类型的16位整数,在主程序读取时,若被中断打断(比如读取高8位时触发中断修改变量),会导致读取到错误的数值,进而让电机函数的循环条件失效。
4. 电机循环逻辑的潜在问题
部分电机函数(如MoveReverse、SpinRight)的while循环使用&&(逻辑与)条件,这意味着只有当两个计数器都未达到steps时才会运行,若其中一个计数器先达标,另一个电机也会立刻停止,可能导致动作不完整。
具体解决方案
1. 修复Loop函数
删除无效代码和干扰性的无条件反转调用,修改后的loop()如下:
void loop() { delay(100); const int compareOne = 1; const int compareTwo = 2; const int compareThree = 3; if (Serial.available() > 0){ String stringFromSerial = Serial.readStringUntil('\n'); // 读取到换行符为止 stringFromSerial.trim(); // 移除空格、换行、回车等冗余字符 int cmd = stringFromSerial.toInt(); if (cmd == compareOne){ Serial.println("Forward"); MoveForward(CMtoSteps(50), 255); } else if (cmd == compareTwo){ Serial.println("Spin Right"); SpinRight(CMtoSteps(10), 255); } else if (cmd == compareThree){ Serial.println("Spin Left"); SpinLeft(CMtoSteps(10), 255); } else { Serial.println("Not equal"); } } }
- 使用
else if替代多个独立if,避免重复判断 - 用
readStringUntil('\n')精准读取单条指令 - 提前将字符串转为整数,减少重复转换操作
2. 优化Python发送逻辑
修改Python代码,确保只发送纯指令字符,不带换行:
import serial, time arduino = serial.Serial('/dev/ttyUSB0', 9600, timeout=1) cmd = '' while cmd != '0': cmd = input('Enter a cmd ').strip() # 移除输入的换行和空格 arduino.write(cmd.encode('ascii')) time.sleep(0.1) # 给Arduino预留处理时间
3. 修复中断变量读取的原子性
在所有读取counter_A和counter_B的地方,使用临界区保护,以MoveForward为例:
void MoveForward(int steps, int mspeed) { // 关闭中断后重置计数器,避免被中断干扰 noInterrupts(); counter_A = 0; counter_B = 0; interrupts(); // Set Motor A forward digitalWrite(in1, HIGH); digitalWrite(in2, LOW); // Set Motor B forward digitalWrite(in3, HIGH); digitalWrite(in4, LOW); // Go forward until step value is reached while (true) { // 读取计数器时关闭中断 noInterrupts(); int currentA = counter_A; int currentB = counter_B; interrupts(); bool aDone = currentA >= steps; bool bDone = currentB >= steps; if (aDone && bDone) break; // 两个电机都达标才退出循环 analogWrite(enA, aDone ? 0 : mspeed); analogWrite(enB, bDone ? 0 : mspeed); } // Stop when done analogWrite(enA, 0); analogWrite(enB, 0); noInterrupts(); counter_A = 0; counter_B = 0; interrupts(); }
- 所有对
counter_A和counter_B的读写操作都用noInterrupts()和interrupts()包裹,保证原子性 - 优化循环退出条件,确保两个电机都完成步数后才停止
4. 统一电机函数的循环逻辑
将MoveReverse、SpinRight、SpinLeft的while循环条件改为和MoveForward一致的逻辑,避免因&&导致的提前停止问题。
验证步骤
- 上传修改后的Arduino代码,确保无编译错误
- 运行修改后的Python脚本,输入
1、2、3测试各动作 - 若仍有问题,可在电机函数中打印
steps、currentA、currentB的值,验证计数器是否正常递增,排查传感器或接线问题
内容的提问来源于stack exchange,提问作者Garberchov
相关产品推荐
相关产品推荐

