STM32滴答定時器與UCOS時鐘系統,以及心跳和延時函式的實現.
Systick就是一個定時器而已,只是它放在了NVIC中,主要的目的是為了給作業系統提供一個硬體上的中斷(號稱滴答中斷)。滴答中斷?這裡來簡單地解釋一下。作業系統進行運轉的時候,也會有“心跳”。它會根據“心跳”的節拍來工作,把整個時間段分成很多小小的時間片,每個任務每次只能執行一個“時間片”的時間長度就得退出給別的任務執行,這樣可以確保任何一個任務都不會霸佔整個系統不放。或者把每個定時器週期的某個時間範圍賜予特定的任務等,還有作業系統提供的各種定時功能,都與這個滴答定時器有關。因此,需要一個定時器來產生週期性的中斷,而且最好還讓使用者程式不能隨意訪問它的暫存器,以維持作業系統“心跳”的節律。 只要不把它在SysTick控制及狀態暫存器中的使能位清除,就永不停息。
知道systick在系統中的地位後,我們來了解systick的實現。這裡只是舉例說明systick的使用。它有四個暫存器,筆者把它列出來:
SysTick->CTRL, --控制和狀態暫存器
SysTick->LOAD, --重灌載暫存器
SysTick->VAL, --當前值暫存器
SysTick->CALIB, --校準值暫存器
庫裡SysTick相關的函式我們能找到兩個
一個在msic.h中
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; } else { SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; } }
一個在core_m3.h中
static __INLINE uint32_t SysTick_Config(uint32_t ticks) { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */ SysTick->VAL = 0; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ return (0); /* Function successful */ }
我們一般只需要後一個就可以了
需要的操作在SysTick_Handler()(庫版本不同可能是SysTickHandler)中新增就好了,意思每到載入到SysTick中的值減到0時就執行SysTick();
SystemInit();
這個函式可以讓主頻執行到72M。可以把它作為systick的時鐘源。
接著開始配置systick,實際上配置systick的嚴格過程如下:
1、呼叫SysTick_CounterCmd() --失能SysTick計數器
2、呼叫SysTick_ITConfig() --失能SysTick中斷
3、呼叫SysTick_CLKSourceConfig() --設定SysTick時鐘源。
4、呼叫SysTick_SetReload() --設定SysTick重灌載值。
5、呼叫SysTick_ITConfig() --使能SysTick中斷
6、呼叫SysTick_CounterCmd() --開啟SysTick計數器
這裡大家一定要注意,必須使得當前暫存器的值VAL等於0!
SysTick->VAL = (0x00);只有當VAL值為0時,計數器自動過載RELOAD。
接下來就可以直接呼叫Delay();函式進行延遲了。延遲函式的實現中,要注意的是,全域性變數TimingDelay必須使用volatile,否則可能會被編譯器優化。(以上的過程 在庫函式SysTick_Config() 裡已經配置稍做了解即可);
時鐘的選擇 在庫檔案 system_stm32f10x.c 裡面
static void SetSysClock(void);
#ifdef SYSCLK_FREQ_HSE
static void SetSysClockToHSE(void);
#elif defined SYSCLK_FREQ_24MHz
static void SetSysClockTo24(void);
#elif defined SYSCLK_FREQ_36MHz
static void SetSysClockTo36(void);
#elif defined SYSCLK_FREQ_48MHz
static void SetSysClockTo48(void);
#elif defined SYSCLK_FREQ_56MHz
static void SetSysClockTo56(void);
#elif defined SYSCLK_FREQ_72MHz
static void SetSysClockTo72(void);
#endif
SetSysClock() 函式在SystemInit() 裡面被呼叫
SystemInit() 在startup_stm32f10x_hd.s 啟動檔案中被呼叫
Systick作為作業系統的心跳:
UCOS 系統的os_cfg.h檔案中 #define OS_TICKS_PER_SEC 100u /* Set the number of ticks in one second */
定義了 每秒心跳(中斷的次數); 100次. 假設是72MH的頻率 , 在這裡ucos給systick 的reload值應該為720000.
100次中斷耗時1s.
心跳()時間片)原理為 SysTick_Handler() 中斷函式. 裡面增加 OSIntEnter() (OSIntNesting++),然後呼叫OSTimeTick() ucos的時鐘服務程式,
最後OSIntExit() 任務排程一次.
OSTimeTick()的功能: 為OSTime+1 以及為所有等待任務控制塊的OSTCBDly-1 (如果-1為0 由後面的OSIntExit啟動排程)
OSTimeDly()與OSTimeDlyHMSM();
OSTimeDly(INT16U ticks)比較簡單就是 任務控制塊 狀態改為等待 ,OSTCBCur->OSTCBDly賦值延遲心跳次數ticks.並任務排程.
OSTimeDlyHMSM(INT8U hours, INT8U minutes, INT8U seconds, INT16U ms)複雜了一點
小時分鐘秒的延時簡單的計算出對應的ticks 並呼叫OSTimeDly();毫秒級別的:
注意:最小延時時間為心跳 (1/OS_TICKS_PER_SEC);
ticks= OS_TICKS_PER_SEC * ((INT32U)ms + 500L / OS_TICKS_PER_SEC) / 1000L;
這裡有個500L的取值.我們暫定為MINTIME變數吧.
邵貝貝書裡說:這裡的500的取值 目的是為了(假設OS_TICKS_PER_SEC=100 最小心跳10ms) 延時4ms的時候不延時,5ms以上的時候才延時. 依次14ms延遲一個節拍 15ms兩個節拍.
個人感覺怪怪的....
那麼如果想單純的引發一次任務排程.那麼OSTimeDlyHMSM(0,0,0,5) 至少是5ms才行.
對於單位 10ms以內的精確延時還是要拋棄ucos的函式而自己寫.
為了實現小於(1/OS_TICKS_PER_SEC)的延時函式.
傳統的做法是定義delay_us()函式.函式功能,對Systicks 定時器的reload 重灌值賦值 需要延時的時間對應的數值.
程式較為簡單,但是會影響ucos的心跳.不能在作業系統下使用;
為了能在作業系統下實現us級別的延時.
參考正點原子教程:可以定義一個delay_us()的延時函式
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的節拍數 fac_us=時鐘頻率/1000000(這裡是72)
tcnt=0;
told=SysTick->VAL; //剛進入時的計數器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow;//這裡注意一下SYSTICK是一個遞減的計數器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break;//時間超過/等於要延遲的時間,則退出.
}
};
}
這段函式功能,就是不斷的讀取SysTick->VAL ; Systick定時器的計數值;並計算差值. 如果大於需要的ticks 嘖延時結束
如果需要的ticks大於重灌載值, 用systick遞減的原理.判斷如果取得計數值大於記錄計數值,嘖更新記錄值,並對cnt計數加reload重灌載值. 實現us延時