第4章:串口通信实战:RS232/RS485原理、串口参数配置与Python编程

各位工程师朋友,大家好。今天我们来聊聊串口通信。

说实话,在MES系统实施中,串口通信是我打交道最多的协议之一。别看它“老”,但工业现场里,90%以上的老旧设备、称重仪表、条码枪、PLC,都靠它跟MES“说话”。

我刚开始做项目时,总觉得串口简单,不就是两根线嘛。结果第一次在现场调一台德国老秤,死活收不到数据,折腾了一下午。最后发现是波特率配错了——设备默认是9600,我配成了19200。嗯,从那以后,我再也不敢小看串口参数配置了。

核心认知:串口通信是MES数据采集的“最后一公里”。搞不定串口,你的MES就是空中楼阁。

4.1 RS232与RS485:两种“说话”方式

先搞清楚这两个东西。说白了,RS232和RS485是两种不同的电气标准,决定了设备之间怎么“喊话”。

RS232:点对点,短距离

  • 通信方式:一对一。一台电脑只能连一台设备。
  • 传输距离:15米左右。再远信号就衰减得不行了。
  • 信号电平:±12V。电压高,抗干扰能力一般。
  • 典型应用:老式PC串口、近距离仪表、条码枪。

我在一个仓库项目里,遇到过一台RS232的电子秤。电脑就在秤旁边,距离不到2米,用RS232完全没问题。但如果你想把数据传到50米外的服务器机房,那RS232就歇菜了。

RS485:多点,远距离,抗干扰

  • 通信方式:一对多。一条总线上可以挂32个设备(甚至更多)。
  • 传输距离:1200米以上。加中继器还能更远。
  • 信号电平:差分信号(±2V~±6V)。抗干扰能力强。
  • 典型应用:工厂产线、远程数据采集、PLC网络。

你想想看,一条产线上几十台设备,如果都用RS232,电脑得装多少个串口?RS485就是为这种场景设计的。我做过一个汽车零部件的产线项目,一条总线上挂了16台拧紧枪,全部走RS485,一根双绞线搞定。

我的经验:现场布线时,RS485一定要用双绞屏蔽线。我曾经图省事用了普通网线,结果电机一启动,数据全乱码。屏蔽层单端接地,切记。

4.2 串口参数配置:波特率、数据位、停止位、校验位

这是串口通信的“四要素”。两边必须完全一致,否则就是鸡同鸭讲。

参数 说明 常见值 我的建议
波特率 每秒传输的比特数。说白了就是“说话速度”。 9600、19200、38400、115200 工业设备大多用9600或19200。别盲目追求高速,距离越远,速度要越低。
数据位 每个数据包中实际数据的位数。 7、8 现在基本都用8位。7位是历史遗留,除非设备手册明确要求。
停止位 数据包结束的标志位。 1、1.5、2 绝大多数设备用1位停止位。如果通信不稳定,可以试试2位。
校验位 用于检测数据是否在传输中出错。 None、Even、Odd 工业现场建议用Even(偶校验)。None虽然简单,但抗干扰差。

避坑指南:我曾经在调试一台注塑机时,设备手册写的是“9600,8,N,1”,但怎么都连不上。最后用示波器抓波形才发现,设备实际用的是“9600,8,E,1”。手册是错的!所以,永远不要100%相信手册,用串口调试工具先抓包验证。

4.3 Python串口编程实战

好了,理论讲完,咱们来点实际的。Python的pyserial库是串口编程的标配。我所有MES数据采集项目,底层通信都用它。

4.3.1 安装pyserial

pip install pyserial

4.3.2 基础代码:打开串口并读取数据

import serial
import serial.tools.list_ports

# 第一步:列出所有可用串口
ports = serial.tools.list_ports.comports()
for port in ports:
    print(f"发现串口: {port.device} - {port.description}")

# 第二步:配置并打开串口
ser = serial.Serial(
    port='COM3',        # Windows下用COM口,Linux下用/dev/ttyUSB0
    baudrate=9600,      # 波特率
    bytesize=8,         # 数据位
    parity='N',         # 校验位:N=None, E=Even, O=Odd
    stopbits=1,         # 停止位
    timeout=1           # 超时时间(秒)
)

# 第三步:读取数据
if ser.is_open:
    print(f"串口 {ser.port} 已打开")
    data = ser.read(100)  # 读取100个字节
    print(f"收到原始数据: {data}")
    # 通常需要解码
    text = data.decode('ascii', errors='ignore')
    print(f"解码后: {text}")

# 第四步:关闭串口
ser.close()

我的习惯:生产环境中,我从来不用ser.read(100)这种固定长度读取。因为你不知道设备什么时候发完数据。我一般用read_until(),指定结束符(比如换行符\n),或者用in_waiting属性判断缓冲区有多少数据。

4.3.3 实战:读取电子秤数据

这是我在MES项目中最常见的场景。电子秤一般会持续发送重量数据,格式类似:ST,+000.500kg\r\n

import serial
import time

def read_scale(port='COM3', baud=9600):
    try:
        ser = serial.Serial(port, baud, timeout=2)
        print(f"已连接电子秤: {port}")
        
        while True:
            # 读取一行数据(直到换行符)
            line = ser.readline()
            if line:
                # 解码并去除首尾空白
                data = line.decode('ascii', errors='ignore').strip()
                print(f"秤数据: {data}")
                
                # 解析重量值(假设格式:ST,+000.500kg)
                if data.startswith('ST,'):
                    try:
                        weight_str = data.split(',')[1].replace('kg', '')
                        weight = float(weight_str)
                        print(f"解析重量: {weight} kg")
                        # 这里可以写入MES数据库
                    except:
                        print("数据解析失败")
            
            time.sleep(0.1)  # 避免CPU占用过高
            
    except serial.SerialException as e:
        print(f"串口错误: {e}")
    finally:
        if 'ser' in locals() and ser.is_open:
            ser.close()

if __name__ == '__main__':
    read_scale()

4.3.4 实战:向PLC发送指令

有时候MES需要主动控制设备,比如让PLC启动一个工位。Modbus RTU是工业上最常用的协议,但底层还是串口。

import serial
import struct

def send_modbus_command(ser, slave_id, function_code, start_addr, quantity):
    """
    发送Modbus RTU读取指令
    """
    # 构建报文
    message = struct.pack('>B', slave_id)  # 从站地址
    message += struct.pack('>B', function_code)  # 功能码
    message += struct.pack('>H', start_addr)  # 起始地址
    message += struct.pack('>H', quantity)  # 寄存器数量
    
    # 计算CRC校验
    crc = calculate_crc(message)
    message += struct.pack('<H', crc)
    
    # 发送
    ser.write(message)
    print(f"发送: {message.hex()}")
    
    # 读取响应
    response = ser.read(8)  # 根据协议长度调整
    print(f"接收: {response.hex()}")
    return response

def calculate_crc(data):
    """Modbus CRC16计算"""
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc = crc >> 1
    return crc

# 使用示例
ser = serial.Serial('COM3', 9600, timeout=1)
response = send_modbus_command(ser, slave_id=1, function_code=3, 
                                start_addr=0, quantity=2)
ser.close()

注意:Modbus RTU的CRC校验是必须的。我曾经见过一个新手工程师,自己写协议忘了加CRC,结果设备偶尔响应、偶尔不响应,排查了整整两天。CRC计算代码网上很多,但建议你自己手写一遍,理解原理。

4.4 本章知识体系

下面这张图,是我对串口通信知识体系的总结。你可以把它当作一个“地图”,遇到问题就知道该查哪一块。

串口通信知识体系 串口通信 物理层标准 RS232:点对点,15m RS485:多点,1200m 参数配置(四要素) 波特率:9600/19200 数据位:7/8 停止位:1/2 校验位:N/E/O Python编程实战 pyserial库安装 打开/关闭串口 读取/写入数据 电子秤数据采集 Modbus RTU通信 核心原则:物理层匹配 + 参数一致 + 协议正确 先抓包验证,再写代码

4.5 常见问题与排查思路

最后,分享几个我踩过的坑,希望能帮你少走弯路。

  1. 串口打不开:检查端口号是否正确,有没有被其他程序占用。Windows下可以用“设备管理器”查看。
  2. 收到乱码:90%是波特率不匹配。用串口调试工具(比如SSCOM)先确认设备实际参数。
  3. 数据时有时无:检查接线,特别是RS485的A/B线有没有接反。另外,终端电阻(120Ω)在长距离通信时一定要加。
  4. 程序卡死:设置合理的超时时间(timeout)。我一般设1-3秒,别设0(非阻塞)也别设None(一直等)。

我的工具箱:现场调试串口,我必带三样东西:USB转RS232/485转换器、串口调试助手(SSCOM或Putty)、示波器(抓波形用)。没有示波器的话,逻辑分析仪也行,几十块钱的就能用。

好了,串口通信就讲到这里。记住一句话:串口不难,但细节决定成败。参数配对了,线接对了,数据自然就来了。下一章,我们会聊更高级的工业以太网协议,但串口这个基本功,值得你花时间彻底搞透。


专注资料整理