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

如何终止FuncAnimation循环实现串口数据绘图存CSV及后续操作

问题根因

plt.show()是阻塞式调用,启动GUI事件循环后会一直占用主线程,直到绘图窗口关闭才会执行该行之后的代码。你原代码的问题有三个:

  1. update_time()帧生成器里调用ani.pause()只会暂停动画刷新,不会退出事件循环关闭窗口,不手动关窗的话后面的CSV写入逻辑永远不会触发
  2. 你在帧生成器里提前执行了ser.close(),会导致动画还在运行时串口就被释放,后续读不到有效数据,就算关窗后执行写入,拿到的也是空列表
  3. FuncAnimation设置了repeat=True,时长到了之后动画会重新从头运行,不符合单次采集的需求
修正方案

按照你需要的流程调整逻辑即可:

  • 帧生成器计时到设定的试验时长后,主动调用plt.close()关闭绘图窗口,退出事件循环
  • 串口关闭、CSV写入、后续LED控制等逻辑全部放到plt.show()之后执行,保证这些逻辑在采集完成、窗口关闭后才顺序运行
  • 增加串口读取的异常容错,避免偶发的传输乱码、空行导致脚本崩溃
  • 取消动画重复配置,采集完成后直接退出动画逻辑
修正后可直接运行的代码
import serial
import time
import matplotlib.pyplot as plt
import datetime as dt
import matplotlib.animation as animation
from matplotlib.ticker import MaxNLocator
import csv

# 接收用户输入
title = input("请输入试验名称: ")
duration = float(input("请输入试验时长(秒): " ))
date = dt.datetime.now()
today_date = date.strftime("%Y-%m-%d %H:%M:%S")

# 初始化Arduino串口,注意根据设备管理器修改COM号
ser = serial.Serial('COM3', 115200, timeout=1)
time.sleep(2)

input("按回车键开始试验")

# 初始化绘图容器
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
t = []
T1 = []
T2 = []
T3 = []

time_resolution = 100 # 采样间隔,单位毫秒

# 帧生成器:控制采集时长
def frame_generator():
    frame_idx = 0
    time_start = time.time()
    while True:
        elapsed = time.time() - time_start
        if elapsed >= duration:
            # 时长到了主动关闭绘图窗口,退出事件循环
            plt.close(fig)
            break
        yield frame_idx
        frame_idx += 1

# 动画刷新回调:读串口、更新数据、重绘图像
def animate(i, t, T1, T2, T3):
    # 加异常捕获,跳过无效的串口数据
    try:
        line = ser.readline()
        string = line.decode().strip()
        if not string:
            return
        split_string = string.split()
        if len(split_string) != 3:
            return
        temp1, temp2, temp3 = [float(T) for T in split_string]
    except:
        return
    
    # 存入采集到的数据
    time_passed = time.time() - time_start_run
    t.append(time_passed)
    T1.append(temp1)
    T2.append(temp2)
    T3.append(temp3)
    
    # 重绘图像
    ax.clear()
    ax.plot(t, T1, label='T1')
    ax.plot(t, T2, label='T2')
    ax.plot(t, T3, label='T3')
    
    # 图像格式配置
    plt.xlim([0, duration])
    ax.xaxis.set_major_locator(MaxNLocator(10)) 
    plt.xticks(rotation=45, ha='right')
    plt.subplots_adjust(bottom=0.30)
    plt.title('温度实时采集数据')
    plt.ylabel('温度 (℃)')
    plt.xlabel('试验时长 (s)')
    ax.legend(loc="lower right")

# 启动动画
time_start_run = time.time()
ani = animation.FuncAnimation(
    fig, 
    animate, 
    fargs=(t, T1, T2, T3), 
    frames=frame_generator, 
    interval=time_resolution, 
    repeat=False # 禁止动画循环重复
)
plt.show()

# ---------------- 窗口关闭后才会执行以下逻辑 ----------------
# 保存CSV
with open(title + '.csv', mode='w',  newline='', encoding='utf-8') as trial_run_data:
    header = ["试验时长(s)", "T1(℃)", "T2(℃)", "T3(℃)", "试验开始时间"]
    trial_writer = csv.writer(trial_run_data, delimiter=',', quotechar='"', quoting=csv.QUOTE_NONNUMERIC)
    trial_writer.writerow(header)
    for idx in range(len(t)):
        trial_writer.writerow((t[idx], T1[idx], T2[idx], T3[idx], today_date))

print("\n数据采集完成,CSV文件已保存")

# 后续自定义串口操作,比如控制LED亮灭示例:
# ser.write(b'LED_ON')
# time.sleep(1)
# ser.write(b'LED_OFF')

# 所有操作完成后再关闭串口
ser.close()
额外说明
  • 如果需要手动提前终止试验,只要点击绘图窗口的关闭按钮,程序一样会自动走到CSV写入逻辑,不会丢数据
  • 串口读取部分加了容错,不会因为偶尔的传输错误直接崩溃
  • 如果采集完成后还要给Arduino发指令控制外设,等所有串口操作完成后再调用ser.close()即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 20:06:28