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