1. 项目概述
这个基于STM32F103平台的示波器设计方案,是我在嵌入式测量仪器领域的一次实践探索。STM32F103作为一款经典Cortex-M3内核MCU,其内置12位ADC和丰富定时器资源,使其成为低成本示波器开发的理想选择。整套方案实现了200kHz带宽、1Msps采样率的核心指标,完全满足常见电子调试需求。
与传统商用示波器相比,这个方案最大的特点是"够用就好"的设计哲学。我们通过精心设计的信号调理电路和高效的软件算法,在保持基础测量功能的同时,将BOM成本控制在百元以内。特别适合电子爱好者、学生群体以及需要便携式测量工具的工程师使用。
2. 硬件架构设计
2.1 主控选型与资源配置
选择STM32F103C8T6作为主控芯片主要基于三点考量:
- 内置双通道1Msps ADC,满足基础波形采集需求
- 72MHz主频配合DMA,可实现零CPU干预的数据传输
- 丰富的外设接口(SPI/I2C/USART)便于功能扩展
具体资源配置如下:
- ADC1_CH0/CH1(PA0/PA1):双通道信号输入
- TIM2:ADC采样时钟源(1MHz基准)
- DMA1_Channel1:ADC数据搬运至内存
- SPI1(PB13-15):TFT显示屏驱动
- TIM3_CH3(PB0):编码器接口
注意:ADC输入阻抗仅50kΩ,必须在前端设计阻抗匹配电路,否则会导致信号衰减。
2.2 信号调理电路设计
信号调理是示波器精度的关键,我们采用三级处理架构:
-
保护电路:
- TVS二极管(SMAJ5.0A)防止过压损坏
- 100Ω限流电阻+自恢复保险丝(0805封装)
-
阻抗匹配:
- 1MΩ±1%精密电阻并联100pF电容
- 使用AD827运放构成电压跟随器
-
增益调节:
circuit复制V_in ──┬─── 10kΩ ────┐ │ │ 100kΩ OPAMP(AD827) │ │ V_out ─┴─── 1kΩ ─────┘通过模拟开关(CD4051)切换反馈电阻,实现×1/×10/×100三档增益
2.3 电源管理设计
采用两级供电方案:
- 输入5V经MP1584EN降压至3.3V(为主控和数字电路供电)
- 3.3V经TPS7A4901升压至±5V(为运放供电)
关键参数:
- 纹波:<10mVpp(@500mA负载)
- 转换效率:>92%(3.3V输出时)
- 静态功耗:<1mA(待机状态)
3. 软件实现细节
3.1 数据采集流程
采用定时器触发+DMA的硬件级采集方案,确保采样间隔精确:
c复制// 初始化代码片段
void ADC_Init(void) {
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
HAL_ADC_Init(&hadc1);
// 配置DMA
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
HAL_DMA_Init(&hdma_adc1);
}
采集过程状态机:
- 等待触发条件(边沿/脉宽/斜率)
- 启动DMA传输至环形缓冲区
- 缓冲区半满时触发中断处理
- 波形数据送显处理
3.2 触发检测算法
实现可调触发电平的边沿检测:
c复制#define TRIGGER_HYSTERESIS 10 // 迟滞范围
uint8_t CheckTrigger(uint16_t *buf, uint16_t level, uint8_t type) {
static uint16_t prev = 0;
// 上升沿触发
if((type == 0) && (prev < (level-TRIGGER_HYSTERESIS))
&& (buf[0] >= (level+TRIGGER_HYSTERESIS)))
return 1;
// 下降沿触发
if((type == 1) && (prev > (level+TRIGGER_HYSTERESIS))
&& (buf[0] <= (level-TRIGGER_HYSTERESIS)))
return 1;
prev = buf[0];
return 0;
}
3.3 波形显示优化
针对320x240分辨率LCD的特点,采用以下优化策略:
-
动态刷新:
- 仅更新波形变化区域(脏矩形技术)
- 双缓冲机制避免闪烁
-
抗锯齿处理:
c复制void DrawLine_AA(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { int dx = abs(x2-x1), dy = abs(y2-y1); int err = dx-dy, e2; while(1) { LCD_DrawPixel(x1,y1,BLUE); // 主像素 if(x1==x2 && y1==y2) break; e2 = 2*err; if(e2 > -dy) { err -= dy; x1 += (x2>x1)?1:-1; } if(e2 < dx) { err += dx; y1 += (y2>y1)?1:-1; LCD_DrawPixel(x1,y1,BLUE_50); // 辅助像素 } } }
4. 性能优化技巧
4.1 ADC采样精度提升
通过实测发现,STM32F103的ADC存在以下非线性特性:
- 在1/4和3/4量程处有约2LSB的跳变
- 高温环境下零点漂移可达5LSB
采取的补偿措施:
- 软件校准:
c复制void ADC_Calibrate(void) { uint16_t zero = 0, ref = 0; for(int i=0; i<32; i++) zero += HAL_ADC_GetValue(&hadc1); AD_Zero = zero >> 5; // 记录零点 HAL_ADC_Start(&hadc1); ref = [HAL](https://taotoken.net/?utm_source=hardware)_ADC_GetValue(&hadc1); AD_Ref = (ref - AD_Zero) * 3300 / 4095; // 实际mV值 } - 硬件补偿:
- 在ADC输入端并联100nF去耦电容
- 保持模拟地(AGND)与数字地(DGND)单点连接
4.2 实时频谱分析
利用CMSIS-DSP库实现FFT运算:
c复制#include "arm_math.h"
#define FFT_SIZE 1024
float32_t fft_input[FFT_SIZE];
float32_t fft_output[FFT_SIZE/2];
void ProcessFFT(void) {
arm_rfft_fast_instance_f32 fft;
arm_rfft_fast_init_f32(&fft, FFT_SIZE);
// 加汉宁窗减少频谱泄漏
for(int i=0; i<FFT_SIZE; i++) {
fft_input[i] *= 0.5*(1-cos(2*PI*i/(FFT_SIZE-1)));
}
arm_rfft_fast_f32(&fft, fft_input, fft_output, 0);
arm_cmplx_mag_f32(fft_output, fft_output, FFT_SIZE/2);
}
4.3 功耗管理策略
通过以下方式降低整体功耗:
-
动态时钟调整:
- 无信号输入时切换至HSI时钟(8MHz)
- 检测到信号后恢复HSE时钟(72MHz)
-
外设智能启停:
c复制void PowerManage(void) { if(NoSignal_Timeout > 1000) { // 1秒无信号 HAL_ADC_Stop(&hadc1); __HAL_TIM_DISABLE(&htim2); LCD_Backlight(OFF); } else { HAL_ADC_Start(&hadc1); __HAL_TIM_ENABLE(&htim2); } }
5. 常见问题与解决方案
5.1 信号失真问题
现象:高频信号出现振铃或过冲
排查步骤:
- 检查探头补偿(使用方波校准信号)
- 验证前端运放带宽(更换为AD8065测试)
- 测量PCB走线阻抗(应保持50Ω特性阻抗)
解决方案:
- 在运放输出端串联33Ω电阻
- 缩短ADC输入走线长度(<10mm)
- 添加EMI滤波器(Murata BLM18系列)
5.2 采样率不稳定
现象:时基显示值与实际频率不符
根本原因:
- 定时器分频系数计算错误
- DMA传输延迟导致采样间隔不均
修正方法:
c复制// 精确计算采样周期
void SetSampleRate(uint32_t freq) {
uint32_t timer_clk = 72000000; // 72MHz
uint16_t prescaler = (timer_clk / freq) - 1;
__HAL_TIM_SET_PRESCALER(&htim2, prescaler);
TIM2->ARR = 1; // 确保每个周期只触发一次
}
5.3 触摸屏响应延迟
优化方案:
- 采用中断驱动代替轮询:
c复制void EXTI9_5_IRQHandler(void) { if(__HAL_GPIO_EXTI_GET_IT(TOUCH_IRQ_PIN)) { Touch_Process(); __HAL_GPIO_EXTI_CLEAR_IT(TOUCH_IRQ_PIN); } } - 实现触摸轨迹预测算法:
c复制typedef struct { int16_t x[3], y[3]; // 历史坐标 int16_t vx, vy; // 移动速度 } TouchTrack; void PredictNextPos(TouchTrack *t) { t->vx = (t->x[0]-t->x[1]) + (t->x[1]-t->x[2])/2; t->vy = (t->y[0]-t->y[1]) + (t->y[1]-t->y[2])/2; t->x[2]=t->x[1]; t->x[1]=t->x[0]; t->y[2]=t->y[1]; t->y[1]=t->y[0]; }
6. 功能扩展实践
6.1 协议解码实现
以I2C协议解码为例:
c复制void DecodeI2C(uint16_t *buf) {
uint8_t state = 0; // 0:IDLE, 1:START, 2:ADDR...
uint8_t bit_cnt = 0;
uint8_t addr = 0;
for(int i=0; i<BUF_SIZE; i++) {
int scl = buf[i] & 0x01; // 假设CH1是SCL
int sda = (buf[i]>>1) & 0x01;
switch(state) {
case 0: // 检测START条件
if(scl && !sda) { state=1; bit_cnt=0; }
break;
case 1: // 采集地址位
if(!scl) continue;
addr = (addr<<1) | sda;
if(++bit_cnt == 8) {
printf("Addr:0x%X %s\n",
addr>>1, (addr&1)?"Read":"Write");
state=2;
}
break;
}
}
}
6.2 自动测量功能
实现峰峰值自动测量算法:
c复制typedef struct {
uint16_t max, min;
uint32_t sum;
uint16_t cnt;
} WaveStats;
void UpdateStats(WaveStats *s, uint16_t val) {
if(val > s->max) s->max = val;
if(val < s->min) s->min = val;
s->sum += val;
s->cnt++;
}
float GetVpp(WaveStats *s) {
return (s->max - s->min) * 3.3 / 4095; // 转换为电压值
}
6.3 PC端数据可视化
通过USB虚拟串口上传数据:
- 配置USB CDC设备:
c复制
USBD_CDC_ItfTypeDef USBD_Interface = { CDC_Itf_Init, CDC_Itf_DeInit, CDC_Itf_Control, CDC_Itf_Receive }; - 数据打包协议:
python复制# PC端解析代码示例 import serial import matplotlib.pyplot as plt ser = serial.Serial('COM3', 115200) data = [] while len(data) < 1024: packet = ser.read(2) val = (packet[0]<<8) | packet[1] data.append(val * 3.3 / 4095) # 转换为电压值 plt.plot(data) plt.show()
7. 工程实践建议
7.1 PCB设计要点
-
层叠结构:
- 四层板最佳(信号-地-电源-信号)
- 双面板至少保证完整地平面
-
布局规则:
- 模拟部分集中在板卡左侧
- 数字部分集中在右侧
- 晶振远离模拟输入走线
-
走线规范:
- ADC输入线宽≥0.3mm
- 模拟走线避免90°拐角
- 时钟信号包地处理
7.2 生产测试流程
-
硬件测试:
- 电源测试:3.3V±5%,纹波<50mV
- 信号通路:注入1kHz正弦波,检查各级输出
-
软件校准:
bash复制# 校准命令示例 > calib zero # 短路输入校准零点 > calib ref 3.3 # 输入3.3V校准满量程 -
老化测试:
- 连续工作24小时检查温升
- 高低温循环(0℃~70℃)测试稳定性
7.3 成本优化方案
-
元件替代:
- AD827 → OP07(低频场合)
- MP1584EN → AMS1117(固定输出时)
-
结构简化:
- 取消编码器,改用触摸屏虚拟旋钮
- 单通道版本可节省50%BOM成本
-
批量生产建议:
- 使用PCBA代工服务(嘉立创等)
- 选择兼容GD32F303的硬件设计,应对芯片缺货
