基於stm32f103單片機對信號頻率、佔空比的測量。
最近開始儀器儀表方面的學習了,計劃後期做一個示波器。所以這周就在stm32f103上面做了一個測量頻率、佔空比的小設計。總體上精度還是比較高的,測量頻率量程在35Hz—190KHz。頻率可以精確到小數點後四位,佔空比測量的精度也比較高,可以到小數點後兩位。
說到用stm32測頻率,都會想到用定時器的輸入捕獲模式,只需要一個定時器和一個IO口即可,前幾天在論壇上看到還有一種是用兩個定時器測頻率,一個定時器用來檢測信號跳變沿,另外一個用來精準定時,比如說用TIM1檢測跳變沿(假設爲上升沿),TIM2開一個1s的定時器中斷,這個1s就比較準確,在1s內TM1檢測到了多少個上升沿改信號的頻率就是多少。這種方法我本週二試過,精度比輸入捕獲模式下的高,而且還比較穩定,缺點是用到了兩個定時器,佔用的cpu資源較多。考慮到我後面任務需要,定時器可能會不夠用,故還是用的輸入捕獲模式。
實驗平臺:stm32f103zet6
定時器及通道:TIM2的通道2
IO口:PA1
定時器及輸入捕獲模式的配置:
u8 Edge_Flag; //高低電平的標誌位
u16 Rising,Falling,Rising_Last;
//定時器2輸入捕獲中斷初始化
void TIM2_Cap_Init()
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_ICInitTypeDef TIM_ICInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING; //浮空
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_InitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_InitStruct.TIM_CounterMode=TIM_CounterMode_Up; //向上計數
TIM_InitStruct.TIM_Period=0xffff;
TIM_InitStruct.TIM_Prescaler=0; //分頻係數 當分頻係數越大時,可測量頻率最小值越小
TIM_TimeBaseInit(TIM2,&TIM_InitStruct);
TIM_ICInitStruct.TIM_Channel=TIM_Channel_2;
TIM_ICInitStruct.TIM_ICFilter=0x00; //不濾波
TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising; //第一次是檢測上升沿進入中斷
TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_DirectTI; //直接映射
TIM_ICInit(TIM2,&TIM_ICInitStruct);
TIM_ITConfig(TIM2,TIM_IT_CC2,ENABLE);
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0x00; //搶佔優先級
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0x01; //響應優先級
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM2,ENABLE); //使能
}
定時器2的中斷服務函數:
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_CC2)!=RESET) //捕獲到上升沿
{
if(Edge_Flag==1)
{
Rising=TIM2->CCR2; //第一次檢測到下降沿
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising); //再次改爲上升沿觸發
Edge_Flag++;
}
else if(Edge_Flag==2)
{
Rising_Last=TIM2->CCR2;
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising);
Edge_Flag=0; //標誌位清0
TIM_SetCounter(TIM2,0); //定時器清0
}
else
{
Falling=TIM2->CCR2; //第一次檢測到上升沿
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Falling); //將上升沿觸發改爲下降沿觸發
Edge_Flag++;
}
}
TIM_ClearITPendingBit(TIM2,TIM_IT_CC2); //清除標誌位
}
輸入捕獲模式下,中斷服務函數裏面處理的內容要儘量少,所以在記錄定時器捕獲到值時,直接將TIM2的CCR2寄存器裏面的值賦值給相應變量。
主函數:
int main(void)
{
delay_init(); //延時函數初始化
OLED_Init(); //oled屏幕初始化
OLED_On();
OLED_Clear();
TIM2_Cap_Init(); //定時器初始化
while(1)
{
OLED_ShowString(0,0,"Freq:",16);
OLED_ShowString(100,0,"Hz",16);
OLED_ShowNum(50,0,72000000/(Rising_Last-Falling),6,16);
OLED_ShowString(0,2,"Duty:",16);
OLED_ShowString(95,2,"%",16);
OLED_Showdecimal(50,2,(float)(Rising-Falling)/(float)(Rising_Last-Falling)*100,2,3,16);
delay_ms(100);
}
}
變量Rising_Last爲第二次檢測到上升沿捕獲到的值,Falling爲第一次捕獲到的值,兩者之差爲定時器計數的數值差,根據TIM_Prescaler=0(即不分頻),主頻爲72M,頻率f=72M/(Rising_Last-Falling)。而Rising-Falling爲高電平的時間,除以一個週期就是佔空比了。
實驗現象
頻率10K,佔空比60%
頻率102.56K,佔空比33%
可見效果還是挺不錯的,但是這種辦法包括上面講到的雙定時器法都有一個缺點,那就是當信號幅度小於2v時,單片機就檢測不到跳變沿。外部還需要硬件對信號進行適當放大。也是前幾天,在論壇上看到有人提出一個測頻率的叫過零檢測法,用ADC讀信號電壓值,ADC值爲0時進行記錄,再次爲0就相當於經過了半個週期。計算兩次ADC爲0的時間差,就可以計算出信號的頻率,這種方法不會受限於信號幅度大小。原理也比較簡單,相關程序我也正在寫,等測試沒問題後我再發出來和大家分享!