/* SysTick滴答定時器 一、功能 SysTick定時器是一個簡單的定時器,CM3\CM4內核芯片都具備此定時器。SysTick定時器常用來做延時,采用實時系統時則用來做系統時鍾。無論用作延時還是用作系統心跳時鍾,不需要太復雜的功能,SysTick即可勝任。 二、實現原理 SysTick定時器是一個24位的倒計數,當倒計數為0時,將從RELOAD寄存器中取值作為定時器的初始值,同時可以選擇在這個時候產生中斷(異常號:15)。 例如從RELOAD的值為999,那么當倒計數為0時,就會從復位為999繼續倒計數。 只要不把它在SysTick控制及狀態寄存器中的使能位清楚,就永不停息,即使在睡眠模式下也能繼續工作。 三、SysTick寄存器(在 core_cm3.h 有定義,凡是 M3 內核的單片機都是一樣的) */ #define SysTick ((SysTick_Type *) SysTick_BASE) #define SysTick_BASE (SCS_BASE + 0x0010) #define SCS_BASE (0xE000E000) typedef struct { __IO uint32_t CTRL; // 控制及狀態寄存器 __IO uint32_t LOAD; // 重裝載數值寄存器 __IO uint32_t VAL; // 當前計數數值寄存器 __I uint32_t CALIB; // 校准寄存器 } SysTick_Type; /* SysTick->CTRL: (可通過 SysTick_CLKSourceConfig() 函數設置) COUNTFLAG(16)R: 計數標志位 當SysTick數到0,則該位被硬件置 1,當讀取該位時,將被硬件清零 CLKSOURCE(2)R/W: 時鍾源設置 1 = 外部時鍾源(STCLK) (AHB總線時鍾的1/8(HCLK/8)) 0 = 內核時鍾(FCLK) (AHB總線時鍾的頻率(HCLK)) TICKINT(1)R/W: 中斷使能位 1 = SysTick 倒數到0時產生 SysTick 異常請求 0 = 數到 0 時無動作 ENABLE(0)R/W: SysTick 定時器使能位 (當中斷被使能后,需要關注 void SysTick_Handler(void) 函數) SysTick_Type->LOAD: (SysTick_Config() 函數會設置該寄存器) RELOAD(23:0)R/W: 重裝載數值寄存器 當SysTick數到0,將被重裝載的值 SysTick_Type->VAL: (SysTick_Config() 函數會設置該寄存器) CURRENT(23:0)R/Wc: 當前計數數值寄存器 讀取時返回當前倒計數的值,寫它則使之清零,同時還會清除在 SysTick 控制及狀態寄存器中的 COUNTFLAG 標志。 四、庫函數分析 misc.c ---------------------------------------------------------------------------------- */ #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)) 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; // 設置 CLKSOURCE 為 1 } else { SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; // 設置 CLKSOURCE 為 0 } } core_cm3.c ---------------------------------------------------------------------------------- #define SysTick_LOAD_RELOAD_Pos 0 #define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) typedef enum IRQn { //... SysTick_IRQn = -1, //... } IRQn_Type; #define __NVIC_PRIO_BITS 4 #define SysTick_CTRL_CLKSOURCE_Pos 2 #define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) #define SysTick_CTRL_TICKINT_Pos 1 #define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) #define SysTick_CTRL_ENABLE_Pos 0 #define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) static __INLINE uint32_t SysTick_Config(uint32_t ticks) { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ // 設置計數值為 ticks - 1 // 原因1:視頻說是執行這些代碼需要時間,所以減少一個節拍 // 原因2:我認為是因為 SysTick 的倒計數到 0,例如設置 1000 ,那么范圍就應該是 999 ~ 0。 SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; // 設置中斷優先級 NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); SysTick->VAL = 0; // 設置時鍾源為外部時鍾源,同時開啟中斷、並使能 SysTick 定時器 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; return (0); } /* 五、延時應用 1、中斷方式 */ 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) { // ... if (SysTick_Config(SystemCoreClock / 1000)) // 注意,這里systick時鍾為HCLK,中斷時間間隔1ms { while (1); } while(1) { Delay(200);//2ms // ... } } /* SysTick_Config(SystemCoreClock / 1000): (原代碼這里假設是采用時鍾源為 HCLK) 這里設置的是 72000000Hz / 1000 = 72000 ticks,也就是說 SysTick 從 (72000-1) 開始倒數。 每倒數完 72000 個節拍就觸發一次中斷。 一個節拍的時間為:72000000 / 72000 = 1000us == 1ms SysTick_Config((SystemCoreClock / 8000000) * 1000 * 1): SysTick_Config() 會設置時鍾源為 HCLK/8 所以實際應用中不能按照上述代碼的參數。 SystemCoreClock / 8000000: 1us 的節拍數 1us的節拍數 * 1000: 則為 1ms 的節拍數 1ms 的節拍數 * 1: 設置 1ms 一個SysTick中斷,即從 ((SystemCoreClock / 8000000) * 1000 * 1) - 1 開始倒數。 2、輪詢方式 */ static u8 fac_us=0; //us延時倍乘數 static u16 fac_ms=0; //ms延時倍乘數 void delay_init() { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //選擇外部時鍾 HCLK/8 fac_us = SystemCoreClock/8000000; // 為系統時鍾的1/8 1us = 72000000 / 8000000 = 9 個節拍 fac_ms = (u16)fac_us*1000; // 1ms 需要 9 * 1000 = 9000 個節拍 } //延時 nus 微秒 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; //等待時間到達,這里使用了一個小技巧,通過(temp&0x01)檢查 SysTick 的使能位,避免 Systick 定時器被關閉而導致無限循環 } while((temp&0x01)&&!(temp&(1<<16))); SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數器 SysTick->VAL =0X00; //清空計數器 }
SYSTick 定時器
CM3 內核的處理器,內部包含了一個 SysTick 定時器,(SysTick 的時鍾源自HCLK的8分頻,8個系統時鍾周期systick跳一個,即8*1/72M=1/9 us)SysTick是一個24位的倒計數定時器,當計到0時,將從RELOAD寄存器中自動重裝載定時初值。只要不把它在SysTick控制及狀態寄存器中的使能位清除,就永不停息。
利用 STM32 的內部 SysTick 來實現延時的,這樣既不占用中斷,也不占用系統定時器。因為在 ucos 下 systic 不能再被隨意更改,如果我們還想利用 systick 來做 delay_us 或者delay_ms 的延時,就必須想點辦法了,這里我們利用的是時鍾摘取法。以 delay_us 為例,比如delay_us (50),在剛進入 delay_us 的時候先計算好這段延時需要等待的 systick 計數次數,這里為 50*9 (假設系統時鍾為 72Mhz,那么 systick 每增加 1,就是 1/9us) ,然后我們就一直統計systick的計數變化,直到這個值變化了50*9,一旦檢測到變化達到或者超過這個值,就說明延時50us時間到了。———實質上就是不改變systick基本單位時長,以基本單位時長為基本元做多次到達摘取。