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