定時器四部分講解內(nèi)容,本文是第一部分
1、定時器基本定時,定一個時間,然后讓定時器每隔一段時間產(chǎn)生一個中斷,來實現(xiàn)每隔一個固定時間執(zhí)行一段程序的目的,比如要做一個時鐘、秒表或者使用一些程序算法
2、定時器輸出比較的功能,輸出比較這個模塊最常見的用途是產(chǎn)生PWM波形,用于驅(qū)動電機等設備,使用stm32的PWM波形來驅(qū)動舵機和直流電機的例子
3、定時器輸入捕獲的功能,學習使用輸入捕獲這個模塊來測量方波頻率的例子
4、定時器的編碼器接口,使用這個編碼器接口,能夠更加方便地讀取正交編碼器的輸出波形,在編碼電機測速中,應用廣泛
TIM簡介
72M/65536/65536得到中斷頻率,取倒數(shù)就是59.65s
級聯(lián),一個定時器的輸出,當作另外一個定時器的輸入,最大時間達到59.65s * 65536 * 65536
高級定時器連接的是性能更高的APB2總線,通用定時器和基本定時器連接的是APB1總線,在RCC開啟時鐘時注意。
高級定時器具有重復計數(shù)器、死區(qū)生成、互補輸出、剎車輸入等功能主要是為了三相無刷電機的驅(qū)動設計的。
高級到低級向下兼容
C8T6只有1個高級定時器和3個通用定時器
基本定時器
時基單元:預分頻器、計數(shù)器、自動重裝載寄存器
預分頻器之前,連接的就是基準計數(shù)時鐘的輸入,由于基本定時器只能選擇內(nèi)部時鐘,所以可以認為這根線直接到輸入端這里,也就是內(nèi)部時鐘CK_INT,內(nèi)部時鐘來源是RCC_TIMxCLK,頻率值一般都是系統(tǒng)的主頻72MHZ。
如果預分頻器寫1,那就是2分頻,輸出頻率=輸入頻率/2=36MHZ
如果預分頻器寫2,那就是3分頻,輸出頻率=輸入頻率/3=24MHZ
預分頻器的值和實際的分頻系數(shù)相差1,即實際分頻系數(shù)=預分頻器的值+1,這個預分頻器的值是16位,所以最大值可以寫65535,也就是65536分頻。預分頻器就是對輸入的基準頻率提前進行一個分頻的操作。
計數(shù)時鐘每來一個上升沿,計數(shù)器的值就加1,計數(shù)器是16位,所以里面的值可以從0一直加到65535.如果再加的話,計數(shù)器就會回到0重新開始。所以計數(shù)器的值在計時過程中會不斷自增運行,當自增運行到目標值時,產(chǎn)生中斷,那就完成了定時的任務。
自動重裝載寄存器:存儲目標值的寄存器。在運行的過程中,計數(shù)值不斷自增,自動重裝值是固定的目標,當計數(shù)值等于自動重裝值時,也就是計時時間到了。那他會產(chǎn)生一個中斷信號,并且清零計數(shù)器。計數(shù)器自動開始下一次的計數(shù)計時。
UI折線箭頭代表這里會產(chǎn)生中斷信號,像這種計數(shù)值等于自動重裝值產(chǎn)生的中斷,稱為更新中斷,這個更新中斷之后,就會通往NVIC,我們再配置好NVIC的定時器通道,那么定時器的更新中斷就能夠得到CPU的響應。
U向下箭頭表示更新事件,更新事件不會觸發(fā)中斷,但可以觸發(fā)內(nèi)部其他電路的工作。
主從觸發(fā)
它能讓內(nèi)部的硬件在不受程序的控制下實現(xiàn)自動運行。主模式觸發(fā)DAC有啥用?
在我們使用DAC時,可能會用DAC輸出一段波形,那就需要每隔一段時間來觸發(fā)一次DAC,讓它輸出下一個電壓點。正常思路,先設置一個定時器產(chǎn)生中斷,每隔一段時間在中斷程序中調(diào)用代碼手動觸發(fā)一次DAC轉(zhuǎn)換,然后DAC輸出,缺點:這樣會使主程序處于頻繁被中斷的狀態(tài),這回影響主程序的運行和其他中斷的響應。
所以定時器就設計了一個主模式,使用主模式可以把這個定時器的更新事件映射到這個觸發(fā)輸出TRGO(trigger out)的位置,然后TRGO直接接到DAC的觸發(fā)轉(zhuǎn)換引腳上。這樣,定時器的更新就不需要再通過觸發(fā)中斷來觸發(fā)DAC轉(zhuǎn)換。無需軟件參與,實現(xiàn)硬件自動化
通用定時器
通用定時器和高級定時器支持3種計數(shù)模式,向上計數(shù)、向下計數(shù)和中央對齊這三種模式。
內(nèi)外時鐘源選擇和主從觸發(fā)模式結(jié)構(gòu)
對于基本定時器而言,定時只能選擇內(nèi)部時鐘,也就是系統(tǒng)頻率72MHZ,
對于通用定時器而言,定時還可以選擇外部時鐘,第一個外部時鐘來自TIMx_ETR引腳的外部時鐘。
TIMx_ETR是一個定時器的輸入端口,可以用來接收外部的時鐘信號。在其引腳接上一個外部方波時鐘。然后配置一下內(nèi)部的極性選擇、邊沿檢測和預分頻器電路,再配置輸入濾波電路,這些電路可以對外部時鐘進行一定的整型,因為外部引腳時鐘,難免有毛刺,這些電路可對輸入的波形進行濾波。
濾波后的信號兵分兩路
上面一路ETRF進入觸發(fā)控制器,可以選擇作為時基單元的時鐘。如果要在ETR外部引腳提供時鐘或者相對ETR時鐘進行計數(shù),把這個定時器當作計數(shù)器來用,可配置以下一路的電路,stm32中稱為==“外部時鐘模式2”==
除了ETRF可以提供時鐘,TRGI還可以提供時鐘,主要用作觸發(fā)輸入來使用的。
觸發(fā)輸入作為外部時鐘來使用,稱為“外部時鐘模式1”
通過TRGI有5通路
TRGO可以通向其他定時器,而通向其他定時器的時候,就可以接到其他定時器的ITR引腳上,通過這一路就可以實現(xiàn)定時器級聯(lián)。
總結(jié)
外部時鐘模式1的輸入可以是ETR引腳,其他定時器,CH1引腳的邊沿、CH1引腳和CH2引腳。
一般情況下,外部時鐘通過ETR引腳即可
對于時鐘輸入而言,最常用的還是內(nèi)部的72MHZ的時鐘,如果要使用外部時鐘,首選ETR引腳外部時鐘模式2的輸入。,這一路最簡單最直接
定時器的編碼器接口,可以讀取正交編碼器的輸出波形
定時器的主模式輸出,可以把內(nèi)部的一些事件映射到這個TRGO引腳上,比如基本定時器分析中,將更新事件映射到TRGO,用于觸發(fā)其他定時器、DAC或ADC。
右邊:輸出比較電路,總共有4個通道,可以用于輸出PWM波形,驅(qū)動電機
左邊:輸入捕獲電路,總共四個通道,可以用于測量輸入方波的頻率、占空比等
中間寄存器:捕獲/比較寄存器,是輸入捕獲和輸出比較電路公用的,因為輸入捕獲和輸出比較不能同時使用,因此寄存器機器引腳是公用的。
通用定時器與高級定時器的區(qū)別
第一個:申請中斷的地方,增加了一個重復次數(shù)計數(shù)器,有了這個計數(shù)器之后,就可以實現(xiàn)每隔幾個計數(shù)周期,才發(fā)生一次更新事件和更新中斷。原來的結(jié)構(gòu)是每個計數(shù)周期完成后都會發(fā)生更新。相當于對輸出的更新信號又做了一次分頻。
以下是高級定時器對輸出比較模塊的升級
DTG是死區(qū)生成電路,右邊的輸出引腳由原來的一個變?yōu)閮蓚€互補的輸出,可以輸出一對互補的PWM波。為了驅(qū)動三相無刷電機
剎車輸入:給電機驅(qū)動提供安全保障,如果外部引腳BKIN產(chǎn)生了剎車信號,或者內(nèi)部時鐘失效,產(chǎn)生了故障,那么控制電路就會自動切斷電機的輸出,防止以外的發(fā)生。
定時中斷基本結(jié)構(gòu)圖
定時中斷和內(nèi)外時鐘源選擇
運行控制:就是控制寄存器的一些位,比如啟動停止、向上或向下計數(shù)等,操作這些寄存器,就能控制時基單元的運行。
左邊:為時基單元提供時鐘的部分.
第一個定時中斷使用RCC的內(nèi)部時鐘
第二個定時器外部時鐘就是用外部時鐘模式2
右邊:計時時間到,產(chǎn)生更新中斷后信號的去向(如果高級定時器,還會有一個重復計數(shù)器)
中斷信號會先在狀態(tài)寄存器里置一個中斷標志位,這個標志位會通過中斷輸出控制,到NVIC申請中斷。中斷輸出控制就是一個中斷輸出的允許位,如果需要某個中斷,就記得允許一下。
時序
預分頻器時序
緩沖寄存器(影子寄存器)真正起作用的寄存器,比如我們在某一時刻,把預分頻寄存器由0改為1,如果在此時立刻改變時鐘的分頻系數(shù),
那么就會導致這里,在一個計數(shù)周期內(nèi),前半部分和后半部分的頻率不一樣。這里計數(shù)計到一半,計數(shù)頻率突然改變,而緩沖寄存器,當計數(shù)計到一半的時候改變了分頻值,這個變化并不會立刻生效,而是會等到本次計數(shù)周期結(jié)束時產(chǎn)生更新事件,預分頻寄存器的值才會被傳遞到緩沖寄存器里面,才會生效。
因此,可以看到,即使在計數(shù)中途改變了預分頻值,技術(shù)頻率仍然會保持原來的頻率,直到本輪計數(shù)完成,在下一輪計數(shù)時,改變后的分頻值才會起作用。
計數(shù)器時序
內(nèi)部時鐘分頻因子為2,就是分頻系數(shù)為2,第一行是內(nèi)部時鐘72MHZ,第二行是時鐘使能,高電平啟動,第三行是計數(shù)器時鐘,因為分頻系數(shù)為2,所以這個頻率是內(nèi)部時鐘除2,然后計數(shù)器在計數(shù)器時鐘每個上升沿自增,當增加到0036的時候,發(fā)生溢出,那再來一個上升沿,計數(shù)器清零,計數(shù)器溢出,產(chǎn)生一個更新事件脈沖,另外還會置一個更新中斷標志位UIF,該標志位置1,就回去申請中斷,然后中斷響應后,需要在中斷程序中手動清零。
引入影子寄存器的目的實際上是為了同步,就是讓值的變化和更新事件同步發(fā)生,防止在運行中途更改造成錯誤。
下圖,在計數(shù)的中途,突然把F5改為36,影子寄存器是真正起作用的。它在自動加載寄存器改變后,還是F5,所以現(xiàn)在計數(shù)的目標還是計算到F5,產(chǎn)生更新事件,同時要更改的36才被傳遞到影子寄存器里,在下一個計數(shù)周期36才會有效。
RCC時鐘樹
RCC時鐘樹是stm32用來產(chǎn)生和配置時鐘,并且把配置好的時鐘發(fā)送到各個外設的系統(tǒng),時鐘是所有外設運行的基礎(chǔ),所以時鐘是最先需要配置的東西。
中間:SYSCLK是系統(tǒng)時鐘72MHZ。在時鐘產(chǎn)生電路,有四個震蕩源,分別是內(nèi)部的8MHZ高速RC振蕩器,外部的4-16MHZ高速石英晶體振蕩器,即晶振,一般是8MHZ,外部的32.768KHZ低速晶振,一般是給RTC提供時鐘的,最后是內(nèi)部的40KHZ低速RC振蕩器,這個可以給看門狗提供時鐘。
高速晶振是用來提供系統(tǒng)時鐘,如提供給AHB APB2 APB1總線
外部的石英振蕩器比內(nèi)部的RC振蕩器更加穩(wěn)定,因此一般用外部晶振,如果系統(tǒng)很簡單,不需要精確的時鐘,可選擇內(nèi)部時鐘
ST配置流程
在SystemInit函數(shù)里,ST配置時鐘,首先它會啟動內(nèi)部時鐘,選擇內(nèi)部8MHZ為系統(tǒng)時鐘,暫時以內(nèi)部8MHZ的時鐘運行,然后再啟動外部時鐘,進入PLL鎖相環(huán)進行倍頻,8MHZ倍頻9倍,得到72MHZ,等到鎖相環(huán)輸出穩(wěn)定后,選擇鎖相環(huán)輸出為系統(tǒng)時鐘,這樣就把系統(tǒng)時鐘由8MHZ切換到72MHZ。
CSS:安全保障
無論高級、通用還是基本定時器,它們的內(nèi)部基準時鐘都是72MHZ
控制位“外部時鐘使能”–>RCC_APB2/1PeriphClockCmd
打開時鐘,就是在這個位置寫1,讓左邊的時鐘能夠通過與門輸出給外設。
代碼部分
程序現(xiàn)象
1、使用定時中斷的功能,定時器使用內(nèi)部時鐘定了1秒的時間,每隔1秒申請一下中斷,然后在中斷函數(shù)里執(zhí)行Num++,最后在OLED上顯示Num
2、定時器外部時鐘,使用外部時鐘驅(qū)動定時器,在定時器指定的外部引腳上,輸入一個方波信號,來提供定時器計數(shù)的時鐘,目前用對射式紅外傳感器來手動模擬一個外部時鐘,用擋光片一次遮擋、移開、遮擋、移開提供一個方波。OLED上的CNT就是定時器中計數(shù)器的值,每遮擋移開一次,計數(shù)器加1,然后計數(shù)器計到9后自動清零。同時申請中斷,執(zhí)行Num++。
定時中斷
接線圖
步驟
第一步:RCC開啟時鐘,定時器的基準時鐘和整個外設的工作時鐘就都會同時打開
第二步: 選擇時基單元的時鐘源,對于定時中斷,我們選擇內(nèi)部時鐘源
第三步:配置時基單元,包括預分頻器、自動重裝器、計數(shù)模式等等
第四步:配置輸出中斷控制,允許更新中斷輸出到NVIC
第五步:配置NVIC,在NVIC中打開定時器中斷的通道,并分配一個優(yōu)先級
第六步:運行控制
第七步:使能計數(shù)器
最后:當計數(shù)器更新時,觸發(fā)中斷,寫一個定時器中斷函數(shù)
定時1s,即定時頻率為1HZ
相當于1 = 72MHz/(PSC + 1) /(ARR + 1),PSC=7200,ARR=10000
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //PSC的值,對72M進行7200分頻,得到10K的計數(shù)頻率,在10K的頻率下,計算10000個數(shù),就是1s的時間。
在當前工程搜索中斷源
在啟動文件找定時器2的中斷服務函數(shù)
Timer.c
#include "stm32f10x.h"
#include "Timer.h"
//如果你想跨文件使用變量,可以在使用變量的那個文件的上面,用extern聲明一下要用的變量
extern uint16_t Num;
//初始化定時器
void Timer_Init(void)
{
//開啟RCC內(nèi)部時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//選擇時基單元的時鐘(定時器上電后默認就是使用內(nèi)部時鐘)
TIM_InternalClockConfig(TIM2);
//配置時基單元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重復計數(shù)器的值
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
//本質(zhì):在調(diào)用中斷前,中斷狀態(tài)寄存器不能有標志位
//避免剛一上電就立刻進入中斷,在TimeBaseInit的后面和中斷的前面(手動清除中斷標志位)
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//使能更新中斷,開啟了更新中斷到NVIC的通路
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //TIM_IT_Update Update更新中斷
//NVIC配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn ; //定時器2在NVIC的通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//最后NVIC_Init
NVIC_Init(&NVIC_InitStructure);
//啟動定時器,此時定時器開始工作,當產(chǎn)生更新時,就會觸發(fā)中斷
TIM_Cmd(TIM2, ENABLE);
}
//當定時器產(chǎn)生更新中斷時,這個函數(shù)就會自動被執(zhí)行
void TIM2_IRQHandler(void)
{
//檢查中斷標志位,TIM_FLAG_Update為想看的中斷標志位
if(TIM_GetFlagStatus(TIM2, TIM_FLAG_Update)== SET)
{
//清除相應的標志位
TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);
Num++;
}
}
Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif /*__TIMER_H*/
main.c
在main函數(shù)里,中斷函數(shù)每秒自動把Num++,然后在主循環(huán)里顯示。
預分頻值和自動重裝值對中斷頻率的影響
如果把自動重裝值改為1000,那么由原來計10000個數(shù)變成了1000個數(shù)
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
//定義全局變量Num,Num要在中斷函數(shù)里執(zhí)行++
uint16_t Num;
int main(void)
{
OLED_Init();
//初始化定時器,此時定時器開始工作
Timer_Init();
OLED_ShowChar(1, 1, 'A');
OLED_ShowString(1, 3, "HelloWorld!");
while (1)
{
OLED_ShowNum(1,5,Num,5);
}
}
外部時鐘
接線圖
對射式紅外傳感器,DO數(shù)字輸出接到PA0引腳,這個PA0引腳就是TIM2的ETR引腳,我們在這個引腳輸入一個外部時鐘。
濾波器:以一個采樣頻率f采樣N個點,如果N個點都一樣,才會輸出有效。
引腳需要用到GPIO,在配置時基單元之前還要先配置GPIO
上拉電阻防止跳動
基本任務還是定時中斷,但我們不選用內(nèi)部時鐘,我們使用ETR外部時鐘模式2
TIM_ETRClockModelConfig通過ETR引腳的外部時鐘模式2配置文章來源:http://www.zghlxwxcb.cn/news/detail-654434.html
什么時候需要用到浮空輸入呢?
如果外部的輸入信號功率很小,內(nèi)部的上拉電阻可能會影響到這個輸入信號,這時就可以用浮空輸入,放置影響外部輸入的電平。文章來源地址http://www.zghlxwxcb.cn/news/detail-654434.html
Timer.c
#include "stm32f10x.h"
#include "Timer.h"
//如果你想跨文件使用變量,可以在使用變量的那個文件的上面,用extern聲明一下要用的變量
extern uint16_t Num;
//初始化定時器
void Timer_Init(void)
{
//開啟RCC內(nèi)部時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
/* 初始化GPIO*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//引腳要用到GPIO,因此在配置外部時鐘模式2前要先配置GPIO
//選擇時基單元的時鐘,不使用內(nèi)部時鐘源,通過ETR引腳的外部時鐘模式2配置,不需要分頻,不用濾波器
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0X00);
//配置時基單元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重復計數(shù)器的值
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
//本質(zhì):在調(diào)用中斷前,中斷狀態(tài)寄存器不能有標志位
//避免剛一上電就立刻進入中斷,在TimeBaseInit的后面和中斷的前面(手動清除中斷標志位)
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//使能更新中斷,開啟了更新中斷到NVIC的通路
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //TIM_IT_Update Update更新中斷
//NVIC配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn ; //定時器2在NVIC的通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//最后NVIC_Init
NVIC_Init(&NVIC_InitStructure);
//啟動定時器,此時定時器開始工作,當產(chǎn)生更新時,就會觸發(fā)中斷
TIM_Cmd(TIM2, ENABLE);
}
//實時查看CNT的計數(shù)器的值
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
//當定時器產(chǎn)生更新中斷時,這個函數(shù)就會自動被執(zhí)行
void TIM2_IRQHandler(void)
{
//檢查中斷標志位,TIM_FLAG_Update為想看的中斷標志位
if(TIM_GetFlagStatus(TIM2, TIM_FLAG_Update)== SET)
{
//清除相應的標志位
TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);
Num++;
}
}
Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
uint16_t Timer_GetCounter(void); //獲取計數(shù)值
#endif /*__TIMER_H*/
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
//初始化定時器,此時定時器開始工作
Timer_Init();
OLED_ShowString(1, 1, "Num:");
OLED_ShowString(2, 1, "CNT:");
while (1)
{
OLED_ShowNum(1,5,Num,5);
OLED_ShowNum(1,5,Timer_GetCounter(),5);
}
}
到了這里,關(guān)于【嵌入式學習-STM32F103-TIM-定時中斷和外部時鐘】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!