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

Arduino与Python串口通信异常:状态机未按预期切换求助

问题排查与修复方案

我一眼就看到几个关键问题,导致你的Python发送命令后状态机没按预期工作,咱们一步步来修:

1. Python端核心问题:重复打开串口导致Arduino复位

Arduino Nano在每次建立新的串口连接时会自动复位(这是硬件设计特性),你每个函数里都重新创建serial.Serial实例,等于每次发送命令都让Arduino重启,状态机直接回到初始的IDLEx状态,自然没法正常切换状态。

修复后的Python代码

把串口初始化移到所有函数外面,只打开一次连接:

import serial
import time

# 仅初始化一次串口,避免重复连接触发Arduino复位
dev = serial.Serial("COM3", 9600, timeout=5)
time.sleep(2)  # 给Arduino留足重启初始化的时间

def query():
    cmd = "0"
    dev.write(cmd.encode("utf-8"))
    print("Python: Query ", dev.readline())

def up():
    cmd = "1"
    dev.write(cmd.encode("utf-8"))
    print("Python: up ", dev.readline())

def pickup():
    cmd = "2"
    dev.write(cmd.encode("utf-8"))
    print("Python: pickup ", dev.readline())

def ship():
    cmd = "3"
    dev.write(cmd.encode("utf-8"))
    print("Python: ship ", dev.readline())

def origin():
    cmd = "4"
    dev.write(cmd.encode("utf-8"))
    print("Python: origin ", dev.readline())

tokenDict = {
    "001":query,
    "002":up,
    "003":ship,
    "004":pickup,
    "005":origin
}

lineList = [line.rstrip('\n') for line in open("cmd.txt")]
for line in lineList:
    time.sleep(1)
    functionToCall = tokenDict[line]
    functionToCall()

# 程序结束后关闭串口
dev.close()

2. Arduino端的细节问题修复

问题1:多余的delay(300)拖慢响应

你用Serial.read()读取单个字符,没必要加延迟,这个300ms的等待会让状态切换变慢,甚至可能错过后续命令。

问题2:状态切换后的响应时机不对

原代码里,进入UP这类状态后,要等到下一次loop执行才会打印状态,但Python发送命令后会立刻读取串口,导致无法及时获取响应。应该在切换状态的同时就发送响应信息。

问题3:ORIGIN状态的break位置不规范

虽然不影响逻辑,但代码结构混乱,调整后更易维护。

修复后的Arduino代码

int cmd = 0;
enum {IDLEx, UP, PICKUP, SHIP, ORIGIN};
uint8_t STATE = IDLEx;

void setup() {
    Serial.begin(9600);
    pinMode(LED_BUILTIN, OUTPUT);
}

void readSerialData() {
    if (Serial.available() > 0) {
        cmd = Serial.read();
        process_serial(cmd, &STATE);
    }
}

void process_serial(int data, uint8_t *state) {
    switch (*state) {
        case IDLEx: {
            if (data == '0') {
                Serial.println("Arduino: IDLE");
            } else if (data == '1' ) {
                *state = UP;
                Serial.println("Arduino: UP"); // 切换状态时立即发送响应
            } else if (data == '2') {
                *state = PICKUP;
                Serial.println("Arduino: PICKUP");
            } else if (data == '3') {
                *state = SHIP;
                Serial.println("Arduino: SHIP");
            } else if (data == '4') {
                *state = ORIGIN;
                Serial.println("Arduino: ORIGIN");
            }
            break;
        }
        case UP: {
            // 这里可以添加UP状态的具体动作,比如控制电机
            delay(1000);
            *state = PICKUP;
            Serial.println("Arduino: PICKUP"); // 自动切换状态时发送通知
            break;
        }
        case PICKUP: {
            // 添加PICKUP状态的具体动作
            delay(1000);
            *state = SHIP;
            Serial.println("Arduino: SHIP");
            break;
        }
        case SHIP: {
            // 添加SHIP状态的具体动作
            delay(1000);
            *state = ORIGIN;
            Serial.println("Arduino: ORIGIN");
            break;
        }
        case ORIGIN: {
            // 添加ORIGIN状态的具体动作
            delay(1000);
            *state = IDLEx;
            Serial.println("Arduino: IDLE");
            break;
        }
    }
}

void loop() {
    readSerialData();
}

3. 额外注意事项

  • 确保cmd.txt里的每行只有一个命令码(比如001),没有多余空格或换行
  • Python的串口超时设置timeout=5足够应对大部分场景,若Arduino响应慢可适当调整
  • 测试时先单独运行query()函数,确认能读到Arduino: IDLE,再逐步测试其他命令

这样修改后,你的状态机就能按照预期工作,Python发送命令后能正确触发状态切换并读取到对应的响应了。

内容的提问来源于stack exchange,提问作者Mr. Chat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 09:12:44