精华内容
下载资源
问答
  • 模拟信号经过ADC采样后变成数字信号,数字信号可以进行FFT运算,在频域中更容易分析信号的特征。此代码用STM32F407的ADC-DMA模式采集4096个点的数据,利用DSP库里的FFT算法进行快速傅里叶变换,经实测可以使用。
  • stm32f103 adc+tim控制采样+dma传输+fft处理频率计。程序可以直接运行,没有BUG,可以升级。可以将频谱显示在LCD显示屏上。
  • stm32+adc+dma+fft.zip

    2021-11-03 19:41:26
    通过STM32F407进行AD采集DMA方式进行FFT计算。
  • STM32F1X+ADC+FFT应用

    万次阅读 多人点赞 2019-08-13 22:57:02
    关于adc采样与fft转换之间的关系与应用 写在前面 最近在要实现一个功能,对外部波形进行采样,然后用官方fft变换对外部波形进行谐波分析。但是本人数学不好,不喜欢研究算法,不喜欢看一堆公式,不想要知道原理,...

    关于adc采样与fft转换之间的关系与应用

    写在前面

    最近在要实现一个功能,对外部波形进行采样,然后用官方fft变换对外部波形进行谐波分析。但是本人数学不好,不喜欢研究算法,不喜欢看一堆公式,不想要知道原理,只想知道如何应用。在网上找了很久都没有找到想要的demo,大部分都解释得不够全面,自己研究后,上传一包源码给大家分享。本人能力有限,关于后文对其中的解释,有不正确的地方,还请大家指正。

    如果有对傅里叶变换原理感兴趣的朋友,强烈推荐大家看这一位大佬的博客,对理解傅里叶分析的意义很有帮助:https://www.cnblogs.com/h2zZhou/p/8405717.html

    附上完整的工程链接,需要的朋友请自行下载(代码中的注释部分,采用ANSI编码格式):https://github.com/WangYooNestik/STM32F10X-ADC-FFT

    直接进入主题

    1.说一下总的框架

    • 1.目 的:用ADC采样值进行fft运算,求取采样后波形相关属性(本例主要分析50Hz正弦波,及高/低次谐波)
    • 2.方 案:计划ADC采样频率为512Hz,采样1024个点(也就是采样2秒钟)进行一次fft运算,使频域分辨率达到0.5Hz
    • 3.配 置:由于我用的系统时钟是72MHz,所以ADC本身不能配置到512Hz,采用tim3定时触发采样(当系统时钟为72MHz时,ADC时钟最慢是9MHz,采样周期最大约为256个时钟周期,所以ADC本身采样频率最慢约为35156.25Hz(大于我需要的512Hz),所以选择TIM定时采样)
    • 4.其 他:
      • 为什么要选512Hz:为了满足采样定理(fs > 100Hz);又因为我想要分析高次谐波;又因为我选了1024的fft变换,为了使频域分辨率识别方便(0.5Hz)。这句话有点绕,后文有详细解释。
      • 本来计划采样5120个点(使频域分辨率达到0.1Hz),但是单片机RAM空间不够,有条件的可以尝试
    • 5.相关知识(很重要,本文用到的知识点都在这)
      • 傅里叶变换的目的:求取被测波形的幅频特性/相频特性
      • 采样定理:采样频率需大于被采样波形频率的两倍
      • 频域分辨率 = fs/N,(fs:采样频率,N:采样点数)
      • 幅频特性分析范围(单位:Hz):0 ~ fs/2,(fs:采样频率)
      • 幅频特性序列:FFT_Mag[i] 代表的意思,频率分量为 (i*(fs/N)) 的幅值为 FFT_Mag[i]

    2.说一下采样频率及点数的选择(重点)

    由于fft变换需要考虑多个变量对最终结果的影响,比较难办;我建议,对采样频率及点数的选择按以下步骤进行分析:

    • 1.采样点数分析:
      • 不考虑耗时,采样点数越多越好。
      • 看你的平台的资源情况,分析整个项目的RAM大小及占用情况,先选择你准备用多少点进行fft变。当你的fs确定下来,采样点数越多,你的频域分辨率越小,你最终的幅频特性序列就越精确。当然我们一般认为越精确越好。(举个例子:我分析了1024个点,则fft变换输入数组大小 = 1024 * 4Byte = 4K,输出数组大小 = 1024 * 4Byte = 4K,这样就已经占用8KRAM)
    • 2.采样频率分析:
      • 满足采样定理:采样频率需大于被采样波形频率的两倍(假设你需要采样的波形频率为50Hz,则采样频率必须在100Hz以上)。
      • 如果你只想分析一个特定频率的波形(比如50Hz),不考虑它的高(>50Hz)/低(<50Hz)次谐波,只需要满足采样定理即可;
      • 如果你需要分析特定频率及低次谐波,只需要满足采样定理即可;
      • 如果你需要分析特定频率,及它的高/低次谐波。那你需要优先考虑你的采样频率对你需要分析的最高次谐波,要满足采样定理;也就是fs要 > 2 * 最高次谐波频率。
        反过来说,就是要考虑你的幅频特性分析范围。
    • 3.采样精度确认:
      • 确定了以上两点后,计算频率分辨率(频域分辨率 = fs/N),看是否满足你的精度要求。

    3.官方给出了fft库函数啥意思,咋用?(官方库在哪里下载?我也不知道)

    /* 64 points*/
    void cr4_fft_64_stm32(void *pssOUT, void *pssIN, uint16_t Nbin);
    /* 256 points */
    void cr4_fft_256_stm32(void *pssOUT, void *pssIN, uint16_t Nbin);
    /* 1024 points */
    void cr4_fft_1024_stm32(void *pssOUT, void *pssIN, uint16_t Nbin);
    

    官方给出了3个fft函数,分别是64、256、1024个点的fft变换(就是采样的样本是64、256、1024个样本)。
    pssIN:是adc采样的样本
    pssOUT:调用官方函数后,输出的傅里叶序列
    Nbin:样本的数量(采了多少个点)

    为什么是64、256、1024这个3个值?因为官方使用的是基4蝶形算法(啥意思?),简单理解要使用官方fft库函数,必须遵循采样点数Nbin,是4的n次方。

    4.傅里叶变换后,输出了个啥,咋用?

    我们需要有个基础知识,傅里叶变换的目的:求取幅频特性/相频特性

    fft变换后,输出的是一个傅里叶序列(怎么用?)。傅里叶序列本身,不是我们能够肉眼分析的东西,我们还需要对傅里叶序列进行计算,求取幅频特性/相频特性序列。(下面这段代码就是求取幅频特性)

    #define SAMPLS_NUM 1024
    u32 FFT_OutData[SAMPLS_NUM] = {0};		//fft输出序列
    u32 FFT_Mag[SAMPLS_NUM/2] = {0};		//幅频特性序列(序号代表频率分量,值代表幅值大小。由于FFT的频谱结果是关于奈奎斯特频率对称的,所以只计算一半的点即可)
    /********************************************************************************************************
    函数名称:GetPowerMag()
    函数功能:计算各次谐波幅值
    参数说明:
    备  注:先将ADC_FFT_OutData分解成实部(X)和虚部(Y),然后计算幅频特性序列FFT_Mag
             本函数参考网页:https://wenku.baidu.com/view/08ccee0984868762cbaed532.html,关于幅频特性计算部分
    **********************************************************************************************************/
    void GetPowerMag(void)
    {
    	signed short lX,lY;
    	float X,Y,Mag;
    	unsigned short i;
    
    	for(i=0; i<SAMPLS_NUM/2; i++)
    	{
    		lX = (FFT_OutData[i] << 16) >> 16;
    		lY = (FFT_OutData[i] >> 16);
    		
    		X = SAMPLS_NUM * ((float)lX) / 32768;
    		Y = SAMPLS_NUM * ((float)lY) / 32768;
    		
    		Mag = sqrt(X * X + Y * Y) / SAMPLS_NUM;
    		
    		if(i == 0)
    			FFT_Mag[i] = (unsigned long)(Mag * 32768);
    		else
    			FFT_Mag[i] = (unsigned long)(Mag * 65536);
    		//printf("%ld\r\n",lBufMagArray[i]);
    	}
    	//printf("\r\n\r\n");
    }
    

    FFT_Mag[SAMPLS_NUM/2],就是幅频特性序列,到此傅里叶变换的目的就达到了。

    我输入的是50Hz正弦波,可以看到FFT_Mag[100]这个值,远大于其他值。证明频率分量为50Hz(频域分辨率(0.5Hz) * 100)的谐波,幅值最大,所以可以知道fft后是正确的。

    5.最后贴上源码

    这个玩意儿好像下载源码要积分,我就直接贴在后面了,讲得不是很清楚大家见谅。

    • ADC配置相关
    #define ADC_CHANNEL_NUMS 	3
    #define SAMPLS_NUM        	1024
    
    #define TIM2_PERIOD 1953
    #define TIM3_PERIOD 1953
    
    
    u16 ADC_SourceData[SAMPLS_NUM][ADC_CHANNEL_NUMS] = {0};
    
    
    
    void ADC_GPIO_Configuration(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);				//使能GPIOA时钟
    
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5;		//管脚1/4/5
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;						//模拟输入模式
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);								//GPIO组
    }
    
    void ADC_TIM2_GPIO_Configuration(void)        //ADC配置函数
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //使能GPIOA时钟
    
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;				//TIM2_CH3
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    }
    
    void ADC_TIM2_Configuration(void)
    { 
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    
    	ADC_TIM2_GPIO_Configuration();
    	
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    
    	TIM_DeInit(TIM2);
    
    	//配置tim2触发频率为512Hz
    	TIM_TimeBaseStructure.TIM_Period = TIM2_PERIOD - 1;				//设置2ms一次TIM2比较的周期
    	TIM_TimeBaseStructure.TIM_Prescaler = 71;						//系统主频72M,这里分频71,相当于1000K的定时器2时钟
    	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_OutputState_Disable;
    	TIM_OCInitStructure.TIM_Pulse = (TIM2_PERIOD - 1) / 2;
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;		//如果是PWM1要为Low,PWM2则为High
    	TIM_OC3Init(TIM2, & TIM_OCInitStructure);
    	
    	//TIM_InternalClockConfig(TIM2);
    	
    	//TIM_OC3PreloadConfig(TIM2,TIM_OCPreload_Enable);
    	
    	//TIM_UpdateDisableConfig(TIM2,DISABLE);
    
    	//TIM_Cmd(TIM2, ENABLE);//最后面打开定时器使能
    
    	//TIM_CtrlPWMOutputs(TIM2,ENABLE);
    }
    
    void ADC_TIM3_Configuration(void)
    {
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; 
    	
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    
    	TIM_TimeBaseInitStructure.TIM_Period = TIM3_PERIOD - 1;		//设置ADC_CHANNEL_NUMS * 512Hz采样频率
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 71;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频,TIM3是通用定时器,基本定时器不用设置
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上扫描
    	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
    	
    	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);				//选择TRGO作为触发源为定时器更新时间
    	
    	TIM_Cmd(TIM3,ENABLE);												//开启定时器3
    }
    
    void ADC_DMA_NVIC_Configuration(void)
    {
    	NVIC_InitTypeDef NVIC_InitStructure;
    
    	NVIC_InitStructure.NVIC_IRQChannel					 = DMA1_Channel1_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority		 = 1;
    	NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    	NVIC_Init(&NVIC_InitStructure);
    
    	DMA_ClearITPendingBit(DMA1_IT_TC1);
    
    	DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE);
    }
    
    void ADC_DMA_Configuration(void)
    {
    	DMA_InitTypeDef  DMA_InitStructure;
    
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
    
    	DMA_DeInit(DMA1_Channel1);
    	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;
    	DMA_InitStructure.DMA_MemoryBaseAddr     = (u32)ADC_SourceData;
    	DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC;
    	DMA_InitStructure.DMA_BufferSize         = ADC_CHANNEL_NUMS*SAMPLS_NUM;
    	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_InitStructure.DMA_Priority           = DMA_Priority_High;
    	DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;
    	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    
    	DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA	
    
    	ADC_DMA_NVIC_Configuration();
    }
    
    void ADC_Init_Configuration(void)//ADC配置函数
    {
    	ADC_InitTypeDef  ADC_InitStructure;
    
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    	
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    
    	ADC_DeInit(ADC1);
    	ADC_InitStructure.ADC_Mode               = ADC_Mode_Independent;
    	ADC_InitStructure.ADC_ScanConvMode       = ENABLE;								//如果是单通道,关闭扫描模式
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    	ADC_InitStructure.ADC_ExternalTrigConv   = ADC_ExternalTrigConv_T3_TRGO;
    	ADC_InitStructure.ADC_DataAlign          = ADC_DataAlign_Right;
    	ADC_InitStructure.ADC_NbrOfChannel       = ADC_CHANNEL_NUMS;
    	ADC_Init(ADC1, &ADC_InitStructure);
    
    	//==========================================================================   
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_1,  1, ADC_SampleTime_239Cycles5);	//AI_VS_A1
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_4,  2, ADC_SampleTime_239Cycles5);	//AI_VS_B1
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_5,  3, ADC_SampleTime_239Cycles5);	//AI_VS_C1
    
    	ADC_DMACmd(ADC1, ENABLE);
    	
    	ADC_Cmd(ADC1, ENABLE);
    	
    	ADC_ResetCalibration(ADC1);
    	while(ADC_GetResetCalibrationStatus(ADC1));
    	ADC_StartCalibration(ADC1);
    	while(ADC_GetCalibrationStatus(ADC1));
    
    	ADC_ExternalTrigConvCmd(ADC1, ENABLE);
    	//ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    }
    
    
    
    /***********************************************************************************************************************************************
    *adc初始化配置
    *1.目     的:用ADC采样值进行fft运算,求取采样后波形相关属性(本例主要分析50Hz正选波,及高/低次谐波)
    *2.方     案:计划ADC采样频率为512Hz,采样1024个点进行fft运算,使频域分辨率达到0.5Hz
    *3.配     置:由于ADC本身不能配置到512Hz,采用tim3定时触发采样
    *4.其     他:当系统时钟为72MHz时,ADC时钟最慢是9MHz,采样周期最大约为256个时钟周期,所以ADC本身采样频率最慢约为35156.25Hz(大于我需要的512Hz)
    *            为什么要选512Hz:由于采用标准fft基4算法(采样点数必须是4的倍数),为了使频域分辨率为识别方便(0.5Hz),又为了满足采样定理
    *            本来计划采样5120个点(使频域分辨率达到0.1Hz),但是单片机RAM空间不够,有条件的可以尝试
    *5.相关知识:采样定理:采样频率需大于被采样波形频率的两倍
    *            频域分辨率 = fs/N,(fs:采样频率,N:采样点数)
    *            幅频特性序列:FFT_Mag[i]代表的意思,频率分量为i*(fs/N)的幅值为FFT_Mag[i]
    ************************************************************************************************************************************************/
    void Adc_Init(void)
    {
    	ADC_GPIO_Configuration();
    	
    	//ADC_TIM2_Configuration();
    
    	ADC_TIM3_Configuration();
    
    	ADC_DMA_Configuration();
    
    	ADC_Init_Configuration();
    }
    
    //ADC_DMA中断服务程序
    void DMA1_Channel1_IRQHandler(void)
    {
    	if(DMA_GetITStatus(DMA1_IT_TC1) != RESET)
    	{
    		global.adc_finish_fg = true;
    		DMA_ClearITPendingBit(DMA1_IT_TC1);
    	}
    }
    
    • fft函数
    u32 FFT_SourceData[SAMPLS_NUM] = {0};	//fft输入序列
    u32 FFT_OutData[SAMPLS_NUM] = {0};		//fft输出序列
    u32 FFT_Mag[SAMPLS_NUM/2] = {0};		//幅频特性序列(序号代表频率分量,值代表幅值大小。由于FFT的频谱结果是关于奈奎斯特频率对称的,所以只计算一半的点即可)
    
    
    #if 0
    /****************************************************************************************************
    函数名称:InitBufInArray()
    函数功能:模拟采样数据,采样数据中包含3种频率正弦波(350Hz,8400Hz,18725Hz)
    参数说明:
    备     注:在lBufInArray数组中,每个数据的高16位存储采样数据的实部,低16位存储采样数据的虚部(总是为0)
    ****************************************************************************************************/
    
    u16 lBufInArray[SAMPLS_NUM];
    
    void InitBufInArray(void)
    {
    	unsigned short i;
    	float fx;
    	for(i=0; i<SAMPLS_NUM; i++)
    	{
    		fx = 1500 * sin(PI2 * i * 55.6 / Fs);
    		lBufInArray[i] = ((signed short)fx)<<16;
    	}
    }
    #endif
    
    /********************************************************************************************************
    函数名称:GetPowerMag()
    函数功能:计算各次谐波幅值
    参数说明:
    备  注:先将ADC_FFT_OutData分解成实部(X)和虚部(Y),然后计算幅频特性序列FFT_Mag
             本函数参考网页:https://wenku.baidu.com/view/08ccee0984868762cbaed532.html,关于幅频特性计算部分
    **********************************************************************************************************/
    void GetPowerMag(void)
    {
    	signed short lX,lY;
    	float X,Y,Mag;
    	unsigned short i;
    
    	for(i=0; i<SAMPLS_NUM/2; i++)
    	{
    		lX = (FFT_OutData[i] << 16) >> 16;
    		lY = (FFT_OutData[i] >> 16);
    		
    		X = SAMPLS_NUM * ((float)lX) / 32768;
    		Y = SAMPLS_NUM * ((float)lY) / 32768;
    		
    		Mag = sqrt(X * X + Y * Y) / SAMPLS_NUM;
    		
    		if(i == 0)
    			FFT_Mag[i] = (unsigned long)(Mag * 32768);
    		else
    			FFT_Mag[i] = (unsigned long)(Mag * 65536);
    		//printf("%ld\r\n",lBufMagArray[i]);
    	}
    	//printf("\r\n\r\n");
    }
    
    /************************************************
    *由于在fft运算过程中不允许源数据更新
    *将源数据拷贝到另一块内存
    *此步骤可以被替换为:进行fft运算时,关闭adc转换
    *************************************************/
    void Get_FFT_Source_Data(EN_FFT_CHANNEL channel_idx)
    {
    	u16 i,j;
    
    	for(i=0; i<SAMPLS_NUM; i++)
    	{
    		FFT_SourceData[i] = (u32)ADC_SourceData[i][channel_idx];
    	}
    }
    
    void FFT_test(void)
    {
    	//InitBufInArray();
    	Get_FFT_Source_Data(FFT_CHANNEL_1);
    	cr4_fft_1024_stm32(FFT_OutData, FFT_SourceData, SAMPLS_NUM);
    	GetPowerMag();
    }
    
    • 主函数
    int main(void)
    {
    	Adc_Init();
    
    	while(1)
    	{
    		if(global.adc_finish_fg)
    		{
    			global.adc_finish_fg = false;
    			FFT_test();				//512Hz采样频率,采样1024点,可以看到2s钟周期执行FFT_test();
    		}
    	}
    }
    
    展开全文
  • ADC+MATLAB FFT分析.zip

    2019-07-28 20:37:38
    通过STM32F407的ADC+TIM2+DMA在‘0’CPU干预下进行一按设定的频率进行采样,在得到一帧数据后通过串口打印输出。通过 .M文件进行FFT分析,内含一个数据样本亲测,准确度相当高。
  • adc-fft.zip

    2019-05-12 19:16:00
    STM32对声音信号采样,语音信号采用咪头通过放大器后转换为电压信号,STM32adc连续adc,然后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
    在这里插入图片描述
    一定要清楚怎么判断每个外设挂载的时钟总线。看英文参考手册!!

    展开全文
  • 这些都是比较基础和实用的东西,故做分享~以前在实验室跑ADC动态性能仿真,会跑不少点,浪费了不少时间。主要是由于当时没有用相干采样的频率,而是直接选一个和采样频率比较互质的输入频率,然后再进行加窗处理防止...

    这些都是比较基础和实用的东西,故做分享~

    以前在实验室跑ADC动态性能仿真,会跑不少点,浪费了不少时间。主要是由于当时没有用相干采样的频率,而是直接选一个和采样频率比较互质的输入频率,然后再进行加窗处理防止频谱泄露,但是一般即使加了窗,也会有一些泄露,因此需要尽量多采样一些点,这样做的效率比较低,浪费了不少时间。

    如果直接选用相干采样的频率,选取合适的点数不让谐波淹没在噪声中就OK,输出的结果不用加窗直接做FFT分析即可。

    1)直接用cadence计算器

    使用dft函数能够直接画出频谱图,由于采用了相干采样,因此直接选用矩形窗就OK,输出的信号是单频点的;

    thd函数得出的结果实际上并不是“thd”一般所定义的,即所有谐波的总和,这里计算器得出的其实是SNDR的结果,而且已经自动剔除了直流分量的,具体可以查看cadence的ocean_ref文档,其中fundemental直接写0就Ok,这样它会自己去找最大的那个信号频点

    2)通过matlab函数

    用诸如Maxim的程序的话,若采用了相干采样这里就不要加窗了,结果应该和cadence直接计算的结果一致。

    注意:如果采用table导出ADC结果的时候,是有个精度设置的,在table里头format->有效位数那里可以设置,导出的精度没有损失,结果才会和cadence直接计算的一致。

    但是如果是实际测试,输入信号和时钟频率之间的关系就不好固定了,所以还是需要加窗函数的。

    展开全文
  • _jisuanfudu: [url=home...R1=长度,r2=表指针地址,r3=ADC数值 @出R0=结果 push {r1-r7,lr} ldr r5, [r2] @读出表指针 lsls r6, r1, # 1 strh r3, [r0, r5] @数值写到滤波器缓冲区 adds r5, r5, # 2 cmp r5, r6 bne _...

    _jisuanfudu:        [url=home.php?mod=space&uid=72445]@[/url] 计算幅度

    [url=home.php?mod=space&uid=72445]@[/url] 入r0= 实部,r1= 虚部

    @ 出r0 = 幅度

    @ Mag ~=Alpha * max(|I|, |Q|) + Beta * min(|I|, |Q|)

    @ Alpha * Max + Beta * Min

    push {r1-r3,lr}

    movs r0, r0

    bpl _shibubushifushu

    mvns r0, r0                                @ 是负数转成正数

    adds r0, r0, # 1

    _shibubushifushu:                                @ 实部不是负数

    movs r1, r1

    bpl _xububushifushu

    mvns r1, r1                                @ 是负数转成正数

    adds r1, r1, # 1

    _xububushifushu:                                @ 虚部不是负数

    cmp r0, # 0

    bne _panduanxubushibushi0

    mov r0, r1

    pop {r1-r3,pc}

    _panduanxubushibushi0:

    cmp r1, # 0

    bne _jisuanfudu1

    pop {r1-r3,pc}

    _jisuanfudu1:

    ldr r2, = 31066                @ Alpha q15 0.948059448969

    ldr r3, = 12867                @ Beta q15 0.392699081699

    cmp r1, r0

    bhi _alpha_min_beta_max

    _alpha_max_beta_min:

    muls r0, r0, r2

    muls r1, r1, r3

    asrs r0, r0, # 15

    asrs r1, r1, # 15

    adds r0, r0, r1

    movs r1, # 1

    pop {r1-r3,pc}

    _alpha_min_beta_max:

    muls r0, r0, r3

    muls r1, r1, r2

    asrs r0, r0, # 15

    asrs r1, r1, # 15

    adds r0, r0, r1

    movs r1, # 0

    pop {r1-r3,pc}

    _lvboqi:

    @滤波器

    @R0=地址,R1=长度,r2=表指针地址,r3=ADC数值

    @出R0=结果

    push {r1-r7,lr}

    ldr r5, [r2]                @读出表指针

    lsls r6, r1, # 1

    strh r3, [r0, r5]        @数值写到滤波器缓冲区

    adds r5, r5, # 2

    cmp r5, r6

    bne _lvboqimeidaohuanchongquding

    movs r5, # 0

    _lvboqimeidaohuanchongquding:

    str r5, [r2]

    movs r7, # 0

    _lvboqixunhuan:

    cmp r5, r6

    bne _lvbozonghe

    movs r5, # 0

    _lvbozonghe:

    ldrh r4, [r0, r5]

    adds r5, r5, # 2

    adds r7, r7, r4

    subs r1, r1, # 1

    bne _lvboqixunhuan

    asrs r0, r7, # 10        @修改

    pop {r1-r7,pc}

    _fftjisuan:

    @ 入口 R0=数据地址

    @ 输出实部=0X20000000-0X20000400

    @ 输出虚部=0X20000400-0X20000800

    @ 结果左移6位(乘64)

    push {r0-r7,lr}

    _fft1:

    ldr r1, = 0x20000000       @ 输出地址

    movs r2, # 128             @ 蝴蝶数量

    ldr r3, = hudieweifanzhuan @ 蝴蝶位反转表

    _fft1xunhuan:

    ldr r6, [r3]               @ 取出位反转表里的地一个数据

    ldr r7, [r3, # 0x04]       @ 取出第二个

    ldr r6, [r0, r6]           @ 根据位反转表找到对应的输入数据

    ldr r7, [r0, r7]           @ 第二个

    adds r4, r6, r7            @ 求出蝴蝶上

    subs r5, r6, r7            @ 求出蝴蝶下

    str r4, [r1]

    str r5, [r1, # 0x04]       @ 把反过来的顺序排列

    adds r1, r1, # 0x08        @ 输出的地址自增

    adds r3, r3, # 0x08        @ 反转表自增

    subs r2, r2, # 1           @ 蝴蝶数减1

    bne _fft1xunhuan           @ 蝴蝶数不到

    _fft2:

    ldr r0, = 0x20000000       @ 实部输出地址0x20000000

    movs r4, # 1

    lsls r4, r4, # 10

    adds r4, r4, r0            @ 虚部输出地址 0x20000400

    mov r8, r4                 @ 实部和虚部中间

    _fft2xunhuan:

    ldr r4, [r0]               @ 取出数据0

    ldr r5, [r0, # 0x08]       @ 取出数据2

    ldr r6, [r0, # 0x04]       @ 取出数据1

    ldr r7, [r0, # 0x0c]       @ 取出数据3

    adds r1, r4, r5            @ 计算蝴蝶上

    subs r2, r4, r5            @ 计算蝴蝶下

    mov r3, r6                @ 0 r 蝴蝶上实部

    movs r4, # 0

    subs r4, r4, r7                @ 0i 蝴蝶上虚部

    mov r5, r6                @ 3r 蝴蝶下实部

    mov r6, r7                @ 3i 蝴蝶下虚部

    movs r7, # 1

    lsls r7, r7, # 10       @ 内存实部和虚部中间

    adds r7, r7, r0         @ R7等于虚部首地址

    str r1, [r0]            @ 蝴蝶上保存实部

    str r2, [r0, # 0x08]    @ 蝴蝶下实部

    str r3, [r0, # 0x04]    @ 第二组蝴蝶上实部

    str r4, [r7, # 0x04]    @ 第二组蝴蝶上虚部

    str r5, [r0, # 0x0c]        @ 第二组蝴蝶下实部

    str r6, [r7, # 0x0c]    @ 第二组下虚部

    adds r0, r0, # 0x10     @ 蝴蝶组自增

    cmp r0, r8

    bne _fft2xunhuan        @ 地址不到循环

    _fft3:

    ldr r6, = xuanzhuanyinzi       @ 旋转因子自增变量

    ldr r2, = fft3xuanzhuanyinzi   @ 旋转因子第三步的表

    ldr r0, = 0x20000000           @ 实部输出地址

    str r2, [r6]                   @ 旋转因子表首地址写到变量

    movs r1, # 1

    lsls r1, r1, # 10

    adds r1, r1, r0                @ 虚部输出地址

    mov r8, r1                       @ R8虚部地址自增变量

    mov r9, r0                     @ R9实部地址自增变量

    mov r10, r2                       @ 旋转因子地址写到R10

    movs r5, # 0x10                       @ 两组蝴蝶的距离

    mov r11, r5                    @ 写到R11

    movs r5, # 4                   @ 旋转因子数量

    mov r12, r5                       @ 旋转因子数量写到R12

    bl _fftg

    _fft4:

    ldr r6, = xuanzhuanyinzi        @ 旋转因子自增变量

    ldr r2, = fft4xuanzhuanyinzi        @ 旋转因子第四步表

    ldr r0, = 0x20000000                @ 实部地址

    str r2, [r6]                        @ 第四步旋转因子表写到自增变量

    movs r1, # 1

    lsls r1, r1, # 10

    adds r1, r1, r0                        @ r1 = 虚部地址

    mov r8, r1                        @

    mov r9, r0

    mov r10, r2

    movs r5, # 0x20

    mov r11, r5

    movs r5, # 8

    mov r12, r5

    bl _fftg

    _fft5:

    ldr r6, = xuanzhuanyinzi

    ldr r2, = fft5xuanzhuanyinzi

    ldr r0, = 0x20000000

    str r2, [r6]

    movs r1, # 1

    lsls r1, r1, # 10

    adds r1, r1, r0

    mov r8, r1

    mov r9, r0

    mov r10, r2

    movs r5, # 0x40

    mov r11, r5

    movs r5, # 16

    mov r12, r5

    bl _fftg

    _fft6:

    ldr r6, = xuanzhuanyinzi

    ldr r2, = fft6xuanzhuanyinzi

    ldr r0, = 0x20000000

    str r2, [r6]

    movs r1, # 1

    lsls r1, r1, # 10

    adds r1, r1, r0

    mov r8, r1

    mov r9, r0

    mov r10, r2

    movs r5, # 0x80

    mov r11, r5

    movs r5, # 32

    mov r12, r5

    bl _fftg

    _fft7:

    ldr r6, = xuanzhuanyinzi

    ldr r2, = fft7xuanzhuanyinzi

    ldr r0, = 0x20000000

    str r2, [r6]

    movs r1, # 1

    lsls r1, r1, # 10

    adds r1, r1, r0

    mov r8, r1

    mov r9, r0

    mov r10, r2

    movs r5, # 1

    lsls r5, r5, # 8

    mov r11, r5

    movs r5, # 64

    mov r12, r5

    bl _fftg

    _fft8:

    ldr r6, = xuanzhuanyinzi

    ldr r2, = fft8xuanzhuanyinzi

    ldr r0, = 0x20000000

    str r2, [r6]

    movs r1, # 1

    lsls r1, r1, # 10

    adds r1, r1, r0

    mov r8, r1

    mov r9, r0

    mov r10, r2

    movs r5, # 1

    lsls r5, r5, # 9

    mov r11, r5

    movs r5, # 128

    mov r12, r5

    bl _fftg

    pop {r0-r7,pc}

    _fftg:

    push {lr}                @ LR保存到堆栈

    movs r4, # 0

    mov lr, r4

    _fftxunhuan:

    mov r0, r9                @ R0实部地址自增变量地址

    mov r1, r8                @ R1虚部地址自增变量地址

    mov r2, r10                @ 旋转因子表地址地址

    mov r3, r11                @ 两组蝴蝶的距离

    ldr r4, [r0]                @ r4=蝴蝶上实部

    ldr r5, [r1]                @ r5=蝴蝶上虚部

    ldr r6, [r0, r3]        @ r6=蝴蝶下实部

    ldr r7, [r1, r3]        @ r7=蝴蝶下虚部

    ldr r3, [r2]                @ SR

    ldr r2, [r2, # 0x04]        @ SI

    push {r4,r5}                @ R4 R5保存到堆栈

    @ (a+bi)(c+di)=(ac-bd)+(ad+bc)i

    mov r4, r6                @ r4=蝴蝶下实部

    muls r4, r4, r3                @ 乘旋转因子        ac

    mov r5, r7                @ R5=蝴蝶下虚部

    muls r5, r5, r2                @ 乘旋转因子        bd

    subs r4, r4, r5                @ ac-bd

    asrs r4, r4, # 15        @ 截断Q15

    mov r5, r6

    muls r5, r5, r2                @ ad

    muls r7, r7, r3                @ bc

    adds r7, r7, r5         @ ad+bc

    asrs r7, r7, # 15        @ 截断Q15

    pop {r2,r5}                @ R2= 蝴蝶上实部

    @ r5= 蝴蝶上虚部

    adds r3, r2, r4            @ 上实部加下实部=结果上实部

    subs r2, r2, r4            @ 下实部减上实部=结果下实部

    adds r4, r5, r7                @ 上虚部加下虚部=结果上虚部

    subs r5, r5, r7                @ 上虚部减下虚部=结果下虚部

    mov r6, r11                @ r6=蝴蝶距离

    asrs r3, r3, # 1        @ 右移一位防止溢出

    asrs r4, r4, # 1        @ 右移一位防止溢出

    asrs r2, r2, # 1        @ 右移一位防止溢出

    asrs r5, r5, # 1        @ 右移一位防止溢出

    str r3, [r0]                @ 保存上实部

    str r4, [r1]                @ 保存上虚部

    str r2, [r0, r6]        @ 保存下实部

    str r5, [r1, r6]        @ 保存下虚部

    adds r0, r0, # 0x04        @ 实部地址加4

    adds r1, r1, # 0x04        @ 虚部地址加4

    mov r9, r0                @ r9=当前实部地址

    mov r8, r1                @ R8=当前虚部地址

    mov r2, r10                @ r2= 当前旋转因子地址

    adds r2, r2, # 0x08        @ R2 = 旋转因子地址加8

    mov r10, r2             @ 保存回去

    mov r4, lr                @ 一个蝴蝶旋转因子变量

    adds r4, r4, # 1        @ 自增

    mov lr, r4                 @ 保存回去

    mov r7, r12             @ 取出旋转因子总数量

    cmp r4, r7              @ 到没到数量

    bne _fftxunhuan         @ 没到继续循环

    movs  r4, # 0                @ 到了R4=0

    mov lr, r4                @ 一个蝴蝶旋转因子变量清0

    ldr r6, = xuanzhuanyinzi @ R6=旋转因子自增变量地址

    ldr r5, = 0x20000400         @ R5=虚部地址

    ldr r6, [r6]                 @ 取出旋转因子自增变量

    mov r7, r11                @ R7= 蝴蝶距离

    mov r10, r6              @ R10 = 旋转因子自增变量

    adds r0, r0, r7                @ 实部地址加蝴蝶距离

    adds r1, r1, r7                @ 虚部地址加蝴蝶距离

    mov r9, r0                @ R9等于当前实部地址

    mov r8, r1                @ R8等于当前虚部地址

    cmp r0, r5                @ 实部到没到虚部边界

    bne _fftxunhuan                @ 没到循环计算

    pop {pc}                @ 返回

    .ltorg                        @ 文字池

    展开全文
  • 通过将一个100ksps的采样频率应用到一个9.9 kHz模拟输入信号的12位ADC,您可以得到图1中的FFT图。9.9 kHz下的信号便为基本输入信号 (A)。该基本输入信号寄生接近于0 dB。
  • 输入原始的ADC数据,加窗处理后经过FFT变换,将单位变换成dBm等。代码集成化程度较高,所使用语言为Python,C语言(基于STM32F767)将会在日后上传。不定期更新功能。 更新日期:3.28
  • 可以通过周期性地收集大量的ADC输出转换采样来生成FFT图。一般而言,ADC厂商们将一种单音、满量程模拟输入信号用于其产品说明书的典型性能曲线。您从这些转换获得数据,然后绘制出一幅与图1相似的图。
  • 5529ADC采样进行fft计算

    2018-08-14 16:00:39
    5529ADC采样进行fft计算,实测,2018电子设计大赛程序
  • STM32自带adc实现低频示波器及FFT频谱显示
  • STM32F4单片机ADC采样及ARM-DSP库的FFT

    千次阅读 多人点赞 2019-07-29 12:31:00
    模拟信号经过ADC采样后变成数字信号,数字信号可以进行FFT运算,在频域中更容易分析信号的特征。本文将介绍如何用STM32F4的进行ADC采样,并利用ARMDSP库里的FFT算法对ADC采样值进行快速傅里叶变换。
  • 最近,因为项目需要处理音频信号,对AD采集的音频信号进行FFT运算,记录一下学习过程。 主要内容: 1.ADC和DMA各配置有什么实际意义 2.配置好的ADC如何计算采样率和电压值 cubeMX配置ADC+DMA 这里我用的是STM32F407...
  • DMA+ADC+TIMER+FFT

    2018-07-24 16:02:59
    使用TIMER定时器触发ADC采集,并将采集的数据通过DMA传送出来,连续采集1024个点后进行一次FFT运算,可以精确定时进行连续多样的采集,使用内部DSP库进行FFT计算结果可以很精确。
  • 基于STM32F4的电流检测 (其实就是对输入的电压信号进行FFT计算)STM32F4 电流检测前言1.FFT函数的运算数组定义2.ADC读取数据3.FFT的计算代码4.FFT 的计算结果处理 STM32F4 电流检测 前言 1.通过12位ADC读取数据存入...
  • STM32F407_ADC_DMA_FFT

    2020-10-18 19:31:02
    使用STM32F407内置ADC,可控制采样频率为512KHZ、256KHZ、128KHZ,采用定时器+DMA+ADC的方式不断读取输入电压,进行FFT,之后再将结果通过串口的方式打印出来,可以修改采样频率与点数,体验一下FFT的神奇之处。
  • 2、ADC引脚输入一般控制在0~3.3V,超过3.3V可能回烧掉。 VSSA= Vref- =GND 2.4V<= Vref+ <=VDDA=3.3V 3、注入通道 和 规则通道 如果只有规则通道就按照通道排序进行运行,但是若是在规则通道中添加...
  • ADC_DMA采集FFT变换求初值相位差,目前已可以成功求出相位,里面部分程序注释掉了,自己打开注释就行。
  • (2)本资源使用stm32自带的ADC采集外部输入的正弦信号(外加信号需要偏置,因为32自带ADC采集0~3.3V)。 (3)采用stm32官方DSP库的FFT算法处理数据(64,256,1024点处理) (4)计算正弦波失真度。 (5) 采样频率...
  • STM32自带adc实现低频示波器及FFT频谱显示 STM32自带adc实现低频示波器及FFT频谱显示 STM32自带adc实现低频示波器及FFT频谱显示 STM32自带adc实现低频示波器及FFT频谱显示
  • 数字示波器,实现ADC采样,波形显示,fft处理数据,频率计作用
  • 对频率变化的信号测量频率后确定时钟触发频率,即确定了采样率,用ADC双通道测量两路信号,用DMA传输至一个数组内存中,然后显示波形、计算Vpp、并对数据进行FFT,分析频谱确定波形名称(可判断正弦波,三角波,方波...
  • (函数主体是按照测量交流函数均值来写的,经过测算发现存在一定的误差,所以仿真就使用直流电压进行测量)(改变电源的输出电压,再按下KEY2,即可改变LCD显示电压)当按下KEY1时,LCD进行显示FFT频谱分析功能,...
  • STM32F7通过TIM+DMA+ADC实现FFT功能,基础版本,没有使用DSP和FPU
  • /*********************************************************************************************************************************************** 这里讲讲傅立叶快速变换FFT算法的C例程,不恰当的地方还请...
  • stm32f4xx adc dma fft 源码工程 DMA_Configuration(); NVIC_Configuration(ENABLE); ADC_Configuration();

空空如也

空空如也

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

fftadc

友情链接: jquery_password.rar