Python使用pyserial库实现串口通信基础教程

向北走
向北走
发布时间:2026-10-22 | 5人浏览过
原创

串口通信(Serial Communication)是计算机与各类硬件设备(如单片机、传感器、工业仪器、PLC等)交换数据最经典、最常用的底层接口方式。在 Python 生态中,pyserial 是实现跨平台串口访问的标准库。本文将以直接、实用的步骤向你介绍如何搭建环境、扫描识别串口、配置通信参数、进行可靠的数据收发,并妥善规避初学者常遇到的阻塞、乱码和端口冲突问题。

一、串口通信与 pyserial 核心原理

串口通信通常指通用异步收发器(UART)协议在硬件接口上的实现。上位机(如开发用电脑)通过 USB 转串口线或物理 COM 口与目标硬件引脚(TXD 发送端、RXD 接收端、GND 共地端)连接。数据以串行方式在单根数据线上一位一位地传输。

为了使收发双方正确识别同一组电平信号,双方必须预先约定一致的通信时序和帧格式。其中最核心的参数组合通常被称为通信配置(如常见的 9600, 8, N, 1):

  • 波特率(Baud Rate):每秒传输的符号数,常见取值有 9600、115200、57600 等。双方波特率不匹配将直接导致解析乱码。
  • 数据位(Data Bits):每个数据帧包含的有效数据位数,标准通常为 8 位。
  • 校验位(Parity):用于简单检错,分为无校验(None)、奇校验(Odd)、偶校验(Even)。绝大多数场景使用无校验。
  • 停止位(Stop Bits):标志一帧数据传输结束,通常为 1 位。
Python使用pyserial库实现串口通信基础教程
图 1:串口通信硬件连线拓扑与 UART 基础数据帧组成

二、环境准备与可用串口识别

在开始编写代码前,需要先正确安装库。Python 生态中存在一个常见的命名陷阱,请务必注意:

注意:安装包的名字是 pyserial,而不是 serial。如果错误执行了 pip install serial,会安装一个过期的非官方包导致导入失败。若已误装,请先 pip uninstall serial,再安装 pyserial。

# 正确的安装命令
pip install pyserial

安装完成后,我们需要知道物理设备插在系统的哪个端口上。手动去设备管理器或系统日志查找较为繁琐,pyserial 提供了内置工具模块 serial.tools.list_ports,可以在代码中自动扫描并列出所有已连接的串口。

import serial.tools.list_ports

def scan_available_ports():
    ports = serial.tools.list_ports.comports()
    if not ports:
        print("未发现可用串口设备,请检查硬件连接与驱动安装。")
        return []
    
    print(f"检测到 {len(ports)} 个可用端口:")
    available = []
    for p in ports:
        print(f"  设备名: {p.device:<18} 描述: {p.description}")
        available.append(p.device)
    return available

if __name__ == "__main__":
    scan_available_ports()

系统端口命名规则说明:

  • Windows:标识为 COM1、COM3、COM4 等。
  • Linux:标识为 /dev/ttyUSB0、/dev/ttyACM0 等。注意:Linux 用户通常需要将当前用户加入 dialout 组(运行 sudo usermod -aG dialout $USER)才能获得免 root 读写权限。
  • macOS:标识为 /dev/tty.usbserial-xxx 或 /dev/tty.usbmodemxxx。
Python使用pyserial库实现串口通信基础教程
图 2:pyserial 串口通信核心开发与生命周期管理流程

三、建立连接与关键参数配置

打开串口只需实例化 serial.Serial 类。如果在构造函数中传入了 port,串口对象会在创建后立即尝试打开。

import serial

# 初始化并打开串口
ser = serial.Serial(
    port='COM3',            # 端口号(Linux下为 '/dev/ttyUSB0')
    baudrate=9600,          # 波特率
    bytesize=serial.EIGHTBITS,  # 数据位:5, 6, 7, 8(默认 8)
    parity=serial.PARITY_NONE,  # 校验位:NONE, EVEN, ODD, MARK, SPACE(默认 NONE)
    stopbits=serial.STOPBITS_ONE, # 停止位:1, 1.5, 2(默认 1)
    timeout=1.0             # 读超时时间,单位秒
)

print(f"串口已成功打开: {ser.is_open}")

# 使用完毕后必须关闭
ser.close()

核心参数深入解析:读写超时(Timeout)

初学者编写串口程序时,最常见的问题是程序在读取数据时“卡住”不往下走。这完全取决于 timeout 参数的设置:

参数设置 工作模式 具体表现
timeout=None (默认值) 完全阻塞模式 调用 read() 或 readline() 时,若没有收到足量字节或换行符,代码会一直等待,永不返回。
timeout=0 非阻塞模式 立即返回。缓冲区里有多少字节就读多少;如果缓冲区为空,立刻返回空 b''。
timeout=1.5 (正浮点数) 超时模式 (强烈推荐) 等待指定秒数。若在限定时间内收到数据则提前返回,超时后立即返回已收到的部分字节或空字节。

最佳实践:使用上下文管理器(with 语句)
推荐使用 with serial.Serial(...) as ser: 语法。无论通信过程中是否发生异常,Python 都会在退出代码块时自动调用 ser.close(),防止操作系统将端口长期锁定。

四、数据的发送与接收细节

串口通信在物理层只传输裸字节(Raw Bytes)。在 Python 3 中,字符串(str)是 Unicode 编码,不能直接写入串口,必须先转换为 bytes 类型;同样,读出的内容也是 bytes,需要解码后才能当作文本处理。

Python使用pyserial库实现串口通信基础教程
图 3:数据类型转换与系统读写缓冲区流转机制

1. 发送数据:ser.write()

向串口发送数据必须传入 bytes 对象。常用方式包括字符串通过 encode() 编码,或者直接使用 b"" 字节前缀:

# 方式 1:字符串编码为 UTF-8 或 ASCII 后发送
cmd = "STATUS\r\n"
ser.write(cmd.encode('utf-8'))

# 方式 2:直接发送字面量字节
ser.write(b"HELLO\r\n")

# 方式 3:发送十六进制控制指令(如 Modbus 协议或传感器控制码)
hex_cmd = bytes([0xAA, 0x01, 0x02, 0x00, 0x55])
ser.write(hex_cmd)

2. 接收数据:读取方法比较

pyserial 提供了多种读取数据的方法,适用于不同的协议场景:

  • ser.read(size=1):读取指定数量的字节。常用于定长数据包或自定义二进制帧。
  • ser.readline():一直读取直到遇到换行符 \n(ASCII 0x0A)或触发超时。最适合基于换行符分隔的 ASCII 文本协议。
  • ser.in_waiting:返回当前输入缓冲区中尚未读取的字节数。常用于在调用读取前非阻塞地判断是否有数据到达。
  • ser.reset_input_buffer():清空接收缓冲区。在建立连接后或发送重要指令前执行,可丢弃历史未读取的脏数据。

五、完整双向通信代码示例

以下示例演示了如何编写一个健壮的交互脚本:打开串口、清空残留缓冲、发送指令、等待响应,并正确捕获可能发生的串口异常。

import time
import serial

def communicate_with_device(port_name, baud=9600):
    try:
        # 打开串口,设置 2 秒超时
        with serial.Serial(port=port_name, baudrate=baud, timeout=2.0) as ser:
            print(f"成功打开串口: {port_name},等待设备就绪...")
            
            # 部分开发板(如 Arduino)在打开串口时会发生 DTR 复位,需等待 1.5 秒
            time.sleep(1.5)
            
            # 清理打开连接前可能存在的缓冲区残留垃圾数据
            ser.reset_input_buffer()
            
            # 发送查询指令(通常以换行符结束)
            command = "PING\r\n"
            ser.write(command.encode('utf-8'))
            print(f"已发送指令: {command.strip()}")
            
            # 读取一行响应数据
            raw_response = ser.readline()
            
            if raw_response:
                # 去除末尾的换行符并解码为字符串
                text_response = raw_response.decode('utf-8', errors='replace').strip()
                print(f"收到设备响应: {text_response}")
            else:
                print("等待响应超时,设备未在规定时间内返回数据。")
                
    except serial.SerialException as e:
        print(f"串口通信发生错误: {e}")

if __name__ == "__main__":
    # 请根据实际扫描结果替换为具体端口名,如 'COM3' 或 '/dev/ttyUSB0'
    communicate_with_device("COM3", 9600)

六、常见问题与避坑排查指南

在实际串口项目调试过程中,常见问题往往集中在权限、硬件特性与分包上:

Python使用pyserial库实现串口通信基础教程
图 4:串口通信开发过程中的典型故障分析与解决路径

核心排查清单小结:

  1. 打不开端口:核对端口名是否正确;检查是否有串口监视器、IDE 等软件正在占用;拔插 USB 转接线以重新初始化驱动。
  2. 程序卡死:确认是否在未设置 timeout 的情况下直接调用了 ser.read() 或 ser.readline()。
  3. 设备不响应:检查物理接线是否接反。注意串口交叉接线原则:上位机 TX 对应下位机 RX,上位机 RX 对应下位机 TX,且双方 GND 必须牢固连在一起共地。
  4. 数据断续乱序:在每次重要指令交互前调用 ser.reset_input_buffer() 消除陈旧残留;对于连续数据流,建议在单独线程中使用循环配合 ser.readline() 处理。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表