STM32 如何利用FFT(快速傅里叶变换)对周期信号的波形识别?


这里使用的芯片型号为STM32F103ZET6

我们要实现的目标是利用FFT(快速傅里叶变换)对周期信号的波形识别,那么接下来要实现的功能有:

  1. 利用时钟中断(这里我用的是TIM3的中断)采集 信号的AD数据
  2. 利用另一时钟中断(这里我用的是TIM5的中断)获取 波形的频率(这里需要留意,我是通过运放的芯片将正弦波转换为方波的,之后会稍微详细讲讲)
  3. 利用TIM5获取到的信号频率对TIM3的AD采样速率进行更改,使得TIM3的采样频率是信号频率的倍数,以保证FFT计算得出的结果准确
  4. 对AD采样得到的数据用FFT进行处理后分析各项数据

那么我们需要对以下功能进行初始化

  • IO口初始化
 1 void GPIOA_Init(void)  2 {  3  GPIO_InitTypeDef GPIO_STR;  4 /*此IO用于ADC采样*/  5  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);  6 GPIO_STR.GPIO_Pin = GPIO_Pin_0;  7 GPIO_STR.GPIO_Mode = GPIO_Mode_AIN;  8 GPIO_Init(GPIOA, &GPIO_STR);  9 /*此IO用于外部中断*/ 10 GPIO_STR.GPIO_Mode=GPIO_Mode_IPU; 11 GPIO_STR.GPIO_Pin=GPIO_Pin_2; 12 GPIO_STR.GPIO_Speed=GPIO_Speed_50MHz; 13 GPIO_Init(GPIOA, &GPIO_STR); 14 }
  • TIM3计数功能的初始化,我这里用的arr=100,psc=1;
 1 void TIM3_Init(u16 arr,u16 psc)  2 {  3  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  4  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  5 TIM_TimeBaseStructure.TIM_Period = arr;  6 TIM_TimeBaseStructure.TIM_Prescaler = psc;  7 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;  8 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  9 TIM_TimeBaseInit(TIM3, & TIM_TimeBaseStructure); 10 TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE );//直接在这里开启更新中断使能,都是TIM开头看着比较整齐 11  TIM_Cmd(TIM3, ENABLE); 12 }
  • TIM3中断优先级初始化
1 void TIM3_NVIC_Init(void) 2 { 3  NVIC_InitTypeDef NVIC_InitStructure; 4 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; 5 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; 6 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; 7 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 8 NVIC_Init(&NVIC_InitStructure); 9 }
  • TIM5计数功能的初始化,我这里用的arr=65535,psc=0;
 void TIM5_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE); TIM_TimeBaseStructure.TIM_Period = arr; TIM_TimeBaseStructure.TIM_Prescaler = psc; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM5, & TIM_TimeBaseStructure); TIM_SetCounter(TIM5,0); TIM_ITConfig(TIM5, TIM_IT_Update | TIM_IT_CC3, ENABLE );//这里也直接开启了更新中断和捕获中断,捕获中断用于获取信号的频率  TIM_Cmd(TIM5, ENABLE); }
  • TIM5中断优先级初始化
1 void TIM5_NVIC_Init(void) 2 { 3  NVIC_InitTypeDef NVIC_InitStructure; 4 NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; 5 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; 6 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; 7 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 8 NVIC_Init(&NVIC_InitStructure); 9 }
  • TIM5输入捕获功能的初始化
 1 void TIM5_IC_Init(void)  2 {  3  TIM_ICInitTypeDef TIM_ICInitStructure;  4 TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;//TIM5_CH3对应的是PA2  5 TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获  6 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//让TIM的四个输入通道对应上IC1,IC2,IC3,IC4  7 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//不分频  8 TIM_ICInitStructure.TIM_ICFilter = 0x0;//不滤波  9 TIM_ICInit(TIM5, &TIM_ICInitStructure); 10 }

 

  • ADC1的初始化,这里我直接用的官方例程来的
 1 void ADC1_Init(void)  2 {  3  ADC_InitTypeDef ADC_InitStructure;  4  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);  5 /* ADC1 configuration ------------------------------------------------------*/  6 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  7 ADC_InitStructure.ADC_ScanConvMode = DISABLE;  8 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  9 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; 10 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; 11 ADC_InitStructure.ADC_NbrOfChannel = 1; 12 ADC_Init(ADC1, &ADC_InitStructure); 13 14 /* ADC1 regular channel14 configuration */ 15 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); 16 17 /* Enable ADC1 DMA */ 18  ADC_DMACmd(ADC1, ENABLE); 19 20 /* Enable ADC1 */ 21  ADC_Cmd(ADC1, ENABLE); 22 23 /* Enable ADC1 reset calibration register */ 24  ADC_ResetCalibration(ADC1); 25 /* Check the end of ADC1 reset calibration register */ 26 while(ADC_GetResetCalibrationStatus(ADC1)); 27 28 /* Start ADC1 calibration */ 29  ADC_StartCalibration(ADC1); 30 /* Check the end of ADC1 calibration */ 31 while(ADC_GetCalibrationStatus(ADC1)); 32 33 /* Start ADC1 Software Conversion */ 34  ADC_SoftwareStartConvCmd(ADC1, ENABLE); 35 }

 我们有了以上初始化程序之后呢,在主函数里面调用就行了

 

 

接下来就要在TIM3中断内写AD采样了,这里的N为采样点数,store为存储AD的采样的信号值

void TIM3_IRQHandler(void) { static unsigned short int count=0; store[count++]=ADC_GetConversionValue(ADC1); if(count>=N)count=0; TIM_ClearITPendingBit(TIM3, TIM_IT_Update); }

 

 

我们既然要使得 TIM3的采样频率是信号频率的倍数,那么就需要用到另一个定时器TIM5来测量信号的频率并对TIM3的计数周期(TIM3->ARR)进行更改

以下代码有全局变量int F[3],Average[20],Count2;unsigned char Rise;

其中:

  1. F数组内,F[0]存放的是这一次捕获到的上升沿计数值(F[2])与上一次捕获到的上升沿计数值(F[1])的差值
  2. Average数组内存放的是将要进行求平均值的数据,通过求平均值来减少频率的误差(其实这个求平均值不要也行),Count2变量存放的就是Average内数据的个数啦
  3. Rise表示的是当前为第几次上升沿,如果是第二个上升沿的话算周期,求频率
 1 void TIM5_IRQHandler(void)//算频率  2 {  3 if(TIM_GetITStatus(TIM5, TIM_IT_CC3))//边沿中断,因为我们设置的是上升沿捕获,所以经过上升沿才会进入中断  4  {  5 F[++Rise]=TIM_GetCapture3(TIM5);//其中,F[0]为空值,可将差值放入F[0]//Rise记录上升沿次数  6 if(Rise==2)//如果经过第二次上升沿那么就求它们的周期  7  {  8 F[0]=(F[2]+65535*Count1-F[1]);//一秒计数36000000次,F[0]的值与其有关  9 Average[Count2++]=F[0]; 10 if(Count2>=20)Count2=0; 11 TIM3->ARR=(uint32_t)(F[0]/N/(TIM3->PSC))-1; 12 /* 13  理论上TIM3->ARR=F[0]/N/(TIM3->PSC+1)-1;进行采样会非常准确 14  但实际上可能是因为开了两个定时器中断有偏差所以改为F[0]/N/(TIM3->PSC)-1 15  ---I/F[0]信号频率 I为主频率72 000 000即72MHz,这里我用#define I 72000000 来表示了 16  ---I/F[0]*N要达到的采样频率 17  ---采样频率=I/(PSC+1)/(ARR+1) 18  ---I/F[0]*N=I/(PSC+1)/(ARR+1) 19 */ 20 Count1=0;//当频率计算出来的那一刻把该值清零 21 Rise=0; 22 TIM_SetCounter(TIM5,0); 23  } 24  } 25 if(TIM_GetITStatus(TIM5, TIM_IT_Update)&&(Rise==1))//更新中断,并且经过第一个上升沿 26  { 27 Count1++;//记录溢出次数 28  } 29 TIM_ClearITPendingBit(TIM5, TIM_IT_CC3 | TIM_IT_Update); 30 }

然后我们就可以用FFT处理store内的数据来算THD(谐波失真度)了,通过THD我们可以得出波形

之后再补上这个FFT代码的坑

 

最后一点,我是如何通过TIM5来计算非方波信号的频率的呢?

让原信号通过一个运放芯片(LM358)将输入信号转换为方波来测频率(转换为方波但频率不变)


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM