
简介基于安卓手机的蓝牙示波器方案包含完整程序源码与硬件原理图面向安卓开发者和电子DIY爱好者旨在把手机变成便携波形显示终端降低示波器使用门槛适合实验室测试、课堂演示、现场调试等场景。资源共8个文件含5个Java源文件、可直接安装的安卓示波器APK、硬件电路图JPG以及打包好的dsPIC底层固件整体仅115KB目录结构清晰便于按模块查看。已有2374人学习下载。借助配套资源读者可完整了解蓝牙无线传输在测量仪器中的实现路径从信号采集、ADC模数转换、dsPIC处理到蓝牙发送与安卓端解析绘制波形。作为开源方案可参照电路图自制或改装采集硬件并根据需求修改Java源码与固件例如提高ADC分辨率以提升精度或探索用Wi-Fi替代蓝牙提升传输速率为二次开发和创新实验打下基础。安卓手机用蓝牙示波器从采集端到App显示一个能直接复刻的完整方案去年帮朋友调一块电机驱动板手边没有台式示波器临时用STM32最小系统板加HC-05蓝牙模块做了个无线示波器配合自己的安卓手机显示波形居然把PWM毛刺和串口噪声都抓出来了。后来我把这个方案整理成一套完整资料——包含安卓App源码、单片机固件程序和硬件原理图这里把整个设计过程、关键参数和踩坑记录都分享出来想动手做的同学可以直接按这篇文章复刻整体成本控制在50元以内。这套方案解决的核心问题是在没有台式示波器的场景下用手机实现基本的电压波形观测、频率测量和触发功能。适合电子爱好者、嵌入式开发者和实验室学生尤其是需要隔离观察、或者被测设备不方便用USB连电脑的场合。我在本文里会把硬件原理、固件协议、安卓App显示逻辑、以及我在实际调试中遇到的典型问题全部讲清楚不吹不黑能做什么、做不到什么都摊开给你看。1. 方案总体设计为什么是“MCU采集 经典蓝牙 安卓App”1.1 架构选型背后的三个关键思考先说说整体架构。市面上手机示波器方案主要有三种路线第一种用耳机孔或USB OTG传输数据优点是速度快但接线麻烦而且手机OTG口被占用后没法同时充电第二种用Wi-Fi传输比如ESP32配合手机App速度上限高但配对流程和功耗都比蓝牙复杂第三种就是我这个方案MCU采集数据后通过蓝牙串口SPP发给手机手机端直接解析波形。我之所以选蓝牙核心原因是你调试电机、电源这类设备时经常需要和被测电路保持一定距离尤其是测高压或大电流回路时无线隔离能避免很多安全问题。至于为什么选经典蓝牙而不是BLE后文会详细解释。在MCU选型上我用了STM32F103C8T6这颗“蓝药丸”核心板。选它的理由有三个一是12位ADC采样率最高能做到1Msps足以覆盖大部分低频和音频范围内的调试二是DMA、定时器、串口这些外设齐全做采集和转发不需要额外逻辑芯片三是生态资料极多就算你之前没用过ST芯片照着参考代码也能很快上手。这套方案你完全可以替换成GD32、ATSAMC21等等只要ADC和串口资源匹配就行。1.2 为什么用经典蓝牙SPP而不是BLE这是我在设计时认真纠结过的问题。BLE蓝牙低功耗在安卓开发中被广泛使用但BLE的数据传输是面向ATT/GATT的单次通知包默认只有20字节MTU为23字节就算协商扩大MTU后单包最多也就244字节左右而且BLE的实际吞吐量受连接间隔、系统调度影响很大实测稳定吞吐量通常在几十KB/s到一两百KB/s之间。对于12位ADC连续采样来说每2字节一个样本这远远不够。经典蓝牙SPP串口仿真协议就完全不一样。它把串口数据流映射到蓝牙协议栈对开发者来说就是一个透明的“无线串口”吞吐量最高可以达到2Mbps的射频速率实际有效数据速率在460800波特率下能稳定跑到约40KB/s左右。对于示波器来说这相当于每秒能传输约2万个12位采样点足够把有效带宽撑到几kHz以上用来观测PWM、音频信号、电源纹波已经够用了。HC-05模块就是经典蓝牙SPP的典型代表你买到手上的模块默认已经烧好固件直接串口透传。如果你想省成本也可以用HC-06从机模式但HC-06不支持主机模式功能要少一些。我建议新手直接买HC-05原因后面调试部分会讲到。2. 硬件电路设计从输入保护到蓝牙天线的完整细节2.1 信号输入前端衰减、阻抗匹配和保护示波器不是直接把信号怼进单片机引脚的。先看输入前端电路这是整套方案中最容易做错、也最影响测量精度的地方。完整的输入级由三部分组成分压衰减网络、电压跟随器、钳位保护。电压跟随器我用了一片LMV358双运放它对轨到轨输出工作电压3.3V带宽约1MHz用在低频示波器上比较合适。如果只是测0~3.3V、阻抗要求不高的信号运算放大器这一级也可以省掉直接在MCU引脚前串一颗1kΩ电阻但如果你要测高阻抗信号源就必须加跟随器做缓冲否则ADC采样瞬间的输入电流会影响被测电路的工作点。分压网络我设计了三挡直通0~3.3V、10:1衰减0~33V、100:1衰减0~330V。挡位用拨码开关或跳线帽切换这样简单可靠不会像电子开关那样引入额外导通电阻。分压电阻选用1%精度两个电阻的阻值经实际测量后我会在App里做软件校准。很多同学直接按标称值算结果测出来的电压总是偏差5%以上原因就在这里。钳位保护电路用两颗肖特基二极管BAV99分别接到3.3V电源和地把输入电压钳位在-0.3V到3.6V之间防止高压误接或静电把MCU的ADC引脚打坏。我实测过偶尔碰到一次反接或过压有钳位保护和没有完全是两个结局。2.2 MCU最小系统与采样电路ADC引脚的抗干扰处理STM32F103C8T6核心板本身已经包含了晶振、复位、USB转串口和稳压电路原理图上这部分可以直接复用不用自己重新设计。但有几个关键点是很多人忽略的ADC参考电压VREF在核心板上通常直接连3.3V这意味着ADC满量程精度受LDO纹波影响很大。我实测核心板上的AMS1117在蓝牙模块瞬时发射时纹波能到50mV以上反映到ADC读数就是来回跳几个字。解决方法是给ADC供电和参考电压加一级LC滤波在VREF引脚前串联一颗10Ω电阻再接一颗10μF钽电容和0.1μF陶瓷电容到地。别小看这一级滤波它能把参考电压纹波压到5mV以内波形显示明显稳定。如果对精度要求更高可以外接一颗REF3033基准电压芯片把ADC参考电压做成稳定的3.3V这样测量误差能控制在0.5%以内。ADC采样引脚我这里用的PA1ADC1通道1在进入MCU前接了一颗RC低通滤波器R100ΩC10nF截止频率约160kHz用于抗混叠。注意这个RC值是根据最大采样率算出来的不要为了“稳定”盲目加大电容否则会压低带宽高频信号会被滤掉。2.3 蓝牙模块电路与电源设计3.3V供电和天线的布局经验HC-05蓝牙模块的电路很简单VCC接3.3VGND接地TXD接STM32的USART1_RXPA10RXD接USART1_TXPA9。这里有个关键点HC-05的逻辑电平标称是3.3V但很多生产厂商的模块实际上是电平不兼容的如果你发现通信不稳定或乱码在RXD和TXD上各串一个1kΩ电阻做电平分压问题基本能解决。电源设计是我在整个项目中踩得最深、也最值得分享的坑。HC-05在配对和发射瞬间的电流峰值可以达到40~50mA如果电源内阻大或者走线细瞬间压降会让MCU复位表现就是“一搜波形手机App就断”。我在原理图上专门给蓝牙模块加了一颗100μF电解电容并联0.1μF陶瓷电容电容尽量靠近模块的VCC引脚。PCB上蓝牙模块的天线区域正下方不要铺铜四周留出至少5mm的净空不然通信距离会从10米掉到2米以内。有人问过我要不要用锂电池直接供电我的建议是如果你测的信号源和电池共地那无所谓如果被测设备有自己的电源系统尽量让示波器完全独立供电否则地回路会引入工频噪声。我通常用一个手机充电宝加稳压板或者两节18650电池加DC-DC降压到3.3V。3. 固件设计ADC采样、触发逻辑与蓝牙透传的完整数据链路3.1 ADC与DMA如何保证采样不发生波形“撕裂”说一下MCU固件这边的代码结构。我的固件用STM32标准外设库写的整个逻辑分三层采样层、协议层、传输层。采样层用ADC1的规则通道组设置成扫描模式由定时器TRGO信号触发启动转换。我这里用TIM2产生1kHz到100kHz可调的触发频率每次触发ADC转换一次。为什么要用定时器触发而不是连续转换模式因为连续转换模式对CPU介入的要求高而且在分时处理蓝牙数据时容易丢失部分转换结果波形就会“撕裂”。用定时器触发采样时刻精确可控而且和蓝牙传输可以并行。ADC转换结果不走中断而是通过DMA循环写入缓冲区。缓冲区设计成4KB大小即2048个16位样本12位数据存在高12位。DMA配置成循环模式半传输中断和全传输中断分别产生标志位。固件里的思路是DMA每填满半区就通知协议层把已经填好的一半打包发送在发送的同时DMA继续往另一半缓冲区写入新数据。这种双缓冲机制能最大限度规避“边采边发”的冲突实际跑下来在低速采样时几乎不丢点。3.2 触发逻辑与数据帧格式不是简单地把ADC数据扔出去如果只是把数据一股脑发出去你会发现波形在手机屏幕上像心电图一样乱跳——因为没有触发。示波器的意义在于重复信号的稳定显示所以固件里必须实现上升沿或下降沿触发。触发实现并不复杂在正常采集前先把ADC结果和触发阈值比较阈值来自App下发的参数例如1.5V换算成12位ADC码值约1861。连续采样直到检测到连续若干个点都满足“前一时刻低于阈值、当前时刻高于阈值”的条件判定触发成立然后才开始把触发点前后的一部分数据打包上传。我在固件里设置了“预触发深度”默认保存触发前128个样本和触发后1920个样本这样能把波形起始处的跳变也显示出来。数据帧格式我做了详细设计保证安卓端解析时能快速定位帧边界、发现丢包字段长度字节说明帧头2固定为0xAA 0x55数据长度1有效数据字节数通道标识1固定0x01采样间隔2单位μs供App计算时间轴数据内容N每个样本2字节大端校验和1累加和取低8位帧头0xAA55加上校验和是保证波形同步的关键。安卓端如果没有在预期位置找到帧头会丢弃当前字节并重新搜索从而自动恢复同步。这里有个细节采样间隔字段在设计时留了很大余量实测发现不同蓝牙模块的传输延迟波动能达到几十毫秒如果只在App端猜采样率波形的时间轴会严重失真所以固件每次发送帧头后都会用固定值在帧中打上时间戳参考。不用担心这套处理整体开销很小。3.3 波特率选择460800是这个方案的甜点值串口波特率直接决定了无线示波器的带宽上限。HC-05出厂默认是9600波特率算下来每秒最多传约960字节也就是480个ADC样本波形显示会非常卡。我通过AT指令把波特率设置到460800AT指令序列为进入AT模式按住模块按键上电→ 发送ATUART460800,0,0→ 重新上电。实测在460800波特率下串口有效数据率约44KB/s对应约22000个样本/秒。考虑到帧头、校验等开销实际有效采样点约19000个/秒。这时如果使用20kHz采样率单帧2048个样本刚好可以实时传输。我还试过921600波特率成功率不稳定有些HC-05模块到900k以上就会出现误码率骤增所以最终定在460800这是兼顾稳定性和速度的甜点值。要注意的是我们这里说的“采样率”是指ADC转换频率而最终能传到手机的“有效更新率”还需要考虑蓝牙带宽。所以我把采样率上限设成了100kHz但在100kHz采样率下1秒钟的数据需要约4~5秒才能传完App端会显示“延迟缓冲”模式而不是实时刷新。实际用于实时观察时建议使用不超过20kHz采样率。4. 安卓App实现蓝牙连接、波形绘制与交互控制的完整流程4.1 蓝牙权限与连接逻辑安卓版本兼容处理安卓App这部分可能是很多人最心虚的地方。我用纯Java写的原生安卓应用不依赖第三方蓝牙库这样你能完全理解每一步在干什么。整体代码量并不大一个用于蓝牙连接的服务类一个用于解析数据的线程一个用于绘制波形的自定义View再加几个设置控件。首先处理权限。安卓6.0以上需要申请定位权限因为蓝牙扫描被归入定位范畴安卓12以上还需要显式声明BLUETOOTH_CONNECT和BLUETOOTH_SCAN权限。在AndroidManifest.xml中除了声明权限还要在代码里动态申请尤其是定位权限如果不申请startDiscovery()会直接抛异常或返回空列表。连接流程上我用了传统SPP连接的推荐方式通过BluetoothAdapter.getBondedDevices()获取已配对设备列表找到目标模块的MAC地址后用UUID.fromString(00001101-0000-1000-8000-00805F9B34FB)发起连接。这个UUID是SPP服务的固定服务号不能写错。连接建立后用独立线程循环读取输入流并将读到的字节存入环形缓冲区解析线程从缓冲区取数据。记住两个原则不要在UI线程操作蓝牙I/O不要用阻塞式读流在主线程里等数据。4.2 波形绘制如何把数据点变成“像样的示波器画面”波形绘制用的是自定义View重写onDraw()方法配合SurfaceView做高刷新率显示。这里要重点说两个细节因为它们直接决定使用体验。第一是适配不同分辨率。手机屏幕分辨率各不相同直接按像素绘制的波形在不同手机上会忽大忽小。我的做法是在onSizeChanged()里先获取View的实际宽高然后按16:9的比例计算波形显示区域用canvas.translate()把原点移到显示区域左上角之后所有坐标都用归一化的方式计算。这样在1080P到2K屏幕上显示效果基本一致。第二是Y轴量程切换。App端设置1V/格、2V/格、5V/格等挡位时后端按比例缩放ADC码值。例如硬件为10:1衰减且VREF3.3V那么1个ADC码值对应电压为3.3/4096×10 ≈ 8.06mV。在App里我维护了一个calibrationFactor变量正式使用前先用万用表测量一个2V标准电压然后输入实测值让App自动计算校准系数。这一步骤很多人会跳过但如果你做过对比就会发现校准后显示的电压误差可以从5%缩小到1%以内。波形刷新策略上我不建议每次收到数据就立刻重绘那样会把UI线程累死。我在解析线程中维护一个 “待绘制帧” 对象通过Handler每50ms通知UI线程刷新一次20帧/秒。实测下来这样既流畅又省电。如果使用SurfaceView可以直接在子线程里加锁画布能再省一层消息传递的开销。4.3 App里的触发参数与交互控件简单但够用交互控制这块我的App主要提供了5个控件一个“连接/断开”按钮连接后自动切换为红色断开状态。一个时间挡位Spinner支持1ms/格、5ms/格、10ms/格、50ms/格对应采样率跟随变化。一个电压挡位Spinner支持0.5V/格、1V/格、2V/格、5V/格、10V/格。一个触发模式下拉框可选择上升沿/下降沿/自动。一个“单次/连续”切换按钮用于捕捉瞬态信号。这些控件和固件的参数联动逻辑是App每次参数变更都会通过蓝牙发送一条自定义控制命令格式为CMD_... 参数值 换行符。固件解析后调整采样间隔、触发阈值等参数。我把控制命令和波形数据做了区分控制命令走同一个串口但接收端靠首字节区分波形数据永远以0xAA开头控制命令以0xC1~0xC5开头。这套做法简单粗暴但实测很可靠没出现过命令和波形数据混淆的情况。5. 实测中容易踩的坑波形漂移、丢包、断连和采样率虚标5.1 波形“上下漂移”的真正原因是参考地我刚开始用这个方案测开关电源的纹波时发现波形总是缓慢上下移动一开始怀疑是蓝牙丢包后来排查了半天才定位到问题——被测设备的地和示波器模块的地之间存在电位差导致ADC参考地和信号源地之间有直流偏置。你不是在测“绝对电压”你是在测“相对电压”。解决方法是使用隔离电源或确保共地。如果你的应用场景允许把示波器模块的GND用一根短线接到被测系统地上去如果不允许就只能接受这个系统误差在测量时以直流偏移量作为参考值。还有一个相关经验探头线要远离电源线和电机线我实测过探头线贴着电机驱动线走时波形毛刺会额外增加30mV左右这在测低噪声信号时是致命的。5.2 波形“拉丝”和帧错位的排查如果你看到波形画面像被“撕开”一样大概率是串口传输丢字节导致的帧错位。成因有两个一是HC-05模块固件内部缓冲区在高速连续传输时溢出二是手机端的串口读取线程没能及时把数据从内核缓冲区读走。处理这个问题我分了三个层面。首先确保固件端开启串口的中断接收并使用DMA发送避免主循环被其它任务阻塞。其次在安卓端把InputStream的读取循环放在独立优先级线程中同时把系统音量之类的后台任务停掉这个听起来玄学但确实会影响。最后App解析时遇到校验错误一定要“主动丢帧”而不是尝试纠错。记住示波器宁可丢掉1帧数据也不能把错位的帧硬画出来否则会误导判断。5.3 连接频繁断开硬件和安卓系统的双重排查连接断开的头号元凶是电源问题。正如前面所说HC-05瞬时发射电流大电源纹波在模组高功率发射时瞬间拉低MCU供电电压导致MCU复位蓝牙连接随之断开。排查方法是把示波器模块改用独立3.7V锂电池RT9193稳压芯片供电如果问题消失那就是原来的供电方案扛不住瞬时电流。安卓系统这边也有坑很多手机默认开启了蓝牙的“省电优化”或“自适应省电”在后台长时间不进行数据传输时系统会把蓝牙射频调至低功耗模式下一次传输直接失败。解决方法是App保持“前台服务”运行注意Android 8.0后必须调用startForegroundService()并同步显示通知才能避免后台被杀。另外部分鸿蒙或定制ROM对蓝牙权限管控更严格需要提示用户手动打开“附近设备”权限。5.4 采样率虚标ADC速率≠你看到的速率这点我放在最后说因为最容易让人误入歧途。固件里ADC确实配置了100kHz采样率DMA也确实按100kHz灌数据但到了蓝牙串口带宽瓶颈就出来了460800波特率下有效数据率约44KB/s100kHz采样率代表着每秒200KB数据量。差距近5倍所以看起来“采样率100kHz”的参数在连续传输模式下实际上永远跑不满。那100kHz采样率是什么时候用的答案是单次触发捕捉模式。你可以用100kHz去捕捉一个突发短信号例如一次按键抖动、一次浪涌把2048个样本全部缓存在本地然后以较低的实时速率回放给手机。这就是固件里“单次采集”模式的意义。如果你需要连续观察高频信号就必须老老实实把采样率降到20kHz以下或者在固件端先做抽取每N点取1点再上传。这点很多人容易忽略我在这里明确说清楚这套蓝牙方案的连续实时带宽约10kHz加上触发模式能够可靠观测100kHz以内的周期信号。6. 后续还能怎么扩展如果你已经把这版方案调通了脑子里的第一反应估计和我当时一样——“这玩意儿能不能再快一点” 答案是能。你可以试试以下几招第一把HC-05换成支持BLE 5.0的模块比如ESP32-C3或nRF52832数据吞吐量能往上走一大截第二在固件端增加抽点、峰值检测等处理逻辑把有效信息压缩后再上传第三给App增加FFT频谱分析把波形页面往上滑就切换到频谱页面这样一台“蓝牙示波器”就有了一部分频谱分析仪的功能。不过在做这些扩展前还是建议先把这套基础方案每个环节都吃透。硬件上有输入保护、有参考电压滤波固件上有DMA双缓冲、有触发机制App上有权限管理、有波形绘制和校准系数这些细节累积起来才是它比网上那些“点个灯连个蓝牙”的例程更有参考价值的地方。希望这篇分享能让你少走几步弯路如果你在实际复刻中遇到什么新问题也欢迎拿我的方案反复对照排查大概率能在硬件或固件层面找到答案。本文还有配套的精品资源点击获取