基于PySerial与FFT实现Arduino实时数据时域、频域可视化的问题求助
基于PySerial与FFT实现Arduino实时数据时域、频域可视化的问题求助
我想从Arduino读取实时数据,绘制时域图,然后对这些数据做FFT并绘制频域图,但完全不知道该怎么做。现在的问题是没法把从串口获取的数据数组传给FFT,写的
freqdom函数估计完全是错的。
我来帮你梳理下问题并给出修正方案,你的核心需求是把Arduino的实时数据同时做时域和FFT频域可视化,现在代码里的问题主要集中在这几个方面:
- 数据同步问题:你现在的时域、频域函数各自独立读取串口,这会导致两份数据完全不同步,而且串口是流式传输,重复读取必然会丢包,完全没必要。
- FFT逻辑完全错误:频域函数里的
y=np.sin(2*np.pi*dataList1*t)完全没有意义——你既没定义时间轴t,也不需要生成正弦波,应该直接用采集到的原始数据做FFT。而且FFT需要足够多的采样点(比如512个,最好是2的幂次)才能得到有意义的频域结果,你每次只处理一个点,根本无法反映频率特征。 - 动画执行阻塞:第一个
plt.show()会完全阻塞程序,后面的ani2和其他代码根本不会运行,你没法同时看到两个动画窗口。 - 变量传递混乱:
ani2里传递的参数是freqdom_real,但freqdomain函数的形参是dataList1,完全不匹配,而且频域函数里的变量逻辑一团糟。
修正后的完整代码
import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation import serial import time # 配置参数(根据你的Arduino实际情况调整) SERIAL_PORT = "COM3" BAUD_RATE = 9600 SAMPLING_RATE = 100 # 要和Arduino的采样频率保持一致,单位Hz FFT_SAMPLE_COUNT = 512 # FFT采样点数,建议用2的幂次(如256/512/1024) TIME_WINDOW_LENGTH = 100 # 时域图显示的实时数据点数 # 全局变量:存储实时采集的数据 data_buffer = [] # 初始化串口连接 ser = serial.Serial(SERIAL_PORT, BAUD_RATE) time.sleep(2) # 等待Arduino完成串口初始化 # 创建一个包含两个子图的绘图窗口,同时显示时域和频域 fig, (ax_time, ax_freq) = plt.subplots(2, 1, figsize=(10, 8)) def update_plots(frame): global data_buffer # ---------------- 统一采集串口数据 ---------------- ser.write(b'g') # 发送指令请求数据 try: # 读取并解析串口数据 raw_data = ser.readline().decode('ascii').strip() data_value = float(raw_data) data_buffer.append(data_value) except (ValueError, UnicodeDecodeError): # 跳过无效数据(比如串口乱码、非数字内容) pass # ---------------- 更新时域图 ---------------- # 保持时域图的窗口长度固定 data_buffer = data_buffer[-TIME_WINDOW_LENGTH:] ax_time.clear() ax_time.plot(data_buffer) ax_time.set_ylim([0, 2000]) # 和你原代码的Y轴范围保持一致 ax_time.set_title("实时时域波形") ax_time.set_ylabel("数据值") ax_time.grid(True, alpha=0.3) # ---------------- 更新频域图(FFT) ---------------- ax_freq.clear() # 只有当采集到足够的FFT采样点时才计算 if len(data_buffer) >= FFT_SAMPLE_COUNT: # 取最近的N个采样点做FFT fft_input = data_buffer[-FFT_SAMPLE_COUNT:] # 执行FFT并计算幅值谱(做归一化处理) fft_result = np.fft.fft(fft_input) fft_amplitude = 2 * np.abs(fft_result) / FFT_SAMPLE_COUNT # 计算对应的频率轴 freq_axis = np.fft.fftfreq(FFT_SAMPLE_COUNT, 1 / SAMPLING_RATE) # 只显示正频率部分(符合实际观测习惯) positive_freq_mask = freq_axis >= 0 valid_freq = freq_axis[positive_freq_mask] valid_amp = fft_amplitude[positive_freq_mask] # 绘制频域图 ax_freq.plot(valid_freq, valid_amp) ax_freq.set_ylim([0, np.max(valid_amp)*1.1 if len(valid_amp) > 0 else 1000]) ax_freq.set_title("实时频域谱(FFT)") ax_freq.set_xlabel("频率 (Hz)") ax_freq.set_ylabel("幅值") ax_freq.grid(True, alpha=0.3) else: # 数据不足时提示 ax_freq.text(0.5, 0.5, "正在采集FFT所需数据...", ha='center', va='center', transform=ax_freq.transAxes) ax_freq.set_title("实时频域谱(FFT)") # 创建动画,每100ms更新一次 ani = FuncAnimation(fig, update_plots, interval=100, cache_frame_data=False) # 确保程序结束时正确关闭串口 try: plt.tight_layout() plt.show() finally: ser.close() print("串口连接已关闭")
关键修正说明
- 统一数据采集:只在一个函数里读取串口数据,保证时域和频域用的是同一份实时数据,彻底解决同步问题
- 正确的FFT实现:
- 直接用采集到的原始数据做FFT,不再生成无意义的正弦波
- 计算了对应实际物理意义的频率轴,频域图的X轴是真实频率
- 对FFT结果做了幅值归一化,结果更准确
- 只显示正频率部分,符合实际观测需求
- 合并绘图窗口:把时域和频域图放在同一个窗口的两个子图里,避免
plt.show()阻塞导致的动画无法运行问题 - FFT有效性保障:只有当采集到足够多的采样点时才执行FFT,保证频域结果的参考价值
- 安全资源释放:用
try...finally确保无论程序正常关闭还是异常退出,串口都会被正确关闭
备注:内容来源于stack exchange,提问作者Nemanja Nenadić
相关产品推荐
相关产品推荐

