如何用Python实现PIC单片机串口数据的示波器式图形可视化?
实现方案与所需技术/算法
一、硬件与串口通信层
- 串口读取:用
pyserial库处理USB转RS232的串口通信,必须确保波特率、数据位、停止位、奇偶校验参数和PIC单片机完全匹配(两次转换器仅做电平转换,不改变串口参数)。 - 数据解析:和PIC约定数据格式,推荐用固定长度二进制帧(传输效率高)或ASCII文本行(调试便捷)。比如单片机每帧发送
[采样序号, ADC值],Python端解析后转成实际电压值:电压 = (ADC值 / ADC分辨率) * 参考电压,若需更高精度,可加入校准系数修正线路或传感器误差。
二、示波器UI与实时绘图实现
工具选择
优先选PyQtGraph(而非Matplotlib),它基于Qt框架,支持GPU加速,适合高采样率下的实时波形绘制,性能远优于Matplotlib的动画模块。如果需要丰富的交互控件(比如幅值滑块、时间缩放按钮),可结合PyQt5/PySide2搭建完整界面。
核心UI功能实现
- 时间刻度显示:根据采样率计算X轴时间,比如采样率1000Hz时,每1000个采样点对应1秒,X轴直接标注0~1s的时间值;若单片机带时间戳,直接用时间戳作为X轴数据更准确。
- 波形滚动刷新:维护一个固定长度的缓存数组(比如保存最近1000个采样点),新数据到来时,将数组左移丢弃最旧的点,把新数据追加到末尾,再刷新绘图。
- 幅值调整:通过滑块控件绑定Y轴范围,拖动滑块时调用
plot.setYRange(min_val, max_val)即可实现波形幅值的放大/缩小。
三、核心算法需求
幅值校准算法
将单片机输出的ADC原始值转换为实际电压:def adc_to_voltage(adc_value, adc_resolution=1023, ref_voltage=5.0, calibration=1.0): return (adc_value / adc_resolution) * ref_voltage * calibration其中
calibration是手动校准系数,用于修正传感器或线路的系统误差。数据滤波(可选)
若信号存在噪声,可采用以下两种滤波方式平滑波形:- 滑动平均滤波:取最近N个采样点的平均值作为当前值,适合消除高频随机噪声。
- 中值滤波:取最近N个采样点的中位数,适合去除尖峰脉冲干扰。
触发功能(可选)
实现类似专业示波器的触发逻辑,比如上升沿触发:检测电压从低于阈值跳到高于阈值时,锁定波形显示。算法上通过比较连续两个采样点的电压值,判断是否满足触发条件,触发后开始填充缓存数组并保持波形稳定。采样率同步
若单片机未发送时间戳,Python端可通过本地定时器记录采样间隔,但更可靠的方式是让单片机发送采样序号,Python端根据序号和预设采样率计算时间,避免串口延迟导致的时间轴失真。
四、极简代码示例(PyQtGraph版)
import serial import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui import numpy as np # 串口初始化 ser = serial.Serial('COM3', 9600, timeout=0.001) ser.flushInput() # 绘图窗口配置 app = QtGui.QApplication([]) win = pg.GraphicsLayoutWidget(show=True, title="自制示波器") plot = win.addPlot(title="交流电压波形") plot.setLabel('left', '电压(V)') plot.setLabel('bottom', '时间(s)') curve = plot.plot(pen='cyan') # 数据缓存:保存最近1000个采样点(对应1秒,采样率1000Hz) sample_rate = 1000 data_len = sample_rate data = np.zeros(data_len) x_axis = np.linspace(0, data_len/sample_rate, data_len) # 实时更新函数 def update_waveform(): global data # 读取串口数据(假设单片机每行发送一个ASCII格式的电压值) raw_data = ser.readline().decode('utf-8').strip() if raw_data: try: voltage = float(raw_data) # 滚动更新数据缓存 data = np.roll(data, -1) data[-1] = voltage curve.setData(x_axis, data) except ValueError: # 忽略无效数据 pass # 设置定时器,按采样率间隔更新 timer = QtCore.QTimer() timer.timeout.connect(update_waveform) timer.start(1000//sample_rate) # 间隔毫秒数 # 启动UI循环 if __name__ == '__main__': QtGui.QApplication.instance().exec_() ser.close()
注意事项
- 串口稳定性:添加异常捕获,处理串口断开、数据丢包的情况,必要时实现自动重连逻辑。
- 性能优化:高采样率(>10kHz)时,建议用二进制格式传输数据,减少解析耗时;同时限制缓存数组长度,避免内存占用过高。
- 功能扩展:可添加波形截图、数据保存、多通道显示等功能,通过PyQt的控件实现交互。
内容的提问来源于stack exchange,提问作者WalterPH
相关产品推荐
相关产品推荐

