使用STM32做一个简易的示波器

发布者:RadiantEyes最新更新时间:2024-04-30 来源: elecfans关键字:STM32  示波器  频率  幅值 手机看文章 扫描二维码
随时随地手机看文章

一、前言

该项目是基于正点原子精英板制作的一个简易示波器,可以读取信号的频率和幅值,并可以通过按键改变采样频率和控制屏幕的更新暂停。

75b240cc-6c74-11ed-8abf-dac502259ad0.png

二、硬件接线

将PA6与PA4相连,可观察到正弦波。

将PA6与PA5相连,可观察到三角波/噪声(默认三角波)。

KEY_UP控制波形的更新和暂停。

KEY_1降低采样率。

KEY_0提高采样率。

三、信号的采集

信号的采集主要是依靠ADC(通过定时器触发采样,与在定时器中断中开启一次采样的效果类似,以此来控制采样的间隔时间相同),然后通过DMA将所采集的数据从ADC的DR寄存器转移到一个变量中,此时完成一次采样。

由于设定采集一次完整的波形需要1024个点,即需要连续采集1024次才算一次完整的波形采样(需要采集1024个点的原因在后面会提到)。

因此我们还需创建一个数组用于存储这些数据,并在DMA中断中,将成功转移到变量中的数据依次存储进数组(注意此数组中存入的数据是12位的数字量,还未做回归处理),完成1024个数据的采样和储存,用于后续在LCD上进行波形的显示和相关参数的处理。

此案例用到的是ADC1的通道6(即PA6口)进行数据的采样,主要需注意将ADC转换的触发方式改为定时器触发(我用的是定时器2的通道2进行触发,由于STM32手册提示只有在上升沿时可以触发ADC,因此我们需要让定时器2的通道2每隔固定的时间产生一个上升沿)。

将定时器2设置成PWM模式,即可令ADC1在定时器2的通道2每产生一次上升沿时触发采样,后续即可通过改变PWM的频率(即定时器的溢出频率),便可控制采样的频率。


四、代码配置


ADC的配置:


 


/**********************************************************

简介:ADC1-CH6初始化函数

***********************************************************/                  

void  Adc_Init(void)

{  

 ADC_InitTypeDef ADC_InitStructure; 

 GPIO_InitTypeDef GPIO_InitStructure;


 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1, ENABLE );   //使能ADC1通道时钟

 


 RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M


 //PA6 作为模拟通道输入引脚                         

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  //模拟输入

 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

 GPIO_Init(GPIOA, &GPIO_InitStructure); 


 ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值


 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1工作在独立模式

 ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式

 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在非连续转换模式

 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2; //转换由定时器2的通道2触发(只有在上升沿时可以触发)

 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐

 ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目

 ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   


 ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1

 

 ADC_DMACmd(ADC1, ENABLE); //ADC的DMA功能使能

 

 ADC_ResetCalibration(ADC1); //使能复位校准  

  

 ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_1Cycles5 );//ADC1通道6,采样时间为239.5周期  

  

 ADC_ResetCalibration(ADC1);//复位较准寄存器

  

 while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束

 

 ADC_StartCalibration(ADC1);  //开启AD校准

 

 while(ADC_GetCalibrationStatus(ADC1));  //等待校准结束

 

 ADC_SoftwareStartConvCmd(ADC1, ENABLE);  //使能指定的ADC1的软件转换启动功能


}   

 


定时器的配置:


 


/******************************************************************

函数名称:TIM2_PWM_Init(u16 arr,u16 psc)

函数功能:定时器3,PWM输出模式初始化函数

参数说明:arr:重装载值

   psc:预分频值

备    注:通过TIM2-CH2的PWM输出触发ADC采样

*******************************************************************/  

void TIM2_PWM_Init(u16 arr,u16 psc)

{  

 GPIO_InitTypeDef GPIO_InitStructure;

 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

 TIM_OCInitTypeDef  TIM_OCInitStructure;

 

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能定时器2时钟

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟

 

   //设置该引脚为复用输出功能,输出TIM2 CH2的PWM脉冲波形 GPIOA.1

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //TIM_CH2

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出

 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO

 

   //初始化TIM3

 TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值

 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 

 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim

 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式

 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

 

 //初始化TIM2 Channel2 PWM模式  

 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2

  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能

 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高

 TIM_OCInitStructure.TIM_Pulse=1000; //发生反转时的计数器数值,用于改变占空比

 TIM_OC2Init(TIM2, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM2


 TIM_CtrlPWMOutputs(TIM2, ENABLE);//使能PWM输出

 

 TIM_Cmd(TIM2, ENABLE);  //使能TIM2

}

 


DMA配置:


 


/******************************************************************

函数名称:MYDMA1_Config()

函数功能:DMA1初始化配置

参数说明:DMA_CHx:DMA通道选择

   cpar:DMA外设ADC基地址

   cmar:DMA内存基地址

   cndtrDMA通道的DMA缓存的大小

备    注:

*******************************************************************/

void MYDMA1_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)

{

 DMA_InitTypeDef DMA_InitStructure;

 NVIC_InitTypeDef NVIC_InitStructure;

 

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输

 

    DMA_DeInit(DMA_CHx);   //将DMA的通道1寄存器重设为缺省值

 DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;  //DMA外设ADC基地址

 DMA_InitStructure.DMA_MemoryBaseAddr = cmar;  //DMA内存基地址

 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //数据传输方向,从外设读取发送到内存//

 DMA_InitStructure.DMA_BufferSize = cndtr;  //DMA通道的DMA缓存的大小

 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变

 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增

 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  //数据宽度为16位

 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位

 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  //工作在循环模式

 DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级 

 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输

 DMA_Init(DMA_CHx, &DMA_InitStructure);  //ADC1匹配DMA通道1

 

 DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE); //使能DMA传输中断 

 

 //配置中断优先级

 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;

 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;

 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  

 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   

 NVIC_Init(&NVIC_InitStructure); 


 DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA通道

}

 


注意:


由于在设置PWM时将TIM_Pulse默认设置为1000,因此在初始化定时器2时,TIM_Period的值不能小于该值,可自行修改。TIM_Pulse的值并不会影响采样频率。


采样频率= 定时器2溢出频率=SYSCLK/预分频值/溢出值因此如果将TIM_Pulse设为1,TIM_Period设为2,TIM_Prescaler设为1,理论上采样频率最高可达36Mhz。


五、数据的处理


数据的处理主要是要求出信号的频率和幅值等相关参数。幅值可以通过找出之前存储1024个点的数组中最大最小值,回归处理过后算出差值。


难点主要在于频率的求取。一个信号中可能包含多种频率成分,而我显示的是幅值最大的频率分量(当然其他频率也可获得)。这里便用到了STM32提供的DSP库中的FFT(快速傅里叶变换),DSP库在最后的源码中有。


需要采样1024个点的原因:FFT算法要求样本数为2的n次方,而DSP库中提供了64,256和1024样本数对应的库函数,因此选用1024最大样本数可以使频率分辨率最小,更加精确。(定义频率分辨率f0=fs/N,其中fs等于采样率,N为采样点数)


需注意:FFT后的输出不是实际的信号频率,需要经过转换。f(k)=k*(fs/N),其中f(k)是实际频率,k是实际信号的最大幅度频率所对应的数。(详见下面代码,分享的源代码中公式有误,未重新上传)


获取频率的函数:


 


#define NPT 1024//一次完整采集的采样点数


/******************************************************************

函数名称:GetPowerMag()

函数功能:计算各次谐波幅值

参数说明:

备  注:先将lBufOutArray分解成实部(X)和虚部(Y),然后计算幅值(sqrt(X*X+Y*Y)

*******************************************************************/

void GetPowerMag(void)

{

    float X,Y,Mag,magmax;//实部,虚部,各频率幅值,最大幅值

    u16 i;

 

 //调用自cr4_fft_1024_stm32

 cr4_fft_1024_stm32(fftout, fftin, NPT); 

 //fftin为傅里叶输入序列数组,ffout为傅里叶输出序列数组

 

    for(i=1; i> 16;

  Y = (fftout[i] >> 16);

  

  Mag = sqrt(X * X + Y * Y); 

  FFT_Mag[i]=Mag;//存入缓存,用于输出查验

  //获取最大频率分量及其幅值

  if(Mag > magmax)

  {

   magmax = Mag;

   temp = i;

  }

    }

 F=(u16)(temp*(fre*1.0/NPT));//源代码中此公式有误,将此复制进去

 

 LCD_ShowNum(280,180,F,5,16);

 


六、模拟正弦波输出


此正弦波输出是用于调试示波器,观察显示和实际是否相同。主要利用DAC输出,在定时器3的中断中不断改变DAC的输出值,产生一个正弦波。因此改变正弦波的频率可以通过更改定时器3的溢出频率。(采用的PA4口进行输出)


在初始化时,我将定时器3的重装载值设置为40,预分频值设置为72,正弦波输出频率为72Mhz/40/72/1024≈24.5Hz(1024是因为将一个周期正弦波均分成1024个输出点,详见下面函数InitBufInArray())。


经采样处理后显示为24-25Hz,与实际值接近。(但是当采样频率提高到最大3.6kHz时,频率显示为32Hz左右,原因未知)


下面是相关代码:


 


u16 magout[NPT];

/******************************************************************

函数名称:InitBufInArray()

函数功能:正弦波值初始化,将正弦波各点的值存入magout[]数组中

参数说明:

备    注:

*******************************************************************/

void InitBufInArray(void)

{

    u16 i;

    float fx;

    for(i=0; i=NPT)

  i=0;

}

 


七、模拟噪声或三角波输出


模拟噪声或三角波输出可直接通过配置DAC,利用芯片内部的发生器产生。DAC2的转换由定时器4的TRGO触发(事件触发)。同时需要注意设置TRGO由更新事件产生。


若为三角波输出,频率=72Mhz/定时器重装载值/预分频系数/幅值/2;


例如:初始化定时器的重装载值为2,预分频系数为36,幅值为最大(4096),即Freq=72Mhz/2/36/4096/2≈122Hz;


具体代码如下所示:


 


void Dac2_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;

    DAC_InitTypeDef DAC_InitType;


    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );   //使能PORTA通道时钟

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE );   //使能DAC通道时钟 


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;     // 端口配置

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;    //模拟输入

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

     

    DAC_InitType.DAC_Trigger=DAC_Trigger_T4_TRGO; //定时器4触发

    DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_Noise;//产生噪声

    //DAC_WaveGeneration_Triangle产生三角波

    DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude =  DAC_TriangleAmplitude_4095;//幅值设置为最大,即3.3V

    DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; //DAC1输出缓存关闭 BOFF1=1

[1] [2]
关键字:STM32  示波器  频率  幅值 引用地址:使用STM32做一个简易的示波器

上一篇:STM32 单片机开发中的 RTOS应用分析
下一篇:不用串口,如何打印STM32单片机log

推荐阅读最新更新时间:2024-11-07 21:57

STM32串口中断的一些资料
在研究STM32串口接收发送中断的时候找到不少不错的资料,现在备份在这里。以供自己查阅,以及方便其他人。 TC ====TXE 顺便预告下最近会写个有关串口处理数据的帖子,从查询和中断方面以及数据处理的方式,从队列以及FIFO方面写起。 SECTION 1 SECTION 2 先说TC。即Transmission Complete。发送一个字节后才进入中断,这里称为 发送后中断 。和原来8051的TI方式一样,都是发送后才进中断,需要在发送函数中先发送一个字节触发中断。发送函数如下 /* 功能:中断方式发送字符串.采用判断TC的方式.即 判断 发送后中断 位. 输入:字符串的首地址 输出:无 */ void U
[单片机]
STM32中较为常见的C语言基础知识
C语言是单片机开发中的必备基础知识,本文列举了部分 STM32 学习中比较常见的一些C语言基础知识。 1位操作 下面我们先讲解几种位操作符,然后讲解位操作使用技巧。C语言支持以下六种位操作: 下面,重点讲解一下位操作在单片机开发中的一些实用技巧。 在不改变其他位的值的状况下,对某几个位进行设值 这个场景在单片机开发中经常使用,方法就是我们先对需要设置的位用&操作符进行清零操作,然后用 | 操作符设值。 比如,我要改变 GPIOA 的状态,可以先对寄存器的值进行&清零操作: GPIOA- CRL&=0xFFFFFF0F;/*将第4~7位清零*/ 然后再与需要设置的值进行 |
[单片机]
<font color='red'>STM32</font>中较为常见的C语言基础知识
MAX30102空气质量监测模块STM32源程序与资料
电路原理图如下: 功能实现:采用STM32F103C8T6小板,驱动血氧心率传感器max30102,实现PPG信号采集,并将计算的心率和血氧值显示在0.96寸OLED和串口上。 软件实现:ST标准库3.5 硬件连接: MAX30102: VCC - 3.3V GND - GND SCL - PB7 SDA - PB8 IM - PB9 0.96inch OLED : VCC - 3.3V GND - GND SCL - PA5 SDA - PA6 RST - PA3 DC - PA4 CS - PA2 USB-TTL: 5V - 5V GND - GND RXD -
[单片机]
MAX30102空气质量监测模块<font color='red'>STM32</font>源程序与资料
STM32串口通信数据乱码的相关问题
STM32串口通信以及温度采集搞定,其中主要遇到STM32系列单片机时钟树的问题,串口通信遇到串口调试助手能够接收到数据但出现乱码现象,开始一直以为是串口配置和程序代码问题,因为是第一次上电在线调试STM32板子,后面主要查串口波特率配置和收发函数程序段,如下图: 波特率设置成115200没问题,试着降低波特率改成9600和4800但问题依旧没有解决,紧接着如下处理: 将重定向函数注释,单独写串口发送字节和字符串函数,依旧失败。最后锁定到系统时钟配置上,由于手上的STM32开发板改用了12M的晶振,根据单片机时钟树的理解和解读,一般采用外部时钟HSE,系统时钟配置成72M,8*9=72,,12*6=72,对于 SYSCLK
[单片机]
<font color='red'>STM32</font>串口通信数据乱码的相关问题
STM32小知识笔记
APB2负责AD,I/O,高级TIM(TIM1,TIM8),串口1。 APB1负责DA,USB,SPI,I2C,CAN,串口2345,普通TIM。 110:PWM模式1- 在向上计数时,一旦TIMx_CNT TIMx_CCR1时通道1为有效电平,否则为 无效电平;在向下计数时,一旦TIMx_CNT TIMx_CCR1时通道1为无效电平(OC1REF=0),否 则为有效电平(OC1REF=1)。 111:PWM模式2- 在向上计数时,一旦TIMx_CNT TIMx_CCR1时通道1为无效电平,否则为 有效电平;在向下计数时,一旦TIMx_CNT TIMx_CCR1时通道1为有效电平,否则为无效电 平。 关于有效电平这句可以设定:
[单片机]
STM32_TIM3_PWM_MDK
最近要驱动电机,看了下PWM的输出,有所总结,这次贴上。 stm32 的高级定时器 比较复杂,看了下例程,设置的东西太多了,就没有碰, 看了通用定时器还比较可爱,什么都刚刚好够用,就用它做了,这次用的是TIM3定时器3。 硬件平台用的是stm32f103vet6 100脚的。 由于手头没有示波器,所以用软件仿真,先上一张仿真图: 下面是代码部分Timer3.c C语言: Codee#20528 #include Timer3.h /******************************************************************************* * Func
[单片机]
STM32_TIM3_PWM_MDK
泰克示波器解密电源纹波测试误区
现今社会,电子产品已经广泛应用到日常生活中。电子产品都离不开电源,而电源部件也是大多数电子产品故障的最主要原因。泰克示波器是电源设计工程师必不可少的测试工具。 输出电压纹波是指叠加在直流电压上的交流成分,是电源测试中的一个很重要的指标。开关电源的电压纹波一般由开关电压经电感电容滤波后产生(由开关频率,输入输出电压,拓扑结构,电感,电容决定),同时还受工频电压整流,干扰,负载波动等的影响。电压纹波做为一个输出电压指标,要满足后端元器件或设备使用要求。如果测试结果不满足要求,一般需要增加滤波器件或者改变设计,造成项目成本的增加或交期延长。 但是,电源纹波测试中工程师常常会遇到以下测试误区: 1. 带宽限制 带宽限制是使用示波器
[测试测量]
泰克<font color='red'>示波器</font>解密电源纹波测试误区
STM32 HAL库串口收发是如何使用的?
STM32是一款高性能的微控制器,它拥有广泛的应用领域,其中包括了各种通讯应用,如UART串口通讯。HAL库是ST公司为了方便开发者使用STM32而开发的一种库,它提供了一种简单易用的方法来使用STM32的各种外设。 本文将详细介绍如何使用STM32 HAL库来进行串口通信,包括初始化、发送数据和接收数据等方面。 1. 初始化串口 首先需要初始化串口外设,按照HAL库的方法,我们需要定义一个串口句柄,然后对句柄中的各项参数进行赋值,包括波特率、数据位、停止位、奇偶校验位等等。根据不同的外设,具体的初始化内容可能会稍有不同。 示例代码如下: ```c UART_HandleTypeDef huart; void UART_Init(
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved