1. 为什么需要便携式示波器在电子工程师的日常工作中示波器就像医生的听诊器一样重要。但传统台式示波器动辄上万元的价格和笨重的体积让很多个人开发者和小团队望而却步。我去年参加电子设计竞赛时就深有体会——实验室的示波器要排队使用外出调试时更是不可能随身携带。这时候用STM32Android的方案就显示出独特优势成本直降90%核心部件STM32F103C8T6开发板仅需20元HC-05蓝牙模块15元手机就是显示屏省去LCD屏和外壳整体重量可以控制在100克以内无线测量更灵活不需要拖着探头线在调试无人机飞控时特别实用实测这套系统可以稳定测量0-200kHz的模拟信号垂直分辨率达到8bit完全满足日常电路调试需求。最关键的是它可以直接塞进工具包遇到突发故障时5秒就能开始工作。2. 硬件设计关键点2.1 STM32选型与配置经过对比STM32F1、F4、H7三个系列最终选择Cortex-M3内核的STM32F103C8T6主要考虑ADC性能12位分辨率1μs转换时间支持最高1MHz采样率DMA支持解放CPU资源实现不间断采样供电灵活3.3V工作电压可直接用移动电源供电具体配置时要注意void ADC_Config(void) { ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; //单通道模式 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; //连续转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_1Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }2.2 前端信号调理电路输入信号需要经过三级处理过压保护用TVS二极管防止±100V浪涌程控放大采用AD603可变增益放大器实现1-100倍可调电平移位通过运放将-5V~5V信号抬升到0-3.3V这里有个坑要注意AD603的增益控制电压必须非常稳定否则会出现基线漂移。实测用TL431提供2.5V基准比普通LDO效果更好。3. 蓝牙通信优化技巧3.1 协议设计采用自定义的紧凑型协议格式[头标志0xAA][长度N][数据1][数据2]...[数据N][校验和]在STM32端通过DMA双缓冲实现零等待发送#define BUF_SIZE 256 uint8_t tx_buf1[BUF_SIZE], tx_buf2[BUF_SIZE]; void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_TC) ! RESET) { if(DMA_GetCurrentMemoryTarget(DMA1_Channel4) tx_buf1) { DMA_MemoryTargetConfig(DMA1_Channel4, tx_buf2, DMA_Memory_1); } else { DMA_MemoryTargetConfig(DMA1_Channel4, tx_buf1, DMA_Memory_0); } USART_ClearITPendingBit(USART1, USART_IT_TC); } }3.2 Android端数据接收关键是要处理好线程同步问题。我的方案是单独开BluetoothSocket读取线程使用环形缓冲区存储原始数据通过Handler将数据分批发送到UI线程private class ReceiveThread extends Thread { public void run() { byte[] buffer new byte[1024]; while(!Thread.interrupted()) { try { int bytes mmInStream.read(buffer); mDataBuffer.write(buffer, 0, bytes); mHandler.sendEmptyMessage(MSG_UPDATE_WAVE); } catch (IOException e) { break; } } } }4. Android波形绘制实战4.1 SurfaceView双缓冲技术直接继承SurfaceView并实现SurfaceHolder.Callback接口public class WaveformView extends SurfaceView implements SurfaceHolder.Callback { private DrawThread mDrawThread; public WaveformView(Context context) { super(context); getHolder().addCallback(this); } Override public void surfaceCreated(SurfaceHolder holder) { mDrawThread new DrawThread(getHolder()); mDrawThread.start(); } private class DrawThread extends Thread { private SurfaceHolder mHolder; private boolean mRunning true; public DrawThread(SurfaceHolder holder) { mHolder holder; } Override public void run() { Canvas canvas null; while(mRunning) { try { canvas mHolder.lockCanvas(); synchronized(mHolder) { drawWaveform(canvas); } } finally { if(canvas ! null) { mHolder.unlockCanvasAndPost(canvas); } } } } } }4.2 触控交互实现通过重写onTouchEvent实现手势操作单指拖动调整波形垂直位置双指缩放调整时基和幅值长按测量显示光标和参数值Override public boolean onTouchEvent(MotionEvent event) { switch(event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mLastX event.getX(); mLastY event.getY(); break; case MotionEvent.ACTION_MOVE: if(event.getPointerCount() 1) { // 单指平移 float dx event.getX() - mLastX; mOffsetX dx; } else if(event.getPointerCount() 2) { // 双指缩放 float newDist spacing(event); if(newDist 10f) { float scale newDist / mOldDist; mScaleX * scale; } } invalidate(); break; } return true; }5. 实际测试与性能优化在完成基本功能后我用信号发生器做了系列测试信号频率采样率波形失真度1kHz100ksps1%10kHz200ksps3%50kHz500ksps8%100kHz1Msps15%发现的问题及解决方案高频失真改用屏蔽线连接探头在ADC输入端增加RC滤波蓝牙延迟将数据包从256字节调整为128字节重传超时设为100ms手机发热优化SurfaceView的刷新机制静止时降帧到30fps最终在保持200ksps采样率时系统可以连续工作4小时以上波形延迟控制在200ms以内。这个项目最让我惊喜的是用如此低的成本实现了接近商业示波器80%的功能特别是在现场调试电机驱动电路时无线测量的便利性远超传统设备。