以STM32F103VET6為例,。 芯片正面是絲印,, ARM 應(yīng)該是表示該芯片使用的是 ARM 的內(nèi)核, STM32F103VET6 是芯片型號,,后面的字應(yīng)該是跟生產(chǎn)批次相關(guān),,最上面的是 ST 的 LOGO。 芯片四周是引腳,,左下角的小圓點(diǎn)表示 1 腳,,然后從 1 腳起按照逆時針的順序排列(所有芯片的引腳順序都是逆時針排列的)。開發(fā)板中把芯片的引腳引出來,,連接到各種傳感器上,,然后在 STM32 上編程(實(shí)際就是通過程序控制這些引腳輸出高電平或者低電平)來控制各種傳感器工作,通過做實(shí)驗(yàn)的方式來學(xué)習(xí) STM32 芯片的各個資源,。 開發(fā)板是一種評估板,,板載資源非常豐富,引腳復(fù)用比較多,,力求在一個板子上驗(yàn)證芯片的全部功能 圖 6-1 STM32F103VET6 實(shí)物圖(紅色框中部分) 圖 6-2 STM32F103VET6 正面引腳圖 6.3 芯片里面有什么我們看到的 STM32 芯片是已經(jīng)封裝好的成品,,主要由內(nèi)核和片上外設(shè)組成。若與電腦類比,,內(nèi)核與外設(shè)就如同電腦上的 CPU 與主板,、內(nèi)存、顯卡,、硬盤的關(guān)系,。 STM32F103 采用的是 Cortex-M3 內(nèi)核,內(nèi)核即 CPU,,由 ARM 公司設(shè)計(jì),。 ARM 公司并不生產(chǎn)芯片,,而是出售其芯片技術(shù)授權(quán)。芯片生產(chǎn)廠商(SOC)如 ST,、 TI,、 Freescale,負(fù)責(zé)在內(nèi)核之外設(shè)計(jì)部件并生產(chǎn)整個芯片,,這些內(nèi)核之外的部件被稱為核外外設(shè)或片上外設(shè),。如 GPIO、 USART(串口),、 I2C,、 SPI 等都叫做片上外設(shè)。具體見圖 6-3,。 圖 6-3 STM32 芯片架構(gòu)簡圖 芯片(這里指內(nèi)核,,或者叫 CPU)和外設(shè)之間通過各種總線連接,,其中驅(qū)動單元有 4個,,被動單元也有 4 個,具體見圖 6-4,。為了方便理解,,我們都可以把驅(qū)動單元理解成是CPU 部分,被動單元都理解成外設(shè),。下面我們簡單介紹下驅(qū)動單元和被動單元的各個部件 1. ICode 總線 ICode 中的 I 表示 Instruction,,即指令。我們寫好的程序編譯之后都是一條條指令,,存放在 FLASH 中,,內(nèi)核要讀取這些指令來執(zhí)行程序就必須通過 ICode 總線,它幾乎每時每刻都需要被使用,,它是專門用來取指的,。 2. 驅(qū)動單元 DCode 總線 DCode 中的 D 表示 Data,即數(shù)據(jù),,那說明這條總線是用來取數(shù)的,。我們在寫程序的時候,數(shù)據(jù)有常量和變量兩種,,常量就是固定不變的,,用 C 語言中的 const 關(guān)鍵字修飾,是放到內(nèi)部的 FLASH 當(dāng)中的,,變量是可變的,,不管是全局變量還是局部變量都放在內(nèi)部的SRAM。因?yàn)閿?shù)據(jù)可以被 Dcode 總線和 DMA 總線訪問,,所以為了避免訪問沖突,,在取數(shù)的時候需要經(jīng)過一個總線矩陣來仲裁,,決定哪個總線在取數(shù)。 系統(tǒng)總線 系統(tǒng)總線主要是訪問外設(shè)的寄存器,,我們通常說的寄存器編程,,即讀寫寄存器都是通過這根系統(tǒng)總線來完成的。 DMA 總線 DMA 總線也主要是用來傳輸數(shù)據(jù),,這個數(shù)據(jù)可以是在某個外設(shè)的數(shù)據(jù)寄存器,,可以在SRAM,可以在內(nèi)部的 FLASH,。因?yàn)閿?shù)據(jù)可以被 Dcode 總線和 DMA 總線訪問,,所以為了避免訪問沖突,在取數(shù)的時候需要經(jīng)過一個總線矩陣來仲裁,,決定哪個總線在取數(shù),。 3. 被動單元 內(nèi)部的閃存存儲器 內(nèi)部的閃存存儲器即 FLASH,我們編寫好的程序就放在這個地方,。內(nèi)核通過 ICode 總線來取里面的指令,。 內(nèi)部的 SRAM 內(nèi)部的 SRAM,即我們通常說的 RAM,,程序的變量,,堆棧等的開銷都是基于內(nèi)部的SRAM。內(nèi)核通過 DCode 總線來訪問它,。 FSMC FSMC 的英文全稱是 Flexible static memory controller,,叫靈活的靜態(tài)的存儲器控制器,是 STM32F10xx 中一個很有特色的外設(shè),,通過 FSMC,,我們可以擴(kuò)展內(nèi)存,如外部的SRAM,, NANDFLASH 和 NORFLASH,。但有一點(diǎn)我們要注意的是, FSMC 只能擴(kuò)展靜態(tài)的內(nèi)存,,即名稱里面的 S: static,,不能是動態(tài)的內(nèi)存,比如 SDRAM 就不能擴(kuò)展,。 AHB 到 APB 的橋 從 AHB 總線延伸出來的兩條 APB2 和 APB1 總線,,上面掛載著 STM32 各種各樣的特色外設(shè)。我們經(jīng)常說的 GPIO,、串口,、 I2C、 SPI 這些外設(shè)就掛載在這兩條總線上,,這個是我們學(xué)習(xí) STM32 的重點(diǎn),,就是要學(xué)會編程這些外設(shè)去驅(qū)動外部的各種設(shè)備,。 圖 6-4 STM32F10xx 系統(tǒng)框圖(不包括互聯(lián)型) 6.4 存儲器映射在圖 6-4 中,被控單元的 FLASH,, RAM,, FSMC 和 AHB 到 APB 的橋(即片上外設(shè)),這些功能部件共同排列在一個 4GB 的地址空間內(nèi),。我們在編程的時候,,可以通過他們的地址找到他們,然后來操作他們(通過 C 語言對它們進(jìn)行數(shù)據(jù)的讀和寫),。 6.4.1 存儲器映射 存儲器本身不具有地址信息,,它的地址是由芯片廠商或用戶分配,給存儲器分配地址的過程就稱為存儲器映射,,具體見圖 6-5,。 圖 6-5 存儲器映射(摘自參考手冊-存儲器映射章節(jié)) 1. 存儲器區(qū)域功能劃分 在這 4GB 的地址空間中, ARM 已經(jīng)粗線條的平均分成了 8 個塊,,每塊 512MB,,每個塊也都規(guī)定了用途,具體分類見表格 6-1,。每個塊的大小都有 512MB,,顯然這是非常大的,,芯片廠商在每個塊的范圍內(nèi)設(shè)計(jì)各具特色的外設(shè)時并不一定都用得完,,都是只用了其中的一部分而已 在這 8 個 Block 里面,有 3 個塊非常重要,,也是我們最關(guān)心的三個塊,。 Block0 用來設(shè)計(jì)成內(nèi)部 FLASH, Block1 用來設(shè)計(jì)成內(nèi)部 RAM,, Block2 用來設(shè)計(jì)成片上的外設(shè),,下面我們簡單的介紹下這三個 Block 里面的具體區(qū)域的功能劃分。 存儲器 Block0 內(nèi)部區(qū)域功能劃分 Block0 主要用于設(shè)計(jì)片內(nèi)的 FLASH,, STM32F103ZET6和STM32F103VET6的 FLASH 都是 512KB,,屬于大容量。要在芯片內(nèi)部集成更大的 FLASH 或者 SRAM 都意味著芯片成本的增加,,往往片內(nèi)集成的 FLASH 都不會太大,,ST 能在追求性價比的同時做到 512KB,實(shí)乃良心之舉,。 Block 內(nèi)部區(qū)域的功能劃分具體見表格 6-2,。 儲存器 Block1 內(nèi)部區(qū)域功能劃分 Block1 用 于 設(shè) 計(jì) 片 內(nèi) 的 SRAM 。 STM32F103ZET6和STM32F103VET6的 SRAM 都是 64KB,, Block 內(nèi)部區(qū)域的功能劃分具體見表格6-3 儲存器 Block2 內(nèi)部區(qū)域功能劃分 Block2 用于設(shè)計(jì)片內(nèi)的外設(shè),,根據(jù)外設(shè)的總線速度不同,, Block 被分成了 APB 和 AHB兩部分,其中 APB 又被分為 APB1 和 APB2,,具體見表格 6-4,。 6.5 寄存器映射我們知道,存儲器本身沒有地址,,給存儲器分配地址的過程叫存儲器映射,,那什么叫寄存器映射?寄存器到底是什么,? 在存儲器 Block2 這塊區(qū)域,,設(shè)計(jì)的是片上外設(shè),它們以四個字節(jié)為一個單元,,共32bit,,每一個單元對應(yīng)不同的功能,當(dāng)我們控制這些單元時就可以驅(qū)動外設(shè)工作,。我們可以找到每個單元的起始地址,,然后通過 C 語言指針的操作方式來訪問這些單元,如果每次都是通過這種地址的方式來訪問,,不僅不好記憶還容易出錯,,這時我們可以根據(jù)每個單元功能的不同,以功能為名給這個內(nèi)存單元取一個別名,,這個別名就是我們經(jīng)常說的寄存器,,這個給已經(jīng)分配好地址的有特定功能的內(nèi)存單元取別名的過程就叫寄存器映射。 比如,,我們找到 GPIOB 端口的輸出數(shù)據(jù)寄存器 ODR 的地址是 0x4001 0C0C(至于這個地址如何找到可以先跳過,,后面我們會有詳細(xì)的講解), ODR 寄存器是 32bit,,低 16bit有效,,對應(yīng)著 16 個外部 IO,寫 0/1 對應(yīng)的的 IO 則輸出低/高電平?,F(xiàn)在我們通過 C 語言指針的操作方式,,讓 GPIOB 的 16 個 IO 都輸出高電平,具體見代碼 6-1,。 0x4001 0C0C 在我們看來是 GPIOB 端口 ODR 的地址,,但是在編譯器看來,這只是一個普通的變量,,是一個立即數(shù),,要想讓編譯器也認(rèn)為是指針,我們得進(jìn)行強(qiáng)制類型轉(zhuǎn)換,把它轉(zhuǎn)換成指針,,即(unsigned int *)0x4001 0C0C,,然后再對這個指針進(jìn)行 * 操作。 剛剛我們說了,,通過絕對地址訪問內(nèi)存單元不好記憶且容易出錯,,我們可以通過寄存器的方式來操作,具體見代碼 6-2,。 為了方便操作,,我們干脆把指針操作“*”也定義到寄存器別名里面,具體見代碼 6-3,。 6.5.1 STM32 的外設(shè)地址映射片上外設(shè)區(qū)分為三條總線,,根據(jù)外設(shè)速度的不同,不同總線掛載著不同的外設(shè),, APB1掛載低速外設(shè),, APB2 和 AHB 掛載高速外設(shè)。相應(yīng)總線的最低地址我們稱為該總線的基地址,,總線基地址也是掛載在該總線上的首個外設(shè)的地址,。其中 APB1 總線的地址最低,片上外設(shè)從這里開始,,也叫外設(shè)基地址,。 1. 總線基地址 表格 6-5 的“相對外設(shè)基地址偏移”即該總線地址與“片上外設(shè)”基地址 0x4000 0000的差值。關(guān)于地址的偏移我們后面還會講到,。 2. 外設(shè)基地址 總線上掛載著各種外設(shè),,這些外設(shè)也有自己的地址范圍,特定外設(shè)的首個地址稱為“XX 外設(shè)基地址”,,也叫 XX 外設(shè)的邊界地址,。具體有關(guān) STM32F10xx 外設(shè)的邊界地址請參考《STM32F10xx 參考手冊》的 2.3 小節(jié)的存儲器映射的表 1: STM32F10xx 寄存器邊界地址,。 這里面我們以 GPIO 這個外設(shè)來講解外設(shè)的基地址,, GPIO 屬于高速的外設(shè) ,掛載到APB2 總線上,,具體見表格 6-6,。 3. 外設(shè)寄存器 在 XX 外設(shè)的地址范圍內(nèi),分布著的就是該外設(shè)的寄存器,。以 GPIO 外設(shè)為例,, GPIO是通用輸入輸出端口的簡稱,簡單來說就是 STM32 可控制的引腳,,基本功能是控制引腳輸出高電平或者低電平,。最簡單的應(yīng)用就是把 GPIO 的引腳連接到 LED 燈的陰極, LED 燈的陽極接電源,然后通過 STM32 控制該引腳的電平,,從而實(shí)現(xiàn)控制 LED 燈的亮滅,。GPIO 有很多個寄存器,每一個都有特定的功能,。每個寄存器為 32bit,,占四個字節(jié),在該外設(shè)的基地址上按照順序排列,,寄存器的位置都以相對該外設(shè)基地址的偏移地址來描述,。這里我們以 GPIOB 端口為例,來說明 GPIO 都有哪些寄存器,,具體見表格 6-7,。 有關(guān)外設(shè)的寄存器說明可參考《STM32F10xx 參考手冊》中具體章節(jié)的寄存器描述部分,在編程的時候我們需要反復(fù)的查閱外設(shè)的寄存器說明,。這里我們以“GPIO 端口置位/復(fù)位寄存器”為例,,教大家如何理解寄存器的說明,具體見圖 6-6,。 ①名稱 寄存器說明中首先列出了該寄存器中的名稱,,“(GPIOx_BSRR)(x=A…E)”這段的意思是該寄存器名為“GPIOx_BSRR”其中的“x”可以為 A-E,也就是說這個寄存器說明適用于 GPIOA,、 GPIOB 至 GPIOE,,這些 GPIO 端口都有這樣的一個寄存器。 ②偏移地址 偏移地址是指本寄存器相對于這個外設(shè)的基地址的偏移,。本寄存器的偏移地址是 0x18,,從參考手冊中我們可以查到 GPIOA 外設(shè)的基地址為 0x4001 0800 ,我們就可以算出GPIOA 的這個 GPIOA_BSRR 寄存器的地址為: 0x4001 0800+0x18 ,;同理,,由于 GPIOB 的外設(shè)基地址為 0x4001 0C00,可算出 GPIOB_BSRR 寄存器的地址為: 0x4001 0C00+0x18 ,。其他 GPIO 端口以此類推即可,。 ③寄存器位表 緊接著的是本寄存器的位表,表中列出它的0-31 位的名稱及權(quán)限,。表上方的數(shù)字為位編號,,中間為位名稱,最下方為讀寫權(quán)限,,其中 w 表示只寫,, r 表示只讀, rw 表示可讀寫,。本寄存器中的位權(quán)限都是 w,,所以只能寫,,如果讀本寄存器,是無法保證讀取到它真正內(nèi)容的,。而有的寄存器位只讀,,一般是用于表示 STM32 外設(shè)的某種工作狀態(tài)的,由 STM32硬件自動更改,,程序通過讀取那些寄存器位來判斷外設(shè)的工作狀態(tài),。 ④位功能說明 位功能是寄存器說明中最重要的部分,它詳細(xì)介紹了寄存器每一個位的功能,。例如本寄存器中有兩種寄存器位,,分別為 BRy 及 BSy,其中的 y 數(shù)值可以是 0-15,,這里的 0-15表示端口的引腳號,,如 BR0、 BS0 用于控制 GPIOx 的第 0 個引腳,,若 x 表示 GPIOA,,那就是控制 GPIOA 的第 0 引腳,而BR1,、 BS1 就是控制 GPIOA 第 1 個引腳(BR1&BS1兩個一起控制一個GPIOA引腳),。其中 BRy 引腳的說明是“0:不會對相應(yīng)的 ODRx 位執(zhí)行任何操作; 1:對相應(yīng) ODRx位進(jìn)行復(fù)位”,。這里的“復(fù)位”是將該位設(shè)置為 0 的意思,,而“置位”表示將該位設(shè)置為1;說明中的 ODRx 是另一個寄存器的寄存器位,,我們只需要知道 ODRx 位為 1 的時候,,對應(yīng)的引腳 x 輸出高電平,為 0 的時候?qū)?yīng)的引腳輸出低電平即可(感興趣的讀者可以查詢該寄存器 GPIOx_ODR 的說明了解),。所以,,如果對 BR0 寫入“1”的話,那么 GPIOx 的第0 個引腳就會輸出“低電平”,,但是對 BR0 寫入“0”的話,,卻不會影響 ODR0 位,所以引腳電平不會改變,。要想該引腳輸出“高電平”,,就需要對“BS0”位寫入“1”,寄存器位BSy 與 BRy 是相反的操作 6.5.2 C 語言對寄存器的封裝以上所有的關(guān)于存儲器映射的內(nèi)容,,最終都是為大家更好地理解如何用 C 語言控制讀寫外設(shè)寄存器做準(zhǔn)備,此處是本章的重點(diǎn)內(nèi)容,。 1. 封裝總線和外設(shè)基地址在編程上為了方便理解和記憶,,我們把總線基地址和外設(shè)基地址都以相應(yīng)的宏定義起來,總線或者外設(shè)都以他們的名字作為宏名,具體見代碼 6-4,。 代碼 6-4 首先定義了 “片上外設(shè)”基地址 PERIPH_BASE,,接著在 PERIPH_BASE 上加入各個 總線 的地址 偏移, 得到 APB1,、 APB2 總線 的地址 APB1PERIPH_BASE ,、APB2PERIPH_BASE,在其之上加入外設(shè)地址的偏移,,得到 GPIOA-G 的外設(shè)地址,,最后在外設(shè)地址上加入各寄存器的地址偏移,得到特定寄存器的地址,。一旦有了具體地址,,就可以用指針讀寫,具體見代碼 6-5,。 該代碼使用 (unsigned int *) 把 GPIOB_BSRR 宏的數(shù)值強(qiáng)制轉(zhuǎn)換成了地址,,然后再用“*”號做取指針操作,對該地址的賦值,,從而實(shí)現(xiàn)了寫寄存器的功能,。同樣,讀寄存器也是用取指針操作,,把寄存器中的數(shù)據(jù)取到變量里,,從而獲取 STM32 外設(shè)的狀態(tài)。 2. 封裝寄存器列表用上面的方法去定義地址,,還是稍顯繁瑣,,例如 GPIOA-GPIOE 都各有一組功能相同的寄存器,如 GPIOA_ODR/GPIOB_ODR/GPIOC_ODR 等等,,它們只是地址不一樣,,但卻要為每個寄存器都定義它的地址。為了更方便地訪問寄存器,,我們引入 C 語言中的結(jié)構(gòu)體語法對寄存器進(jìn)行封裝,,具體見代碼 6-6。 這段代碼用 typedef 關(guān)鍵字聲明了名為 GPIO_TypeDef 的結(jié)構(gòu)體類型,,結(jié)構(gòu)體內(nèi)有 7 個成員變量,,變量名正好對應(yīng)寄存器的名字。 C 語言的語法規(guī)定,,結(jié)構(gòu)體內(nèi)變量的存儲空間是連續(xù)的,,其中 32 位的變量占用 4 個字節(jié), 16 位的變量占用 2 個字節(jié),,具體見圖 6-7,。 也就是說,,我們定義的這個 GPIO_TypeDef , 假如這個結(jié)構(gòu)體的首地址為 0x40010C00(這也是第一個成員變量 CRL 的地址) ,, 那么結(jié)構(gòu)體中第二個成員變量 CRH 的地址即為 0x4001 0C00 +0x04 ,, 加上的這個 0x04 ,正是代表 CRL 所占用的 4 個字節(jié)地址的偏移量,,其它成員變量相對于結(jié)構(gòu)體首地址的偏移,,在上述代碼右側(cè)注釋已給。 這樣的地址偏移與 STM32 GPIO 外設(shè)定義的寄存器地址偏移一一對應(yīng),,只要給結(jié)構(gòu)體設(shè)置好首地址,,就能把結(jié)構(gòu)體內(nèi)成員的地址確定下來,然后就能以結(jié)構(gòu)體的形式訪問寄存器,,具體見代碼 6-7,。 這段代碼先用 GPIO_TypeDef 類型定義一個結(jié)構(gòu)體指針 GPIOx,并讓指針指向地址GPIOB_BASE(0x4001 0C00),,使用地址確定下來,,然后根據(jù) C 語言訪問結(jié)構(gòu)體的語法,用GPIOx->ODR 及 GPIOx->IDR 等方式讀寫寄存器,。最后,,我們更進(jìn)一步,直接使用宏定義好 GPIO_TypeDef 類型的指針,,而且指針指向各個 GPIO 端口的首地址,,使用時我們直接用該宏訪問寄存器即可,具體代碼 6-8,。 這里我們僅是以 GPIO 這個外設(shè)為例,,給大家講解了 C 語言對寄存器的封裝。以此類推,,其他外設(shè)也同樣可以用這種方法來封裝,。好消息是,這部分工作都由固件庫幫我們完成了,,這里我們只是分析了下這個封裝的過程,,讓大家知其然,也只其所以然,。 |
|