STM32 如何利用FFT(快速傅立葉變換)對週期訊號的波形識別?
阿新 • • 發佈:2022-04-05
這裡使用的晶片型號為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)將輸入訊號轉換為方波來測頻率(轉換為方波但頻率不變)