4、串口通信基础:RS232/RS485物理层、串口参数、Python serial库实战

各位同学,今天我们来聊聊串口通信。说实话,串口通信在工业现场的地位,就像螺丝刀在工具箱里一样——看着不起眼,但真干活的时候离不开它。我做了十几年自动化,调试过的设备没有一千也有八百,其中至少七成都是通过串口连起来的。

这一章,我会带大家把串口通信的底裤扒干净。从物理层的RS232和RS485,到那些让人头疼的波特率、数据位、校验位,最后再用Python的serial库撸一遍实战。嗯,咱们开始吧。

4.1 物理层:RS232 vs RS485,到底选哪个?

先问大家一个问题:你见过那种9针的串口接头吗?那就是RS232的典型接口。RS232是早期PC的标准配置,传输距离也就15米左右,而且只能点对点通信——说白了,一台设备只能跟另一台设备聊。

我个人习惯,在实验室或者短距离调试时用RS232。但到了工厂现场,我几乎只用RS485。为什么?

  • 传输距离:RS232最多15米,RS485能到1200米。我在一个化工厂的项目里,PLC在控制室,传感器在车间另一头,距离少说500米。用RS232?想都别想。
  • 多设备组网:RS485支持一主多从,最多能挂256个设备。我记得有个污水处理项目,一台上位机要同时采集30多个仪表的数据,用RS485一条总线就搞定了。
  • 抗干扰能力:RS485是差分信号传输,说白了就是两根线拧在一起,外界干扰来了,两根线上受到的干扰差不多,一减就抵消了。我在电机变频器旁边走过RS485线,照样稳定通信。

核心区别总结:RS232适合短距离、一对一;RS485适合长距离、多设备、工业现场。选型时别纠结,看现场条件定。

4.2 串口参数:波特率、数据位、校验位

串口通信就像两个人打电话,得约定好用什么语言、多快语速、怎么确认听没听清。这些约定就是串口参数。

4.2.1 波特率

波特率就是每秒传输的比特数。常见的值有9600、19200、115200等。我刚开始做项目时,总想着越快越好,直接上115200。结果呢?线长了点,干扰大了点,数据全是乱码。

后来我学乖了:短距离、干扰小,用115200;长距离、干扰大,老老实实用9600。PLC和上位机通信,9600是最稳妥的选择,虽然慢点,但稳定啊。

4.2.2 数据位

数据位就是一次传输多少个比特的数据。常见的有7位和8位。现在绝大多数设备都用8位,因为一个字节就是8位,方便。7位主要是老式设备或者传输ASCII字符时用。

我建议:默认选8位,除非你明确知道对方设备要求7位。我在一个老款温控器上吃过亏,它只支持7位数据,我设成8位,折腾了半天才发现问题。

4.2.3 校验位

校验位用来检测数据传输过程中有没有出错。常见的有三种:

  • 无校验(N):不校验,速度快,但容易出错。适合短距离、干扰小的场景。
  • 奇校验(O):保证数据中1的个数为奇数。如果接收端发现1的个数是偶数,就知道出错了。
  • 偶校验(E):保证数据中1的个数为偶数。

我曾经在一个项目里,PLC和变频器通信,数据偶尔会跳变。查了半天,发现是校验位没设对。PLC设的是偶校验,变频器设的是无校验。嗯,这种低级错误,犯过一次就记住了。

我的经验:工业现场首选偶校验(E),8位数据位,1位停止位。这个组合叫8E1,兼容性最好。如果设备不支持校验,就用无校验(N),但要做好数据校验的软件处理。

4.3 Python serial库实战

理论说完了,咱们来点实际的。Python的serial库是操作串口的利器,我几乎每个项目都用它。

4.3.1 安装

pip install pyserial

就这么简单。注意,库名叫pyserial,但导入时用import serial。

4.3.2 打开串口

import serial

# 打开串口
ser = serial.Serial(
    port='COM3',        # Windows下用COM口,Linux下用/dev/ttyUSB0
    baudrate=9600,      # 波特率
    bytesize=8,         # 数据位
    parity='E',         # 校验位:'N'无校验,'E'偶校验,'O'奇校验
    stopbits=1,         # 停止位
    timeout=1           # 超时时间,单位秒
)

# 检查是否打开成功
if ser.is_open:
    print(f"串口 {ser.port} 打开成功")

这里要注意,timeout一定要设。我见过有人不设timeout,结果程序卡死在读取那里,整个上位机都假死了。设个1秒,够用了。

4.3.3 发送和接收数据

# 发送数据
data_to_send = b'\x01\x03\x00\x00\x00\x01'  # Modbus读保持寄存器指令
ser.write(data_to_send)

# 接收数据
response = ser.read(8)  # 读取8个字节
print(f"接收到的数据: {response.hex()}")

# 或者用read_until,读到指定字节为止
response = ser.read_until(b'\x0A')  # 读到换行符为止

我个人习惯用read()指定字节数,因为Modbus协议的数据长度是固定的,这样最可靠。用read_until()适合那些以特定字符结尾的协议,比如一些ASCII协议。

4.3.4 完整示例:读取PLC数据

下面是一个完整的例子,读取PLC的保持寄存器地址0的数据:

import serial
import time

def read_plc_register(ser, slave_id, register_addr):
    """
    读取PLC保持寄存器
    :param ser: 串口对象
    :param slave_id: 从站地址
    :param register_addr: 寄存器地址
    :return: 寄存器值
    """
    # 构建Modbus RTU读保持寄存器指令
    # 功能码03:读保持寄存器
    cmd = bytearray()
    cmd.append(slave_id)        # 从站地址
    cmd.append(0x03)            # 功能码
    cmd.append((register_addr >> 8) & 0xFF)  # 寄存器地址高字节
    cmd.append(register_addr & 0xFF)         # 寄存器地址低字节
    cmd.append(0x00)            # 读取数量高字节
    cmd.append(0x01)            # 读取数量低字节(读1个寄存器)
    
    # 计算CRC校验(这里简化处理,实际项目中要用CRC库)
    # 为了演示,我们假设CRC正确
    cmd.append(0x00)  # CRC低字节
    cmd.append(0x00)  # CRC高字节
    
    # 发送指令
    ser.write(cmd)
    time.sleep(0.1)  # 等待PLC响应
    
    # 读取响应(8个字节:地址+功能码+数据长度+数据+CRC)
    response = ser.read(8)
    
    if len(response) >= 5:
        # 提取数据(第3和第4个字节)
        value = (response[3] << 8) | response[4]
        return value
    else:
        return None

# 主程序
if __name__ == '__main__':
    ser = serial.Serial(
        port='COM3',
        baudrate=9600,
        bytesize=8,
        parity='E',
        stopbits=1,
        timeout=1
    )
    
    if ser.is_open:
        value = read_plc_register(ser, 1, 0)
        if value is not None:
            print(f"寄存器值: {value}")
        else:
            print("读取失败,请检查连接")
        
        ser.close()

避坑指南:我曾经在读取PLC数据时,发现偶尔会读到错误的值。排查了半天,发现是发送指令后没有加延时。PLC处理指令需要时间,你发完指令马上读,它还没准备好呢。加个0.1秒的延时,问题就解决了。

4.4 本章知识体系

下面这张图,把本章的核心内容串起来了。你可以把它当作一个快速参考:

串口通信基础:知识体系 物理层 RS232:点对点,15米 RS485:多设备,1200米 差分信号抗干扰 串口参数 波特率:9600/115200 数据位:7位/8位 校验位:N/O/E 停止位:1位/2位 Python serial库 安装:pip install pyserial 打开:serial.Serial() 发送:write() 接收:read() 核心原则:物理层选型 → 参数匹配 → 代码实现 三者缺一不可,任何一个环节出错,通信都跑不起来 实战建议:先用串口调试助手测试,再用Python代码实现 记住:工业现场,稳定第一,速度第二

4.5 本章小结

串口通信看着简单,但坑不少。我总结几个要点:

  • 物理层选型:短距离用RS232,长距离用RS485。别搞反了。
  • 参数匹配:通信双方的波特率、数据位、校验位、停止位必须完全一致。差一点都不行。
  • 代码实现:Python serial库用起来很简单,但别忘了设timeout,别忘了加延时。
  • 调试技巧:先用串口调试助手(比如SSCOM)手动发指令,确认设备能正常响应,再写代码。这样能快速定位问题是出在硬件还是软件。

好了,这一章就到这里。下一章我们会用这些知识,真正开始和PLC通信。到时候,你会发现自己已经能搞定大部分串口问题了。


公众号:蓝海资料掘金营,微信deep3321