[STM32] 10-Timer-Input_Capture

Morgan Ting
閱益如美
Published in
19 min readApr 24, 2022
Photo by Markus Winkler on Unsplash

Input Capture 輸入捕捉是 Timer 的應用之一,主要用來測試週期性待測訊號的頻率與工作週期。本文章介紹 STM32 的 Input Capture 功能並以 PWM 訊號作為待測訊號輸入實作輸入捕捉功能。使用環境為 STM32CubeIDE 搭配 Hardware Abstraction Layer, HAL 函式庫進行實作。

文章內容

  1. Input Capture 輸入捕捉原理
  2. STM32 Input Capture
  3. HAL 函式庫
  4. 實作
  5. 成果展示
  6. 總結

工具與材料

  1. STM32CubeIDE
  2. Blue Pill ( STM32F103C8T6 ) 開發板
  3. ST-LINK v2
  4. 10 KΩ 電阻

Input Capture 輸入捕捉原理

Timer Input Capture 輸入捕捉是用來測試待測訊號的頻率與工作週期,一般情況下待測訊號具有週期性因此我們只要知道訊號的上升緣 ( Rising edge ) 與下降緣 ( Falling edge ) 之間的間隔就可以推算出該訊號的週期與脈波寬度,計時器在這裡的用處便是當偵測到輸入訊號的上升緣或下降緣時提供一筆時間戳記,該時間戳記的數值大小與計時器計數週期有關,在紀錄時間戳記後便可以知道時間差也就推算出待測訊號的脈波寬度與週期了。

STM32 Input Capture

STM32 提供了數組計時器 Timer 可供利用,以通用計時器而言其計時器的頻率決定了計數器的時間差,原因是計時器的時脈訊號每踢一下就會讓計數器內容加一。

STM32 每一個計時器有 4 個通道,每一個通道可以作為輸入與輸出。

下圖是 Timer 的系統方塊圖。

STM32 Timer

TIMx_CH1 ~ TIMx_CH4 分別代表 4 個通道,當訊號從通道進入後經過邊緣偵測電路,我們可以依需求設定邊緣偵測的觸發條件,例如只有偵測到訊號上升邊緣時或下降邊緣時才放行。當訊號經過邊緣偵測後來到 prescaler 前,這個預除頻 prescaler 可以設定是否進行除頻以降低待測訊號頻率,接下來就會進入捕捉單元進行時間戳的紀錄。

下圖顯示了更多細節。

Timer Capture Channel

圖中的 TI1 是訊號進入點,經過了 Filter downcounter 這是用來濾除雜訊的,我們可以設定 Filter downcounter 經過幾個時脈週期後才放行訊號通過,阻擋訊號剛進入時可能產生的雜訊。Edge detector 便是用來偵測訊號邊緣的電路,我們可以設定訊號處於上升緣或下降緣才放行訊號進入,此時訊號已經進入在經過除頻後便會來到輸入捕捉單元,觸發捕捉後當下的計數器內容值會複製到 TIMx_CCRy 暫存器中,這個就是時間戳記,如此便完成一次邊緣偵測捕捉。

signal sampling

上圖顯示計時器進行訊號捕捉時各階段計數值狀況。對於計數器我們所關心的有兩點:

  • 計數器頻率,我們可以透過設定計數器時脈頻率來決定一個 clock 間格時間。例如,計數器頻率為 1 MHz 那麼就是每隔 1 us 就會讓計數器加一。
  • 計數值上限,由暫存器 TIMx_ARR 可以設定計數值上限,當計數到上限時便會歸零重新計數並且觸發計數溢位事件。

由圖示可以了解到待測訊號的高電位維持了一段時間,在這一段時間中計數器已經產生了 2 次上數溢位中斷,假設計數上限為 65535 那麼在計算時間時也應該將 65535 * 2 納入計算。進行輸入捕捉時可以藉由以下步驟來操作:

  1. 設定計時器的頻率以及計數上限,並且開啟計數溢位中斷 ( 通常用上數的方式 )。
  2. 設定上升緣 ( Rising edge ) 觸發,啟動計時器。
  3. 發生邊緣觸發中斷時表示偵測到訊號的第一個上升緣,這裡對於計數值有 2 種處理方式,其一為讀取 TIMx_CCRy 暫存器內容記錄當下時間戳,以及計數器上數溢位次數。之後再用減法的方式來計算間隔時間。其二,既然這是待測訊號的第一個上升緣那麼就把當下的計數值歸零,以及計數器上數溢位次數也歸零,就像用直尺量距離一樣從零開始比較直觀。選用第二種方式對照圖示的時間戳記 TIMx_CCRy_1 就是 0 。注意 TIMx_CCRy_1 ~ TIMx_CCRy_3 是計算用的變數,與計時器系統無關。
  4. 捕捉到上升緣後立即設定邊緣觸發條件為下降緣 ( Falling edge ) 觸發。
  5. 發生邊緣觸發中斷表示捕捉到下降緣,這時讀取 TIMx_CCRy 暫存器內容會得到當前時間戳記存入變數 TIMx_CCRy_2,並且利用變數紀錄當前計數器上數溢位次數。由於是訊號的下降緣因此可以知道訊號的高電位維持時間,計算方式
  6. TIMx_CCRy_2 + ( 上數溢位次數 * 65536 )。完成後,更改邊緣觸發條件為上升緣觸發。
  7. 發生邊緣觸發中斷表示捕捉到第二個上升緣,若待測訊號具有週期性那麼此時已經捕捉到一個完整週期訊號了,讀取 TIMx_CCRy 暫存器內容存入變數 TIMx_CCRy_3 並且停止計時器,這時上數溢位次數就不會再增加而
待測訊號的週期就是TIMx_CCRy_3 + ( 上數溢位次數 * 65536 )。工作週期就是 TIMx_CCRy_2 / TIMx_CCRy_3 *100 %。

HAL 函式庫

Hardware Abstraction Layer, HAL 函式庫提供了相關函式:

  • HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2)
  • 啟動帶有中斷的計時器,IC 表示 Input Capture 功能,參數 &htim2 表示 Timer 2,TIM_CHANNEL_2 表示通道 2。
  • HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2)
  • 關閉輸入捕捉計時器。
  • TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2)
  • 清除位於 Timer 2 的 通道 2 邊緣觸發條件。
  • __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_RISING)
  • 將 Timer 2 的通道 2 邊緣觸發條件設為上升緣觸發。
  • __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_FALLING)
  • 將 Timer 2 的通道 2 邊緣觸發條件設為下降緣觸發。
  • HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2)
  • 讀取 Timer 2 的通道 2 因邊緣觸發所產生的計數值,即讀取 TIM2_CCR2。

實作

本次實作將使用 2 個 Timer ,分別是 Timer 3 用來產生可控的 PWM 訊號作為待測訊號,Timer 2 作為 Input Capture 輸入捕捉,其設定如下。

Timer 3 :

  • Channel 1 作為輸出腳位,位於 Blue Pill 開發版的 PA6。
  • Prescaler 設為 72 ,計時器時脈輸入頻率為 72 MHz ,因此 Timer 3 計時器頻率為 1 MHz。
  • Counter Period 也就是 ARR 暫存器內容,設為 500 ,計數器每隔 500 就歸零,因此 PWM 訊號頻率為 1 MHz / 500 = 2 KHz。
  • Counter Mode 預設為上數模式。
  • Pulse 設為 200,則工作週期為 200 / 500 = 40 %。

Timer 2 :

  • Channel 2 作為捕捉輸入腳位,位於 Blue Pill 開發版的 PA1。
  • Prescaler 設為 72 ,計時器時脈輸入頻率為 72 MHz ,因此 Timer 2 計時器頻率為 1 MHz。
  • Counter Period 也就是 ARR 暫存器內容,設為 65535,計數器每隔 65535 就歸零。
  • Counter Mode 預設為上數模式。

一、開啟 STM32CubeIDE 設定專案目錄後來到歡迎頁面,點選 Start New STM32 project 新增一個專案。

start new project

二、在 Target Selection 頁面中於搜尋列輸入 「F103C8 」便會出現 STM32F103C8T6 型號,選取該晶片型號後按下 Next。

select MCU

三、輸入專案名稱後按下 Finish 。

project name

四、在 Categories 項目中找到 Timers ,展開後點選 TIM 3 。右邊設定項目中勾選 Internal Clock ,Channel 1 選擇 PWM Generation CH1 表示將使用 Timer 3 的通道 1 作為 PWM 輸出。

Timer 3 參數設定如下:

  • Prescaler 輸入 72–1 。
  • Counter period 輸入 500–1。
  • PWM Generation Channel 1 項目底下的 Pulse 輸入 200 。
PWM parameter setting

五、在 Categories 項目中找到 Timers ,展開後點選 TIM 2 。右邊設定項目中 Clock Source 選擇 Internal Clock ,Channel 2 選擇 Input Capture direct Mode 表示將使用 Timer 2 的通道 2 作為輸入捕捉。

Timer 2 參數設定如下:

  • Prescaler 輸入 72–1 。
  • Counter period 預設 65535。
capture parameter setting

六、Categories 中的 SYS 項目,debug 選擇 Serial Wire。

SYS configuration

七、 Categories 中的 RCC 項目,High Speed Clock ( HSE ) 選擇 Crystal / Ceramic Resonator。

RCC configuration

八、Categories 中的 NVIC 項目,將 TIM2 global interrupt 的 Enable 選項打勾,啟動 Timer 2 的中斷。

Interrupt enabled

九、Clock Configuration 設定 TIMER 時脈來源,首先在 PLL Source Mux 選擇 HSE,PLL Mul 選擇 9 ,System Clock Mux 選擇 PLLCLK ,最後在 APB1 Prescaler 選擇 /2 。Timer 的時脈來源便設定完成。

clock configuration

十、Project Manager 的部分確認一下專案目錄與名稱以及 Code Generator 的部分是否需要更改。

project manager
project manager

十一、沒什麼問題的話上方命令列 Project ==> Generate Code ,產生初始程式碼。

Generate Code

十二、 STM32CubeIDE 已經自動產生相關初始化設定程式碼,接下來我們依照需求來進行程式撰寫。因為需要用到一些變數來記錄程式運行狀態與儲存數值,因此宣告一些變數。

在 / * USER CODE BEGIN PV * / 區段中宣告以下變數

  • volatile uint32_t overflow_cnt = 0; // 用來紀錄上數溢位次數
  • volatile uint32_t time_stamp[2] = {0}; // 用來記錄時間戳記
  • volatile uint8_t capture_finished = 0; // 作為旗標,表示輸入捕捉程序已經完成
  • volatile uint8_t state = 0; // 輸入捕捉程序進行狀態
  • volatile uint32_t period = 0; // 儲存待測訊號週期
  • float duty_cycle = 0; // 儲存待測訊號工作週期
  • uint32_t positive_width = 0; // 儲存待測訊號高電位維持時間
variables

十三、程式的部分,其思路為:

  1. 處於狀態 0 時為停止狀態,設定邊緣觸發條件為上升緣觸發,並開啟 Timer 2 與 Timer 3 啟動計時器,進入狀態1。
  2. 狀態 1 會在觸發輸入捕捉時執行,該狀態表示待測訊號的第一個上升緣被補捉到了,此時將上數溢位次數與計數器歸零,並且更改邊緣觸發條件為下降緣觸發。進入狀態 2。
  3. 狀態 2 會在觸發輸入捕捉時執行,該狀態表示待測訊號的下降緣被補捉,讀取 TIM2_CCR2 暫存器內容並存放到變數 time_stamp[0] 當中,此時可以計算上一次邊緣觸發到目前所經過的時間,time_stamp[0] + overflow_cnt * 65536 就是待測訊號的高電位維持時間。接著再把邊緣觸發條件更改為上升緣觸發,進入狀態 3。
  4. 狀態 3 會在觸發輸入捕捉時執行,該狀態表示待測訊號的第二個上升緣被補捉到, 讀取 TIM2_CCR2 暫存器內容並存放到變數 time_stamp[1] 當中, time_stamp[1] + overflow_cnt * 65536 就是待測訊號的週期時間,到此已經完成訊號捕捉程序,將作為旗標的變數 capture_finished 設為 1。
  5. 程式循環會一直檢測狀態以及 capture_finished 變數狀態,檢測到 capture_finished 為 1 表示完成一次捕捉程序,此時將負責輸入捕捉的 Timer 2 關閉,並將相關變數歸零以進行下一次捕捉程序。
while (1){if(state == 0){TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2); // 清除捕捉極性__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_RISING); // 設定上升緣觸發捕捉HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); // 啟動帶有中斷的捕捉功能HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);  // 啟動 PWMstate ++;}if(capture_finished){HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2); // 關閉捕捉功能state = 0;capture_finished = 0;duty_cycle = (float)positive_width / (float)period; // 計算工作週期HAL_Delay(3000);period = 0;positive_width = 0;duty_cycle = 0;}

中斷程序

/* USER CODE BEGIN 4 */void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){if(htim -> Instance == TIM2) overflow_cnt ++; // 紀錄計數器溢位次數}void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){if(htim -> Instance == TIM2)  // 觸發捕捉中斷{switch(state){case 1:overflow_cnt = 0;__HAL_TIM_SET_COUNTER(&htim2, 0);  // 清除計數器TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2); // 清除捕捉極性__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_FALLING); // 設定下降緣觸發捕捉state ++;break;case 2:
// 記錄 PWM 脈波寬度
time_stamp[0] = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2);positive_width = time_stamp[0] + overflow_cnt * 65536;TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2);__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_RISING); // 設定上升緣觸發捕捉state ++;break;case 3:
// 紀錄 PWM 脈波週期
time_stamp[1] = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2);period = time_stamp[1] + overflow_cnt * 65536;capture_finished = 1;break;}}}/* USER CODE END 4 */

十四、完成程式碼的撰寫後,上方命令列 Project ==> Build All 或是榔頭圖示,建立相關檔案。

build code

十五、相關檔案建立後,按下 RUN 綠色播放鍵圖示。

Run code

十六、出現 Debug 設定,Main 項目一般不太需要更改設定。

點選 Debugger ,將 Interface 內的 ST_LINK 前方框框打勾並按下右方的 Scan 按鈕,如果此時ST-LINK v2 燒錄器有接在電腦上就會出現燒錄器 ID 號碼。

將 Serial wire viewer 打勾,完成後分別按下 Apply 與 Debug 後進行編譯與燒錄。

十七、由於沒有連接任何顯示裝置,我們可以透過 Live Expression 來觀察變數。進入 Debug 模式後再視窗畫面右上方找到並點選 Live Expression ,按 + 號圖示增加變數,我們可以添加以下變數來觀察待測訊號的訊息,positive_width 、period、duty_cycle。

Live Expression

完成變數設定後按 Resume 按鈕,如果操作沒有錯誤就會看到結果顯示在 Live Expression 視窗。

result

電路圖

成果展示

本次實作中成功使用 Timer 分別產生 PWM 訊號與輸入捕捉功能,以 Timer 3 產生預期的 PWM 訊號,其頻率為 2 KHz ,工作週期Duty_cycle 為 40 %,輸入 Timer 2 進行輸入捕捉測試成功地計算出該 PWM 訊號之週期與工作週期分別為 497 us 與 39.6 %,與 PWM 訊號設定有些許誤差。

duty cycle
period
frequency

總結

本次實作以搭載 STM32F103C8T6 的 Blue Pill 開發板進行 Input Capture 實驗,分別利用 Timer 3 產生可控的 PWM 訊號作為待測訊號,Timer 2 作為 Input Capture 輸入捕捉,總結如下:

  1. 每一組 Timer 有 4 個通道可以實現輸入捕捉功能。
  2. 訊號輸入可以設定去雜訊,可以設定經過數個週期後再放行訊號進入。
  3. 輸入訊號可依需求進行除頻處理。
  4. 邊緣觸發條件可選擇設定上升緣觸發或是下降緣觸發。
  5. 發生邊緣觸發事件 / 中斷時,會將計數值存入 TIMx_CCRy 暫存器內。
  6. 計算輸入訊號周期或脈波寬度應考慮計時器溢位中斷。

參考資料

  1. STM32F103 手冊 [ 連結 ]
  2. STM32F1 HAL and Low-layer drivers [ 連結 ]

感謝讀者

若文章有幫助到您可以拍手給我鼓勵,或是加入 Like Coin 帳戶並點擊下方拍手按鈕,免費支持我。

相關文章

  • [STM32] 00-Install STM32CubeIDE [連結]
  • [STM32] 01-ST-LINK [連結]
  • [STM32] 02-STM32F103C8T6 [連結]
  • [STM32] 03-GPIO-Output [連結]
  • [STM32] 04-GPIO-Input [連結]
  • [STM32] 05-Ext-Interrupt [連結]
  • [STM32] 06-Timer-Basic [連結]
  • [STM32] 07-Timer-Interrupt [連結]
  • [STM32] 08-Timer-Output_Compare [連結]
  • [STM32] 09-Timer-PWM [連結]
  • [STM32] 10-Timer-Input_Capture [連結]
  • [STM32] 11-RTC-Second-Interrupt [連結]
  • [STM32] 12-RTC-Alarm_Interrupt [連結]
  • [STM32] 13-Independent_Watch_Dog [連結]
  • [STM32] 14-Windows_Watch_Dog [連結]
  • [STM32] 15-ADC_Conversion [連結]
  • [STM32] 16-ADC_Conversion_Temperature_Sensor [連結]
  • [STM32] 17-ADC_Convversion_DMA [連結]
  • [STM32] 18-SPI [連結]
  • [STM32] 19-UART [連結]
  • [STM32] 20-I2C [連結]

--

--

Morgan Ting
閱益如美

用好奇心探索世界。喜愛學習樂於分享。