# [STM32] 定時器(Timer)、脈波寬度調變(PWM)、中斷(Interrupt)
## 定時器(Timer)
通常用於測量時間間隔,從零開始向上計數以測量經過的時間的計時器,它是一種從指定時間間隔開始倒計時並用於產生時間延遲的設備,例如秒錶
### 定時器用法
Interrupt:
TIMER的應用多,微處理機中常見的中斷(interrupt)讓CPU定時做一件事情,多久做一次的時間要告訴它,所以我們需要Timer。
Delay:
產生延遲會需要等待幾秒鐘,這時候就需要Timer來計算delay的時間
PWM:
產生一個週期性的信號,例如可供馬達控制

### STM32 定時器(Timer)計數週期 T 的計算公式

此公式用來計算 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%。


Timer使用PWM輸出模式時,CNT計數值大於CCRx的時候會輸出高電位,相反的低於CCRx時則是低電位。
ARR : 主要控制的是頻率,也就是綠框的部分
CCRx : 主要為控制佔空比,也就是藍框的部分
求Duty Cycle:

## 中斷(Interrupt)
在嵌入式系統中,中斷是一個機制,用於處理各種外部事件或內部條件的發生。這些事件可以是來自硬體設備的訊號,例如按鈕按下、計時器計時完成,或是軟體內部的條件,如:數據溢出或滿足一些特定條件。當一個中斷事件發生時,MCU會中斷正在執行的程式,轉而執行相應的中斷服務程式(Interrupt Service Routine,ISR),處理該事件,然後繼續執行原來的程式。
### 中斷的類型
MCU支援多種不同類型的中斷,以下是一些常見的中斷類型:
外部中斷: 由外部硬體觸發的中斷,例如按鈕按下、感測器觸發等。
定時器中斷: 由MCU內部定時器計時完成時觸發,用於執行定時任務。
UART中斷: 用於處理串列通信中的數據接收或傳送完成事件。
ADC中斷: 用於類比數位轉換(ADC)完成時,通常用於讀取模擬數據。
內部中斷: 由MCU內部條件觸發的中斷,例如除以零的錯誤。
看門狗定時器中斷: 用於檢測系統錯誤並執行相應的動作。
### 中斷優先級
當多個中斷事件同時發生時,MCU需要確定哪個中斷應該首先處理。這就涉及到中斷優先級的概念。每個中斷都有一個優先級,通常是數字越小,優先級越高。當多個中斷事件同時發生時,MCU會首先處理優先級最高的中斷,然後再處理其他中斷,以此類推。
# 實作
### Demo
{%youtube Aiw47c68cww %}
## STM32CubeIDE設定
### PIN 設定

### GPIO設定
PA5 EXIT5 中斷開啟

PA7 output


### RCC設定

### SYS設定

### TIM設定
開啟tim3 pwn設定頻率2kHz

開啟tim4 設定每毫秒跑一次

### NVIC 設定

### Clock設定

### 外設檔案分開+生成


## 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 */
```
:::