本文档系统介绍 Python 环境下常用的串口通信库,包含安装方法、API 说明及完整示例代码。


目录

  1. 概述
  2. pySerial 库
  3. pySerial-asyncio 库
  4. serial.tools.list_ports 端口枚举
  5. 其他串口库
  6. 常见问题与调试
  7. 综合示例

1. 概述

串口通信(Serial Communication)是嵌入式开发、物联网、工业控制等领域中最基础的数据传输方式之一。Python 拥有成熟且易用的串口通信生态,其中最核心、最常用的库是 pySerial

主要库对比

库名 特点 适用场景
pySerial 最主流,跨平台,同步阻塞/非阻塞 常规串口读写
pySerial-asyncio 基于 asyncio 的异步串口 高并发、异步架构
pyusb 直接操作 USB 设备 USB 转串口底层控制

2. pySerial 库

2.1 安装

pip install pyserial

2.2 基本 API

打开串口

import serial

# 方式一:直接初始化
ser = serial.Serial(
    port='/dev/ttyUSB0',      # Windows 用 'COM3'
    baudrate=9600,            # 波特率
    bytesize=serial.EIGHTBITS, # 数据位
    parity=serial.PARITY_NONE, # 校验位
    stopbits=serial.STOPBITS_ONE, # 停止位
    timeout=1                 # 读取超时(秒)
)

# 方式二:先创建对象,再打开
ser = serial.Serial()
ser.port = 'COM3'
ser.baudrate = 115200
ser.open()

常用属性与方法

方法/属性 说明
ser.is_open 串口是否打开
ser.write(data) 发送数据(bytes)
ser.read(size) 读取指定字节数
ser.readline() 读取一行(以 \n 结尾)
ser.readlines() 读取所有行
ser.in_waiting 输入缓冲区字节数
ser.out_waiting 输出缓冲区字节数
ser.flush() 刷新输出缓冲区
ser.reset_input_buffer() 清空输入缓冲区
ser.reset_output_buffer() 清空输出缓冲区
ser.close() 关闭串口

2.3 完整示例:基础读写

import serial
import time

def basic_serial_example():
    """
    基础串口通信示例:打开串口、发送数据、接收数据
    """
    try:
        # 打开串口(根据系统修改端口名)
        ser = serial.Serial(
            port='/dev/ttyUSB0',  # Linux/Mac
            # port='COM3',        # Windows
            baudrate=9600,
            timeout=1
        )

        print(f"串口已打开: {ser.port}")
        print(f"波特率: {ser.baudrate}")

        # 发送数据
        message = b"Hello, Serial!\n"
        bytes_written = ser.write(message)
        print(f"已发送 {bytes_written} 字节: {message}")

        # 等待设备响应
        time.sleep(0.5)

        # 读取响应
        if ser.in_waiting > 0:
            response = ser.read(ser.in_waiting)
            print(f"收到响应: {response}")

        # 关闭串口
        ser.close()
        print("串口已关闭")

    except serial.SerialException as e:
        print(f"串口错误: {e}")
    except Exception as e:
        print(f"其他错误: {e}")

if __name__ == '__main__':
    basic_serial_example()

2.4 完整示例:循环读取

import serial
import time

def continuous_read_example():
    """
    持续监听串口数据,直到收到退出指令
    """
    ser = serial.Serial(
        port='/dev/ttyUSB0',
        baudrate=115200,
        timeout=1
    )

    print("开始监听串口数据,输入 'exit' 退出...")

    try:
        while True:
            if ser.in_waiting > 0:
                # 读取一行数据
                line = ser.readline().decode('utf-8').strip()
                print(f"[{time.strftime('%H:%M:%S')}] 收到: {line}")

                # 处理特定指令
                if line.lower() == 'exit':
                    print("收到退出指令")
                    break

            time.sleep(0.1)  # 避免 CPU 占用过高

    except KeyboardInterrupt:
        print("\n用户中断")
    finally:
        ser.close()
        print("串口已关闭")

if __name__ == '__main__':
    continuous_read_example()

2.5 完整示例:十六进制通信

import serial

def hex_communication_example():
    """
    十六进制数据通信示例(常用于 Modbus、传感器等协议)
    """
    ser = serial.Serial(
        port='/dev/ttyUSB0',
        baudrate=9600,
        timeout=1
    )

    # 构造十六进制指令(例如:读取传感器数据)
    # 示例:Modbus RTU 读取保持寄存器
    command = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B])

    print(f"发送指令: {command.hex()}")
    ser.write(command)

    # 等待响应
    import time
    time.sleep(0.1)

    # 读取响应
    if ser.in_waiting > 0:
        response = ser.read(ser.in_waiting)
        print(f"收到响应: {response.hex()}")
        print(f"响应长度: {len(response)} 字节")

    ser.close()

if __name__ == '__main__':
    hex_communication_example()

3. pySerial-asyncio 库

3.1 安装

pip install pyserial-asyncio

3.2 完整示例:异步串口通信

import asyncio
import serial_asyncio

class SerialProtocol(asyncio.Protocol):
    """自定义串口协议处理器"""

    def __init__(self):
        self.transport = None
        self.buffer = b''

    def connection_made(self, transport):
        self.transport = transport
        print(f"串口已连接: {transport.serial.port}")

    def data_received(self, data):
        self.buffer += data
        print(f"收到数据: {data.decode('utf-8', errors='ignore').strip()}")

    def connection_lost(self, exc):
        print("串口连接已断开")

async def async_serial_example():
    """
    异步串口通信示例
    """
    loop = asyncio.get_event_loop()

    # 创建串口连接
    transport, protocol = await serial_asyncio.create_serial_connection(
        loop,
        SerialProtocol,
        '/dev/ttyUSB0',
        baudrate=9600
    )

    # 发送数据
    transport.serial.write(b"Hello Async Serial!\n")

    # 保持运行一段时间
    await asyncio.sleep(10)

    # 关闭连接
    transport.close()

if __name__ == '__main__':
    asyncio.run(async_serial_example())

4. serial.tools.list_ports 端口枚举

4.1 列出可用串口

import serial.tools.list_ports

def list_serial_ports():
    """
    列出系统中所有可用的串口设备
    """
    ports = serial.tools.list_ports.comports()

    if not ports:
        print("未找到可用串口")
        return []

    print(f"发现 {len(ports)} 个串口设备:")
    print("-" * 50)

    available_ports = []
    for port in ports:
        print(f"端口: {port.device}")
        print(f"  描述: {port.description}")
        print(f"  硬件ID: {port.hwid}")
        print(f"  制造商: {port.manufacturer or '未知'}")
        print(f"  序列号: {port.serial_number or '未知'}")
        print()
        available_ports.append(port.device)

    return available_ports

if __name__ == '__main__':
    list_serial_ports()

4.2 自动选择串口

import serial.tools.list_ports

def auto_select_port(keyword='USB'):
    """
    根据关键词自动选择串口
    """
    ports = serial.tools.list_ports.comports()

    for port in ports:
        if keyword.lower() in port.description.lower():
            print(f"自动选择端口: {port.device} ({port.description})")
            return port.device

    print(f"未找到包含 '{keyword}' 的串口")
    return None

if __name__ == '__main__':
    port = auto_select_port('USB')
    if port:
        print(f"使用端口: {port}")

5. 其他串口库

5.1 minimalmodbus(Modbus RTU)

pip install minimalmodbus
import minimalmodbus

def modbus_example():
    """
    使用 minimalmodbus 进行 Modbus RTU 通信
    """
    # 创建仪器对象
    instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1)  # 端口, 从站地址
    instrument.serial.baudrate = 9600
    instrument.serial.timeout = 1

    try:
        # 读取保持寄存器(功能码 03)
        value = instrument.read_register(0, 0)  # 寄存器地址, 小数位数
        print(f"寄存器 0 的值: {value}")

        # 写入保持寄存器(功能码 06)
        instrument.write_register(0, 100)
        print("写入成功")

    except Exception as e:
        print(f"Modbus 错误: {e}")
    finally:
        instrument.serial.close()

if __name__ == '__main__':
    modbus_example()

5.2 pyusb(底层 USB 控制)

pip install pyusb
import usb.core
import usb.util

def usb_serial_example():
    """
    使用 pyusb 进行底层 USB 设备操作
    """
    # 查找 USB 设备(需要知道 VID 和 PID)
    dev = usb.core.find(idVendor=0x1234, idProduct=0x5678)

    if dev is None:
        print("未找到指定 USB 设备")
        return

    # 设置配置
    dev.set_configuration()

    # 获取接口
    cfg = dev.get_active_configuration()
    intf = cfg[(0,0)]

    # 查找端点
    ep_out = usb.util.find_descriptor(
        intf,
        custom_match=lambda e:             usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT
    )

    ep_in = usb.util.find_descriptor(
        intf,
        custom_match=lambda e:             usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN
    )

    # 发送数据
    ep_out.write(b'Hello USB!')

    # 接收数据
    data = ep_in.read(64)
    print(f"收到: {bytes(data)}")

if __name__ == '__main__':
    usb_serial_example()

6. 常见问题与调试

6.1 权限问题(Linux)

# 临时解决:将用户加入 dialout 组
sudo usermod -a -G dialout $USER

# 临时赋予权限
sudo chmod 666 /dev/ttyUSB0

6.2 常见错误排查

错误信息 原因 解决方案
Permission denied 权限不足 加入 dialout 组或使用 sudo
Device or resource busy 串口被占用 关闭其他使用该串口的程序
Could not open port 端口不存在 检查端口名是否正确
Read timeout 读取超时 检查设备是否响应,调整 timeout

6.3 调试技巧

import serial
import logging

# 启用详细日志
logging.basicConfig(level=logging.DEBUG)

ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)

# 查看串口状态
print(f"是否打开: {ser.is_open}")
print(f"输入缓冲区: {ser.in_waiting} 字节")
print(f"输出缓冲区: {ser.out_waiting} 字节")

# 清空缓冲区
ser.reset_input_buffer()
ser.reset_output_buffer()

7. 综合示例

7.1 完整串口通信类

import serial
import serial.tools.list_ports
import threading
import time
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class SerialCommunicator:
    """
    通用串口通信类
    支持:自动连接、数据监听、回调处理、线程安全
    """

    def __init__(self, port=None, baudrate=9600, timeout=1):
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self.serial = None
        self._running = False
        self._thread = None
        self._callbacks = []
        self._lock = threading.Lock()

    def auto_connect(self, keyword='USB'):
        """自动查找并连接串口"""
        ports = serial.tools.list_ports.comports()
        for p in ports:
            if keyword.lower() in p.description.lower():
                self.port = p.device
                return self.connect()
        logger.error(f"未找到包含 '{keyword}' 的串口")
        return False

    def connect(self):
        """连接串口"""
        try:
            self.serial = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                timeout=self.timeout
            )
            logger.info(f"已连接串口: {self.port} @ {self.baudrate}bps")
            return True
        except serial.SerialException as e:
            logger.error(f"连接失败: {e}")
            return False

    def disconnect(self):
        """断开连接"""
        self._running = False
        if self._thread and self._thread.is_alive():
            self._thread.join(timeout=2)
        if self.serial and self.serial.is_open:
            self.serial.close()
            logger.info("串口已断开")

    def send(self, data):
        """发送数据"""
        if not self.serial or not self.serial.is_open:
            logger.warning("串口未连接")
            return 0

        with self._lock:
            if isinstance(data, str):
                data = data.encode('utf-8')
            bytes_written = self.serial.write(data)
            logger.debug(f"发送 {bytes_written} 字节")
            return bytes_written

    def send_line(self, text):
        """发送一行文本(自动添加换行)"""
        if not text.endswith('\n'):
            text += '\n'
        return self.send(text)

    def register_callback(self, callback):
        """注册数据接收回调函数"""
        self._callbacks.append(callback)

    def start_listening(self):
        """启动后台监听线程"""
        self._running = True
        self._thread = threading.Thread(target=self._listen_loop, daemon=True)
        self._thread.start()
        logger.info("监听线程已启动")

    def _listen_loop(self):
        """后台监听循环"""
        buffer = b''
        while self._running:
            try:
                if self.serial and self.serial.in_waiting > 0:
                    data = self.serial.read(self.serial.in_waiting)
                    buffer += data

                    # 按行分割处理
                    while b'\n' in buffer:
                        line, buffer = buffer.split(b'\n', 1)
                        line = line.decode('utf-8', errors='ignore').strip()
                        if line:
                            self._handle_data(line)

                time.sleep(0.01)
            except Exception as e:
                logger.error(f"监听错误: {e}")
                break

    def _handle_data(self, data):
        """处理接收到的数据"""
        logger.info(f"收到: {data}")
        for callback in self._callbacks:
            try:
                callback(data)
            except Exception as e:
                logger.error(f"回调错误: {e}")

    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()


# ============ 使用示例 ============

def on_data_received(data):
    """数据接收回调"""
    print(f"[回调] 收到数据: {data}")

if __name__ == '__main__':
    # 方式一:使用上下文管理器
    with SerialCommunicator(port='/dev/ttyUSB0', baudrate=9600) as comm:
        if comm.serial and comm.serial.is_open:
            comm.register_callback(on_data_received)
            comm.start_listening()

            # 发送测试数据
            comm.send_line("Hello, Serial!")

            # 保持运行
            try:
                while True:
                    user_input = input("输入要发送的内容 (或 'quit' 退出): ")
                    if user_input.lower() == 'quit':
                        break
                    comm.send_line(user_input)
            except KeyboardInterrupt:
                pass

    # 方式二:自动连接
    # comm = SerialCommunicator(baudrate=115200)
    # if comm.auto_connect('USB'):
    #     comm.register_callback(on_data_received)
    #     comm.start_listening()
    #     # ... 业务逻辑
    #     comm.disconnect()

7.2 串口数据记录器

import serial
import csv
from datetime import datetime
import signal
import sys

class SerialDataLogger:
    """
    串口数据记录器:将串口数据实时保存到 CSV 文件
    """

    def __init__(self, port, baudrate, output_file='serial_data.csv'):
        self.port = port
        self.baudrate = baudrate
        self.output_file = output_file
        self.ser = None
        self._running = False

    def start(self):
        self.ser = serial.Serial(self.port, self.baudrate, timeout=1)
        self._running = True

        with open(self.output_file, 'w', newline='') as f:
            writer = csv.writer(f)
            writer.writerow(['timestamp', 'data'])

            print(f"开始记录数据到: {self.output_file}")
            print("按 Ctrl+C 停止...")

            while self._running:
                try:
                    if self.ser.in_waiting > 0:
                        line = self.ser.readline().decode('utf-8', errors='ignore').strip()
                        if line:
                            timestamp = datetime.now().isoformat()
                            writer.writerow([timestamp, line])
                            f.flush()  # 立即写入磁盘
                            print(f"[{timestamp}] {line}")
                except serial.SerialException as e:
                    print(f"串口错误: {e}")
                    break

    def stop(self):
        self._running = False
        if self.ser:
            self.ser.close()
        print("记录已停止")

if __name__ == '__main__':
    logger = SerialDataLogger('/dev/ttyUSB0', 9600, 'sensor_data.csv')

    def signal_handler(sig, frame):
        logger.stop()
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    logger.start()

附录

常用波特率

波特率 适用场景
9600 默认常用,兼容性好
19200 中等速度
38400 较高速率
57600 高速率
115200 高速通信,现代设备常用

数据格式

  • 数据位:通常为 8 位(EIGHTBITS)
  • 校验位:无校验(PARITY_NONE)最常用
  • 停止位:1 位(STOPBITS_ONE)最常用
  • 流控:无(无硬件/软件流控

更多推荐