# [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 */ ``` :::
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.