---
# System prepended metadata

title: '[STM32] 定時器、中斷'
tags: [stm32]

---

# [STM32] 定時器(Timer)、脈波寬度調變(PWM)、中斷(Interrupt)

## 定時器(Timer)
通常用於測量時間間隔，從零開始向上計數以測量經過的時間的計時器，它是一種從指定時間間隔開始倒計時並用於產生時間延遲的設備，例如秒錶

### 定時器用法
Interrupt：
TIMER的應用多，微處理機中常見的中斷(interrupt)讓CPU定時做一件事情，多久做一次的時間要告訴它，所以我們需要Timer。
Delay： 
產生延遲會需要等待幾秒鐘，這時候就需要Timer來計算delay的時間
PWM：
產生一個週期性的信號，例如可供馬達控制
![tim](https://hackmd.io/_uploads/SkG63TY5yx.png)

 ### STM32 定時器（Timer）計數週期 T 的計算公式
![time](https://hackmd.io/_uploads/rkOa36Kqyx.png)
此公式用來計算 STM32 定時器的定時週期，也就是計時器從 0 計數到 ARR（自動重裝載值）後發生一次溢出的時間。

### Prescaler (PSC)
預分頻器值
### Period (ARR)
自動重裝載值
### PCLK 時鐘頻率
計時器的輸入時鐘頻率
### T
定時器週期( 0 到 ARR 需要的時間)

### 舉例:
設定 1 秒計時
假設：

PCLK1（APB1 時鐘）= 72MHz
PSC = 7199 （分頻 7200）
ARR = 9999 （計數範圍 0 ~ 9999）

$$
T = \frac{(7199 + 1) \times (9999 + 1)}{72,000,000}
$$

$$
= \frac{7200 \times 10000}{72,000,000}
$$

$$
= 1 \text{ 秒}
$$

定時器每 1 秒 產生一次溢出，觸發中斷。

##  脈波寬度調變Pulse width modulation（PWM）
PWM是將類比信號轉換為脈波，一般轉換後脈波的週期固定，但脈波的占空比會依類比信號的大小而改變。

在使用 PWM 時我們會需要控制兩種參數：頻率與 Duty Cycle（佔空比）。頻率的部分和 Timer 一樣，由 PSC 與 ARR 的值來設定，而 Duty Cycle 則由 CCR （Capture/Compare Register，捕獲/比較暫存器）來指定。
### 佔空比(Duty Cycle)
佔空比是指輸出的PWM當中，高電位保持的時間與整個週期之比。可以看到下圖當中最上方為50%的佔空比，也就是高電位與低電位各佔50%。
![image](https://hackmd.io/_uploads/ryhwAP25yl.png)

![image](https://hackmd.io/_uploads/SkK3Cv25Jx.png)
Timer使用PWM輸出模式時，CNT計數值大於CCRx的時候會輸出高電位，相反的低於CCRx時則是低電位。
ARR : 主要控制的是頻率，也就是綠框的部分
CCRx : 主要為控制佔空比，也就是藍框的部分

求Duty Cycle:
![image](https://hackmd.io/_uploads/rJINQ_2ckx.png)





## 中斷(Interrupt)
在嵌入式系統中，中斷是一個機制，用於處理各種外部事件或內部條件的發生。這些事件可以是來自硬體設備的訊號，例如按鈕按下、計時器計時完成，或是軟體內部的條件，如：數據溢出或滿足一些特定條件。當一個中斷事件發生時，MCU會中斷正在執行的程式，轉而執行相應的中斷服務程式（Interrupt Service Routine，ISR），處理該事件，然後繼續執行原來的程式。

### 中斷的類型
MCU支援多種不同類型的中斷，以下是一些常見的中斷類型：

外部中斷： 由外部硬體觸發的中斷，例如按鈕按下、感測器觸發等。

定時器中斷： 由MCU內部定時器計時完成時觸發，用於執行定時任務。

UART中斷： 用於處理串列通信中的數據接收或傳送完成事件。

ADC中斷： 用於類比數位轉換（ADC）完成時，通常用於讀取模擬數據。

內部中斷： 由MCU內部條件觸發的中斷，例如除以零的錯誤。

看門狗定時器中斷： 用於檢測系統錯誤並執行相應的動作。

### 中斷優先級
當多個中斷事件同時發生時，MCU需要確定哪個中斷應該首先處理。這就涉及到中斷優先級的概念。每個中斷都有一個優先級，通常是數字越小，優先級越高。當多個中斷事件同時發生時，MCU會首先處理優先級最高的中斷，然後再處理其他中斷，以此類推。

# 實作
### Demo
{%youtube Aiw47c68cww %}
## STM32CubeIDE設定
### PIN 設定
![image](https://hackmd.io/_uploads/SyOag_n9ke.png)
### GPIO設定 
PA5 EXIT5 中斷開啟
![image](https://hackmd.io/_uploads/SkSEpPhc1x.png)
PA7 output
![image](https://hackmd.io/_uploads/BJsw6D2q1g.png)
![pinout](https://hackmd.io/_uploads/Hy2OPpLjJl.png)

### RCC設定
![image](https://hackmd.io/_uploads/BJk0pw3qkl.png)
### SYS設定
![image](https://hackmd.io/_uploads/H1U-Rv2q1x.png)
### TIM設定
開啟tim3 pwn設定頻率2kHz
![image](https://hackmd.io/_uploads/HksZ6Ih91x.png)
開啟tim4 設定每毫秒跑一次
![image](https://hackmd.io/_uploads/ryPUaL291l.png)
### NVIC 設定
![image](https://hackmd.io/_uploads/H1JNCwn9yx.png)
### Clock設定
![image](https://hackmd.io/_uploads/S1_W1uhqkg.png)
### 外設檔案分開+生成
![image](https://hackmd.io/_uploads/HJMkxu29yl.png)
![image](https://hackmd.io/_uploads/ryhExd2qJe.png)




## PWM

### 流程圖
```mermaid
flowchart TD
    A(開始)
    B[初始化計時器]
    C[進入主迴圈]
    
    subgraph LED 亮度控制
        D[pwm_led 函數]
        D1{flag_led == 1?}
        D2[調整 duty_num]
        D3[更新 PWM]
        D4[flag_led = 0]
    end
    
    subgraph PWM 啟動/停止
        E[pwm_state 函數]
        E1{flag_brk == 0?}
        E2[啟動 PWM]
        E3[停止 PWM]
    end

    subgraph 計時中斷 TIM4
        F[TIM4 IRQHandler]
        F1[每 5 毫秒: 更新 LED 狀態]
        F2[每 5 秒: 切換 PWM 啟動/停止]
    end

    subgraph GPIO 中斷
        G[HAL_GPIO_EXTI_Callback]
        G1{PA5_Pin 低電位?}
        G2[切換 PA7_Pin 狀態]
    end

    A --> B --> C
    C --> D
    D --> D1
    D1 -- 是 --> D2 --> D3 --> D4
    D1 -- 否 --> E
    C --> E
    E --> E1
    E1 -- 是 --> E2
    E1 -- 否 --> E3

    F --> F1
    F --> F2

    G --> G1
    G1 -- 是 --> G2

```
### 程式碼
#### main.c
:::spoiler main.c
```c=
 /* USER CODE BEGIN PV */
 int duty_num = 0;         // 用於記錄 PWM 佔空比的變數
 int flag = 0;             // 用於控制 PWM 佔空比增減的旗標
 extern int flag_brk;      // 外部變數，控制 PWM 開關
 extern int flag_led;      // 外部變數，控制 LED 呼吸效果開關
 /* USER CODE END PV */
/***************************************************************************/
 /* USER CODE BEGIN 2 */
  // 啟動 TIM3 和 TIM4 的基本計時器中斷
  HAL_TIM_Base_Start_IT(&htim3);
  HAL_TIM_Base_Start_IT(&htim4);

  // 啟動 TIM3 的 PWM 輸出
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);

  // 定義 PWM 變更的函數，實現 LED 呼吸效果
  void pwm_led(void)
  {
    if(flag_led == 1) {
      // 根據 flag 調整 PWM 佔空比的增減
      if(flag == 0)
        duty_num = duty_num + 5;  // 增加佔空比
      if(flag == 1)
        duty_num = duty_num - 5;  // 減少佔空比
      
      // 當佔空比超過最大值時，改變增減方向
      if(duty_num > 500)
        flag = 1;
      // 當佔空比低於最小值時，改變增減方向
      if(duty_num < 5)
        flag = 0;

      // 更新 PWM 佔空比
      __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, duty_num);

      // 重置 flag_led，等待下一次更新
      flag_led = 0;
    }
  }

  // 定義控制 PWM 開關的函數
  void pwm_state(){
    // 當 flag_brk 為 0 時啟動 PWM，否則停止 PWM
    if(flag_brk == 0) {
      HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
    } else {
      HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1);
    }
  }

  /* USER CODE END 2 */
/***************************************************************************/
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    pwm_led();  // 呼叫 LED PWM 更新函數
    pwm_state();  // 呼叫 PWM 開關函數

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */
```
:::
#### stm32f4xx_it.c
:::spoiler stm32f4xx_it.c
```c=43
/* USER CODE BEGIN PV */
int tim1 = 0;        // 用於計算定時器中斷的計數器
int flag_led = 0;    // 用於控制 LED 呼吸效果的旗標
int flag_brk = 0;    // 用於控制 PWM 開關的旗標
/* USER CODE END PV */
/***************************************************************************/
void TIM4_IRQHandler(void)
{
  /* USER CODE BEGIN TIM4_IRQn 0 */

  /* USER CODE END TIM4_IRQn 0 */
  HAL_TIM_IRQHandler(&htim4);  // 呼叫 TIM4 的中斷處理函數
  /* USER CODE BEGIN TIM4_IRQn 1 */
  
  tim1 += 1;  // 計數器加 1

  // 每 5 次中斷更新一次 flag_led，觸發 LED 呼吸效果(5毫秒)
  if(tim1 % 5 == 0) {
    flag_led = 1;
  }

  // 每 5000 次中斷切換 PWM 開關(5秒)
  if(tim1 % 5000 == 0) {
    flag_brk = !flag_brk;
  }

  // 當 tim1 計數達到 10000 時，重置為 0
  if(tim1 == 10000) {
    tim1 = 0;
  }

  /* USER CODE END TIM4_IRQn 1 */
}
/***************************************************************************/
/* USER CODE BEGIN 1 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if (GPIO_Pin == PA5_Pin)
	{
		// 判斷 PD1 引腳是否為低電位
		if(HAL_GPIO_ReadPin(PA5_GPIO_Port, PA5_Pin) == GPIO_PIN_RESET)
		{
			HAL_GPIO_TogglePin(GPIOA, PA7_Pin);
		}
	}
}
/* USER CODE END 1 */
```
:::