前言
在一些需要的情況下軟件延時十分必要,有時爲了測試方便大都直接用了while(–i)或者for循環大致延時下看看就可以了。當需要精確延時情況下一般需要定時器來定時,當然對於STM32系列單片機都有SysTick,一般都是用這個作爲延時定時器。這兩天突然想着用個基本定時器實現一個延時程序,想着幾十分鐘的解決的事情結果搞了一整天,所以寫個博客紀念一下調試經歷。
必須瞭解
想要正確使用定時器就不得不先了解兩個必要內容:定時器的時鐘頻率和影子寄存器這兩個內容。不止針對基本定時器其它定時器也是一樣的。
定時器時鐘頻率
本着遇到問題就查數據手冊的精神,首先來查閱《STM32F4xx中文參考手冊》關於定時器章節發現基本上就說計數原理以及寄存器說明,對於時鐘沒說什麼。沒關係往上一層看,直接看STM32的時鐘樹,如下圖:
先大致看了下有關於定時器時鐘描述的,從圖上看大致也就是如果APBx不分頻(也就是APBx presc = /1),定時器時鐘頻率也就是APBx的時鐘頻率,反之就是對APBx時鐘的×2倍頻。然後繼續往下看會看到這樣的話:
可以看出跟我們的理解差不多。那麼就到了下一步對於APBx的時鐘怎麼確定呢?那麼一般情況下如果事自己配置的時鐘自己應該直接就知道了,如果不是自己配置的我們會直接調用庫函數的時鐘初始化函數,那麼此時有兩種方法可以確定我們的APBx的時鐘:
法一:還是這個圖,首先一個前提我們都知道使用庫函數時鐘配置,SYSCLK爲168MHZ,那麼如果知道AHB和APBx的分頻值,根據上述描述直接就可以得到定時器頻率了。
問題來了,在哪裏可以看到這個兩個分頻值勒,很簡單庫函數時鐘配置在system_stm32f4xx.c,當然在這裏找了。很簡單不用看代碼直接看@brief裏面的第5條,找到自己F4的型號,以STM32F40xxx/41xxx devices爲例,如下圖:
可以看到AHB與APBx的分頻值,根據確定上述判定,到APBx的時鐘爲168MHz,APBx的分頻值也不是1,所以定時器時鐘也就是APBx時鐘的2倍數。以上就可以得到了定時器頻率(這種方式可能有些人不清楚定時器掛載在哪一個APB上,簡單看法就是直接看rcc裏面開啓外設時鐘裏面的參數可選值可以看到或者直接查看數據手冊)。
法二:不說廢話直接看手冊,外設掛載圖以及相應時鐘(只對使用庫函初始化的時鐘配置可以,自己配置不可以)
直接看到APB1是42MHz,APB2是84MHz。分頻值也不是1所以定時器6、7的時鐘頻率也就是84MHz。
影子寄存器
關於影子寄存器,我大致說下所謂影子寄存器就是定時器實際工作寄存器,而給與用戶的操作的寄存器就是影子寄存器的本體,那麼當我們操作寄存器時,需要一個條件去將寫入寄存器值更新到影子寄存器中或者“直接操作”影子寄存器。(關於定時器的影子操作寄存器這個文章寫的挺好:STM32定時器的預裝寄存器及影子寄存器PSC—ARR-CCRx
)
代碼設計
首先是初始化。
void tim7Init()
{
NVIC_InitTypeDef NVIC_InitStruct;
//開啓相關時鐘
RCC_APB1PeriphClockCmd(TIM_RCC_PERIPH, ENABLE);
//開啓自動重裝
TIM_ARRPreloadConfig(TIM_NAME, ENABLE);
//中斷NVIC配置
NVIC_InitStruct.NVIC_IRQChannel = TIM7_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
//中斷條件設置
TIM_ITConfig(TIM_NAME, TIM_IT_Update, ENABLE);
//開啓定時器
}
void TIM7_IRQHandler()
{
if(TIM_GetITStatus(TIM_NAME, TIM_IT_Update))//獲取中斷狀態
{
TIM_ClearITPendingBit(TIM_NAME, TIM_IT_Update);
tim7ITCount++;
}
}
void delayMs(uint16_t ms)
{
tim7MsInit(ms);
}
然後時延時函數
static void tim7MsInit(uint32_t ms)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//定時器相關初始化設置-剩下兩個參數對於基本定時6、7無影響
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = ms*10-1;//單位是1ms
TIM_TimeBaseInitStruct.TIM_Prescaler = 8400 - 1;//100us計數一次
TIM_TimeBaseInit(TIM_NAME, &TIM_TimeBaseInitStruct);
//開始計時
while(TIM_GetITStatus(TIM_NAME, TIM_IT_Update));//等待初始化事件更新,將設定值設置到寄存器
tim7ITCount = 0;
TIM_Cmd(TIM_NAME, ENABLE);
//等待結束
while(!tim7ITCount);//等待置1
TIM_Cmd(TIM_NAME, DISABLE);
TIM_SetCounter(TIM_NAME, 0);//清空已經計數數值
}
我當時調試了很久一直沒有想到,當調用TIM_TimeBaseInit後會產生一個事件更新,之所以產生事件更新也就是因爲要將寫入的寄存器數值更新到影子寄存器中,不管你開不開是自動預裝,當調用調用TIM_TimeBaseInit後會產生一個事件更新,可以查看庫函數源碼:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
uint16_t tmpcr1 = 0;
/* Check the parameters */
assert_param(IS_TIM_ALL_PERIPH(TIMx));
assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));
tmpcr1 = TIMx->CR1;
if((TIMx == TIM1) || (TIMx == TIM8)||
(TIMx == TIM2) || (TIMx == TIM3)||
(TIMx == TIM4) || (TIMx == TIM5))
{
/* Select the Counter Mode */
tmpcr1 &= (uint16_t)(~(TIM_CR1_DIR | TIM_CR1_CMS));
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
}
if((TIMx != TIM6) && (TIMx != TIM7))
{
/* Set the clock division */
tmpcr1 &= (uint16_t)(~TIM_CR1_CKD);
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
}
TIMx->CR1 = tmpcr1;
/* Set the Autoreload value */
TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
/* Set the Prescaler value */
TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
if ((TIMx == TIM1) || (TIMx == TIM8))
{
/* Set the Repetition Counter value */
TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
}
/* Generate an update event to reload the Prescaler
and the repetition counter(only for TIM1 and TIM8) value immediatly */
TIMx->EGR = TIM_PSCReloadMode_Immediate;
}
從最後一句可以看書,會產生了一個更新事件,所以,在初始化完成後,需要等待這個更新事件,並清除標誌位,纔打開定時器並等待定時結束,纔是所需要的定時值。以下是我定時1s時操作結果(因爲中間有初始化函數以及等待了一個更新事件所有花費了點時間,小於1ms)
進入函數內部計算就可以看出基本就是1s了
參考
STM32F4xx中文參考手冊.pdf
stm32f407Datasheet.pdf