基于STM32实现简易示波器
基于STM32实现简易示波器
示波器是电子设计和调试中不可或缺的工具,用于观察信号的波形及其特性。然而,市场上的商业示波器价格较高,对于学习或简单应用来说,使用微控制器(如STM32)实现一个简易示波器是一个很好的选择。不仅可以节约成本,还能加深对信号采集、处理和显示的理解。本文将详细介绍如何基于STM32实现一个简易的示波器功能,包括系统架构、关键技术等。
一、系统架构
一个简易示波器的基本功能包括信号采集、数据处理和波形显示。基于STM32的设计可大致分为以下模块:
- 信号采集模块:利用ADC(模数转换器)采集模拟信号,将其转换为数字信号。
- 数据处理模块:对采集到的信号进行FFT算法,来计算频率等参数。
- 显示模块:通过LCD、TFT屏幕或串行通信(如USART)将波形呈现出来。这里使用的是陶晶驰串口屏(TJC4827X243_011R)。其UI界面如下图所示
可以显示频率,峰峰值,有效值以及实时波形更新。
硬件准备
- 核心板:STM32微控制器(本样例采用STM32F103C8T6最小系统板)。
- 屏幕:陶晶驰串口屏(支持串口通信)。
- 信号输入端:信号发生器或自制的模拟信号电路。
- 电源模块:为STM32和外围设备提供稳定的电源。
- 必要的连接线:用于屏幕、信号输入与STM32的连接。
二、核心功能实现
(1) 信号采集
1. 采样脉冲初始化
采样脉冲的生成基于 TIM2 定时器的 PWM 输出。该功能为 ADC 提供触发信号,确保采样的稳定性。
void TIM2_PWM_init(void) {
arr1 = 45;
psc1 = 150;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr1;
TIM_TimeBaseStructure.TIM_Prescaler = psc1;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_CtrlPWMOutputs(TIM2, ENABLE);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_OC2Ref);
}
2. ADC 数据采集与 DMA 配置
通过 ADC 将模拟信号转换为数字信号,并使用 DMA 实现高速传输。
在传统数据传输方式中,CPU 需要逐次读取 ADC 的采样结果并将其存储到内存,这种方式不仅占用了大量的 CPU 时间,还可能导致在高速采样场景下的数据丢失。而 DMA(直接存储器访问)模块可以在无需 CPU 参与的情况下,将 ADC 的采样结果直接传输到内存,大大减轻了 CPU 的负担,使其能够专注于其他任务处理。同时,DMA 的循环模式支持连续数据传输,保证了采样过程的稳定性。
以下是 ADC 和 DMA 配置的代码实现:
void AD_FFT_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = nn;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
}
(2) 数据处理
1. FFT 数据处理
通过 STM32 的 DSP 库对采集到的数据进行 FFT 计算,从而提取信号的频率和幅值。
void fre_calcu(void) {
signed short lX, lY;
float X, Y, Mag;
unsigned short i;
fus = 72000000 / (arr1 * psc1);
adxmax = AD_Value[0];
adxmin = AD_Value[0];
for (i = 0; i < nn; i++) {
fftin[i] = ((u32)(AD_Value[i])) << 16;
if ((float)AD_Value[i] >= adxmax) adxmax = (float)AD_Value[i];
if ((float)AD_Value[i] <= adxmin) adxmin = (float)AD_Value[i];
}
adxmin = adxmin / 4095 * 3.3;
adxmax = adxmax / 4095 * 3.3;
Bias = (adxmin + adxmax) / 2;
Af = adxmax - adxmin;
cr4_fft_1024_stm32(fftout, fftin, nn);
for (i = 0; i < nn / 2; i++) {
lX = (fftout[i] << 16) >> 16;
lY = (fftout[i] >> 16);
X = nn * ((float)lX) / 32768;
Y = nn * ((float)lY) / 32768;
Mag = sqrt(X * X + Y * Y) / nn;
fftbuffer[i] = (unsigned long)(Mag * 32768);
}
Mag = fm = 0;
for (i = 1; i < nn / 2; i++) {
if (Mag < fftbuffer[i]) {
Mag = fftbuffer[i];
fm = i;
}
}
F = (u32)(fm * fus / nn);
}
(3) 数据显示
3. 波形更新与显示
通过定时器中断定期更新波形数据,并通过串口发送到串口屏中。
void TIM4_IRQHandler(void) {
if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET) {
fre_calcu();
Serial_SendHZ(F);
Serial_SendVPP(Af);
Serial_SendRMS(Af / 2 / 1.414);
if(F-F1 > 10 | fabs(Af-Af1) > 0.08)
{
for(i=0;i<255;i++)
{
Vol[i] = (float)((float)AD_Value[i] / 4095 * 3.3);
Data[i] = (int)((0.188*Af+0.248)*(Vol[i]-Bias)/Af*128+127);
}
Serial_SendData(Data);
F1=F;Af1=Af;
}
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
}
到这里,简易示波器的大致流程就到这里。
三、注意事项
以下是润色和修改后的内容,保持了专业性和易读性:
- 关于波形实时变化
由于ADC读取数据、单片机处理数据并通过串口发送到串口屏之间存在一定延迟,我在此基础上设计了一个小算法,使波形能够实现实时变化。但需要注意的是,这种变化是实时的,并非完全动态变化。
2.自定义串口屏的函数
为了实现串口屏的发送和接收功能,需要自定义相关的函数。例如,在文章中定义了类似于
Serial_SendHZ()
和
Serial_SendVPP()
的函数,用于具体的功能实现。
3.ADC读取范围的限制
本项目的ADC设计为仅支持读取0~3.3V范围内的输入信号。如果输入信号的峰峰值超过3.3V,由于STM32内置ADC的限制,将会导致测量不准确,并出现严重失真的情况。
4.数据映射关系
串口屏的波形控件仅支持显示范围为0255的整数值,而ADC读取的是03.3V的模拟信号。因此,需要将ADC的03.3V线性映射到0255之间,以便波形控件能够正确显示数据。
以下是之前项目中拍摄的示例图:输入为频率100Hz、峰峰值0.65V的正弦波信号,经过处理后在串口屏上显示的效果。
四、总结
本文介绍了如何使用 STM32 实现一个简易示波器功能,从采样脉冲生成到信号频谱分析,再到波形显示,涵盖了完整的实现流程。通过此项目,您不仅能够深入理解 STM32 的定时器、ADC、DMA 和 DSP库功能,还可以掌握基础的信号处理技术。这为后续开发更复杂的电子测量工具打下了坚实的基础。如果发现有任何错误,请随时指出。如果反馈的人数较多,我将考虑将项目开源。接下来,我会更新关于电路特性测试仪的相关内容。