1)實驗平臺:正點原子stm32mini 開發板
2)摘自《正點原子STM32 不完全手冊(HAL 庫版)》關注官方微信號公眾號,獲取更多資料:正點原子
第十二章 定時器中斷實驗
這一章,我們將向大家介紹如何使用 STM32 的通用定時器,STM32 的定時器功能十分強
大,有 TIME1 和 TIME8 等高級定時器,也有 TIME2~TIME5 等通用定時器,還有 TIME6 和
TIME7 等基本定時器。在《STM32 參考手冊》里面,定時器的介紹占了 1/5 的篇幅,足見其重
要性。在本章中,我們將使用 TIM3 的定時器中斷來控制 DS1 的翻轉,在主函數用 DS0 的翻轉
來提示程序正在運行。本章,我們選擇難度適中的通用定時器來介紹,本章將分為如下幾個部
分:
12.1 STM32 通用定時器簡介
12.2 硬件設計
12.3 軟件設計
12.4 下載驗證
12.1 STM32 通用定時器簡介
STM32 的通用定時器是一個通過可編程預分頻器(PSC)驅動的 16 位自動裝載計數器(CNT)
構成。STM32 的通用定時器可以被用于:測量輸入信號的脈沖長度(輸入捕獲)或者產生輸出波
形(輸出比較和 PWM)等。 使用定時器預分頻器和 RCC 時鐘控制器預分頻器,脈沖長度和波形
周期可以在幾個微秒到幾個毫秒間調整。STM32 的每個通用定時器都是完全獨立的,沒有互相
共享的任何資源。
STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定時器功能包括:
1)16 位向上、向下、向上/向下自動裝載計數器(TIMx_CNT)。
2)16 位可編程(可以實時修改)預分頻器(TIMx_PSC),計數器時鐘頻率的分頻系數為 1~
65535 之間的任意數值。
3)4 個獨立通道(TIMx_CH1~4),這些通道可以用來作為:
A.輸入捕獲
B.輸出比較
C.PWM 生成(邊緣或中間對齊模式)
D.單脈沖模式輸出
4)可使用外部信號(TIMx_ETR)控制定時器和定時器互連(可以用 1 個定時器控制另外
一個定時器)的同步電路。
5)如下事件發生時產生中斷/DMA:
A.更新:計數器向上溢出/向下溢出,計數器初始化(通過軟件或者內部/外部觸發)
B.觸發事件(計數器啟動、停止、初始化或者由內部/外部觸發計數)
C.輸入捕獲
D.輸出比較
E.支持針對定位的增量(正交)編碼器和霍爾傳感器電路
F.觸發輸入作為外部時鐘或者按周期的電流管理
由于 STM32 通用定時器比較復雜,這里我們不再多介紹,請大家直接參考《STM32 參考
手冊》第 253 頁,通用定時器一章。下面我們介紹一下與我們這章的實驗密切相關的幾個通用
定時器的寄存器。
首先是控制寄存器 1(TIMx_CR1),該寄存器的各位描述如圖 12.1.1 所示:
圖 12.1.1 TIMx_CR1 寄存器各位描述
在本實驗中,我們只用到了 TIMx_CR1 的最低位(位 0),也就是計數器使能位,該位必須
置 1,才能讓定時器開始計數。接下來介紹第二個與我們這章密切相關的寄存器:DMA/中斷使
能寄存器(TIMx_DIER)。該寄存器是一個 16 位的寄存器,其各位描述如圖 12.1.2 所示:
圖 12.1.2 TIMx_ DIER 寄存器各位描述
這里我們同樣僅關心它的最低位,該位是更新中斷允許位,本章用到的是定時器的更新中
斷,所以該位要設置為 1,來允許由于更新事件所產生的中斷。
接下來我們看第三個與我們這章有關的寄存器:預分頻寄存器(TIMx_PSC)。該寄存器用
設置對時鐘進行分頻,然后提供給計數器,作為計數器的時鐘。該寄存器的各位描述如圖 12.1.3
所示:
圖 12.1.3 TIMx_ PSC 寄存器各位描述
這里,定時器的時鐘來源有 4 個:
1)內部時鐘(CK_INT)
2)外部時鐘模式 1:外部輸入腳(TIx)
3)外部時鐘模式 2:外部觸發輸入(ETR)
4)內部觸發輸入(ITRx):使用 A 定時器作為 B 定時器的預分頻器(A 為 B 提供時鐘)。
這些時鐘,具體選擇哪個可以通過 TIMx_SMCR 寄存器的相關位來設置。這里的 CK_INT
時鐘是從 APB1倍頻的來的,STM32 中除非APB1 的時鐘分頻數設置為 1,否則通用定時器TIMx
的時鐘是 APB1 時鐘的 2 倍,當 APB1 的時鐘不分頻的時候,通用定時器 TIMx 的時鐘就等于
APB1 的時鐘。這里還要注意的就是高級定時器的時鐘不是來自 APB1,而是來自 APB2 的。
這里順帶介紹一下 TIMx_CNT 寄存器,該寄存器是定時器的計數器,該寄存器存儲了當前
定時器的計數值。
接著我們介紹自動重裝載寄存器(TIMx_ARR),該寄存器在物理上實際對應著 2 個寄存器。
一個是程序員可以直接操作的,另外一個是程序員看不到的,這個看不到的寄存器在《STM32
參考手冊》里面被叫做影子寄存器。事實上真正起作用的是影子寄存器。根據 TIMx_CR1 寄存
器中 APRE 位的設置:APRE=0 時,預裝載寄存器的內容可以隨時傳送到影子寄存器,此時 2
者是連通的;而 APRE=1 時,在每一次更新事件(UEV)時,才把預裝在寄存器的內容傳送到
影子寄存器。
自動重裝載寄存器的各位描述如圖 12.1.4 所示:
圖 12.1.4 TIMx_ ARR 寄存器各位描述
最后,我們要介紹的寄存器是:狀態寄存器(TIMx_SR)。該寄存器用來標記當前與定時
器相關的各種事件/中斷是否發生。該寄存器的各位描述如圖 12.1.5 所示:
圖 12.1.5 TIMx_ SR 寄存器各位描述
TIMx_ SR 寄存器,我們同樣只用到了最低位,當計數器 CNT 被重新初始化的時候,產生
更新中斷標記,通過這個中斷標志位,就可以知道產生中斷的類型。
關于這些位的詳細描述,請參考《STM32 參考手冊》第 282 頁。
只要對以上幾個寄存器進行簡單的設置,我們就可以使用通用定時器了,并且可以產生中
斷。
這一章,我們將使用定時器產生中斷,然后在中斷服務函數里面翻轉 DS1 上的電平,來指
示定時器中斷的產生。接下來我們以通用定時器 TIM3 為實例,來說明要經過哪些步驟,才能
達到這個要求,并產生中斷。這里我們就對每個步驟通過庫函數的實現方式來描述。首先要提
到 的 是 , 定 時 器 相 關 的 庫 函 數 主 要 集 中 在 HAL 庫 文 件 stm32f1xx_hal_tim.h 和
stm32f1xx_hal_tim.c 文件中。定時器配置步驟如下:
1)TIM3 時鐘使能。
HAL 中定時器使能是通過宏定義標識符來實現對相關寄存器操作的,方法如下:
__HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3 時鐘
2)初始化定時器參數,設置自動重裝值,分頻系數,計數方式等。
在 HAL 庫中,定時器的初始化參數是通過定時器初始化函數 HAL_TIM_Ba_Init 實現的:
HAL_StatusTypeDef HAL_TIM_Ba_Init(TIM_HandleTypeDef *htim);
該函數只有一個入口參數,就是 TIM_HandleTypeDef 類型結構體指針,結構體類型為下面
我們看看這個結構體的定義:
typedef struct
{
TIM_TypeDef *Instance;
TIM_Ba_InitTypeDef Init;
HAL_TIM_ActiveChannel
Channel;
DMA_HandleTypeDef
*hdma[7];
HAL_LockTypeDef Lock;
__IO HAL_TIM_StateTypeDef State;
}TIM_HandleTypeDef;
第一個參數 Instance 是寄存器基地址。和串口,看門狗等外設一樣,一般外設的初始化結
構體定義的第一個成員變量都是寄存器基地址。這在HAL中都定義好了,比如要初始化串口1,
那么 Instance 的值設置為 TIM1 即可。
第二個參數 Init 為真正的初始化結構體 TIM_Ba_InitTypeDef 類型。該結構體定義如下:
typedef struct
{
uint32_t Prescaler;
//預分頻系數
uint32_t CounterMode; //計數方式
uint32_t Period;
//自動裝載值 ARR
uint32_t ClockDivision; //時鐘分頻因子
uint32_t RepetitionCounter;
} TIM_Ba_InitTypeDef;
該初始化結構體中,參數 Prescaler 是用來設置分頻系數的,剛才上面有講解。參數
CounterMode 是用來設置計數方式,可以設置為向上計數,向下計數方式還有中央對齊計數方
式 , 比 較 常 用 的 是 向 上 計 數 模 式 TIM_CounterMode_Up 和 向 下 計 數 模 式
TIM_CounterMode_Down。參數 Period 是設置自動重載計數周期值。參數 ClockDivision 是用來
設置時鐘分頻因子,也就是定時器時鐘頻率 CK_INT 與數字濾波器所使用的采樣時鐘之間的分
頻比。參數 RepetitionCounter 用來設置重復計數器寄存器的值,用在高級定時器中。
第三個參數 Channel 用來設置活躍通道。前面我們講解過,每個定時器最多有四個通道可
以用來做輸出比較,輸入捕獲等功能之用。這里的 Channel 就是用來設置活躍通道的,取值范
圍為:HAL_TIM_ACTIVE_CHANNEL_1~ HAL_TIM_ACTIVE_CHANNEL_4。
第四個 hdma 是定時器的 DMA 功能時用到,為了簡單起見,我們暫時不講解太復雜。
第五個參數 Lock 和 State,是狀態過程標識符,是 HAL 庫用來記錄和標志定時器處理過程。
定時器初始化范例如下:
TIM_HandleTypeDef TIM3_Handler;
//定時器句柄
TIM3_Handler.Instance=TIM3; //通用定時器 3
TIM3_Handler.Init.Prescaler= 7199;
//分頻系數
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;
//向上計數器
TIM3_Handler.Init.Period=4999;
//自動裝載值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//時鐘分頻因子
HAL_TIM_Ba_Init(&TIM3_Handler);
3)使能定時器更新中斷,使能定時器
HAL 庫 中 , 使 能 定 時 器 更 新 中 斷 和 使 能 定 時 器 兩 個 操 作 可 以 在 函 數
HAL_TIM_Ba_Start_IT()中一次完成的,該函數聲明如下:
HAL_StatusTypeDef HAL_TIM_Ba_Start_IT(TIM_HandleTypeDef *htim);
該 函 數 非 常 好 理 解 , 只 有 一 個 入 口 參 數 。 調 用 該 定 時 器 之 后 , 會 首 先 調 用
__HAL_TIM_ENABLE_IT 宏定義使能更新中斷,然后調用宏定義__HAL_TIM_ENABLE 使能
相應的定時器。這里我們分別列出單獨使能/關閉定時器中斷和使能/關閉定時器方法:
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);//使能句柄指定的定時器更新中斷
__HAL_TIM_DISABLE_IT (htim, TIM_IT_UPDATE);//關閉句柄指定的定時器更新中斷
__HAL_TIM_ENABLE(htim);//使能句柄 htim 指定的定時器
__HAL_TIM_DISABLE(htim);//關閉句柄 htim 指定的定時器
4)TIM3 中斷優先級設置。
在定時器中斷使能之后,因為要產生中斷,必不可少的要設置 NVIC 相關寄存器,設置中
斷優先級。之前多次講解到中斷優先級的設置,這里就不重復講解。
和串口等其他外設一樣,HAL 庫為定時器初始化定義了回調函數 HAL_TIM_Ba_MspInit。ALIENTEK MiniSTM32
V3.0 開發板教程
183
STM32 不完全手
一般情況下,與 MCU 有關的時鐘使能,以及中斷優先級配置我們都會放在該回調函數內部。
函數聲明如下:
void HAL_TIM_Ba_MspInit(TIM_HandleTypeDef *htim);
對于回調函數,這里我們就不做過多講解,大家只需要重寫這個函數即可。
5)編寫中斷服務函數。
在最后,還是要編寫定時器中斷服務函數,通過該函數來處理定時器產生的相關中斷。通
常情況下,在中斷產生后,通過狀態寄存器的值來判斷此次產生的中斷屬于什么類型。然后執
行相關的操作,我們這里使用的是更新(溢出)中斷,所以在狀態寄存器 SR 的最低位。在處
理完中斷之后應該向 TIM3_SR 的最低位寫 0,來清除該中斷標志。
跟串口一樣,對于定時器中斷,HAL 庫同樣為我們封裝了處理過程。這里我們以定時器 3
的更新中斷為例來講解。
首先,中斷服務函數是不變的,定時器 3 的中斷服務函數為:
TIM3_IRQHandler();
一般情況下我們是在中斷服務函數內部編寫中斷控制邏輯。但是 HAL 庫為我們定義了 新
的定時器中斷共用處理函數 HAL_TIM_IRQHandler,在每個定時器的中斷服務函數內部,我們
會調用該函數。該函數聲明如下:
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
而函數 HAL_TIM_IRQHandler 內部,會對相應的中斷標志位進行詳細判斷,判斷確定中斷
來源后,會自動清掉該中斷標志位,同時調用不同類型中斷的回調函數。所以我們的中斷控制
邏輯只用編寫在中斷回調函數中,并且中斷回調函數中不需要清中斷標志位。
比如定時器更新中斷回調函數為:
void HAL_TIM_PeriodElapdCallback(TIM_HandleTypeDef *htim);
跟串口中斷回調函數一樣,我們只需要重寫該函數即可。對于其他類型中斷,HAL 庫同樣
提供了幾個不同的回調函數,這里我們列出常用的幾個回調函數:
void HAL_TIM_PeriodElapdCallback(TIM_HandleTypeDef *htim);//更新中斷
void HAL_TIM_OC_DelayElapdCallback(TIM_HandleTypeDef *htim);//輸出比較
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);//輸入捕獲
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);//觸發中斷
對于這些回調函數的使用方法我們在后面用到的時候會給大家詳細講解。
通過以上幾個步驟,我們就可以達到我們的目的了,使用通用定時器的更新中斷,來控制
DS1 的亮滅。
12.2 硬件設計
本實驗用到的硬件資源有:
1) 指示燈 DS0 和 DS1
2) 定時器 TIM3
本章將通過 TIM3 的中斷來控制 DS1 的亮滅,DS0 和 DS1 的電路在前面已經有介紹了。而
TIM3 屬于 STM32 的內部資源,只需要軟件設置即可正常工作。
12.3 軟件設計
打開我們光盤實驗 7 定時器中斷實驗可以看到,我們的工程中的 HARDWARE 下面比以前
多了一個 time.c 文件(包括頭文件 time.h),這兩個文件是我們自己編寫。同時還引入了定時器
相關的 HAL 庫函數文件 stm32f1xx_hal_tim.c 和頭文件 stm32f1xx_hal_tim.h。下面我們來看看我
們的 time.c 文件。timer.c 文件代碼如下:
#include "timer.h"
#include "led.h"
TIM_HandleTypeDef TIM3_Handler; //定時器句柄
//通用定時器 3 中斷初始化
//arr:自動重裝值。
//psc:時鐘預分頻數
//定時器溢出時間計算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定時器工作頻率,單位:Mhz
//這里使用的是定時器 3!
void TIM3_Init(u16 arr,u16 psc)
{
TIM3_Handler.Instance=TIM3;
//通用定時器 3
TIM3_Handler.Init.Prescaler=psc;
//分頻系數
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上計數器
TIM3_Handler.Init.Period=arr;
//自動裝載值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//時鐘分頻因子
HAL_TIM_Ba_Init(&TIM3_Handler);
HAL_TIM_Ba_Start_IT(&TIM3_Handler);
//使能定時器 3 和定時器 3 更新中斷:TIM_IT_UPDATE
}
//定時器底冊驅動,開啟時鐘,設置中斷優先級
//此函數會被 HAL_TIM_Ba_Init()函數調用
void HAL_TIM_Ba_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3 時鐘
HAL_NVIC_SetPriority(TIM3_IRQn,1,3);
//設置中斷優先級,搶占優先級 1,子優先級 3
HAL_NVIC_EnableIRQ(TIM3_IRQn); //開啟 ITM3 中斷
}
}
//定時器 3 中斷服務函數
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM3_Handler);
}
//回調函數,定時器中斷服務函數調用
void HAL_TIM_PeriodElapdCallback(TIM_HandleTypeDef *htim)
{
if(htim==(&TIM3_Handler))
{
LED1=!LED1; //LED1 反轉
}
}
該文件下包含一個中斷服務函數和一個定時器 3 中斷初始化函數,中斷服務函數比較簡單,
在每次中斷后,判斷 TIM3 的中斷類型,如果中斷類型正確,則執行 LED1(DS1)的取反。
TIM3_Int_Init 函數就是執行我們上面介紹的那 5 個步驟,使得 TIM3 開始工作,并開啟中
斷。該函數的 2 個參數用來設置 TIM3 的溢出時間。因為我們在 Stm32_Clock_Init 函數里面已
經初始化 APB1 的時鐘為 2 分頻,所以 APB1 的時鐘為 36M,而從 STM32F1 的內部時鐘樹圖
(圖 5.2.2.1)得知:當 APB1 的時鐘分頻數為 1 的時候,TIM2~7 的時鐘為 APB1 的時鐘,而
如果 APB1 的時鐘分頻數不為 1,那么 TIM2~7 的時鐘頻率將為 APB1 時鐘的兩倍。因此,TIM3
的時鐘為 72M,再根據我們設計的 arr 和 psc 的值,就可以計算中斷時間了。計算公式如下:
Tout= ((arr+1)*(psc+1))/Tclk;
其中:
Tclk:TIM3 的輸入時鐘頻率(單位為 Mhz)。
Tout:TIM3 溢出時間(單位為 us)。
我們將 timer.c 文件保存,然后加入到 HARDWARE 組下。接下來,在 timer.h 文件里,我
們輸入如下代碼:
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
extern TIM_HandleTypeDef TIM3_Handler; //定時器句柄
void TIM3_Int_Init(u16 arr,u16 psc);
#endif
此部分代碼十分簡單,這里不做介紹。
最后,我們在主程序里面輸入如下代碼:
int main(void)
{
HAL_Init();
//初始化 HAL 庫
Stm32_Clock_Init(RCC_PLL_MUL9); //設置時鐘,72M
delay_init(72);
//初始化延時函數
uart_init(115200);
//初始化串口
LED_Init();
//初始化 LED
KEY_Init();
//初始化按鍵
TIM3_Init(5000-1,7200-1);
//定時器 3 初始化,定時器時鐘為 72M,
//分頻系數為 7200-1,所以定時器 3 的頻率為 72M/7200=10K,自動重裝載為 5000-1,
//那么定時器周期就是 500ms
while(1)
{
LED0=!LED0;
delay_ms(200);
}
}
這里的代碼和之前大同小異,此段代碼對 TIM3 進行初始化之后,進入死循環等待 TIM3
溢出中斷,當 TIM3_CNT 的值等于 TIM3_ARR 的值的時候,就會產生 TIM3 的更新中斷,然
后在中斷里面取反 LED1,TIM3_CNT 再從 0 開始計數。
這里定時器定時時長 500ms 是這樣計算出來的,定時器的時鐘為 72Mhz,分頻系數為 7200,
所以分頻后的計數頻率為 72Mhz/7200=10KHz,然后計數到 5000,所以時長為 5000/10000=0.5s,
也就是 500ms。
12.4 下載驗證
在完成軟件設計之后,我們將編譯好的文件下載到 MiniSTM32 開發板上,觀看其運行結果
是否與我們編寫的一致。如果沒有錯誤,我們將看 DS0 不停閃爍(每 400ms 閃爍一次),而 DS1
也是不停的閃爍,但是閃爍時間較 DS0 慢(1s 一次)。
本文發布于:2023-02-28 20:07:00,感謝您對本站的認可!
本文鏈接:http://www.newhan.cn/zhishi/a/167765664079275.html
版權聲明:本站內容均來自互聯網,僅供演示用,請勿用于商業和其他非法用途。如果侵犯了您的權益請與我們聯系,我們將在24小時內刪除。
本文word下載地址:定時器實驗(定時器實驗分析及總結).doc
本文 PDF 下載地址:定時器實驗(定時器實驗分析及總結).pdf
| 留言與評論(共有 0 條評論) |