You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python串口单字节读取每64次出现耗时异常的问题求助

问题

在Windows10系统上,使用Python通过USB连接设备,以115200波特率逐字节读取数据时,发现每读取第64个字节时,耗时比其他操作高50-100倍,导致程序运行缓慢甚至出现数据丢失。最初怀疑是append函数的实现问题,但改用预分配列表后问题依旧,目前怀疑问题出在serial.read()函数上,测试代码如下:

import serial
import time

counter_max = 24*100
test_mode = 0

PORT = 'COM12'
BAUDRATE = 115200
uart_port = serial.Serial()
uart_port.port = PORT
uart_port.baudrate = BAUDRATE
uart_port.timeout = None
uart_port.open()

counter = 0

if test_mode == 0:
# --- LIST VERSION -----------------------------------------------------------

    elapsed_times = []
    uart_datas = []
    
    while counter < counter_max:
        time_start = time.perf_counter_ns()
         
        uart_data = uart_port.read()
        uart_datas.append(uart_data)
        
        time_stop = time.perf_counter_ns()
        elapsed = time_stop - time_start
        elapsed_times.append(elapsed)
    
        counter += 1    

elif test_mode == 1:
# --- PREALLOCATED LIST VERSION ----------------------------------------------
    
    elapsed_times = [None] * (counter_max)
    uart_datas = [None] * (counter_max)
    
    while counter < counter_max:    
    
        time_start = time.perf_counter_ns()
    
        uart_data = uart_port.read()
        uart_datas[counter] = uart_data
        
        time_stop = time.perf_counter_ns()
        elapsed = time_stop - time_start
        elapsed_times[counter] = elapsed
    
        counter += 1    

uart_port.close()
# print(elapsed_times)
原因分析
  • 这是Windows系统串口驱动的64字节默认接收缓冲区特性导致的:Windows串口驱动会将接收到的数据暂存到64字节的缓冲区中,当缓冲区未填满时,serial.read(1)可以直接从缓冲区中取出单个字节;当缓冲区刚好攒够64字节时,驱动会触发一次完整的缓冲区提交操作,该操作涉及内核态到用户态的数据拷贝,开销远大于单次取字节的操作,因此会出现第64个字节读取耗时暴增的周期性现象。
  • pyserial库底层依赖Windows的串口API,read(1)的实现会等待驱动返回数据,而驱动的批量缓存策略直接导致了这种周期性的性能波动。
解决办法

1. 批量读取替代逐字节读取

避免每次仅读取1个字节,改为一次性读取多个字节(比如一次读取64字节或更大的块),这样可以减少驱动缓冲区提交操作的触发频率,大幅降低整体耗时。示例代码:

import serial
import time

counter_max = 24*100
PORT = 'COM12'
BAUDRATE = 115200

uart_port = serial.Serial(PORT, BAUDRATE, timeout=None)
total_bytes_read = 0
elapsed_times = []

while total_bytes_read < counter_max:
    time_start = time.perf_counter_ns()
    # 一次读取64字节,剩余不足64字节时读取剩余量
    chunk = uart_port.read(min(64, counter_max - total_bytes_read))
    total_bytes_read += len(chunk)
    time_stop = time.perf_counter_ns()
    elapsed_times.append(time_stop - time_start)

uart_port.close()

2. 调整串口驱动缓冲区大小

在Windows设备管理器中找到对应串口设备,进入属性→端口设置→高级,尝试调大接收缓冲区大小(例如设置为1024字节),减少缓冲区填满的频率,从而缓解周期性耗时问题。注意该操作依赖硬件驱动是否支持自定义缓冲区大小。

3. 异步线程读取解耦

开启独立线程负责持续读取串口数据到内存队列,主线程从队列中获取数据,将串口读取的耗时与业务逻辑解耦,避免单次高耗时读取阻塞主线程。示例代码:

import serial
import time
import threading
from queue import Queue

def serial_reader(port, data_queue):
    while port.is_open:
        chunk = port.read(64)
        if chunk:
            data_queue.put(chunk)

counter_max = 24*100
PORT = 'COM12'
BAUDRATE = 115200

uart_port = serial.Serial(PORT, BAUDRATE, timeout=1)
data_queue = Queue()

# 启动后台读取线程
reader_thread = threading.Thread(target=serial_reader, args=(uart_port, data_queue), daemon=True)
reader_thread.start()

total_bytes_read = 0
elapsed_times = []

while total_bytes_read < counter_max:
    time_start = time.perf_counter_ns()
    chunk = data_queue.get()
    total_bytes_read += len(chunk)
    time_stop = time.perf_counter_ns()
    elapsed_times.append(time_stop - time_start)

uart_port.close()

内容的提问来源于stack exchange,提问作者booo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 23:55:56