第四節:定時器中斷及定時器產生PWM(用CubeMX學習STM32)


定時器中斷及定時器產生PWM


《用CubeMX學習STM32》

注釋 點擊上面藍字進入完整專欄,這個系列所有文章都會整合到這個專欄

4、STM32定時器中斷以及定時器PWM

前言: STM32定時器概述
  我演示用的STM32F407ZGt6的核心板有多達14個定時器;  其中包含兩個高級定時器(TIM1和TIM8); 十個通用定時器(TIM2~TIM5, TIM9~TIM14); 兩個基礎定時器(TIM6、TIM7)。

Tips: 在十個通用定時器里面包含兩個看門狗定時器(two watchdog timers)

下面是datasheet里面官方給出的介紹

在這里插入圖片描述

Couter resolution—> 計數器位數, 位數越高精度越高;
Counter type —> 計數類型 即向上計數還是向下計數
Prescaler factor —> 分頻因子, 對時鍾的分頻, 比如單片機的時鍾為84MHz, 預分頻系數為4的話, 那定時器的計數器所能用的的就是21MHz了


  本文以用的比較多的通用定時器介紹, 用TIM3;

(1)主要涉及到的寄存器為
  •    計數器寄存器 (TIMx_CNT)
  •    預分頻器寄存器 (TIMx_PSC)
  •    自動重載寄存器 (TIMx_ARR)
    在這里插入圖片描述
    太細致的寄存器相關的就不扯了, 看了也是忘, 多用自然會了然於心
(2)關於計數模式

以向上計數為例:

計數器從0計數到自動加載值(TIMx_ARR),然后重新從0開始計數並且產生一個計數器溢出事件。 我們可以在這個溢出事件進入中斷, 只要使能中斷, 那么溢出事件這個點就會進入中斷, 我們只要在中斷服務函數中寫自己要做的功能, 這就是定時器中斷了

溢出時間計算

Tout = ((ARR + 1)*(PSC + 1)) / Tclk
Tout: 溢出時間(us);  Tclk: 定時器的輸入時鍾頻率(MHz);  ARR和PSC的值都在1~65535這個范圍

在這里插入圖片描述
如圖中所示, PSC(預分頻系數)決定計數周期, ARR(自動重裝載值) 確定那個時刻發生溢出

下面開始Cube配置+IAR編程


4.1 操作簡介

   (1) 定時器中斷, 在中斷中做點燈操作。使用向上計數模式;  每 200ms LED0翻轉一次

   (2) 定時器產生PWM


4.2 定時器中斷

在主函數的while(1) 里面我們什么都不做, 只在中斷里面讓LED閃爍。

Step1 : Cube配置

  • (1) 新建工程
     在新建的工程里面這一次先配置時鍾樹, 因為我們需要先配置好時鍾頻率然后計算定時器參數。

    • tips: 配置時鍾樹之前別忘了先把RCC和SYS勾選好
      在這里插入圖片描述
    • 接着配置時鍾樹
      在這里插入圖片描述
  • (2) TIM3配參數置

    • 1、 計算200ms的溢出時間所需要配置的ARR和PSC的值的大小
        通過查閱datatsheet可以知道, TIM3的時鍾掛接在APB1總線上, 所以就使用APB1總線時鍾頻率(84MHz)進行分頻
      在這里插入圖片描述
      而我們用CubeMx配置的時鍾樹里面APB1是84MHz, 根據前面的公式:
      Tout = ((ARR + 1)*(PSC + 1)) / Tclk
      已知 Tclk 為 84MHz , 我們需要 Tout 為 200ms 即 200000us , 不妨先讓PSC為 839, 帶入上述公式可得 ARR = 19999. 這樣算出來之后ARR和PSC都在0~65535的范圍, 則可以使用這組參數
      *注 :*這個過程最好自己算一下; 還有就是ARR和PSC都是16位的寄存器, 數值一定要在0~65535這個范圍
下面是TIM3的具體配置

在這里插入圖片描述

因為我們用到TIM3的中斷, 所以不要忘記勾選中斷使能; 可以在TIM3的NVIC Setting里面使勾選也可以在System Core里面勾選
在這里插入圖片描述

關於 Priority Group(中斷優先級分組)

在這次的學習中隨便設置, 前面的博客也說了, 這個分組主要用於多個定時器中斷的時候, 為了各個定時器中斷不互相干擾, 而給每個定時器中斷排個號, 大家按順序來;
按圖中的配置解釋一下, 2位用於搶占優先級和2位用於子優先級就是搶占優先級可以為0~3, 子優先級也可以為0~3;
在二進制中, 00就是0, 11就是3, 所以兩位數可以表示0~3;

注 : 優先級大小 0 > 1 > 2 > 3

中斷事件 Preemption Priority Sub Priority
E1 2 0
E2 2 1
E3 1 0

  這個表格表示:
    1、 當 E1 和 E2 中斷發生時, 若 E3 發生中斷, 則可以打斷 E1 和 E2 的中斷, 因為 E3 的搶占優先級是三者最高的;
    2、 對於搶占優先級相同的E1和E2事件, 若E1 中斷正在執行, E2 中斷的到來不可以打斷 E1 , 等 E1 執行完再去執行 E2 的中斷; 反之亦然, 只要搶占優先級一樣, 那么誰先發生就先執行誰, 不用比較Sub Priority
    3、 E1 和 E2, 若同時到來, 這個時候才考慮Sub Priority, 先執行E1再執行E2

  • (3) 其他引腳的配置
      我們要在中斷里面翻轉LED, 所以配置一下LED燈即可
    在這里插入圖片描述

  • (4) 工程配置(與前面的類似)
    在這里插入圖片描述

  • (5) Generate Code
    在這里插入圖片描述


Step2 : IAR或Keil編程

  • (1) 先看一下tim.c 里面的初始化寫了些什么
    在這里插入圖片描述
    在這里插入圖片描述

  • (2) 寫入用戶代碼

    • 在主函數里面開啟定時器中斷
    /* USER CODE BEGIN 2 */
    HAL_TIM_Base_Start_IT(&htim3);
    
    /* USER CODE END 2 */
    

    注 : 要使用定時器中斷一定要自己在主函數里面用代碼打開, cubeMX配置的只是初始化參數, 是否打開取決於用戶

    • 寫中斷服務函數
    在 stm32f4xx_it.h 里面找到 void TIM3_IRQHandler(void); 然后Go To Definition查看定義
    /**
    * @brief This function handles TIM3 global interrupt.
    */
    void TIM3_IRQHandler(void)
    {
        /* USER CODE BEGIN TIM3_IRQn 0 */
    
        /* USER CODE END TIM3_IRQn 0 */
        HAL_TIM_IRQHandler(&htim3);
        /* USER CODE BEGIN TIM3_IRQn 1 */
    
        /* USER CODE END TIM3_IRQn 1 */
    }
    
    再查看 HAL_TIM_IRQHandler() 的定義

在這里插入圖片描述
  可以看到, 對於更新中斷事件, 最終是執行的 HAL_TIM_PeriodElapsedCallback() 這個回調函數, 所以我們只需重構這個回調函數, 在里面寫入自己的代碼
在這里插入圖片描述

  • (3) 重構中斷回調函數
      我們把對回調函數的重構寫在tim.c 里面, 便於查看也便於以后修改什么的
    在這里插入圖片描述
/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    // 如果是定時器3的中斷
    if (htim == (&htim3))
    {
        // 翻轉LED0的引腳; 下面兩句是等價的, main.h 有對應宏定義
        HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
        //HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
    }
}

/* USER CODE END 1 */
  • (4) 編譯下載
    實際效果如動圖所示
    在這里插入圖片描述

   驗證LED燈是每200ms翻轉一次, 需要用示波器查看LED0對應引腳的波形數據
在這里插入圖片描述
可以看到正頻寬是200ms, 負頻寬也是200ms, 所以跟我們預期的計算結果是一樣的, 完美。


4.3 定時器產生PWM

操作簡介 : 通過STM32定時器產生的PWM, 讓LED燈做出呼吸燈的效果;
參考正點原子開發指南, 用CubeMX完成其標准庫的程序配置
PWM(Pulse Width Modulation): 即脈沖寬度調制

在這里插入圖片描述
  這個是以向上計數比較的, 當定時器計數器向上計數到CCRx之前, IO口一直輸出0, 從到達CCRx開始輸出1, 到ARR時溢出,重新計數。
  IO口輸出高低電平就用0和1表示。
  顯然, 如果改變CCRx值的大小, 就可以變化IO輸出的PWM波形的占空比; 改變ARR(自動重裝載值), 就可以改變PWM波形的頻率。

在前面配置TIM3的時候我們沒有用到那幾個Channel, 現在產生PWM就需要用到了。 關掉IAR工程, 回到CubeMX開始配置

Step1 : Cube配置

  • (1) LED燈的引腳對應的是TIM14的通道1, 所以要配置TIM14來實現效果

在這里插入圖片描述

注釋 :

CH Polarity即通道輸出極性。我們用TIM3向上計數模式, 按照前面PWM原理圖解, CH Polarity的極性加上IO邏輯, 才是最終PWM的波形; 舉個栗子, 如果CH Polarity為High(1), 那么當向上計數到CCRx之前本該是0(低電平), 但實際上還要加上Ch的輸出極性, 所以那一段實際上是輸出1(高電平)。 細細品一下腦子就通了
在這里插入圖片描述

選用PWM模式1, Pulse(脈寬)這個不用設置, 因為我們在程序里面要不斷改變占空比去調節LED的亮度
  • (2) 生成代碼
先聲明兩個變量

在這里插入圖片描述

/* USER CODE BEGIN 1 */
uint16_t CCRx_val = 0;	// CCRx的值; 通過不斷改變這個值, 並把這個值送入CCRx寄存器, 達到改變占空比的效果
uint8_t dir = 1;		// 計數方向控制

/* USER CODE END 1 */
啟動定時器的PWM

在這里插入圖片描述

/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1);

/* USER CODE END 2 */
while(1)循環內部

在這里插入圖片描述

/* USER CODE BEGIN 3 */
    HAL_Delay(10);
    if(dir)
        CCRx_val++;
    else
        CCRx_val--;
    if(CCRx_val > 300)
        dir=0;
    if(CCRx_val == 0)
        dir=1;
    // 追蹤 HAL_TIM_OC_ConfigChannel   找到  TIM_OC1_SetConfig 再追蹤進去
    // TIM14->CCR1 = 任意數值x  即可控制TIM14通道1的脈寬, 占空比為 (x)/(htim14.Init.Period + 1)
    TIM14->CCR1 = CCRx_val;		// 修改占空比  (調節脈寬我們需要調節 sConfigOC.Pulse )
所有用戶代碼就是這些, 這里用是寄存器操作去修改占空比, 不管使用什么庫函數, 標准庫或者HAL庫亦或者LL庫, 他們在程序里的地位都要低於寄存器。 所以當使用CubeMX配置使用HAL庫不方便對某些IO操作的時候, 就可以用寄存器來操作
剛才在cube里面脈寬(Pulse)我們沒有配置, 就是在這里通過寄存器操作的, 最終的本質都是寄存器操作; 感興趣可以按照我的代碼注釋, 去 Go To Definition 一路追蹤到 TIM_OC1_SetConfig() 這個函數, 這里面就是對相應的寄存器操作的。 在 stm32f4xx_hal_tim.c 這個文件里面
  • (3) 編譯下載
    • 實際效果如下面動圖所示, 看個三到四秒鍾就可以看到變化了
      可能看起來不是非常的明顯, 自己操作一下可以好好觀察現象

在這里插入圖片描述


人生豈止愛恨, 還有孤獨和酒, 更有夢想與生活, 堅持住。 加油 !

Author : 李光輝
date : Sun Jan 5 22:18:02 CST 2020
blog ID: Kevin_8_Lee
blog site : https://blog.csdn.net/Kevin_8_Lee


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM