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

Linux环境下Python pySerial中USB重连ttyUSB端口切换的解决方法

这确实是Linux下USB转串口设备常见的痛点——系统会按设备插入顺序分配ttyUSB*编号,只要旧的设备节点没被正确释放(比如程序还持有连接),重新插拔就会用下一个编号。你的翻转切换方案能工作,但确实不够灵活,尤其是如果有多个USB串口设备时容易出错。下面给你几个更优雅的解决方案,按推荐程度排序:

1. 给USB串口设备绑定固定别名(最推荐,一劳永逸)

从系统层面解决问题,给你的USB转串口适配器分配一个固定的、有意义的设备名(比如/dev/my_serial),不管插拔多少次都不会变。

操作步骤:

  • 插入你的USB适配器,运行命令查看设备的唯一标识:
    udevadm info --attribute-walk --name=/dev/ttyUSB0 | grep -E "(idVendor|idProduct)"
    
    你会得到类似ATTRS{idVendor}=="1a86"和ATTRS{idProduct}=="7523"的输出(这是CH340芯片的典型值,你的设备可能不同)。
  • 创建udev规则文件:
    sudo nano /etc/udev/rules.d/99-usb-serial.rules
    
  • 写入以下规则(替换成你刚才拿到的idVendor和idProduct值):
    SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="my_serial"
    
  • 重载udev规则让其生效:
    sudo udevadm control --reload-rules && sudo udevadm trigger
    

之后你的设备会一直有一个别名/dev/my_serial,程序里直接用这个路径即可,完全不用管ttyUSB0/ttyUSB1的问题。对应的Python代码示例:

import serial
import time

FIXED_PORT = "/dev/my_serial"
BAUD_RATE = 9600

def get_serial_connection():
    try:
        ser = serial.Serial(FIXED_PORT, BAUD_RATE, timeout=1)
        print(f"成功连接到 {FIXED_PORT}")
        return ser
    except serial.SerialException as e:
        print(f"连接失败: {str(e)}")
        return None

ser = get_serial_connection()
while True:
    if ser and ser.is_open:
        try:
            # 这里写你的串口业务逻辑
            received = ser.readline()
            if received:
                print(f"收到数据: {received.decode().strip()}")
        except serial.SerialException:
            print("连接断开,尝试重连...")
            ser.close()
            ser = None
    else:
        ser = get_serial_connection()
    time.sleep(1)

2. 程序中动态扫描并匹配目标设备

如果不想修改系统配置,可以在程序里主动扫描所有可用串口,通过设备的描述、厂商ID等信息匹配你的目标适配器,而不是硬编码端口编号。

需要用到pyserial的list_ports工具(如果没装的话,先跑pip install pyserial):

import serial
from serial.tools import list_ports
import time

BAUD_RATE = 9600

def find_target_serial_port():
    # 扫描所有可用的串口设备
    all_ports = list_ports.comports()
    for port in all_ports:
        # 可以根据设备描述、厂商ID、产品ID来匹配
        # 比如我的设备描述是"USB Serial Device",你可以改成你设备的描述
        if "USB Serial Device" in port.description:
            print(f"找到目标设备: {port.device}")
            return port.device
    # 没找到返回None
    print("未找到目标串口设备")
    return None

def connect_to_port(port):
    try:
        ser = serial.Serial(port, BAUD_RATE, timeout=1)
        return ser
    except serial.SerialException as e:
        print(f"连接到 {port} 失败: {str(e)}")
        return None

ser = None
target_port = find_target_serial_port()

while True:
    if not ser or not ser.is_open:
        if not target_port:
            target_port = find_target_serial_port()
        else:
            ser = connect_to_port(target_port)
            if not ser:
                # 连接失败,重新扫描端口
                target_port = find_target_serial_port()
    else:
        try:
            # 串口操作逻辑
            data = ser.readline()
            if data:
                print(f"收到: {data.decode().strip()}")
        except serial.SerialException:
            print("连接中断,关闭旧连接并重连")
            ser.close()
            ser = None
            target_port = None
    time.sleep(1)

3. 优化你的翻转切换逻辑(临时过渡方案)

如果暂时想用你原来的思路,一定要确保在连接失败时关闭旧的串口对象,避免占用端口导致系统分配新编号:

import serial
import time

available_ports = ["/dev/ttyUSB0", "/dev/ttyUSB1"]
current_port_index = 0
ser = None

def attempt_connection(port):
    global ser
    # 先关闭可能存在的旧连接
    if ser and ser.is_open:
        try:
            ser.close()
        except Exception:
            pass
    try:
        ser = serial.Serial(port, 9600, timeout=1)
        print(f"已连接到 {port}")
        return True
    except serial.SerialException as e:
        print(f"连接 {port} 失败: {str(e)}")
        return False

# 初始尝试连接
attempt_connection(available_ports[current_port_index])

while True:
    if ser and ser.is_open:
        try:
            # 你的串口操作
            data = ser.readline()
            if data:
                print(f"收到数据: {data.decode().strip()}")
        except serial.SerialException:
            print("连接出错,准备切换端口")
            ser.close()
            ser = None
            current_port_index = (current_port_index + 1) % len(available_ports)
    else:
        # 尝试连接当前端口,失败则切换
        if not attempt_connection(available_ports[current_port_index]):
            current_port_index = (current_port_index + 1) % len(available_ports)
    time.sleep(1)

总结一下,最推荐的是第一种方案,从系统层面绑定固定别名,彻底解决端口编号变动的问题;第二种方案适合不能修改系统配置的场景,动态识别设备也很灵活;第三种只是优化了你原来的思路,作为临时过渡可以用,但长期来看还是前两种更可靠。

内容的提问来源于stack exchange,提问作者secarica

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:49:07