3、Python串口库入门:pySerial安装、serial.Serial参数详解、打开/关闭串口、读写基础方法

串口通信是硬件控制领域最基础、最常用的通信方式之一。无论是嵌入式设备调试、传感器数据采集,还是工业自动化控制,UART(通用异步收发器)都扮演着不可替代的角色。Python 之所以能在硬件控制领域快速普及,很大程度上得益于 pySerial 库的简洁与强大。本章将带领你从零开始掌握 pySerial 的安装、核心参数配置、串口生命周期管理以及基础读写方法,为后续实现自动化控制打下坚实基础。

pySerial 串口通信核心链路 Python 应用层 serial.Serial() open() / close() read() / write() in_waiting / flush() pySerial 核心参数配置 • port: COM3 / /dev/ttyUSB0 • baudrate: 9600 / 115200 • bytesize: 8 / 7 / 5 / 6 • parity: N / E / O / S / M • stopbits: 1 / 1.5 / 2 • timeout: None / 0 / 0.5 / 2 硬件设备 MCU / 传感器 UART 接口 TTL / RS232 / RS485 USB 转串口适配器 API 调用 数据帧 数据流与生命周期 write(data) 串口发送缓冲 硬件 TX 引脚 设备 RX 接收 设备 TX 发送 硬件 RX 引脚 串口接收缓冲 read() 读取 生命周期:创建 Serial 对象 → open() → 读写操作 → close() 释放资源

上图展示了 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
# 重新登录后生效
经验提醒:在 Windows 下,如果插入 USB 转串口设备后没有识别到新 COM 口,请检查设备管理器中的"端口 (COM 和 LPT)"分类。如果出现黄色感叹号,需要安装对应的 USB 转串口驱动(如 CH340、CP210x、FTDI 等)。

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()
风险提示:timeout 参数的设置至关重要。如果设置为 None(默认值),read() 方法会一直阻塞直到读到数据,这在自动化控制中可能导致程序卡死。建议始终设置一个合理的超时时间(如 0.5~2 秒),以便在设备无响应时能够及时退出。

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 设备进行通信。在后续章节中,我们将深入探讨更高级的主题,如二进制协议解析、多线程通信、错误处理与重试机制等。

重要提醒:在实际项目中,串口通信的稳定性至关重要。建议在代码中加入异常捕获、重试机制和日志记录,以便在通信异常时快速定位问题。同时,务必在开发阶段使用串口调试助手(如 SSCOM、PuTTY)验证设备通信协议,确保 Python 代码与设备行为一致。