You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
          }
        }
      }
    }
  }
}

问题原因分析

  1. 状态未重置:首次运行后stopReading被设为true,后续即使收到新的"read"指令,Arduino也不会进入读取引脚的循环,自然不会返回"ok"。
  2. 换行符不匹配:Python发送指令时没有加换行符,但Arduino用readStringUntil('\n')读取,首次运行可能因初始化巧合成功,后续会一直等待换行符才处理指令。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 18:50:39