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

基于Matplotlib的STM32示波器可视化:时基与性能优化问询

解决Matplotlib示波器的时基缩放与性能优化问题

一、时基缩放实现(优先级高)

要解决信号细节丢失的问题,核心是维护固定容量的数据缓存,并结合Matplotlib的交互式导航工具,根据用户的缩放/平移操作动态截取对应时间范围的数据绘制。

关键思路

  1. 预计算时间轴:根据STM32的ADC采样率,生成每个采样点对应的时间戳(例如采样率fs=100000Hz时,单帧时间间隔dt=1/fs)。
  2. 环形数据缓存:用固定长度数组存储最新N个采样点(如N=20000),既避免内存溢出,又能回溯历史数据。
  3. 绑定坐标轴变化事件:当用户调整X轴范围时,自动筛选缓存中对应时间区间的数据,更新波形显示。

代码实现片段

import numpy as np
import matplotlib.pyplot as plt
import serial
import threading

# 硬件配置
SERIAL_PORT = 'COM3'
BAUD_RATE = 115200
FS = 100000  # 对应STM32的ADC采样率
DT = 1 / FS
BUFFER_SIZE = 20000  # 缓存采样点数

# 初始化数据缓存
time_buf = np.zeros(BUFFER_SIZE)
ch1_buf = np.zeros(BUFFER_SIZE)
ch2_buf = np.zeros(BUFFER_SIZE)
ch3_buf = np.zeros(BUFFER_SIZE)
buf_idx = 0  # 缓存写入指针

# 串口同步(等待8个0xAA)
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
sync_data = b''
while len(sync_data) < 8:
    sync_data += ser.read(8 - len(sync_data))
if sync_data != b'\xaa'*8:
    raise ValueError("设备同步失败,请检查连接")

# 串口数据读取线程
def read_serial():
    global buf_idx
    while True:
        # 读取3通道ADC数据(假设每通道2字节小端、12位精度)
        raw_frame = ser.read(6)
        if len(raw_frame) != 6:
            continue
        # 解析并转换为电压(参考电压3.3V)
        ch1 = int.from_bytes(raw_frame[0:2], 'little') & 0xFFF
        ch2 = int.from_bytes(raw_frame[2:4], 'little') & 0xFFF
        ch3 = int.from_bytes(raw_frame[4:6], 'little') & 0xFFF
        # 更新缓存
        time_buf[buf_idx] = buf_idx * DT
        ch1_buf[buf_idx] = ch1 * 3.3 / 4095
        ch2_buf[buf_idx] = ch2 * 3.3 / 4095
        ch3_buf[buf_idx] = ch3 * 3.3 / 4095
        buf_idx = (buf_idx + 1) % BUFFER_SIZE

threading.Thread(target=read_serial, daemon=True).start()

# 初始化绘图
fig, ax = plt.subplots(figsize=(10,6))
line1, = ax.plot([], [], label='CH1', linewidth=1)
line2, = ax.plot([], [], label='CH2', linewidth=1)
line3, = ax.plot([], [], label='CH3', linewidth=1)
ax.set_ylabel('电压(V)')
ax.set_xlabel('时间(s)')
ax.legend()
ax.set_ylim(0, 3.3)

# 时基缩放回调:X轴范围变化时更新显示数据
def update_plot(event):
    if event.inaxes != ax:
        return
    x_min, x_max = ax.get_xlim()
    # 筛选缓存中符合时间范围的数据
    mask = (time_buf >= x_min) & (time_buf <= x_max)
    if np.any(mask):
        line1.set_data(time_buf[mask], ch1_buf[mask])
        line2.set_data(time_buf[mask], ch2_buf[mask])
        line3.set_data(time_buf[mask], ch3_buf[mask])
        fig.canvas.draw_idle()

# 绑定X轴变化事件
fig.canvas.mpl_connect('xlim_changed', update_plot)

# 初始显示最新1秒数据
ax.set_xlim(max(0, buf_idx*DT - 1), buf_idx*DT)
update_plot(None)

# 启用Matplotlib原生导航工具栏(支持缩放、平移)
plt.show()

二、性能优化方案

针对可视化速度跟不上串口传输的问题,从渲染机制和数据处理两方面优化:

1. 渲染优化:使用Blitting位图缓存

Blitting仅重绘画布中变化的区域,大幅降低渲染开销。修改绘图更新逻辑:

# 初始化时缓存背景
fig.canvas.draw()
background = fig.canvas.copy_from_bbox(ax.bbox)

def update_plot_blit():
    global background
    # 恢复背景
    fig.canvas.restore_region(background)
    # 更新线条数据
    x_min, x_max = ax.get_xlim()
    mask = (time_buf >= x_min) & (time_buf <= x_max)
    line1.set_data(time_buf[mask], ch1_buf[mask])
    line2.set_data(time_buf[mask], ch2_buf[mask])
    line3.set_data(time_buf[mask], ch3_buf[mask])
    # 重绘线条
    ax.draw_artist(line1)
    ax.draw_artist(line2)
    ax.draw_artist(line3)
    # 更新画布
    fig.canvas.blit(ax.bbox)
    fig.canvas.flush_events()

# 缩放时更新背景缓存
def on_xlim_change(event):
    global background
    fig.canvas.draw()
    background = fig.canvas.copy_from_bbox(ax.bbox)
    update_plot_blit()

fig.canvas.mpl_connect('xlim_changed', on_xlim_change)

# 定时更新波形(30ms/次,平衡流畅度与性能)
import time
def animate():
    while True:
        update_plot_blit()
        time.sleep(0.03)

threading.Thread(target=animate, daemon=True).start()

2. 数据处理优化

  • 批量解析串口数据:避免逐帧读取解析,一次性读取多帧后用numpy批量处理,减少IO调用次数:
def read_serial():
    global buf_idx
    while True:
        raw_data = ser.read_all()
        if len(raw_data) < 6:
            continue
        # 按6字节为一组批量解析
        num_frames = len(raw_data) // 6
        frames = raw_data[:num_frames*6]
        data = np.frombuffer(frames, dtype=np.uint16).reshape(-1,3)
        data = data & 0xFFF  # 保留12位有效数据
        volts = data * 3.3 / 4095
        # 批量更新缓存
        for i in range(num_frames):
            time_buf[buf_idx] = buf_idx * DT
            ch1_buf[buf_idx] = volts[i,0]
            ch2_buf[buf_idx] = volts[i,1]
            ch3_buf[buf_idx] = volts[i,2]
            buf_idx = (buf_idx + 1) % BUFFER_SIZE

3. 其他优化点

  • 关闭非必要渲染元素:ax.grid(False),减少绘图负担。
  • 复用绘图对象:始终使用初始化时创建的line1/2/3,避免重复创建新的Line2D实例。
  • 降低线条宽度:设置linewidth=1,减少绘制复杂度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 15:37:08