做硬件这行,谁没想过手里有一台自己的示波器?台式机占地方,带出门又嫌重,买一台几百块的玩具示波器性能又太捉急。前两年我被项目折腾得够呛,每次跨实验室调板子都得扛着公司的示波器跑,后来索性自己动手,用一片FPGA加一颗AD9288,从零搭了一台能触发的便携式示波器。这篇文章就是那次折腾的完整记录,包含方案怎么选、电路怎么搭、逻辑怎么写、板子怎么调,还有我踩过的一堆坑。不管你是FPGA入门学习者,还是想低成本给实验室添一台备用示波器,照着这个思路走,都能做出来。
1. 为什么非要自己造示波器:方案选型与核心思路
1.1 先弄清楚示波器到底在干什么
示波器的本质,是一个“高速采集 + 存储 + 显示”的数字系统。信号从探头进来,经过模拟前端调理,送到ADC进行模数转换,转换结果存入存储器,再由控制逻辑处理成波形,最终在屏幕上画出来。
这里面最要命的就是ADC的采样率。根据奈奎斯特采样定理,采样率至少是信号最高频率的2倍,否则频谱就会混叠。但实际做示波器,2倍根本不够看,工程上讲究采样率是信号带宽的5到10倍,才能在波形上看出失真、毛刺和细节。举个生活中类比,你拍视频想拍清楚风扇叶片转动的轨迹,每秒拍2帧肯定不行,至少得拍10帧、20帧,才能大致还原运动过程。信号采样也是这个道理。
所以一台数字示波器好不好,第一个指标就是ADC位宽和采样率。位宽决定垂直分辨率,也就是一个波形幅度能分多少个台阶;采样率决定水平分辨率,也就是波形能看得多细。这两项基本锁死了示波器的定位和上限。
1.2 AD9288这颗芯片到底什么水平
我选AD9288,主要是看中了它三点:
- 双通道8位、单通道最高100MSPS采样率,做便携入门示波器非常合适
- 数据输出是并行CMOS电平,直接接FPGA的普通IO,不需要高速串行解串器
- 无需软件配置寄存器,上电就能工作,对FPGA初学者特别友好
AD9288是双通道ADC,每个通道8bit,最大采样率100MSPS,供电3.3V,典型功耗在几十毫瓦级别。8bit意味着幅值被分成256份,做10MHz以内信号的波形观察完全够用;100MSPS采样率,按5倍采样率反推,可以做到20MHz带宽,日常调试STM32输出的PWM、I2C总线、SPI时序、电源纹波、音频信号,绰绰有余。
双通道也非常实用。很多调试场景需要同时看两个信号的关系,比如串口TX和RX、PWM输入和输出、运算放大器的同相和反相输出。用单通道示波器看两路信号只能来回切换探头,麻烦得很。AD9288双通道解决了我一大痛点。
和它对比的一些方案是这样的:
| 方案 | 优势 | 劣势 |
|---|---|---|
| MCU内置ADC + DMA | 电路最简单,代码好写 | 采样率一般不超过几MSPS,带宽太低 |
| 带USB接口的数字示波器模块 | 即插即用,有现成上位机 | 价格不低,无法定制触发和显示 |
| AD9288 + FPGA | 可学习、可定制、成本低 | 工作量最大,硬件软件都要自己搞 |
如果你只想快速测波形,直接买成品模块最省事。但如果你想从底层搞懂示波器原理,还想把FPGA练熟,那我强烈推荐第三方案。用FPGA做示波器,本质上就是把采样、存储、触发、显示这几大块全部用Verilog写出来,做完这一遍,你对FPGA的理解会上一个台阶。
1.3 FPGA在示波器里扮演的角色
有人可能会问,我用几十块钱的STM32做不行吗?只谈采样部分的话,STM32G474内置的ADC可以到5MSPS,做音频频段差不多,但想上20MHz带宽就完全不行了。外部挂AD9288虽然也可以,但ADC一秒钟输出1亿个8bit数据,MCU的IO翻转速度和DMA带宽根本扛不住,更别说同时做触发判断和实时显示。这就是FPGA的核心优势:并行处理天然适合这种高速数据流。
FPGA在示波器里承担四件事:
- 提供稳定的采样时钟,用PLL对晶振倍频,驱动ADC
- 实时接收ADC的并行数据,完成采样和降速处理
- 实现触发逻辑,识别信号的上升沿、下降沿或指定电平
- 驱动屏幕显示,或者通过串口/USB把数据打包上传到PC
我用的FPGA是Intel Cyclone IV E系列的EP4CE10E22C8,约1万个逻辑单元,片内有M9K块RAM可以做FIFO,有PLL可以做时钟管理,价格只要几十块钱。这套组合在国产高云FPGA、Xilinx Artix-7上也都能实现,逻辑代码思路完全通用。
2. 系统架构拆解:一台便携示波器的完整硬件链路
2.1 模拟前端:信号调理才是成败关键
很多DIY示波器只做数字部分,模拟前端随便拿个电阻分压就完事,这样出来波形往往不忍直视。示波器的第一个环节是模拟前端,它决定了你能测什么信号、测得多准。
输入级需要做三件事:
- 高阻输入,一般是1MΩ并联10pF左右电容,减少对被测电路的影响
- 幅度调理,把毫伏级到几十伏的信号,都映射到ADC输入范围(AD9288差分输入满量程约1Vp-p)
- 过压保护,防止被测信号太大把ADC烧掉
我做了三个档位:×1、×10、×100,用模拟开关切换分压电阻网络。最前面用一对BAT54S做输入钳位保护,把电压限制在0V到3.3V之内。这样就算误测12V电源也不会立刻报废芯片。
触发电平比较器我直接放在FPGA内部做,不额外加硬件比较器。方法很简单,在Verilog里持续判断采样值和触发电平寄存器的大小关系:上一个点小于电平、当前点大于等于电平,就认为捕获到一个上升沿。这样做的好处是不需要调电位器,触发电平可以在软件层面精确到1/256满量程。
这里有个经验之谈:模拟前端的元器件布局对信号质量影响极大。我第一次打板,为了追求小体积,把分压电阻和ADC输入脚拉得很远,结果波形上高频细节明显衰减,后来重新布局才好转。原则就是尽量缩短模拟信号路径,地线要短而粗。
2.2 FPGA选型和资源规划
FPGA选型不必追求高性能,够用就好。我的资源使用大概是这样:
| 模块 | 资源消耗 | 说明 |
|---|---|---|
| 采样捕获 | 约几十个LE | 只有寄存器和组合逻辑 |
| 异步FIFO | 1到2个M9K块 | 每块9Kbit,4096深度需要4到8块 |
| 触发逻辑 | 约百个LE | 比较器 + 状态机 |
| 显示驱动 | 约千个LE | SPI屏刷新控制 |
| PLL | 1个 | 产生几十MHz采样时钟 |
用Quartus Prime Lite开发,编译后资源占用不到50%,留了不少余量做扩展。如果第一次做,我建议直接用学习板的FPGA型号,比如黑金或小梅哥的EP4CE10开发板,外接一片AD9288模块,先把逻辑调通,再自己画PCB也不迟。
2.3 显示与人机交互:便携性从哪来
屏幕我选了3.5寸TFT彩色液晶屏,SPI接口,320×480像素。SPI屏功耗低、接线少,但刷新率有限,适合显示频率较低的波形。如果你想让波形刷新更流畅,建议用RGB接口屏幕加SDRAM做显存,但那对FPGA逻辑写码要求高不少。
屏幕只负责显示,人机交互靠一个旋转编码器加两个按键。旋转编码器用来调节时基(比如每格1us、10us、100us)和电压档位(每格0.1V、1V、10V),按键用来切换菜单、冻结波形。这些都是非常成熟的外设驱动,重点是逻辑上别搞得太复杂,保证易用即可。
还有一个非常实用的小方案:先不做屏幕,把采样数据通过串口发到电脑,上位机用Python画波形。这样能快速验证采集和触发好不好用,等整机逻辑没坑了,再投入精力搞屏幕。这个思路我在后面实操章节详细说。
2.4 电源树设计:小细节毁掉大项目
便携设备自然是USB供电,5V进来后分两路:
- 3.3V给数字电路:FPGA IO、ADC的数字电源、显示屏逻辑
- 1.2V给FPGA内核
ADC的模拟电源AVDD必须单独滤波。我一开始偷懒,AVDD和DVDD直接从同一个3.3V LDO出来,结果屏幕上波形噪声接近±1个LSB,怎么看怎么不舒服。后来改成AVDD用LP5907超低噪声LDO单独供电,波形立刻干净了很多。ADC的参考电压VREF也一定要加好电容滤波,它直接决定转换精度。
电源布局的一点心得:模拟电源和数字电源用磁珠或0欧电阻做单点连接,模拟地和数字地在ADC芯片底下汇合,别让大电流数字信号穿越模拟区域。这个原则和做音频DAC的电源布局是一样的。
3. 核心逻辑实操:从采样、FIFO、触发到时基显示
3.1 ADC采样逻辑:把并行数据稳稳抓进来
AD9288的工作模式分两种:单倍数据率(SDR)和双倍数据率(DDR)。SDR模式下每个采样时钟上升沿输出一组数据,DDR模式下上下沿各输出一组,速度翻倍但时序约束更紧。第一次做示波器,建议只用SDR模式,100MSPS已经足够。
采样时钟由FPGA的PLL产生,接到AD9288的时钟引脚,同时送入内部逻辑作为采样数据同步时钟。这里有个容易忽略的细节:ADC输出数据相对于采样时钟有一定延迟,FPGA采样时不能在时钟边沿卡得太紧,否则数据不稳定。我一般用PLL产生一个相位偏移45到90度的时钟来采ADC数据,或者用IO时序约束工具自动调整,保证数据稳定。
最基础的采样代码长这样:
module adc_capture( input wire adc_clk, // PLL输出的采样时钟 input wire [7:0] adc_data_a,// 通道A数据 input wire [7:0] adc_data_b,// 通道B数据 output reg [7:0] sample_a, output reg [7:0] sample_b ); always @(posedge adc_clk) begin sample_a <= adc_data_a; sample_b <= adc_data_b; end endmodule就这么简单。但要注意的是,AD9288并行输出可能有多根数据线,一定要对照数据手册检查引脚编号,接反一根就是满屏乱码。
3.2 跨时钟域与FIFO:别让亚稳态坑了你
采样时钟是100MHz级别,而屏幕刷新是几十Hz到几kHz,这中间有巨大的时钟鸿沟。你不可能让屏幕直接跟着100MHz的采样数据跑,必须把数据先存储起来,一帧存完,再慢慢刷到屏幕。这个过程就需要异步FIFO。
异步FIFO的核心难点是跨时钟域。写时钟域是采样时钟,读时钟域是显示时钟,两个时钟完全异步。处理不好就会读到半新半旧的数据,违反同步器规则还会造成亚稳态。亚稳态是个很抽象的概念,简单理解就是触发器在时钟边沿时刻没有采到稳定的数据,输出端进入一种非0非1的中间状态,然后随机向0或1塌缩。FPGA硬件设计中,跨时钟域信号不经过同步直接使用,就是埋雷。
我直接用Quartus的FIFO IP核,选择异步读时钟和写时钟,深度设为4096,足够存储一帧波形。IP核内部已经把格雷码跨时钟域同步做好了,比自己写安全得多。如果你是Xilinx环境就选FIFO Generator IP核,原理一样。
FIFO使用流程大概是:
- 触发条件到来时,通过写使能往FIFO里写采样数据
- 写入固定长度(比如一帧2048个点)后停止写
- 读侧按显示刷新率读数据,送给屏幕驱动
3.3 触发逻辑:示波器能不能稳住看这里
示波器如果只是傻乎乎地连续采样,你看到的波形会在屏幕上乱滚,这就是没有触发。触发的意思是让采样过程在某个特征点开始,这样才能一帧一帧稳定显示同一个波形。
最简单的触发逻辑是上升沿电平触发:
reg [7:0] prev_data; reg trig_flag; parameter [7:0] TRIG_LEVEL = 8'd128; always @(posedge adc_clk) begin if (prev_data < TRIG_LEVEL && adc_data_a >= TRIG_LEVEL) trig_flag <= 1'b1; else trig_flag <= 1'b0; prev_data <= adc_data_a; end这段代码的意思是:上一个采样点低于触发电平,当前采样点等于或高于触发电平,认为信号出现上升沿,产生一个触发脉冲。触发产生后,控制器开始写FIFO,存够一帧数据就去显示。
实际使用中还需要两个优化:
- 触发抑制(holdoff):触发一次后,暂停一段时间不再响应触发,防止抖动造成重复触发
- 触发电平调节:通过旋转编码器改变TRIG_LEVEL值,通常设成满量程的50%左右
触发不稳定是最常见的调试问题。我做第一版时,方波信号触发正常,换成不规则的串口信号就疯狂乱跳。后来加了触发抑制,观察立刻稳定下来。如果你遇到波形抖动、滚屏,先怀疑触发参数,别急着改采样率。
3.4 时基控制与波形映射
时基指的是屏幕上水平一格代表多少时间。时基换算公式:
采样点数量 = 采样率 × 时基 × 屏幕格数
比如采样率是40MSPS,屏幕显示10格,要显示每格10us,也就是整个屏幕宽100us,那么一帧需要4000个采样点。如果屏幕水平分辨率是320个像素,每个像素点对应12.5个采样点。这时有几种做法:
- 每12.5个点抽一个点,保留峰值和谷值
- 做平均,用均值填充
- 用最大值和最小值画包络,类似余辉显示
我建议做峰值包络,因为示波器本来就是为了找异常,平均反而把毛刺抹掉了。实现就是每N个采样点,取最大值和最小值,两个点画在同一列像素上。这样既能保证水平分辨率,不丢失尖峰毛刺,显示效果也好。
时基改变有两种思路:改变PLL输出采样时钟频率,或者在固定采样率下做抽取。前者适合慢信号长时基,后者适合快速切换,实际项目中两种方案可以同时存在。我为了省事,采用固定40MSPS采样率加抽取的方式,能覆盖1us到1s的时基范围,完全够用。
4. 从原理图到调通:完整实操过程与踩坑记录
4.1 准备工作:先把最小系统点亮
PCB打样回来后,不要急着把芯片全焊上去。我的习惯是分三步走:
第一步,只焊电源和FPGA最小系统,包括晶振、滤波电容、JTAG下载电路,用LED闪烁程序验证FPGA工作正常,PLL能锁定。FPGA的PLL锁定后有一个locked信号,最好拉到一个LED上,方便观察。
第二步,焊AD9288,接一个低频信号源,用万用表量ADC的电源是否正常,然后写一个最简单的采样程序,把采样值的高4位接到LED上,通过调电位器改变输入电压,观察LED变化,确认数据线没有接反。
第三步,接上显示屏或者串口,调整体逻辑。
这个阶段最常见的错误是FPGA引脚分配错误。你看着原理图明明接对了,但可能PIN分配时填错一个引脚号,结果数据输出完全混乱。所以一定要在Quartus里重新核对引脚约束文件(.qsf),用编译报告里的引脚表逐一确认。
4.2 先用数码管和串口验证采集
热词里总有人搜“fpga 实现数码管动态显示”“cd4511控制七段数码管fpga”,其实在调试示波器时这个思路特别实用。我第一版调试时,就是把采样值发到四位数码管显示。给ADC输入端加一个缓慢变化的直流电压,数码管上数据跟着变,说明ADC采集链路通了。然后输入1kHz正弦波,数码管刷新肉眼看不到具体波形,但至少能看出数据在跳动。
更推荐的做法是接串口。FPGA每采样一组数据,打包成帧,通过UART发到电脑。电脑上我用一个简单的Python脚本接收并画图:
import serial import matplotlib.pyplot as plt ser = serial.Serial('COM3', 921600, timeout=1) buf = [] while len(buf) < 1024: data = ser.read(1024 - len(buf)) for b in data: buf.append(b) plt.plot(buf) plt.ylim(0, 255) plt.show()当时在电脑屏幕上看到一串完整的正弦波曲线时,心里特别踏实。这个方法的价值在于剥离了显示问题,单独验证采集和传输链路。如果这一步波形都不对,先不要碰屏幕,问题一定在模拟前端或ADC采集逻辑上。
4.3 接上TFT屏幕:把波形画出来
串口验证通过后,我开始调TFT屏幕。屏幕驱动可以用SPI总线,速度比较慢,但代码简单。我做了一个显存区域,把一帧1024个采样点映射到240列像素上。刷新策略是:先清屏,再画波形点,再画网格。
画竖线很简单,每个采样点对应一列,在这一列上把幅值对应的像素点亮。但如果每列只点一个点,信号锯齿感很强。我采用的办法是先算好每一列的包络最高点和最低点,然后连续画一列垂直线条,这样波形看起来饱满得多。
这里给一个显示映射的公式:
横坐标 x = (采样点索引 / 总采样点数) × 屏幕宽度
纵坐标 y = 屏幕高度 - (采样值 / 255) × 波形显示区域高度
两个坐标都是整数除法,注意别让计算溢出,先用乘法再用除法,避免精度损失。
4.4 PCB布局和硬件细节的心得
打完第二次板子之后,我总结了几条PCB设计经验:
- AD9288模拟输入走线尽量短,不要在输入脚附近走数字时钟线或数据线
- FPGA和AD9288之间数据线长度尽量一致,等长设计有助于信号时序稳定
- 每一组电源引脚附近都放0.1uF和10uF两级退耦电容,ADC的AVDD和DVDD分开走线
- 晶振尽量靠近FPGA,晶振底下不铺地,减少寄生电容
- 探头输入BNC座的焊接点要和外壳地做好连接,屏蔽效果会好很多
另外,DIY示波器最容易让人忽略的是探头。普通示波器探头有10倍衰减,输入电容大约10到20pF,对高频信号有补偿作用。自制的示波器如果不加探头,直接用BNC线输入,效果也不错,但测量高频信号时要注意引入的负载电容。
5. 常见问题排查与实测性能评估
5.1 典型故障速查表
我调试过程中遇到的最典型问题,整理成了一张速查表:
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 屏幕上全是乱码 | 数据线接反、FPGA引脚分配错误 | 用数码管或串口逐位检查数据 |
| 波形毛刺大 | ADC模拟电源噪声、输入悬空 | 输入信号接地看底噪,换LDO滤波 |
| 波形抖动滚动 | 触发条件没满足、电平不对 | 调触发电平,增加触发抑制时间 |
| 波形错位断裂 | 异步FIFO读写时钟配置错误 | 检查FIFO IP核时钟设置,确认写入停止条件 |
| 显示刷新慢 | SPI屏刷屏速度限制 | 降低分辨率显示,或换并行RGB屏 |
| 采样值满偏或全零 | 输入信号超出ADC范围 | 检查模拟前端分压和钳位保护 |
| FPGA程序下载不稳定 | JTAG电源不稳、电平不匹配 | 检查下载器供电,USB线换短线 |
这些问题里,最诡异的是“波形偶发断裂一帧”。查了很久发现是FIFO写侧触发后,还没写够一帧就被读侧读空了。解决办法是加一个FIFO写满标志后再允许读,或者把FIFO深度做大。异步逻辑就是这么折磨人,看起来现象是显示问题,根因在存储控制逻辑。
5.2 实测性能:它能测什么,不能测什么
整机调好后,我用信号发生器做了几个简单测试:
- 输入1MHz正弦波,幅值调在1Vpp左右,波形显示正常,没有明显台阶感
- 输入10MHz方波,上升沿能看到完整过程,但边沿有一定平滑,说明带宽够用但不算富余
- 输入STM32的UART信号(115200波特率),触发稳定,能看到每个字节的起始位和数据位
- 测3.3V电源输出,底噪在1个LSB左右,能看清纹波的大致幅度
这套配置的定位很清楚:便携、低成本、适合现场快速查看信号,不能指望它替代几万一台的宽带示波器。8bit分辨率约等于48dB动态范围,看数字信号和电源纹波完全够用,但想分析音频谐波、射频包络这些,垂直分辨率就不太够了。ADC满量程只有256级,小信号测量误差会比较明显。
一句话总结性能边界:20MHz带宽以内的调试场景,它都能应对;超过这个范围,建议换更高性能的ADC。
5.3 后续升级路线:从示波器到多功能仪器
做完这台示波器,你可以顺着几个方向继续升级:
- 增加信号发生器功能。FPGA里做一个DDS模块,输出正弦波、方波、三角波到DA芯片,示波器和信号发生器二合一,测试时免去外接仪器
- 升级ADC。换成12bit、250MSPS的串行LVDS接口ADC,虽然逻辑复杂不少,但可以对付更高频信号
- 加逻辑分析仪通道。并行数据采样是FPGA的强项,加几个输入通道就能抓数字总线,调试协议非常方便
- 把上位机做成虚拟示波器。通过USB或以太网把采样数据传到PC,用上位机做显示和存储,硬件就可以做得更小
- 集成电池和触摸屏,进一步提升便携性
顺便提一句,FPGA里有很多相关方向可以继续深入,比如LVDS高速接口、MIPI摄像头接收、ISP图像处理、以太网通信等。如果你做完了示波器还想找练手项目,这些方向都是很好的下一步。
6. 一些关于学习和实践的心里话
说点掏心窝子的体会。做这个项目之前,我FPGA的底子属于“能写流水灯、能跑个状态机”的水平。对采样定理、FIFO、跨时钟域这些概念,书上都看过,但理解停留在做题层面。这次从零做示波器,才真正把这些概念变成每天都得打交道的东西。比如握手信号、亚稳态、异步FIFO的格雷码处理,在示波器里全是实际需求,不是课本上抽象的名词。
调试过程中最深的感悟是:很多问题不是逻辑写错,而是时序关系没理清。比如信号到来和时钟边沿差那么几个纳秒,触发就采不到;FIFO的读时钟没稳定,数据就乱。FPGA调试跟软件调试的最大不同,就是你不能只盯着代码看,得同时看时钟、看数据、看复位时序。
如果再让我做一次,我会在立项阶段就先想清楚屏幕方案。第一次做的时候,我把大量时间花在调试SPI屏幕刷新上,后来发现这部分完全可以先放一放,用串口上位机验证更高效。等采集和触发都稳定了,再做屏幕就是水到渠成的事。
这篇文章写下来的所有代码和思路,其实都是围绕一个核心问题展开的:怎样把一秒钟一亿次的采样,变成屏幕上一条稳定的、能触发、能看懂幅值的波形曲线。这个问题理解透了,示波器也就做出来了。如果你也正打算用FPGA做点有实际用途的东西,这个项目绝对是回报率最高的选择之一。材料不贵,逻辑不算太难,成就感却非常足。