文章目录
-
- 每日一句正能量
- 导读
- 一、项目概述与系统架构
-
- [1.1 系统总体架构](#1.1 系统总体架构)
- 二、硬件选型与电路设计
-
- [2.1 核心器件选型](#2.1 核心器件选型)
- [2.2 信号调理电路](#2.2 信号调理电路)
- [2.3 TFT屏接口](#2.3 TFT屏接口)
- 三、ADC采样与DMA传输
-
- [3.1 STM32 ADC工作原理](#3.1 STM32 ADC工作原理)
- [3.2 DMA双缓冲采样机制](#3.2 DMA双缓冲采样机制)
- [3.3 采样率与时基控制](#3.3 采样率与时基控制)
- 四、触发系统实现
-
- [4.1 触发原理](#4.1 触发原理)
- [4.2 触发检测算法](#4.2 触发检测算法)
- [4.3 预触发与显示窗口](#4.3 预触发与显示窗口)
- 五、TFT波形显示
-
- [5.1 显示界面设计](#5.1 显示界面设计)
- [5.2 波形绘制算法](#5.2 波形绘制算法)
- [5.3 波形插值优化](#5.3 波形插值优化)
- 六、奈奎斯特采样定理与混叠
-
- [6.1 采样定理核心](#6.1 采样定理核心)
- [6.2 混叠效应](#6.2 混叠效应)
- 七、参数测量与光标功能
-
- [7.1 自动参数测量](#7.1 自动参数测量)
- [7.2 光标测量功能](#7.2 光标测量功能)
- 八、性能测试与优化
-
- [8.1 实测性能指标](#8.1 实测性能指标)
- [8.2 性能优化策略](#8.2 性能优化策略)
- 九、常见问题与调试技巧
- 十、总结与展望

每日一句正能量
人生没有绝对的圆满,过于执着外界的评价容易丢失真正的自己。
圆满是一种理想状态,现实中总有遗憾、缺漏或意外。如果你把"得到所有人认可"当作幸福的前提,就会永远活在别人的嘴里。外界评价就像海浪,执着于它的人会像浮标一样起伏不定。而真正的自己,是在安静时、独处时、做热爱的事时,那个不假思索就出现的状态。
命运的变化不是突然降临的奇迹,而是当你从"被推着走"切换到"自己定规则"时,每一天微小选择中累积的偏移。
导读
上一篇我们完成了基于STM32的智能小车系统,本篇将转向测量仪器领域,从零构建一台简易数字示波器。通过STM32的ADC高速采样、DMA数据传输、触发控制与TFT波形显示,深入理解数字示波器的核心原理,并特别关注采样率、触发机制和时基控制这三个决定示波器性能的关键要素。
一、项目概述与系统架构
数字示波器是电子工程师的"眼睛",其本质是将模拟信号转换为数字信号并可视化显示。本项目基于STM32F103C8T6构建一台简易数字示波器,实现以下核心功能:
- 双通道信号采集:0-3.3V输入,12位分辨率
- 可调采样率:最高1Msps(每秒百万次采样)
- 多种触发模式:上升沿/下降沿/电平触发
- 10档时基控制:100μs/div ~ 100ms/div
- 实时波形显示:2.4寸TFT彩屏,320×240分辨率
- 自动参数测量:频率、峰峰值、最大值、最小值

1.1 系统总体架构
本系统采用经典的数字示波器信号链路:
信号调理链路:BNC输入 → 衰减网络(1x/10x) → 运放缓冲 → 分压限幅 → STM32 ADC
数字处理链路:ADC采样 → DMA传输 → RAM缓冲区 → 触发检测 → 波形处理 → TFT显示
控制链路:4个按键(+/-/OK/MODE)→ GPIO输入 → 菜单系统 → 参数调节
二、硬件选型与电路设计

2.1 核心器件选型
| 器件 | 型号 | 关键参数 | 选型理由 |
|---|---|---|---|
| 主控 | STM32F103C8T6 | 72MHz, 12bit ADC | 内置ADC支持1Msps,DMA传输 |
| TFT屏 | 2.4寸ILI9341 | 320×240, SPI接口 | 性价比高,驱动成熟 |
| 运放 | LM358 | 双通道, 1MHz带宽 | 低成本电压跟随器 |
| 稳压管 | BZX55C3V3 | 3.3V稳压 | ADC输入保护 |
2.2 信号调理电路
衰减网络设计:
10x衰减采用900KΩ + 100KΩ电阻分压,将0-10V输入衰减为0-1V,再经运放缓冲后送入ADC。1x模式下通过继电器/模拟开关直通。
输入保护:
- 稳压管钳位:3.3V稳压管并联在ADC输入端,防止过压损坏
- 限流电阻:100Ω串联电阻限制瞬态电流
- 运放缓冲:LM358电压跟随器提供高输入阻抗(约1MΩ),减少对被测电路的影响
关键设计要点:
- 输入阻抗需≥1MΩ,避免对被测电路产生负载效应
- 运放带宽需≥信号最高频率的3倍,保证信号不失真
- 分压电阻精度选用1%,确保衰减比准确
2.3 TFT屏接口
ILI9341驱动芯片支持8/16位并行和SPI串行接口。本项目采用SPI模式以节省IO口:
| TFT引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| CS | PA4 | 片选,低电平有效 |
| DC | PA3 | 数据/命令选择 |
| RESET | PA2 | 硬件复位 |
| MOSI | PA7 | SPI数据输出 |
| SCK | PA5 | SPI时钟 |
| LED | 3.3V | 背光供电 |
SPI配置为模式0(CPOL=0, CPHA=0),时钟分频后约18MHz,刷新率可达30-60fps。
三、ADC采样与DMA传输
3.1 STM32 ADC工作原理
STM32F103的ADC为逐次逼近型(SAR),12位分辨率,最大采样率1Msps。单次转换时序:
转换时间 = 采样保持时间 + 逐次逼近时间
- 采样保持:1.5个ADC时钟周期(可配置为1.5/7.5/13.5/28.5/41.5/55.5/71.5/239.5)
- 逐次逼近:固定的12.5个周期
当ADC时钟为12MHz时,最短转换时间 = (1.5 + 12.5) / 12MHz = 1.17μs ,对应采样率约857ksps。
要达到1Msps,需将ADC时钟提升至14MHz(接近最大允许值),或使用更短的采样时间。
3.2 DMA双缓冲采样机制

采用DMA双缓冲机制实现零等待采样:
c
#define SAMPLE_BUFFER_SIZE 1024
uint16_t adc_buffer_a[SAMPLE_BUFFER_SIZE];
uint16_t adc_buffer_b[SAMPLE_BUFFER_SIZE];
uint16_t *adc_ready_buffer = NULL;
volatile uint8_t buffer_ready_flag = 0;
/* ADC+DMA初始化 */
void ADC_DMA_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
ADC_InitTypeDef ADC_InitStruct;
DMA_InitTypeDef DMA_InitStruct;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// PA0: ADC输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
GPIO_Init(GPIOA, &GPIO_InitStruct);
// DMA配置: 外设→内存,循环模式
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)adc_buffer_a;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStruct.DMA_BufferSize = SAMPLE_BUFFER_SIZE;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStruct);
// ADC配置
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 单通道
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStruct);
// 配置采样时间: 1.5 cycles(最快速度)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_1Cycles5);
// 使能ADC DMA请求
ADC_DMACmd(ADC1, ENABLE);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
// ADC校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
/* DMA中断:半传输/传输完成 */
void DMA1_Channel1_IRQHandler(void) {
if(DMA_GetITStatus(DMA1_IT_HT1)) { // 半传输中断
adc_ready_buffer = adc_buffer_a;
buffer_ready_flag = 1;
DMA_ClearITPendingBit(DMA1_IT_HT1);
}
if(DMA_GetITStatus(DMA1_IT_TC1)) { // 传输完成中断
adc_ready_buffer = adc_buffer_b;
buffer_ready_flag = 1;
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
}
双缓冲机制优势:
- ADC持续填充Buffer A时,CPU处理Buffer B
- 半传输中断和传输完成中断交替触发,实现零拷贝数据交换
- 避免CPU等待ADC,最大化系统吞吐量
3.3 采样率与时基控制

采样率与时基档位的关系:
采样率 = 显示点数 / 屏幕总时间
屏幕总时间 = 每格时间 × 水平格数(10格)
例如,时基为1ms/div时:
- 屏幕总时间 = 1ms × 10 = 10ms
- 采样率 = 1024点 / 10ms = 102.4ksps
通过调整ADC的预分频器和采样时间,实现不同档位的采样率:
c
typedef struct {
uint32_t time_per_div; // 每格时间(μs)
uint32_t sample_rate; // 采样率(Hz)
uint32_t adc_prescaler; // ADC预分频
uint32_t sample_time; // 采样时间配置
} TimeBase_Config;
TimeBase_Config timebase_table[] = {
{100, 1024000, RCC_PCLK2_Div2, ADC_SampleTime_1Cycles5}, // 100μs/div
{200, 512000, RCC_PCLK2_Div2, ADC_SampleTime_1Cycles5}, // 200μs/div
{500, 204800, RCC_PCLK2_Div4, ADC_SampleTime_7Cycles5}, // 500μs/div
{1000, 102400, RCC_PCLK2_Div4, ADC_SampleTime_7Cycles5}, // 1ms/div
{2000, 51200, RCC_PCLK2_Div6, ADC_SampleTime_13Cycles5}, // 2ms/div
{5000, 20480, RCC_PCLK2_Div6, ADC_SampleTime_28Cycles5}, // 5ms/div
{10000, 10240, RCC_PCLK2_Div8, ADC_SampleTime_41Cycles5}, // 10ms/div
{20000, 5120, RCC_PCLK2_Div8, ADC_SampleTime_55Cycles5}, // 20ms/div
{50000, 2048, RCC_PCLK2_Div8, ADC_SampleTime_71Cycles5}, // 50ms/div
{100000, 1024, RCC_PCLK2_Div8, ADC_SampleTime_239Cycles5}, // 100ms/div
};
四、触发系统实现
4.1 触发原理

触发是示波器的灵魂功能,其作用是稳定显示周期性波形。没有触发时,波形会在屏幕上左右滚动;触发后,每次从信号的同一位置开始显示,实现波形"冻结"。
触发过程:
- 设定触发电平 (如2.5V)和触发边沿(上升沿/下降沿)
- ADC持续采样,软件扫描采样数据
- 当检测到信号从低于触发电平变为高于触发电平时,判定为触发条件满足
- 以触发点为基准,截取显示窗口的数据
- 将截取的数据送往TFT显示
4.2 触发检测算法
c
typedef enum {
TRIGGER_RISING, // 上升沿触发
TRIGGER_FALLING, // 下降沿触发
TRIGGER_LEVEL, // 电平触发
TRIGGER_AUTO, // 自动触发(无信号时强制刷新)
TRIGGER_SINGLE // 单次触发
} Trigger_Mode;
typedef struct {
Trigger_Mode mode;
uint16_t level; // 触发电平(ADC值 0-4095)
uint16_t pre_trigger; // 预触发点数(触发点左侧显示点数)
} Trigger_Config;
Trigger_Config g_trigger = {TRIGGER_RISING, 2048, 100};
/* 触发检测 */
int16_t Trigger_FindPoint(uint16_t *buffer, uint16_t size) {
uint16_t trigger_adc = g_trigger.level;
switch(g_trigger.mode) {
case TRIGGER_RISING:
for(int i = 1; i < size; i++) {
if(buffer[i-1] < trigger_adc && buffer[i] >= trigger_adc) {
return i; // 找到上升沿触发点
}
}
break;
case TRIGGER_FALLING:
for(int i = 1; i < size; i++) {
if(buffer[i-1] > trigger_adc && buffer[i] <= trigger_adc) {
return i; // 找到下降沿触发点
}
}
break;
case TRIGGER_AUTO:
// 强制返回中心位置
return size / 2;
case TRIGGER_SINGLE:
// 仅触发一次,触发后停止采样
for(int i = 1; i < size; i++) {
if(buffer[i-1] < trigger_adc && buffer[i] >= trigger_adc) {
ADC_Stop(); // 停止ADC
return i;
}
}
break;
default: break;
}
return -1; // 未找到触发点
}
4.3 预触发与显示窗口
实际示波器中,触发点通常不在屏幕最左侧,而是预留预触发深度(如20%),这样可以看到触发前的信号状态:
c
/* 截取显示窗口 */
void Waveform_Extract(uint16_t *src, uint16_t src_size,
uint16_t *dst, uint16_t dst_size,
int16_t trigger_point) {
int16_t start_idx = trigger_point - g_trigger.pre_trigger;
// 边界检查
if(start_idx < 0) start_idx = 0;
if(start_idx + dst_size > src_size) start_idx = src_size - dst_size;
// 复制数据
for(int i = 0; i < dst_size; i++) {
dst[i] = src[start_idx + i];
}
}
五、TFT波形显示
5.1 显示界面设计
示波器显示界面包含以下元素:
波形区域:黑色背景,白色/彩色网格线,绿色波形轨迹
状态栏(顶部):
- CH1/CH2垂直灵敏度(V/div)
- 时基档位(s/div)
- 触发状态(TRIG'D/READY/AUTO)
测量值区域(左侧/底部):
- 频率(Freq)
- 峰峰值(Vpp)
- 最大值(Vmax)
- 最小值(Vmin)
- 平均值(Vavg)
5.2 波形绘制算法
c
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define GRID_DIV_X 10
#define GRID_DIV_Y 8
#define WAVE_Y_CENTER 120
/* 坐标映射:ADC值 → 屏幕Y坐标 */
uint16_t ADC_To_ScreenY(uint16_t adc_value, float v_scale) {
// ADC: 0-4095 对应 0-3.3V
float voltage = (adc_value * 3.3f) / 4095.0f;
float v_offset = 1.65f; // 中心电压
// 像素/伏 = 屏幕高度/8格 / v_scale
float pixel_per_volt = (SCREEN_HEIGHT / GRID_DIV_Y) / v_scale;
int16_t y = WAVE_Y_CENTER - (int16_t)((voltage - v_offset) * pixel_per_volt);
// 限幅
if(y < 0) y = 0;
if(y >= SCREEN_HEIGHT) y = SCREEN_HEIGHT - 1;
return (uint16_t)y;
}
/* 绘制波形 */
void Waveform_Draw(uint16_t *wave_data, uint16_t points, float v_scale) {
uint16_t x_step = SCREEN_WIDTH / points;
uint16_t last_x = 0, last_y = WAVE_Y_CENTER;
// 清除旧波形(使用背景色重绘)
TFT_FillRect(0, 20, SCREEN_WIDTH, SCREEN_HEIGHT - 40, COLOR_BLACK);
// 绘制网格
Draw_Grid();
// 绘制波形
TFT_SetColor(COLOR_GREEN);
for(int i = 0; i < points - 1; i++) {
uint16_t x1 = i * x_step;
uint16_t y1 = ADC_To_ScreenY(wave_data[i], v_scale);
uint16_t x2 = (i + 1) * x_step;
uint16_t y2 = ADC_To_ScreenY(wave_data[i + 1], v_scale);
// 使用线段连接相邻点
TFT_DrawLine(x1, y1, x2, y2);
last_x = x2;
last_y = y2;
}
}
/* 绘制网格 */
void Draw_Grid(void) {
TFT_SetColor(COLOR_DARKGRAY);
// 垂直线(10格)
for(int i = 0; i <= GRID_DIV_X; i++) {
uint16_t x = i * (SCREEN_WIDTH / GRID_DIV_X);
TFT_DrawLine(x, 20, x, SCREEN_HEIGHT - 20);
}
// 水平线(8格)
for(int i = 0; i <= GRID_DIV_Y; i++) {
uint16_t y = 20 + i * ((SCREEN_HEIGHT - 40) / GRID_DIV_Y);
TFT_DrawLine(0, y, SCREEN_WIDTH, y);
}
// 中心十字线(加粗)
TFT_SetColor(COLOR_GRAY);
TFT_DrawLine(SCREEN_WIDTH/2, 20, SCREEN_WIDTH/2, SCREEN_HEIGHT-20);
TFT_DrawLine(0, WAVE_Y_CENTER, SCREEN_WIDTH, WAVE_Y_CENTER);
}
5.3 波形插值优化
当采样点数较少时,直接连线会产生锯齿。采用线性插值使波形更平滑:
c
/* 线性插值:在相邻采样点之间插入中间点 */
void Waveform_Interpolate(uint16_t *src, uint16_t src_size,
uint16_t *dst, uint16_t dst_size) {
float ratio = (float)(src_size - 1) / (dst_size - 1);
for(int i = 0; i < dst_size; i++) {
float src_idx = i * ratio;
int idx_low = (int)src_idx;
int idx_high = idx_low + 1;
float frac = src_idx - idx_low;
if(idx_high >= src_size) idx_high = src_size - 1;
// 线性插值
dst[i] = (uint16_t)(src[idx_low] * (1 - frac) + src[idx_high] * frac);
}
}

六、奈奎斯特采样定理与混叠
6.1 采样定理核心

奈奎斯特采样定理 :为了无失真地恢复原始信号,采样频率必须大于信号最高频率的2倍。
f s > 2 × f m a x f_s > 2 \times f_{max} fs>2×fmax
实际工程建议 :采样率 ≥ 5倍信号频率,以保证波形还原质量。
6.2 混叠效应
当采样率不足时,高频信号会被"伪装"成低频信号,这种现象称为混叠(Aliasing)。
混叠频率计算 :
f a l i a s = ∣ f s i g n a l − n × f s a m p l e ∣ f_{alias} = |f_{signal} - n \times f_{sample}| falias=∣fsignal−n×fsample∣
其中n为使f_alias落在0~f_sample/2范围内的整数。
抗混叠措施:
- 硬件低通滤波:在ADC输入端加入RC低通滤波器,截止频率设为f_sample/2
- 软件过采样:以更高采样率采集后数字降采样
- 合理选择时基:确保当前时基下的采样率满足奈奎斯特准则
c
/* 检查当前设置是否满足奈奎斯特准则 */
uint8_t Check_Nyquist(float signal_freq) {
float nyquist_freq = g_sample_rate / 2.0f;
if(signal_freq > nyquist_freq) {
// 显示警告
TFT_DrawString(10, 5, \"ALIAS WARNING!\", COLOR_RED);
return 0; // 不满足
}
return 1; // 满足
}
七、参数测量与光标功能
7.1 自动参数测量
c
typedef struct {
float frequency; // 频率
float vpp; // 峰峰值
float vmax; // 最大值
float vmin; // 最小值
float vavg; // 平均值
float duty_cycle; // 占空比
} Waveform_Measure;
/* 参数测量 */
void Waveform_Measure(uint16_t *data, uint16_t size, Waveform_Measure *result) {
uint32_t sum = 0;
uint16_t max_val = 0, min_val = 4095;
// 最大值、最小值、平均值
for(int i = 0; i < size; i++) {
if(data[i] > max_val) max_val = data[i];
if(data[i] < min_val) min_val = data[i];
sum += data[i];
}
result->vmax = (max_val * 3.3f) / 4095.0f;
result->vmin = (min_val * 3.3f) / 4095.0f;
result->vpp = result->vmax - result->vmin;
result->vavg = ((sum / size) * 3.3f) / 4095.0f;
// 频率测量:过零检测法
uint16_t cross_count = 0;
uint16_t first_cross = 0, last_cross = 0;
uint16_t threshold = (max_val + min_val) / 2;
for(int i = 1; i < size; i++) {
if(data[i-1] < threshold && data[i] >= threshold) {
if(cross_count == 0) first_cross = i;
last_cross = i;
cross_count++;
}
}
if(cross_count >= 2) {
float period_samples = (float)(last_cross - first_cross) / (cross_count - 1);
result->frequency = g_sample_rate / period_samples;
} else {
result->frequency = 0;
}
}
7.2 光标测量功能
光标功能允许用户手动测量波形上任意两点的时间差和电压差:
c
typedef struct {
uint16_t x1, y1; // 光标A位置
uint16_t x2, y2; // 光标B位置
uint8_t active; // 光标是否启用
} Cursor_Measure;
/* 绘制光标 */
void Cursor_Draw(Cursor_Measure *cursor, float v_scale, uint32_t time_per_div) {
if(!cursor->active) return;
// 光标A(虚线十字)\n TFT_SetColor(COLOR_YELLOW);\n Draw_DashedLine(cursor->x1, 20, cursor->x1, SCREEN_HEIGHT-20);\n Draw_DashedLine(0, cursor->y1, SCREEN_WIDTH, cursor->y1);\n \n // 光标B(虚线十字)\n TFT_SetColor(COLOR_CYAN);\n Draw_DashedLine(cursor->x2, 20, cursor->x2, SCREEN_HEIGHT-20);\n Draw_DashedLine(0, cursor->y2, SCREEN_WIDTH, cursor->y2);\n \n // 计算差值\n float dx = (cursor->x2 - cursor->x1) * time_per_div / (SCREEN_WIDTH / GRID_DIV_X);\n float dy = (cursor->y2 - cursor->y1) * v_scale / (SCREEN_HEIGHT / GRID_DIV_Y);\n \n // 显示测量结果\n char buf[64];\n sprintf(buf, \"dT=%.2fus dV=%.2fV\", dx, dy);\n TFT_DrawString(200, 5, buf, COLOR_WHITE);\n}
八、性能测试与优化
8.1 实测性能指标

| 指标 | 设计值 | 实测值 | 备注 |
|---|---|---|---|
| 最大采样率 | 1Msps | 857ksps | 受限于ADC时钟 |
| 模拟带宽 | 100kHz | ~80kHz | -3dB点 |
| 垂直分辨率 | 12bit | 12bit | 理论值 |
| 存储深度 | 1024点 | 1024点 | 可扩展至2048 |
| 刷新率 | 30fps | 25-40fps | 取决于时基档位 |
| 输入阻抗 | 1MΩ | ~1.1MΩ | 含运放偏置 |
| 频率测量精度 | ±1% | ±2% | 受噪声影响 |
8.2 性能优化策略
1. 采样率优化:
- 使用双ADC交错采样(STM32部分型号支持),理论采样率翻倍
- 降低ADC分辨率至10bit或8bit,缩短转换时间
2. 显示刷新优化:
- 仅刷新波形区域,不重复绘制静态UI元素
- 使用DMA传输向TFT发送数据,减少CPU占用
- 实现局部刷新:只更新变化的像素
3. 触发稳定性优化:
- 加入触发滞后(Hysteresis):设置两个阈值(上升沿触发需跨越 upper_threshold,下降沿需低于 lower_threshold)
- 使用数字滤波消除噪声导致的误触发
c
/* 带滞后的触发检测 */
int16_t Trigger_Find_Hysteresis(uint16_t *buffer, uint16_t size) {
uint16_t upper = g_trigger.level + 50; // 上阈值
uint16_t lower = g_trigger.level - 50; // 下阈值
for(int i = 2; i < size; i++) {
if(buffer[i-2] < lower && buffer[i-1] < upper && buffer[i] >= upper) {
return i; // 严格上升沿
}
}
return -1;
}
九、常见问题与调试技巧
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 波形抖动 | 触发不稳定/噪声干扰 | 增加触发滞后,启用平均滤波 |
| 显示花屏 | SPI速率过高/接线不良 | 降低SPI速率,检查接线 |
| 采样率低 | ADC时钟配置错误 | 检查RCC_ADCCLKConfig |
| 波形失真 | 输入信号超量程 | 切换衰减档位,调整垂直灵敏度 |
| 频率测量不准 | 信号噪声大/过零点抖动 | 增加采样点数,使用插值算法 |
| 屏幕刷新慢 | 全屏重绘效率低 | 实现局部刷新,优化绘制算法 |
十、总结与展望
本项目从硬件电路到软件算法,完整实现了一台基于STM32的简易数字示波器。核心要点总结:
- 采样率是基础:理解奈奎斯特定理,合理配置ADC时钟和采样时间
- 触发是灵魂:实现稳定的触发机制是波形显示的关键
- 时基决定视野:通过调整采样率实现不同时间尺度的信号观察
- DMA解放CPU:双缓冲机制实现高效的数据流传输
- 显示优化体验:合理的UI布局和绘制算法提升使用体验
后续升级方向:
- 增加FFT频谱分析功能,实现频域观察
- 支持双通道同时显示,对比两个信号
- 加入USB数据传输,连接PC进行深度分析
- 升级为彩色波形显示,不同通道用不同颜色
- 增加波形存储与回放功能,保存重要信号
转载自:https://blog.csdn.net/u014727709/article/details/162450016
欢迎 👍点赞✍评论⭐收藏,欢迎指正