TIMER 可以透過外部輸入訊號或內部訊號觸發中斷,進而產生 Capture 事件,記錄 Capture 時刻計數器的CNT值。配合計時器可以透過 Capture 信號知道中斷時的CNT值,進而量測脈衝寬度,用於量測週期性波形的頻率、週期或Duty Cycle。
下文將以STM32L053R8Tx為示範,使用意法半導體官方所提供的Nucleo開發板完成該實驗,使用的IDE為Keil uVision5(MDK-ARM),而STM32CubeMX的版本為6.2.1。
在Lesson 5有介紹到 TIMER ,其中TIMER2、TIMER21、TIMER22皆可以用使用 Capture 功能,在此以TIMER2的Channel1使用Input Capture direct mode為例。
在 TIMER 中有不少的暫存器,其中CCER中有 Capture 的邊緣觸發設定,而CNT是用來記錄 TIMER 的計數值,透過下圖可以更了解我們需要紀錄的時間點。
如上圖所示,一共有三個時間點,分別是t0、t1、t2。當第一次正緣來時,則觸發 Capture 中斷,紀錄t0的Counter值,接著修改暫存器CCER的設定,使觸發條件為負緣觸發,待負緣來臨紀錄t1的Counter值,再次修改暫存器CCER的設定為正緣觸發,正緣來臨紀錄t2的Counter值,同時該t2的值也是下一個t0的值,則可以完整記錄t0、t1、t2的三個時間點,當t2-t0則是一個波的週期時間,倒數則為頻率;而(t1-t0)/(t2-t0)則是Duty Cycle。
開啟STM32CubeMX後,將 TIMER2 的Channel1設定為Input Capture direct mode輸出,可以看見PA0的Pin腳已經被設定為TIM2_CH1,而PSC不需要除頻,以最快的32Mhz計數,且Period設定為最大值65536,讓Capture的頻寬可以更大。
並且在NVIC的設定當中開啟TIMER2的中斷致能。
接著產生程式碼後,可以發現stml0xx_it.c下方多了TIM2_IRQHandler,如Lesson 5提到的TIMER內部中斷一樣。
可以發現進入中斷服務程式後,他呼叫的HAL_TIM_IRQHandler,並告知中斷源為TIM2,先編譯後對該副程式點選右鍵Go to Definition,進入該副程式的原始碼。
進入後可以看到針對不同的中斷進入方式有不同的處理及呼叫,可以觀察到有不少的觸發事件,如 Capture compare 1 event、 Capture compare 2 event、Output compare event等等。
往下拉可以看到TIM Input capture event,這正是TIMER2中斷進入後所處理的事件,並且可以看到清除中斷旗標後,呼叫了HAL_TIM_IC_CaptureCallback,代表我們需要在main.c建立這個副程式,以利中斷產生時會呼叫該副程式。
對著HAL_TIM_IC_CaptureCallback點選右鍵Go to Definition,了解該如何宣告此副程式到main.c。
回到main.c建立HAL_TIM_IC_CaptureCallback副程式,並判斷所傳入的中斷源是否為TIM2,因為傳入的參數是指標,因此我們判斷中斷源需要多一個”&”符號,判斷htim的位址是否與htim2的位址相同,並判斷channel是否為channel1,並宣告如下幾個變數。
t0、t1、t2用來記錄TIMER2的CNT值,Duty用來存放該輸入波型的Duty Cycle,Frequncy用來存放該輸入波型的頻率,CAP_Flag用來記錄目前的時間點是t0、t1還是t2,以及postime、negtime。
uint16_t t0,t1,t2,Duty,CAP_Flag=0;
uint16_t Frequency,postime,negtime;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim==&htim2 && htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)
{
}
}
接著我們來了解上方提到的CCER暫存器,可以參考資料手冊DM00095744。可以看到bit1的CC1P是用來選擇Capture的中斷edge為正緣還是負緣。若CC1P為0則為Rising edge;1則是Falling edge。
這邊可能會有疑問為什麼不在STM32CubeMX的設定介面直接選擇both edges,因為不論是正緣或是負緣皆會觸發,則我們無法判斷第一次進入是屬於正緣還是負緣,所計算的Duty Cycle就不準確。這部分也歡迎更多高手交流,或許可以從暫存器的旗標得知觸發狀態,就不需要改變CCER暫存器的內容。
了解了暫存器的內容後,回到程式碼。大致上的流程如下
- 判斷觸發源為TIMER2的Channel1
- 預設Rising edge觸發中斷,取得t0時的TIMER2 Counter值
- 改變Trigger Mode為Falling edge,並將CAP_Flag設為1
- Falling edge觸發中斷,取得t1時的TIMER2 Counter值
- 改變Trigger Mode為Rising edge
- 計算正占空比時間postime,並將CAP_Flag設為2
- Rising edge觸發中斷,取得t2時的TIMER2 Counter值
- 改變Trigger Mode為Falling edge
- 計算負占空比時間negtime
- 同時將t2賦值給t0,因t2同時是下一個波型的t0
- 計算該波型的Duty Cycle及Frequency,並將CAP_Flag設為1
※注意:若CNT值超過65535會觸發Timer update的中斷,可以判斷旗標,使t0、t1、t2為TIM2->CNT+更新次數*65535,同時變數宣告須為32位元。以下沒有做此部分,因此頻率過低會無法量測。
uint16_t t0,t1,t2,Duty,CAP_Flag=0;
uint16_t Frequency,postime,negtime;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim==&htim2 && htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)
{
if(CAP_Flag==0)
{
t0=TIM2->CNT; //first trigger,get counter value
TIM2->CCER|=TIM_CCER_CC1P; //change trigger mode to falling edge
CAP_Flag=1;
}
else if(CAP_Flag==1)
{
t1=TIM2->CNT; //get counter value
TIM2->CCER&=~TIM_CCER_CC1P; //change trigger mode to rising edge
if(t1>t0)postime=t1-t0; //t1~t0 is postive duty time
else postime=t1+(~t0)+1;
CAP_Flag=2;
}
else if(CAP_Flag==2)
{
t2=TIM2->CNT; //get counter value
TIM2->CCER|=TIM_CCER_CC1P; //change trigger mode to falling edge
if(t2>t1)negtime=t2-t1; //t2~t1 is negtive duty time
else negtime=t2+(~t1)+1;
t0=t2; //t2 value also is next t0 value
Duty=postime*100/(postime+negtime);
Frequency=(SystemCoreClock)/(postime+negtime);
CAP_Flag=1;
}
}
}
完成後須在main function啟動TIMER2的Capture中斷,因此在while前方新增HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
接著透過訊號產生器或GPIO送一個0~3.3V的方波,頻率為250Hz至PA0。
使用Debug Mode觀察變數可以很清楚的觀察到Duty為50,且Frequency為準確的250Hz。同時可以發現postime、negtime已經接近65535,因此250Hz已是接近量測邊緣的頻率,再更低頻的頻率量測就會錯誤。
以上就是使用TIMER Capture的教學,歡迎各位嘗試把Update的旗標判斷新增進去,則可以捕捉到更低頻率的波型。
Thanks for your blog, nice to read. Do not stop.