STM32的定時器知識相當(dāng)復(fù)雜,這里列舉一些基礎(chǔ)知識,在之后的文章我會寫一下它的各種應(yīng)用。
通用定時器是一個通過可編程預(yù)分頻器驅(qū)動的16位自動裝載計數(shù)器構(gòu)成。它適用于多種場合,包括定時中斷、測量輸入信號的脈沖長度(輸入捕獲)或者產(chǎn)生輸出波形(輸出比較和PWM)。每個定時器都是完全獨立的,沒有互相共享任何資源,它們可以一起同步操作。
STM32F103RC系列有4個通用定時器,2個高級定時器和兩個基本定時器:
我們最常使用通用定時器,功能包括:
1、16位向上 、向下、向上/向下自動裝載計數(shù)器(TIMx_CNT),例如向上是指:計數(shù)器從0計數(shù)到自動加載值(TIMx_ARR),然后重新從0開始計數(shù)并且產(chǎn)生一個計數(shù)器溢出事件。
2、16 位可編程(可以實時修改)預(yù)分頻器(TIMx_PSC),計數(shù)器時鐘頻率的分頻系數(shù) 為 1~65535 之間的任意數(shù)值。
3、4 個獨立通道(TIMx_CH1~4)。
4、支持多種中斷,如溢出中斷等。
這里我們講述使用通用定時器中斷實驗:
一、定時器時鐘選擇
我們常使用內(nèi)部時鐘(CK_INT),通過配置TIMx_SMCR的SMS[2:0],配置為000。該時鐘是ABP1時鐘的的1倍(APB1不分頻)或2倍(APB1分頻)。
二、計數(shù)器模式
可選擇向上 、向下或者向上/向下。
三、定時時間計算
溢出時間 = ( 自動加載值(ARR)+ 1 )( 預(yù)分頻系數(shù)(PSC)+ 1 ) / 定時器時鐘(Tclk)
四、庫函數(shù)配置
1、使能能定時器時鐘。
2、初始化定時器,配置ARR,PSC(在stm32f10x_tim.c)
TIM_TimeBaseInit();
其中的結(jié)構(gòu)體:typedef struct
{
uint16_t TIM_Prescaler; //設(shè)置分頻系數(shù)
uint16_t TIM_CounterMode; //計數(shù)方式
uint16_t TIM_Period; //自動重裝載值
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
3、開啟定時器中斷,配置中斷優(yōu)先級分組NVIC。
void TIM_ITConfig();
NVIC_Init();
4、 使能定時器。
TIM_Cmd();
5、編寫中斷服務(wù)函數(shù)。
TIMx_IRQHandler();
stm32定時器誤區(qū)
在用到STM32定時器的更新中斷時,發(fā)現(xiàn)有些情形下只要開啟定時器就立即進入一次中斷。準(zhǔn)確說,只要使能更新中斷允許位就立即響應(yīng)一次更新中斷【當(dāng)然前提是相關(guān)NVIC也已經(jīng)配置好】。換言之,只要使能了相關(guān)定時器更新中斷,不管你定時間隔多長甚至不在乎你是否啟動了相關(guān)定時器,它都會立即進入一次定時器更新中斷服務(wù)程序。
以STM32F051芯片為例,做了幾種不同順序的組合測試。根據(jù)測試發(fā)現(xiàn),的確有些情況下一運行TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); 【即使能更新中斷】就立即進入更新中斷服務(wù)程序。當(dāng)然后面的中斷都是正常的。
老實說,這個問題比較容易忽視,有些情況下也無關(guān)緊要,但有些情況可能會給應(yīng)用帶來困擾。從ST MCU相關(guān)技術(shù)手冊似乎并不能明顯地找到關(guān)于這個問題的很合適或者邏輯性很強的前因后果。
經(jīng)過驗證測試,如果注意一下相關(guān)指令代碼順序是可以回避這個問題的。
先做更新中斷標(biāo)志的清除操作,即清除TIMx-》SR寄存器里的UIF標(biāo)志,然后做定時器更新中斷的使能操作。至于開啟相關(guān)定時器的指令擺放位置并不嚴(yán)格。下面是相關(guān)動作的操作順序及結(jié)果,可以參考、驗證之。這里共羅列了6種寫法,其中有3種情形是會立即進入中斷的,另外3種不會。
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); //清除更新中斷請求位
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); //使能定時器1更新中斷
TIM_Cmd(TIM1, ENABLE); //啟動定時器
(1)。。。。。。不會立即進入更新中斷程序。
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);//清除更新中斷請求位
TIM_Cmd(TIM1, ENABLE);
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);//使能定時器1更新中斷
(2)。。。。。。不會立即進入更新中斷程序。
TIM_Cmd(TIM1, ENABLE);
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);//清除更新中斷請求位
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);//使能定時器1更新中斷
(3)。。。。。。不會立即進入更新中斷程序。
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);//清除更新中斷請求位
TIM_Cmd(TIM1, ENABLE);
(5)。。。。。。立即進入更新中斷程序。
(6)。。。。。。立即進入更新中斷程序。
順便提下關(guān)于定時器里UG位和URS位的使用,分別在TIMx-》EGR和TIMx-》CR1寄存器里。對UG位置1可以產(chǎn)生更新事件并對相關(guān)計數(shù)器和寄存器重新初始化,如果URS位為0的話,同時會產(chǎn)生更新中斷。如果不希望對UG位置1的同時產(chǎn)生更新中斷,得置URS位為1,否則會立即進入更新中斷。
另外
我們平時使用定時器的時候多數(shù)都是處于開啟狀態(tài),平時的定時中斷書寫格式一般是:
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
//要處理的事件內(nèi)容。。。。
}
}
但是,項目的實驗過程中,我使用的定時器處理事件稍微有點特殊,即,定時器不是一直處于開啟狀態(tài), 而且關(guān)閉時候也是在中斷里關(guān)閉。大概形式這樣:
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
//要處理的事件內(nèi)容。。。。
TIM_Cmd(TIM3, DISABLE); //失能(函數(shù)外使能)
}
}
看似沒錯,而且也看似正常。但是,處理的事件內(nèi)容出現(xiàn)了很多未知錯誤(由于我的這個處理事件有很強的時序性,開始和結(jié)束都比較嚴(yán)格),無法正常執(zhí)行。通過后來的調(diào)試中發(fā)現(xiàn)(把處理時間改為點燈或者打印輸出方式),發(fā)現(xiàn)是:TIM_Cmd(TIM3, DISABLE); 擾亂了時序關(guān)系。當(dāng)失能后,其實中斷并沒有真正失能,還會再進入一次中斷,因此事件又被執(zhí)行了一次,對于時序比較嚴(yán)格的事件,就產(chǎn)生了問題!
找到了原因,因此,我猜測雖然定時器失能并且關(guān)閉了定時器,但是可能中斷標(biāo)志位并沒真正清除,雖然中斷開始已經(jīng)清除過一次,但估計因為失能使得標(biāo)志位又被置位了,因此,我在失能前面加了句清除中斷更新標(biāo)志位,如下:
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
//要處理的事件內(nèi)容。。。。
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);//再清除標(biāo)志位
TIM_Cmd(TIM3, DISABLE); //失能(函數(shù)外使能)
}
}
果然,程序可以正常的時序運行。
比較納悶關(guān)定時器前又得清下標(biāo)志位,因此引起了另一個好奇心,是不是在其他地方關(guān)閉定時器(如主函數(shù)),也得這樣做才可以。所以對這個好奇心進行了下測試。發(fā)現(xiàn):如果把關(guān)閉定時器放到了主函數(shù)后,不用再清中斷標(biāo)志位。能正常把定時器關(guān)閉,并不會進入中斷。
通過這次的問題,浪費了很多時間解決,不過也吸取到了點經(jīng)驗,但對于內(nèi)在真正原因:在中斷里失能和中斷外失能效果為什么不一樣,暫時還沒搞清楚。。。但這個可以作為以后的前車之鑒,以及大家的前車之鑒,少走彎路。
評論