寫過單片機程序的人都知道,軟件延時是不準確的,當然,當在我們可接受的情況下,很多地方還是用軟件延時的!但是在情況允許的條件下,我們還是希望延時越準確越好,這樣可以保證我們Demo的一些精度或者時候準確性。
在所以的ST32位MCU中,基本上都存在這么一個定時器,很多人都叫它“滴答定時器”,也就是SysTick,在我移植過的好幾個實時操作系統中,這個滴答定時器都用來作為操作系統調度的定時器了。其實這個定時器的使用非常簡單,但是基本上很多人又覺得它是神秘的!為毛呢??打開MCU的DadaSheet,參考手冊,都很少提到SysTick,并且提到的地方也就是一句話帶過,庫手冊也就是說明一下操作它的接口!然后!然后!然后就沒有然后了。
那么我們怎么樣使用它呢??有可能我們根本就不了解這個定時器,就算它再簡單,沒資料,呵呵!玩起來也是很費勁的啊!這個疑問先放起來!看看下一個疑問??
文章的開頭不是“做個準確的延時”么??那么它和SysTick有毛關系呢??(可能多遠單片機程序員來說,延時就是:delay_ms(x)這種),其實我就想用SysTick來給我做延時,因為MCU的運行時鐘在配置好之后,就基本上是穩定的了!穩定的時鐘數山羊,那就可以計算出每數一次山羊所用的時間,更可以算出在一定時間內能數多少只山羊了(還記得小時候的數山羊游戲嗎?)。所以就是利用這么個思想來干這種事。
當然,問題又來了,STM32有那么多個通用定時器和特殊定時器,干嘛非得用SysTick啊??我個人給的答案就是:(1)只要你開心,想怎么樣都好。(2)對于通用定時器和特殊定時器而言,他們除了定時功能之外,還有其他的很多特殊復用功能,比如說PWM的輸出等等,非得這么干的話你這是在浪費資源(當然,你若開心,便是晴天),然而,SysTick據我本人所知,它就是ARM核用來數山羊的,就這么個定時計時功能,不用它用誰??
回到上面的問題,我們怎么使用SysTick定時器呢??
首先,第一件事就是找到它再時鐘樹的位置(還是時鐘樹,可以想想它的地位有多重要了)。如下圖:
上圖還是時鐘樹(Clock Tree)從上圖我們可以得到這么幾個信息:
(1)SysTick就是內核系統定時器(不管它,咱還是叫滴答定時器)
(2)SysTick的時鐘源來自HCLK
(3)SysTick的時鐘為HCLK的8分頻,即Fsystick = HCLK/8
(4)藍色框表示系統時鐘咱在前面的帖子已經配置好了!哈哈!
好!第一件事干完了,也得到了相應的信息,那么咱們干第二件事:
還記得在準備資料的時候,特別提示,一點要將MCU的編程手冊下載下來嗎???在這里就用到它了!
就是上圖這個東西,名字叫STM32F0xxx Cortex-M0 programming manual ---->STM32F0xxx 系列Corte-M0編程手冊。
打開這個手冊,我們可以看到很多的東西,我簡單介紹一下吧!
瀏覽整個目錄,分為5章,如下:
第一章從技術角度來說,可能不是那么重要,但是對于不了解ST說明文檔的布局的童鞋而言,我個人認為還是必須瀏覽第一章的,因為他介紹了,本文檔的格式和關鍵詞使用還有必要的說明格式等,再就是簡單的介紹了文章的布局,和所包含的內容,這對于閱讀文檔,找到想要的資料是快速的方法。
第二章基本上就是對Cortex-M0內核的簡單介紹了,比如模式,堆棧,內核寄存器,數據類型,內存,低功耗模式等等的介紹了。
第三章看到這些想都不用想,就知道這是Cortex-M0內核的匯編指令,操作指令了。
第四章,哈哈哈哈哈哈!看到標題沒??Core peripherals 我想英語水平再差的人都知道這是Cortex-M0的核心外設了,那就是說這是ARM架構Cortex-M0核有的東西,并不只有ST的才有。OK!SysTick就是核心外設之一啦!這就是為毛找它的原因了啊!哈哈!!等等,還有第五章,得裝完B再說。
下去!看看!就可以看到!第五章就是記錄著修訂歷史的,如下圖:
從這個圖我們可以得知,第一次出這個文檔的時間,而且從未修改過!哈哈 !不管了!談談感受些。
首先,我覺得這個手冊是寫給程序員看的!它不是真正的Cortex-M0手冊,因為從手冊的內容來看,它再教我們怎么使用,怎么寫程序配置,而不是解釋Cortex-M0內核(當然,從名字就知道了!哈哈!)。這一點很重要,所以它是非常重要的手冊,比庫函數手冊重要N倍。
OK!廢話了一大堆!先把滴答定時器用起來吧!
第一件事,找到庫中相應的操作函數接口,所以我在keil中全工程搜索了一下,結果如下:
從上圖可以看出了,只看到了兩個與SysTick相關的函數(我用紅框標出了),分別是:void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)函數和__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)函數,(在Linux中__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)這種類型的函數叫內聯函數,不知道這是不是這樣叫),其中,void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)函數的作用,是是選擇SysTick的時鐘和初始化SysTick(從注釋和函數名就可以看出來了)。__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)函數就是“The function initializes the System Timer and its interrupt, and starts the System Tick Timer.
Counter is in free running mode to generate periodic interrupts.”初始化SysTick和中斷,開啟定時器。
從注釋上來分析,要讓SysTick跑起來使用這兩函數的確足夠了,但是想想哪里不對勁啊??咱的目的是做個延時程序,希望能夠精確的延時,并且咱隨時指定延時多久,這怎么還玩起中斷來了,不對,不對,這不靠譜(我說的不靠譜是和我們的目的不靠譜,并非這個庫不靠譜,哈哈!),唉!沒辦法,只能對ST的攻城獅說,你不給咱寫好,咋就自己玩了!哈哈!那砸門就自己玩!
那么怎么玩呢???這就是為毛在開篇的時候一大堆廢話談《Cortex-M0編程手冊》的原因了,咱自己玩,得靠它啊!OK!繼續--0------>
看到上圖,我想再不明白的人也要明白了!這就清清楚楚的介紹了SysTick的使用了哈!(翻譯就算了,水平太菜了,不惡心人了)。
繼續曬圖:
哈哈哈!看到了沒??SysTick的寄存器被我看到了!既然看到了寄存器,那要搞它就不難了。哈哈!繼續再往下!再往下!再往下!
嘎嘎!你看到的沒錯,你沒眼花,這里清清楚楚的說明了SySTick的第一個寄存器STK_CSR的功能和使用了。東西比較少,我就解釋一下:
Bit 0 ----- 0位,SysTick的開關,置1使能
Bit 1-----1位,SysTick的異常開關,其實就是中斷開關,置1,當SysTick計時到0時,產生中斷
注意:SysTick數山羊的方式和咱小時候玩的不太一樣,人家要倒著數,到0說明完成一次數山羊
Bit 2-----2位,SysTick的時鐘資源選擇,置0,使用外部參考時鐘;置1,使用處理器的時鐘
Bit 16---16位,定時器數山羊數到0的時候,返回1
這個寄存器就這么愉快的搞定了!!!哈哈哈哈!繼續裝B!
看到這個,就知道了,STK_RVR寄存器就是SysTick的裝載寄存器了,用來裝山羊的個數的嘛,哈哈!但是注意哦,因為SysTick是24位的定時器(前面文檔有介紹),所以別越界了哦!越界就像是用個吃飯的碗來裝一桶水,肯定裝不完啦!肯定會溢出啦!不懂啥事溢出的話,就想想水從碗里溢出來的現象哈哈!只是在這里是數據溢出而言!
所以這個寄存器沒啥好解釋的了(沒解釋也廢話了半天。。。汪汪)!
看看下一個:
看標題就知道了,SysTick的當前計數值寄存器,想知道此時計數到哪里了,讀它就好了!
下一個!
SysTick的校準定時器!!!!咱不校準,想校準的童鞋自己看看!多么簡單的東西!
到這里,我們得到的信息是:
(1)我們需要操作的寄存器:STK_CSR、STK_RVR和STK_CVR
(2)寄存器的地址:如下圖
現在要干的第一件事是,我們應該怎樣才能操作寄存器:
方法1:直接操作
在頭文件里直接定義這三個寄存器的物理地址(特別注意:寄存器是32位的),相應操作寄存器的某一位只需要操作TK_CSR、STK_RVR和STK_CVR的相應的某一位即可。就是這么簡單!
方法2:使用庫的定義
在core_m0.h文件中,有如下定義:
從注釋來看,它說這就是SysTick的寄存器結構體!OK!怎么證明呢??
再往下:
有這么幾個信息:
(1)基地址為 SCS_BASE (0xE000E000UL) ,即基地址就是0xE000E000了
(2)SysTick的基地址為:(SCS_BASE + 0x0010UL),即為:0xE000E010 咦是不是和STK_CSR寄存器地址一樣了呢??對的,就是一樣的,再往下
(3)宏#define SysTick ((SysTick_Type *) SysTick_BASE ),首先SysTick的基地址SysTick_BASE被強制轉換為結構體SysTick_Type類型的指針(也就是以這個地址為起點sizeof(SysTick_Type)大小的空間成為這個結構體類型),然后定義成宏SysTick,所以宏SysTick就成為了SysTick_Type的指針。再往下分析:
(4)分析得下圖:
SysTick的地址就是0xE000E010了,而根據結構體的貼心,第一個成員的地址和結構體的地址值是相等的,所以就有了上圖(要是不懂的話,建議好好的去補補C語言,把基本功打扎實了,沒點功力腫么能玩轉物理地址呢??),所以,結構體的成員和SysTick的寄存器就對應上了。哈哈!其實ST的庫里面的寄存器的結構都是這么干的,定義寄存器的方法都是一樣的!
當然,喜歡玩寄存器的童鞋,我建議就應該用以上的方法1的方法,這才是玩寄存器啊,!直接使用ST定義好的結構,多沒意思!!哈哈!!
好的!完事具備!只欠程序了!如下:
首先,先定義兩個本文件全局變量(記住這兩個全局變量只適用在本文件),分別是:fac_ms和fac_us,啥意思呢??它倆就是分別用來記錄1ms和1us時間內SysTick能計的數。
變量定義玩了,就是初始化SysTick了!其實初始化SysTick就是一句話SysTick->CTRL = 0xfffffffb;,就是操作TK_CSR寄存器。至于為毛是這個值,那就自己看手冊了!
但是,其實適用庫函數接口也是可以的:就是這個:
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource),這個函數的注釋是選擇SysTick的時鐘,其實就是初始化了,但是必須注意:參數必須是:SysTick_CLKSource_HCLK_Div8即HCLK的8分頻,證據就是前面的時鐘樹。但是將到這里咱不放看看這個函數的原型,
從函數中,也可以看出也是操作TK_CSR寄存器,因為參數必須是SysTick_CLKSource_HCLK_Div8,所以我們可以看看SysTick_CLKSource_HCLK_Div8的定義值如何:
看到沒:SysTick_CLKSource_HCLK_Div8的值也是0xFFFFFFFB
OK!初始化解決了!那么,這個初始化函數還有一個參數,干啥的呢??其實就是系統時鐘啦!比如,咱的系統時鐘已經配置成48MHz,那么調用的時候,直接SysTick_Init(48);即可,其實這個參數就是用來計算fac_ms和fac_us的值的,公式如下:
SysTick的時鐘:Fsystick = HCLK/8
SysTick計數一次的時間:Tsystick = 1/Fsystick
有了以上兩個公式(對于哪來的公式,別問我,問手冊去),那么計算fac_ms和fac_us的值就不難了!哈哈!OK!初始化結束。
咱們來個毫秒延時:
上面函數的意思就是:延時nms,比如需要延時100毫秒,就調用:delay_ms(100);即可。
那么實現是怎么樣的呢??
其實在編程手冊里面就教了我們怎么使用:
哈哈!人家明明白白的告訴了咱怎么使用,并且列出了1,2,3,那咱就不能客氣了!哈哈!
1.將計數值裝載到裝載寄存器
2.清空計數器
3.計數開始
4.等等計數到達
5.關閉計數器
6.清空計數器
過程就如上6步了。
注意一點啊:SysTick->CTRL = 0x01;開啟計時器時是對寄存器直接賦值,而不是操作某一位啊!所以這樣的話,是不會產生中斷的!因為中斷被關了啊!
OK!毫秒延時就這樣!!
下面就是微秒延時了!哈哈!
毫秒延時都講的這么清楚了,微秒延時就不說了,都是一個媽生的!過程沒啥說的!
來看看咱怎么調用吧!
調用就如上圖了!記住哦,先配置好系統時鐘哦!要是順序搞反了,搞不出來就該打屁屁了哦!哈哈哈!
縱觀IT界,一個簡單的東西內說個20頁可能也就是我這種逗逼了!哈哈!不過呢!我只是想解決一些初學者迷茫或者吐血的問題!高手的問題咱不敢解決!