1. 程式人生 > 其它 >STM32 如何利用FFT(快速傅立葉變換)對週期訊號的波形識別?

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)將輸入訊號轉換為方波來測頻率(轉換為方波但頻率不變)