文章目錄
- 定時器(TIM)
- 定時器種類與分布
- 定時器的基本結構
- 時基單元
- 時基單元基本結構
- 計數器計數方向
- 時基單元時鐘來源計算
- 寄存器預加載機制
- 自制延時函數
- 獲取單片機當前時間
- 實現延遲函數
- 初始化定時器3的時基單元
- 配置中斷
- 編寫中斷響應函數
- 測試延遲函數
定時器(TIM)
定時器種類與分布
- 定時器種類:單片機里定時器分高級、通用、基本三種,功能上高級定時器最全,通用定時器是在高級定時器基礎上閹割部分功能,基本定時器則是在通用定時器基礎上再閹割部分功能。
- 分布情況:STM32F1 系列芯片最多有 14 個定時器,定時器 1 和 8 是高級定時器,定時器 6 和 7 是基本定時器,定時器 2 到 5 以及定時器 9 到 14 是通用定時器。實際使用芯片中有 1 個高級定時器(定時器 1)和 3 個通用定時器(定時器 2 - 4),學習編程一般以高級定時器 1 為例。
定時器的基本結構
時基單元,輸出比較,輸入捕獲,從模式控制器四個部分。
時基單元
時基單元基本結構
- 時鐘源:時鐘信號主要有兩個來源,通常認為來自上一章學習的時鐘樹,一個為從模式控制器。
- 預分頻器:因輸入時鐘信號頻率高,需用預分頻器降頻,其分頻系數等于 PSC 的值加 1 。
- 計數器 CNT:對經過分頻后的脈沖信號計數,每來一個脈沖,計數器的值增加 1。
- 自動重裝寄存器 ARR:用于設置定時周期,定時周期的值等于 ARR 的值加 1。當計數器 CNT 的值增長到與 ARR 相等時,會發生溢出,然后從 0 開始重新計數。
- 重復計數器 RCR:設置重復計數的次數。沒有 RCR 時,計數器 CNT 每溢出一次產生一個 update 事件;加入 RCR 后,計數器 CNT 需要溢出 RCR + 1 次才產生一個 update 事件。
和手表類似,手表有石英晶振提供時鐘信號,還有其他電路裝置進行降頻,秒針相當于一個計數器,ARR為59,周期為60,RCR為0,每一個周期產生一個事件。
計數器計數方向
- 上計數(count up):從 0 開始,左邊每來一個脈沖,計數器 CNT 的值增加 1,直到增長到和自動重裝計算器 ARR 相等時發生溢出,然后從 0 開始重新計數。
- 下計數(count down):與上計數相反,從 ARR 開始計數,左邊每來一個脈沖,計數器 CNT 的值減少 1,直到減少到 0 發生溢出,然后從 ARR 開始重新計數。
- 中心對齊(center line):首先從 0 開始上計數到 ARR,然后轉變成下計數,從 ARR 開始又計數到 0,完成一個定時周期。下計數和中心對齊用得較少,一般用上計數。
時基單元時鐘來源計算
-
分辨率和周期概念:類比手表,分辨率是計時的最小間隔,周期是轉一圈所消耗的時間。對于定時器,計數器 CNT 每跳一下所消耗的時間是分辨率,從某點到計數器溢出是計數周期。
-
計算過程:以設置定時器 3 的 11 單元分辨率為 1 微秒,周期為 1 毫秒為例。先根據時鐘樹確定輸入信號頻率,當前配置下所有定時器時鐘頻率為 72 兆赫茲。要得到 1 微秒分辨率即 1 兆赫茲信號,設置預分頻器 PSC 值為 71 ;要設置 1 毫秒周期,自動重裝寄存器 AR 值為 999,重復計數器 RCR 值為 0。
TIM_CLK是系統自動配置的,如果APB2和APB1的分頻系數為1,TIM_CLK = PCLK2,如果其他系數 TIM_CLK = 2 * PCLK2,右面同理。
寄存器預加載機制
預加載機制,如果陰影為黑色,表示默認為開啟,灰色表示默認為關閉。
- 概念:向寄存器寫值時,先寫入影子寄存器,值不會立即生效,等計數器 CNT 溢出產生 update 事件時,影子寄存器的值才進入活動寄存器并生效,這種緩存機制叫寄存器預加載。
-
作用:以自動重裝寄存器 AR 為例,在定時器運行中改變 AR 值,不使用預加載機制會導致定時器跑飛,加入預加載機制后,可避免該問題,使計數周期正常過渡 。
未使用預加載寄存器
使用了預加載寄存器
自制延時函數
使用定時器3和中斷函數自制延時函數,因為使用的是while輪詢等待所以還是會造成程序阻塞。
獲取單片機當前時間
-
定時器配置:假設時基單元輸入時鐘頻率 72 兆赫茲,設置 PSC 值為 71 對輸入時鐘 72 分頻,得到 1 兆赫茲時鐘頻率,周期 1 微秒;設置定時周期為 1000(999 + 1),即 1 毫秒;設置重復計數器 RCR 值為 0,使 update 事件頻率 1 毫秒一次,激活 update 標志位產生中斷。
-
變量聲明與作用:聲明無符號 32 位整型變量 current tick ,賦初值 0,用于記錄單片機當前時間(單位毫秒),變量前加 volatile 關鍵字(作用與中斷有關,可自行百度學習)。
-
中斷響應操作:中斷每毫秒產生一次,在中斷響應函數中讓 current tick 值增加 1,從而記錄單片機開機后的時間。
實現延遲函數
- 函數設計:定義延遲函數 APP delay,參數為要延遲的毫秒數。
- 實現思路:用 current tick 獲取當前時間,加上要延遲的時間得到延遲結束時間 expire time ,通過 while 循環等待當前時間超過延遲結束時間,函數結束即實現延遲。
初始化定時器3的時基單元
- 第一步:使能時鐘:定時器 3 在 APB1 總線上,調用 RCC_APB1PeriphClockCmd 開啟其時鐘。
- 第二步:配置參數:學習 TIM_TimeBaseInit 編程接口,聲明結構體變量 TM_TimeBaseInitTypeDef 并賦值,設置預分頻器 PSC 值 71、周期值 999、計數方向為上計數、重復計數器 RCR 值 0,調用該接口完成參數配置。
- 第三步:閉合開關:學習 TIM_Cmd 編程接口,用該接口閉合開關使定時器運行。
配置中斷
- 使能 update 中斷:學習 TIM_ITConfig 編程接口,通過該接口閉合 update 標志位產生中斷的開關。
- 配置 NVIC 模塊:
- 先在 main 方法開頭配置中斷優先級分組;
- 再聲明結構體變量 NVIC_InitTypeDef 并賦值,設置定時器 3 中斷相關參數,調用 NVIC_Init 完成 NVIC 模塊配置。
編寫中斷響應函數
- 找到函數名:在啟動文件的中斷向量表中找到定時器 3 的中斷響應函數 TIM3_IRQHandler ,復制到 main.c 文件進行實現。
- 函數內容:先通過 TM_GetFlagStatus 檢查是否由 update 標志位觸發中斷,若是則用 TM_ClearFlag 清除標志位,再讓 current tick 值加 1。
測試延遲函數
- 初始化板載 LED:寫函數初始化板載 LED(PC13 引腳),開啟 GPIOC 時鐘,聲明結構體變量并設置相關參數,初始化完成后關燈。
- 編寫閃燈程序:在 main 方法的 while 循環中編寫閃燈代碼,調用 APP delay 延遲函數,實現不同閃爍效果(如亮 500 毫秒滅 500 毫秒、兩次短閃一次長閃等),編譯下載代碼到單片機測試,驗證延遲函數可用 。
完整main.c代碼
#include "stm32f10x.h" // Device headervolatile uint32_t currentTick = 0;
void App_delay(uint32_t ms);
void App_TIM3_BaseInit(void);
void App_GpioInit(void);
int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);App_TIM3_BaseInit();App_GpioInit();while(1){GPIO_ResetBits(GPIOC, GPIO_Pin_13);App_delay(100);GPIO_SetBits(GPIOC, GPIO_Pin_13);App_delay(100);GPIO_ResetBits(GPIOC, GPIO_Pin_13);App_delay(500);GPIO_SetBits(GPIOC, GPIO_Pin_13);App_delay(500);}
}
//自制延時函數邏輯
void App_delay(uint32_t ms)
{uint32_t extime = ms + currentTick;while(extime > currentTick);
}
//定時器3初始化
void App_TIM3_BaseInit(void)
{//開啟定時器3的時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//配置時基單元參數TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_Period = 999;TIM_TimeBaseInitStruct.TIM_Prescaler = 71;TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);//閉合時基單元開關TIM_Cmd(TIM3, ENABLE);//使能updateTIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//配置NVIC模塊NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct);}
void TIM3_IRQHandler(void)
{//先判斷是不是要的中斷if(TIM_GetFlagStatus(TIM3, TIM_FLAG_Update) == SET){//將中斷標志位清除TIM_ClearFlag(TIM3, TIM_FLAG_Update);//操作currentTick++;}
}
void App_GpioInit(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStruct);
}