這里使用的芯片型號為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)將輸入信號轉換為方波來測頻率(轉換為方波但頻率不變)