Python pyserial库安装与基础串口读写教程
在嵌入式开发、工业设备调试以及单片机(如 STM32、Arduino、ESP32)交互项目中,计算机通过串口(UART/RS-232/RS-485)与外部硬件交换数据是极其普遍的需求。在 Python 生态中,pyserial 是用于串口访问的事实标准库。本文将围绕该库展开,完整讲解其安装要点、端口发现、配置连接以及底层二进制数据的读写处理流程,帮助读者建立稳定、规范的串口读写逻辑。
一、pyserial 库的正确安装与环境确认
在 Python 中使用串口前,第一步是安装驱动库。这里存在一个初学者极其容易踩中的安装陷阱:在 PyPI 仓库中,存在名为 serial 与 pyserial 的两个不同库。如果直接执行 pip install serial,安装的可能是一个长期未维护或功能不相关的包,导致代码中引入模块时发生属性缺失或运行时异常。
pyserial,而在 Python 脚本中导入时模块名为 import serial。这一差异是由该库的历史命名规范决定的。
# 正确的安装命令(必须指定 pyserial)
pip install pyserial
# 如果在多 Python 环境下,建议使用模块方式调用 pip
python -m pip install pyserial
安装完成后,建议在终端中快速验证该库的版本信息,以确认环境就绪:
python -c "import serial; print(serial.__version__)"
# 预期输出类似:3.5
二、查找可用串口与设备识别
在编写串口读写逻辑前,必须明确当前主机连接的串口物理名称(Port)。不同操作系统的串口命名体系有所差异:
- Windows 系统:通常命名为
COM1、COM3、COM8等。 - Linux 系统:USB 转串口设备一般显示为
/dev/ttyUSB0、/dev/ttyUSB1或/dev/ttyACM0。 - macOS 系统:通常显示为
/dev/cu.usbserial-xxx或/dev/cu.usbmodemxxx。
pyserial 提供了方便的工具模块 serial.tools.list_ports,可以在不依赖外部设备管理器的情况下,直接用 Python 枚举当前系统中所有在线的串口设备:
import serial.tools.list_ports
def scan_ports():
ports = serial.tools.list_ports.comports()
if not ports:
print("未检测到任何可用串口,请检查硬件连接或驱动。")
return []
print(f"检测到 {len(ports)} 个可用串口:")
for port in ports:
print(f"端口: {port.device}")
print(f" 描述: {port.description}")
print(f" 硬件ID: {port.hwid}")
return ports
if __name__ == "__main__":
scan_ports()
通过该脚本,可以准确获取目标硬件的端口字符串,避免硬编码端口导致程序在更换 USB 插槽或切换设备时发生 SerialException 无法连接。
三、串口连接基础配置与参数解析
串口通信是异步串行通信,通信双方必须约定完全相同的电气与协议参数,才能保证数据位被准确还原。核心参数包括端口号、波特率、数据位、校验位、停止位以及超时时间。
| 参数名 | 参数含义 | 常用推荐值 | 作用说明 |
|---|---|---|---|
port |
串口名称 | "COM3" 或 "/dev/ttyUSB0" |
指定要打开的物理或虚拟串口路径。 |
baudrate |
波特率(bps) | 9600, 115200 |
每秒传输的符号数,通信双方必须保持严格一致。 |
bytesize |
数据位 | serial.EIGHTBITS (8) |
每个数据帧的数据字节长度,默认通常为 8 位。 |
parity |
校验位 | serial.PARITY_NONE ('N') |
用于检错,常用无校验 (N)、奇校验 (O) 或偶校验 (E)。 |
stopbits |
停止位 | serial.STOPBITS_ONE (1) |
标识数据帧传输结束,常用 1 位或 2 位。 |
timeout |
读超时(秒) | 1.0(根据场景调整) |
防止读取数据时线程永久阻塞,非常关键的保护参数。 |
四、基础串口数据读写操作实战
1. 安全开启与上下文管理(with 语句)
串口属于系统独占型硬件资源。一旦某个进程打开了端口,其它程序将无法访问。如果在程序崩溃或异常退出时未执行 close(),端口可能会被系统占用锁死,导致后续必须拔插 USB 甚至重启电脑才能释放。因此,推荐使用 Python 的 with 上下文管理器来操作串口,确保退出作用域时自动释放资源:
import serial
import time
port_name = "COM3" # Windows示例,Linux可替换为 "/dev/ttyUSB0"
baud_rate = 115200
# 使用上下文管理器,离开代码块时自动执行 ser.close()
with serial.Serial(port=port_name, baudrate=baud_rate, timeout=1.0) as ser:
if ser.is_open:
print(f"成功连接至串口: {ser.name}")
# 部分单片机(如 Arduino)在打开串口时会触发自动复位,建议预留 1~2 秒就绪延时
time.sleep(1.5)
2. 发送数据:必须显式使用 bytes 类型
在 Python 3 中,字符串(str)是 Unicode 格式,而串口物理线路上传输的是纯粹的二进制字节流(bytes)。因此,调用 ser.write() 时必须传入字节类型,不能直接传入字符串:
# 方式一:直接传递字节字面量(仅限 ASCII 字符)
ser.write(b"HELLO_MCU\n")
# 方式二:字符串显式编码(通用推荐,支持中文及复杂协议字符)
msg = "设置温度:25.5\n"
ser.write(msg.encode('utf-8'))
# 立即刷新硬件/系统缓冲区,确保数据即时发出
ser.flush()
ser.flush() 的作用是等待所有输出缓冲区的数据全部写出到物理层,避免数据滞留在底层缓冲区未及时发送。
3. 读取数据:三种常用模式与超时控制
根据通信协议的不同,pyserial 提供了不同的读取方法:
- 按行读取 (
readline()):最适用于以换行符(\n或\r\n)结尾的 ASCII 文本协议。读取直到遇到换行符或timeout超时到达。 - 指定字节数读取 (
read(size)):最适用于固定长度帧的二进制协议(例如 Modbus RTU)。读取指定数量的字节,若超时前未收齐则返回已读到的部分。 - 等待全部缓冲区 (
read_all()):一次性读取当前操作系统接收缓冲区中已有的所有字节,不阻塞等待。
# 示例:以文本行方式读取响应
if ser.in_waiting > 0: # 检查当前缓冲区是否有待接收字节
raw_data = ser.readline()
if raw_data:
try:
# 将接收到的 bytes 解码为可读字符串,并剔除首尾空白符
text = raw_data.decode('utf-8').strip()
print(f"收到设备响应: {text}")
except UnicodeDecodeError:
print(f"无法以 UTF-8 解码,原始十六进制: {raw_data.hex()}")
五、常见问题排查与关键避坑指南
1. SerialException: could not open port(端口无法打开/拒绝访问)
该错误通常由两个原因引起:
- 端口被占用:串口调试助手、其他运行中的脚本或终端仍在占用该 COM 口。请关闭其他调试软件后再运行脚本。
- Linux/macOS 权限问题:在 Linux 下,串口设备默认归属于
dialout组。普通用户若无权限读写/dev/ttyUSB0,可通过以下命令将当前用户加入串口组后注销重新登录:sudo usermod -aG dialout $USER
2. 读出乱码或乱七八糟的十六进制字节
绝大多数乱码问题都源于波特率不匹配。例如下位机配置为 9600 bps,而 Python 脚本中配置了 115200 bps,采样时钟偏差会导致数据位识别错误。遇到乱码时,首先核对双方的波特率、数据位和停止位设置。
3. 打开串口后单片机反复复位
许多开发板(如 Arduino Uno、Nano、NodeMCU)将 USB 芯片的 DTR 或 RTS 引脚连接到了单片机的复位(RESET)引脚,以便实现程序下载时的自动复位。在 pyserial 打开串口时,默认可能会拉低 DTR 引脚引发复位。如果无需自动复位,可以在初始化时明确禁用该信号:
ser = serial.Serial()
ser.port = "COM3"
ser.baudrate = 115200
ser.dtr = False # 禁用 DTR 自动拉低
ser.rts = False # 禁用 RTS 自动拉低
ser.open()
六、总结与完整通信代码范式
掌握 pyserial 的核心在于理解异步串行通信的物理特性:严格匹配波特率、将字符串转换为字节、设置读超时以防死锁,并在退出时可靠释放端口资源。以下提供一份可直接运行的完整通信模板,集成了端口发现、超时防护、异常处理与数据读写功能:
import serial
import serial.tools.list_ports
import time
def run_serial_demo(target_port="COM3", baud=115200):
# 1. 检查端口是否存在
available = [p.device for p in serial.tools.list_ports.comports()]
if target_port not in available:
print(f"错误: 端口 {target_port} 未找到。当前可用端口: {available}")
return
# 2. 安全建立连接
try:
with serial.Serial(port=target_port, baudrate=baud, timeout=1.0) as ser:
print(f"成功打开串口: {ser.name}")
# 延时等待设备启动稳定(针对部分单片机)
time.sleep(1.0)
# 清除旧的未读取缓存
ser.reset_input_buffer()
# 3. 发送数据 (注意必须编码为 bytes)
send_text = "PING\n"
ser.write(send_text.encode('utf-8'))
ser.flush()
print(f"发送成功: {send_text.strip()}")
# 4. 阻塞等待或读取响应(受 timeout 保护)
response = ser.readline()
if response:
try:
decoded = response.decode('utf-8').strip()
print(f"接收成功: {decoded}")
except UnicodeDecodeError:
print(f"接收到十六进制原始数据: {response.hex()}")
else:
print("在设定的超时时间内未收到设备响应。")
except serial.SerialException as e:
print(f"串口通信发生异常: {e}")
if __name__ == "__main__":
# 执行示例
run_serial_demo()
通过遵循上述规范,开发者可以在各类物联网、工控采集及单片机控制项目中构建出稳定、容错性高的串口通信交互逻辑。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













