一、系统总体架构
被测信号
↓
信号调理电路(分压 + 偏置 + 跟随)
↓
STM32 ADC
↓
DMA 传输(核心)
↓
RAM 缓存(波形缓冲区)
↓
触发判断(上升沿/下降沿)
↓
TFT 显示(网格 + 波形)
↓
按键/编码器(时基/电压档位)
二、硬件选型与参数
| 项目 | 基础版 (F103) | 进阶版 (F407/H743) |
|---|---|---|
| 主控 | STM32F103C8T6 | STM32F407ZGT6 / H743 |
| ADC | 1Msps (12bit) | 2.4Msps+ (12bit) |
| 带宽 | 200KHz | 10MHz+ |
| 屏幕 | 320x240 TFT (SPI/FSMC) | 480x320 TFT (LTDC/FSMC) |
| 存储深度 | 1K ~ 4K 点 | 32K ~ 1M 点 |
三、核心硬件设计(至关重要)
STM32 的 IO 口只能承受 0~3.3V。示波器必须有过压保护和信号调理。
1. 衰减与分压
- 1x 档:直接进 ADC(0-3.3V)。
- 10x 档 :使用 1MΩ + 9MΩ 电阻分压网络,将 ±30V 信号衰减到 ±3.3V。
2. 直流偏置(最关键)
STM32 ADC 不能采负电压。
- 使用运放(如 LM358 或 AD8065)搭建加法器。
- 将 -3.3V ~ +3.3V 的信号抬升到 0 ~ 3.3V。
- 公式:Vout=Vin+1.65VV_{out} = V_{in} + 1.65VVout=Vin+1.65V
3. 阻抗匹配
- 输入串联 1kΩ 电阻 保护 ADC。
- 并联 TVS 管 防静电。
四、STM32 软件核心(F103 示例)
1. ADC + 定时器 + DMA(黄金三角)
严禁 使用软件循环采样,必须使用定时器触发 ADC,保证采样间隔严格相等。
c
// 定时器配置(触发 ADC)
void TIM_ADC_Init(void) {
TIM_HandleTypeDef htim3;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 72; // 72MHz / 72 = 1MHz 采样率
HAL_TIM_Base_Init(&htim3);
TIM_MasterConfigTypeDef sMasterConfig;
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // 更新事件触发 ADC
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);
HAL_TIM_Base_Start(&htim3);
}
// ADC 配置
void ADC_Init(void) {
ADC_HandleTypeDef hadc1;
hadc1.Instance = ADC1;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE; // 关闭连续,由定时器触发
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO; // 定时器3触发
HAL_ADC_Init(&hadc1);
// 启动 DMA
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE);
}
2. 触发逻辑(示波器灵魂)
触发是为了让波形"稳定"下来。
c
typedef enum {
TRIGGER_RISING,
TRIGGER_FALLING
} TriggerMode;
uint8_t Check_Trigger(uint16_t *buf, uint16_t size, uint16_t threshold) {
static uint8_t triggered = 0;
for (int i = 1; i < size; i++) {
// 上升沿触发
if (buf[i-1] < threshold && buf[i] >= threshold) {
triggered = 1;
return 1; // 触发成功
}
}
return 0; // 未触发
}
3. 波形显示(TFT 绘制)
将 ADC 的 0~4095 映射到屏幕坐标。
c
void Draw_Waveform(uint16_t *adc_data, uint16_t size) {
uint16_t x, y;
uint16_t prev_x = 0, prev_y = 0;
for (x = 0; x < size; x++) {
// 将 ADC 值 (0~4095) 映射到屏幕高度 (0~239)
y = LCD_HEIGHT - (adc_data[x] * LCD_HEIGHT / 4096);
if (x > 0) {
LCD_DrawLine(prev_x, prev_y, x, y, WHITE);
}
prev_x = x;
prev_y = y;
}
}
五、主循环逻辑
c
uint16_t adc_buffer[1024];
uint8_t wave_ready = 0;
int main(void) {
HAL_Init();
SystemClock_Config();
LCD_Init();
TIM_ADC_Init();
ADC_Init();
while (1) {
// 等待 DMA 传输完成一半或全满(双缓冲思想)
if (wave_ready) {
// 1. 触发检测
if (Check_Trigger(adc_buffer, 1024, 2048)) {
// 2. 清除屏幕
LCD_Clear(BLACK);
// 3. 画网格
Draw_Grid();
// 4. 画波形
Draw_Waveform(adc_buffer, 1024);
}
wave_ready = 0;
}
HAL_Delay(1);
}
}
// DMA 传输完成中断
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
wave_ready = 1; // 通知主循环处理数据
}
参考代码 基于stm32的示波器 www.youwenfan.com/contentcsu/69855.html
六、瓶颈与突破
| 瓶颈 | 现象 | 解决方案 |
|---|---|---|
| ADC 速度 | 测不了高频 | 使用 STM32F4/F7 |
| 屏幕刷新 | 波形闪烁 | 使用 FSMC 并行屏(比 SPI 快 10 倍) |
| 存储深度 | 看不到细节 | 使用 SRAM 扩展内存 |
| FFT | 卡死 | 降低点数或使用 DSP 库 |
七、进阶功能(炫技用)
- FFT 频谱分析 :使用 CMSIS-DSP 库的
arm_rfft_q15。 - 自动测量:峰峰值(Vpp)、频率、占空比。
- 波形冻结:按下按键保存当前波形。
- USB 导出:将 CSV 数据通过 USB 虚拟串口发给电脑。
八、避坑指南
- 不要用 Delay 采样:会导致采样率不准。
- 输入电压必须隔离:新手最容易烧芯片。
- 地线要接好:示波器探头地和 STM32 地必须共地,否则波形乱飘。
- 刷新率优先:如果屏幕刷新太慢,宁可降低采样点数,也要保证画面流畅。