精华内容
下载资源
问答
  • STM32FFT算法的实现说明,有详细的说明具体的求解过程适合新手学习哦
  • 本例程采用C语言版本的FFT算法对一个软件合成的信号进行FFT变换,并将FFT变换后每个频点的幅值打印输出。 信号合成程序: FFT变换后幅值输出如下: 性能测试(STM32 72M):
  • 一个使用了stm32_hal库的fft工程文件
  • STM32_FFT测信号相位.zip

    2020-02-23 09:14:30
    本程序是采用STM32F407ZGT6,利用AD+DMA采集叠加正弦波数据,在DMA中断中采用FFT+滤波,通过DDS9599将需要的波形输出,是本学校的电子设计大赛-《自适应滤波器》程序,现分享给大家,欢迎下载,有什么问题私聊我。
  • STM32FFT官方库.zip

    2019-07-21 11:54:38
    STM32 FFT官方库,导入自己的工程文件,直接调用就行了
  • STM32 FFT算法 C语言

    2011-08-24 11:02:57
    STM32单片机的fft程序,有函数描述和数据类型定义
  • 但是我们通过STM32F4单片机的强大计算能力,运用FFT变换,可以直接对正弦信号直接测量。且精度更可靠。不过由于STM32F4本身的限制,信号频率大于200K之后误差就会有所增加。我这里是用的7针OLED屏显示的参数。F4系列...
  • stm32FFT程序.rar

    2020-08-04 09:13:27
    stm32f103 FFT的程序源码,已注释,对单片机adc获取的电压值就行快速傅里叶变换,进行频谱分析
  • STM32 FFT算法实现

    2012-12-26 12:05:45
    在原子哥的开发板上实现的FFT算法和UCOS。全部资料均来自网络,谢谢原子哥,谢谢网络上无私的朋友
  • 里面有两个文件夹,第一个是基础版本,第二个可以触屏改变采样频率,进而优化频率分辨率,使频谱分析更精确。频谱分析(50Hz~200Hz,其他范围内应该也可以)包括了基频,3,5,7次谐波的峰值,波形识别可识别正弦,...
  • (1) 本资源基于正点原子stm32F103mini板,...(3)采用stm32官方DSP库的FFT算法处理数据(64,256,1024点处理) (4)计算正弦波失真度。 (5) 采样频率可自由配置,修改相关参数即可。 (6)具体操作详见README.md
  • FFTSTM32处理器上的实现完整代码,可以直接运行在STM32系列芯片上
  • 该资源为,将FFT算法移植到STM32F1系列上,通过ADC采集例如音频信号,并将信号通过算法实时动态的显示在LCD上,实现一个动态音乐频谱。
  • HAL库实现stm32f4实数FFT的逆变换实现,实数FFT的逆变换,实数FFT的逆变换!
  • STM32 FFT DMA ADC THD

    千次阅读 2020-10-30 21:25:37
    利用STM32 FFT算法计算THD 一、设备准备 ——>粤嵌STM32F429IGT6开发板 1块 ——>串口调试助手 二、FFT算法意义 使用FFT算法,是为了获取信号在频域的相关参数,即信号的频谱。包括信号在频谱上各点的频率和该...

    利用STM32 FFT算法计算THD

    一、设备准备

    ——>粤嵌STM32F429IGT6开发板 1块
    ——>串口调试助手

    二、FFT算法意义

    使用FFT算法,是为了获取信号在频域的相关参数,即信号的频谱。包括信号在频谱上各点的频率和该点的幅值。

    由上面的调制信号和已调信号的频谱图,我们可以观察到正弦信号在未经过调制前,其频谱图仅仅在0频附近有一条谱线,这条谱线即为未调制信号的频谱。同时,我们可以观察到调制后的正弦信号的频谱发生了搬移,即从零频附近搬移到载波信号的频谱处。
    首先让我们理解一个概念,信号的组成:任何连续测量的时序或信号,都可以表示为不同频率的正弦波信号的无限叠加。而根据该原理创立的傅立叶变换算法利用直接测量到的原始信号,以累加方式来计算该信号中不同正弦波信号的频率、振幅和相位。
    其实,简单的理解,观察信号的角度从时域转换到频域,对观察者最大的好处是:在频域,信号的频谱图能够反映该信号所含的各种频率成分的信号及它们的幅值。
    傅里叶级数的意义

    三、DSP实际使用方法

    我们使用ST公司提供的DSP库中的FFT算法。
    使用步骤如下:

    - STM32F4 DSP简介

    STM32F4采用Cortex-M4内核,相比Cortex-M3系列除了内置硬件FPU单元(浮点运算单元),FPU是专用于浮点运算的处理器,在数字信号处理方面还增加了DSP指令集,支持诸如单周期乘加指令(MAC),优化的单指令多数据指令(SIMD),饱和算数等多种数字信号处理指令集。Cortex-M4执行所有的DSP指令都可以在单周期内完成,而Cortex-M3需要多个指令和多个周期才能完成同样的功能。

    - 下载DSP_Lib源码包

    找到DSP_Lib源码包,该文件夹目录结构如下图所示:
    在这里插入图片描述
    DSP_Lib源码包的Source文件夹是所有DSP库的源码,Examples文件夹是相对应的一些测试实例。这些测试实例都是带main函数的,也就是拿到工程中可以直接使用。
    在这里插入图片描述
    查阅原子STM32F4开发指南-寄存器版本,P699页,了解各种源码的功能介绍。

    - 搭建DSP运行环境
    只要DSP库运行环境搭建好了,使用DSP库里面的函数来做相关处理就非常简单。在MDK里面搭建STM32F4的DSP运行环境(使用.lib方式)是很简单的,分为3个步骤:

    1、添加文件
    在例程工程目录下新建:DSP_LIB文件夹,存放要添加的arm_correxM4lf_math.lib和相关头文件。
    在这里插入图片描述
    在这里插入图片描述

    打开工程,新建DSP_LIB分组,并将arm_cortexM4lf_math.lib添加到工程里面。

    在这里插入图片描述
    如上面两图所示,这样添加文件就结束了。

    2、添加头文件包含路径。
    添加好了.lib文件后,我们要添加头文件包含路径,将第一步拷贝的Include文件夹和DSP_LIB文件夹,加入头文件包含路径,如下图所示。
    在这里插入图片描述
    3.添加全局变量宏
    在这里插入图片描述
    注意:这里两个宏之间用“,”隔开。这里我添加的宏包括:USE_STDPERIPH_DRIVER,STM32F429_439xx,ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING,如果没有在Target选项卡设置Code Generation选择use FPU,则必须在此处手动添加_FPU_USED。

    这样,STM32F4DSP库运行环境就搭建完成了。

    四、代码介绍

    我采用在时域对信号进行采样,将时域采样数据进行4096点FFT变换,得到信号各频率成分的幅值,最后计算该信号的THD。

    1.DMA介绍
    DMA: 全称为: Direct Memory Access,即直接存储器访问。 DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。
    STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中), DMA1 有 7 个通道。 DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。

    2.编程思想
    编程采用ADC+DMA+定时器触发的方式,简单的理解就是定时器具有生成PWM信号的功能,该脉冲信号在上升沿会触发ADC采样,采集的数据会直接被存储在用户自定义的内存单元中,当每次采集完4096个点的采样数据后,才会被再一次传输到用户自定义内存单元,即进行采样数据的更新。

    3.采样定理介绍
    在进行模拟/数字信号的转换过程中,当采样频率fs.max大于信号中最高频率fmax的2倍时(fs.max>2fmax),采样之后的数字信号完整地保留了原始信号中的信息,一般实际应用中保证采样频率为信号最高频率的2.56~4倍;采样定理又称奈奎斯特定理。即当满足采样定理时,可以不失真的恢复原信号,或者说得到原始信号的有用信息。

    采用ADC+DMA+定时器的方式实现信号的采样时,采样频率由定时器的频率决定,需要注意的是,定时器的频率必须小于ADC频率。关于这个问题可以看这里:STM32定时触发ADC 采样频率等问题总结

    4、ADC配置

    	ADC_InitTypeDef ADC_InitStructure;
    	ADC_CommonInitTypeDef ADC_CommonInitStructure;
      // 开启ADC时钟
      RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
      // -------------------ADC Common 结构体 参数 初始化------------------------
      // 独立ADC模式
      ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
      // 时钟为fpclk x分频	
      ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
      // 禁止DMA直接访问模式	
      ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
      // 采样时间间隔	
      ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;  
      ADC_CommonInit(&ADC_CommonInitStructure);
    
      // -------------------ADC Init 结构体 参数 初始化--------------------------
      ADC_StructInit(&ADC_InitStructure);
      // ADC 分辨率
      ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
      // 禁止扫描模式,多通道采集才需要	
      ADC_InitStructure.ADC_ScanConvMode = DISABLE; 
      // 连续转换	
      ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; 
      //禁止外部边沿触发
      ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
      //外部触发通道,本例子使用软件触发,此值随便赋值即可
      ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
      //数据右对齐	
      ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
      //转换通道 1个
      ADC_InitStructure.ADC_NbrOfConversion = 1;                                    
      ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
      //---------------------------------------------------------------------------
    	
      // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
      ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1,ADC_SampleTime_15Cycles);
    
      // 使能DMA请求 after last transfer (Single-ADC mode)
      ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
      // 使能ADC DMA
      ADC_DMACmd(RHEOSTAT_ADC, ENABLE);	
      
     // 使能ADC
        ADC_Cmd(RHEOSTAT_ADC, ENABLE);  
      //开始adc转换,软件触发
     //  ADC_SoftwareStartConv(RHEOSTAT_ADC);
    }
    

    5、DMA配置

       DMA_InitTypeDef DMA_InitStructure;
       NVIC_InitTypeDef NVIC_InitStructure;
      // ------------------DMA Init 结构体参数 初始化--------------------------
      // ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
      // 开启DMA时钟
        RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE); 
    	// 外设基址为:ADC 数据寄存器地址
    	DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;	
      // 存储器地址,实际上就是一个内部SRAM的变量	
    	DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue;  
      // 数据传输方向为外设到存储器	
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;	
    	// 缓冲区大小为,指一次传输的数据量
    	DMA_InitStructure.DMA_BufferSize = 4096;	
    	// 外设寄存器只有一个,地址不用递增
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
      // 存储器地址固定
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
       // // 外设数据大小为半字,即两个字节 
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 
         //	存储器数据大小也为半字,跟外设数据大小相同
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;	
    	// 循环传输模式
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
      // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
    	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    	  // 禁止DMA FIFO	,使用直连模式
    	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;  
    	  // FIFO 大小,FIFO模式禁止时,这个不用配置	
    	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
    		// 选择 DMA 通道,通道存在于流中
    	DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL; 
    	  //初始化DMA流,流相当于一个大的管道,管道里面有很多通道
    	DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
    	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	
    
        NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
    
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
     
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
    	
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    
        NVIC_Init(&NVIC_InitStructure);
        
        
    // 使能DMA流
        DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE); 
    	 
       DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
    

    6、定时器配置

    一般情况下,定时器触发ADC转换并不需要中断,因此不需要配置定时器的中断优先级。

    //使用通用定时器 触发ADC采样  TIM6,由于时钟频率不清楚,本身进行的配置
    static void TIM_Mode_Config(void)
    {
        //NVIC_InitTypeDef NVIC_InitStructure;
    	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    	// 开启TIMx_CLK,x[6,7] 
        RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE); 
    
      /* 累计 TIM_Period个后产生一个更新或者中断*/		
        //定时器初值,采样间隔0.05/1024s,初值为(0.05/1024)/(1/10M)=488
        //TIM_TimeBaseStructure.TIM_Period = 81 //(原始设置的数值)      
    	TIM_TimeBaseStructure.TIM_Period = 3515; 
    	// 通用控制定时器时钟源TIMxCLK = HCLK/2=90MHz 
    	// 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10MHz
        TIM_TimeBaseStructure.TIM_Prescaler = 0;	
        // 采样时钟分频
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        // 计数方式
        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    	
    	// 初始化定时器TIMx, x[1,8]
    	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
    
    	// 清除定时器更新中断标志位
        // TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update);
    	
    	// 开启定时器更新中断
        // TIM_ITConfig(GENERAL_TIM,TIM_IT_Update,ENABLE);
    	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
       
        //使能定时器
    	//TIM_Cmd(GENERAL_TIM, ENABLE);	
       
        //使能定时器
        //TIM_Cmd(GENERAL_TIM, DISABLE);	
    }
    

    7、进行FFT

    //在main.c文件中调用,进行ADC DMA 定时器的先相关初始化
    void Rheostat_Init(void)
    {
    	Rheostat_ADC_GPIO_Config();
    	Rheostat_ADC_Mode_Config();
    //	TIMx_NVIC_Configuration();
        TIM_Mode_Config();
    }
    
    //使能ADC DMA FFT功能,需要进行FFT运算时,调用该语句
    void  ENABLE_deinit(void)
    {
    		TIM_Cmd(GENERAL_TIM, ENABLE);	
            ADC_Cmd(RHEOSTAT_ADC, ENABLE); 
            DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
    }
    
    //失能ADC DMA FFT功能
    void  DISABLE_deinit(void)
    {
    		TIM_Cmd(GENERAL_TIM, DISABLE);	
            ADC_Cmd(RHEOSTAT_ADC, DISABLE); 
            DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM,DISABLE);
    }
    
    
    void DMA2_Stream0_IRQHandler(void)
    {
       u16 i;
       if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) //是否进入中断
       { 
    		DISABLE_deinit();
    		
    	    sprintf(dispBuff,"%d fft is running... please wait",counts0++); 
    		
    		LCD_ClearLine(LINE(2));
    		
    		LCD_DisplayStringLine(LINE(2),(uint8_t* )dispBuff);		 //在第三行显示THD
    		 	
            if(DMA_GetCurrentMemoryTarget(DMA2_Stream0) == DMA_Memory_0)//
            {
    			
    			for(i=0;i<4096;i++){    
    			ADC_Vol[i] =(float) ADC_ConvertedValue[i]/4096*(float)3.3-1;  //电压转换
    //			printf("\r\n ADC_Vol[%d]:%f \r\n",i,ADC_Vol[i] );            //打印采样电压值
    					FFT_IN[2*i]=ADC_Vol[i];
    					FFT_IN[2*i+1]=0;                    //虚部全部为0
    		 }               
                 fft_transformation();                     //基4FFT
    			 /*用LCD打印相关FFT变换后数组的输出值*/
    //                LCD_Clear(LCD_COLOR_WHITE);
    //                LCD_DisplayStringLine(LINE(1),(uint8_t* )" FFT ");
    								 
    			  /*显示频谱*/
    //                  for(i=0;i<799;i++){
                    LCD_DrawUniLine(i+1, 480, i+1, (u16)(480-FFT_OUT[i]/8.53));
    //			      printf("\r\n FFTout[%d]=%f\r\n ",i,FFT_OUT[i]);
    //             }//先屏蔽串口打印测试
             }   
    		//计算各种波形的总谐波失真
    		THD=(sqrt(FFT_OUT[320]*FFT_OUT[320]+FFT_OUT[480]*FFT_OUT[480]+FFT_OUT[640]*FFT_OUT[640]+FFT_OUT[800]*FFT_OUT[800]))*100/FFT_OUT[160];
            //根据THD  计算的值 判断谐波失真的类型
    				 
    		 sprintf(dispBuff,"1kHz-FFT_out[160]:%f  2kHz-FFT_out[320]:%f ",FFT_OUT[160],FFT_OUT[320]); 
    		
    		LCD_ClearLine(LINE(4));
    	  
    		LCD_DisplayStringLine(LINE(4),(uint8_t* )dispBuff);	
    				
            sprintf(dispBuff,"3kHz-FFT_out[480]:%f  4kHz-FFT_out[640]:%f ",FFT_OUT[480],FFT_OUT[640]); 
    		
    		LCD_ClearLine(LINE(5));
    	  
    		LCD_DisplayStringLine(LINE(5),(uint8_t* )dispBuff);					 
    			
    		sprintf(dispBuff,"5kHz-FFT_out[800]:%f",FFT_OUT[800]); 
    		
    		LCD_ClearLine(LINE(6));
    	  
    		LCD_DisplayStringLine(LINE(6),(uint8_t* )dispBuff);					 
    	
    		//
    		sprintf(dispBuff,"%d  DMA fft finished... a new THD",counts1++); 
    				
    		LCD_ClearLine(LINE(3));
    				
    		LCD_DisplayStringLine(LINE(3),(uint8_t* )dispBuff);	 
    		//		 
    		printf("THD=%f",THD);//串口打印显示
    				 
    		sprintf(dispBuff,"%d THD=%f",counts2++,THD); 
    		
    		LCD_ClearLine(LINE(7));
    	  
    		LCD_DisplayStringLine(LINE(7),(uint8_t* )dispBuff);		 //在第三行显示THD
          DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);   //清除中断标志位
        }
    }
    

    8、adc.c源码

    #include "./adc/bsp_adc.h"
    
    __IO uint16_t ADC_ConvertedValue[4096];
    float FFT_IN[4096*2];
    float FFT_OUT[4096];
    static void Rheostat_ADC_GPIO_Config(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	
    	// 使能 GPIO 时钟
    	RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);
    		
    	// 配置 IO
    	GPIO_InitStructure.GPIO_Pin  = RHEOSTAT_ADC_GPIO_PIN;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;	    
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉
    	GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);		
    }
    
    static void Rheostat_ADC_Mode_Config(void)
    {
    	DMA_InitTypeDef DMA_InitStructure;
    	ADC_InitTypeDef ADC_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
    
    	ADC_CommonInitTypeDef ADC_CommonInitStructure;
    
      // ------------------DMA Init 结构体参数 初始化--------------------------
      // ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
      // 开启DMA时钟
        RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE); 
    	// 外设基址为:ADC 数据寄存器地址
    	DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;	
      // 存储器地址,实际上就是一个内部SRAM的变量	
    	DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue;  
      // 数据传输方向为外设到存储器	
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;	
    	// 缓冲区大小为,指一次传输的数据量
    	DMA_InitStructure.DMA_BufferSize = 4096;	
    	// 外设寄存器只有一个,地址不用递增
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
      // 存储器地址固定
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
       // // 外设数据大小为半字,即两个字节 
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 
         //	存储器数据大小也为半字,跟外设数据大小相同
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;	
    	// 循环传输模式
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
      // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
    	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    	  // 禁止DMA FIFO	,使用直连模式
    	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;  
    	  // FIFO 大小,FIFO模式禁止时,这个不用配置	
    	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
    		// 选择 DMA 通道,通道存在于流中
    	DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL; 
    	  //初始化DMA流,流相当于一个大的管道,管道里面有很多通道
    	DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
    	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	
    
        NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
    
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
     
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
    	
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    
        NVIC_Init(&NVIC_InitStructure);
        
        
    //使能DMA流
        DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE); 
    	 
    //DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
    	 
    //DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, DISABLE);
    	 
    		// 开启ADC时钟
      RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
      // -------------------ADC Common 结构体 参数 初始化------------------------
    	// 独立ADC模式
      ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
      // 时钟为fpclk x分频	
      ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
      // 禁止DMA直接访问模式	
      ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
      // 采样时间间隔	
      ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;  
      ADC_CommonInit(&ADC_CommonInitStructure);
    
         // -------------------ADC Init 结构体 参数 初始化--------------------------
      ADC_StructInit(&ADC_InitStructure);
      // ADC 分辨率
      ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
      // 禁止扫描模式,多通道采集才需要	
      ADC_InitStructure.ADC_ScanConvMode = DISABLE; 
      // 连续转换	
      ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 
      //禁止外部边沿触发
      ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
      //外部触发通道,本例子使用软件触发,此值随便赋值即可
      ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
      //数据右对齐	
      ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
      //转换通道 1个
      ADC_InitStructure.ADC_NbrOfConversion = 1;                                    
      ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
      //---------------------------------------------------------------------------
    	
      // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
      ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1, ADC_SampleTime_15Cycles);
    
      // 使能DMA请求 after last transfer (Single-ADC mode)
      ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
      // 使能ADC DMA
      ADC_DMACmd(RHEOSTAT_ADC, ENABLE);
    	
    	// 使能ADC
    //  ADC_Cmd(RHEOSTAT_ADC, ENABLE);  
    
        // 使能ADC
    //  ADC_Cmd(RHEOSTAT_ADC, DISABLE); 
      //开始adc转换,软件触发
    //  ADC_SoftwareStartConv(RHEOSTAT_ADC);
    }
    
    
    //把DMA 和 ADC 和定时器都失能   在想要使用的时候  将定时器重新使能CMD
    
    //使用通用定时器 触发ADC采样  TIM6,由于时钟频率不清楚,本身进行的配置
    static void TIM_Mode_Config(void)
    {
        //NVIC_InitTypeDef NVIC_InitStructure;
    	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    	// 开启TIMx_CLK,x[6,7] 
        RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE); 
    
      /* 累计 TIM_Period个后产生一个更新或者中断*/		
        //定时器初值,采样间隔0.05/1024s,初值为(0.05/1024)/(1/10M)=488
        //TIM_TimeBaseStructure.TIM_Period = 81 //(原始设置的数值)      
    	TIM_TimeBaseStructure.TIM_Period = 3515; 
    	// 通用控制定时器时钟源TIMxCLK = HCLK/2=90MHz 
    	// 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10MHz
        TIM_TimeBaseStructure.TIM_Prescaler = 0;	
        // 采样时钟分频
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        // 计数方式
        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    	
    	// 初始化定时器TIMx, x[1,8]
    	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
    
    	// 清除定时器更新中断标志位
        // TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update);
    	
    	// 开启定时器更新中断
        // TIM_ITConfig(GENERAL_TIM,TIM_IT_Update,ENABLE);
    	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
       
        //使能定时器
    	//TIM_Cmd(GENERAL_TIM, ENABLE);	
       
        //使能定时器
        //TIM_Cmd(GENERAL_TIM, DISABLE);	
    }
    
    //static void TIMx_NVIC_Configuration(void)
    //{
    //    NVIC_InitTypeDef NVIC_InitStructure; 
    //    // 设置中断组为0
    //    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);		
    //    // 设置中断来源
    //    NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQn; 	
    //	// 设置抢占优先级
    //    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	 
    //	// 设置子优先级
    //    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    //	
    //    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    //	
    //    NVIC_Init(&NVIC_InitStructure);
    //}
    
    //采用通用定时器  TIM3 (原本的配置)
    void Rheostat_Init(void)
    {
    	Rheostat_ADC_GPIO_Config();
    	Rheostat_ADC_Mode_Config();
    //	TIMx_NVIC_Configuration();
        TIM_Mode_Config();
    }
    
    void  ENABLE_deinit(void)
    {
    		TIM_Cmd(GENERAL_TIM, ENABLE);	
            ADC_Cmd(RHEOSTAT_ADC, ENABLE); 
            DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
    }
    
    void  DISABLE_deinit(void)
    {
    		TIM_Cmd(GENERAL_TIM, DISABLE);	
            ADC_Cmd(RHEOSTAT_ADC, DISABLE); 
            DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM,DISABLE);
    }
    
    

    五、需要注意的问题和相关的总结

    1、定时器决定采样频率的计算公式:
    Ft=TIMxCLK /TIM_ClockDivision/(1+TIM_Prescaler)/(TIM_Prescaler)

    2、关于ADC扫描模式设置

       //禁止扫描模式,多通道采集才需要	
      ADC_InitStructure.ADC_ScanConvMode = DISABLE; 
    

    多通道是指一个或多个ADC的多个通道对数据进行采集。在这里我使用的是单通道。记住扫描模式对应着多通道!!果只是用了一个通道的话,DISABLE就可以了,如果使用了多个通道的话,则必须将其设置为ENABLE。
    对扫描模式的理解

    3、关于ADC独立模式

    // 独立ADC模式
     ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
    

    在这个模式下,双ADC不能同步,每个ADC接口独立工作。所以如果不需要ADC同步或者只是用了一个ADC的时候,就应该设成独立模式了。

    4、关于ADC的连续转换

      // 连续转换	
      ADC_InitStructure.ADC_ContinuousConvMode =ENABLE; 
    

    第四个参数是ADC_ContinuousConvMode,这里设置为ENABLE,即连续转换。如果设置为DISABLE,则是单次转换。两者的区别在于连续转换直到所有的数据转换完成后才停止转换,而单次转换则只转换一次数据就停止,要再次触发转换才可以。所以如果需要一次性采集4096个数据或者更多,则采用连续转换。
    关于STM32连续转换和单次转换

    5、关于DMA传输模式

    // 循环传输模式
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
    

    DMA传输模式工作在正常缓存模式,那么每当ADC转换完4096个采样数据时,传输一次后DMA就会停止工作。
    关于DMA

    6、关于定时器上升沿触发ADC时,定时器选择的问题

    可以选择通用定时器2,3,8,能够生成PWM信号。
    在这里插入图片描述

      //外部触发通道,本例子使用软件触发,此值随便赋值即可
      ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
    
    
    //定时器触发定义
    #define GENERAL_TIM           		TIM3
    #define GENERAL_TIM_CLK       		RCC_APB1Periph_TIM3
    
    

    7、DMA1通道资源
    在这里插入图片描述
    8、DMA2通道资源
    在这里插入图片描述
    9、ADC资源
    在这里插入图片描述
    10、ADC_TwoSamplingDelay 用来设置两个采样阶段之间的延迟周期数
    第1次采样和第二次采样之间间隔的周期数。计算方法如下:
    在这里插入图片描述
    x=ADC_TwoSamplingDelay(采样时间)
    ADCCLK=45M
    在这里插入图片描述
    一定要清楚怎么判断每个外设挂载的时钟总线。看英文参考手册!!

    展开全文
  • FFT 是离散傅立叶变换的快速算法,可以将一个信号变换到频域。有些信号在时域上是很难看出什么特征的,但是如果变换到频域之后,就很容易看出特征了。这就是很多信号分析采用 FFT 变换的原因。另外,FFT 可以将一个...
  • cr4_fft_64_stm32 :实现64点FFT。 cr4_fft_256_stm32 :实现256点FFT。 cr4_fft_1024_stm32 : 实现1024点FFT。 移植 添加这几个文件到自己的工程 在stm32_dsp.h文件中添加f4的头文件 其他几个点类似,

    x[n]是采样信号
    n得满足4^n(n =1,2, 3……),也就是以4为基数。

    采样信号必须是32位数据,高16位存实部,低16位存虚部(这个是针对大端模式),小端模式是高位存虚部,低位存虚部。一般常用的是小端模式。

    1. cr4_fft_64_stm32 :实现64点FFT。
    2. cr4_fft_256_stm32 :实现256点FFT。
    3. cr4_fft_1024_stm32 : 实现1024点FFT。

    移植

    添加这几个文件到自己的工程
    在这里插入图片描述
    在stm32_dsp.h文件中添加f4的头文件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    其他几个点类似,就是说output这个数组中,高位是虚部,低位是实部,要用幅度、相位信息的时候分别把它们取出来就好了。

    展开全文
  • GD32E103移植stm32 fft函数 cr4_fft_1024_stm32。 一、移植fft函数后占用空间对比 移植前 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201126112256444.png#pic_center 移植后大约多了10K 二、代码实现...


    前言

    GD32E103移植stm32 fft函数
    void cr4_fft_1024_stm32(void *pssOUT, void *pssIN, uint16_t Nbin);


    一、移植fft函数后占用空间对比

    移植前
    在这里插入图片描述

    移植后大约多了10K
    在这里插入图片描述

    二、代码实现

    1.GD32代码

    代码如下:

    #include "main.h"
    #include <math.h>
    #include <stdint.h>
    #include "DSP.h"
    
    #define NPT 1024
    #define PI2 6.28318530717959
    #define Fs 44800
    
    uint32_t lBufInArray[NPT];
    uint32_t lBufOutArray[NPT/2];
    uint32_t lBufMagArray[NPT/2];
    
    void InitBufInArray()
    {
        unsigned short i;
        float fx,fs;
         fs = (float)Fs;
        for(i=0; i<NPT;i++)
        {
            fx = 1000 + 1500 * sin(PI2 * i * 350.0 / fs) +
                2500 * sin(PI2 * i * 8400.0 / fs) +
                4000 * sin(PI2 * i * 18725.0 / fs);
            lBufInArray[i] = ((signed short)fx) << 16 ;
        }
    }
    
    void GetPowerMag()
    {
        signed short lX,lY;
        float X,Y,Mag;
        unsigned short i;
    
        for(i=0; i<NPT/2;i++)
        {
            lX  = (lBufOutArray[i] << 16) >> 16;
            lY  = (lBufOutArray[i] >> 16);
            X = NPT * ((float)lX) / 32768;
            Y = NPT * ((float)lY) / 32768;
            Mag = sqrt(X * X + Y * Y) * 1.0 / NPT;
            if(i == 0)
               lBufMagArray[i] = (unsigned long)(Mag * 32768);
            else
               lBufMagArray[i] = (unsigned long)(Mag * 65536);
        }
    }
    
    void DSP_FFT1024(void)
    {
    	InitBufInArray();
    	cr4_fft_1024_stm32(lBufOutArray, lBufInArray, NPT);
    	GetPowerMag();
    }
    
    
    
    
    

    2.matlab代码

    代码如下:

    clear
    Fs = 44800;                  % 采样率
    N  = 1024;           % 采样点数
    n  = 0:N-1;           % 采样序列
    t  = 0:1/Fs:1-1/Fs;     % 时间序列
    f = n * Fs / N;          %真实的频率
    
    x = 1000 + 1500 * sin(2*pi*350*t) +2500 * sin(2*pi*8400*t) +4000 * sin(2*pi*18725*t);
    y = fft(x, N);               %对原始信号做FFT变换
    
    subplot(2,1,1);
    Mag = abs(y)*2/N;         %求FFT转换结果的模值
    plot(f, Mag);               %绘制幅频相应曲线
    title('Matlab计算结果');
    xlabel('频率');
    ylabel('幅度');
    

    总结

    这里对文章进行总结:GD32在第9个点,真实频率f=350hz,幅值1492。matlab计算结果为1500。可以使用此库。
    在这里插入图片描述

    在这里插入图片描述

    展开全文
  • STM32 FFT 音频处理

    千次阅读 多人点赞 2018-11-15 16:55:34
    制作过程: 1.准备材料: ...stm32f103核心板 1块 OLED12864显示屏 1块(SPI接口) 声音检测传感器 1块 (咪头+放大电路 可以网上买现成的模块,也可根据后文提供的原理图自己做) 2.硬件连接...

    制作过程:

    1.准备材料:

    stm32f103核心板 1块

    OLED12864显示屏 1块(SPI接口)

    声音检测传感器 1块 (咪头+放大电路 可以网上买现成的模块,也可根据后文提供的原理图自己做)

    2.硬件连接:

    (1)OLED连接:

    OLED_SCLK   ————  PB7
    OLED_SDIN    ————  PB6
    OLED_RST     ————  PB5
    OLED_RS       ————  PB4

    (2)声音检测传感器连接:

    直接将模块的输出接到单片机的PA0即可。

    OK硬件连接完成!就这么简单!

    3.程序下载

    接下来将程序下载到单片机即可,音乐频谱就完成了!(别告诉我你连下载程序都不会 滑稽)

    程序烧录文件 链接:https://pan.baidu.com/s/1EjKPvBFbTmYzzh6fSn0U5A 密码:o6uu

    程序源码:https://download.csdn.net/download/mc_li/10601743

    ps:以上就是简单的音乐频谱制作过程,下面是较为详细的制作过程,提供源码和原理图,有兴趣的同志们可以看看。

    /************************分***********************************割***************************************线********************************/

    整体思路:

    1.使用ADC采集音频信号

    2.使用官方提供的FFT函数对采集到的信号进行处理

    3.量化显示

    前言:

    在人耳能听到的频率范围(20-20KHz)中,各类乐器和人声的频率大部分在5KHz以下。主要是在0-5KHz这段频率的频谱变化明显一些,所以观赏性更好。故我们设置采样频率为10KHz(根据采样定理,采样频率要大于信号最大频率的两倍,才能保证不失真)来采集这0-5KHz的音频信号。显示是使用的OLED12864屏,分辨率为128*64,在x轴方向上最多显示128个点,所以我们把采样点数设置为256个点,因为FFT计算出来的数据是对称的,我们只取一半,128个点刚刚好。

    采样频率:Fs = 10KHz

    样本数量:NPT = 256

    这两个参数是FFT计算时候要用到的。

    深入研究FFT可参考这位大大的博客(强烈推荐):

    http://www.opticsjournal.net/Mobile/postdetails/PT160728000122iOlRn?code=3&from=singlemessage&isappinstalled=0

    (一)音频信号的采集

    (1)信号来源于咪头采集的声音信号,以下是原理图:

    咪头放大电路

    电路使用LM358搭建而成,采用单电源5V直流供电。由于单片机的ADC不能采集到负值,所以我们需要把信号加上了1/2Vcc的直流偏置,50倍增益可调。

    这里如果考虑多一些,应当加一个低通滤波,去除高频信号的影响,防止出现频谱叠加,影响观赏效果。我们简单制作,就不考虑这个了。

    下图是我自己做的咪头放大电路,原理图就是上面的。

    咪头1

    咪头2

    (2)使用stm32的ADC去采集咪头电路输出的信号

    因为我们的采样频率要固定在10KHz,所以这里使用定时器去触发ADC转换,再使用DMA搬运,最后使用stm32cubemx去配置这些硬件,生成基础代码即可。

    1.adc配置

    勾选通道
    勾选ADC1的通道0 对应PA0引脚

     

     

     

    选择定时器来触发ADC转换
    使能DMA传输
    使能DMA传输

    2.定时器配置

    使能定时器时钟
    使能定时器3时钟
    配置定时器
    配置定时器

     

    3.系统时钟树

    时钟树
    时钟树

     

    到此基础配置完成,生成代码即可。这里简单的说一下流程,这里配置的定时器触发ADC转换是硬件自动触发,不会进入中断,ADC转换完直接由DMA传输到内存,只有等256点全部传输完才会进入到DMA的中断,这样保证了每个采样点的间隔时间都是一致的。

    关于stm32cubemx这个工具,作为开发来说十分便捷,大大缩短开发周期,但是想学习32的朋友来说,这个就不太理想了,学习还是建议使用标注库和寄存器。这软件配置生成的代码是用的hal库,具体使用教程可以参考学习“硬石科技”的相关教程。

    (二)信号处理

    (1)移植官方DSP库

    DSP库
    标题

    可参考这位大大的博客:https://www.cnblogs.com/menlsh/p/4154070.html

    (2)填充数据和计算幅值

    数据填充
    在采集完256个点后进行FFT运算

    (三)量化显示

    (1)简单的GUI

    GUI
    ZLG_GUI方便绘图 不用GUI也可以 看自己需求

    这里移植一个简单的GUI,方便绘制各种现实效果。

    主界面
    开机效果

    (2)显示

    显示

    显示2

    显示过程,就是把我们FFT计算出来的幅值量化显示到我们屏幕上。比如我们在填充数据的时候是填充的ADC采集到的电压对应的数字量,FFT计算出来后经过取模,得到一个该频率点对应幅值的一个数字量。我们只需要对这个数字量进行处理即可,这里处理方式每个人方法都有所不同,这里我提供的可供参考。

    至此基于stm32f103的FFT音乐频谱制作流程就分享到这里,小弟才疏学浅内容中有问题的地方还望斧正。欢迎转载,请注明出处,谢谢大家。

    展开全文
  • 学术,自动化,单片机
  • stm32 fft简单实现
  • STM32F103 FFT算法的实现

    热门讨论 2014-12-15 16:44:02
    1024点的FFT算法实现
  • 基于STM32FFT变换

    2018-04-12 19:56:30
    已通过验证,在开发板可以进行正常的工作。信号信息处理
  • STM32 FFT 音频.rar

    2020-03-18 14:57:03
    本实验,通过开发板上载有的4个按钮(KEY_UP、KEY0、KEY1和KEY2),来控制板上的2个LED(DS0和DS1)和蜂鸣器,其中WK_UP控制蜂鸣器,按一次叫,再按一次停;KEY2控制DS0,按一次亮,再按一次灭;...
  • STM32FFT官方库 - 副本.zip
  • STM32下的FFT实现

    2015-02-03 15:52:14
    STM32下的FFT实现,可以直接在STM32下运行
  • FFT实现的MATLAB及在STM32处理器上C语言实现,非常具备参考价值,可以直接运行。
  • STM32F7通过TIM+DMA+ADC实现FFT功能,基础版本,没有使用DSP和FPU

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,404
精华内容 561
关键字:

stm32fft