3、Python串口库入门:pySerial安装、serial.Serial参数详解、打开/关闭串口、读写基础方法
串口通信是硬件控制领域最基础、最常用的通信方式之一。无论是嵌入式设备调试、传感器数据采集,还是工业自动化控制,UART(通用异步收发器)都扮演着不可替代的角色。Python 之所以能在硬件控制领域快速普及,很大程度上得益于 pySerial 库的简洁与强大。本章将带领你从零开始掌握 pySerial 的安装、核心参数配置、串口生命周期管理以及基础读写方法,为后续实现自动化控制打下坚实基础。
上图展示了 pySerial 在 Python 应用与硬件设备之间的桥梁作用。左侧是 Python 应用层,通过 serial.Serial 对象与底层驱动交互;中间是 pySerial 的核心参数配置区,这些参数决定了通信的物理层协议;右侧是硬件设备端,通常通过 USB 转串口适配器或板载 UART 接口连接。底部则展示了数据发送与接收的双向链路,以及串口的完整生命周期。理解这张图,你就掌握了串口通信的整体框架。
3.1 pySerial 安装与环境准备
3.1.1 安装方法
pySerial 是 Python 生态中最成熟的串口通信库,支持 Windows、Linux、macOS 全平台。安装过程非常简单,推荐使用 pip 进行安装:
# 使用 pip 安装(Python 3.x)
pip install pyserial
# 如果使用 Anaconda 环境
conda install pyserial
# 验证安装是否成功
python -c "import serial; print(serial.__version__)"
安装完成后,可以通过上述命令验证版本号。如果输出类似 3.5 的版本号,说明安装成功。需要注意的是,pySerial 的导入名是 serial,而不是 pyserial,这是一个常见的混淆点。
3.1.2 串口设备识别
在开始编程之前,首先要确认目标串口设备的端口名称。不同操作系统下的命名规则差异较大:
| 操作系统 | 端口命名规则 | 常见示例 | 注意事项 |
|---|---|---|---|
| Windows | COM + 数字 | COM3、COM7 | 设备管理器查看,USB 转串口通常为 COM3 以上 |
| Linux | /dev/ttyS*、/dev/ttyUSB*、/dev/ttyACM* | /dev/ttyUSB0、/dev/ttyACM0 | ttyUSB 为 USB 转串口,ttyACM 为原生 USB 设备 |
| macOS | /dev/tty.* 或 /dev/cu.* | /dev/tty.usbserial-0001 | tty 为阻塞模式,cu 为无阻塞模式,推荐使用 cu |
在 Linux 系统下,如果遇到权限不足的问题,需要将当前用户加入 dialout 组:
sudo usermod -a -G dialout $USER
# 重新登录后生效
3.2 serial.Serial 参数详解
serial.Serial 是 pySerial 的核心类,其构造函数提供了丰富的参数来控制串口行为。理解这些参数是正确配置通信的前提。
3.2.1 核心参数速查表
| 参数名 | 默认值 | 可选值 | 说明 |
|---|---|---|---|
| port | None | 字符串,如 "COM3"、"/dev/ttyUSB0" | 串口设备名,必填参数 |
| baudrate | 9600 | 300、1200、2400、4800、9600、19200、38400、57600、115200 等 | 波特率,决定每秒传输的比特数 |
| bytesize | 8 | 5、6、7、8 | 数据位,通常为 8 位 |
| parity | N | N(无校验)、E(偶校验)、O(奇校验)、S(空格校验)、M(标记校验) | 校验位,用于错误检测 |
| stopbits | 1 | 1、1.5、2 | 停止位,标志一帧数据的结束 |
| timeout | None | None(阻塞)、0(非阻塞)、正数(秒) | 读操作超时时间,None 表示一直等待 |
| write_timeout | None | None 或秒数 | 写操作超时时间 |
| xonxoff | False | True / False | 软件流控(XON/XOFF) |
| rtscts | False | True / False | 硬件流控(RTS/CTS) |
| dsrdtr | False | True / False | 硬件流控(DSR/DTR) |
3.2.2 参数配置示例
在实际项目中,最常见的配置组合是 115200 波特率、8 数据位、无校验、1 停止位(简称 115200-8-N-1):
import serial
# 方式一:通过构造函数直接配置
ser = serial.Serial(
port='COM3', # Windows 示例
baudrate=115200, # 波特率
bytesize=serial.EIGHTBITS, # 8 数据位
parity=serial.PARITY_NONE, # 无校验
stopbits=serial.STOPBITS_ONE, # 1 停止位
timeout=0.5, # 读超时 0.5 秒
write_timeout=0.5 # 写超时 0.5 秒
)
# 方式二:先创建对象,再设置参数
ser = serial.Serial()
ser.port = '/dev/ttyUSB0' # Linux 示例
ser.baudrate = 9600
ser.bytesize = 8
ser.parity = 'N'
ser.stopbits = 1
ser.timeout = 2
ser.open()
3.3 打开与关闭串口
串口的打开与关闭是资源管理的关键环节。pySerial 提供了两种打开方式:隐式打开和显式打开。
3.3.1 打开串口
隐式打开:在创建 serial.Serial 对象时,如果指定了 port 参数,串口会自动打开:
ser = serial.Serial('COM3', 115200, timeout=1)
print(ser.is_open) # 输出 True
显式打开:先创建对象,再调用 open() 方法:
ser = serial.Serial()
ser.port = 'COM3'
ser.baudrate = 115200
ser.open()
print(ser.is_open) # 输出 True
3.3.2 关闭串口
关闭串口使用 close() 方法。关闭后,串口资源被释放,其他程序可以访问该端口:
ser.close()
print(ser.is_open) # 输出 False
为了确保资源始终被释放,推荐使用 with 语句进行上下文管理:
with serial.Serial('COM3', 115200, timeout=1) as ser:
# 在此代码块内进行读写操作
ser.write(b'AT\r\n')
response = ser.readline()
print(response)
# 离开 with 块后,串口自动关闭
with 语句管理串口生命周期。即使程序抛出异常,串口也会被正确关闭,避免端口被占用导致后续程序无法访问。
3.4 读写基础方法
pySerial 提供了丰富的读写方法,理解这些方法的差异是高效通信的关键。
3.4.1 写入数据
write() 方法用于向串口发送数据,接受字节类型(bytes)参数:
# 发送字符串(需要编码为 bytes)
ser.write(b'Hello UART\r\n')
# 发送十六进制数据
ser.write(bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC5, 0xCD]))
# 发送字符串变量
cmd = "AT+GMR\r\n"
ser.write(cmd.encode('ascii'))
# 检查实际写入的字节数
written = ser.write(b'12345')
print(f"实际写入 {written} 字节")
flush() 方法用于等待所有数据写入完成:
ser.write(b'important data')
ser.flush() # 阻塞直到数据全部发送完毕
3.4.2 读取数据
pySerial 提供了多种读取方式,适用于不同的应用场景:
| 方法 | 返回值 | 适用场景 |
|---|---|---|
| read(size=1) | 读取指定字节数 | 读取固定长度的数据帧 |
| readline() | 读取一行(以 \n 结尾) | AT 指令、文本协议 |
| readlines() | 读取多行列表 | 批量读取文本数据 |
| read_until(expected) | 读取直到遇到指定字节 | 自定义分隔符协议 |
| in_waiting | 返回接收缓冲区字节数 | 非阻塞读取前的检查 |
实际应用示例:
# 方式一:读取固定字节数
data = ser.read(10) # 读取 10 字节
# 方式二:读取一行(适合文本协议)
line = ser.readline() # 读取直到 \n
print(line.decode('utf-8').strip())
# 方式三:非阻塞读取全部可用数据
if ser.in_waiting > 0:
data = ser.read(ser.in_waiting)
print(f"读取到 {len(data)} 字节: {data.hex()}")
# 方式四:读取直到指定字符
response = ser.read_until(b'\r\n') # 读取直到回车换行
3.4.3 清空缓冲区
在通信过程中,缓冲区可能残留旧数据,影响新数据的解析。pySerial 提供了清空缓冲区的方法:
# 清空接收缓冲区
ser.reset_input_buffer()
# 清空发送缓冲区
ser.reset_output_buffer()
3.5 实战案例:温湿度传感器数据采集
下面通过一个完整的案例,演示如何使用 pySerial 与一个典型的 UART 温湿度传感器(如 DHT11 通过 Arduino 转发)进行通信。这个案例涵盖了本章所有核心知识点。
3.5.1 场景描述与目标
假设我们有一个 Arduino 开发板连接了 DHT11 温湿度传感器,Arduino 通过串口以 9600 波特率持续输出温度数据,格式为 T:25.3,H:60.2。我们的 Python 程序需要每 2 秒读取一次数据,并解析出温度和湿度值。
3.5.2 代码实现
import serial
import time
def read_sensor_data(ser):
"""读取并解析传感器数据"""
try:
# 清空接收缓冲区,避免读取旧数据
ser.reset_input_buffer()
# 读取一行数据
line = ser.readline().decode('utf-8').strip()
print(f"原始数据: {line}")
# 解析数据格式 "T:25.3,H:60.2"
if line.startswith('T:'):
parts = line.split(',')
temp = float(parts[0].split(':')[1])
hum = float(parts[1].split(':')[1])
return temp, hum
else:
return None, None
except Exception as e:
print(f"读取异常: {e}")
return None, None
def main():
# 配置串口参数
ser = serial.Serial(
port='COM3', # 根据实际端口修改
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
timeout=2 # 2 秒超时
)
print(f"串口已打开: {ser.name}")
try:
for i in range(5): # 采集 5 次
temp, hum = read_sensor_data(ser)
if temp is not None:
print(f"温度: {temp:.1f}°C, 湿度: {hum:.1f}%")
else:
print("数据解析失败")
time.sleep(2) # 间隔 2 秒
finally:
ser.close()
print("串口已关闭")
if __name__ == "__main__":
main()
3.5.3 运行结果与分析
串口已打开: COM3
原始数据: T:25.3,H:60.2
温度: 25.3°C, 湿度: 60.2%
原始数据: T:25.4,H:60.1
温度: 25.4°C, 湿度: 60.1%
原始数据: T:25.4,H:60.3
温度: 25.4°C, 湿度: 60.3%
串口已关闭
这个案例展示了完整的串口操作流程:配置参数 → 打开串口 → 循环读取 → 解析数据 → 关闭串口。在实际项目中,你可能需要处理更复杂的协议,比如 Modbus RTU、自定义二进制帧等,但核心的读写方法是一致的。
3.6 常见误区与总结复盘
3.6.1 常见误区
- 误区一:忘记设置 timeout。默认 timeout=None 会导致 read() 永久阻塞,程序卡死。解决:始终设置合理的超时时间。
- 误区二:字符串与字节混淆。write() 只接受 bytes 类型,不能直接发送 str。解决:使用
encode()方法转换。 - 误区三:忽略缓冲区残留。不清理接收缓冲区,导致读取到旧数据。解决:在发送指令前调用
reset_input_buffer()。 - 误区四:串口未关闭。程序异常退出后,串口仍被占用。解决:使用
with语句或 try-finally 确保关闭。 - 误区五:波特率不匹配。设备端和 Python 端波特率不一致,导致乱码。解决:确认设备实际波特率,并保持一致。
3.6.2 本章总结
本章系统讲解了 pySerial 的核心使用方法。从安装配置到参数详解,从串口生命周期管理到读写方法,再到实战案例,形成了一个完整的知识闭环。掌握这些基础后,你已经能够与大多数 UART 设备进行通信。在后续章节中,我们将深入探讨更高级的主题,如二进制协议解析、多线程通信、错误处理与重试机制等。