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