stm32f051精確延時的實現
1,使用HAL庫中自帶的延時函式為
HAL_Delay();
/** * @brief This function provides accurate delay (in milliseconds) based * on variable incremented. * @note In the default implementation , SysTick timer is the source of time base. * It is used to generate interrupts at regular time intervals where uwTick * is incremented. * @note ThiS function is declared as __weak to be overwritten in case of other * implementations in user file. * @param Delay: specifies the delay time length, in milliseconds. * @retval None */ __weak void HAL_Delay(__IO uint32_t Delay) { uint32_t tickstart = 0; tickstart = HAL_GetTick(); while((HAL_GetTick() - tickstart) < Delay) { } }
該函式傳遞的引數是uint32_t 型別的,in milliseconds.
2,使用定時器實現
(1)使用操作暫存器的方法實現一個ms延時函式
CR1是控制暫存器,SR是狀態暫存器,ARR就是溢位值暫存器,CNT就是計數器的當前值。
PSC是預分頻暫存器,你可以給預分頻暫存器裡面寫一個從0~65535的值,這個值+1,就是定時器執行的時鐘。舉個例子,比如微控制器工作在主頻72MHz,預分頻暫存器寫0,預分頻係數就是0+1=1,定時器的時鐘就是72MHz/1=72MHz;再舉個例子,比如微控制器還是工作在主頻72MHz,預分頻暫存器寫71,預分頻係數就是71+1=72,定時器的時鐘就是72MHz/72=1MHz。知道定時器的時鐘有什麼用?相信很多初學者不清楚,定時器的時鐘關乎定時器計數器CNT遞增的時間間隔,根據頻率和週期的公式f=1/T,定時器計數器遞增的時間間隔就是1/定時器的時鐘,例如當定時器時鐘為1MHz時,定時器計數器遞增的時間間隔就是1/1MHz=1微秒,這時,如果你把溢位值設定為1000,就是1000*1us=1ms溢位。
void delay_ms(uint16_t ms)
{
TIM6->PSC=35999;
TIM6->ARR=ms*2;
TIM6->CR1|=(1<<3);
TIM6->CR1|=0x1;
while((TIM6->SR&0X1)==0);
TIM6->SR=0;
}
第一條語句,設定預分頻係數為35999+1=36000,所以定時器的時鐘為72000000/36000=2000Hz,那麼定時時間間隔就是1/2000=0.0005秒,即0.5毫秒。
第二條語句,設定溢位值為ms乘以2,假如要延時1秒,函式的引數ms就是1000,溢位值就是1000*2=2000,2000*0.5毫秒=1000毫秒,即1秒。這時候,有人會說,為什麼不乾脆把預分頻值PSC設定為71999,即預分頻係數為72000,定時器的時鐘就是72000000/72000=1000Hz,定時時間就是1毫秒,那麼直接把函式的引數ms給了溢位值暫存器ARR就可以了,就不必乘以2了。想法是可以,但是你得知道,定時器都是16位的,所以PSC的值最大到65535,到不了71999。
第五條語句,檢測狀態暫存器SR中的bit0UIF是否置1,置1的時候,定時值就達到溢位值了,說明定時時間到了。
第六條語句,清除狀態暫存器SR中剛才溢位造成的UIF位。
(2)使用庫函式的方法實現ms級別的延時
void TIM6_Delay_ms(uint16_t ms)
{
/* 定義一個定時器基本定時初始化結構體變數 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
/* 時鐘預分頻數為36000,在主頻72M時,計數器每500us加1*/
TIM_TimeBaseInitStruct.TIM_Prescaler= 35999;
/* 自動重灌載暫存器值 */
TIM_TimeBaseInitStruct.TIM_Period=ms*2;
/* 把上面的值配置到暫存器 */
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStruct);
/* 設定定時時間到了以後停止定時器計數 */
TIM_SelectOnePulseMode(TIM6, TIM_OPMode_Single);
/* 清除SR中的UIF標誌 */
TIM_ClearFlag(TIM6, TIM_IT_Update);
/* 開啟定時器6 */
TIM_Cmd(TIM6, ENABLE);
/* 檢測定時時間是否到來 */
while(TIM_GetFlagStatus(TIM6, TIM_IT_Update)==RESET);
/* 軟體清除更新標誌 */
TIM_ClearFlag(TIM6, TIM_IT_Update);
}
你可以細細觀察一下上面的庫函式,實際上,和直接操作暫存器是一樣的。
3,使用systick實現精確延時
(1)SysTick介紹
韌體庫中的Systick相關函式: SysTick_CLKSourceConfig() //Systick時鐘源選擇 misc.c檔案中 該函式的時鐘源的選擇有2種方式,一種是外部時鐘源是 HCLK(AHB匯流排時鐘)的1/8,另外一種是核心時鐘是 HCLK時鐘. #define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB) #define SysTick_CLKSource_HCLK ((uint32_t)0x00000004)SysTick_Config(uint32_t ticks) //初始化systick,時鐘為HCLK,並開啟中斷 //core_cm3.h/core_cm4.h檔案中 ticks是兩次中斷之間時鐘週期的個數,假如HCLK=72MHZ,那麼兩次中斷之間的時間間隔為72M/ticks * 1/72M (s) (2)systick的中斷服務函式 void SysTick_Handler(void);
(3)使用中斷的方式實現ms的延時
static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0){
}
}
void SysTick_Handler(void)
{
if(TimingDelay != 0x00)
{
TimingDelay--;
}
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
if(SysTick_Config(SystemCoreClock / 1000)) /*中斷時間間隔為1ms */
{
while(1);
}
................
Delay(50);
..................
while(1)
{
}
}
(4)使用查詢的方式實現ms 和 us的延時
static u8 fac_us=0; //us延時倍乘數
static u16 fac_ms=0; //ms延時倍乘數
//初始化延遲函式
//SYSTICK的時鐘固定為HCLK時鐘的1/8
//SYSCLK:系統時鐘
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //選擇外部時鐘 HCLK/8 systic時鐘的頻率f=72M/8=9MHZ T=1/9M(s)=1/9(us)
fac_us=SystemCoreClock/8000000; //為系統時鐘的1/8 72M/8M= 9 也就是說在當前的時鐘下:延時1個us需要走9個時鐘週期
fac_ms=(u16)fac_us*1000; //非OS下,代表每個ms需要的systick時鐘數 延時1ms就需要走1000個時鐘週期
}
//延時nus
//nus為要延時的us數.
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //時間載入
SysTick->VAL=0x00; //清空計數器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //開始倒數
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待時間到達
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數器
SysTick->VAL =0X00; //清空計數器
}
//延時nms
//注意nms的範圍
//SysTick->LOAD為24位暫存器,所以,最大延時為:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK單位為Hz,nms單位為ms
//對72M條件下,nms<=1864
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //時間載入(SysTick->LOAD為24bit)
SysTick->VAL =0x00; //清空計數器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //開始倒數
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待時間到達
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數器
SysTick->VAL =0X00; //清空計數器
}
查詢方式與中斷方式的比較: 查詢方式就是不斷的查詢某個標誌位,需要耗費大量的cpu 的時間,一般情況下除專門用於延時外不用這種方式;中斷方式比較適合處理具有隨即特性的事件,事件發生後向cpu提出申請,然後cpu會儲存當前的任務轉去處理事件 程式設計時查詢方式要不斷查詢標誌位,而中斷要編寫中斷服務子程式來處理中斷事件
4,使用while死等待的方式 這種方式的延時是最不準確的,用於粗略的延時。
void delay(uint16_t i)
{
while(i--);
}