---
# System prepended metadata

title: STM的基礎timer與timer中斷
tags: [STM32]

---

[TOC]

<style>
	.text-center{text-align: center; //文字置中}
	
</style>

# STM timer 簡介
- timer最基礎的工作就是計時，計時在結合一些技術可以實現很多功能，有的timer自帶一些不同的功能
- 見野火 31、 32 章

## 類型
- 主要分為高級(Advanced controll)、通用(General-purpose)、基礎(basic)三種timer
- 簡言之，基礎timer只能計時，通用有其他功能，高級有通用的所有功能外加其他互補輸出、重複計數功能。
    ![image](https://hackmd.io/_uploads/SyPQwzLhA.png)


## 基礎計時原理與相關暫存器
- 此內容對應野火31章
    ![image](https://hackmd.io/_uploads/ryVO-c82A.png)

    - 1是時鐘源，基礎timer只吃內部時鐘源，如同時鐘樹的設置
    - 2是timer的控制暫存器，內部各bit決定timer的開啟、復位機制、計數機制
    - 3是主要用於計時功能會用到的暫存器
    - 方塊如果有陰影表示其有對應的"影子暫存器"，影子暫存器是只給程式內部做使用的，也就是有兩個同樣意義的暫存器，只是一個供讀寫，一個只供內部用。而控制器可以決定何時要把估寫的暫存器值更新到影子暫存器
- 針對計時功能會用到的暫存器做簡單的說明
    - PSC做為預除頻器，會把CK_PSC這個時鐘輸入轉為CK_CNT這個時鐘輸出，關係應該為
        <p class="text-center">
            CK_CNT = CK_PSC / 預除頻值
        </p>
        如果不除頻的話，預除頻值應該為1，但因為暫存器值都是從 0~XXXX，所以在賦予暫存器值時要想成
        <p class="text-center">
            預除頻值 = PSC 暫存器值 + 1
        </p> 
    - 此預除頻方法是使用另一個預除頻計數器做實現的不像時鐘樹的預除頻器有2、4、8、...的限制，可見下面時序圖原理
    - CNT(counter)依據CK_CNT這個時鐘以及timer控制暫存器指示決定上數或下數
    - 自動重載暫存器(ARR)內可以存放一個值，當CNT的數值與ARR數值相同，就生成事件並且清除counter
        ![image](https://hackmd.io/_uploads/HJhTL983R.png)


# 計時中斷
- 知道計時原理後，以下試著實現計時中斷功能
- 因為其他timer有更多額外功能，因此盡量把單純的計時功能留給基本計時器
- 計時中斷的產生源自於timer的狀態暫存器(SR)的中斷旗標(UIF)被立起，這個旗標在接收到更新(update)事件時會立起，依據TIMx_CR1(控制暫存器)的 Bit 2 有不同update判別:
    ![image](https://hackmd.io/_uploads/ByR27iU2C.png)
但可以明確知道 <font color="red">counter的溢出</font>絕對是update，rm0090有說明溢出 : 
    ![image](https://hackmd.io/_uploads/B1iQbCI20.png)


## CubeMX 配置
- 點左側的TIM6，Mode那邊點擊Activated，one-pulse mode指counter數到ARR值時會停下來
    ![image](https://hackmd.io/_uploads/rkzYK9LhR.png)
- Prescaler : 設定的值 + 1 = 預除頻值
- Counter Mode :  基本定時器只能上數
- Counter Period : 即ARR值
- auto-reload preload : ENABLE表示事件更新時才把讀寫值更新至影子暫存器，DISABLE則表示，一讀寫就更新
- TRGO : 依據事件類型做外部觸發，可對ADC等其他功能做觸發
    - RESET : counter被手動歸0時就觸發
    - Enable : timer被啟用時就觸發
    - Update : 有更新事件時觸發
- 生成timer 頻率
    <p class="text-center">
        Freq. = freq. of clk source / (prescaler+1) / (Counter Period +1)
    </p>
    
- 要使用timer中斷，同樣要記得在NVIC啟用中斷喔
    ![image](https://hackmd.io/_uploads/ByaNrs8nA.png)

## 程式編寫
- CubeMX生成的檔案中，有一個以下宣告
    ``` c++=
    TIM_HandleTypeDef htim6;
    ```
    在很多timer相關的HAL庫函式都會使用到這個htim6，這個structure的內部組成也表達timer的各個暫存器
- 需要知道更多TIM相關函式用法可查看stm32f4xx_hal_tim.c檔案
- timer的啟用要針對timer控制器設定，這部分使用者可以決定何時開啟，所以CubeMX沒有一開始就啟用。在USER CODE BEGIN 2 區段添加這行
    ``` c++=
    HAL_TIM_Base_Start_IT(&htim6);
    ```
- 接著需要自行寫回調函式，同樣的這就是產生中斷時要處理的事情。這個Callback用之前的方法也可在stm32f4xx_hal_tim.c中的HAL_TIM_IRQHandler函式中被呼叫
    ``` c++=
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
	{
	  // Check which version of the timer triggered this callback and toggle LED
	  // if (htim == &htim6)
	  if (htim->Instance == htim6.Instance)
	  {
	    // 中斷處理
	  }
    }
    ```

# 練習1
- 使用timer 6產生一個2HZ的計時器，並且產生timer中斷，在callback function中翻轉LED亮暗
	- 確認時鐘樹配置，timer 6的時鐘源頻率是多少?
	- 設定Prescaler、Period、ARR使得timer頻率如預期
	- auto-reload preload 設為Disable
	- TGRO用不到不用設
# 練習2
- 用同樣頻率的timer中斷實作紅綠燈
    - 紅燈3秒->綠燈5秒->黃燈0.5秒->紅燈3秒->綠燈....不斷循環
- 記住中斷內程式應該簡潔，試著結合主迴圈while和計時中斷功能寫程式
- 可以使用狀態機概念，while中使用狀態做輸出，利用計時中斷切換狀態
