简介:这是一套将安卓手机改造为蓝牙示波器的完整开源方案,面向电子爱好者、嵌入式开发者和相关专业学生,解决传统示波器体积大、不便携带的痛点。资源将硬件与软件拆开呈现:硬件侧提供原理图,展示信号采集电路、ADC与蓝牙模块的连接方式;软件侧提供安卓App源码及可安装APK,完成蓝牙数据接收、波形绘制和参数调节。压缩包共8个文件,包含5个Java源码文件、1个APK安装包、1张硬件原理图jpg和1个dsPIC固件压缩包,整体大小仅115KB,结构紧凑。目前站内已有2374人学习下载。借助这套资料,读者可快速搭建便携示波器原型,也可在源码和原理图基础上自行提高ADC分辨率、优化软件界面,从而深入理解Android蓝牙编程、无线数传与低成本测量系统的完整设计流程。
1. 项目整体思路与选型考量
1.1 这个项目到底解决什么问题
做嵌入式或电子维修的朋友应该都有这种经历:现场调试一块板子,波形不对、时序有问题,手边又没有台式示波器;或者你在学校实验室里,想给一个简单的传感器信号看个波形,但示波器只有几台,排队等半天。我这次做的"安卓手机用蓝牙示波器",就是把这些常见的调试场景转移到手机上——硬件端负责采集模拟信号并通过蓝牙无线发出,安卓端负责接收、解析、显示波形。
项目的核心并不复杂,本质是一条数据链路:模拟信号 -> ADC采样 -> 微控制器组帧 -> 蓝牙透传 -> 安卓App接收 -> 绘制波形。但链路里的每一环都有不少细节值得说透,特别是蓝牙带宽、采样率、帧协议、波形绘制性能这几个点,处理不好就容易变成"能连上但看不清波形"的半成品。整套内容包括安卓App源码、嵌入式端固件和硬件原理图,适合有基础单片机知识、想入门无线采集或者是临时需要一台便携示波器的朋友参考。
1.2 方案对比:为什么选择经典蓝牙SPP而不是BLE
最开始我也纠结过用BLE(低功耗蓝牙)还是经典蓝牙SPP。很多新项目默认推BLE,但如果你认真算一笔带宽账,就会明白BLE在"连续高速传输波形数据"这个场景里并不占优。BLE4.0/4.2的实际有效吞吐一般只有几十KB/s,而且一个连接事件里能传的数据包还有间隔限制,要传连续波形就得不断调MTU、调连接间隔,工程复杂度上去了,效果还不一定好。
经典蓝牙SPP(串口透传)虽然老,但它有一个很实在的好处:对开发者来说它就是一根"无线串口",微控制器端往UART里丢数据,手机端拿Socket读就行,协议栈帮你把流控和分包处理好了。实测下来,HC-05这类经典蓝牙模块设置到115200或更高波特率,配合合适的数据缓冲,能达到约几十KB/s的有效吞吐,画一个1K采样点的波形、每秒刷新20~30帧,够用了。如果你只是低频采集温湿度、偶尔看个缓慢变化曲线,BLE方案也完全可行,但如果你要盯着几十kHz的PWM波形或者音频信号毛刺,SPP仍然是更稳妥的选择。
所以最终方案定为:STM32F103C8T6负责ADC采样和组帧,HC-05蓝牙模块负责无线传输,安卓客户端实现波形显示。图上看起来桩桩件件都是老面孔,但胜在稳定、资料多、坑少,我可以把更多精力放在协议和显示优化上。
2. 硬件原理图设计与核心电路解析
2.1 硬件整体结构:三个模块的协作关系
硬件端我没有做得很复杂,最终就三块:主控板(STM32最小系统)、信号调理前端、蓝牙透传模块。原理图设计时重点考虑的是信号完整性和电源干净度,毕竟示波器本质上是测量仪器,前端进来的信号如果被噪声污染,后面软件做得再好也白搭。
信号流是这样的:探头或杜邦线接入信号 -> 高阻输入级 -> 分压/放大调理到ADC量程内 -> STM32的ADC引脚采样 -> DMA搬运到内存 -> 组帧打包 -> UART送给HC-05 -> 蓝牙发给手机。这里STM32之所以比某些直接带蓝牙的SoC更方便,是因为它的ADC可以做到12位精度、最高1MHz采样率(实际使用留余量),而且DMA+定时器触发的组合能让采样间隔非常精准。
电源部分我用了一颗AMS1117-3.3把外部5V降到3.3V给MCU和蓝牙模块供电。这里有个容易踩的坑:HC-05在传输数据时电流峰值可以达到40~50mA左右,如果你用的是那种劣质LDO或者直接从开发板USB口取电,电压跌落会导致蓝牙丢包甚至断连。我在3.3V输出端并了两个电容,一个100uF电解电容、一个0.1uF陶瓷电容,实测传输稳定性明显提升。
2.2 模拟前端调理:把信号"喂"给ADC之前要做的事
STM32内部ADC的输入范围是0~3.3V,但我们测量信号可能是正负电压、可能是几十伏高压、也可能只有几十毫伏的小信号,直接怼到ADC引脚上轻则截波失真,重则烧引脚。所以模拟前端需要一个基本结构:输入保护电阻 -> 运放跟随/放大 -> 分压/偏移到ADC量程。
我的设计里保留了三个档位的分压比(1:1、10:1、100:1),通过一个拨码开关选择。实际电路上就是几颗精密电阻串联,再把中点接到运放同相输入端。运放我选了LMV358,轨到轨输出,能比较接近3.3V的满幅输出,关键是它可以在单电源下工作,电路简单很多。如果是双极性信号(比如音频或交流波形),还需要加一个直流偏置电路,把负半周抬升到0V以上,不然ADC采不到负电压。
另外,ADC引脚前最好串一个100欧电阻,并一个对地的几nF电容(比如4.7nF),构成一个低通滤波器,能滤掉一部分高频毛刺。这个阻容滤波的截止频率大概是1/(2πRC),我这里算出来大约340kHz,足够保留目标信号又不会把噪声全放进来。初次做这个项目的朋友不要省这一步,我试过不滤波直接接信号,波形上面全是密密麻麻的毛刺,过采样也救不回来。
2.3 PCB布局与焊接注意事项
如果只做洞洞板版本,要留意走线的长度和电源地的处理。我的板子是打样了PCB,四层还是两层无所谓,关键是模拟地和数字地要单点连接,ADC部分不要和蓝牙天线挨太近,否则采样值会周期性跳动,看起来像波形上叠加了高频干扰。
蓝牙模块的板载天线附近,我建议下面不要铺铜,天线净空区留出来,不然信号辐射效率大打折扣。另外HC-05模块的工作电压范围是3.6V~6V,但它的逻辑电平是3.3V,和STM32之间可以直接连,不过最好还是在RXD/TXD上各串一个1K电阻,万一模块供电高了一点点也不会倒灌烧引脚。
3. Android端程序架构与关键代码实现
3.1 工程结构与蓝牙连接初始化
安卓端我用了经典的Java+原生View组合,没有上复杂的第三方图表库,因为波形绘制一旦涉及实时刷新,通用图表库往往性能不够或者定制不灵活。工程核心就三个类:MainActivity负责界面和权限;BluetoothService负责经典蓝牙Socket的连接和数据读取;WaveformView负责绘制波形和处理手势缩放。
连接蓝牙的步骤大家应该比较熟:获取蓝牙适配器 -> 配对设备列表 -> 通过UUID建立SPP连接 -> 打开输入输出流。这里最关键也是最容易出问题的是UUID必须用SPP标准UUID00001101-0000-1000-8000-00805F9B34FB,如果随便填一个其他UUID,手机会一直连不上。我在蓝牙模块端没有做PIN码配对,因为HC-05默认1234,如果手机端每次都弹窗配对会比较烦,可以在首次配对时记住设备,后面代码里直接按MAC地址连接。
核心连接代码大致如下:
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice device = adapter.getRemoteDevice("XX:XX:XX:XX:XX:XX"); BluetoothSocket socket = device.createRfcommSocketToServiceRecord( UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") ); socket.connect(); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream();连上之后,读数据一定要丢到独立线程里,不能在主线程直接读流,否则界面卡顿。我开了一个DataReaderThread,用while(!isInterrupted)不断从InputStream里读字节到缓冲区,然后解析帧。
3.2 数据帧协议设计:告诉手机"这一段波形怎么摆"
串口蓝牙是字节流,不是包流,不能保证你每次read()到的数据正好是一帧。所以必须在固件端定义好帧格式,手机端做帧同步和校验。我用的帧结构是:
| 帧头1 | 帧头2 | 命令字 | 数据长度 | 数据区 | 校验和 |
|---|---|---|---|---|---|
| 0x55 | 0xAA | 0x01 | 0x04 | ADC_VALUE(16bit/采样点) | SUM |
每次采样一组1024个点之后,固件把整组数据打包,每帧放64个采样点,一共16帧发完。这样设计的好处有两点:一是如果某帧校验失败,或者手机端重启了,只需要丢弃当前帧等到下一个帧头,不影响后续数据;二是固定小帧有利于蓝牙底层把数据均匀地发出去,不容易出现一大坨数据积压导致丢包。
手机端解析时需要注意状态机的写法:读到一个字节,判断状态(等待帧头1、等待帧头2、等待数据长度、读取数据区、校验)。我一开始用简单顺序判断写过一版,结果遇到丢帧后整个流就乱了,后来改成状态机就稳多了。伪代码如下:
while ((b = in.read()) != -1) { switch (state) { case WAIT_HEADER1: if (b == 0x55) state = WAIT_HEADER2; break; case WAIT_HEADER2: state = (b == 0xAA) ? WAIT_LEN : WAIT_HEADER1; break; case WAIT_LEN: remainLen = b; buffer = new byte[remainLen]; state = READ_DATA; break; case READ_DATA: buffer[offset++] = b; if (offset >= remainLen) { // 校验、提取数据、回调UI state = WAIT_HEADER1; } break; } }3.3 波形绘制:频繁刷新的核心优化
Android里画波形,最容易犯的错误是在onDraw()里做过多计算,或者频繁调用invalidate()。我这个项目的WaveformView在架构上做了三层优化:
第一层,整个波形View继承SurfaceView,通过SurfaceHolder.lockCanvas()获取画布,在独立的渲染线程里画图,避免对主线程造成压力。不要用普通View+invalidate(),那在低端手机上很容易掉到十几帧。
第二层,采用双缓冲思想,每收到完整的一组采样点后,先转换成一个float[]数组放到"待绘制数据区",渲染线程以固定节奏(比如每秒25帧)从这个区取数绘制。这样即使蓝牙偶尔突刺,画面也不会疯狂跳变,整体观感稳定很多。
第三层,画波形路径的时候用Path对象,把1024个点连成线,再调用canvas.drawPath()。如果点太密,可以每隔两个点抽一个点绘制,肉眼几乎看不出差异,但速度提升明显。颜色我用经典的绿色(0xFF00FF00),黑色背景,网格线是暗灰色,看久了眼睛舒服。
缩放平移的手势也很重要,不然只看一屏数据没法观察细节。我做了双指缩放,底层是按比例把采样点映射到屏幕像素宽度,核心方法:
private void updateTransform() { // viewWidth是屏幕宽度,visiblePoints是当前显示的点数窗口 scaleX = (float) viewWidth / visiblePoints; // minValue、maxValue根据放大区间动态计算 scaleY = (float) viewHeight / (maxValue - minValue); }网格线随着缩放动态变化:当显示范围大时网格粗一些,放大时自动变细,方便估读电压和周期。
4. 联调中的常见问题与排查技巧实录
4.1 连不上、不稳定、波形异常的快速排查表
做整个过程下来,我整理了一份排查速查表,基本上遇到问题先对着查一遍,能省下很多时间:
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 手机搜不到HC-05 | 模块未进入AT模式/没有上电 | 长按模块按键或给AT引脚高电平后重新搜索 |
| 能配对但Socket连接失败 | UUID错误或模块被占 | 确认使用SPP专用UUID,重启模块 |
| 数据时断时续 | 供电不足或波特率不匹配 | 换独立5V电源,核对固件和蓝牙模块波特率一致 |
| 波形全是毛刺 | ADC引脚缺少滤波/电源噪声 | 检查前端RC滤波,3.3V并电容 |
| 波形幅度不对 | 分压档位选错或运放饱和 | 切换拨码开关档位,确认输入范围 |
| 刷新率很低 | 蓝牙吞吐受限或缓冲太小 | 减少单帧采样点数,增大Socket读缓冲 |
4.2 数据乱码和帧同步问题的实际处理
我记得第一次把固件烧进去、手机端打通的时候,屏幕上是乱七八糟的波浪线,看频率完全不对。排查到最后发现是串口配置里一个停止位的锅:固件用的是8N1(8数据位、无校验、1停止位),但HC-05出厂的AT配置里有些是8E1,结果双方对不上,数据全是乱的。进AT模式重新设置成AT+UART=115200,0,0之后,波形立即恢复正常。从这里得到的教训是:固件里的串口配置一定要和蓝牙模块配对端的配置完全一致,不只是波特率,数据位、校验位、停止位三个都要一样。
另外,蓝牙传输偶尔丢一个字节是难免的事,所以帧校验不能省。我用的是简单的单字节累加校验,虽然不如CRC32强大,但对这个场景足够了,有效数据载荷本来就几十个字节,漏检概率很小。如果发现波形偶尔出现一根非常离谱的尖峰,十有八九就是某一帧校验失败但没被检出来,可以在手机端把连续两次数值跳变超过一定阈值的点过滤掉,比如电压突变了满量程的80%以上,多半是坏点,直接丢弃。
4.3 采样率上不去:瓶颈往往不是ADC
很多人以为ADC能跑到1MHz,波形显示刷新就应该很流畅。真实瓶颈几乎都在蓝牙传输上。我算过一笔账:假设ADC采12位数据,每个点存2字节,如果想每秒传50K个采样点,那就是100KB/s。HC-05实际有效吞吐很难到这个量级,更别说还有帧头、协议开销。所以最终我把采样率限制在12.5kHz(每秒钟12500个点),每组1024个点,理论上可以每秒传12.2帧,实际加上数据解析和UI开销,稳定在10帧左右,对于观察几十Hz到几kHz的信号完全够用。
如果你需要看更高频的信号,建议走"离线采集"思路:让MCU先以高速率采一段波形存到Flash或内存里,采完停下,再慢慢分批发给手机。这样蓝牙慢没关系,波形照样能完整看到,只是不是实时而已。这在调试电机启动电流或通信波形时特别实用。
5. 个人经验:复刻这个项目时最值得注意的三件事
做完这个项目再回头看,有三件事我会建议后面复刻的人重点关注。第一,不要一开始就追求功能大而全,先把最简单的通路跑通——比如先用安卓手机上的现成串口工具把HC-05发的数据打印出来,确认识别到数据了,再写自己的App,这样每个环节都容易定位问题。第二,硬件的前端调理和供电部分多花心思,如果示波器显示出来的波形本身就不干净,后面软件再怎么滤波都是给噪声擦屁股,不如源头就干净。第三,安卓端的波形绘制不要贪图表库,自己实现一个简单的SurfaceView绘制并不难,而且后期想加什么交互都很自由,性能也更好控制。
这个项目其实还有很多可以扩展的方向,比如加一个简单的触发功能,让波形稳定不左右乱跑——我用过软件触发,在MCU端做比较器加定时器,实现起来不算复杂,效果却很直观;再比如把数据存成CSV文件导出,方便后期在电脑上做频谱分析。如果你手头正好有闲置的STM32板子和蓝牙模块,不妨按这个思路搭一套出来,手机从此就是一个随身携带的简易示波器,现场调试、课堂演示、临时抓个波形都能派上用场。
本文还有配套的精品资源,点击获取