0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于STM32的开源简易示波器项目

嵌入式开发爱好者 来源:嵌入式开发爱好者 2023-05-08 09:35 次阅读

一、前言

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

34629186-ecca-11ed-90ce-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初始化函数
***********************************************************/
voidAdc_Init(void)
{
ADC_InitTypeDefADC_InitStructure;
GPIO_InitTypeDefGPIO_InitStructure;

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


RCC_ADCCLKConfig(RCC_PCLK2_Div6);//设置ADC分频因子672M/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(u16arr,u16psc)
函数功能:定时器3,PWM输出模式初始化函数
参数说明:arr:重装载值
 psc:预分频值
备注:通过TIM2-CH2的PWM输出触发ADC采样
*******************************************************************/
voidTIM2_PWM_Init(u16arr,u16psc)
{
GPIO_InitTypeDefGPIO_InitStructure;
TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
TIM_OCInitTypeDefTIM_OCInitStructure;

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

//设置该引脚为复用输出功能,输出TIM2CH2的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的时间基数单位

//初始化TIM2Channel2PWM模式
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缓存的大小
备注:
*******************************************************************/
voidMYDMA1_Config(DMA_Channel_TypeDef*DMA_CHx,u32cpar,u32cmar,u16cndtr)
{
DMA_InitTypeDefDMA_InitStructure;
NVIC_InitTypeDefNVIC_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是实际信号的最大幅度频率所对应的数。(详见下面代码,分享的源代码中公式有误,未重新上传)

获取频率的函数:

#defineNPT1024//一次完整采集的采样点数

/******************************************************************
函数名称:GetPowerMag()
函数功能:计算各次谐波幅值
参数说明:
备注:先将lBufOutArray分解成实部(X)和虚部(Y),然后计算幅值(sqrt(X*X+Y*Y)
*******************************************************************/
voidGetPowerMag(void)
{
floatX,Y,Mag,magmax;//实部,虚部,各频率幅值,最大幅值
u16i;

//调用自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左右,原因未知)

下面是相关代码:

u16magout[NPT];
/******************************************************************
函数名称:InitBufInArray()
函数功能:正弦波值初始化,将正弦波各点的值存入magout[]数组中
参数说明:
备注:
*******************************************************************/
voidInitBufInArray(void)
{
u16i;
floatfx;
for(i=0;i=NPT)
i=0;
}

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

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

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

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

具体代码如下所示:

voidDac2_Init(void)
{
GPIO_InitTypeDefGPIO_InitStructure;
DAC_InitTypeDefDAC_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
DAC_Init(DAC_Channel_2,&DAC_InitType);//初始化DAC通道2

DAC_Cmd(DAC_Channel_2,ENABLE);//使能DAC-CH2

DAC_SetChannel1Data(DAC_Align_12b_R,0);//12位右对齐数据格式设置DAC值
}
voidTIM4_Int_Init(u16arr,u16psc)
{
TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//时钟使能

TIM_TimeBaseStructure.TIM_Period=arr;//设置在下一个更新事件装入活动的自动重装载寄存器周期的值计数到5000为500ms
TIM_TimeBaseStructure.TIM_Prescaler=psc;//设置用来作为TIMx时钟频率除数的预分频值10Khz的计数频率
TIM_TimeBaseStructure.TIM_ClockDivision=0;//设置时钟分割:TDTS=Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//TIM向上计数模式
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseStructure);//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

TIM_SelectOutputTrigger(TIM4,TIM_TRGOSource_Update);//触发外设方式为更新触发

TIM_Cmd(TIM4,ENABLE);//使能TIMx外设

}

八、显示函数与按键控制

显示波形只需将所获得的1024个采样数据选择一部分进行显示大致思路如下:

u16pre_vol;//当前电压值对应点的纵坐标
u16past_vol;//前一个电压值对应点的纵坐标
//adcx[]数组及通过DMA存入的1024个原始数据
pre_vol=50+adcx[x]/4096.0*100;
LCD_DrawLine(x,past_vol,x+1,pre_vol);//根据实际,打点位置可进行相应更改
past_vol=pre_vol;

按键的控制是在外部中断中进行(正点原子资料中提供相应参考代码)比较重要的是改变采样频率。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 示波器
    +关注

    关注

    111

    文章

    5659

    浏览量

    181780
  • adc
    adc
    +关注

    关注

    95

    文章

    5651

    浏览量

    539481
  • STM32
    +关注

    关注

    2239

    文章

    10673

    浏览量

    348770
  • 定时器
    +关注

    关注

    23

    文章

    3147

    浏览量

    112040
  • 开源
    +关注

    关注

    3

    文章

    2986

    浏览量

    41720

原文标题:基于STM32的开源简易示波器项目

文章出处:【微信号:嵌入式开发爱好者,微信公众号:嵌入式开发爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    使用STM32做一个简易示波器(工程已开源

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

    Proteus:简易示波器

    Proteus:简易示波器
    的头像 发表于 06-14 11:35 1258次阅读
    Proteus:<b class='flag-5'>简易</b><b class='flag-5'>示波器</b>

    基于STM32制作的500KHz示波器项目

    今天小编给大家带来国外的Maker Mirko Pavleskid的基于STM32制作的500KHz示波器项目
    的头像 发表于 12-03 10:07 833次阅读
    基于<b class='flag-5'>STM32</b>制作的500KHz<b class='flag-5'>示波器</b><b class='flag-5'>项目</b>

    基于stm32简易示波器

    基于stm32简易示波器,笔记目录一.设计要求二.整体思路三.硬件设计1.功能部分2.电源部分3.stm32f407核心板及3.2寸的TFT显示屏四.软件设计1.主函数的设计2.测试
    发表于 08-09 09:14

    如何去制作一个基于STM32简易示波器

    如何去制作一个基于STM32简易示波器呢?基于STM32简易示波器有何功能呢?
    发表于 11-12 07:07

    如何利用stm32mini设计制作简易示波器简易函数发生器?

    如何利用stm32mini设计制作简易示波器简易函数发生器?
    发表于 11-25 07:06

    基于STM32简易示波器设备驱动的研究

    基于STM32简易示波器设备驱动的研究
    发表于 11-06 17:04 68次下载

    简易数字存储示波器设计

    设计一简易数字存储示波器简易DSO)。
    发表于 03-18 15:20 406次下载
    <b class='flag-5'>简易</b>数字存储<b class='flag-5'>示波器</b>设计

    STM32G0简易示波器与信号发生器项目

    写在前面 今年寒假我完成了硬禾学堂的STM32G0简易示波器与信号发生器项目(网址:https://www.eetree.cn/project/detail/167),暑假这次活动的开
    的头像 发表于 09-14 09:47 2759次阅读
    <b class='flag-5'>STM32</b>G0<b class='flag-5'>简易</b><b class='flag-5'>示波器</b>与信号发生器<b class='flag-5'>项目</b>

    stm32简易示波器(标准库)

    简介此项案例是基于正点原子精英板制作的一个简易示波器,可以读取信号的频率和幅值,并可以通过按键改变采样频率和控制屏幕的更新暂停。也是通过学习网上大佬的经验加以改编。下面将介绍所用到的基本原理以及相关
    发表于 11-23 18:21 34次下载
    <b class='flag-5'>stm32</b><b class='flag-5'>简易</b><b class='flag-5'>示波器</b>(标准库)

    基于stm32mini开发板的简易函数发生器和简易示波器

    基于stm32 mini开发板的简易示波器
    发表于 01-17 10:01 35次下载
    基于<b class='flag-5'>stm32</b>mini开发板的<b class='flag-5'>简易</b>函数发生器和<b class='flag-5'>简易</b><b class='flag-5'>示波器</b>

    ERCF简易BRD开源项目

    电子发烧友网站提供《ERCF简易BRD开源项目.zip》资料免费下载
    发表于 07-18 15:42 2次下载
    ERCF<b class='flag-5'>简易</b>BRD<b class='flag-5'>开源</b><b class='flag-5'>项目</b>

    示波器Murzik开源项目

    电子发烧友网站提供《示波器Murzik开源项目.zip》资料免费下载
    发表于 07-18 11:37 9次下载
    <b class='flag-5'>示波器</b>Murzik<b class='flag-5'>开源</b><b class='flag-5'>项目</b>

    手持式示波器开源项目

    电子发烧友网站提供《手持式示波器开源项目.zip》资料免费下载
    发表于 12-01 14:12 2次下载
    手持式<b class='flag-5'>示波器</b><b class='flag-5'>开源</b><b class='flag-5'>项目</b>

    基于单片机的简易示波器设计

    学习单片机,通过做简单的小项目,是成长最快的一种方法。今天就给大家分享一个制作简易示波器的小项目,代码开源,希望对初学者有帮助。
    的头像 发表于 08-30 09:22 964次阅读
    基于单片机的<b class='flag-5'>简易</b><b class='flag-5'>示波器</b>设计