第4章:串口通信实战——扫描可用串口、配置参数、打开/关闭串口

串口通信,说白了就是让电脑和光谱仪“说上话”。

我刚开始做自动进样系统时,最头疼的就是串口连不上。后来发现,90%的问题都出在串口参数没配对上。这一章,咱们就把串口通信的底裤扒干净。

4.1 扫描可用串口——别让设备“隐身”

你想想看,光谱仪插上USB转串口线后,它在电脑里到底叫啥名字?

Windows里可能是 COM3COM5,Linux里可能是 /dev/ttyUSB0。手动去设备管理器里翻,太慢了。我习惯用代码自动扫一遍。

核心思路:调用系统API,枚举所有可用的串口设备。

Python里最常用的库是 pyserial 自带的 serial.tools.list_ports

import serial.tools.list_ports

def scan_serial_ports():
    """扫描并打印所有可用串口"""
    ports = serial.tools.list_ports.comports()
    if not ports:
        print("没有发现可用串口。请检查连接。")
        return []
    
    print(f"发现 {len(ports)} 个串口:")
    for port in ports:
        print(f"  端口: {port.device}")
        print(f"  描述: {port.description}")
        print(f"  硬件ID: {port.hwid}")
        print("-" * 40)
    return ports

# 调用扫描
available_ports = scan_serial_ports()

我在项目中遇到过一个问题:有些USB转串口线插上后,系统能识别但 list_ports 返回空。后来发现是驱动没装好。嗯,这里要注意——先装驱动,再插线,顺序反了容易出幺蛾子。

小技巧:如果扫描结果为空,试试拔插USB线,或者换个USB口。有些劣质HUB会导致串口“假死”。

4.2 配置串口参数——对不上号就“哑巴”

串口通信就像两个人打电话,必须用同一个“语言”。这个语言由四个参数决定:

参数 常见值 说明
波特率 9600, 19200, 115200 每秒传输的比特数。光谱仪常用9600或115200
数据位 7, 8 一帧数据中有效数据的位数。绝大多数设备用8位
停止位 1, 1.5, 2 帧结束标志。常用1位停止位
校验位 None, Even, Odd 检错方式。无校验(None)最常见

我曾经调试一台老款光谱仪,折腾了两小时连不上。最后发现它的波特率是 19200,而我默认设成了9600。说白了,就是鸡同鸭讲。

配置代码很简单,但参数必须和设备手册一致:

import serial

def configure_serial(port_name, baudrate=9600, bytesize=8, 
                     parity='N', stopbits=1, timeout=1):
    """
    配置并打开串口
    :param port_name: 串口名,如 'COM3' 或 '/dev/ttyUSB0'
    :param baudrate: 波特率
    :param bytesize: 数据位
    :param parity: 校验位 'N'无 'E'偶 'O'奇
    :param stopbits: 停止位
    :param timeout: 读取超时(秒)
    :return: serial.Serial 对象
    """
    try:
        ser = serial.Serial(
            port=port_name,
            baudrate=baudrate,
            bytesize=bytesize,
            parity=parity,
            stopbits=stopbits,
            timeout=timeout
        )
        print(f"串口 {port_name} 配置成功!")
        print(f"  参数: {baudrate}/{bytesize}/{parity}/{stopbits}")
        return ser
    except serial.SerialException as e:
        print(f"串口配置失败: {e}")
        return None

# 示例:配置光谱仪常用参数
ser = configure_serial('COM3', baudrate=115200, bytesize=8, 
                       parity='N', stopbits=1)

注意:配置参数时,parity 参数在 pyserial 里用字符串表示:'N' 无校验,'E' 偶校验,'O' 奇校验。大小写敏感,别写错了。

4.3 打开与关闭串口——优雅地“挂电话”

串口是独占资源。一个程序打开了,别的程序就不能再用。所以用完一定要关掉,不然下次打开会报错。

我见过有人写代码只开不关,结果程序崩溃后串口一直占着,只能重启电脑。嗯,这很坑。

正确的做法是:

import serial
import time

def open_and_close_demo():
    """演示串口的打开与关闭"""
    # 打开串口
    ser = serial.Serial('COM3', 115200, timeout=1)
    
    if ser.is_open:
        print(f"串口 {ser.port} 已打开")
        print(f"当前参数: {ser.baudrate}/{ser.bytesize}/{ser.parity}/{ser.stopbits}")
        
        # 这里做你的通信操作
        # ser.write(b'AT\r\n')
        # response = ser.readline()
        
        # 模拟工作2秒
        time.sleep(2)
        
        # 关闭串口
        ser.close()
        print(f"串口 {ser.port} 已关闭")
    else:
        print("串口打开失败")

# 更推荐的方式:使用 with 语句自动管理
def safe_serial_operation():
    """使用 with 语句,自动关闭串口"""
    with serial.Serial('COM3', 115200, timeout=1) as ser:
        print(f"串口 {ser.port} 已打开,开始工作...")
        # 你的通信代码写在这里
        # 即使发生异常,with 块结束后也会自动关闭
        pass
    print("串口已自动关闭")

最佳实践:永远使用 with 语句操作串口。它会在代码块结束后自动调用 close(),哪怕中间出了异常。这比手动 try-finally 更简洁。

4.4 完整实战——扫描+配置+开关一条龙

把上面三块拼起来,就是一个完整的串口初始化流程:

import serial
import serial.tools.list_ports

def auto_connect_spectrometer(target_description=None):
    """
    自动扫描并连接光谱仪
    :param target_description: 可选,根据描述筛选设备
    :return: 串口对象或 None
    """
    # 1. 扫描可用串口
    ports = serial.tools.list_ports.comports()
    if not ports:
        print("未发现任何串口设备")
        return None
    
    # 2. 筛选目标设备
    target_port = None
    for port in ports:
        print(f"发现: {port.device} - {port.description}")
        if target_description and target_description in port.description:
            target_port = port.device
            print(f"   -> 匹配目标设备: {target_port}")
            break
    
    if not target_port:
        # 如果没有指定筛选条件,默认用第一个
        target_port = ports[0].device
        print(f"未指定筛选条件,使用第一个串口: {target_port}")
    
    # 3. 配置并打开串口
    try:
        ser = serial.Serial(
            port=target_port,
            baudrate=115200,   # 光谱仪常用波特率
            bytesize=8,
            parity='N',
            stopbits=1,
            timeout=2
        )
        print(f"成功连接到 {target_port}")
        return ser
    except serial.SerialException as e:
        print(f"连接失败: {e}")
        return None

# 使用示例
ser = auto_connect_spectrometer(target_description="USB Serial Port")
if ser:
    # 通信完成后关闭
    ser.close()
    print("串口已关闭,资源释放完毕")

避坑指南:我曾经在调试时发现,某些光谱仪在打开串口后会发送一段“握手信息”。如果你不读走它,后续的指令就会错位。所以打开串口后,建议先 ser.flushInput() 清空缓冲区。

4.5 常见问题与排查

  • 串口扫描不到? 先看设备管理器里有没有。没有就是驱动问题,有但扫不到可能是权限问题(Linux下需要 sudo 或加入 dialout 组)。
  • 能打开但收不到数据? 检查波特率、数据位、停止位、校验位是否和设备一致。用示波器看TX/RX引脚有没有波形,最直接。
  • 打开串口报“权限被拒绝”? Windows下可能是别的程序占用了(比如串口调试助手)。关掉其他程序再试。
  • 数据乱码? 大概率是波特率不匹配。或者数据位/校验位设置错误。

嗯,串口通信就这么点事。但就是这点事,搞不定的话整个自动进样系统都动不了。下一章,咱们开始真正和光谱仪“对话”——发送指令、接收数据。