Python与Arduino线程通信故障排查求助
问题:Python与Arduino Leonardo串行通信仅首次运行正常,再次运行卡在等待"ok"
我被这个bug困扰了一整天,四处搜索、尝试调试都没能找到解决方案!
我正尝试通过线程实现Python程序与Arduino的通信:线程向Arduino发送"read"指令,等待Arduino返回"ok"信号后,再发送要读取的引脚编号;Arduino收到"read"指令后返回"ok",等待Python发送的模拟引脚编号,获取编号后读取该引脚的模拟数据。我已为代码添加注释方便分析。
当前代码仅能正常运行一次:即Arduino重新编译完成后的首次运行。再次运行程序时,会卡在等待"ok"的循环中……我尝试过使用定时器强制Arduino返回"ok",也试过清空输入缓冲区,但都没有效果。我现在迫切想知道问题原因及解决办法。
注:我使用的是Arduino Leonardo
Python 代码
import threading import serial import time class AnalogReader(threading.Thread): def __init__(self, arduino_port, arduino_baudrate, analog_pin, read_rate): threading.Thread.__init__(self) self.arduino_port = arduino_port self.arduino_baudrate = arduino_baudrate self.analog_pin = analog_pin self.read_rate = read_rate # 添加读取频率变量 self.delay = 1/self.read_rate self._stop_event = threading.Event() def stop(self): self._stop_event.set() def run(self): # 连接Arduino ser = serial.Serial(self.arduino_port, self.arduino_baudrate) ser.reset_input_buffer() time.sleep(2) # 等待Arduino初始化 print("before first read") # 发送读取指令 ser.write(bytes("read", 'utf-8')) # 等待"ok"响应 start_time = time.time() while True: response = ser.readline().decode().strip() print("t " + response) if response == "ok": break elif time.time() - start_time > 3: # 3秒超时重发 ser.reset_input_buffer() ser.write(bytes("read", 'utf-8')) start_time = time.time() # 发送引脚编号 ser.write(bytes(str(self.analog_pin), 'utf-8')) # 持续读取数据直到停止 while not self._stop_event.is_set(): try: line = ser.readline().decode().strip() value = float(line) print(value) # 打印收到的数值 except ValueError: pass time.sleep(self.delay) # 按设定频率读取 # 发送停止指令 ser.write(bytes('STOP', 'utf-8')) # 关闭串口 ser.close()
Arduino 代码
int analogPin; bool stopReading = false; float voltage; void setup() { // 启动串行通信 Serial.begin(9600); randomSeed(analogRead(0)); } void loop() { if (Serial.available() > 0) { String command = Serial.readStringUntil('\n'); if (command == "read") { //delay(2); Serial.println("ok"); delay(2); analogPin = Serial.parseInt(); int analogValue; while (!stopReading) { analogValue = analogRead(analogPin); voltage = analogValue * (5.0 / 1023.0) ; Serial.println(voltage, 8); if (Serial.available() > 0) { String command = Serial.readStringUntil('\n'); if (command == "STOP\n") { stopReading = true; } } } } } }
问题原因分析
- 状态未重置:首次运行后
stopReading被设为true,后续即使收到新的"read"指令,Arduino也不会进入读取引脚的循环,自然不会返回"ok"。 - 换行符不匹配:Python发送指令时没有加换行符,但Arduino用
readStringUntil('\n')读取,首次运行可能因初始化巧合成功,后续会一直等待换行符才处理指令。 - STOP指令判断错误:
readStringUntil('\n')返回的字符串不包含末尾的换行符,所以command == "STOP\n"永远匹配失败,导致stopReading无法被重置。
解决方案
修改后的Arduino代码
int analogPin; bool stopReading = false; float voltage; void setup() { Serial.begin(9600); randomSeed(analogRead(0)); } void loop() { if (Serial.available() > 0) { String command = Serial.readStringUntil('\n'); command.trim(); // 去除首尾空白(包括换行、空格) if (command == "read") { stopReading = false; // 每次收到read指令重置状态 Serial.println("ok"); // 等待引脚编号,添加3秒超时 unsigned long startTime = millis(); while (Serial.available() == 0 && millis() - startTime < 3000) { delay(10); } if (Serial.available() > 0) { analogPin = Serial.parseInt(); // 清除parseInt后残留的换行符 while (Serial.available() > 0 && Serial.read() != '\n'); } else { Serial.println("timeout"); return; } int analogValue; while (!stopReading) { analogValue = analogRead(analogPin); voltage = analogValue * (5.0 / 1023.0); Serial.println(voltage, 8); if (Serial.available() > 0) { String stopCmd = Serial.readStringUntil('\n'); stopCmd.trim(); if (stopCmd == "STOP") { stopReading = true; // 清空缓冲区残留数据 while (Serial.available() > 0) Serial.read(); } } delay(10); // 降低循环频率,减少资源占用 } } else if (command == "STOP") { // 单独处理STOP指令,确保随时能终止 stopReading = true; while (Serial.available() > 0) Serial.read(); } } }
修改后的Python代码
import threading import serial import time class AnalogReader(threading.Thread): def __init__(self, arduino_port, arduino_baudrate, analog_pin, read_rate): threading.Thread.__init__(self) self.arduino_port = arduino_port self.arduino_baudrate = arduino_baudrate self.analog_pin = analog_pin self.read_rate = read_rate self.delay = 1/self.read_rate self._stop_event = threading.Event() def stop(self): self._stop_event.set() def run(self): ser = serial.Serial(self.arduino_port, self.arduino_baudrate) ser.reset_input_buffer() ser.reset_output_buffer() time.sleep(2) print("before first read") # 发送指令时添加换行符,匹配Arduino读取逻辑 ser.write(bytes("read\n", 'utf-8')) start_time = time.time() while True: if ser.in_waiting > 0: response = ser.readline().decode().strip() print("t " + response) if response == "ok": break elif response == "timeout": ser.write(bytes("read\n", 'utf-8')) start_time = time.time() elif time.time() - start_time > 3: ser.reset_input_buffer() ser.write(bytes("read\n", 'utf-8')) start_time = time.time() # 发送引脚编号时添加换行符 ser.write(bytes(f"{self.analog_pin}\n", 'utf-8')) while not self._stop_event.is_set(): try: if ser.in_waiting > 0: line = ser.readline().decode().strip() value = float(line) print(value) except ValueError: pass time.sleep(self.delay) # 发送STOP指令时添加换行符 ser.write(bytes('STOP\n', 'utf-8')) time.sleep(0.5) # 等待Arduino处理完指令再关闭串口 ser.close()
额外注意事项
- Arduino Leonardo连接串口时会自动重启,Python中的
time.sleep(2)可延长至3秒确保初始化完成。 - 每次关闭串口前,等待指令处理完成,避免残留数据影响下一次通信。
- 可在Python中添加串口关闭的异常处理,确保程序中断时也能正确关闭串口。
内容的提问来源于stack exchange,提问作者beatle
相关产品推荐
相关产品推荐

