简介面向51单片机学习者的迷你示波器设计资料完整覆盖从电路原理、PCB布局到固件实现的全流程。资源包为RAR压缩格式共31个文件、约3.88MB内含原理图与PCB设计文件schdoc、pcbdoc、schlib、pcblib及预览文件还有C语言源码、a51汇编初始化代码、hex/bin固件和Keil工程配置方便直接打开查看并二次修改目前已有299人学习下载。方案以51单片机为控制核心选用ADC0809完成多通道模数转换配合输入放大和滤波电路进行信号调理再通过LCD显示波形PCB设计重点考虑元件布局、地线电源层划分、抗电磁干扰、手工焊接便捷性以及管脚标识清晰。源程序按照系统初始化、A/D转换控制、数据处理、显示刷新和按键交互五个模块拆分便于逐块阅读与调试。无论是课程设计、电子竞赛还是入门制作简易波形显示工具都可以借助这些资料掌握硬件选型、信号采集、接口扩展及软硬件联调方法。1. 为什么有人会拿51单片机做一台迷你示波器先给一盆冷水51单片机做出来的示波器测不了MHz级别的信号。但把目标定在10kHz以内这件事就没那么荒谬。PWM占空比、RC充放电曲线、运放输出的慢变信号、传感器校准时的直流漂移这些都是日常调试最常见的观测对象一台“慢速但够用”的迷你示波器完全接得住。这个选题对三类人特别有用刚做完51课程设计、想把数电模电揉在一起练一遍的学生需要在工位上随手看低频波形、又不想掏出台式机的硬件工程师以及想把手上一套“原理图PCB源程序”做成完整作品的开发者。做一遍之后你对示波器最核心的采样率、带宽和触发三个词的理解会从“会按按钮”变成“懂原理”。下面沿着自制的完整路径展开先算指标再设计模拟前端接着处理PCB布局最后把采集、触发和显示程序跑起来并完成校准。2. 迷你示波器的指标先算账采样率、带宽与ADC选型2.1 一台最低限度的示波器需要什么参数示波器显示波形要经历“采样—量化—描点”三个环节51单片机适合扮演其中的数字部分而模拟前端和采样节奏决定了它到底能不能看。先定三个底线指标。采样率至少是目标信号频率的10倍。10倍只是能显示出一个“像样”的正弦波20倍会平滑很多低于5倍会出现严重的包络失真和混叠。垂直分辨率8位够看波形形状10位能分辨细节ADC0804是8位STC12C5A60S2内置ADC是10位价格差异很小但分辨率差异明显。存储深度方面波形要连续刷新采样点数组一般在256到1024点之间51单片机的片内RAM不大但使用xdata扩展后可以把缓冲开到1KB。这三个指标互相牵扯。采样率上去了一次显示的时间窗就变短垂直分辨率上去了ADC的转换时间和数据跳动也跟着来。对低频应用的迷你示波器我一般会按下述配置起步采样率20到50kSPS8到10位垂直分辨率256点环形缓冲。这个配置看1kHz方波很顺看5kHz正弦能接受再高就要靠运气了。2.2 内置ADC还是外扩ADC0804两条路怎么选常见的51课程设计会推荐ADC0804理由是接口简单、原理图好画、程序也好理解。但ADC0804的转换时间大约100us换算下来最快只有10kSPS而且只有8位。用它的时钟参数算一遍640kHz时钟下转换时间约110us那么1kHz信号一个周期只有10个点显示出来是折线不是波形。带内置ADC的增强型51更值得作为主方案典型代表是STC12C5A60S2。它仍然使用8051指令集Keil里直接编译但集成了10位ADC、多个定时器和内部看门狗。ADC转换时间比0804短不少实际采样率能做到几十kSPS对低频示波器已经够用。方案垂直分辨率实际采样率量级硬件复杂度适用场景STC12C5A60S2内置ADC10bit20~50kSPS低低频波形、传感器信号ADC08048bit约10kSPS中课程设计、原理图演示PCF85918bit约几kSPS低已被前两者替代不推荐我的结论是如果目标是整机落地、源程序能跑就用STC12C5A60S2内置ADC如果目标是让模数转换原理讲起来更直观可以在原理图上并列画一片ADC0804作为参考设计。前者调试一周能出结果后者更适合作为教学演示。2.3 用一段Python把采样率和可测频率换算清楚真正决定能测多高频率的不是ADC位数而是采样节奏。下面这段脚本是我设计时反复用的小工具def max_signal_freq(adc_sps: float, points_per_cycle: float 10) - float: adc_sps : 实际采样率每秒采样点数 points_per_cycle : 每个周期至少需要的点数10 是清晰显示的最低要求 return adc_sps / points_per_cycle # 定时器触发式采样20kSPS 对应能看 2kHz 以内的正弦波 print(max_signal_freq(20_000)) # 2000.0 # 查询式采样8kSPS 时1kHz 以下才比较可靠 print(max_signal_freq(8_000)) # 800.0adc_sps是最终实测到的采样率不是ADC理论转换速度因为定时器中断开销、ADC查询等待、显示刷新都会拖慢实际节奏。points_per_cycle设为10是最低标准想做漂亮一点的显示就把参数改成15或20。这个项目的有效测量范围大致定在DC~5kHz超过之后屏幕会给你画出一个看起来合理、实则骗人的波形。3. 原理图设计的核心链路从BNC到ADC引脚3.1 输入阻抗与衰减网络R值到底取多大探头标准是1MΩ输入阻抗迷你示波器也尽量往这个标准靠。输入阻抗越高对被测电路的分流越小但电阻值越大噪声和运放偏置电流的影响越明显。常见做法是用900kΩ对100kΩ分压得到10:1衰减同时保持总输入阻抗1MΩ。这里用一个10:1衰减网络做计算R1是串联电阻R2是并联在ADC输入端到地的电阻r_input 1_000_000 # 总输入阻抗 1M k 0.1 # 10 倍衰减 r2 r_input * k / (1 - k) # 并联电阻 r1 r_input - r2 print(fR1 {r1/1000:.1f} kΩ, R2 {r2/1000:.1f} kΩ) # 输出 R1 900.0 kΩ, R2 111.1 kΩR2取标准值110kΩ或110kΩ并联微调电阻都可以实际误差通过软件垂直校准修正。分压网络的寄生电容会形成低通滤波器所以PCB上要为R1和R2各预留一个并联补偿电容焊盘容量从2.2pF到22pF之间试。没有信号发生器时可以先不焊补偿电容对10kHz以内信号影响不大。常见衰减档位的电阻取值如下表做完第一版之后可以扩展档位开关档位R1R2输入阻抗适用信号幅度1:10Ω1MΩ1MΩ200mVpp 以下10:1900kΩ111kΩ1MΩ0.5V~5Vpp100:1990kΩ10.1kΩ1MΩ高压摆幅信号3.2 钳位保护与偏置让负压和单电源共存示波器输入端会碰到意外电压比如被测点接触不良时碰到12V甚至更高的电源。常规保护是用两颗二极管把输入电压钳位到电源轨之间。我习惯用一颗BAT54S双二极管它把VCC到信号、信号到GND两颗二极管封装在一起布局紧凑。钳位二极管会带来反向漏电流典型值在几十纳安到几微安之间。如果漏电流明显会在1MΩ输入阻抗上产生可观压降表现为波形零点漂移。这就是为什么不建议把衰减电阻随便加大到4.7MΩ的原因900kΩ/110kΩ这个量级比较平衡。偏置电路负责把交流信号搬到ADC可测量的中间电压。5V供电时ADC输入范围通常是0~VCC把信号的直流工作点设在2.5V正负摆幅都能留出余量。2.5V基准我一般用TL431产生再通过两颗10kΩ电阻分压为2.5V后接一颗100nF电容滤波。硬件上不要直接把VCC分压到2.5VVCC上的纹波会直接变成测量误差。3.3 运放缓冲与耦合电容常见做法是跟随器衰减网络输出阻抗在100kΩ量级不能直接驱动ADC中间需要一级电压跟随器。LM358在5V供电下输出只能到大约3.5V会削掉波形上半部分不适合做轨到轨前端。常见做法是换LMV321或LMV358这类单电源轨到轨运放5V供电时输出能到接近0V和接近5V。运放选型时还要关注输入偏置电流和带宽。LMV321的输入偏置电流是皮安级直接接100kΩ电阻网络不会产生明显直流误差带宽虽然只有1MHz左右但用它放大10kHz以内的信号余量很大。如果手头只有LM358可以将直流偏置降到2V并缩小垂直量程也能将就用但我不会拿它做第一版。交流耦合的截止频率由耦合电容和输入电阻决定。在10:1档位下输入电阻看进去约100kΩ用1uF电容时截止频率约为1.6Hz计算如下cutoff 1 / (2 * 3.14159265 * 100_000 * 1e-6) print(cutoff) # 约 1.59Hz1.6Hz的截止频率意味着20Hz以上的信号基本不受影响适合观察音频和传感器信号。如果做低频振动测量把电容换成10uF截止频率降到接近0.2Hz。3.4 时基与采样保持用定时器做ADC触发迷你示波器的水平时基由ADC采样间隔决定而采样间隔必须要用硬件定时器产生。用while加delay的软件延时会导致采样点间隔抖动屏幕上表现为波形横向抖动频率越高越明显。硬件做法是让定时器中断去触发ADC。这里有两个思路一是在中断里直接启动ADC并查询转换结束简单直接但要注意中断服务时间必须小于采样周期二是中断里只启动ADC转换完成后由ADC中断或主循环读取结果时序更准确但代码复杂一些。对于第一版我建议用第一种思路。把目标采样率设低一档比如10kHz也就是100us采样一次给中断服务函数留足执行时间。定时器采用方式28位自动重装重装值的计算方法在下一节源程序里详细展开。4. 原理图PCB的布局重点电源噪声、时钟抖动与ADC参考4.1 模拟区和数字区如何划分进入PCB布局时很多人第一反应是把地铜箔从中间劈开左边模拟地、右边数字地。这个做法在低频电路里经常适得其反地平面被切开后返回电流只能绕大圈走反而把数字噪声耦合进模拟区。我的习惯是整体铺地但在元件布局上把模拟链路和数字链路分开。模拟链路依次排开BNC座、钳位二极管、分压电阻、运放、偏置网络、ADC输入引脚中间不要穿插数字走线。单片机、OLED排线、按键和电源部分放到另一侧两者通过ADC所在位置汇合。如果板上已经有DC-DC电路不管它是boost还是buck拓扑输出纹波都会远大于LDO。迷你示波器前端对电源噪声不敏感但ADC参考对电源纹波敏感所以模拟区域供电建议从数字5V再串一级LDO或RC滤波单独分一支模拟5V。4.2 ADC参考电压去耦靠近REF引脚的1uF与100nFADC参考电压是误差的第一来源。把VCC直接接ADC参考引脚是最省事、也最容易翻车的做法——数码管或OLED刷新瞬间拉低VCC波形会出现一条条横向条带。正确做法是用TL431独立产生2.5V基准在REF引脚附近放1uF和100nF两级去耦。这里有个容易忽略的细节参考芯片的输出端和ADC的REF引脚之间走线不要太长优先靠近ADC放置并且这颗2.5V基准的地要单独打过孔回到模拟地汇合点。如果ADC使用VCC做参考VCC上的任何纹波都会直接叠加在采样结果上。10位ADC中1mV纹波会带来约1个LSB的偏差表面看不出来但在信号接近触发电平时会让波形边缘出现随机抖动。规避时钟抖动与电源噪声的3个PCB布局要点我归纳为参考去耦靠近引脚、地回路不跨分割、时钟线远离模拟走线。4.3 信号走线与铺铜的常见参数双层板是这个项目最合适的层数顶层走信号底层整面铺地。模拟走线尽量短而直减少寄生电感数字走线也不建议为了布线美观绕大圈。合理参数如下区域最小线宽过孔推荐铺铜策略模拟信号10mil内径0.3mm / 外径0.6mm尽量短两侧留地间距数字信号6mil内径0.3mm / 外径0.6mm驱动OLED可放宽到8mil电源线30mil内径0.5mm / 外径1.0mm加宽避免尖角地整面铺铜0.6mm孔径阵列过孔底层整体铺地关键位置上下相连晶振位置也要在这一阶段定好晶振尽量靠近单片机的XTAL引脚晶振下方不要走模拟信号线晶振外壳接地。示波器对时间基准的稳定性要求比其他51项目高采样时钟抖动会在水平方向模糊波形。线宽和电流的关系可以用经验公式粗算拿不准时直接用30mil电源线不会有问题# 1oz 铜厚下粗略估算电流承载能力 def trace_current(width_mil: float) - float: return 0.5 * (width_mil / 1.0) ** 0.5 print(trace_current(10)) # 约 1.58A模拟区域余量很大 print(trace_current(30)) # 约 2.74A4.4 嘉立创EDA里选择网络后如何联动PCB高亮在嘉立创EDA中整理完原理图后最实用的联动做法是在原理图编辑器里选中某个网络然后切换到PCB编辑器对应网络会自动高亮。如果你的版本没有自动联动在“视图”菜单里打开“交互选择”或“选择联动”选项这两个功能的叫法在不同版本里略有差异但作用相同。调试这个功能时我有个建议不用整张图一起高亮而是每次只选中一个关键网络比如REF、模拟5V、AGND在PCB上观察它的走向。三个网络分开看比一次看全部要清楚得多。确认这组网络没有绕远路、没有穿过数字区域之后再切换下一组。5. 源程序的框架采集、触发与OLED显示5.1 Keil5工程配置与STC-ISP烧录打开Keil5以后如果工程列表里找不到STC12C5A60S2这个型号需要先用STC-ISP烧录软件里的“Keil仿真设置”功能把STC型号库添加进去。这个功能会在Keil目录下生成一个设备数据库文件之后在Device选择里就能找到STC12C5A60S2。工程创建后要做两个关键设置存储器模式选Large或Small并配合xdata数组声明Target选项里晶振频率填实际值比如11.0592MHz或12MHz。烧录时用STC-ISP连接串口先选对芯片型号再点下载最后给板上电顺序不要反过来。如果先接触过头文件编译报错常见原因是头文件选择的系列不对。STC12C5A60S2的头文件不是通用的REG52.H需要包含STC12C5A60S2.H里面定义了ADC_CONTR、ADC_RES、ADC_RESL等寄存器地址。5.2 定时器触发ADC采样连续采集256个点以下代码按12MHz晶振、12T分频模式计算。如果ISP烧录时把单片机设置成了1T模式机器周期会缩短到时钟周期的约1/12定时器初值必须重新计算后面单独说明。#include STC12C5A60S2.H #define ADC_POWER 0x80 #define ADC_FLAG 0x10 #define ADC_START 0x08 volatile unsigned char xdata buf[256]; // 用 xdata 放环形缓冲 volatile unsigned char idx; void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x02; // 定时器0方式28位自动重装 TH0 TL0 256 - 100; // 100us中断一次折合10kSPS ET0 1; TR0 1; } unsigned char ReadADC(void) { ADC_CONTR ADC_POWER | ADC_START; // 启动转换通道0 while (!(ADC_CONTR ADC_FLAG)); // 等待ADC完成 ADC_CONTR ~ADC_FLAG; // 清除标志 return ADC_RES; // 取高8位结果 } void Timer0_ISR(void) interrupt 1 { buf[idx] ReadADC(); idx 0xFF; // 256长度自动回绕 }代码逻辑说明定时器0配置为方式2后TH0和TL0同时装载初值溢出后由硬件自动重新装载中断服务函数里不需要再写TH0/TL0抖动比软件重装小得多。初始化时TH0 TL0 256 - 100在12MHz和12T模式下机器周期就是1us100us对应计数100次。buf声明了volatile和xdatavolatile防止编译器把中断里修改的变量优化进寄存器副本xdata是因为STC12C5A60S2的片内直接寻址RAM只有256字节一个256字节数组放不下而扩展RAM至少1KB放一个256字节的缓冲绰绰有余。这里特别说一下分频模式带来的坑。STC12C5A60S2是1T内核很多开发板默认烧录为1T模式。在1T模式下12MHz晶振的机器周期约为83ns100us对应的计数次数约12008位定时器装不下。遇到采样率只有预期一半、波形刻度对不上的现象先检查ISP软件里的“CPU分频”选项把它确认在12T再继续调程序。5.3 软件触发与水平同步只有环形缓冲还不够显示屏必须知道从哪个点开始画。先找上升沿跨越触发电平的位置#define TRIG_LEVEL 128 // 大约50%量程 unsigned char FindTrigger(void) { unsigned char i; for (i 1; i 256; i) { if (buf[i - 1] TRIG_LEVEL buf[i] TRIG_LEVEL) { return i; } } return 0xFF; // 没有找到不刷新 }FindTrigger从缓冲起始点向后扫描找到前一个点低于触发电平、当前点高于触发电平的索引。这个位置作为本轮绘制的起点波形就能稳定在屏幕上不左右乱跑。如果找不到触发点常见原因是信号幅度小于触发电平或者已经超出显示范围。最简单的处理是找不到触发时继续用上一帧波形不更新屏幕同时把触发电平向信号中值方向自动调整几步。这个“自动扫描”模式在低频调试时比死等触发好用得多。5.4 波形显示把256个点画到128×64屏幕上OLED的横向分辨率是128列而采样点是256个每列要合并2个采样点。直接用“一个采样点对应一列”会丢掉一半信息正确做法是取每列的最大值和最小值画一条竖线#define SCREEN_W 128 #define SCREEN_H 64 void DrawWave(unsigned char start_idx) { unsigned char col, vmax, vmin; unsigned char j, y; unsigned char step 256 / SCREEN_W; unsigned int i; for (col 0; col SCREEN_W; col) { vmax 0; vmin 255; for (j 0; j step; j) { i start_idx col * step j; while (i 256) i - 256; // 环形回绕 if (buf[i] vmax) vmax buf[i]; if (buf[i] vmin) vmin buf[i]; } // 把 vmin..vmax 映射到屏幕垂直范围 for (y 64 - (vmax * SCREEN_H / 256); y 64 - (vmin * SCREEN_H / 256); y) { OLED_DrawPoint(col, y); // 画点函数 } } }这段代码按列扫描每列取多个采样点的最大和最小值画成竖线。max/min的方式能保留毛刺和尖峰直接取平均会把窄脉冲抹掉。映射计算中vmax * SCREEN_H / 256把0~255的采样值缩放到0~64像素再用64 -翻转坐标让波形顶部对应屏幕上方。OLED驱动函数涉及SPI或I2C时序不同屏幕差异较大这里不展开。要注意的是OLED刷新时会占用总线不要让绘制函数在采样中断里执行否则中断被拉长采样间隔就乱了。主循环负责读按键、触发显示、刷新屏幕中断里只做采样两者分工明确。6. 信号发生器校准法把两台机器互相测一测6.1 垂直刻度校准没有信号发生器时用手机下载一个正弦波发声App从耳机口输出1kHz、200mVpp的信号也能完成大部分校准。把探头接到耳机左右声道的公共地注意耳机输出有直流偏置串一个10uF电容再输入到示波器。垂直校准的目标是让屏幕网格上的电压值与实际值一致。假如信号发生器输出2Vpp信号屏幕上一格定义为200mV则10:1衰减档下信号应占满10格。如果不准优先调整3.1节提到的分压电阻精度其次再改软件里的比例系数。6.2 水平时基校准水平校准更直接给一个1kHz标准方波正常时每个周期应占100us。观察屏幕上一周期水平方向占几个像素块反推实际时间。如果一周期对应10格每格1ms那么显示正确如果屏幕上显示1.2ms每格说明实际采样率低于设计值回去检查分频模式和定时器初值。校准的最终目的是建立“采样点数字到物理量”的映射关系电压/格 (ADC参考电压 / 1023) × 探头衰减比 × 每格对应采样点数 时间/格 采样周期 × 每格水平像素数把这两个系数写成宏垂直和水平刻度就固定下来了。6.3 加一个简单的Vpp测量在源程序里加几行代码可以省掉OLED上数格子的麻烦unsigned char vmax 0, vmin 255; unsigned char i; for (i 0; i 256; i) { if (buf[i] vmax) vmax buf[i]; if (buf[i] vmin) vmin buf[i]; } // Vpp (vmax - vmin) * 2.5 / 1023 * 1010:1探头这段代码在显示波形的同时统计整帧最大和最小值再用参考电压2.5V、10位分辨率1023和探头衰减比10换算成实际电压幅度。51单片机没有硬件浮点单元浮点运算比较慢但只在每次触发完成后算一次刷新率不受影响。调试中遇到波形横向抖动先确认晶振是否真的起振再用示波器看定时器引脚输出实测中断频率和预期是否一致。遇到波形顶部削平检查运放输出是否超过电源轨2.5V偏置点两边余量是否足够。遇到低频漂移把耦合电容从1uF换成10uF观察是否改善。把校准系数写进源程序里的宏定义下次烧录就不用重新调。本文还有配套的精品资源点击获取