其他分享
首页 > 其他分享> > STM32 如何利用FFT(快速傅里叶变换)对周期信号的波形识别?

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

作者:互联网

这里使用的芯片型号为STM32F103ZET6

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

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

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

 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 }
 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 }
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 }
 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);
}
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 }
 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 }

 

 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)将输入信号转换为方波来测频率(转换为方波但频率不变)

标签:NVIC,TIM5,FFT,STM32,TIM,ADC,ADC1,TIM3,傅里叶
来源: https://www.cnblogs.com/Run-Noob/p/16102504.html