如何终止FuncAnimation循环实现串口数据绘图存CSV及后续操作
问题根因
plt.show()是阻塞式调用,启动GUI事件循环后会一直占用主线程,直到绘图窗口关闭才会执行该行之后的代码。你原代码的问题有三个:
update_time()帧生成器里调用ani.pause()只会暂停动画刷新,不会退出事件循环关闭窗口,不手动关窗的话后面的CSV写入逻辑永远不会触发- 你在帧生成器里提前执行了
ser.close(),会导致动画还在运行时串口就被释放,后续读不到有效数据,就算关窗后执行写入,拿到的也是空列表 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
相关产品推荐
相关产品推荐

