1. 程式人生 > >STM32使用systick定時器定義硬體精準延時函式

STM32使用systick定時器定義硬體精準延時函式

前言

  1. 博文基於STM32F103ZET6和標準韌體庫V3.5.0在MDK5環境下開發;
  2. 本博文討論的是晶片不執行作業系統的情況下完成1s的延時功能;
  3. 如有不足之處還請多多指教;

SysTick—系統滴答定時器是什麼?

是一個24位的硬體倒計數定時器;

SysTick的功能是什麼?(分兩種情況)

  1. 晶片執行作業系統(UCOS)情況下做:為作業系統(例如UCOS)提供硬體上的定時中斷(滴答中斷),作為整個系統的時基,為多個任務分配不同的時間片,確保不會出現一個任務霸佔系統的情況;或者把每個定時器週期的某個時間範圍賜予特定的任務等;還有作業系統提供的各種定時功能;
  2. 不執行作業系統,單純做定時器:提供精準的定時功能;

SysTick的特點

  1. 和以往的外設定時器不同,SysTick這個定時器以及相關暫存器在CM3核心裡。《STM32使用手冊》裡對並沒有涉及SysTick的內容,只能去《Cortex-M3權威指南》看(PDF的133頁);寄存的可定址,且暫存器被定義在程式包中的core_m3.c 中,涉及相關的程式設計時一定要注意;
  2. 可以工作在晶片的睡眠狀態下;
  3. SysTick被捆綁在NVIC(中斷向量控制器)中,可以產生異常(異常號:15),這是在作業系統下為系統提供時間基準的必要條件;
  4. 每個CM3核心都含有一個SysTick,這樣方便程式移植;
  5. SysTick時鐘源的選擇可以來自於外部,也可以來自於內部;使用來自外部的時鐘源的時候要根據具體晶片生產廠商的手冊;(本博文采用外部時鐘源)

SysTick的四個相關暫存器(圖片摘取《Cortex-M3權威指南》)

CTRL控制及狀態暫存器

在這裡插入圖片描述
描述中解釋的都很清楚,這裡強調兩個重要的點:

  1. 位[16]:只能讀,不能寫,算是一個狀態位;如果計數器達到0,則讀入為1;當讀取或清除當前計數器值時,將自動清除為0;
  2. 地址:0xE000E010 ,這是本暫存器地址,一會程式設計的時候要用到;
LOAD重灌載數值暫存器

在這裡插入圖片描述

VAL當前值暫存器

在這裡插入圖片描述

CALIB校準數值暫存器(這個暫存器在本部落格中用不到)

在這裡插入圖片描述

SysTick時鐘源的選擇(兩個)

在這裡插入圖片描述
在紅框處,其實我是有疑問的:紅框處提供的有3個時鐘源HCLK,HCLK/8,FCLK;從名字上來看,FCLK似乎就是上面提到的SysTick的CTRL暫存器中時鐘源可選擇的核心時鐘;但是從標準庫函式提供的函式名上來看,提供核心時鐘的是HCLK時鐘源,提供外部時鐘的是HCLK/8時鐘源;看如下程式碼:

/*此段為我自己寫的,為初始化SysTick*/
void delay_Init(void)
{
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //設定SysTick時鐘源為外部時鐘源--HCLK/8;
	fac_us=SystemCoreClock/8000000;     //系統核心時鐘,即CM3的時鐘;
	fac_ms=(u16)fac_us*1000;					
}
/*此段程式碼為庫檔案 misc.c中關於配置SysTick時鐘源的函式*/
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;   //0x00000004  選擇時鐘源為內部時鐘源
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;  //0xFFFFFFFB  選擇時鐘源為外部時鐘源
  }
}
/*此段程式碼是misc.h檔案中的關於時鐘源的選項*/
#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
                                       ((SOURCE) == SysTick_CLKSource_HCLK_Div8))

從以上三段程式碼來看: SysTick_CLKSource_HCLK_Div8SysTick_CLKSource_HCLK是配置CTRL時鐘源選擇的值,由於這兩個符號裡都包含HCLK,所以我認為,SysTick的時鐘源就來自於上面時鐘框圖中的HCLK和HCLK/8(先這麼理解著,即使理解的不對,並不影響這個程式的進行,因為HCLK和FCLK的時鐘頻率是一樣的,只是名字不同而已)

好了,關係都捋清了,只差程式碼了;

SysTick的配置和1s延時

配置步驟:(這一個函式的執行從頭到尾就是1s)

  1. 配置SysTick時鐘源;
  2. 獲取SysTick計數器遞減週期(每個週期計數器減一),這個需要計算;
  3. 根據上面的遞減週期配置過載暫存器LOAD;
  4. 清零當前值暫存器VAL;
  5. 使能定時器(使能CTRL暫存器使能位);
  6. 等待VAL遞減到0,從而CTRL暫存器的計數標誌位COUNTFLAG被置1;
  7. 延時完成,函式的最後關閉定時器(關閉CTRL暫存器使能位);
  8. 函式結束;

所以綜合敘述一下就是,先配置時鐘源,有了時鐘源就能確定當前值暫存器VAL遞減週期,然後根據遞減週期與1s之間的算數關係配置過載暫存器,然後使能暫存器後只需檢測CTRL暫存器的計數標誌位即可,

1s延時程式碼:

#include <stm32f10x.h>

u32 fac_us;
u32 fac_ms;

void delay_Init(void)
{	/*
	配置時鐘源為外部HCLK/8,經標準庫初始化後的HCLK = 72MHz,
	即SysTick計數器的時鐘源頻率為72MHz/8 = 9MHz,即每9M個方波就是1s(秒基準),
	每9000個脈衝1ms(毫秒基準),每9個秒衝就是1us(微妙基準);
	所以我們可以認為當前值暫存器VAL的遞減週期就是:每(1/9)us就減1;即每經過1us,經過了9個方波;
	*/
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); 
	fac_us=SystemCoreClock/8000000;     //最終fac_us=9;
	fac_ms=fac_us*1000;							//fac_ms = 9000;			
}

void delay_us(u32 nus)  
{																						//注意這裡的SysTick變數是結構體指標變數;
	SysTick->LOAD=nus*fac_us;   									//將要定時的時間(nus*fac_us)裝載過載暫存器;
	SysTick->VAL =0x00;         											//清零當前值暫存器;
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; 	//使能控制暫存器(CTRL),開啟定時器;
	while((SysTick->CTRL) >> 16 != 1);				          //等待被重新轉載後的當前值暫存器遞減到0,從而使得控制暫存器的計數標誌位被置1;

	SysTick->CTRL |= SysTick_CTRL_ENABLE_Pos;  //關閉計數器(注意運算子是|=,這一點一定要特別注意,因為此處配置的是CTRL暫存器,小心把其他位也給配置了,下面的程式也是一樣)
}

void delay_ms(u32 nms)   //含義和上面一樣;  當nms=1000是即可獲得1s延時;
{		
	SysTick->LOAD=nms*fac_ms;
	SysTick->VAL =0x00;
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;
	while((SysTick->CTRL) >> 16 != 1);

	SysTick->CTRL |= SysTick_CTRL_ENABLE_Pos;       	
}