1. 为什么需要便携式示波器?
记得我第一次接触电子电路调试时,抱着一台笨重的台式示波器在实验室里挪来挪去,光是找电源插座就花了十分钟。后来做野外设备维护时更尴尬——总不能把十几公斤的仪器背到现场吧?这就是我想做便携式示波器的初衷。
传统示波器三大痛点:体积大、价格高(入门级也要三四千)、操作复杂。而现在的STM32F4系列芯片内置12位ADC,采样率轻松达到2.4MSPS,配合蓝牙5.0模块,完全能实现"手机+火柴盒大小硬件"的轻量化方案。实测用这套系统测量0-200kHz信号,波形失真度小于3%,日常调试开关电源、音频电路完全够用。
2. 硬件设计踩坑指南
2.1 STM32选型不是越强越好
刚开始我直接上了STM32H750(主频480MHz),结果发现大材小用。后来换成STM32F401CCU6(84MHz主频,256KB Flash),成本直降60%。关键点在于:
- 使用DMA+定时器触发ADC采样,CPU零开销
- 双缓冲乒乓存储:当DMA填满Buffer1时自动切换Buffer2,同时CPU处理Buffer1数据
- 12位ADC配置为6bit分辨率时,采样率可提升到5MSPS
// ADC DMA配置示例 hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution = ADC_RESOLUTION_6B; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.DMAContinuousRequests = ENABLE; HAL_ADC_Init(&hadc1); // 定时器触发配置 htim3.Instance = TIM3; htim3.Init.Prescaler = 84-1; // 1MHz计数频率 htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 200-1; // 5kHz采样率 HAL_TIM_Base_Init(&htim3);2.2 蓝牙模块的隐藏技能
市面上的HC-05模块其实支持透传模式,但实测发现连续传输时会丢包。后来改用ESP32的蓝牙双模方案,通过三点优化提升稳定性:
- 数据分包:每包128字节+4字节校验头
- 动态速率:当信号强度(RSSI)低于-80dBm时自动降速到115200bps
- 重传机制:接收端每收到10包回复一次ACK
// 数据包结构示例 typedef struct { uint16_t syncWord; // 0xAA55 uint16_t seqNum; // 包序号 uint8_t payload[128]; uint32_t crc32; // 校验码 } BLE_Packet;3. Android端的关键实现
3.1 蓝牙通信的坑我帮你踩了
在AndroidManifest.xml里声明权限只是第一步,真正头疼的是版本兼容:
- Android 6.0+需要动态申请位置权限(因为蓝牙扫描需要)
- Android 12+新增BLUETOOTH_SCAN权限
- 不同手机厂商对后台扫描的限制策略不同
建议用这个回调处理连接状态:
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { gatt.discoverServices(); // 必须主动发现服务 } } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { mtuSize = mtu; // 记录实际MTU值 } } };3.2 波形绘制性能优化
直接用Canvas.drawLine()在SurfaceView上绘制,当刷新率超过30fps时会明显卡顿。后来改用OpenGL ES 2.0渲染,帧率提升到60fps+:
- 将采样数据转换为纹理坐标
- 使用GLSL着色器实现抗锯齿
- 双缓冲机制:后台线程准备数据,渲染线程只负责绘制
// 顶点着色器示例 private final String vertexShaderCode = "attribute vec4 vPosition;" + "void main() {" + " gl_Position = vPosition;" + "}"; // 片段着色器示例 private final String fragmentShaderCode = "precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + " gl_FragColor = vColor;" + "}";4. 实测效果与改进方向
用信号发生器输出1kHz正弦波,对比市面3000元级示波器,我们的方案在20kHz以下带宽时波形重合度达95%。但存在两个明显问题:
- 输入阻抗只有1MΩ(标准示波器是10MΩ)
- 垂直灵敏度调节时有约50ms延迟
改进方案已经在路上:
- 前级运放改用JFET输入的TL082提升输入阻抗
- Android端增加本地预测算法,根据历史数据预渲染下一帧
这套系统的BOM成本不到200元,但实现了基础示波器80%的功能。特别适合学生党电子制作、现场设备维修等场景。下次准备加入FFT频谱分析功能,毕竟STM32的ARM Cortex-M4内核自带DSP指令集,不用白不用。