STM32 如何利用FFT(快速傅里叶变换)对周期信号的波形识别?
作者:互联网
这里使用的芯片型号为STM32F103ZET6
我们要实现的目标是利用FFT(快速傅里叶变换)对周期信号的波形识别,那么接下来要实现的功能有:
- 利用时钟中断(这里我用的是TIM3的中断)采集 信号的AD数据
- 利用另一时钟中断(这里我用的是TIM5的中断)获取 波形的频率(这里需要留意,我是通过运放的芯片将正弦波转换为方波的,之后会稍微详细讲讲)
- 利用TIM5获取到的信号频率对TIM3的AD采样速率进行更改,使得TIM3的采样频率是信号频率的倍数,以保证FFT计算得出的结果准确
- 对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计数功能的初始化,我
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计数功能的初始化
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;
其中:
- F数组内,F[0]存放的是这一次捕获到的上升沿计数值(F[2])与上一次捕获到的上升沿计数值(F[1])的差值
- Average数组内存放的是将要进行求平均值的数据,通过求平均值来减少频率的误差(其实这个求平均值不要也行),Count2变量存放的就是Average内数据的个数啦
- 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