安卓蓝牙示波器:STM32+SPP实现无线波形采集

安卓蓝牙示波器:STM32+SPP实现无线波形采集 简介这是一套将安卓手机改造为蓝牙示波器的完整开源方案面向电子爱好者、嵌入式开发者和相关专业学生解决传统示波器体积大、不便携带的痛点。资源将硬件与软件拆开呈现硬件侧提供原理图展示信号采集电路、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命令字数据长度数据区校验和0x550xAA0x010x04ADC_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()获取画布在独立的渲染线程里画图避免对主线程造成压力。不要用普通Viewinvalidate()那在低端手机上很容易掉到十几帧。第二层采用双缓冲思想每收到完整的一组采样点后先转换成一个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 数据乱码和帧同步问题的实际处理我记得第一次把固件烧进去、手机端打通的时候屏幕上是乱七八糟的波浪线看频率完全不对。排查到最后发现是串口配置里一个停止位的锅固件用的是8N18数据位、无校验、1停止位但HC-05出厂的AT配置里有些是8E1结果双方对不上数据全是乱的。进AT模式重新设置成ATUART115200,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板子和蓝牙模块不妨按这个思路搭一套出来手机从此就是一个随身携带的简易示波器现场调试、课堂演示、临时抓个波形都能派上用场。本文还有配套的精品资源点击获取