简介:基于51、STM32、TMS320F28033和Arduino四种平台的示波器设计资料包,面向电子爱好者、单片机学习者以及需要完成课程设计或电子竞赛的开发者。资料提供五套完整方案,从OLED显示的51简易示波器,到STM32数字示波器、20MHz手持式双踪袖珍示波器,再到手把手教学的数字存储示波器制作流程,覆盖原理图、PCB、源代码、固件和操作说明,既能用于理解示波器工作原理,也能直接仿制或二次开发。每套方案均配有原理图、PCB与源程序,可对照学习。包体共66个文件,约112.94MB,以C语言源码和头文件为主,配合schdoc/pcbdoc电路文件、PDF文档、hex/bin固件、jpg图片及uvproj工程文件,层次清楚地对应各套方案。目前已有一千余人学习浏览,适合作为实战参考资料,尤其对想在真实项目中掌握采集、触发、显示等示波器核心设计的读者很有帮助。
1. 五套单片机示波器方案,该从哪一套开始拆
我最早拿到这个112MB的压缩包时,第一反应是“又是课程设计合集”,真正打开才发现,五套资料从51、Arduino、STM32到DSP TMS320F28033全部覆盖,并且每套都带原理图、PCB、源程序和操作文档,这比大多数网上只有一张图没有代码的“开源示波器”完整得多。用单片机做示波器,最难的并不是“采集数据”这个动作,而是决定在什么精度级别上让采样、触发和显示三者协同。对要做课程设计、想理解数字存储示波器工作原理、或者需要一台低成本工具去测量Modbus总线和遥控脉冲信号的工程师来说,这套资料值得用一个周末逐层拆解。下面我按方案选型、模拟前端、PCB布局、固件实现和实际调试的顺序聊聊取舍。
2. 方案选型:51、STM32、TMS320F28033和Arduino的示波器架构差异
2.1 五套资料在定位上有多不同
资料包里最亮眼的是覆盖了从最简单到较专业的完整路径。“基于51单片机简易示波器设计OLED显示”那套,用51内核配合外置ADC和OLED,做出来的是带基础阈值触发功能的简易波形显示;而“基于STM32单片机数字示波器的设计”利用Cortex-M3核内置12位ADC,配合DMA和FSMC,可以在TFT上做到数百kSa/s到1MSa/s的实时采样;至于“基于TMS320F28033处理器的20MHz手持式双踪袖珍示波器”已经不是课程设计级别,而是接近商品仪器的工程方案,它用DSP的PWM和ADC协同方式实现双踪显示。Arduino Pro mini那套更适合快速复刻,用SPI OLED,代码量小,适合验证思路。
| 方案 | 主控/显示 | 采样链路特征 | 适合场景 |
|---|---|---|---|
| 51+OLED | 51单片机,SPI OLED | 低速ADC,软件触发,代码直观 | 课程设计、示波器原理入门 |
| STM32+TFT | STM32F1/F4,FSMC TFT | 内置ADC+DMA,定时器触发 | 数字信号观察、Modbus调试 |
| TMS320F28033 | DSP,LCD/OLED | 高速ADC+等效采样,双踪 | 20MHz带宽,手持设备DIY |
| Arduino Pro mini | AVR,SPI OLED | 开源库驱动,修改快 | 快速原型、学习采样逻辑 |
单片机示波器和台式示波器最大的差别在于没有专门的触发与采集ASIC,所以必须用CPU外设去模拟。51工程的优势是能把“每到一定时间采一个点”的过程看得清清楚楚;STM32工程的优势是DMA把数据从ADC搬到内存,不需要CPU一个一个搬,采样率直接高一个量级。真实场景中,如果只是看电源纹波,51足够;要抓SPI/UART时序,STM32这种实时采样率比较合适;要测20MHz晶振信号,就得上DSP方案里的等效采样。
2.2 采样率与模拟带宽换算:为什么20MHz必须用等效采样
先立一个基本公式:实时采样率fs至少要大于信号最高频率成分的两倍,工程上一般按4到10倍取。例如观察10MHz方波,如果只关心包络,实时采样率最好做到40MSa/s以上,否则上升沿完全失真。TMS320F28033的内置ADC在单通道模式下一般只能做到几MSa/s,达不到实时采样20MHz的要求,所以资料里的20MHz手持双踪示波器采用等效采样:对周期性重复信号,每次触发只取一个不同相位的点,多次触发拼接出一个完整波形周期。这里的关键是触发比较器要有足够快的建立时间和低抖动,采样相位步进由DSP内部定时器精确控制。
可以用下面这段Python快速验证采样率对波形重建的影响:
import numpy as np fs = 1_000_000 # 1 MSa/s f = 250_000 # 250 kHz 方波 t = np.arange(0, 1e-3, 1/fs) wave = np.sign(np.sin(2*np.pi*f*t)) # 等效采样:用低速ADC但改变相位偏移 offsets = np.arange(0, 50) # 50个相位 for k in offsets: phase = k/fs/50 # 这里只模拟相位,实际由DSP触发控制这段代码不是完整示波器程序,而是验证“等效采样必须知道当前触发点相对位置的偏移”。如果每个触发后的采样点相位是随机抖动的,重建波形会像噪声一样。DSP方案里用于触发的比较器输出会连接到捕获单元CAP,由硬件记录触发时刻并启动ADC,相位抖动控制在纳秒量级,才能在20MHz频率上看到干净的波形。这个选型差异也解释了为什么STM32内置ADC实时采样方案更适合中低频信号,而DSP方案才配叫“数字示波器”。
2.3 STM32工程里的ADC初始化:定时器触发和DMA是骨架
拿到STM32工程的源代码后,我建议先看ADC初始化和主循环,而不是先看UI。示波器的灵魂是采样时序。下面是这类工程常见的HAL库写法,参数需要根据你的主板时钟调整:
static void MX_ADC1_Init(void) { ADC_HandleTypeDef hadc1; ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; // 单通道,不用多通道扫描 hadc1.Init.ContinuousConvMode = DISABLE; // 关闭连续转换,由TIM2触发 hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 右对齐,方便十六进制显示 hadc1.Init.NbrOfConversion = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) Error_Handler(); sConfig.Channel = ADC_CHANNEL_0; // PA0,接模拟前端输出 sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; // 最快采样时间,1.5周期 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) Error_Handler(); }采样率由定时器决定,而不是靠delay循环。以84MHz的APB1时钟为例,要得到1MSa/s,就把定时器自动重载值ARR设成84,由更新事件触发ADC转换。调试时我会用一个固定频率方波接在模拟输入端,用显示屏上的波形周期反推实际采样率是否等于理论值。要注意ADC_SAMPLETIME_1CYCLE_5并不适合高阻抗信号源,因为采样保持电容来不及充电;如果波形幅度明显偏低,就改到ADC_SAMPLETIME_239CYCLES_5再观察幅度是否回升。
51工程则简单得多,用定时器中断读ADC寄存器,例如STC12系列内置ADC,每次中断读一次转换结果。这种软采样方式在屏幕刷新时会受串口和按键干扰,所以资料里的51工程会先采集完一整块数据再刷新,避免边采边显示造成波形撕裂。这也是学习示波器固件最有价值的一点:采样和显示要解耦。
3. 原理图与PCB:模拟前端、电源与地平面的三个设计要点
3.1 模拟前端信号链:从BNC到ADC的阻抗匹配
不管51还是DSP,示波器输入端都需要模拟前端,否则ADC只能测很窄的电压范围。资料里的五套原理图虽然细节不同,但链路基本一致:BNC输入经过保护二极管、隔直电容和分压电阻,再接一个高输入阻抗缓冲器,通常是JFET运放,然后进入可编程增益放大,最后送给ADC。以最常见的1MΩ输入阻抗为例,分压网络要把±10V衰减到ADC的0~3.3V范围内,并在靠近ADC一侧加钳位二极管。
为了确认前端RC参数是否合理,我习惯在LTspice里先搭一个简单的分压模型,再回到原理图核对电阻值:
* 模拟前端输入分压与采样保持电容仿真 V1 IN 0 AC 1 R1 IN A 1M R2 A 0 1M C1 A 0 10p R3 A OUT 50 C2 OUT 0 15p .ac dec 100 100 100MEG这个网表里,1MΩ和1MΩ分压提供2倍衰减,C2模拟ADC采样保持电容,C1用来补偿探头容性负载。跑完AC分析后,幅频曲线在10MHz以下应保持平坦,如果出现40MHz以上的峰起,说明补偿电容过大,需要在前端串一个阻尼电阻。另外,分压电阻值越大,热噪声越大,值越小,驱动前级的功耗越高。资料里DSP方案大量使用低温漂贴片电阻,就是为了在较高温度下保持输入阻抗和分压比不变,这也是手持示波器能达到20MHz带宽的原因之一。
3.2 PCB走线要求:模拟区域铺铜挖空与地平面回流
阅读资料中的PCB文件时,首先要分清哪个区域是模拟地,哪个是数字地。许多初学设计会直接分割地平面,但低频时这样反而会造成跨分割的回流路径。常见做法是保持整块地平面连续,只在ADC下方设计一个小范围模拟地岛,用0Ω电阻或磁珠在ADC接地引脚处单点连接。对应到AD20中,可以给模拟区域画一个Keep-Out,执行“铺铜挖空”,把DCDC、LCD排线这些数字噪声源和模拟输入段隔开。下面是我常用的一套检查规则:
| 检查项 | 推荐值/规则 | 原因 |
|---|---|---|
| 信号线最小线宽 | 10 mil / 关键信号50Ω阻抗线 | 减少电流瓶颈和串扰 |
| 模拟-数字地 | 单点连接 | 避免地回流噪声耦合 |
| 模拟输入过孔 | 关键信号不要换层 | 过孔电感影响高频响应 |
| 电源去耦 | 每个IC电源引脚100nF电容 | 降低电源平面阻抗 |
| 铺铜挖空区域 | ADC和DSP模拟输入周围0.5mm | 降低寄生电容耦合 |
PCB文件里如果看到“晶振下方不要走线”“模拟输入两侧加地孔栅栏”这类注释,不要当作装饰。TMS320F28033方案中,DSP的ADC参考电压引脚需要单独滤波电容,而且电容必须靠近VREF引脚,否则内部采样保持电容在转换瞬间抽走电流,测量码值就会跳动。这些都是实际调试时能明显感受到差异的细节。
3.3 打开资料后先做三件事
拿到这五套资料的原理图和PCB源工程后,我的操作顺序是:
- 用Altium Designer或Cadence打开PCB,先执行DRC,确认没有未连接的引脚或短路。资料里如果出现“OrCAD page number重复”这类报错,先把原理图属性里的Page Number改成唯一值再生成网表,否则导入PCB后网络会错乱。
- 在网络列表中检查模拟信号名称,例如
SIG_ANALOG、ADC_IN、VREF,确认它们没有直接跨越数字区域,也没有被铺铜挖空区域切断。 - 再读一遍操作文档里的元件清单,核对电阻电容封装,重点排查0402和0603混用时焊接的麻烦。
这三步做完再上电。直接上电无法区分“源程序错误”还是“硬件焊接错误”,只有先把设计意图搞清楚,调试才有对照物。
4. 固件核心:触发检测、DMA搬运和OLED刷新的实现顺序
4.1 数据采集:用DMA把ADC结果连续搬到内存
很多新手把“示波器固件”理解为画波形的主函数,实际上第一步是把波形数据正确、连续地搬进内存。STM32工程的常用做法是:ADC由定时器触发,转换结果通过DMA循环写到固定长度缓冲区,DMA传输完成一半或全部时触发中断,CPU只负责处理数据,不参与采样。下面的代码是启动采集的核心调用:
#define SAMPLE_BUF_SIZE 2048 static volatile uint16_t adc_buf[SAMPLE_BUF_SIZE]; void oscillo_start_acquisition(void) { // 每转换完成一次,DMA自动把结果从ADC_DR搬到adc_buf HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, SAMPLE_BUF_SIZE); // 开启DMA半传输和传输完成中断 __HAL_DMA_ENABLE_IT(&hdma_adc1, DMA_IT_HT | DMA_IT_TC); }参数说明:SAMPLE_BUF_SIZE决定一次能存多少个采样点,2048个点在全屏TFT上显示绰绰有余。半传输中断HT表示前1024点已经填满,此时可以刷新上半屏;传输完成中断TC表示后1024点填满,刷新下半屏。这种双缓冲处理避免显示任务阻塞采样任务,是51工程里很难做到的。如果不开DMA,用while循环读ADC再画点,实际采样率会掉到几十kSa/s,方波边沿会出现明显台阶。
4.2 触发逻辑:边沿检测和滞回比较
数字示波器的“触发”不是为了好看,而是为了不让波形左右滚动。STM32源码里通常会定义一个寻找触发点的函数:
#define LEVEL_THRESHOLD 2048 // 12位ADC中间值,对应1.65V #define HYSTERESIS 32 // 滞回窗口,防止噪声反复触发 uint32_t find_trigger_edge(uint16_t *buf, uint32_t len, uint8_t rising) { for (uint32_t i = 1; i < len; i++) { if (rising && buf[i-1] <= LEVEL_THRESHOLD + HYSTERESIS && buf[i] > LEVEL_THRESHOLD + HYSTERESIS) { return i; } if (!rising && buf[i-1] >= LEVEL_THRESHOLD - HYSTERESIS && buf[i] < LEVEL_THRESHOLD - HYSTERESIS) { return i; } } return 0xFFFFFFFF; // 未找到触发点,返回异常 }为什么上升沿判断里用LEVEL_THRESHOLD + HYSTERESIS而不是直接用LEVEL_THRESHOLD?因为实际信号叠加了噪声,如果阈值电平刚好落在噪声范围内,一帧里会出现几十个假触发点,显示画面来回跳动。加上32个LSB的滞回窗口后,信号要第一次越过阈值才触发,之后必须回到低于阈值再跨过窗口,才能触发下一次。找到触发点后,从该位置向右取固定长度数据作为一帧,显示时波形就稳定了。
如果信号带有窄毛刺,建议把判断条件改成“连续两个点都高于阈值”,可以滤除窄脉冲干扰。滞回窗口并不是越大越好,窗口过大时小幅值信号可能完全触发不了,所以固件里最好把HYSTERESIS开放到菜单中,方便实测时调整。
4.3 屏幕刷新:OLED的SPI写入与TFT的FSMC写入
51工程的OLED示波器大多使用SPI接口,刷新128x64屏幕需要发送1024字节缓冲。如果每个字节都通过GPIO翻转模拟SPI,一帧刷新时间可能超过30ms,所以源码里通常会封装一个OLED_Refresh()函数,直接连续写入显存数据,避免显示过程中被中断打断。STM32工程则把TFT接在FSMC上,把LCD当作外部SRAM写地址,刷新速度远高于51。判断固件是否合理,可以看它是否在中断里做屏幕刷新:好的实现只把采样数据拷贝到显存,主循环里统一刷新,采样中断不会被拉长。
以这套资料里的STM32工程为例,我整理了一个适合移植的刷新流程参数表:
| 参数项 | 建议值 | 说明 |
|---|---|---|
| 采样缓冲长度 | 2048点 | 保证至少包含一个完整周期 |
| 显示缓冲长度 | 240x320像素 | 与TFT兼容,不足时做缩放 |
| ADC采样率 | 500kSa/s | 观察10kHz以下信号足够 |
| 触发源通道 | ADC0/比较器输入 | 单通道模式选CH0 |
| 刷新方式 | 显存+主循环DMA | 中断内不应直接操作LCD |
这里要特别注意,HAL_ADC_Start_DMA()启动后,应在主循环里轮询标志位,而不是在DMA中断里调用HAL_Delay(),否则会导致ADC暂停采样。很多奇怪的波形断裂、屏幕闪烁都是这个原因。确定采样率后,再把时基设置对应到实际场景。比如观察9600bps的Modbus,一个位是104us,要在一屏里看到约5个位,时基设为500us/div即可,刷新率大约30fps,观感已经足够流畅。
5. 进阶实践:用自制示波器抓Modbus帧、PT2262解码和李萨如图形
5.1 把51/STM32示波器接到RS485总线上
自制示波器最有价值的应用场景之一就是调试Modbus单片机帧接收数据程序。将探头夹在RS485收发器如SP3485的A、B输出端,地线夹在板子电源地上,在示波器菜单中选择上升沿触发,触发电平设为总线差分电压的一半。9600bps下一位宽度约104us,时基1ms/div时一格能显示约10位,足以看到起始位、8个数据位和停止位。如果波形严重过冲,说明总线终端电阻没有匹配;如果看到数据跳变处有一串毛刺,优先怀疑收发器DE切换瞬间的时序,而不是线缆问题。
| 总线参数 | 示波器设置 | 原因 |
|---|---|---|
| 9600bps Modbus | 时基1ms/div,采样率500kSa/s | 每位约104us,500kSa/s下每位52个点 |
| RS485 A/B差分信号 | 垂直刻度1V/div,上升沿触发 | 差分信号高电平约2~3V |
| 总线空闲电平 | 触发电平1.5V | 低于或高于则无法稳定起步信号 |
| 电气噪声排查 | 开启峰峰值测量菜单 | 记录纹波mV级变化 |
如果用的是51 OLED方案,OLED格子线不好估读,可以先用串口把采样数据发送到上位机,再用虚拟示波器软件观察,能省掉很多估算误差。STM32方案可以直接在ADC数据后面追加打印帧长度和触发点偏移,配合波形一起判断。
5.2 用自制示波器观察PT2262解码后的脉冲序列
许多遥控设备使用PT2262编码芯片,其编码输出是若干个宽窄不同的脉冲。单片机解码程序通常用外部中断测量脉冲宽度,没有示波器很难分辨“脉冲宽度偏差”来自定时器分辨率还是接收头噪声。把示波器触发方式设为下降沿,时基500us/div,可以看到同步头之后连续出现的脉冲组。PT2262单个脉冲宽度通常在几十us量级,STM32的500kSa/s采样率足够还原。如果本应保持恒高或恒低的位置出现短暂毛刺,用单次触发模式抓一次完整编码,再计算相邻两个下降沿之间的时间差就能定位问题。
注意,PT2262射频接收头输出的是已经解调出来的TTL电平,可以直接接单片机引脚,不需要把RF载波信号送进示波器。自制示波器的模拟带宽一般在1MHz以内,直接测试几百MHz的载波只会看到一条平均电平线,这是物理限制。
5.3 最后一个技巧:探头补偿电容的校准
把示波器探头接到板载方波测试点,正常应该看到平顶直角;如果波形变成圆角,说明探头输入电容补偿不足,需要调节探头上的微调电容直到四角变锐。这个校准几乎所有示波器资料里都会提到,但最容易忽略。做完之后,再用X-Y模式输入两路正弦波,应当能看到标准的椭圆或李萨如图形,这是验证两个通道相位差最简单的方式,也是我给这套自制示波器方案最后留下的一个操作习惯。
本文还有配套的精品资源,点击获取