Python串口通信实战:pyserial库从入门到精通

张开发
2026/4/11 1:55:16 15 分钟阅读

分享文章

Python串口通信实战:pyserial库从入门到精通
1. 为什么需要串口通信当你第一次接触硬件开发时可能会被各种通信协议搞得晕头转向。I2C、SPI、USB、蓝牙...为什么我们还要用看起来古老的串口我在2014年做第一个智能家居项目时就犯过这个错误试图用蓝牙连接Arduino结果调试了两周都没稳定传输数据。后来改用串口半小时就搞定了。串口通信最大的优势在于简单可靠。它只需要三根线TX、RX、GND就能建立双向通信抗干扰能力强协议开销小。我经手过的工业设备中90%的调试接口都是串口。比如通过USB转串口调试树莓派读取温湿度传感器的数据控制3D打印机执行G代码与老式PLC设备交互pyserial之所以成为Python硬件开发的首选库是因为它完美解决了三个痛点跨平台一致性同一套代码可以在Windows(COMX)、Linux(/dev/ttyUSB0)和MacOS(/dev/cu.usbmodem*)上运行硬件抽象层不需要关心底层操作系统如何操作串口同步/异步灵活既支持简单的阻塞读写也能配合select实现异步监听2. 环境搭建与基础配置2.1 安装避坑指南虽然pip install pyserial看起来简单但新手常会遇到两个坑在树莓派等Linux设备上需要先安装依赖sudo apt-get install python3-dev可能和已安装的serial库冲突这是完全不同的库建议先卸载pip uninstall serial实测在Python 3.8环境中最稳定的版本是pyserial 3.5可以用指定版本安装pip install pyserial3.5 --user2.2 串口设备识别技巧Windows用户打开设备管理器找COM口时经常会疑惑为什么COM号经常变。我的经验是右键设备→属性→详细信息→选择硬件ID记下VID和PID如VID_2341PID_0043下次插入时就能通过硬件ID确认设备Linux下更推荐用dmesg | grep tty查看新接入设备或者直接列出所有串口import serial.tools.list_ports ports serial.tools.list_ports.comports() for port in ports: print(port.device, port.description)3. 核心API实战详解3.1 串口对象创建参数详解serial.Serial()的参数远不止波特率这么简单这里分享我的常用配置模板ser serial.Serial( port/dev/ttyUSB0, # 设备路径 baudrate115200, # 波特率 bytesize8, # 数据位 parityN, # 校验位(N/O/E/M/S) stopbits1, # 停止位 timeout0.5, # 读超时(秒) write_timeout0.5, # 写超时(秒) xonxoffFalse, # 软件流控 rtsctsFalse, # 硬件RTS/CTS流控 dsrdtrFalse, # 硬件DSR/DTR流控 inter_byte_timeoutNone # 字节间超时 )重点参数实测建议波特率常见9600/115200但有些设备需要特殊速率如28800timeout设置为None会阻塞读取0是非阻塞建议0.1-1秒流控长距离传输超过5米建议开启硬件流控3.2 数据读写的高级技巧新手最容易犯的错误是直接发送字符串ser.write(Hello) # 错误必须转字节正确的做法至少有三种# 方法1b前缀 ser.write(bHello) # 方法2encode转换 ser.write(温度查询.encode(utf-8)) # 方法3struct打包 import struct data struct.pack(Bff, 0xA5, 23.5, 68.9) # 协议头两个浮点数 ser.write(data)读取数据时read()的坑更多。我总结的最佳实践是先检查缓冲区字节数if ser.in_waiting 预期长度: data ser.read(预期长度)对于不定长数据可以用read_until()data ser.read_until(b\n) # 读到换行符重要数据建议校验def checksum(data): return sum(data) 0xFF received ser.read(10) if checksum(received[:-1]) received[-1]: print(数据有效)4. 实战项目智能温室监控系统4.1 硬件连接方案我们用一个真实案例来说明pyserial的综合应用。项目需要Arduino采集温湿度DHT22光照传感器BH1750继电器控制补光灯Python端实现数据可视化接线示意图Arduino Uno ↔ USB转串口 ↔ PC ├─ DHT22 ├─ BH1750 └─ 继电器模块4.2 协议设计要点经过多个项目迭代我总结出高效的串口协议设计原则固定帧头如0xAA 0x55识别有效数据长度字段避免依赖超时判断结束类型标识区分传感器数据/控制命令校验机制简单的累加和或CRC8示例协议帧[头1][头2][长度][类型][数据...][校验] 0xAA 0x55 8 0x01 ... SUM对应的Python解析代码def parse_frame(data): if len(data) 5 or data[0] ! 0xAA or data[1] ! 0x55: return None length data[2] if len(data) 3 length: return None frame_type data[3] payload data[4:4length-2] checksum data[-1] if sum(data[2:-1]) 0xFF ! checksum: return None return {type: frame_type, data: payload}4.3 完整代码实现Arduino端代码精简版void setup() { Serial.begin(115200); // 传感器初始化... } void loop() { float temp readDHT22(); float lux readBH1750(); uint8_t buf[10]; buf[0] 0xAA; // 帧头 buf[1] 0x55; buf[2] 8; // 长度 buf[3] 0x01; // 数据类型 memcpy(buf4, temp, 4); memcpy(buf8, lux, 4); buf[9] 0; // 校验和 for(int i2; i9; i) buf[9] buf[i]; Serial.write(buf, 10); delay(1000); }Python端数据处理import serial import struct from matplotlib import pyplot as plt ser serial.Serial(COM3, 115200) temps, luxs [], [] try: while True: if ser.in_waiting 10: data ser.read(10) if data[0] 0xAA and data[1] 0x55: if sum(data[2:9]) 0xFF data[9]: temp struct.unpack(f, data[4:8])[0] lux struct.unpack(f, data[8:12])[0] temps.append(temp) luxs.append(lux) plt.clf() plt.subplot(211) plt.plot(temps, r-) plt.subplot(212) plt.plot(luxs, b-) plt.pause(0.01) finally: ser.close()5. 高级调试与性能优化5.1 常见错误排查表现象可能原因解决方案无法打开串口端口被占用关闭其他串口工具乱码波特率不匹配检查双方波特率数据截断流控未配置启用RTS/CTS间歇性断开供电不足使用带电源USB Hub写入失败线缆问题换质量好的USB线5.2 性能优化技巧在大数据量传输时如图像传输我总结的优化方法增大缓冲区Linux特有ser serial.Serial(/dev/ttyUSB0, 115200, write_buffer_size1024*1024)批量写入# 不好 for byte in data: ser.write(byte) # 推荐 ser.write(b.join([packet1, packet2]))零拷贝读取# 替代read()copy view ser.read(ser.in_waiting).tobytes()多线程处理from threading import Thread def read_thread(): while running: data ser.read_until(b\n) queue.put(data) Thread(targetread_thread, daemonTrue).start()6. 扩展应用无线串口与网关开发现代IoT项目中经常需要通过4G/WiFi转串口。我用pyserial开发过多个网关程序核心架构是[设备] ↔ (串口) ↔ [网关] ↔ (Socket) ↔ [云服务器]关键实现代码片段import socket import serial ser serial.Serial(/dev/ttyAMA0, 115200) sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((iot.server.com, 6000)) while True: # 串口→网络 if ser.in_waiting: data ser.read(ser.in_waiting) sock.sendall(data) # 网络→串口 data sock.recv(1024) if data: ser.write(data)这种架构下需要注意添加消息队列避免阻塞实现断线重连机制使用心跳包保持长连接

更多文章