一、什么是定時器
關于什么是定時器,簡單來講,就是是用來定時的。STM32F103ZET6有兩個基本定時器TIM6和TIM7,四個通用定時器TIM2~TIM5和兩個高級定時器TIM1,TIM8。每一個定時器都是完全獨立的,不共享任何資源。
根據中文參考手冊介紹,基本定時器最為簡單,類似于51單片機的定時器。通用定時器在基本定時器的基礎上增加了輸入捕獲和輸出比較功能。高級定時器相比通用定時器,又增加了可編程死區互補輸出,重復計數器等功能。
STM32F103ZET6的通用定時器是一個通過可編程預分頻器驅動的16位自動裝載計數器構成。使用定時器預分頻器和RCC時鐘控制器預分頻器,脈沖長度和波形周期可以在幾個微秒到幾個毫秒間調整。
這里介紹一下對于定時器的個人理解。定時器的定時原理實際可以理解為按照固定的頻率數數。按照固定頻率就說明定時器一定要有輸入時鐘。比如輸入為一個1KHz的時鐘,那么數一個數的時間就是1ms。另外,數數也不是無限地數,數值有一個上限。可以規定是從0開始數到上限值,還是從上限值數到0。而且每次數到頭,需要重新開始。比如,需要控制燈亮200ms。那么只需要在點亮LED之后,等到數到200時熄滅即可。當數到上限值或者數到0時,重新開始數。
二、定時器有什么用
定時器有許多用途,以通用定時器為例。它可以測量輸入信號的脈沖寬度,產生PWM波。此外定時器也可以用于觸發ADC采集,按鍵檢測等方面。
中文參考手冊介紹如下
中文參考手冊對于通用定時器功能的描述
三、通用定時器詳細介紹
速成選手可以線跳過這一部分,直接看下面,后來再返回來仔細看。
3.1 時鐘來源
根據中文參考手冊,通用定時器的時鐘來源有四個。
- ? 內部CK_INT
- ? 外部觸發時鐘輸入TIMx_ETR(外部時鐘模式2)
- ? 內部觸發輸入(ITRx):使用一個定時器作為另一個定時器的預分頻器
- ? 外部引腳輸入(外部時鐘模式1)
通用定時器時鐘來源
通過配置TIMx_SMCR寄存器來選擇,關于寄存器這里就不再詳細介紹了,大家可以去看中文參考手冊。
根據中文參考手冊關于時鐘的介紹,通用定時器掛接在APB1總線。對于APB1總線的時鐘如下
APB1時鐘介紹
如果APB1的預分頻系數為1,那么通用定時器的輸入時鐘頻率為36MHz,否則為72MHz。但是通常APB1總線的預分頻系數我們不會設置成1,所以通用定時器的時鐘頻率為72MHz。
3.2 預分頻器,計數器,自動重裝載寄存器
預分頻器,計數器和自動重裝載寄存器
3.2.1 預分頻器
預分頻器是對時鐘進行分頻,范圍是1~65536。比如通用定時器輸入時鐘頻率為72MHz,此時,將預分頻值設置為72,那么最終計數時的時鐘頻率為72MHz / 72 = 1MHz。
3.2.2 計數器
計數器就是用來計數的,計數值取值范圍是0~65535。有三種計數方式:向上計數,向下計數,中央對齊模式(向上/向下計數)。
在向上計數模式中,計數器從0計數到自動加載值(TIMx_ARR計數器的內容),然后重新從0開始計數并且產生一個計數器溢出事件。
在向下計數模式中,計數器從自動裝入的值(TIMx_ARR計數器的值)開始向下計數到0,然后從自動裝入的值重新開始并且產生一個計數器向下溢出事件。
在 中央對齊模式 ,計數器從0開始計數到自動加載的值(TIMx_ARR寄存器)?1,產生一個計數器溢出事件,然后向下計數到1并且產生一個計數器下溢事件;然后再從0開始重新計數。
3.2.3 自動重裝載寄存器
比如,選擇向下計數模式,初始值為2000。當計數到0時,會再次從2000開始向下計數。這就叫重裝載。但是實際起作用的并不是這里的自動重裝載寄存器,而是影子寄存器。關于影子寄存器這里就不再做介紹了,大家可以自行了解。
3.3 觸發控制器
從圖中的右上角可以注意到,有一個觸發控制器。它可以用來觸發一些外設,比如觸發ADC采集,也可以用來給其他定時器提供時鐘。
四、PWM
4.1 什么是PWM
PWM(脈沖寬度調制),它是一種利用微處理器的數字輸出來對模擬電路進行控制的技術,也可以理解為是對模擬信號電平進行數字編碼的方法。PWM可被應用于電機驅動,調光,通信等方面。
4.2 什么是占空比
一個PWM是有固定頻率的,也就意味著周期一定,一個周期內有效電平持續時間占整個周期的比例可以稱為占空比。比如一個周期100ms,其中50ms持續為有效電平,那么占空比就是50%。正是通過調節占空比,來調節電機轉速,或者用不同占空比代表不同信號,用于通信。
4.3 STM32F1 PWM介紹
STM32F1系列單片機,除了基本定時器TIM6和TIM7外,都可以產生PWM輸出。其中高級定時器TIM1和TIM8可以同時產生高達7路PWM輸出。PWM輸出其實就是對外輸出占空比可調的方波,信號頻率由自動重裝載寄存器ARR的值決定,占空比由比較寄存器CCR的值決定。假設高電平為有效電平,見下圖。ARR決定了周期(頻率),CCR調節占空比。
PWM示意圖
根據中文參考手冊介紹,STM32F1的PWM比較輸出模式共有8種。脈沖寬度調制模式可以產生一個由TIMx_ARR寄存器確定頻率、由TIMx_CCRx寄存器確定占空比的信號。在TIMx_CCMRx寄存器中的OCxM位寫入’110’(PWM模式1)或’111’(PWM模式2),能夠獨立地設置每個OCx輸出通道產生一路PWM。有關寄存器的內容,這里就不不再做詳細介紹。
這里介紹一下8種輸出模式中比較常用的兩種PWM輸出模式,PWM1和PWM2。其實這兩種輸出模式差別不大,只不過輸出電平的極性不同。
4.4 PWM頻率計算
頻率 = (主時鐘頻率(72MHz) / (分頻系數 + 1)) / 自動重裝載值(單位為Hz)
五、通用定時器輸出引腳
其他幾個定時器如下
TIM2的PWM引腳
TIM4的PWM引腳
TIM5的PWM引腳
六、實戰項目
這里以一個經典項目——呼吸燈,來一起熟悉一下定時器的配置和使用,要求滅—>亮—>滅,時間為2.5s。
6.1 呼吸燈
呼吸燈是指燈能夠像人的呼吸一樣,實現由暗到亮或由亮到暗的變化,通常用于消息提示功能,或者作為系統正在運行的提示。之前一篇博文介紹過三種呼吸燈的實現方式,這里針對普中核心板,來介紹一下如果實現呼吸燈。
6.2 實現思路
這里用兩種方法來實現一下呼吸燈。分別是定時器的溢出中斷和PWM。其實第一種和PWM類似,我非就是控制LED點亮時間。
? 定時器中斷實現 配置好預分頻系數和重裝載值,使每0.25ms進入一次定時器中斷,記錄進入中斷次數(count)。當進入次數滿100次之后(2.5ms),控制LED點亮的變量(t)值加1。主函數的while(1)輪詢中,如果t小于等于count的時候,LED點亮,否則LED熄滅。t的值累計100加次后(2.5s),開始遞減,LED由亮到滅。控制t是遞增還是遞減的是一個標志位(flag),初始值為0,具體可以看程序設計。
? PWM 利用PWM實現呼吸燈就更加簡單了,只需要不斷調節占空比即可。
66.3 定時器配置
配置通用定時器,有以下步驟
? 使能定時器時鐘
? 初始化定時器參數,包括自動重裝載值,分頻系數,計數方式等
? 設置中斷類型,并使能
? 設置中斷優先級,使能定時器中斷通道
? 開啟定時器
? 編寫定時器中斷服務函數
需要注意的是,配置預分頻系數時,比如設置為6,實際是6 + 1。
定時時間T = 自動重裝載值 * ((預分頻系數 + 1) / 主時鐘頻率)。主時鐘頻率為72MHz。(為了避免誤導,這里寫的主時鐘頻率為72MHz是APB1總線分頻系數不是1的前提下。)
6.4 定時器中斷實現呼吸燈
定時器配置程序如下,使用定時器2,控制LED1實現呼吸燈,由滅—>亮—>滅,時間為5秒。
/*
*==============================================================================
*函數名稱:TIM2_Iint
*函數功能:初始化定時器2
*輸入參數:per:自動重裝載值;psc:預分頻系數
*返回值:無
*備 注:無
*==============================================================================
*/
void TIM2_Iint (u16 per,u16 psc)
{
// 結構體定義
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); // 使能TIM2時鐘
TIM_TimeBaseInitStructure.TIM_Period = per; // 自動裝載值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 分頻系數
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 不分頻
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; // 設置向上計數模式
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); // 開啟定時器中斷
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); // 使能更新中斷
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 定時器中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; // 搶占優先級
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; // 子優先級
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE); // 使能定時器
}
初始化時定時器的程序如下
TIM2_Iint(250,71); // TIM2初始化
預分頻系數為71 + 1 = 72,計數到250進入一次中斷,也就是0.25ms進入一次中斷。累計進入100次(25ms)中斷開始調節一點LED的亮度。由滅到亮,累計調節100次(2.5s)。主函數和中斷服務函數如下
u8 gTimIrqCunt = 0; // 進入中斷次數計數變量
u8 gLedLightCtrl = 0; // LED亮度控制變量
u8 gLedFlag = 0; // LED亮滅控制標志位,0:滅— >亮;1:亮— >滅
int main(void)
{
Med_Mcu_Iint(); // 系統初始化
while(1)
{
if (gLedLightCtrl <= gTimIrqCunt)
{
Med_Led_StateCtrl (LED1,LED_OFF); // 熄滅LED1
}
if (gLedLightCtrl > gTimIrqCunt)
{
Med_Led_StateCtrl (LED1,LED_ON); // 熄滅LED1
}
}
}
// TIM2中斷服務函數
void TIM2_IRQHandler(void) // TIM2中斷
{
// 產生更新中斷
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
gTimIrqCunt = gTimIrqCunt + 1; // 進入中斷次數加1
// 累計進入100次中斷,且是由滅到亮
if (gTimIrqCunt >= 100 && gLedFlag < 100)
{
gTimIrqCunt = 0; // 清零進入中斷計數變量
gLedLightCtrl = gLedLightCtrl + 1; // LED亮度控制變量加1
gLedFlag = gLedFlag + 1; // LED亮滅控制標志位加1
}
// 累計進入100次中斷,且是由亮到滅
if (gTimIrqCunt >= 100 && gLedFlag >= 100)
{
gTimIrqCunt = 0; // 清零進入中斷計數變量
gLedLightCtrl = gLedLightCtrl - 1; // LED亮度控制變量加1
gLedFlag = gLedFlag + 1; // LED亮滅控制標志位加1
}
// 一個亮滅周期結束
if (gLedFlag >= 200)
{
gLedFlag = 0; // 清零LED亮滅控制標志位
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除TIM2更新中斷標志
}
6.5 使用PWM實現呼吸燈
PWM配置步驟
- ? 使能定時器以及GPIO時鐘,設置引腳復用映射
- ? 初始化定時器參數,包括自動重裝載值,分頻系數,計數方式等
- ? 初始化PWM輸出參數,包括PWM模式,輸出極性,使能等
- ? 開啟定時器
- ? 修改CCRx的值來修改占空比
- ? 使能TIMx在CCRx上的預裝載寄存器
- ? 使能TIMx在ARR上的預裝載寄存器允許位
TIM3的通道1配置程序如下這里對引腳進行了重映射。
/*
*==============================================================================
*函數名稱:TIM3_CH1_PWM_Init
*函數功能:初始化定時器3的PWM通道1
*輸入參數:per:自動重裝載值;psc:預分頻系數
*返回值:無
*備 注:無
*==============================================================================
*/
void TIM3_CH1_PWM_Init (u16 per,u16 psc)
{
// 結構體定義
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 開啟時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
// 初始化GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 復用推挽輸出
GPIO_Init(GPIOC,&GPIO_InitStructure);
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE); // 改變指定管腳的映射
// 初始化定時器參數
TIM_TimeBaseInitStructure.TIM_Period = per; // 自動裝載值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 分頻系數
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 設置向上計數模式
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
// 初始化PWM參數
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 比較輸出模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 輸出極性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 輸出使能
TIM_OC1Init(TIM3,&TIM_OCInitStructure); // 輸出比較通道1初始化
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable); // 使能TIMx在 CCR1 上的預裝載寄存器
TIM_ARRPreloadConfig(TIM3,ENABLE); // 使能預裝載寄存器
TIM_Cmd(TIM3,ENABLE); // 使能定時器
}
實現呼吸燈時,只需要在main函數中不斷調整占空比即可,調整占空比的函數為
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1)
這里main函數就不在列出來了。需要注意的是,設置的CCR的值,不能超過自動重裝載值 - 1。
七、拓展
之前介紹按鍵檢測時,介紹過檢測按鍵長短按的方法。當時比較簡單粗暴,這里介紹另一種,使用定時器來判斷按鍵WK UP的長短按。假設規定,按下時間在10ms~500ms之間為短按,按下時間大于等于1s,為長按。短按LED1點亮,長按LED1熄滅。之前是利用delay來實現時間控制,現在改用定時器實現時間控制,但是基本思路都是相同的。
關于按鍵部分的程序這里就不再做介紹了。首先配置定時器,10ms進入一次更新中斷,預分頻系數為72,自動重裝載值為10000。使用TIM2,定時器配置程序和上面一樣,初始化程序如下填寫
TIM2_Iint(10000,71); // TIM2初始化
main函數以及中斷服務函數如下
u32 gKeyDownTimeCunt = 0; // 按鍵按下時間計數變量
u8 gKeyLongFlag = 0; // 按鍵長按標志位
u8 gKeyShotFlag = 0; // 按鍵短按標志位
int main(void)
{
Med_Mcu_Iint(); // 系統初始化
while(1)
{
// 短按
if (gKeyShotFlag == 1)
{
Med_Led_StateCtrl (LED1,LED_ON); // 點亮LED1
gKeyShotFlag = 0; // 清除短按標志位
}
// 長按
if (gKeyLongFlag == 1)
{
Med_Led_StateCtrl (LED1,LED_OFF); // 熄滅LED1
gKeyLongFlag = 0; // 清除長按標志位
}
}
}
// TIM2中斷服務函數
void TIM2_IRQHandler(void) // TIM2中斷
{
// 產生更新中斷
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
if (KEY_UP == 1)
{
gKeyDownTimeCunt = gKeyDownTimeCunt + 1; // 時間計數變量加1
}
// 松開后
else
{
// 短按
if (1 <= gKeyDownTimeCunt && gKeyDownTimeCunt <= 50)
{
gKeyDownTimeCunt = 0; // 清除時間計數變量
gKeyShotFlag = 1; // 短按標志位置1
}
// 長按
if (gKeyDownTimeCunt >= 100)
{
gKeyDownTimeCunt = 0; // 清除時間計數變量
gKeyLongFlag = 1; // 長按標志位置1
}
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除TIM2更新中斷標志
}
-
定時器
+關注
關注
23文章
3248瀏覽量
114800 -
RCC
+關注
關注
0文章
93瀏覽量
26937 -
預分頻器
+關注
關注
0文章
18瀏覽量
8135 -
ADC采樣
+關注
關注
0文章
134瀏覽量
12845 -
STM32F103ZET6
+關注
關注
9文章
67瀏覽量
21128
發布評論請先 登錄
相關推薦
評論