本內容基于江協科技STM32視頻內容,整理而得。
文章目錄
- 1. TIM
- 1.1 TIM定時器
- 1.2 定時器類型
- 1.3 基本定時器
- 1.4 通用定時器
- 1.4 高級定時器
- 1.5 定時中斷基本結構
- 1.6 預分頻器時序
- 1.7 計數器時序
- 1.8 計數器無預裝時序
- 1.9 計數器有預裝時序
- 1.10 RCC時鐘樹
- 2. TIM庫函數及代碼
- 2.1 TIM庫函數
- 2.2 6-1定時器中斷代碼
- 2.2.1 硬件電路:
- 2.2.2 代碼流程
- 2.2.3 代碼
- 2.3 6-2定時器外部時鐘代碼
- 2.3.1 硬件電路
- 2.3.2 代碼流程
- 2.3.3 代碼
1. TIM
1.1 TIM定時器
- 定時器可以對輸入的時鐘進行計數,并在計數值達到設定值時觸發中斷
- 16位計數器、預分頻器、自動重裝寄存器的時基單元,在72MHz計數時鐘下可以實現最大59.65s的定時。1/(72/65536/65536)(計數器就是用來執行計數定時的一個寄存器,每來一個時鐘,計數器加1。預分頻器可以對計數器的時鐘進行分頻,讓計數更加靈活。自動重裝寄存器就是計數的目標值,就是想要計多少個時鐘申請中斷。這些寄存器構成了定時器最核心的部分,把這一塊電路稱為時基單元)
- 不僅具備基本的定時中斷功能,而且還包含內外時鐘源選擇、輸入捕獲、輸出比較、編碼器接口、主從觸發模式等多種功能。
- 根據復雜度和應用場景分為高級定時器、通用定時器、基本定時器三種類型。
1.2 定時器類型
類型 | 編號 | 總線 | 功能 |
---|---|---|---|
高級定時器 | TIM1、TIM8 | APB2 | 擁有通用定時器全部功能,并額外具有重復計數器、死區生成、互補輸出、剎車輸入等功能 |
通用定時器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 擁有基本定時器全部功能,并額外具有內外時鐘源選擇、輸入捕獲、輸出比較、編碼器接口、主從觸發模式等功能 |
基本定時器 | TIM6、TIM7 | APB1 | 擁有定時中斷、主模式觸發DAC的功能 |
- STM32F103C8T6定時器資源:TIM1、TIM2、TIM3、TIM4
- DAC:數模轉換器
1.3 基本定時器
主要功能:
- 16位自動重裝載累加計數器
- 16位可編程預分頻器,用于對輸入的時鐘按系數為1~65536之間的任意數值分頻
- 觸發DAC的同步電路
- 在更新事件(計數器溢出)時產生中斷/DMA請求
-
預分頻器PSC
預分頻器之前連接的就是基準計數時鐘的輸入。由于基本定時器只能選擇內部時鐘,因此可以認為預分頻器的輸入線是連接到內部時鐘(CK_INT)。
內部時鐘的來源是RCC的TIMxCLK,這里的頻率值一般都是系統的主頻72MHz。
預分頻器寫0,就是不分頻;寫1就是2分頻,輸出頻率=輸入頻率/2=36MHz。
寫2就是3分頻,輸出= 輸入/3。所以預分頻器的值和實際的分頻系數相差了1,實際分頻系數=預分頻器的值 + 1。預分頻器是16位的,所以最大值可以寫65535,也就是65536分頻。 -
計數器CNT
計數器可以對預分頻后的計數時鐘進行計數,計數時鐘每來一個上升沿,計數器的值就加1。計數器也是16位的,所以里面的值可以從0一直加到65535。如果再加的話,計數器就會回到0重新開始。所以計數器的值在計數過程中會不斷自增運行,當自增運行到目標值時,會產生中斷,那就完成了定時的任務。所以還需要一個存儲目標值的寄存器,那就是自動重裝寄存器了。 -
自動重裝寄存器
自動重裝寄存器也是16位的,它存的就是要寫入的計數目標。在運行的過程中,計數值不斷自增,自動重裝值是固定的目標,當計數值等于自動重裝值時,也就是計時時間到了。那它就會產生一個中斷信號,并且清零計數器,計數器自動開始下一次的計數計時。 -
U和UI箭頭
圖上畫的一個向上的折現箭頭,就代表這里會產生中斷信號,像這種計數值等于自動重裝值產生的中斷,一般把它叫做“更新中斷”。這個更新中斷之后就會通往NVIC,我們再配置好NVIC的定時器通道,那定時器的更新中斷就能夠得到CPU的響應了。向下的箭頭,代表的是會產生一個事件,這里對應的事件就叫做“更新事件”。更新事件不會觸發中斷,但可以觸發內部其他電路的工作。
1.4 通用定時器
主要功能:
-
16位向上、向下、向上/向上 自動重裝載累加計數器
-
16位可編程預分頻器,用于對輸入的時鐘按系數為1~65536之間的任意數值分頻
-
4個獨立通道:
- 輸入捕獲
- 輸出比較
- PWM生成(邊緣或中間對齊模式)
- 單脈沖模式輸出
-
使用外部信號控制定時器和定時器互連的同步電路
-
如下事件發生時產生中斷/DMA:
- 更新:計數器向上溢出/向下溢出,計數器初始化(通過軟件或內部/外部觸發)
- 觸發事件(計數器啟動、停止、初始化或者由內部/外部觸發計數)
- 輸入捕獲
- 輸出比較
-
支持針對定位的增量(正交)編碼器和霍爾傳感器電路
-
觸發輸入作為外部時鐘或者按周期的電流管理
-
CNT計數器
CNT計數器支持向上計數模式、向下計數模式、中央對齊模式。向下計數模式就是從重裝值開始,向下自減,減到0之后,回到重裝值同時申請中斷。中央對齊模式是從0開始,先向上自增,計到重裝值,申請中斷,然后再向下自減,減到0,再申請中斷。 -
時鐘源
通用定時器的時鐘源不僅可以選擇內部72MHz時鐘,還可以選擇外部時鐘。
(1)第一個外部時鐘就是來自TIMx_ETR引腳上的外部時鐘,也就是可以在TIM2的ETR引腳即PA0上接一個外部方波時鐘,然后配置一下內部的極性選擇、邊沿檢測和預分頻器電路,以及輸入濾波電路,這兩塊電路可以對外部時鐘進行一定的整形。因為是外部引腳的時鐘,所以難免有一些毛刺,那這些電路就可以對輸入的波形進行濾波。濾波后的信號,兵分兩路,上面一路ETRF進入觸發控制器,緊跟著就可以選擇作為時基單元的時鐘了。這一路也叫做“外部時鐘模式2”。
(2)TRGI也可以提供外部時鐘,主要用作觸發輸入使用的,這個觸發輸入可以觸發定時器的從模式。當TRGI當作外部時鐘來使用時,這一路就叫做“外部時鐘模式1”。通過這一路的外部時鐘有哪些呢?第一個就是ETR引腳的信號。第二個是ITR信號,這一部分的時鐘信號是來自其他定時器的。主模式的TRGO可以通向其他定時器,通向其他定時器的時候,就接到了其他定時器的ITR引腳上來了,ITR0到ITR3分別來自其他4個定時器的TRGO輸出。也可以選擇TI1F_ED,這里連接的是輸入捕獲單元的CH1引腳,也就是從CH1引腳獲得時鐘,這里后綴加一個ED(Edge)就是邊沿的意思。還可以通過TI1FP1和TI2FP2獲得,TI1FP1是連接到了CH1引腳的時鐘,TI2FP2連接到了CH2引腳的時鐘。
總結:外部時鐘模式1的輸入可以是ETR引腳、其他定時器、CH1引腳的邊沿、CH1引腳和CH2引腳。 -
輸出比較電路
下面的右邊部分是輸出比較電路,總共有四個通道,分別對應CH1到CH4的引腳,可以用于輸出PWM波形,驅動電機。 -
輸入捕獲電路
左邊是輸入捕獲電路,也是有四個通道,對應的也是CH1到CH4的引腳,可以用于測輸入方波的頻率等。 -
捕獲/比較寄存器
中間的寄存器是捕獲/比較寄存器,是輸入捕獲和輸出比較電路共用的,因為輸入捕獲和輸出比較不能同時使用,所以這里寄存器是共用的,引腳也是共用的。
1.4 高級定時器
主要功能:
- 16位向上、向下、向上/向上 自動重裝載累加計數器
- 16位可編程預分頻器,用于對輸入的時鐘按系數為1~65536之間的任意數值分頻
- 4個獨立通道:
- 輸入捕獲
- 輸出比較
- PWM生成(邊緣或中間對齊模式)
- 單脈沖模式輸出
- 死區時間可編程的互補輸出
- 使用外部信號控制定時器和定時器互連的同步電路
- 允許在指定數據的計數器周期之后更新定時器寄存器的重復計數器
- 剎車輸入信號可以將定時器輸出信號置于復位狀態或者一個已知狀態
- 如下事件發生時產生中斷/DMA:
- 更新:計數器向上溢出/向下溢出,計數器初始化(通過軟件或內部/外部觸發)
- 觸發事件(計數器啟動、停止、初始化或者由內部/外部觸發計數)
- 輸入捕獲
- 輸出比較
- 剎車信號輸入
- 支持針對定位的增量(正交)編碼器和霍爾傳感器電路
- 觸發輸入作為外部時鐘或者按周期的電流管理
-
重復次數計數器
和通用定時器的區別:第一個是在申請中斷的地方增加了一個重復次數計數器,有了這個計數器之后,就可以實現每隔幾個計數周期才發生一次更新事件和更新中斷。原來的結構是每個計數周期完成后就都會發生更新,這就相當于對輸出的更新信號又做了一次分頻。 -
DTG死區生成器和互補輸出
下面是高級定時器對輸出比較模塊的升級了,DTG(dead time generate)是死區生成電路。右邊的輸出引腳由原來的一個變為了兩個互補的輸出,可以輸出一對互補的PWM波,這些電路是為了驅動三相無刷電機的,比如四軸飛行器、電動車的后輪、電鉆等,里面都可能是三相無刷電機。因為三相無刷電機的驅動電路一般需要3個橋臂,每個橋臂2個大功率開關管來控制,所以總共需要6個大功率開關管。因此這里的輸出PWM引腳的前三路就變為了互補的輸出。另外,為了防止互補輸出的PWM驅動橋臂時,在開關切換的瞬間,由于器件的不理想,造成短暫的直通現象,所以前面就加上了死區生成電路。在開關切換的瞬間,產生一定時長的死區,讓橋臂的上下管全都關斷,防止直通現象。 -
剎車輸入
最后一部分就是剎車輸入的功能了,這個是為了給電機驅動提供安全保障的。如果外部引腳BKIN(Break IN)產生了剎車信號,或內部時鐘失效,產生了故障,那么控制電路就會自動切斷電機的輸出,防止意外的發生。
1.5 定時中斷基本結構
運行控制:控制寄存器中的一些位,如啟動停止、向上或向下計數等。
右邊就是計時時間到,產生更新中斷后的信號去向,如果是高級定時器的話,還會多一個重復計數器。中斷信號會先在狀態寄存器里置一個中斷標志位,這個標志位會通過中斷輸出控制,到NVIC申請中斷。中斷輸出控制就是一個中斷輸出的允許位,如果需要某個中斷,就記得允許一下。
1.6 預分頻器時序
- 計數器計數頻率:CK_CNT = CK_PSC / (PSC + 1)
- CK_PSC:預分頻器時鐘,內部時鐘就是72MHz。
- CNT_EN:計數器使能,高電平計數器正常運行,低電平計數器停止。
- CK_CNT:計數器時鐘,它既是預分頻器的時鐘輸出,也是計數器的時鐘輸入。
- 在開始時,計數器未使能,計數器時鐘不運行。使能后,前半段,實際分頻系數為1(PSC=0),計數器的時鐘等于預分頻器前的時鐘;后半段,實際分頻系數為2(PSC=1)了,計數器的時鐘也就變為預分頻器前時鐘的一半了。在計數器時鐘的驅動下,下面的計數器寄存器也跟隨時鐘的上升沿不斷自增,在中間的這個位置FC之后,計數值變為0了,從這里可以推斷出ARR自動重裝值就是FC。當計數值計到和重裝值相等,并且下一個時鐘來臨時,計數值才清零,同時下面產生一個更新事件,這就是一個計數周期的工作流程。
- 下面的三行描述的是預分頻寄存器的一種緩沖機制,也就是這個預分頻寄存器實際上是有兩個,一個是預分頻控制寄存器,是供我們讀寫用的,它并不直接決定分頻系數。另外還有一個緩沖寄存器或者說是影子寄存器:預分頻緩沖器,這個緩沖寄存器才是真正起作用的寄存器,比如我們在某個時刻,把預分頻寄存器由0改成了1,如果在此時立刻改變時鐘的分頻系數,那么就會導致在一個計數周期內,前半部分和后半部分的頻率不一樣。因此設計了緩沖器,當計數計到一半的時候改變了分頻值,這個變化并不會立刻生效,而是會等到本次計數周期結束時,產生了更新事件,預分頻寄存器的值才會被傳遞到緩沖寄存器里,才會生效。
- 最后一行可以看出:預分頻器內部也是靠計數來分頻的,當預分頻值為0時,計數器就一直為0,直接輸出原頻率;當預分頻值為1時,計數器就0、1、0、1、0、1這樣計數,在回到0時,輸出1個脈沖,這樣輸出頻率就是輸入頻率的2分頻,預分頻器的值和實際的分頻系數之間有一個數的偏移。
1.7 計數器時序
- 計數器溢出頻率:CK_CNT_OV = CK_CNT / (ARR + 1)= CK_PSC / (PSC + 1) / (ARR + 1)
- CK_INT:內部時鐘72MHz;
- CNT_EN:計數器使能,上升沿有效;
- CK_CNT:計數器時鐘,因為分頻系數為2,所以這個頻率是CK_INT除2。然后計數器在這個時鐘每個上升沿自增,當增到0036的時候,發生溢出。計到36之后,再來一個上升沿,計數器清零,計數器溢出,產生一個更新事件脈沖,另外還會置一個更新中斷標志(UIF),更新中斷標志(UIF)置1了,就會申請中斷,中斷響應后,需要在中斷程序中手動清零。
1.8 計數器無預裝時序
更改了自動加載寄存器,由FF改成了36,那計數值的目標值就由FF變成了36,所以這里計到36之后,就直接更新開始下一輪的計數,
1.9 計數器有預裝時序
在計數的中途,把計數目標值由F5改成了36。下面有個影子寄存器,這個影子寄存器才是真正起作用的,它還是F5,所以現在計數的目標還是計到F5,產生更新事件,同時,要更改的36才被傳遞到影子寄存器,在下一個計數周期這個更改的36才有效,所以這個引入影子寄存器的目的實際上是為了同步,就是讓值的變化和更新事件同步發生,防止在運行途中,更改造成錯誤。
1.10 RCC時鐘樹
-
時鐘源
在時鐘產生電路,有四個震蕩源:
(1)HSI:內部的8MHz高速RC振蕩器;
(2)HSE:外部的4~16MHz高速石英晶體振蕩器,也就是晶振,一般都是接8MHz;
(3)LSE:外部的32.768KHz低速晶振,這個一般是給RTC提供時鐘的;
(4)LSI:最后是內部的40KHz低速RC振蕩器,這個可以給看門狗提供時鐘。
上面兩個高速晶振是用提供系統時鐘的,AHB、APB2、APB1的時鐘都是來源于這兩個高速晶振,只不過是外部的石英振蕩器比內部的RC振蕩器更加穩定,所以一般都是用外部晶振。 -
ST配置時鐘
在SystemInit函數里,ST配置時鐘:首先會啟動內部時鐘HSI,選擇內部8MHz為系統時鐘,暫時以內部8MHz的時鐘運行。然后再啟動外部時鐘,配置外部時鐘進入PLL鎖相環進行倍頻,8MHz倍頻9倍,就得到72MHz,等到鎖相環輸出穩定后,選擇鎖相環輸出為系統時鐘,這樣就把系統時鐘由8MHz切換為了72MHz。 -
CSS時鐘安全系統
CSS(clock security system):時鐘安全系統,也是負責切換時鐘的,可以監測外部時鐘的運行狀態,一旦外部時鐘失效,就會自動把外部時鐘切換回內部時鐘,保證系統時鐘的運行,防止程序卡死造成事故。 -
時鐘分配電路
- AHB總線: 首先系統時鐘72MHz進入AHB總線,AHB總線有個預分頻器,在SystemInit里配置的分配系數為1,那AHB的時鐘就是72MHz。
- APB1總線:這里配置的分配系數為2,所以APB1總線的時鐘為72MHz/2=36MHz。下面的如果APB1預分頻系數=1,則頻率不變,否則頻率
*2
,然后右邊,是單獨為定時器2-7開通的,因為這里預分頻系數,我們給的是2,所以這里頻率要再*2
,所以通向定時器2~7的時鐘為72MHz。因此無論是高級定時器、通用定時器還是基本定時器,它們的內部基準時鐘都是72MHz。 - APB2總線:APB2的分頻系數為1,所以時鐘為72MHz。然后接在APB2上的時鐘也單開了一路,即如果APB2預分頻系數=1,則頻率不變,否則頻率
*2
。因為分頻系數給的是1,所以定時器1和8的時鐘就是72MHz。 - 時鐘輸出部分都有一個與門進行輸出控制,控制位寫的是外部時鐘使能,這就是我們再在程序中寫RCC_APB2/1PeriphClockCmd作用的地方,打開時鐘,就是在這個位置寫1,讓左邊的時鐘能夠通過與門輸出給外設。
2. TIM庫函數及代碼
2.1 TIM庫函數
// 恢復缺省配置
void TIM_DeInit(TIM_TypeDef* TIMx);// 時基單元初始化
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);// 結構體變量賦一個默認值
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);// 使能計數器---運行控制
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);// 使能中斷輸出---中斷輸出控制
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);/*-----------------時基單元的時鐘選擇---------*/
// 選擇內部時鐘
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);// 選擇ITRx其他定時器的時鐘
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);// 選擇TIx捕獲通道的時鐘
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,uint16_t TIM_ICPolarity, uint16_t ICFilter);// 選擇ETR通過外部時鐘模式1輸入的時鐘
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);// 選擇ETR通過外部時鐘模式2輸入的時鐘
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);/*--------------------------------------------------------*/
// 不是用來選擇時鐘的,單獨用來配置ETR引腳的預分頻器、極性、濾波參數的
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);// 用來單獨寫預分頻值的
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);// 用來改變計數器的計數模式
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);// 自動重裝器預裝功能配置
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);// 給計數器寫入一個值
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);// 給自動重裝器寫入一個值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);// 獲取當前計數器的值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);// 獲取當前的預分頻器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);/*-------------獲取標志位和清除標志位的------------*/
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
2.2 6-1定時器中斷代碼
2.2.1 硬件電路:
實現功能:定時1s,并在OLED上顯示Num值(中斷觸發次數,也即定時時間)
2.2.2 代碼流程
-
定時器代碼
- 開啟時鐘RCC,定時器的基準時鐘和整個外設的工作時鐘都會同時打開了;
- 選擇時基單元的時鐘源,對于定時中斷,選擇內部時鐘源;
- 配置時基單元,包括預分頻器、自動重裝器、計數模式等–用結構體;
- 配置中斷輸出控制,允許更新中斷輸出到NVIC;
- 配置NVIC,在NVIC中打開定時器中斷的通道,并分配一個優先級;
- 運行控制;
整個模塊配置完成后,還需要使能一下計數器,要不然計數器是不會運行的。當定時器使能后,計數器就開始計數了,當計數器更新時,觸發中斷,最后再寫一個定時器的中斷函數,這樣這個中斷函數就每隔一段時間就能自動執行一次了。
-
中斷函數
- 判斷是否進入定時器TIM2的中斷,然后清除中斷標志位。
- 使定時器每秒自動加一下Num變量
-
ARR和PSC設置
- ARR=10000 - 1;PSC = 7200 - 1;
- 定時器時鐘CK_CNT =72M / (PSC + 1) = 10000 ;
- 定時頻率 = CK_CNT / (ARR + 1) = 1。定時1s,也就是定時頻率為1Hz。
預分頻是對72M進行7200分頻,得到的就是10K的計數頻率,在10K的頻率下,計10000個數,就是1s的時間。(在1s的時間內計10000個數,當計到10000個數后,自動清0,同時申請中斷,在OLED顯示屏上就是顯示Num值每1s加1)。也可以更改ARR和PSC的值。
- main函數
- OLED顯示Num值。
2.2.3 代碼
- Timer.c代碼
#include "stm32f10x.h" // Device header/*** 函 數:定時中斷初始化* 參 數:無* 返 回 值:無*/
void Timer_Init(void)
{/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //開啟TIM2的時鐘/*配置時鐘源*/TIM_InternalClockConfig(TIM2); //選擇TIM2為內部時鐘,若不調用此函數,TIM默認也為內部時鐘/*時基單元初始化*/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; //重復計數器,高級定時器才會用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //將結構體變量交給TIM_TimeBaseInit,配置TIM2的時基單元 /*中斷輸出配置*/TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定時器更新標志位//TIM_TimeBaseInit函數末尾,手動產生了更新事件//若不清除此標志位,則開啟中斷后,會立刻進入一次中斷//如果不介意此問題,則不清除此標志位也可TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //開啟TIM2的更新中斷/*NVIC中斷分組*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC為分組2//即搶占優先級范圍:0~3,響應優先級范圍:0~3//此分組配置在整個工程中僅需調用一次//若有多個中斷,可以把此代碼放在main函數內,while循環之前//若調用多次配置分組的代碼,則后執行的配置會覆蓋先執行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure; //定義結構體變量NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //選擇配置NVIC的TIM2線NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC線路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC線路的搶占優先級為2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC線路的響應優先級為1NVIC_Init(&NVIC_InitStructure); //將結構體變量交給NVIC_Init,配置NVIC外設/*TIM使能*/TIM_Cmd(TIM2, ENABLE); //使能TIM2,定時器開始運行
}
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"uint16_t Num; //定義在定時器中斷里自增的變量int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化Timer_Init(); //定時中斷初始化/*顯示靜態字符串*/OLED_ShowString(1, 1, "Num:"); //1行1列顯示字符串Num:while (1){OLED_ShowNum(1, 5, Num, 5); //不斷刷新顯示Num變量}
}/*** 函 數:TIM2中斷函數* 參 數:無* 返 回 值:無* 注意事項:此函數為中斷函數,無需調用,中斷觸發后自動執行* 函數名為預留的指定名稱,可以從啟動文件復制* 請確保函數名正確,不能有任何差異,否則中斷函數將不能進入*/
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判斷是否是TIM2的更新事件觸發的中斷{Num ++; //Num變量自增,用于測試定時中斷TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中斷標志位//中斷標志位必須清除//否則中斷將連續不斷地觸發,導致主程序卡死}
}
2.3 6-2定時器外部時鐘代碼
2.3.1 硬件電路
- 將對射式紅外傳感器接在PA0引腳,即TIM2_CH1_ETR,時鐘配置為外部時鐘模式2。
- 每次對傳感器進行遮擋時,計數值CNT(TIM_GetCounter(TIM2))加1,當加到ARR時,觸發中斷,使Num值加1。
- 在OLED顯示屏上顯示Num和CNT的值。
2.3.2 代碼流程
- 定時器代碼
- 開啟時鐘RCC,TIM2和GPIOA。
- 配置GPIO,為上拉輸入。
- 選擇時基單元的時鐘源,外部時鐘模式2,時鐘從TIM2_ETR引腳輸入;
- 配置時基單元,包括預分頻器、自動重裝器、計數模式等–用結構體;
- 配置中斷輸出控制,允許更新中斷輸出到NVIC;
- 配置NVIC,在NVIC中打開定時器中斷的通道,并分配一個優先級;
- 運行控制;
- ARR和PSC
- ARR = 10 - 1; PSC = 1 - 1;
- 因為沒有進行分頻,所以對射式紅外傳感器每遮擋一次,計數值CNT加1,當計數值加到9后,自動清零,同時申請中斷,Num++。
- main函數
- 實現在OLED顯示屏上顯示Num和CNT的值。
2.3.3 代碼
- Timer.c
#include "stm32f10x.h" // Device header/*** 函 數:定時中斷初始化* 參 數:無* 返 回 值:無* 注意事項:此函數配置為外部時鐘,定時器相當于計數器*/
void Timer_Init(void)
{/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //開啟TIM2的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //開啟GPIOA的時鐘/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //將PA0引腳初始化為上拉輸入/*外部時鐘配置*/TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);//選擇外部時鐘模式2,時鐘從TIM_ETR引腳輸入//注意TIM2的ETR引腳固定為PA0,無法隨意更改//最后一個濾波器參數加到最大0x0F,可濾除時鐘信號抖動/*時基單元初始化*/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; //重復計數器,高級定時器才會用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //將結構體變量交給TIM_TimeBaseInit,配置TIM2的時基單元 /*中斷輸出配置*/TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定時器更新標志位//TIM_TimeBaseInit函數末尾,手動產生了更新事件//若不清除此標志位,則開啟中斷后,會立刻進入一次中斷//如果不介意此問題,則不清除此標志位也可TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //開啟TIM2的更新中斷/*NVIC中斷分組*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC為分組2//即搶占優先級范圍:0~3,響應優先級范圍:0~3//此分組配置在整個工程中僅需調用一次//若有多個中斷,可以把此代碼放在main函數內,while循環之前//若調用多次配置分組的代碼,則后執行的配置會覆蓋先執行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure; //定義結構體變量NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //選擇配置NVIC的TIM2線NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC線路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC線路的搶占優先級為2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC線路的響應優先級為1NVIC_Init(&NVIC_InitStructure); //將結構體變量交給NVIC_Init,配置NVIC外設/*TIM使能*/TIM_Cmd(TIM2, ENABLE); //使能TIM2,定時器開始運行
}/*** 函 數:返回定時器CNT的值* 參 數:無* 返 回 值:定時器CNT的值,范圍:0~65535*/
uint16_t Timer_GetCounter(void)
{return TIM_GetCounter(TIM2); //返回定時器TIM2的CNT
}
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"uint16_t Num; //定義在定時器中斷里自增的變量int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化Timer_Init(); //定時中斷初始化/*顯示靜態字符串*/OLED_ShowString(1, 1, "Num:"); //1行1列顯示字符串Num:OLED_ShowString(2, 1, "CNT:"); //2行1列顯示字符串CNT:while (1){OLED_ShowNum(1, 5, Num, 5); //不斷刷新顯示Num變量OLED_ShowNum(2, 5, Timer_GetCounter(), 5); //不斷刷新顯示CNT的值}
}/*** 函 數:TIM2中斷函數* 參 數:無* 返 回 值:無* 注意事項:此函數為中斷函數,無需調用,中斷觸發后自動執行* 函數名為預留的指定名稱,可以從啟動文件復制* 請確保函數名正確,不能有任何差異,否則中斷函數將不能進入*/
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判斷是否是TIM2的更新事件觸發的中斷{Num ++; //Num變量自增,用于測試定時中斷TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中斷標志位//中斷標志位必須清除//否則中斷將連續不斷地觸發,導致主程序卡死}
}