基于树莓派Pico的简易示波器:低成本便携信号观测方案

📅 2026/7/29 13:44:50
基于树莓派Pico的简易示波器:低成本便携信号观测方案
1. 项目概述与核心价值最近在捣鼓一些嵌入式的小项目调试I2C、SPI信号时手头那台笨重的台式示波器搬来搬去实在不方便。于是萌生了一个想法能不能用我抽屉里吃灰的树莓派Pico自己动手做一个便携、够用的简易示波器这个想法听起来有点“野”但仔细一想Pico那颗RP2040芯片双核ARM Cortex-M0主频133MHz内置ADC还有可编程的PIO可编程IO这个“外挂”硬件底子其实不差。更重要的是它成本极低体积小巧如果能实现那就是一个可以揣进口袋的调试利器。这个“Pico示波器”项目核心目标不是去挑战专业仪器的性能指标而是解决一个非常具体的痛点为电子爱好者、嵌入式开发者提供一个低成本、便携的实时信号观测工具。它适合用来观察数字信号的时序比如调试UART、I2C的起始位、测量PWM占空比、粗略查看模拟信号如传感器输出的波形和频率。你不需要用它去测几百MHz的高速信号也不需要追求极高的垂直精度但对于大多数单片机级别的数字电路调试和简单的模拟信号观察它完全能胜任。我花了大概两周时间从硬件连接到软件编写从底层采样到上位机显示完整地走通了这个流程。最终实现了一个带宽约500kHz理论值实际受多重因素限制、实时刷新的双通道示波器。下面我就把这个从零搭建的过程、踩过的坑以及一些性能优化的心得毫无保留地分享出来。2. 硬件设计与核心思路拆解2.1 为什么是树莓派Pico选择Pico作为核心是基于几个非常现实的考量。首先是成本与普及度Pico的价格让这个项目几乎没有门槛。其次是RP2040的独特架构其内置的12位ADC虽然只有500kSPS每秒采样点数的吞吐率且实际有效位数ENOB可能只有9位左右但对于音频范围20kHz以内的信号观测已经足够。最关键的是它的PIO可编程输入输出和DMA直接内存访问组合这构成了我们实现实时、不间断采样的技术基石。传统的单片机ADC采样需要CPU参与每一次的“启动转换-读取数据-存储”循环这会占用大量CPU时间并且采样间隔不稳定。而我们的方案是利用PIO编写一个精确的“采样时钟发生器”触发ADC转换转换完成后通过DMA自动将数据搬运到指定的内存缓冲区CPU仅在缓冲区满或需要处理数据时介入。这样采样过程几乎完全由硬件自动完成CPU被解放出来专注于波形处理、通信和显示从而实现了更高的实时性和更稳定的采样率。2.2 输入电路设计安全第一性能第二直接把信号接到Pico的GPIO上是极其危险的做法。Pico的GPIO引脚耐受电压是3.3V超过此电压很可能永久损坏芯片。因此一个简单但必要的输入保护与调理电路是必须的。我的设计思路是衰减、限幅、偏置。对于期望测量0-3.3V的信号一个最简单的方案是使用电阻分压网络。例如如果你想测量0-5V的信号可以用一个10kΩ和20kΩ的电阻串联信号从10kΩ电阻上端输入从两个电阻中间点输出到Pico的ADC引脚这样就能将5V衰减到约3.33V。但更通用和安全的做法是使用运放构建一个衰减器与加法器。这里我采用了单电源供电的轨到轨运放如MCP6002。电路核心功能有两个衰减与电平移位通过电阻网络将输入信号比如±5V衰减并抬升到0-3.3V范围内确保信号始终落在ADC的量程内。过压保护在运放输入端和输出端到电源轨之间并联肖特基二极管如BAT54S利用其正向导通电压约0.3V将电压钳位在GND-0.3V 到 3.3V0.3V 之间为ADC引脚提供最后一道保护。注意这个输入电路直接决定了示波器的输入阻抗和带宽。使用运放缓冲可以轻松实现高输入阻抗1MΩ以上减少对被测电路的影响。但运放本身有增益带宽积GBP的限制选择GBP较高的运放如10MHz以上对保证几百kHz的带宽至关重要。2.3 双通道与触发电路构思Pico有3个ADC引脚GP26, GP27, GP28我们至少可以实现双通道输入。在软件上我们可以让两个通道交替采样但这会使得每个通道的实际采样率减半。更高级的做法是利用RP2040的ADC有一个多路复用器配合DMA在单个采样周期内快速切换通道但这需要更精细的时序控制。触发是示波器的灵魂功能它让波形稳定显示。在硬件上我们可以预留一个GPIO作为外部触发输入。在软件层面实现触发逻辑持续采样并将数据存入一个环形缓冲区当检测到信号满足触发条件如上升沿穿越某个电压阈值时记录下这个点然后将触发点前后一段时间的数据发送给上位机显示这样每次都能在波形的同一位置开始显示图像就稳定了。3. 固件开发PIO与DMA的魔法这是整个项目的软件核心目标是在Pico上实现一个高效、稳定的数据采集系统。3.1 PIO程序精准的采样时钟我们用一个PIO状态机来生成周期性的脉冲这个脉冲的上升沿将作为ADC转换的启动信号。PIO的指令执行速度是系统时钟的一个分频我们可以通过分频值精确控制采样间隔。下面是一个简化的PIO汇编程序示例它每隔N个时钟周期拉高一个引脚作为采样触发信号一个时钟周期.program adc_trigger .side_set 1 opt ; 声明side-set操作用于控制输出引脚 .wrap_target pull noblock ; 从TX FIFO读取采样间隔计数值如果FIFO空则使用OSR旧值 mov x, osr ; 将计数值移动到X寄存器 set pins, 1 [1] ; side-set: 拉高触发引脚并延迟1个周期 set pins, 0 ; side-set: 拉低触发引脚 label loop jmp x-- loop ; 循环X次实现N个周期的等待 .wrap在C语言主程序中我们需要配置这个PIO状态机并将计算好的分频值和循环次数推送到它的TX FIFO以设定我们想要的采样率。例如对于1MHz的采样率每个采样周期需要133个系统时钟133MHz / 1MHz 133。我们需要将这个值换算成PIO程序中的循环次数。3.2 ADC与DMA配置构建自动化的数据流水线RP2040的ADC API使用起来相对简单但关键在于将其与DMA联动。初始化ADC选择输入通道GP26/27/28设置ADC时钟分频影响转换速度通常设为最快。配置DMA通道这是关键一步。我们需要配置一个DMA通道其传输请求DREQ设置为DREQ_ADC。这意味着每当ADC完成一次转换产生一个数据就绪信号DMA就会自动执行一次传输将ADC结果寄存器adc_hw-fifo中的数据搬运到我们预先定义好的内存数组采样缓冲区中。启动循环将DMA配置为循环模式当它把目标缓冲区填满后会自动回到起点重新开始填充并可以产生一个中断通知CPU。这样我们就拥有了一个被ADC数据持续刷新的“环形缓冲区”。// 简化的代码逻辑 uint16_t sample_buffer[BUFFER_SIZE]; dma_channel_config c dma_channel_get_default_config(dma_chan); channel_config_set_transfer_data_size(c, DMA_SIZE_16); // ADC结果是12位用16位存储 channel_config_set_read_increment(c, false); // 始终从ADC FIFO地址读 channel_config_set_write_increment(c, true); // 写入到递增的内存地址 channel_config_set_dreq(c, DREQ_ADC); // 数据请求来自ADC channel_config_set_ring(c, true, RING_SIZE); // 可选设置环形写入方便管理 dma_channel_configure(dma_chan, c, sample_buffer, // 写入地址 adc_hw-fifo, // 读取地址ADC FIFO BUFFER_SIZE, // 传输次数 true // 立即启动 );3.3 触发与数据封包逻辑采样数据源源不断地流入环形缓冲区。我们需要一个“触发检测”线程可以跑在第二个核心上来监视这个缓冲区。它实时检查新到来的数据是否满足触发条件例如上一个点低于阈值当前点高于阈值即为上升沿触发。一旦触发事件发生就记录下触发点在缓冲区中的位置。当上位机比如PC请求一帧数据时固件程序就从触发点之前某个位置开始提取一段固定长度的数据比如1024个点通过USB CDC虚拟串口或USB Bulk Transfer更快发送出去。为了减少通信开销和便于解析我会将数据打包成带有帧头、通道信息、采样率、数据长度和校验和的小数据包。4. 上位机软件波形显示与交互下位机负责“采”上位机负责“显”和“控”。我选择了使用Python和PyQt5来开发上位机因为快速原型开发能力强库丰富。4.1 通信协议解析上位机通过USB串口与Pico通信。我定义了一个简单的二进制协议命令帧从上位机发送到下位机用于控制采样率、触发模式、垂直灵敏度等。数据帧从下位机发送到上位机包含一帧波形数据。数据帧的结构例如[帧头0xAA 0x55] [通道号] [采样率低字节] [采样率高字节] [数据长度低字节] [数据长度高字节] [数据...] [校验和]。上位机需要按照这个格式解析字节流还原出原始的ADC数值数组。4.2 基于PyQtGraph的实时绘图PyQtGraph是专门为科学计算和实时数据可视化设计的库性能远超Matplotlib。它的核心是使用Qt的GraphicsView框架和OpenGL进行加速渲染。基本流程如下创建绘图窗口和曲线对象使用pg.PlotWidget创建画布使用pg.PlotCurveItem创建曲线对象。将曲线对象的setData方法替换为自定义的、更高效的数据更新方法或者直接更新其内部数据数组。数据接收线程开辟一个独立的QThread专门负责从串口读取数据、解析协议。这个线程不能直接更新UI。信号与槽更新数据线程解析完一帧数据后通过PyQt的信号Signal机制将数据数组发送给主UI线程。主线程中的槽函数Slot接收到数据后去更新曲线对象的数据并调用update()方法请求重绘。性能优化双缓冲/环形缓冲区在内存中维护两个数据缓冲区一个用于接收线程写入一个用于绘图线程读取通过指针交换避免锁竞争。降频显示如果采样点太多比如100k点屏幕像素点有限全部绘制反而卡顿。可以在绘图前对数据进行抽点Decimation或峰值检测Peak Detection只抽取关键点进行绘制既能保留波形特征又能极大提升渲染速度。关闭抗锯齿实时绘图时关闭曲线的抗锯齿可以显著提升性能。4.3 基础示波器功能实现在UI上我模仿了传统示波器的布局时基控制Time/Div改变水平方向每格代表的时间。上位机发送命令给Pico调整采样间隔同时调整绘图时的X轴范围。垂直灵敏度控制Volts/Div改变垂直方向每格代表的电压值。这主要在上位机进行标定和缩放。需要知道ADC的参考电压通常是3.3V和输入电路的衰减比。例如ADC满量程4095对应3.3V如果垂直灵敏度设为1V/div那么屏幕上8个格子假设对应8V就需要将ADC数值(adc_value / 4095 * 3.3 * 衰减系数)进行换算。触发设置UI上提供触发模式自动、正常、单次、触发边沿上升、下降、触发电平一个可拖动的水平线设置。这些参数被封装成命令帧发送给下位机。测量功能实现电压Vpp, Vavg、频率、周期、占空比的自动测量。这完全在上位机通过分析接收到的波形数据数组来实现。例如找出一段数据中的最大值和最小值相减得到Vpp通过寻找过零点的时间间隔来计算频率。5. 系统集成、测试与性能优化5.1 整合与调试将焊接好的输入调理板与Pico连接上电烧录固件启动上位机软件。第一次连接往往不会一帆风顺。首先确保USB通信正常上位机能识别到串口并能打开。然后发送一个读取版本号的简单命令测试命令-响应机制是否通畅。接着开始测试数据流设置一个较低的采样率如1kSPS让Pico持续发送正弦波测试数据可以用另一个GPIO生成PWM再经RC滤波产生观察上位机是否能稳定绘制出正弦波。5.2 实际性能测试与瓶颈分析理论带宽500kHz但实际能达到多少我用一个信号发生器输出一个干净的正弦波逐步增加频率观察屏幕上波形的幅度衰减和形状失真。在100kHz以下波形显示良好幅度准确。达到300kHz波形幅度开始有轻微衰减-3dB左右形状仍可辨认。超过500kHz幅度衰减严重波形严重失真方波上升沿变得圆滑。瓶颈主要来自以下几个方面ADC本身性能RP2040的ADC在500kSPS时有效位数下降。输入电路带宽运放和PCB布局引入的寄生电容会形成低通滤波器限制高频信号。采样策略如果是双通道交替采样每个通道的有效采样率减半根据奈奎斯特采样定理能无失真重建的信号频率也减半。软件开销数据打包、USB传输、上位机解析和绘图每一个环节都有延迟。特别是USB虚拟串口CDC模式吞吐量和实时性有限。5.3 优化手段实录针对这些瓶颈可以尝试以下优化提升USB传输效率放弃CDC使用RP2040的USB Bulk Transfer端点。这需要在上位机安装特定的USB驱动如libusb但数据传输速率和实时性会大幅提升。增加数据压缩。对于变化缓慢的信号可以使用差分编码或简单的游程编码减少需要传输的数据量。优化采样策略单次突发高速采样对于重复性信号可以利用Pico的264KB SRAM设置一个巨大的缓冲区以最高速采样一段很短的时间然后慢慢上传。这可以捕捉瞬态信号。等效采样对于周期性高频信号如果触发非常稳定可以采用多次触发、每次触发后延迟不同时间进行采样的方式用多个周期拼凑出一个高密度采样的波形。这能突破ADC采样率的限制测量比采样率高得多的信号频率但只适用于稳定周期信号。上位机绘图优化使用PyQtGraph的setData时传入autoDownsampleTrue参数让其自动进行优化抽点。对于固定网格的示波器界面可以预计算好网格线和背景避免每次重绘都重新计算。6. 常见问题、排查与进阶玩法6.1 问题速查表问题现象可能原因排查步骤上位机无连接USB驱动问题、线缆问题、固件未运行1. 检查设备管理器是否有未知设备或CDC串口。2. 换USB线换USB口。3. 按住Pico的BOOTSEL按钮上电看是否出现U盘确认芯片正常。能连接但无波形命令格式错误、采样未启动、触发条件不满足1. 用串口助手手动发送命令看是否有回复。2. 检查固件中ADC和DMA初始化是否成功。3. 将触发模式设为“自动”AUTO。波形严重噪声电源噪声、输入悬空、接地不良1. 给Pico的3.3V和GND之间并联一个10uF和0.1uF的电容。2. 确保信号源和Pico共地。3. 不测量时将输入端子短接到地看基线是否平稳。测量电压不准ADC参考电压不准、输入电路分压比误差、软件标定错误1. 用万用表测量Pico的3.3V引脚实际电压修正代码中的参考电压值。2. 测量输入电路的实际分压比。3. 输入一个已知的精确电压如1.65V查看ADC读数并计算标定系数。高频信号幅值衰减输入电路带宽不足、采样率不足1. 检查运放型号的增益带宽积是否足够。2. 尝试减少输入电路的串联电阻或使用更小的滤波电容。3. 确认当前设置的采样率是否满足奈奎斯特定理大于信号频率2倍。6.2 从“能用”到“好用”的进阶技巧软件触发与高级触发除了基本的边沿触发可以实现脉宽触发捕捉特定宽度的脉冲、欠幅触发捕捉幅度不足的脉冲等这对分析数字通信总线异常非常有用。FFT频谱分析利用上位机的计算能力如NumPy的FFT函数对采集到的时域波形做快速傅里叶变换在另一个窗口显示频谱。这样你的简易示波器就升级成了频谱分析仪可以分析信号的谐波成分。协议解码针对I2C、SPI、UART等常见协议可以在上位机添加解码功能。波形显示区域上方用不同颜色的标记条直接显示出解码出的数据字节、地址、起始/停止位等极大提升调试效率。持久化与分享添加截图、保存波形数据CSV格式、加载历史波形等功能方便记录和分享调试结果。6.3 个人实操心得这个项目最有意思的部分不是最终的结果而是不断逼近性能极限和解决问题的过程。有几点深刻的体会首先硬件是基础软件是灵魂。一个糟糕的输入电路会让软件的所有努力白费。在焊接调理板时一点点的接地环路或电源噪声都会被ADC灵敏地捕捉到。使用星型接地、给每个IC的电源引脚就近放置去耦电容这些教科书上的原则在高速采样时至关重要。其次要善用数据手册和官方例程。RP2040的数据手册和Pico C/C SDK的示例代码是宝藏。DMA和PIO的配置看起来复杂但SDK中的dma和pio例子几乎提供了所有需要的模板。我的大部分时间其实是花在理解这些例程并将它们组合起来。最后分层调试步步为营。不要试图一口气写完所有代码。我的步骤是先写一个最简单的ADC轮询采样通过串口打印数据验证硬件通路。然后加入PIO控制采样节奏。再引入DMA实现自动搬运。接着实现环形缓冲区和触发逻辑。最后才完善通信协议和上位机。每完成一步都进行充分的测试这样当问题出现时你很容易定位到是哪个新引入的环节导致的。这个用树莓派Pico制作的示波器最终性能可能比不上几百元的二手货但它在制作过程中带给你的对ADC、DMA、实时系统、数据可视化的理解是任何现成仪器都无法给予的。它已经成功帮我调试了好几个ESP32和STM32项目中的传感器通信问题作为一个随手可用的“信号侦探”完全超出了我最初的预期。如果你手头也有块Pico在吃灰强烈建议你尝试一下这个项目它绝对能让你对嵌入式硬件和信号处理有一个全新的认识。