4、Python串口通信基础:pyserial库安装与配置、串口参数设置(波特率/数据位/停止位)、读写超时处理
好,咱们进入实战环节的第一个硬核知识点——串口通信。
说实话,做物联网设备采集,串口是绕不开的坎。不管是RS232还是RS485,底层通信协议基本都靠串口。我当年第一次调试温度巡检仪,就是被串口参数折腾了一整天。波特率不对,数据全是乱码;停止位错了,设备直接不理你。嗯,今天咱们就把这些坑填平。
4.1 pyserial库:你的串口瑞士军刀
Python操作串口,最主流的库就是pyserial。它跨平台,Windows、Linux、macOS都能用。我个人习惯用pip安装,简单粗暴:
pip install pyserial
如果你在公司内网环境,或者像我之前在某工厂调试时没网,可以离线安装。先去官网下载whl包,然后:
pip install pyserial-3.5-py2.py3-none-any.whl
验证是否装好,打开Python交互环境,输入:
import serial
print(serial.__version__)
不报错,就说明成了。我建议你养成这个习惯——装完库先验证,别等到写代码才发现没装上。
4.2 串口参数设置:四个关键参数
串口通信就像两个人打电话。你得先约定好:语速多快(波特率)、一句话几个字(数据位)、怎么表示结束(停止位)、要不要校验(校验位)。
咱们直接看代码,这是我最常用的初始化模板:
import serial
ser = serial.Serial(
port='COM3', # Windows下是COM口,Linux下是/dev/ttyUSB0
baudrate=9600, # 波特率,常见的有9600、19200、115200
bytesize=8, # 数据位,通常是8位
parity='N', # 校验位:N无校验,E偶校验,O奇校验
stopbits=1, # 停止位:1位或2位
timeout=1 # 读超时,单位秒
)
这里我重点说几个容易翻车的地方:
4.3 波特率:通信的“语速”
波特率必须和设备完全一致。差一点都不行。我曾经调试一台老款巡检仪,手册上写9600,结果怎么读都是乱码。折腾两小时才发现,那台设备实际用的是19200。你想想看,手册也会骗人。
常见波特率对照表:
| 波特率 | 适用场景 | 传输距离 |
|---|---|---|
| 9600 | 工业仪表、老旧设备 | 较远(RS485可达1200米) |
| 19200 | 中速采集场景 | 中等 |
| 115200 | 高速数据、调试口 | 较短(建议15米内) |
我的建议是:能用9600就别用115200。虽然慢,但稳定。工业现场干扰多,高速率反而容易丢包。
4.4 数据位与停止位:别小看这两个参数
数据位通常是8位,因为一个字节就是8位。但有些老设备用7位,比如某些Modbus ASCII模式。停止位一般是1位,偶尔有设备用2位。
怎么判断?看设备手册。如果手册丢了,有个笨办法——挨个试。我当年在项目现场就这么干过,写个脚本循环尝试所有参数组合,哪个能读到正常数据就用哪个。
4.5 读写超时处理:别让程序卡死
这是新手最容易忽略的地方。串口通信不像网络请求,没有自动超时机制。如果你不设置timeout,程序可能永远卡在read()那里。
看这个对比:
# 错误示范:没有超时
ser.read(10) # 如果设备没发数据,程序永远卡在这里
# 正确做法:设置超时
ser.timeout = 2 # 2秒超时
data = ser.read(10) # 最多等2秒,没数据就返回空
我一般这样处理读写:
import serial
import time
ser = serial.Serial('COM3', 9600, timeout=1)
def read_with_retry(expected_len=10, max_retries=3):
"""带重试的读取函数"""
for i in range(max_retries):
data = ser.read(expected_len)
if len(data) == expected_len:
return data
print(f"第{i+1}次读取失败,重试中...")
time.sleep(0.5)
return None # 全部失败返回None
# 使用示例
result = read_with_retry(10)
if result:
print(f"收到数据: {result.hex()}")
else:
print("读取超时,请检查设备连接")
为什么要重试?因为工业现场干扰多,偶尔丢一帧很正常。我遇到过最极端的情况,某台设备每10次通信就有1次超时。不加重试机制,系统根本跑不稳。
4.6 避坑指南:我踩过的三个坑
坑一:串口被占用
我曾经写了个脚本,打开串口后没关闭就异常退出了。下次再运行,提示“端口被占用”。解决方案:用with语句自动管理资源。
with serial.Serial('COM3', 9600, timeout=1) as ser:
ser.write(b'hello')
data = ser.read(5)
# 退出with块后自动关闭串口
坑二:写数据后立刻读
有些设备收到指令后需要时间处理。你写完立刻读,可能什么都读不到。加个延时:
ser.write(b'\x01\x03\x00\x00\x00\x01')
time.sleep(0.1) # 给设备100ms处理时间
data = ser.read(10)
坑三:Windows和Linux端口名不同
我建议写个自动检测函数:
import sys
import serial.tools.list_ports
def find_serial_port():
ports = serial.tools.list_ports.comports()
for port in ports:
if 'USB' in port.description or 'COM' in port.device:
return port.device
return None
port = find_serial_port()
if port:
print(f"找到串口: {port}")
else:
print("未找到串口设备")
4.7 完整示例:一个能跑的温度巡检仪采集脚本
最后,给你一个可以直接用的模板。这是我做项目时的标准写法:
import serial
import time
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class TemperatureCollector:
def __init__(self, port='COM3', baud=9600):
self.ser = None
self.port = port
self.baud = baud
def connect(self):
try:
self.ser = serial.Serial(
port=self.port,
baudrate=self.baud,
bytesize=8,
parity='N',
stopbits=1,
timeout=2
)
logging.info(f"串口 {self.port} 连接成功")
return True
except Exception as e:
logging.error(f"连接失败: {e}")
return False
def read_temperature(self):
"""读取温度值,返回摄氏度"""
if not self.ser or not self.ser.is_open:
logging.warning("串口未连接")
return None
# 发送读取指令(具体指令根据设备手册)
cmd = bytes.fromhex('01 03 00 00 00 01 84 0A')
self.ser.write(cmd)
time.sleep(0.2)
# 读取响应
response = self.ser.read(7)
if len(response) == 7:
# 解析温度值(示例:第3-4字节为温度值)
temp_raw = (response[3] << 8) | response[4]
temperature = temp_raw / 10.0 # 假设精度为0.1℃
return temperature
return None
def close(self):
if self.ser and self.ser.is_open:
self.ser.close()
logging.info("串口已关闭")
# 使用示例
if __name__ == '__main__':
collector = TemperatureCollector('COM3', 9600)
if collector.connect():
for i in range(5):
temp = collector.read_temperature()
if temp is not None:
print(f"温度: {temp:.1f}℃")
else:
print("读取失败")
time.sleep(1)
collector.close()
这个脚本我用了好几年,换过几十种设备,核心逻辑基本没变。你把它当成骨架,往里面填具体的协议指令就行。
嗯,串口基础就讲到这里。下一节咱们聊怎么用这个基础去对接真实的温度巡检仪协议。到时候你会发现,今天学的这些参数设置和超时处理,全都能用上。