3、串口通信基础:RS232/RS485协议、波特率、数据位、停止位、校验位,Python串口扫描与连接测试

做仪器数据采集,串口通信是绕不开的基本功。我刚开始接触多通道同步采集时,以为串口就是插上线就能读数据,结果被各种奇奇怪怪的问题折腾了好几个通宵。说白了,串口通信就像两个人在嘈杂的车间里对话——你得约定好说话的速度、每句话的长度、什么时候结束,还得加点校验防止听错。

3.1 RS232 vs RS485:两种常见的物理层协议

先说说这两个协议的区别。RS232和RS485,本质上都是串口通信的电气标准,但脾气完全不同。

RS232:点对点通信,一根线只能连两个设备。传输距离短,一般15米以内。电压是±12V的逻辑电平,抗干扰能力一般。我在实验室调试一台老式气相色谱仪时,就遇到过232线长了点,数据就开始丢包。嗯,后来老老实实把线剪短到3米,问题就解决了。

RS485:差分信号传输,抗干扰能力强。最让我喜欢的是它支持多节点——一条总线上可以挂32个设备(甚至更多)。传输距离能到1200米。做多通道同步采集时,我几乎都用RS485。你想想看,一个车间里几十台仪器,用485总线串起来,一根双绞线搞定,多省事。

特性 RS232 RS485
传输方式 单端 差分
最大节点数 2(点对点) 32(典型)
最大距离 约15米 约1200米
抗干扰 一般
典型应用 近距离调试 工业现场、多设备组网
我的经验:如果设备距离超过5米,或者现场有电机、变频器之类的干扰源,果断选RS485。别问我怎么知道的——我曾经用232线接了10米,数据全乱码,折腾了两天才发现是线太长。

3.2 波特率、数据位、停止位、校验位

这四个参数,是串口通信的「四要素」。任何一个对不上,数据就收不到。我见过太多新手在这上面栽跟头。

波特率:说白了就是每秒传输多少个比特。常见的有9600、19200、115200。波特率越高,传输越快,但误码率也会上升。我个人习惯:仪器数据量不大的话,用9600或19200,稳定第一。高速采集时才上115200。

数据位:一帧数据里有效数据的位数。通常是7位或8位。现在绝大多数设备都用8位。7位是早期ASCII码时代的遗物,基本可以忽略。

停止位:表示一帧数据结束的标志位。常见的是1位、1.5位、2位。我几乎只用1位停止位。只有在一些老设备上,才会遇到需要2位停止位的情况。

校验位:用于检测传输错误。有奇校验、偶校验、无校验三种。我建议:除非设备手册明确要求,否则一律用无校验(None)。为什么?因为校验位会占用一个数据位,而且很多设备的校验实现有bug。我曾经被一台进口光谱仪的校验位坑过——手册写的是偶校验,实际跑起来必须设成无校验才能正常通信。

避坑指南:我曾经调试一台多通道数据采集器,四个通道的波特率设置不一样,结果只有两个通道能读到数据。后来才发现,每个通道的串口参数必须完全一致。记住:发送端和接收端的波特率、数据位、停止位、校验位,必须一模一样,差一点都不行。

3.3 Python串口扫描:找到你的设备

拿到一台新仪器,第一件事就是找到它连在哪个串口上。Windows下通常是COM3、COM4这样的名字,Linux下是/dev/ttyUSB0、/dev/ttyS0。手动一个个试太慢了,我们用Python自动扫描。

先安装pyserial库:

pip install pyserial

然后写个扫描脚本:

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("---")
    
    return ports

if __name__ == "__main__":
    scan_serial_ports()

运行后,你会看到类似这样的输出:

发现 2 个串口:
  端口: COM3
  描述: USB Serial Port (COM3)
  硬件ID: USB VID:PID=0403:6001 SER=12345678
---
  端口: COM5
  描述: Bluetooth Serial Port (COM5)
  硬件ID: BTHENUM\{00001101-0000-1000-8000-00805F9B34FB}
---
小技巧:硬件ID(VID/PID)是识别设备最可靠的方式。同一台仪器插到不同电脑上,COM号可能不同,但VID/PID是固定的。我写采集程序时,都会让用户先扫描一次,然后根据VID/PID自动匹配串口,省去手动配置的麻烦。

3.4 连接测试:确认通信正常

找到串口后,别急着读数据。先做个简单的连接测试,确认参数设置正确。

import serial
import time

def test_serial_connection(port, baudrate=9600, timeout=2):
    """测试串口连接"""
    try:
        # 打开串口
        ser = serial.Serial(
            port=port,
            baudrate=baudrate,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            timeout=timeout
        )
        
        print(f"成功打开串口 {port}")
        print(f"参数: {baudrate}波特率, 8数据位, 无校验, 1停止位")
        
        # 发送一个简单的查询命令(具体命令看仪器手册)
        # 这里以发送"*IDN?"为例,很多仪器支持这个标准命令
        test_cmd = b"*IDN?\r\n"
        ser.write(test_cmd)
        print(f"发送: {test_cmd}")
        
        # 等待响应
        time.sleep(0.5)
        response = ser.read_all()
        
        if response:
            print(f"收到响应: {response.decode('utf-8', errors='ignore')}")
            print("连接测试通过!")
        else:
            print("没有收到响应,请检查:")
            print("  1. 波特率是否正确")
            print("  2. 仪器是否上电")
            print("  3. 线缆是否接好")
        
        ser.close()
        return True
        
    except serial.SerialException as e:
        print(f"串口打开失败: {e}")
        return False

if __name__ == "__main__":
    # 先扫描,再测试
    ports = scan_serial_ports()
    if ports:
        test_serial_connection(ports[0].device)
注意:测试时如果没收到响应,不要急着改参数。先检查物理连接——线松了、仪器没开机、串口被其他程序占用了,这些都是常见原因。我曾经花了一下午调参数,最后发现是USB转串口的驱动没装好。嗯,这种低级错误,犯过一次就记住了。

3.5 实战中的常见问题

最后分享几个我踩过的坑:

  • 波特率不匹配:仪器手册写的是9600,但实际可能是19200。我建议先用9600试,收不到再换19200、115200逐个试。
  • 流控问题:有些仪器需要硬件流控(RTS/CTS),有些不需要。我一般先关掉流控,不行再开。
  • 换行符:有的仪器用\r\n,有的只用\n,还有的用\r。发送命令前,先确认仪器手册里的换行符格式。
  • 串口被占用:调试时经常遇到「端口被占用」的错误。检查一下是不是有其他程序(比如串口助手、别的Python脚本)占用了同一个串口。

串口通信看起来简单,但细节决定成败。把基础打牢了,后面做多通道同步采集才能顺风顺水。下一章,我们开始实战——用Python读取第一台仪器的数据。