1. 程式人生 > >stm32f051精確延時的實現

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。

     第三條語句,CR1暫存器bit3寫1,由暫存器定義得知,這是把定時器設定為一旦發生溢位,就停止定時器,因為我們做的是延時函式,延時到了以後,就沒有必要讓定時器再不斷遞增了,所以要這樣設定。

第五條語句,檢測狀態暫存器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--);
}