3、串口通信基础:RS232/RS485协议、波特率与数据位、Python串口编程入门

各位同学,咱们今天聊聊串口通信。说实话,做电机控制上位机,串口是绕不开的一关。我刚开始接触这行的时候,也觉得串口这东西太古老了,USB都普及这么多年了,谁还用串口啊?

后来真做了项目才发现——工业现场,串口才是王道。稳定、简单、抗干扰,很多老设备就认这个。你想想看,一个变频器、一个PLC,十有八九都留着RS485接口。所以,这一章咱们把串口通信的基础打牢。

3.1 RS232 vs RS485:到底选哪个?

先说说这两个协议的区别。说白了,RS232和RS485都是串口通信的电气标准,但它们的设计思路完全不同。

特性 RS232 RS485
传输方式 单端(不平衡) 差分(平衡)
最大距离 约15米 约1200米
最大节点数 1对1 最多32个(标准)
信号电平 ±12V ±5V(差分)
抗干扰能力 较弱
典型应用 调试口、短距离通信 工业总线、多设备组网

我个人习惯是:调试阶段用RS232,简单直接,一根线连电脑就行。但正式做电机控制项目,我几乎都用RS485。为什么?

我在项目中遇到过这样的情况——一个车间里十几台电机驱动器,如果用RS232,你得拉十几根线,电脑还得有十几个串口。用RS485就简单了,两根线一挂,所有设备都在一条总线上,地址一分配,搞定。

核心区别一句话:RS232是点对点,RS485是总线型。做多电机控制,首选RS485。

3.2 波特率与数据位:别小看这几个参数

串口通信的参数,说白了就四个:波特率、数据位、停止位、校验位。听起来简单,但坑不少。

波特率

波特率就是每秒传输的符号数。常见的值有9600、19200、38400、115200等。我一般用115200,速度快,而且大多数设备都支持。

但要注意——发送端和接收端的波特率必须一致,差一点都不行。我曾经调试一个电机驱动器,数据总是乱码,查了半天,结果是电脑设了115200,驱动器默认是9600。嗯,这种低级错误,犯过一次就记住了。

数据位

数据位通常有7位或8位。现在绝大多数场景都用8位。7位主要是老式设备或者传输ASCII字符时用的,咱们做电机控制,直接选8位就行。

停止位与校验位

停止位一般是1位或2位。校验位有奇校验、偶校验、无校验三种。我个人习惯:无校验,1位停止位。为什么?简单可靠。工业现场环境复杂,校验位反而容易出问题,不如靠协议层的校验(比如CRC)来保证数据正确性。

我的经验:调试时先用115200、8N1(8数据位、无校验、1停止位)这个组合。如果不行,再降波特率试试。90%的问题都能解决。

3.3 Python串口编程入门

好了,理论说完了,咱们上手写代码。Python做串口通信,核心库就是pyserial。安装很简单:

pip install pyserial

装好之后,咱们写一个最基础的串口收发程序。我习惯先写一个测试脚本,确认硬件没问题再往下做。

基础示例:打开串口并发送数据

import serial
import time

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

# 检查串口是否打开
if ser.is_open:
    print(f"串口 {ser.port} 已打开")
    
    # 发送数据(需要转成bytes)
    data_to_send = b'\x01\x03\x00\x00\x00\x01\x84\x0A'
    ser.write(data_to_send)
    print(f"发送: {data_to_send.hex()}")
    
    # 读取响应
    response = ser.read(8)  # 读取8个字节
    print(f"接收: {response.hex()}")
    
    # 关闭串口
    ser.close()
else:
    print("串口打开失败")

这段代码看着简单,但有几个细节要注意:

  • 端口号要写对——Windows去设备管理器里看,Linux用ls /dev/tty*
  • 发送的数据必须是bytes类型——字符串要加b''或者用.encode()
  • timeout一定要设——不设的话,程序可能卡死在read()那里

警告:千万不要在没确认串口参数的情况下直接发数据。我曾经有一次把波特率设错了,结果发给驱动器的指令全乱码,驱动器直接报错停机。生产线停了半小时,被领导骂了一顿。

进阶:持续读取与异常处理

实际项目中,我们往往需要持续读取串口数据。比如监控电机的实时转速,就得不停地收数据。这时候要用循环加异常处理:

import serial
import serial.tools.list_ports

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

# 选择第一个可用串口
if ports:
    ser = serial.Serial(
        port=ports[0].device,
        baudrate=115200,
        timeout=0.1
    )
    
    try:
        while True:
            # 读取所有可用数据
            data = ser.read_all()
            if data:
                print(f"收到: {data.hex()}")
                # 这里可以加解析逻辑
                
    except KeyboardInterrupt:
        print("用户中断")
    except serial.SerialException as e:
        print(f"串口异常: {e}")
    finally:
        ser.close()
        print("串口已关闭")
else:
    print("没有找到可用串口")

这个脚本里我加了几个实用功能:自动扫描串口、异常处理、优雅退出。你想想看,如果程序跑着跑着突然报错退出,电机那边还在转,多危险。所以异常处理一定要做。

实用技巧:用with语句管理串口

Python的with语句可以自动管理资源,串口也不例外:

import serial

with serial.Serial('COM3', 115200, timeout=1) as ser:
    ser.write(b'hello')
    response = ser.read(5)
    print(response)
# 退出with块后,串口自动关闭

这样写更安全,不用担心忘记关串口。我现在的项目基本都用这种写法。

3.4 避坑指南:串口通信常见问题

做串口编程,有几个坑是新人必踩的。我列出来,你们记一下:

  1. 端口被占用——有些软件(比如串口调试助手)会占用串口,Python就打不开了。关掉其他软件再试。
  2. 数据粘包——串口是流式传输,没有明确的帧边界。我习惯用固定长度帧或者加帧头帧尾来分割数据。
  3. 缓冲区溢出——如果读取速度跟不上发送速度,缓冲区会满。解决办法是加大缓冲区或者用流控。
  4. 电平不匹配——RS232和RS485的电平不同,不能直接连。要用转换器。我见过有人直接把RS232的线插到RS485设备上,结果烧了接口芯片。

我的建议:刚开始做串口项目,先买一个USB转RS485的转换器(几十块钱),再买一个USB转RS232的备用。调试时先用串口调试助手确认硬件没问题,再写Python代码。这样能省一半的调试时间。

好了,这一章的内容就这些。串口通信看起来简单,但它是整个上位机监控系统的基石。下一章咱们会基于这些知识,真正开始写电机控制的通信协议。到时候你会发现,今天学的这些波特率、数据位、pyserial,全都会用上。