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 避坑指南:串口通信常见问题
做串口编程,有几个坑是新人必踩的。我列出来,你们记一下:
- 端口被占用——有些软件(比如串口调试助手)会占用串口,Python就打不开了。关掉其他软件再试。
- 数据粘包——串口是流式传输,没有明确的帧边界。我习惯用固定长度帧或者加帧头帧尾来分割数据。
- 缓冲区溢出——如果读取速度跟不上发送速度,缓冲区会满。解决办法是加大缓冲区或者用流控。
- 电平不匹配——RS232和RS485的电平不同,不能直接连。要用转换器。我见过有人直接把RS232的线插到RS485设备上,结果烧了接口芯片。
我的建议:刚开始做串口项目,先买一个USB转RS485的转换器(几十块钱),再买一个USB转RS232的备用。调试时先用串口调试助手确认硬件没问题,再写Python代码。这样能省一半的调试时间。
好了,这一章的内容就这些。串口通信看起来简单,但它是整个上位机监控系统的基石。下一章咱们会基于这些知识,真正开始写电机控制的通信协议。到时候你会发现,今天学的这些波特率、数据位、pyserial,全都会用上。