8 位內(nèi)核的51 類MCU 的資源往往是最大幾K-100K 的flash。 100-幾K 字節(jié)的RAM, IO, 串口,定時(shí)器,8 位數(shù)據(jù)總線, AD 等簡(jiǎn)單的資源。 目標(biāo)確定,單一。結(jié)構(gòu)簡(jiǎn)單,指令簡(jiǎn)單。 易于理解和操作,這些特點(diǎn)也是51 能深入人心的因素。 目前依然是高校的主導(dǎo)實(shí)驗(yàn)平臺(tái)。 也是很多企業(yè)的應(yīng)用平臺(tái)。
隨著coretex-m3 內(nèi)核的STM32 在中國(guó)的興起,引起了廣大51 使用者的注意,對(duì)于我當(dāng)初進(jìn)入時(shí)的認(rèn)識(shí),我覺得STM32 速度非常快, flash,ram 好大。 能操作SD 卡,這簡(jiǎn)直相當(dāng)于微控制器的硬盤了。Usb功能這一個(gè)51 以前從來沒有的東西,終于可以和計(jì)算機(jī)不需要串口就可以實(shí)現(xiàn)通信了。定時(shí)器那么多路,可以使我做多少的PWM 控制啊。16 位的FSMC 總線,實(shí)現(xiàn)了高分辨率的LCD 也一樣可以高速控制了, 再不是51 那個(gè)僅僅能使用一些低分辨率且昂貴的LCM 比如12864 這些行將沒落的東東。 以前在51 想都不要想的ucos ucgui 都可以STM32 上盡情發(fā)揮了。還有好多好的功能,can 控制器輕易實(shí)現(xiàn)以前要組合電路才能實(shí)現(xiàn)的can 通信以及以太網(wǎng)的應(yīng)用等等。 這是真正意義的微控領(lǐng)域的SOC 芯片。
初入STM32,可能我們最親切的就是在51 使用過的keil , 在51 它叫keil c51, 在arm 它叫RealviewMKD-ARM,簡(jiǎn)稱它MDK, 現(xiàn)在版本是MDK4.22, 操作方法基本類似于keil。 我們常用的功能除了編輯工程,編譯代碼,還會(huì)用到下載,調(diào)試。 我們?cè)?1 時(shí),可能會(huì)很少有人用仿真功能,因?yàn)?1 足夠的簡(jiǎn)單,腦子想的往往就是你所看到的。 直接下載到目標(biāo)板對(duì)你來說更快捷。 所以在51 最常見的是下載器。 但在arm階段,資源繁雜,寄存器復(fù)雜。變量眾多,沒有一個(gè)仿真器,會(huì)感到那么的無助。因此 coretex-m3 的使用者基本都會(huì)擁有仿真器,一般分為ST-LINK ULINK 及JLINK,尤以JLINK 在中國(guó)的應(yīng)用最為普及。 我們都懂的原因,JLINK V8 的性價(jià)比是這幾個(gè)最好的。 所以,你需要在獲得了MDK 后,再擁有一個(gè)JLINK。 它不僅僅只支持STM32,它支持絕大多數(shù)的ARM 芯片。
51 使用者初入STM32,都會(huì)存在一個(gè)平臺(tái)轉(zhuǎn)換帶來迷惘的一個(gè)短暫過程,這是器件類型變化較大造成的認(rèn)知差異。
但調(diào)整一下,這個(gè)不適會(huì)很快過去的。
??1先看看 51 和STM32 具有的相同類型資源是哪些。根據(jù)你對(duì)51 的熟悉程度, 你會(huì)從STM32 的手冊(cè)上看到。 這些往往是較簡(jiǎn)單的,也是最容易理解的。 比如IO 口線控制,等等。
??2 STM32 高級(jí)一些的資源,往往也是需要較多精力去理解的。這可以在入門后再行學(xué)習(xí),比如USB,SDIO等。
??4編程方式的不同, 比如在 51,用置位或者復(fù)位指令就可以很方便的控制IO,而在STM32,由于所有資源的功能都和該資源對(duì)應(yīng)的32 位寄存器組的操作有關(guān)系。 因此對(duì)于資源的設(shè)置和操作都可能需要操作一個(gè)或者多個(gè)寄存器, 如果用多條指令來控制的話,會(huì)引起閱讀的障礙,以及日后代碼維護(hù)復(fù)雜,因此ST 公司引入了庫函數(shù)的概念。用執(zhí)行庫函數(shù)的方式解決復(fù)雜的資源操作的問題。
??4STM32 例程的MDK 工程都有相似的程序結(jié)構(gòu),結(jié)合手冊(cè)多看例程,會(huì)使你快速的形成對(duì)STM32 例程模板的認(rèn)識(shí),這個(gè)認(rèn)識(shí)一旦形成,剩下的代碼細(xì)節(jié)就好比是你預(yù)測(cè)到的填空題目。
當(dāng)你做好了想學(xué)習(xí)新平臺(tái)的準(zhǔn)備,那就義無反顧的投入CORETEX-M3 的懷抱吧。它會(huì)使你進(jìn)步到一個(gè)新的境界。 帶給你愉悅的技術(shù)享受。
如何迅速入門STM32單片機(jī)?
網(wǎng)上有大神說如果會(huì)51單片機(jī)和C語言一天可入門STM32,僅一天的時(shí)間,是否有真的這么快。這個(gè)要看自己給自己定的入門的標(biāo)準(zhǔn)了。
我眼中的入門:(前提是你學(xué)過51 單片機(jī)和C 語言)
??1知道參考官方的什么資料來學(xué)習(xí),而不是陷入一大堆資料中無從下手。
??2知道如何參考官方的手冊(cè)和官方的代碼來獨(dú)立寫自己的程序,而不是一味的看到人家寫的代碼就覺得人家很牛逼。
??3消除對(duì)STM32 的恐懼,消除對(duì)庫開發(fā)的恐懼,學(xué)習(xí)是一個(gè)快樂而富有成就感的過程。
學(xué)習(xí)本文時(shí),配合《STM32 中文參考手冊(cè)》GPIO 章節(jié)一起閱讀,效果會(huì)更佳,特別是涉及到寄存器說明的部分。
1、51 與STM32 簡(jiǎn)介51 是嵌入式學(xué)習(xí)中一款入門級(jí)的精典MCU,因其結(jié)構(gòu)簡(jiǎn)單,易于教學(xué),且可以通過串口編程而不需要額外的仿真器,所以在教學(xué)時(shí)被大量采用,至今很多大學(xué)在嵌入式教學(xué)中用的還是51。51 誕生于70 年代,屬于傳統(tǒng)的8 位單片機(jī),如今,久經(jīng)歲月的洗禮,既有其輝煌又有其不足。現(xiàn)在的市場(chǎng)產(chǎn)品競(jìng)爭(zhēng)激烈,對(duì)成本極其敏感,相應(yīng)地對(duì)MCU 的要求也更苛刻:功能更多,功耗更低,易用界面和多任務(wù)。面對(duì)這些要求,51 現(xiàn)有的資源就顯得得抓襟見肘了。所以無論是高校教學(xué)還是市場(chǎng)需求,都急需一款新的MCU 來為這個(gè)領(lǐng)域注入新的活力。
基于這市場(chǎng)的需求, ARM 公司推出了其全新的基于ARMv7 架構(gòu)的32 位Cortex-M3微控制器內(nèi)核。緊隨其后,ST(意法半導(dǎo)體)公司就推出了基于Cortex-M3 內(nèi)核的MCU—STM32。STM32 憑借其產(chǎn)品線的多樣化、極高的性價(jià)比、簡(jiǎn)單易用的庫開發(fā)方式,迅速在眾多Cortex-M3 MCU 中脫穎而出,成為最閃亮的一顆新星。STM32 一上市就迅速占領(lǐng)了中低端MCU 市場(chǎng),受到了市場(chǎng)和工程師的無比青睞,頗有星火燎原之勢(shì)。
作為一名合格的嵌入式工程師,面對(duì)新出現(xiàn)的技術(shù),我們不是充耳不聞,而是要盡快吻合市場(chǎng)的需要,跟上技術(shù)的潮流。如今STM32 的出現(xiàn)就是一種趨勢(shì),一種潮流,我們要做的就是搭上這趟快車,讓自己的技術(shù)更有競(jìng)爭(zhēng)力。
51 與STM32 架構(gòu)的區(qū)別
我們先普及一個(gè)概念,單片機(jī)(即MCU)里面有什么。一個(gè)人最重要的是大腦,身體的各個(gè)部分都在大腦的指揮下工作。MCU 跟人體很像,簡(jiǎn)單來說是由一個(gè)最重要的內(nèi)核加其他外設(shè)組成,內(nèi)核就相當(dāng)于人的大腦,外設(shè)就如人體的各個(gè)功能器官。
下面我們來簡(jiǎn)單介紹下51 和STM32 的結(jié)構(gòu)。
151 系統(tǒng)結(jié)構(gòu)
51 系統(tǒng)結(jié)構(gòu)框圖
圖1 51 系統(tǒng)結(jié)構(gòu)框圖
我們說的51 一般是指51 系列的單片機(jī),型號(hào)有很多,常見的有STC89C51、AT89S51,其中國(guó)內(nèi)用的最多的是STC89C51/2,下面我們就以STC89C51 來講解,并以51 簡(jiǎn)稱。
內(nèi)核
51 由一個(gè)IP 核和片上外設(shè)組成,IP 核就是上圖中的CPU,片上外設(shè)就是上圖中的:時(shí)鐘電路、SFR 和RAM、ROM、定時(shí)/計(jì)數(shù)器、并行I/O 口、串行I/O 口、中斷系統(tǒng)。IP核跟外設(shè)之間由系統(tǒng)總線連接,且是8bit 的,速度有限。
51 內(nèi)核是上個(gè)世紀(jì)70 年代intel 公司設(shè)計(jì)的,速度只有12M,外設(shè)是IC 廠商(STC)在內(nèi)核的基礎(chǔ)上添加的,不同的IC 廠商會(huì)在內(nèi)核上添加不同的外設(shè),從而設(shè)計(jì)出各具特色的單片機(jī)。這里intel 屬于IP 核廠商,STC 屬于IC 廠商。我們后面要講的STM32 也一樣,ARM 屬于IP 核廠商,ARM 給ST 授權(quán),ST 公司在Cortex-M3 內(nèi)核的基礎(chǔ)上設(shè)計(jì)出STM32 單片機(jī)。
外設(shè)
我們?cè)趯W(xué)習(xí)51 的時(shí)候,關(guān)于內(nèi)核部分接觸的比較少,使用的最多的是片上外設(shè),我們?cè)诰幊痰臅r(shí)候操作的也就是這些外設(shè)。
編程的時(shí)候操作的寄存器位于SFR 和RAM 這個(gè)部分,其中SFR(特殊功能寄存器)占有128 字節(jié)(實(shí)際上只用了26 個(gè)字節(jié),只有26 個(gè)寄存器,其他都屬于保留區(qū)),RAM占有128 字節(jié),我們?cè)诔绦蛑卸x的變量就是放在RAM 中。其中SFR 和RAM 在地址上是重合的,都是在80~FF 這個(gè)地址區(qū)間,但在物理區(qū)間上是分開的,所以51 的RAM 是有256 個(gè)字節(jié)。
編寫好的程序是燒寫到ROM 區(qū)。剩下的外設(shè)都是我們非常熟悉的IO 口,串口、定時(shí)器、中斷這幾個(gè)外設(shè)。
2STM32 系統(tǒng)結(jié)構(gòu)
STM32 系統(tǒng)結(jié)構(gòu)框圖
圖2 STM32 系統(tǒng)結(jié)構(gòu)框圖
內(nèi)核
在系統(tǒng)結(jié)構(gòu)上,STM32 和51 都屬于單片機(jī),都是由內(nèi)核和片上外設(shè)組成。只是STM32 使用的Cortex-M3 內(nèi)核比51 復(fù)雜得多,優(yōu)秀得多,支持的外設(shè)也比51 多得多,同時(shí)總線寬度也上升到32bit,無論速度、功耗、外設(shè)都強(qiáng)與51。
從結(jié)構(gòu)框圖上看,對(duì)比51 內(nèi)核只有一種總線,取指和取數(shù)共用。Cortex-M3 內(nèi)部有若干個(gè)總線接口,以使CM3 能同時(shí)取址和訪內(nèi)(訪問內(nèi)存),它們是:
指令存儲(chǔ)區(qū)總線(兩條)、系統(tǒng)總線、私有外設(shè)總線。有兩條代碼存儲(chǔ)區(qū)總線負(fù)責(zé)對(duì)代碼存儲(chǔ)區(qū)(即FLASH 外設(shè))的訪問,分別是I-Code 總線和D-Code 總線。
I-Code 用于取指,D-Code 用于查表等操作,它們按最佳執(zhí)行速度進(jìn)行優(yōu)化。
系統(tǒng)總線(System)用于訪問內(nèi)存和外設(shè),覆蓋的區(qū)域包括SRAM,片上外設(shè),片外RAM,片外擴(kuò)展設(shè)備,以及系統(tǒng)級(jí)存儲(chǔ)區(qū)的部分空間。
私有外設(shè)總線負(fù)責(zé)一部分私有外設(shè)的訪問,主要就是訪問調(diào)試組件。它們也在系統(tǒng)級(jí)存儲(chǔ)區(qū)。
還有一個(gè)MDA 總線,從字面上看,DMA 是data memory access 的意思,是一種連接內(nèi)核和外設(shè)的橋梁,它可以訪問外設(shè)、內(nèi)存,傳輸不受CPU 的控制,并且是雙向通信。簡(jiǎn)而言之,這個(gè)家伙就是一個(gè)速度很快的且不受老大控制的數(shù)據(jù)搬運(yùn)工,這個(gè)在51 里面是沒有的。
外設(shè)
從結(jié)構(gòu)框圖上看,STM32 比51 的外設(shè)多得多,51 有的串口、定時(shí)器、IO 口等外設(shè)STM32 都有。STM32 還多了很多特色外設(shè):如FSMC、SDIO、SPI、I2C 等,這些外設(shè)按照速度的不同,分別掛載到AHB、APB2、APB1 這三條總線上
3、小結(jié)
從內(nèi)核和外設(shè)這兩大方面來比較,STM32 之于51 就是一個(gè)升級(jí)版的單片機(jī)。它適應(yīng)市場(chǎng),引流潮流,在中低端的微控制器中流光溢彩。
2、學(xué)習(xí)方法的區(qū)別學(xué)習(xí)51 用寄存器,學(xué)習(xí)STM32 用庫。
以前我們?cè)趯W(xué)習(xí)51 的時(shí)候,用的是寄存器編程的方法,想要實(shí)現(xiàn)什么效果,直接往寄存器里面賦值,優(yōu)點(diǎn)是直觀,簡(jiǎn)單粗暴,知道自己具體干了啥,心里踏實(shí)。
直接操作寄存器之所以在51 上可行,究其原因,我想有兩點(diǎn):
??151 主頻不高,資源有限,必須注重程序執(zhí)行的效率,只能直接操作寄存器。關(guān)鍵的地方還得用匯編,不適合用固件庫。
要知道當(dāng)初我們學(xué)習(xí)51 單片機(jī)的時(shí)候用的還是匯編,連現(xiàn)在的C 編程都不是,就更別說什么庫函數(shù)編程。
??251 功能簡(jiǎn)單,寄存器不多。以國(guó)內(nèi)普及最廣的STC89C52 為例,寄存器全部加起來不到30 個(gè)。按照功能區(qū)分來記的話,可以把每個(gè)寄存器背的滾瓜爛熟,并且寄存器每一位的功能都可以記得住,在編程的時(shí)候做到了然于胸。
現(xiàn)在從51 過度到STM32 的學(xué)習(xí),很多人還是喜歡沿用51 的學(xué)習(xí)方法。接受不了庫,在學(xué)習(xí)庫的時(shí)候陷入迷糊之中,來回幾個(gè)月下來,都不知道到底有沒學(xué)會(huì)STM32,因?yàn)樵谶@一路的學(xué)習(xí)中都是在調(diào)用庫函數(shù),壓根就沒有操作過寄存器,心里面很不踏實(shí)。其實(shí)大家在調(diào)用庫函數(shù)的時(shí)候心中難道就沒有疑問,庫的底層是怎么實(shí)現(xiàn)的?難道就沒有勇氣對(duì)庫的底層一探究竟??勺詈螽?dāng)我們開始跟蹤庫函數(shù)底層的時(shí)候,看到一堆的宏定義、結(jié)構(gòu)體、指針、各種的文件包含,而且注釋全部都是英文的,是不是又心生忌憚。
鑒于此,我想用兩個(gè)原因來總結(jié)下很多初學(xué)者畏懼庫不愿意用庫的原因。
??1C 語言知識(shí)點(diǎn)的欠缺
庫在實(shí)現(xiàn)寄存器映像時(shí)使用的宏定義,強(qiáng)制類型轉(zhuǎn)換,在定義寄存器時(shí)使用的結(jié)構(gòu)體,在外設(shè)初始化函數(shù)時(shí)使用的指針,在組織頭文件時(shí)使用的條件編譯等C 語言知識(shí),在大學(xué)課程中很少涉及,大多數(shù)老師也基本是不講。在一些簡(jiǎn)單的51 單片機(jī)編程中又很少會(huì)用到這些知識(shí)。學(xué)單片機(jī),做嵌入式開發(fā)其實(shí)80%的工作都跟C 語言編程相關(guān),剩下的20%的工作就是閱讀各種數(shù)據(jù)手冊(cè),熟悉各種硬件外設(shè)。所以掌握這些基本的C 語言知識(shí),是嵌入式學(xué)習(xí)中一道邁不過去的坎,STM32 的庫則給了我們一次提升C 的機(jī)會(huì)。凡是可以從書本中找到的,相信我們基本都可以學(xué)會(huì),很多初學(xué)者并不是不夠聰明或者勤奮,只是缺少方向性的指導(dǎo)罷了。對(duì)于這欠缺的知識(shí)點(diǎn)我們稍微花點(diǎn)時(shí)間就可以掌握,剩下的就是不斷地實(shí)踐調(diào)試。這里我為大家推薦一本C 語言的書籍《C 和指針》。
??2程序架構(gòu)設(shè)計(jì)思想的欠缺
這個(gè)比較難搞,很多C 語言學(xué)習(xí)得挺好好的人,也比較難掌握。還好我們遇到了STM32 的庫,這給了我們一個(gè)學(xué)習(xí)和提升C 語言絕佳的機(jī)會(huì)。庫的整個(gè)架構(gòu)是如何搭建起來的,代碼上是如何如何一步一步寫出來的:從寄存器映像開始,到寄存器的封裝,然后到函數(shù)的編寫,到每個(gè)外設(shè)函數(shù)對(duì)應(yīng)的驅(qū)動(dòng)文件,這里面涉及到了大量的條件編譯,文件包含的思想,對(duì)應(yīng)剛寫過幾行51 單片機(jī)的初學(xué)者來說簡(jiǎn)直就是噩夢(mèng)。但是,如果你把這一系列的關(guān)系弄明白了,那么對(duì)庫的整個(gè)架構(gòu)也了解的差不多了,以后你就不用嚷嚷著說要操作寄存器了。
如果你一開始不喜歡用庫,對(duì)庫開發(fā)很忌憚,那么請(qǐng)自問:是不是我的C 語學(xué)得不夠好。庫是一種全新的學(xué)習(xí)方法,是一種潮流,我更把它看做是與C 語言的又一次歷練和提升。是否用庫,只差你一個(gè)閃亮的回眸。
3、用寄存器點(diǎn)亮LED為了順利過渡到庫開發(fā),在STM32 編程的開始,我們對(duì)照51 點(diǎn)亮一個(gè)LED 的方法,給大家演示一下STM32 如何用操作寄存器的方法點(diǎn)亮一個(gè)LED,然后再慢慢講解到底什么是庫,讓大家知道庫跟寄存器的關(guān)系。
1用51 點(diǎn)亮一個(gè)LED
在用STM32 點(diǎn)亮一個(gè)LED 之前,我們先來復(fù)習(xí)下用51 如何點(diǎn)亮一個(gè)LED。
硬件上我們假設(shè)51 單片機(jī)的P0 口的第0 位接了一個(gè)LED,負(fù)邏輯亮。如果我們要點(diǎn)亮這個(gè)LED,代碼上我們會(huì)這么寫:
這里面我們用的是總線操作的方法,即是對(duì)P0 口的8 個(gè)IO 同時(shí)操作,但起作用的只是P0^0。
除了這種總線操作的方法,我們還學(xué)習(xí)過位操作,利用51 編譯器的關(guān)鍵字sbit,我們可以定義一個(gè)位變量:
那么LED = 0;就點(diǎn)亮了LED,LED = 1;就關(guān)閉了LED。為了讓程序看起來見名知義,我們定義兩個(gè)宏:
點(diǎn)亮和關(guān)閉LED 的代碼就變成了:
上面總線和位操作的的方法,學(xué)過51 的朋友是非常熟悉的,也很容易理解。
那么我們?cè)僬f一下大家容易忽略的幾個(gè)知識(shí)點(diǎn)。
??1什么是寄存器
在點(diǎn)亮LED 的時(shí)候,我們都是用操作寄存器的方法來實(shí)現(xiàn)的,那大家是否想過,這個(gè)寄存器到底是什么?為什么我們可以直接操作P0 口?
解答上面的問題之前,我們先簡(jiǎn)單介紹下51 單片機(jī)的主要組成部分,這對(duì)我們學(xué)習(xí)其他單片機(jī)也有好處。
我們以國(guó)內(nèi)的STC89C51 為例,該單片機(jī)主要由51 內(nèi)核、外設(shè)IP、和總線這三大部分組成。內(nèi)核是由Intel 公司生產(chǎn)的,外設(shè)IP 就是STC 公司在內(nèi)核的基礎(chǔ)上添加的諸如定時(shí)器、串口、IO 口等這些東西,總線就是用來連接內(nèi)核和外設(shè)的接口單元。Intel 在這里屬于IP 核設(shè)計(jì)公司,STC 屬于IC 設(shè)計(jì)公司。世界上能設(shè)計(jì)IP 核的公司屈指可數(shù)。我們非常熟悉的ARM 公司就屬于IP 核設(shè)計(jì)公司,ARM 給其他公司授權(quán),其他IC 公司就在ARM 內(nèi)核上設(shè)計(jì)出各具特色的MCU,我們后面要學(xué)習(xí)的STM32 就是屬于一中基于ARM 內(nèi)核的MCU。
寄存器則是內(nèi)置于各個(gè)IP 外設(shè)中,是一種用于配置外設(shè)功能的存儲(chǔ)器,就是一種內(nèi)存,并且有想對(duì)應(yīng)的地址。學(xué)過C 語言我們就知道,要操作這些內(nèi)存就可以使用C 語言中的指針,通過尋址的方式來操作這些具有特殊功能的內(nèi)存—寄存器。比如P0 口對(duì)應(yīng)的地址是0X80,那么我們要修改0X80 這個(gè)地址對(duì)應(yīng)的內(nèi)存的內(nèi)容的話,按照常理可以這樣操作:
可當(dāng)我們編譯的時(shí)候,編譯器會(huì)報(bào)錯(cuò),在51 里面只能通過SFR 和SBIT 這兩個(gè)關(guān)鍵字來實(shí)現(xiàn)寄存器映像,不能直接操作寄存器對(duì)應(yīng)的地址,這是51 相較于STM32 不同的地方。
51 單片機(jī)的這些寄存器位于地址80H~FFH 中,對(duì)應(yīng)著128 個(gè)地址,但不是每個(gè)地址都是有效的,51 系列的單片機(jī)有21 個(gè),52 系列的則有26 個(gè),其他的都是保留區(qū)。
圖3 51 寄存器映射
??2寄存器映射
實(shí)際上我們?cè)诰幊痰臅r(shí)候并不是通過指針來操作寄存器的,而是直接給P0、P1 這些端口寄存器賦值。那么這些外設(shè)資源是如何與地址建立一一對(duì)應(yīng)的關(guān)系(寄存器映射定義),這得益與51 特有的兩個(gè)關(guān)鍵字:SFR 和sbit,其他單片機(jī)沒有,只能用其他的方式來實(shí)現(xiàn)寄存器映射。這兩個(gè)關(guān)鍵字幫我們實(shí)現(xiàn)了所有寄存器的定義,所以我們才可以像操作普通變量一個(gè)來操作寄存器。其實(shí)我們一開始提到的點(diǎn)亮LED 的代碼,全貌應(yīng)該是這樣的:
為了方便起見,我們可以把寄存器映射全部寫好封裝在一個(gè)頭文件里面,不用每用一個(gè)寄存器就定義一次。其實(shí)這方面的工作不用我們做,我們?cè)诰幊痰臅r(shí)候都會(huì)在開始的地方添加一個(gè)頭文件:
這個(gè)頭文件已經(jīng)實(shí)現(xiàn)了全部寄存器的定義,該文件是keil 自帶,在安裝目錄:KeilC51INC 下可以找到。這個(gè)文件實(shí)現(xiàn)了字節(jié)寄存器和位寄存器的定義。
??3啟動(dòng)文件—STARTUP.A51
還有一個(gè)就是啟動(dòng)代碼,這個(gè)也是很多初學(xué)者容易忽略的地方,對(duì)于這部分我們主要總結(jié)下它的功能,不詳解講解里面的代碼。
單片機(jī)在上電復(fù)位后,首先執(zhí)行的是啟動(dòng)文件—STARTUP.A51,而不是我們通??吹降膍ain 函數(shù)。我們新建51 工程的時(shí)候會(huì)有一個(gè)提示:是否拷貝啟動(dòng)代碼到當(dāng)前的工程,我們一般選擇是。
圖4 是否添加啟動(dòng)代碼
啟動(dòng)代碼用匯編語言編寫,主要實(shí)現(xiàn)了以下功能:清除內(nèi)部數(shù)據(jù)存儲(chǔ)器、清除外部數(shù)據(jù)存儲(chǔ)器、清除外部頁儲(chǔ)存器、初始化small 模式下的可重入棧和指針、初始化large 模式下可重入棧和指針、初始化compact 模式下的可重入棧和指針、初始化8051 硬件棧指針、傳遞初始化全局變量的控制命令或者在沒有初始化全局變量時(shí)給main 函數(shù)傳遞命令。然后程序就跳轉(zhuǎn)到main 函數(shù),來到我們熟知的C 世界。
??4總結(jié)
在講解用51 點(diǎn)亮LED 的時(shí)候,我們補(bǔ)充了什么是寄存器、寄存器映射、啟動(dòng)代碼這三部分的內(nèi)容,這三部分內(nèi)容本來是放到STM32 里面講解的,但考慮到大家已經(jīng)有51 的基礎(chǔ),并且對(duì)51 比較熟悉,那我再添加點(diǎn)內(nèi)容,大家自然沒有那么抗拒,并且可以根據(jù)上面講的內(nèi)容親自實(shí)踐,學(xué)習(xí)得也會(huì)更深入。那當(dāng)我再在STM32 講解這幾個(gè)內(nèi)容的時(shí)候,大家就會(huì)對(duì)比著學(xué)習(xí),對(duì)STM32 也就沒有那么忌憚。
2用STM32 點(diǎn)亮一個(gè)LED
對(duì)比著51 點(diǎn)亮LED 的方法,我們先用操作寄存器的方法用STM32 點(diǎn)亮一個(gè)LED,然后再一步步完善代碼,構(gòu)建最簡(jiǎn)單的庫函數(shù),讓我們知道庫是怎么建立起來的。
在寫代碼之前,我們先建一個(gè)工程。大家要注意的是,雖然51 跟STM32 用的都是keil,但是針對(duì)的MCU 是不一樣,軟件在安裝的時(shí)候要安裝在不同的目錄且不能安裝在英文目錄,不然會(huì)起沖突。我們這里用的是keil5,MDK5.15 版本。
??1新建工程
用KEIL5 新建一個(gè)工程,把工程放在一個(gè)事先建好的文件夾內(nèi),工程命名為REG 后保存。然后在工程目錄下添加啟動(dòng)文件:startup_stm32f10x_hd.s,該文件可以從KEIL5 安裝目錄找到,也可以從ST 庫里面找到,然后把啟動(dòng)文件添加到工程里面。
??2啟動(dòng)文件—startup_stm32f10x_hd.s
啟動(dòng)文件由匯編語言編寫,具體功能跟51 里面的啟動(dòng)文件:STARTUP.A51 差不多。
STM32 的啟動(dòng)文件主要實(shí)現(xiàn)了:
1、設(shè)置初始SP 。
2、設(shè)置初始PC=Reset_Handler
3、設(shè)置向量表入口地址,并初始化向量表。
4、調(diào)用庫函數(shù)SystemInit,把系統(tǒng)時(shí)鐘配置成72M,SystemInit 在庫文件system_stm32f10.c 定義。
5、跳轉(zhuǎn)到標(biāo)號(hào)_mian,最終來到C 的世界。這里我們先去除繁枝細(xì)節(jié),挑重點(diǎn)的講,主要理解第四和第五點(diǎn),在啟動(dòng)文件的147~155 行,是復(fù)位處理函數(shù),代碼如下:
這里我們簡(jiǎn)單介紹下這10 行代碼。
第一行是程序注釋,在匯編里面注釋用的是“;”,跟C 語言不一樣。
第二行是定義了一個(gè)子程序:Reset_Handler。PROC 是子程序定義偽指令。一般用法為:
其中NEAR 和FAR 是屬性詞。NEAR 屬性(段內(nèi)近調(diào)用): 調(diào)用程序和子程序在同一代碼段中,只能被相同代碼段的其他程序調(diào)用。FAR 屬性(段間遠(yuǎn)調(diào)用): 調(diào)用程序和子程序不在同一代碼段中,可以被相同或不同代碼段的程序調(diào)用。
第三行EXPORT 表示Reset_Handler 這個(gè)子程序可供其他模塊調(diào)用。
關(guān)鍵字[WEAK] 表示弱定義,如果編譯器發(fā)現(xiàn)在別處定義了同名的函數(shù),則在鏈接時(shí)用別處的地址進(jìn)行鏈接,如果其它地方?jīng)]有定義,編譯器也不報(bào)錯(cuò),以此處地址進(jìn)行鏈接。
第四行和第五行IMPORT 說明SystemInit 和__main 這兩個(gè)標(biāo)號(hào)在其他文件,在鏈接的時(shí)候需要到其他文件去尋找。
SystemInit 在庫文件system_stm32f10x.c 實(shí)現(xiàn),用來初始化STM32 的一系列時(shí)鐘,把系統(tǒng)時(shí)鐘設(shè)置為72MHZ。STM32 的時(shí)鐘比51 單片機(jī)復(fù)雜,需要經(jīng)過一系列的配置才能達(dá)到穩(wěn)定運(yùn)行的狀態(tài)。
__main 其實(shí)不是我們定義的,當(dāng)編譯器編譯時(shí),只要遇到這個(gè)標(biāo)號(hào)就會(huì)定義這個(gè)函數(shù),該函數(shù)的主要功能是:負(fù)責(zé)初始化棧、堆,配置系統(tǒng)環(huán)境,并在最后跳轉(zhuǎn)到用戶自定義的main 函數(shù),從此來到C 的世界。
第六行把SystemInit 的地址加載到寄存器R0。
第七行程序跳轉(zhuǎn)到R0 中的地址執(zhí)行程序,之后系統(tǒng)的時(shí)鐘就被設(shè)置成72MHZ。
第八行把_main 的地址加載到寄存器R0。
第九行程序跳轉(zhuǎn)到R0 中的地址執(zhí)行程序,執(zhí)行完畢之后就去到我們熟知的C 世界。
第十行表示子程序的結(jié)束。
總結(jié)下就是,Reset_Handler 這個(gè)函數(shù)執(zhí)行了兩個(gè)函數(shù)調(diào)用,一個(gè)是SystemInit,把系統(tǒng)時(shí)鐘設(shè)置成72M,令一個(gè)是__main,初始化好系統(tǒng)環(huán)境,最終調(diào)用C 的main,從此去到C 的世界。
等下我們點(diǎn)亮LED 的時(shí)候采用最簡(jiǎn)單的方法,直接使用內(nèi)部的LSI 時(shí)鐘(8MHZ)作為主時(shí)鐘即可,不使用外部時(shí)鐘LSE。
__main 函數(shù)由編譯器生成,負(fù)責(zé)初始化棧、堆等,并在最后跳轉(zhuǎn)到用戶自定義的main()函數(shù),來到C 的世界。
??3新建main.c
用記事本新建一個(gè)main.c 文件放到工程目錄下,然后把main.c 添加到工程中。
現(xiàn)在我們就可以開始編寫程序了,我們先編寫一個(gè)main 函數(shù),里面啥都沒有,暫時(shí)為空。這時(shí)跟編寫51 程序時(shí)是不是很像。
現(xiàn)在我們可以編譯看看,看看有啥現(xiàn)象。
這時(shí)候出現(xiàn)如下錯(cuò)誤:
錯(cuò)誤提示說SystemInit 沒有定義。從分析啟動(dòng)文件時(shí)我們知道,Reset_Handler 調(diào)用了該函數(shù)用來初始化系統(tǒng)時(shí)鐘,而該函數(shù)是在庫文件system_stm32f10x.c 中實(shí)現(xiàn)的。我們重新寫一個(gè)這樣的函數(shù)也可以,把功能完整實(shí)現(xiàn)一遍,但是為了簡(jiǎn)單起見,我們?cè)趍ain 文件里面定義一個(gè)SystemInit 空函數(shù),為的是騙過編譯器,把這個(gè)錯(cuò)誤去掉。關(guān)于配置系統(tǒng)時(shí)鐘我們?cè)诤竺嬖賹懞?jiǎn)單的代碼。
這時(shí)我們?cè)倬幾g就沒有錯(cuò)了,完美解決。還有一個(gè)方法就是在啟動(dòng)文件中把有關(guān)SystemInit 的代碼注釋掉也可以,代碼如下所示:
??4控制IO 口
下面我們從三個(gè)方面來講解STM32 的IO 在控制LED 時(shí)跟51 的區(qū)別。有關(guān)STM32 的IO 的寄存器介紹,我們可以看《STM32 中文參考手冊(cè)》的第八章即可,下面涉及到的IO寄存器均來自這一章的第二小節(jié):8.2 GPIO 寄存器描述
電平控制
51 單片機(jī)的IO 口如果要輸出1 和0,可以直接賦值,不用控制其他寄存器。
而STM32 的IO 口比較復(fù)雜,如果要輸出1 和0,則要通過控制:端口輸出數(shù)據(jù)寄存器ODR 來實(shí)現(xiàn),ODR 是:Output data register 的簡(jiǎn)寫,在STM32 里面,其寄存器的命名名稱都是英文的簡(jiǎn)寫,很容易記住。從手冊(cè)上我們知道ODR 是一個(gè)32 位的寄存器,低16位有效,高16 位保留。低16 位對(duì)應(yīng)著IO0~IO16,只要往相應(yīng)的位置寫入0 或者1 就可以輸出低或者高電平。
PB0 輸出低電平,代碼如下:
這時(shí)候編譯,我們會(huì)發(fā)現(xiàn)有個(gè)錯(cuò)誤,說GPIOB_ODR 沒有定義,不過我們確實(shí)沒有定義。在51 單片機(jī)中,我們可以直接往P0 口賦值,那是因?yàn)樵趓eg51.h 這個(gè)頭文件中實(shí)現(xiàn)了P0 口這個(gè)寄存器的映像,用的是51 特有的關(guān)鍵字SFR 來定義的。
STM32 跟51 不一樣,沒有SFR,只能用其他的方式來實(shí)現(xiàn)寄存器映像。因?yàn)榧拇嫫鲗?shí)際上就是具有特殊功能的內(nèi)存,那么我們可以通過宏定義來實(shí)現(xiàn)寄存器映像,其實(shí)ST的庫函數(shù)中用的也是這種方法。
從手冊(cè)中我們看到ODR 寄存器的地址偏移是:0CH,這個(gè)偏移地址是基于端口的起始地址而言的。在STM32 中,每個(gè)外設(shè)都有一個(gè)起始地址,叫做外設(shè)基地址,外設(shè)的寄存器就以這個(gè)基地址為標(biāo)準(zhǔn)按照順序排列,跟結(jié)構(gòu)體里面的成員差不多。
在手冊(cè)中的第二章:存儲(chǔ)器和總線構(gòu)架的2.3:存儲(chǔ)器映像小節(jié)中可以查看到所有外設(shè)的基地址,如下:
圖5 STM32 寄存器組起始地址
其中GPIOB 的起始地址是:0X4001 0C00,這樣就可以算出GPIOB_ODR 寄存器的地址是:0X4001 0C00 + 0X0C = 0X4001 0C0C?,F(xiàn)在我們就可以定義GPIOB_ODR 這個(gè)寄存器了,代碼如下:
有了這個(gè)寄存器定義,我們就可以直接操作GPIOB_ODR 了。
方向控制
雖然配置了ODR 寄存器,但是這個(gè)時(shí)候還不能點(diǎn)亮LED,因?yàn)镾TM32 的IO 口還要配置方向,這個(gè)由端口配置寄存器來控制。端口配置寄存器分為高低兩個(gè),每4bit 控制一個(gè)IO 口,所以端口配置低寄存器:CRL 控制這IO 口的低8 位,端口配置高寄存器:CRH控制這IO 口的高8bit。在4 位一組的控制位中,CNFy[1:0] 用來控制端口的輸入輸出,MODEy[1:0]用來控制輸出模式的速率,即輸出時(shí),IO 電平翻轉(zhuǎn)的速度。
輸入有三種模式,輸出有4 中模式,我們?cè)诳刂芁ED 的時(shí)候選擇通用推挽輸出。
輸出速率有三種模式:2M、10M、50M,這里我們選擇2M。
同GPIOB_ODR 一樣,我們也可以算出GPIO_CRL 的地址為:0x40010C00。那么設(shè)置PB0 為通用推挽輸出,輸出速率為2M 的代碼則如下所示:
時(shí)鐘控制
當(dāng)我們?cè)O(shè)置了IO 口的方向,并在相應(yīng)的輸出寄存器里面輸入了值的時(shí)候,以為現(xiàn)在總算可以點(diǎn)亮LED 了吧,其實(shí)還差最后一步。
STM32 外設(shè)很多,為了降低功耗,每個(gè)外設(shè)都對(duì)應(yīng)著一個(gè)時(shí)鐘,在系統(tǒng)復(fù)位的時(shí)候這些時(shí)鐘都是被關(guān)閉的,如果想要外設(shè)工作,必須把相應(yīng)的時(shí)鐘打開。
STM32 的所有外設(shè)的時(shí)鐘由一個(gè)專門的外設(shè)來管理,叫RCC(reset and clockcontrol),RCC 在STM32 中文參考手冊(cè)的第六章。
STM32 的外設(shè)因?yàn)樗俾实牟煌?,分別掛載到三條總系上:AHB、APB2、APB1,APB為高速總線,APB2 次之,APB1 再次之。所以的IO 口都掛載到APB2 總線上,屬于高速外設(shè)。時(shí)鐘由APB2 外設(shè)時(shí)鐘使能寄存器(RCC_APB2ENR)來控制,其中PB 端口的時(shí)鐘由該寄存器的位3 寫1 使能。
同ODR 和CRL,我們可以算出RCC_APB2ENR 的地址為:0x40021018。那么使能PB 口的時(shí)鐘代碼則如下所示:
如果你足夠細(xì)心,你會(huì)發(fā)現(xiàn)我們雖然開了端口時(shí)鐘,那這個(gè)時(shí)鐘到底是多大?時(shí)鐘到底是從哪里來的?
如果我們用的是庫,那么有個(gè)庫函數(shù)SystemInit,會(huì)幫我們把系統(tǒng)時(shí)鐘設(shè)置成72M?,F(xiàn)在我們沒有使用庫,那現(xiàn)在時(shí)鐘是多少?答案是8M,當(dāng)外部HSE 沒有開啟或者出現(xiàn)故障的時(shí)候,系統(tǒng)時(shí)鐘由內(nèi)部低速時(shí)鐘LSI 提供,現(xiàn)在我們是沒有開啟HSE,所以系統(tǒng)默認(rèn)的時(shí)鐘是LSI=8M。至于更深入的細(xì)節(jié)我們?cè)诤竺娴腞CC 時(shí)鐘樹中再詳細(xì)分析。如果你想自己先嘗鮮,那么看RCC 外設(shè)中的:時(shí)鐘控制寄存器(RCC_CR)和時(shí)鐘配置寄存器(RCC_CFGR)這兩個(gè)寄存器即可。
水到渠成
控制了電平,配置了方向,開啟了時(shí)鐘,經(jīng)過這三步,我們總算可以控制一個(gè)LED了。比起51 直接輸出電平,控制STM32 的IO 多了兩步:即配置方向可開啟時(shí)鐘。比起AVR 和PIC 這兩種單片機(jī)則多了開啟時(shí)鐘這一步。
現(xiàn)在我們完整組織下用STM32 控制一個(gè)LED 的代碼:
很多人說學(xué)習(xí)STM32 很難,一堆的寄存器,不知道怎么操作,特別是那些剛學(xué)習(xí)完51 的朋友,不知道怎么過度。這里我們對(duì)比了51 的編程方法,寫了個(gè)簡(jiǎn)單的用STM32 寄存器點(diǎn)亮LED 的方法,希望可以起到拋磚引玉的作用。
4、再接再厲—構(gòu)建庫的雛形學(xué)習(xí)STM32 存在著一個(gè)用寄存器好還是用庫好的爭(zhēng)議點(diǎn),就好比編程是用匯編好還是用C 好一樣。其實(shí)孰優(yōu)孰劣,市場(chǎng)自有定論,用戶群說明一切。
雖然我們上面用寄存器點(diǎn)亮了LED,乍看一下好像代碼也很簡(jiǎn)單,但是我們別僥幸以后就可以一直用寄存器開發(fā)。在用寄存器點(diǎn)亮LED 的時(shí)候,我們是否發(fā)現(xiàn)STM32 的寄存器都是32 位的,在配置的時(shí)候非常容易出錯(cuò),而且代碼還很不好理解。所以學(xué)習(xí)TM32 最好的方法是用庫,然后在庫的基礎(chǔ)上了解底層,看遍所有寄存器。
但是很多人對(duì)庫還是很忌憚,因?yàn)橐婚_始用庫的時(shí)候有很多代碼,很多文件,不知道如何入手。不知道你是否認(rèn)同這么一句話:一切的恐懼都來源于認(rèn)知的空缺。我們對(duì)庫忌憚那是因?yàn)槲覀儾恢朗裁词菐?,不知道庫是怎么?shí)現(xiàn)的。
接下來,我們?cè)诩拇嫫鼽c(diǎn)亮LED 的代碼上繼續(xù)完善,把代碼一層層封裝,實(shí)現(xiàn)庫的最初的雛形,相信經(jīng)過這一步的學(xué)習(xí)后,你會(huì)對(duì)庫的運(yùn)用做到游刃有余。這里我們只講關(guān)于GPIO 庫,其他外設(shè)的我們直接參考庫學(xué)習(xí)即可,不必自己寫。
1定義外設(shè)寄存器結(jié)構(gòu)體
上面我們?cè)诓僮骷拇嫫鞯臅r(shí)候,操作的是寄存器的絕對(duì)地址,如果每個(gè)寄存器都這樣操作,那將非常麻煩。
我們考慮到外設(shè)寄存器的地址都是基于外設(shè)基地址的偏移地址,都是在外設(shè)基地址上逐個(gè)連續(xù)遞增的,每個(gè)寄存器占32 個(gè)或者16 個(gè)字節(jié),這種方式跟結(jié)構(gòu)體里面的成員類似。所以我們可以定義一種外設(shè)結(jié)構(gòu)體,結(jié)構(gòu)體的地址等于外設(shè)的基地址,結(jié)構(gòu)體的成員等于寄存器,成員的排列順序跟寄存器的順序一樣。這樣我們操作寄存器的時(shí)候就不用每次都找到絕對(duì)地址,只要知道外設(shè)的基地址就可以操作外設(shè)的全部寄存器,即操作結(jié)構(gòu)體的成員即可。
下面我們先定義一個(gè)GPIO 寄存器結(jié)構(gòu)體,結(jié)構(gòu)體里面的成員是GPIO 的寄存器,成員的順序按照寄存器的偏移地址從低到高排列,成員類型跟寄存器類型一樣。
在《STM32 中文參考手冊(cè)》8.2 寄存器描述章節(jié),我們可以找到結(jié)構(gòu)體里面的7 個(gè)寄存器描述。在點(diǎn)亮LED 的時(shí)候我們只用了CRL 和ODR 這兩個(gè)寄存器,至于其他寄存器的功能大家可以自行看手冊(cè)了解。
在GPIO 結(jié)構(gòu)體里面我們用了兩個(gè)數(shù)據(jù)類型,一個(gè)是uint32_t,表示無符號(hào)的32 位整型,因?yàn)镚PIO 的寄存器都是32 位的。這個(gè)類型聲明在標(biāo)準(zhǔn)頭文件stdint.h 里面,我們?cè)诔绦蛏现灰@個(gè)頭文件即可。
另外一個(gè)是__IO,這個(gè)是我們自己定義的,原型是volatile,作用就是告訴編譯器不要因優(yōu)化而省略此指令,必須每次都直接讀寫其值,這樣就能確保每次讀或者寫寄存器都真正執(zhí)行到位。
關(guān)于這兩個(gè)數(shù)據(jù)類型,我們添加如下代碼:
2外設(shè)聲明
現(xiàn)在GPIO 寄存器結(jié)構(gòu)體已經(jīng)定義好了,STM32F1 系列的GPIO 端口分A~G,即GPIOA、GPIOB。。。。。。GPIOG。每個(gè)端口都含有GPIO_TypeDef 結(jié)構(gòu)體里面的寄存器,我們可以根據(jù)各個(gè)端口的基地址把GPIO 的各個(gè)端口定義成一個(gè)GPIO_TypeDef 類型的指針,然后我們就可以根據(jù)端口名(實(shí)際上現(xiàn)在是結(jié)構(gòu)體指針了)來操作各個(gè)端口的寄存器,碼實(shí)現(xiàn)如下:
對(duì)于其他外設(shè)我們也可以這樣把外設(shè)的名字定義成一個(gè)外設(shè)寄存器結(jié)構(gòu)體類型的指針,這里我們只講GPIO。
對(duì)于每個(gè)GPIO 的基地址我們可以從《STM32 中文參考手冊(cè)》2.3 小節(jié):存儲(chǔ)器映像中找到,如下所示:
圖6 APB2 總線外設(shè)寄存器起始地址
3外設(shè)內(nèi)存映射
講到基地址的時(shí)候我們?cè)僖艘粋€(gè)知識(shí)點(diǎn):Cortex-M3 存儲(chǔ)器系統(tǒng),這個(gè)知識(shí)點(diǎn)在《Cortex-M3 權(quán)威指南》第5 章里面講到。CM3 的地址空間是4GB,如下圖所示:
圖7 CM3 內(nèi)存映射
我們這里要講的是片上外設(shè),就是我們所說的寄存器的根據(jù)地,其大小總共有512MB,512MB 是其極限空間,并不是每個(gè)單片機(jī)都用得完,實(shí)際上各個(gè)MCU 廠商都只是用了一部分而已。STM32F1 系列用到了:0x4000 0000 ~0x5003 FFFF。
??1APB1、APB2、AHB 總線基地址
現(xiàn)在我們說的STM32 的寄存器就是位于這個(gè)區(qū)域,這里面ST 設(shè)計(jì)了三條總線:AHB、APB2 和APB1,其中AHB 和APB2 是高速總線,APB1 是低速總線。不同的外設(shè)根據(jù)速度不同分別掛載到這三條總線上。從下往上依次是:APB1、APB2、AHB,每個(gè)總線對(duì)應(yīng)的地址分別是:APB1:0x40000000,APB2:0x4001 0000,AHB:0x4001 8000。
這三條總線的基地址我們是從《STM32 中文參考手冊(cè)》2.3 小節(jié)—存儲(chǔ)器映像得到的:APB1 的基地址是TIM2 定時(shí)器的起始地址,APB2 的基地址是AFIO 的起始地址,AHB 的基地址是SDIO 的起始地址。
其中APB1 地址又叫做外設(shè)基地址,是所有外設(shè)的基地址,叫做PERIPH_BASE。
現(xiàn)在我們把這三條總線地址用宏定義出來,以后我們?cè)诙x其他外設(shè)基地址的時(shí)候,只需要在這三條總線的基址上加上偏移地址即可,代碼如下:
??2 GPIO 端口基地址
因?yàn)镚PIO 掛載到APB2 總線上,那么現(xiàn)在我們就可以根據(jù)APB2 的基址算出各個(gè)GPIO 端口的基地址,用宏定義實(shí)現(xiàn)代碼如下:
現(xiàn)在我們把上面的代碼稍微整理下,如下:
在點(diǎn)亮LED 的時(shí)候,我們還開了GPIO 的時(shí)鐘,用到了RCC 這個(gè)外設(shè),現(xiàn)在我們也定義一個(gè)RCC 寄存器結(jié)構(gòu)體,加上那些地址定義,總體代碼如下:
跟GPIO 不同的是,RCC 這個(gè)外設(shè)是掛載到AHB 總線上。
現(xiàn)在我們點(diǎn)亮LED 的函數(shù)就變成了
對(duì)比之前的代碼
一個(gè)用的是結(jié)構(gòu)體,一個(gè)用的是宏,僅僅從這三行代碼看不出有啥區(qū)別,但是如果要操作其他寄存器的時(shí)候,用結(jié)構(gòu)體就可以直接操作,用宏就還要一個(gè)個(gè)找到寄存器的絕對(duì)地址重新定義。
比如我們要操作GPIOB 的BSRR(bit reset register)的時(shí)候,用結(jié)構(gòu)體時(shí)我們就可以這樣操作:
這時(shí)候PB0 就輸出低電平,LED 被點(diǎn)亮。注意:BRR 低16 位有效,只能以字的形式操作,功能是復(fù)位相應(yīng)的IO 口,寫1 清0,寫0 沒有影響。
圖8 GPIO 端口位清除寄存器
現(xiàn)在我們?cè)僬硐麓a,如下所示:
4小結(jié)流程
現(xiàn)在我們來總結(jié)下上面代碼實(shí)現(xiàn)的過程,這個(gè)過程也是我們從零開始點(diǎn)亮LED 的過程,代碼全部由我們自己編寫(除了啟動(dòng)代碼),每一行都有根有據(jù),都可以從《STM32中文參考手冊(cè)》查到。
①、定義一個(gè)外設(shè)(GPIO)寄存器結(jié)構(gòu)體,結(jié)構(gòu)體的成員包含該外設(shè)的所有寄存器,成員的排列順序跟寄存器偏移地址一樣,成員的數(shù)據(jù)類型跟寄存器的一樣。
②外設(shè)內(nèi)存映射,即把地址跟外設(shè)建立起一一對(duì)應(yīng)的關(guān)系。51 單片機(jī)中用SFR 實(shí)現(xiàn),STM32 中用宏定義實(shí)現(xiàn)。
③外設(shè)聲明,即把外設(shè)的名字定義成一個(gè)外設(shè)寄存器結(jié)構(gòu)體類型的指針。
④操作寄存器,實(shí)現(xiàn)點(diǎn)亮LED。
5新建頭文件stm32f10x.h
為了使代碼看起來不那么臃腫,我們這里引入文件的概念,讓不同功能的代碼放在不同的文件里面。在main.c 里面我們只保留main 函數(shù)和一些頭文件,把其他的宏定義放到一個(gè)單獨(dú)的文件。
新建一個(gè)stm32f10x.h,跟寄存器相關(guān)的代碼都放在這里,主要是寄存器映像,跟51單片機(jī)里面的reg51.h 這個(gè)頭文件差不多。然后我們?cè)趍ain.c 里面包含這個(gè)頭文件即可,現(xiàn)在我們的主函數(shù)就變成這樣:
6新建tm32f10x_gpio.h
上面我們?cè)诳刂艷PIO 輸出內(nèi)容的時(shí)候控制的是ODR(Output data register)寄存器,ODR 是一個(gè)16 位的寄存器,必須以字的形式控制,相當(dāng)于51 里面的總線操作。
其實(shí)我們還可以控制BSRR 和BRR 這兩個(gè)寄存器來控制IO 的電平,下面我們簡(jiǎn)單介紹下BRR 寄存器的功能,BSRR 自行看手冊(cè)研究。
BRR:bit reset register
圖9 GPIO 端口位清除寄存器
位清除寄存器BRR 只能實(shí)現(xiàn)位清0 操作,是一個(gè)32 位寄存器,低16 位有效,寫0 沒影響,寫1 清0。
現(xiàn)在我們要使PB0 輸出低電平,點(diǎn)亮LED,則只要往BRR 的BR0 位寫1 即可,其他位為0,代碼如下:
這時(shí)PB0 就輸出了低電平,LED 就被點(diǎn)亮了。
如果要PB2 輸出低電平,則是:
如果要PB3/4/5/6。。。。。。這些IO 輸出低電平呢?道理是一樣的,只要往BRR 的相應(yīng)位置賦不同的值即可。因?yàn)锽RR 是一個(gè)16 位的寄存器,位數(shù)比較多,賦值的時(shí)候容易出錯(cuò),而且從賦值的16 進(jìn)制數(shù)字我們很難清楚的知道控制的是哪個(gè)IO。這時(shí),我們是否可以把BRR 的每個(gè)位置1 都用宏定義來實(shí)現(xiàn),如GPIO_Pin_0 就表示0X0001,GPIO_Pin_2 就表示0X0004。只要我們定義一次,以后都可以使用,而且還見名知意。
GPIO_pins_define 代碼如下:
這時(shí)PB0 就輸出了低電平的代碼就變成了:
為了不使main 函數(shù)看起來冗余,GPIO_pins_define 的代碼不應(yīng)該放在main 里面,因?yàn)槠涫歉鶪PIO 相關(guān)的,我們可以把這些宏放在一個(gè)單獨(dú)的頭文件里面。
在工程目錄下新建stm32f10x_gpio.h,把GPIO_pins_define 代碼放里面,然后把這個(gè)文件添加到工程里面。這時(shí)我們只需要在main.c 里面包含這個(gè)頭文件即可。
7新建stm32f10x_gpio.c
我們點(diǎn)亮LED 的時(shí)候,控制的是PB0 這個(gè)IO,如果LED 接到的是其他IO,我們就需要把GPIOB 修改成其他的端口,其實(shí)這樣修改起來也很快很方便。但是為了提高程序的可讀性和可移植性,我們是否可以編寫一個(gè)專門的函數(shù)用來復(fù)位GPIO 的某個(gè)位,這個(gè)函數(shù)有兩個(gè)形參,一個(gè)是GPIOX(X=A...G),另外一個(gè)是GPIO_Pin(0...15),函數(shù)的主體則是根據(jù)形參GPIOX 和GPIO_Pin 來控制BRR 寄存器,代碼如下:
這時(shí),PB0 輸出低電平,點(diǎn)亮LED 的代碼就變成了:
同樣,因?yàn)檫@個(gè)函數(shù)是控制GPIO 的函數(shù),我們可以新建一個(gè)專門的文件來放跟gpio有關(guān)的函數(shù)。
在工程目錄下新建stm32f10x_gpio.c,把GPIO 相關(guān)的函數(shù)放里面。
這時(shí)我們是否發(fā)現(xiàn)剛剛新建了一個(gè)頭文件stm32f10x_gpio.h,這兩個(gè)文件存放的都是跟外設(shè)GPIO 相關(guān)的。C 文件里面的函數(shù)會(huì)用到h 頭文件里面的定義,這兩個(gè)文件是相輔相成的,故我們?cè)趕tm32f10x_gpio.c 文件中也包含stm32f10x_gpio.h 這個(gè)頭文件。別忘了把stm32f10x.h 這個(gè)頭文件也包含進(jìn)去,因?yàn)橛嘘P(guān)寄存器的所有定義都在這個(gè)頭文件里面。
如果我們寫其他外設(shè)的函數(shù),我們也應(yīng)該跟GPIO 一樣,新建兩個(gè)文件專門來存函數(shù),比如RCC 這個(gè)外設(shè)我們可以新建stm32f10x_rcc.c 和stm32f10x_rcc.h。其他外依葫蘆畫瓢即可。
stm32f10x_gpio.c 文件代碼如下:
我們還要記得把void GPIO_ResetBits()在stm32f10x_gpio.h 里面聲明下,這樣其他文件只要包含stm32f10x_gpio.h 這個(gè)頭文件就可以使用GPIO_ResetBits()這個(gè)函數(shù)了。以后不論新增加了什么函數(shù)都應(yīng)該在自己的頭文件下聲明,這是個(gè)C 語言的常識(shí)問題。
點(diǎn)亮LED 會(huì)了,那關(guān)閉LED 怎么辦,我們可以控制BSRR 這個(gè)寄存器來實(shí)現(xiàn),這里我就直接寫代碼了:
先寫一個(gè)GPIO 端口置位函數(shù),放到stm32f10x_gpio.c 文件中,同樣在stm32f10x_gpio.h 頭文件聲明。
PB0 輸出高電平,關(guān)閉LED,代碼如下:
現(xiàn)在我們?cè)賮砜纯磎ain 函數(shù),看看點(diǎn)亮LED 的代碼是如何一步一步進(jìn)化的:
8小結(jié)
我們從寄存器映像開始,把內(nèi)存跟寄存器建立起一一對(duì)應(yīng)的關(guān)系,然后操作寄存器點(diǎn)亮LED,再到把寄存器操作封裝成一個(gè)個(gè)函數(shù)。為了把不同外設(shè)的函數(shù)歸類,我們引入了相應(yīng)的文件來放這些函數(shù),這一步一步走來,我們實(shí)現(xiàn)了庫最簡(jiǎn)單的雛形,知道庫是怎么來的。后面的工作就是不斷的增加操作外設(shè)的函數(shù),并且把所有的外設(shè)都寫完,這樣一個(gè)完整的庫就實(shí)現(xiàn)了。
什么是庫,這就是庫。
下面我們用一張圖來描述下我們剛剛的代碼,讓大家有一個(gè)整體的把握。
5、新的嘗試—用庫函數(shù)點(diǎn)亮LED
1新建工程
??1新建本地工程文件夾
為了工程目錄更加清晰,我們?cè)诒镜?a target="_blank">電腦上新建6 個(gè)文件夾,具體如下:
表格1 工程目錄文件夾清單
圖10 工程文件夾目錄
在本地新建好文件夾后,把準(zhǔn)備好的庫文件添加到相應(yīng)的文件夾下:
表格2 工程目錄文件夾內(nèi)容清單
??2新建工程
打開KEIL5,新建一個(gè)工程,工程名根據(jù)喜好命名,我這里取LED-LIB,保存在ProjectRVMDK(uv4)文件夾下。
選擇CPU 型號(hào)
這個(gè)根據(jù)你開發(fā)板使用的CPU 具體的型號(hào)來選擇,比如MINI 選STM32F103VE,ISO 選STM32F103ZE。
圖11 選擇具體的CPU 型號(hào)
在線添加庫文件
等下我們手動(dòng)添加庫文件,這里我們點(diǎn)擊關(guān)掉。
圖12 庫文件管理
添加組文件夾
在新建的工程中添加5 個(gè)組文件夾,用來存放各種不同的文件,文件從本地建好的工程文件夾下獲?。?/span>
表格3 工程內(nèi)組文件夾內(nèi)容清掉
圖13 如何在工程中添加文件夾
配置魔術(shù)棒選項(xiàng)卡
這一步的配置工作很重要,很多人串口用不了printf 函數(shù),編譯有問題,下載有問題,都是這個(gè)步驟的配置出了錯(cuò)。
①在Target 中選中微庫,為的是在日后編寫串口驅(qū)動(dòng)的時(shí)候可以使用printf 函數(shù)
圖14 添加微庫
②在Output 選項(xiàng)卡中把輸出文件夾定位到我們工程目錄下的output 文件夾,如果想在編譯的過程中生成hex 文件,那么那Create HEX File 選項(xiàng)勾上。
圖15 配置Output 選項(xiàng)卡
③在Listing 選項(xiàng)卡中把輸出文件夾定位到我們工程目錄下的Listing 文件夾。
圖16 配置Listing 選項(xiàng)卡
④在C/C++選項(xiàng)卡中添加處理宏,和編譯器編譯的時(shí)候查找的頭文件路徑。
STM32F10X_HD:這個(gè)宏是為了區(qū)分使用STM32F103 系列中不同容量型號(hào)的單片機(jī)庫。我們用的單片機(jī)的FLASH 的容量都是512K,屬于大容量
STM32F10X_HD:FLASH 大小在256K~512K 之間的STM32F101xx 和STM32F103xx控制器。STM32F10X_MD:FLASH 大小在64K~128K 之間的STM32F101xx 和STM32F103xx 控制器。STM32F10X_LD:FLASH 大小在16K~32K 之間的STM32F101xx和STM32F103xx 控制器。
USE_STDPERIPH_DRIVER:為了包含stm32f10x_conf.h 這個(gè)頭文件。
在編譯器中添加宏的好處就是,只要用了這個(gè)模版,就不用源文件中修改代碼或者添加頭文件。
圖17 配置C/C++ 選項(xiàng)卡
Include Paths 這里添加的是頭文件的路徑,如果編譯的時(shí)候提示說找不到頭文件,一般就是這里配置出了問題。你把頭文件放到了哪個(gè)文件夾,就把該文件夾添加到這里即可。
下載器配置
這部分的配置最好是在安裝好下載器驅(qū)動(dòng),下載器連接了電腦和開發(fā)板,且開發(fā)板上電后來配置。
這里面需要根據(jù)你使用了什么仿真器來配置,常用的有三種仿真器:JLINK/ARMOB,ST-LINK,ULINK2,而且這個(gè)配置不是配置完一次之后以后就不會(huì)改變,當(dāng)你換了芯片型號(hào),或者其他操作(具體原因不明)都會(huì)改變下載器的配置。
①JLINK/ARM-OB 配置
要先安裝了JLINK 驅(qū)動(dòng)之后,該配置才能下載,兩者缺一不可。
圖18 JLINK/ARM-OB 下載配置
②ST-LINK 配置
要先安裝了ST-LINK 驅(qū)動(dòng)之后,該配置才能下載,兩者缺一不可。
圖19 ST-LINK 下載配置
③ULINK2 配置
要先安裝了ULINK2 驅(qū)動(dòng)之后,該配置才能下載,兩者缺一不可。要注意的是設(shè)置成ULINK2,而不是ULINK。
圖20 ULINK2 下載配置
選擇CPU 型號(hào)
這一步的配置也不是配置一次之后完事,常常會(huì)因?yàn)楦鞣N原因需要重新選擇,當(dāng)你下載的時(shí)候,提示說找不到Device 的時(shí)候,請(qǐng)確保該配置是否正確。有時(shí)候下載程序之后,不會(huì)自動(dòng)運(yùn)行,要手動(dòng)復(fù)位的時(shí)候,也回來看看這里的Reset and Run 配置是否失效。MINI 和ISO 用的STM32 的FLASH 都是512K,所以選擇512K 大容量,如果使用的是其他型號(hào)的,要根據(jù)實(shí)際情況選擇。
2固件庫分析
在寫代碼之前,我們先來分析下固件庫,看看每個(gè)文件的作用是什么,這對(duì)我們能否清晰的調(diào)用庫函數(shù)編程非常重要。
STM32 由Cortex-M3 內(nèi)核和內(nèi)核之外的各種外設(shè)組成,庫在編寫的時(shí)候也遵循這中組成結(jié)構(gòu),把代碼分成兩大部分,一種是操作內(nèi)核外設(shè)的,另外一種是內(nèi)核之外的外設(shè),為了聽起來不那么繞,下面我們把內(nèi)核之外的外設(shè)用處理器外設(shè)來代替。
下面我們大概分析下每個(gè)文件的作用。
??1處理器相關(guān)
startup_stm32f10x_hd.s
這個(gè)是由匯編編寫的啟動(dòng)文件,是STM32 上電啟動(dòng)的第一個(gè)程序,啟動(dòng)文件主要實(shí)現(xiàn)了:1、初始化堆棧指針SP;2、設(shè)置PC 指針=Reset_Handler ;3、設(shè)置向量表的地址,并初始化向量表,向量表里面放的是STM32 所有中斷函數(shù)的入口地址4、調(diào)用庫函數(shù)SystemInit,把系統(tǒng)時(shí)鐘配置成72M,SystemInit 在庫文件stytem_stm32f10x.c 中定義;5、跳轉(zhuǎn)到標(biāo)號(hào)_main,最終去到C 的世界。
system_stm32f10x.c
這個(gè)文件的作用是里面實(shí)現(xiàn)了各種常用的系統(tǒng)時(shí)鐘設(shè)置函數(shù),有72M,56M,48,36,24,8M,我們使用的是是把系統(tǒng)時(shí)鐘設(shè)置成72M。
Stm32f10x.h
這個(gè)頭文件非常重要,可以說是上帝之手。這個(gè)頭文件實(shí)現(xiàn)了:1、處理器外設(shè)寄存器的結(jié)構(gòu)體定義2、處理器外設(shè)的內(nèi)存映射3、處理器外設(shè)寄存器的位定義。
關(guān)于1 和2 我們?cè)谟眉拇嫫鼽c(diǎn)亮LED 的時(shí)候有講解。其中3:處理器外設(shè)寄存器的位定義,這個(gè)非常重要,具體是什么意思?我們知道一個(gè)寄存器有很多個(gè)位,每個(gè)位寫1 或者寫0 的功能都是不一樣的,處理器外設(shè)寄存器的位定義就是把外設(shè)的每個(gè)寄存器的每一個(gè)位寫1 的16 進(jìn)制數(shù)定義成一個(gè)宏,宏名即用該位的名稱表示,如果我們操作寄存器要開啟某一個(gè)功能的話,就不用自己親自去算這個(gè)值是多少,可以直接到這個(gè)頭文件里面找。
我們以片上外設(shè)ADC 為例,假設(shè)我們要啟動(dòng)ADC 開始轉(zhuǎn)換,根據(jù)手冊(cè)我們知道是要控制ADC_CR2 寄存器的位0:ADON,即往位0 寫1,即:ADC->CR2=0x00000001;這是一般的操作方法?,F(xiàn)在這個(gè)頭文件里面有關(guān)于ADON 位的位定義:
#define ADC_CR2_ADON ((uint32_t)0x00000001),有了這個(gè)位定義,我們剛剛的代碼就變成了:ADC->CR2=ADC_CR2_ADON。這對(duì)于我們編程是何其方便,簡(jiǎn)直就是天降救星,感激之情無以言表。
無論是寄存器編程還是固件庫編程,都必須包含這個(gè)頭文件,有關(guān)外設(shè)寄存器的說明都在這里面。
stm32f10x_xxx.h
stm32f10x_xxx.h:外設(shè)xxx 應(yīng)用函數(shù)庫頭文件,這里面主要定義了實(shí)現(xiàn)外設(shè)某一功能的結(jié)構(gòu)體,比如通用定時(shí)器有很多功能,有定時(shí)功能,有輸出比較功能,有輸入捕捉功能,而通用定時(shí)器有非常多的寄存器要實(shí)現(xiàn)某一個(gè)功能,比如定時(shí)功能,我們根本不知道具體要操作哪些寄存器,這個(gè)頭文件就為我們打包好了要實(shí)現(xiàn)某一個(gè)功能的寄存器,是以機(jī)構(gòu)體的形式定義的,比如通用定時(shí)器要實(shí)現(xiàn)一個(gè)定時(shí)的功能,我們只需要初始化TIM_TimeBaseInitTypeDef 這個(gè)結(jié)構(gòu)體里面的成員即可,里面的成員就是定時(shí)所需要操作的寄存器。有了這個(gè)頭文件,我們就知道要實(shí)現(xiàn)某個(gè)功能需要操作哪些寄存器,然后再回手冊(cè)中精度這些寄存器的說明即可。
stm32f10x_xxx.c
stm32f10x_xxx.c:外設(shè)xxx 應(yīng)用函數(shù)庫,這里面寫好了操作xxx 外設(shè)的所有常用的函數(shù),我們使用庫編程的時(shí)候,使用的最多的就是這里的函數(shù)。
??2內(nèi)核相關(guān)
cor_cm3.h
這個(gè)頭文件實(shí)現(xiàn)了:1、內(nèi)核結(jié)構(gòu)體寄存器定義2、內(nèi)核寄存器內(nèi)存映射3、內(nèi)存寄存器位定義。跟處理器相關(guān)的頭文件stm32f10x.h 實(shí)現(xiàn)的功能一樣,一個(gè)是針對(duì)內(nèi)核的寄存器,一個(gè)是針對(duì)內(nèi)核之外,即處理器的寄存器。
misc.h
內(nèi)核應(yīng)用函數(shù)庫頭文件,對(duì)應(yīng)stm32f10x_xxx.h。
misc.c
內(nèi)核應(yīng)用函數(shù)庫文件,對(duì)應(yīng)stm32f10x_xxx.c。在CM3 這個(gè)內(nèi)核里面還有一些功能組件,如NVIC、SCB、ITM、MPU、CoreDebug,CM3 帶有非常豐富的功能組件,但是芯片廠商在設(shè)計(jì)MCU 的時(shí)候有一些并不是非要不可的,是可裁剪的,比如MPU、ITM 等在STM32 里面就沒有。其中NVIC 在每一個(gè)CM3 內(nèi)核的單片機(jī)中都會(huì)有,但都會(huì)被裁剪,只能是CM3 NVIC 的一個(gè)子集。在NVIC 里面還有一個(gè)SysTick,是一個(gè)系統(tǒng)定時(shí)器,可以提供時(shí)基,一般為操作系統(tǒng)定時(shí)器所用。
misc.h 和mics.c 這兩個(gè)文件提供了操作這些組件的函數(shù),并可以在CM3 內(nèi)核單片機(jī)直接移植。
3開始寫代碼
??1 如何管理庫的頭文件
這么多的庫文件,如何調(diào)用,如何管理?當(dāng)我們開始調(diào)用庫函數(shù)寫代碼的時(shí)候,有些庫我們不需要,在編譯的時(shí)候可以不編譯,可以通過一個(gè)總的頭文件stm32f10x_conf.h 來控制,該頭文件主要代碼如下:
代碼1 stm32f10x_conf.h 頭文件代碼
這里面包含了全部外設(shè)的頭文件,點(diǎn)亮一個(gè)LED 我們只需要RCC 和GPIO 這兩個(gè)外設(shè)的庫函數(shù)即可,其中RCC 控制的是時(shí)鐘,GPIO 控制的具體的IO 口。所以其他外設(shè)庫函數(shù)的頭文件我們注釋掉,當(dāng)我們需要的時(shí)候就把相應(yīng)頭文件的注釋去掉即可。
stm32f10x_conf.h 這個(gè)頭文件在stm32f10x.h 這個(gè)頭文件的最后面被包含,在第8296行:
代碼的意思是,如果定義了USE_STDPERIPH_DRIVER 這個(gè)宏的話,就包含stm32f10x_conf.h 這個(gè)頭文件。我們?cè)谛陆üこ痰臅r(shí)候,在魔術(shù)棒選項(xiàng)卡C/C++中,我們定義了USE_STDPERIPH_DRIVER 這個(gè)宏,所以stm32f10x_conf.h 這個(gè)頭文件就被stm32f10x.h 包含了,我們?cè)趯懗绦虻臅r(shí)候只需要調(diào)用一個(gè)頭文件:stm32f10x.h 即可。
??2編寫LED 初始化函數(shù)
經(jīng)過寄存器點(diǎn)亮LED 的操作,我們知道操作一個(gè)GPIO 輸出的編程要點(diǎn)大概如下:
1、開啟GPIO 的端口時(shí)鐘
2、選擇要具體控制的IO 口,即pin
3、選擇IO 口輸出的速率,即speed
4、選擇IO 口輸出的模式,即mode
5、輸出高/低電平
STM32 的時(shí)鐘功能非常豐富,配置靈活,為了降低功耗,每個(gè)外設(shè)的時(shí)鐘都可以獨(dú)自的關(guān)閉和開啟。STM32 中跟時(shí)鐘有關(guān)的功能都由RCC 這個(gè)外設(shè)控制,RCC 中有三個(gè)寄存器控制著所以外設(shè)時(shí)鐘的開啟和關(guān)閉:RCC_APHENR、RCC_APB2ENR 和RCC_APB1ENR,AHB、APB2 和APB1 代表著三條總線,所有的外設(shè)都是掛載到這三條總線上,GPIO 屬于高速的外設(shè),掛載到APB2 總線上,所以其時(shí)鐘有RCC_APB2ENR 控制。
GPIO 時(shí)鐘控制
固件庫函數(shù):RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE)函數(shù)的原型為:
當(dāng)程序編譯一次之后,把光標(biāo)定位到函數(shù)/變量/宏定義處,按鍵盤的F12 或鼠標(biāo)右鍵的Go to definition of,就可以找到原型。固件庫的底層操作的就是RCC 外設(shè)的APB2ENR這個(gè)寄存器,宏RCC_APB2Periph_GPIOB 的原型是:0x00000008,即(1<<3),還原成寄存器操作就是:RCC->APB2ENR |= 1<<<3。相比固件庫操作,寄存器操作的代碼可讀性就很差,只有才查閱寄存器配置才知道具體代碼的功能,而固件庫操作恰好相反,見名知意。
GPIO 端口配置
GPIO 的pin,速度,模式,都由GPIO 的端口配置寄存器來控制,其中IO0~IO7 由端口配置低寄存器CRL 控制,IO8~IO15 由端口配置高寄存器CRH 配置。
寄存器方式
相比寄存器一句話的代碼,固件庫的操作就顯得有些復(fù)雜,但換來的是簡(jiǎn)單明了。固件庫把端口配置的pin,速度和模式封裝成一個(gè)結(jié)構(gòu)體:
pin 可以是GPIO_Pin_0~GPIO_Pin_15 或者是GPIO_Pin_All,這些都是庫預(yù)先定義好的宏。
speed 也被封裝成一個(gè)結(jié)構(gòu)體:
速度可以是10M,2M 或者50M,這個(gè)由端口配置寄存器的MODE 位控制,速度是針對(duì)IO 口輸出的時(shí)候而言,在輸入的時(shí)候可以不用設(shè)置。
mode 也被封裝成一個(gè)結(jié)構(gòu)體:
IO 口的模式有8 種,輸入輸出各4 種,由端口配置寄存器的CNF 配置。平時(shí)用的最多的就是通用推挽輸出,可以輸出高低電平,驅(qū)動(dòng)能力大,一般用于接數(shù)字器件。至于剩下的七種模式的用法和電路原理,我們?cè)诤竺娴腉PIO 章節(jié)再詳細(xì)講解。
所以GPIO 端口的配置,最終用固件庫實(shí)現(xiàn)就變成這樣:
配置好pin,speed,mode 之后,我們最后調(diào)用庫函數(shù)GPIO_Init()把剛剛的參數(shù)寫到CRL 或者CRH 這兩個(gè)寄存器中。
GPIO 輸出控制
GPIO 輸出控制,可以通過端口數(shù)據(jù)輸出寄存器ODR、端口位設(shè)置/清除寄存器BSRR和端口位清除寄存器BRR 這三個(gè)來控制。
端口輸出寄存器ODR 是一個(gè)32 位的寄存器,低16 位有效,對(duì)應(yīng)著IO0~IO15,只能以字的形式操作,不能單獨(dú)對(duì)某一個(gè)位置位/清除。
代碼2 寄存器操作ODR
圖21 ODR 寄存器
端口位清除寄存器BRR 是一個(gè)32 位的寄存器,低十六位有效,對(duì)應(yīng)著IO0~IO15,只能以字的形式操作,可以單獨(dú)對(duì)某一個(gè)位操作,寫1 清0。
代碼3 寄存器操作BRR
代碼4 固件庫操作BRR
圖22 BRR 寄存器
BSRR 是一個(gè)32 位的寄存器,低16 位用于置位,寫1 有效,高16 位用于復(fù)位,寫1有效,相當(dāng)于BRR 寄存器。高16 位我們一般不用,而是操作BRR 這個(gè)寄存器,所以BSRR 這個(gè)寄存器一般用來置位操作。
代碼5 固件庫操作BSRR
圖23 BSRR 寄存器
LED GPIO 初始化函數(shù)
代碼6 寄存器LED GPIO 初始化函數(shù)
代碼7 固件庫LED GPIO 初始化函數(shù)
軟件延時(shí)
簡(jiǎn)單的通過軟件來延時(shí),具體時(shí)間不確定,并不能像51 那么通過計(jì)算每條指令執(zhí)行的時(shí)間來確切的計(jì)算延時(shí)時(shí)間。要想精確延時(shí),必須通過定時(shí)器實(shí)現(xiàn)。
主函數(shù)
初始化LED 用到的GPIO,在while 死循環(huán)中讓LED 閃爍。在程序來到main 函數(shù)前,系統(tǒng)時(shí)鐘已經(jīng)初始化成了72M,有關(guān)時(shí)鐘部分我們?cè)赗CC 這個(gè)章節(jié)中會(huì)詳細(xì)講解,這里不是重點(diǎn)。
GPIO 其他庫函數(shù)
有關(guān)GPIO 的其他庫函數(shù),我們可以在stm32f10x_gpio.h 中找到聲明,然后在stm32f10x_gpio.c 中找到函數(shù)的原型,根據(jù)函數(shù)的注釋,可以知道每個(gè)函數(shù)的作用。閱讀這些庫函數(shù)的時(shí)候,最好配合《STM32 中文參考手冊(cè)》寄存器描述部分一起看,這樣學(xué)習(xí)的效果會(huì)非常好。
-
PWM控制
+關(guān)注
關(guān)注
7文章
191瀏覽量
26240 -
C51
+關(guān)注
關(guān)注
5文章
282瀏覽量
57922
原文標(biāo)題:如何從快速51單片機(jī)轉(zhuǎn)戰(zhàn)STM32?
文章出處:【微信號(hào):fcsde-sh,微信公眾號(hào):fcsde-sh】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論