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计数功能的初始化,我这里用的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;
其中:
- 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)将输入信号转换为方波来测频率(转换为方波但频率不变)