学习记录(二)

本文介绍了快速傅里叶变换(FFT)在嵌入式系统中的运用,包括FFT的功能测试,如求幅度、频率和相位的步骤。同时,详细阐述了如何结合TIM、ADC和DMA进行信号采集,以获取采样频率。此外,讨论了频谱泄露问题及其解决方案,并探讨了函数指针在代码中的应用,包括定义、使用及作为参数传递。
摘要由CSDN通过智能技术生成

提示:小菜鸟学习路


前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、FFT功能测试?

FFT理解

FFT,快速傅里叶变换,可以将一个时域信号变换到频域。
对一个时域信号进行采集,通过ADC,以一定的采样频率去控制ADC去采样,采集N个点,对这N个点进行FFT,可以得到信号的幅值、频率、以及相位。在对信号采样要符合采样定理:F>2fmax(fmax的含义是大于你待测信号最大频率包含谐波),但实际测得>3fmax的采样率,数据最好。

FFT求幅度

1.进行FFT,得到n个复数。每个复数值包含着一个特定频率的信息。根据这N个复数,可以知道每个点的频率以及幅度值。
2.求出复数的绝对值。
3.提取出的0Hz对应强度(信号振幅),又称直流分量。
其对应的赋值为 当前点的绝对值/N。
直流分量以外的分量所对应的信号振幅为 当前点的绝对值*2/N。

FFT求频率

1.在知道了采样频率Fs后,1.FFT后的第X个复数对应的实际频率为X*Fs/N
2.删去重复值–只有在0~N/2这一半的频率有效。要提高频率的分辨率,则必须增加采样点数,即采样时间。

FFT求相位

1.知道频率点的位置
2.相位: 对应点的a+i b, 相位角为arc tan (b/a)。

二、TIM+ADC+DMA

FFT之后要获得信号的频率、幅值、相位就还需要知道采样频率Fs,因此一般都是用定时器触发ADC采样,再用DMA进行搬运,定时器时间可以自己设置,采样频率也就知道了。
FFT之后的分辨率是:采样频率/采样点数。
由定时器触发ADC采样就可以得到采样频率。

__IO uint16_t	ADC_ConvertedValue[4]={0};
u8 DMA_ADC_finish=0;

void TIM_ADC_DMA(void)
{
	
	   NVIC_InitTypeDef  NVIC_InitStructure;
     ADC_InitTypeDef  ADC_InitStructure;
     DMA_InitTypeDef  DMA_InitStructure;    
     GPIO_InitTypeDef  GPIO_InitStructure;
     ADC_CommonInitTypeDef ADC_CommonInitStructure;    
     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOF , ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);
	  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3时钟

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 ;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOF, &GPIO_InitStructure);
		

    DMA_DeInit(DMA2_Stream0);
		while (DMA_GetCmdStatus(DMA2_Stream0)!= DISABLE);//等待 DMA可配置
    DMA_InitStructure.DMA_Channel = DMA_Channel_2; 
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADC_ConvertedValue;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC3->DR);
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_InitStructure.DMA_BufferSize = 4;
    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_FIFOMode = DMA_FIFOMode_Disable;//DMA_FIFOMode_Enable;         
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA2_Stream0, &DMA_InitStructure);
    DMA_ClearITPendingBit(DMA2_Stream0,DMA_IT_TC);
    DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
    DMA_Cmd(DMA2_Stream0, ENABLE);



    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit(&ADC_CommonInitStructure); 
	

    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;//多通道采用扫面模式
    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;
    ADC_InitStructure.ADC_NbrOfConversion = 4;
    ADC_Init(ADC3, &ADC_InitStructure);
		

    ADC_RegularChannelConfig(ADC3, ADC_Channel_4, 1, ADC_SampleTime_56Cycles);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_5, 2, ADC_SampleTime_56Cycles);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_6, 3, ADC_SampleTime_56Cycles);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_7, 4, ADC_SampleTime_56Cycles);
   
    ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);
		ADC_DMACmd(ADC3, ENABLE); //使能指定的ADC的DMA 
		ADC_Cmd(ADC3, ENABLE);    //使能指定的ADC
		
    TIM_Cmd(TIM3, DISABLE);

		TIM_TimeBaseInitStruct.TIM_Period = 167;         //计数值  42MHz*2/1000/168=500Hz  
		TIM_TimeBaseInitStruct.TIM_Prescaler = 999;     //预分频器1000分频
		TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟输入1分频
		TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
		TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct); 
		TIM_ARRPreloadConfig(TIM3, ENABLE); //允许TIM3定时重载
		TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);  //选择TIM3的UPDATA事件更新为触发源
		TIM_Cmd(TIM3, ENABLE);     //使能TIM3
	 
   

     /**************************ADC DMA NVIC配置**********************************/  
  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
 	
	
	 
}


void DMA2_Stream0_IRQHandler(void)
{
     if(DMA_GetITStatus(DMA2_Stream0,DMA_IT_TCIF0))
		 {
			   DMA_ADC_finish=1;
               TIM_Cmd(TIM3, DISABLE);     //失能TIM3
			  DMA_ClearITPendingBit(DMA2_Stream0,DMA_IT_TCIF0);
		 }
}



三、FFT实现

	arm_cfft_radix4_f32(&scfft,fft_inputbuf);	//FFT计算(基4)
 	arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH);	//把运算结果复数求模得幅值 
   	 arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,0,1);//初始化scfft结构体,设定FFT相关参数
   	 要代码 发QQ邮箱

但在实际会存在频谱泄露的问题。
频谱泄露,对于频率为fs的正弦序列,它的频谱只在fs处有离散谱。
离散时间序列通常是无限长的序列,因而处理这个序列的时候需要将她截断。截断相当于将序列乘以窗函数。根据频域卷积定理,时域相乘等于频域卷积。因此,离散时间序列截距后的频谱不同于它之前的频谱。

(1-cos(2*PI*t/FFT_LENGTH))/2;
将时域信号乘上这个会降低频谱泄露。

四、函数指针的学习

函数指针:在整体规则不变的情况下,需要改变每一变量时使用函数指针。
定义函数指针

        int  myfunc(int a, char b )
        {
            
            printf(" int  myfunc(int a, char b )");
            
        }
        void  text01()
        {
             int(*FUN_TYPE)(int,char)  =myfunc  ;
            
        }

for example

    如何定义一个指向函数的指针。
	int con1(int a,int b)
			{
			  return a+b;
			}
			
				int con2(int a,int b)
			{
			  return a+b+10;
			}	
			
				int con3(int a,int b)
			{
			  return a+b+20;
			}
			
			void text01()
			{
			   int(*FUN_TYPE)(int ,int )=con3;
			   int ret = FUN_TYPE(10,20);
				 printf("zhi = %d \r\n",ret);
			}
//函数指针:		函数可以做另外一个函数的参数	
			void  chuansan(int(*FUN_TYPE)(int ,int ))
			{
			  int a = 10;
				int b = 20;
				int ret = FUN_TYPE(a,b);
				printf("zhi = %d \r\n",ret);
			}
			
			void text02()
			{
				
					chuansan(con1);
				
			}		

//函数指针: 函数指针数组
	int func0()
	{
		printf("func0  \r\n");
	}
	
		int func1()
	{
		printf("func1  \r\n");
	}
	
		int func2()
	{
		printf("func2  \r\n");
	}
			
			
  void text03()
  {
	  void (*FUN_TYPE[3])();
		int i;
		FUN_TYPE[0] = func0;
		FUN_TYPE[1] = func1;
		FUN_TYPE[2] = func2;
		for(i=0;i<3;i++)
		{
				FUN_TYPE[i]();
		}
	}		
//函数指针做回调函数   打印数组的每个成员变量
	void  printALLarry(void* arr,int eleSize,int len,void(*print)(void*)) 
	{
	  int i;
	  char *start = (char *)arr;//将void *强制类型转换成char *,方便计算每一个元素的首地址
		for( i =0;i<len;i++)
		 {
		   print(start+i*eleSize); //start+i*eleSize:每个元素的首地址
		 }
	
	}
	
	void print(void * data)
	{
		int *p =(int *)data; //强制类型转换成数组的类型
		printf("%d",*p);
	}
	 void text04()
	 {
	    int arr[5]={1,2,3,4,5};
		  printALLarry(arr,sizeof(int),5,print);
	 }

	 
	//打印结构体数组
			 struct person
			 {
				 char name[64];
				 int age;
			 };
	 void printarry(void *data)
	 {
	   struct person* person =(struct person*)data;
		 printf("%s  %d \r\n",person ->name ,person ->age );
	 
	 }
	 void text05()
	 {
		
			 struct person person1[]={
			 {"aaa",10},
			 {"aaa",20},
			 {"aaa",30},
			 {"aaa",40},
			 {"aaa",50},
			 };
	 	  printALLarry(person1,sizeof(struct person),5,printarry);
	 }

总结

提示:这周,我处于浮躁的状态,并没有沉下心去攻克某一个知识点,希望在下一周的学习能改变这种状态。
要持续不断地熟练和体会最基本的概念,不论你已经达到多高的水平,永远不要荒废基本功。 很好的一句激励自己的话。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值