當(dāng)前位置:首頁 > 技術(shù)學(xué)院 > 技術(shù)前線
[導(dǎo)讀]本章參考資料:《STM32F76xxx參考手冊》、《STM32F76xxx數(shù)據(jù)手冊》、學(xué)習(xí)本章時,配合《STM32F76xxx參考手冊》“存儲器和總線架構(gòu)”、“嵌入式FLASH接口”及“通用I/O(GPIO)”章節(jié)一起閱讀,效果會更佳,特別是涉及到寄存器說明的部分。

5.1 什么是寄存器

我們經(jīng)常說寄存器,那么什么是寄存器?這是我們本章需要講解的內(nèi)容,在學(xué)習(xí)的過程中,大家?guī)е@個疑問好好思考下,到最后看看大家能否用一句話給寄存器下一個定義。

5.2 STM32長啥樣

我們開發(fā)板中使用的芯片是176pin的STM32F767IGT6,具體見圖 51。這個就是我們接下來要學(xué)習(xí)的STM32,它講帶領(lǐng)我們進(jìn)入嵌入式的殿堂。

芯片正面是絲印,ARM應(yīng)該是表示該芯片使用的是ARM的內(nèi)核,STM32F767IGT6是芯片型號,后面的字應(yīng)該是跟生產(chǎn)批次相關(guān),最下面的是ST的LOGO。

芯片四周是引腳,左下角的小圓點表示1腳,然后從1腳起按照逆時針的順序排列(所有芯片的引腳順序都是逆時針排列的)。開發(fā)板中把芯片的引腳引出來,連接到各種傳感器上,然后在STM32上編程(實際就是通過程序控制這些引腳輸出高電平或者低電平)來控制各種傳感器工作,通過做實驗的方式來學(xué)習(xí)STM32芯片的各個資源。開發(fā)板是一種評估板,板載資源非常豐富,引腳復(fù)用比較多,力求在一個板子上驗證芯片的全部功能。


寄存器長什么樣子

圖 5-1 STM32F767IGT6 實物圖


寄存器長什么樣子

圖 5-2 STM32F767IGT6正面引腳圖

5.3 芯片里面有什么

我們看到的STM32芯片已經(jīng)是已經(jīng)封裝好的成品,主要由內(nèi)核和片上外設(shè)組成。若與電腦類比,內(nèi)核與外設(shè)就如同電腦上的CPU與主板、內(nèi)存、顯卡、硬盤的關(guān)系。

STM32F767采用的是Cortex-M7內(nèi)核,內(nèi)核即CPU,由ARM公司設(shè)計。ARM公司并不生產(chǎn)芯片,而是出售其芯片技術(shù)授權(quán)。芯片生產(chǎn)廠商(SOC)如ST、TI、Freescale,負(fù)責(zé)在內(nèi)核之外設(shè)計部件并生產(chǎn)整個芯片,這些內(nèi)核之外的部件被稱為核外外設(shè)或片上外設(shè)。如GPIO、USART(串口)、I2C、SPI等都叫做片上外設(shè)。具體見圖 53。


寄存器長什么樣子

圖 5-3 STM32芯片架構(gòu)簡圖

芯片主系統(tǒng)架構(gòu)基于兩個子系統(tǒng),一個是AXI轉(zhuǎn)多層AHB橋,多層AHB總線矩陣。

AXI轉(zhuǎn)多層AHB橋,從AXI4協(xié)議轉(zhuǎn)成AHB-Lite協(xié)議,其中包含3個AXI轉(zhuǎn)32-bit AHB橋通過32-bit的AHB總線矩陣連接到外部存儲器FMC接口、外部存儲器Quad SPI接口、內(nèi)部SRAM(SRAM1 and SRAM2)。還包含一個AXI轉(zhuǎn)64-bit AHB橋通過64-bit總線矩陣連接到內(nèi)部FLASH。

多層AHB總線矩陣,其中32-bit多層AHB總線矩陣互聯(lián)11個主設(shè)備和8個從設(shè)備,64-bit多層AHB總線矩陣則是CPU通過AXI轉(zhuǎn)AHB橋通過這個64-bit多層AHB總線矩陣連接到內(nèi)部Flash。DMA主設(shè)備通過32-bit AHB總線矩陣通過這個64-bit多層AHB總線矩陣連接到內(nèi)部Flash。具體見圖 54。主控總線通過一個總線矩陣來連接被控總線,總線矩陣用于主控總線之間的訪問仲裁管理,仲裁采用循環(huán)調(diào)度算法??偩€之間交叉的時候如果有個圓圈則表示可以通信,沒有圓圈則表示不可以通信。


寄存器長什么樣子

圖 5-4 STM32F76xxx 和 STM32F77xxx 器件的總線接口

5.4 存儲器映射

在圖 54中,連接被控總線的是FLASH,RAM和片上外設(shè),這些功能部件共同排列在一個4GB的地址空間內(nèi)。我們在編程的時候,操作的也正是這些功能部件。

5.4.1 存儲器映射

存儲器本身不具有地址信息,它的地址是由芯片廠商或用戶分配,給存儲器分配地址的過程就稱為存儲器映射,具體見圖 55。如果給存儲器再分配一個地址就叫存儲器重映射。


寄存器長什么樣子

圖 5-5 存儲器映射

1. 存儲器區(qū)域功能劃分

在這4GB的地址空間中,ARM已經(jīng)粗線條的平均分成了8個塊,每塊512MB,每個塊也都規(guī)定了用途,具體分類見表格 51。每個塊的大小都有512MB,顯然這是非常大的,芯片廠商在每個塊的范圍內(nèi)設(shè)計各具特色的外設(shè)時并不一定都用得完,都是只用了其中的一部分而已。

表格 5-1 存儲器功能分類

序號用途地址范圍

Block 0SRAM0x0000 0000 ~ 0x1FFF FFFF(512MB)

Block 1SRAM0x2000 0000 ~ 0x3FFF FFFF(512MB)

Block 2片上外設(shè)0x4000 0000 ~ 0x5FFF FFFF(512MB)

Block 3FMC的bank1 ~ bank20x6000 0000 ~ 0x7FFF FFFF(512MB)

Block 4FMC的bank3 ~ bank40x8000 0000 ~ 0x9FFF FFFF(512MB)

Block 5FMC0xA000 0000 ~ 0xCFFF FFFF(512MB)

Block 6FMC0xD000 0000 ~ 0xDFFF FFFF(512MB)

Block 7Cortex-M4內(nèi)部外設(shè)0xE000 0000 ~ 0xFFFF FFFF(512MB)

在這8個Block里面,有3個塊非常重要,也是我們最關(guān)心的三個塊。Boock0用來設(shè)計成內(nèi)部FLASH,Block1用來設(shè)計成內(nèi)部RAM,Block2用來設(shè)計成片上的外設(shè),下面我們簡單的介紹下這三個Block里面的具體區(qū)域的功能劃分。

存儲器Block0內(nèi)部區(qū)域功能劃分

Block0主要用于設(shè)計片內(nèi)的FLASH, F429系列片內(nèi)部FLASH最大是2MB,我們使用的STM32F767IGT6的FLASH是1MB。要在芯片內(nèi)部集成更大的FLASH或者SRAM都意味著芯片成本的增加,往往片內(nèi)集成的FLASH都不會太大,ST能在追求性價比的同時做到1MB以上,實乃良心之舉。Block內(nèi)部區(qū)域的功能劃分具體見表格 52。

表格 52 存儲器Block0 內(nèi)部區(qū)域功能劃分

塊用途說明地址范圍

Block0預(yù)留0x1FFF 0020 ~ 0x1FFF FFFF

16個字節(jié)用于鎖定對應(yīng)的OTP數(shù)據(jù)塊。0x1FFF 0000 ~ 0x1FFF 001F

預(yù)留0x0820 0000 ~ 0x1FFE FFFF

FLASH:我們的程序就放在這里。0x0800 0000 ~ 0x081F FFFF

預(yù)留0x0030 0000 ~ 0x07FF FFFF

FLASH存儲基于ITCM總線接口,不支持寫操作,即只讀。 0x0020 0000 ~ 0x003F FFFF

預(yù)留0x0011 0000 ~ 0x001F FFFF

系統(tǒng)存儲器:里面存的是ST出廠時燒寫好的ISP自舉程序,用戶無法改動。串口下載的時候需要用到這部分程序。0x0010 0000 ~ 0x0010 EDBF

預(yù)留0x0000 4000 ~ 0x000F FFFF

ITCM RAM,只能被CPU訪問,不用經(jīng)過總線矩陣,屬于高速的RAM。0x0000 0000 ~ 0x0000 3FFF

儲存器Block1內(nèi)部區(qū)域功能劃分

Block1用于設(shè)計片內(nèi)的SRAM。F767 內(nèi)部SRAM的大小為512KB,分SRAM1 368KB,SRAM2 16KB,DTCM 128KB,Block內(nèi)部區(qū)域的功能劃分具體見表格 5-3。

表格 5-3 存儲器Block1 內(nèi)部區(qū)域功能劃分

塊用途說明地址范圍

Block1預(yù)留0x2008 0000 ~ 0x3FFF FFFF

SRAM2 16KB0x2007 C000 ~ 0x2007 FFFF

SRAM1 368KB0x2002 0000 ~ 0x2007 BFFF

DTCM 128KB0x2000 0000 ~ 0x2001 FFFF

儲存器Block2內(nèi)部區(qū)域功能劃分

Block2用于設(shè)計片內(nèi)的外設(shè),根據(jù)外設(shè)的總線速度不同,Block被分成了APB和AHB兩部分,其中APB又被分為APB1和APB2,AHB分為AHB1和AHB2,具體見表格 5-4。還有一個AHB3包含了Block3/4/5/6,這四個Block用于擴展外部存儲器,如SDRAM,NORFLASH和NANDFLASH等。

表格 5-4 存儲器Block2 內(nèi)部區(qū)域功能劃分

塊用途說明地址范圍

Block2APB1 總線外設(shè)0x4000 0000 ~ 0x4000 7FFF

預(yù)留0x4000 8000 ~ 0x4000 FFFF

APB2 總線外設(shè)0x4001 0000 ~ 0x4001 6BFF

預(yù)留0x4001 6C00 ~ 0x4001 FFFF

AHB1 總線外設(shè)0x4002 0000 ~ 0x4007 FFFF

預(yù)留0x4008 0000 ~ 0x4FFF FFFF

AHB2總線外設(shè)0x5000 0000 ~ 0x5006 0BFF

預(yù)留0x5006 0C00 ~ 0x5FFF FFFF

5.5 寄存器映射

我們知道,存儲器本身沒有地址,給存儲器分配地址的過程叫存儲器映射,那什么叫寄存器映射?寄存器到底是什么?

在存儲器Block2這塊區(qū)域,設(shè)計的是片上外設(shè),它們以四個字節(jié)為一個單元,共32bit,每一個單元對應(yīng)不同的功能,當(dāng)我們控制這些單元時就可以驅(qū)動外設(shè)工作。我們可以找到每個單元的起始地址,然后通過C語言指針的操作方式來訪問這些單元,如果每次都是通過這種地址的方式來訪問,不僅不好記憶還容易出錯,這時我們可以根據(jù)每個單元功能的不同,以功能為名給這個內(nèi)存單元取一個別名,這個別名就是我們經(jīng)常說的寄存器,這個給已經(jīng)分配好地址的有特定功能的內(nèi)存單元取別名的過程就叫寄存器映射。

比如,我們找到GPIOH端口的輸出數(shù)據(jù)寄存器ODR的地址是0x4002 1C14(至于這個地址如何找到可以先跳過,后面我們會有詳細(xì)的講解),ODR寄存器是32bit,低16bit有效,對應(yīng)著16個外部IO,寫0/1對應(yīng)的的IO則輸出低/高電平?,F(xiàn)在我們通過C語言指針的操作方式,讓GPIOH的16個IO都輸出高電平,具體見代碼 51。

代碼 51 通過絕對地址訪問內(nèi)存單元

1 // GPIOH 端口全部輸出 高電平

2 *(unsigned int*)(0x4002 1C14) = 0xFFFF;

0x4002 1C14在我們看來是GPIOH端口ODR的地址,但是在編譯器看來,這只是一個普通的變量,是一個立即數(shù),要想讓編譯器也認(rèn)為是指針,我們得進(jìn)行強制類型轉(zhuǎn)換,把它轉(zhuǎn)換成指針,即(unsigned int *)0x4002 1C14,然后再對這個指針進(jìn)行 * 操作。

剛剛我們說了,通過絕對地址訪問內(nèi)存單元不好記憶且容易出錯,我們可以通過寄存器的方式來操作,具體見代碼 52。

代碼 52 通過寄存器別名方式訪問內(nèi)存單元

1 // GPIOH 端口全部輸出 高電平

2 #define GPIOH_ODR (unsigned int*)(GPIOH_BASE+0x14)

3 * GPIOH_ODR = 0xFF;

為了方便操作,我們干脆把指針操作“*”也定義到寄存器別名里面,具體見代碼 53。

代碼 53 通過寄存器別名訪問內(nèi)存單元

1 // GPIOH 端口全部輸出 高電平

2 #define GPIOH_ODR *(unsigned int*)(GPIOH_BASE+0x14)

3 GPIOH_ODR = 0xFF;

5.5.1 STM32的外設(shè)地址映射

片上外設(shè)區(qū)分為四條總線,根據(jù)外設(shè)速度的不同,不同總線掛載著不同的外設(shè),APB掛載低速外設(shè),AHB掛載高速外設(shè)。相應(yīng)總線的最低地址我們稱為該總線的基地址,總線基地址也是掛載在該總線上的首個外設(shè)的地址。其中APB1總線的地址最低,片上外設(shè)從這里開始,也叫外設(shè)基地址。

1. 總線基地址

表格 5-5 總線基地址

總線名稱總線基地址相對外設(shè)基地址的偏移

APB10x4000 00000x0

APB20x4001 00000x0001 0000

AHB10x4002 00000x0002 0000

AHB20x5000 00000x1000 0000

AHB30x6000 0000已不屬于片上外設(shè)

表格 5-5的“相對外設(shè)基地址偏移”即該總線地址與“片上外設(shè)”基地址0x4000 0000的差值。關(guān)于地址的偏移我們后面還會講到。

2. 外設(shè)基地址

總線上掛載著各種外設(shè),這些外設(shè)也有自己的地址范圍,特定外設(shè)的首個地址稱為“XX外設(shè)基地址”,也叫XX外設(shè)的邊界地址。具體有關(guān)STM32F4xx外設(shè)的邊界地址請參考《STM32F76xx數(shù)據(jù)手冊》的第4章節(jié)的存儲器映射的表Table 13. STM32F765xx, STM32F767xx, STM32F768Ax and STM32F769xx register boundary addresses。

這里面我們以GPIO這個外設(shè)來講解外設(shè)的基地址,具體見表格 5-6。

表格 5-6 外設(shè)GPIO基地址

外設(shè)名稱外設(shè)基地址相對AHB1總線的地址偏移

GPIOA0x4002 00000x0

GPIOB0x4002 04000x0000 0400

GPIOC0x4002 08000x0000 0800

GPIOD0x4002 0C000x0000 0C00

GPIOE0x4002 10000x0000 1000

GPIOF0x4002 14000x0000 1400

GPIOG0x4002 18000x0000 1800

GPIOH0x4002 1C000x0000 1C00

從表格 5-6看到,GPIOA的基址相對于AHB1總線的地址偏移為0,我們應(yīng)該就可以猜到,AHB1總線的第一個外設(shè)就是GPIOA。

3. 外設(shè)寄存器

在XX外設(shè)的地址范圍內(nèi),分布著的就是該外設(shè)的寄存器。以GPIO外設(shè)為例,GPIO是通用輸入輸出端口的簡稱,簡單來說就是STM32可控制的引腳,基本功能是控制引腳輸出高電平或者低電平。最簡單的應(yīng)用就是把GPIO的引腳連接到LED燈的陰極,LED燈的陽極接電源,然后通過STM32控制該引腳的電平,從而實現(xiàn)控制LED燈的亮滅。

GPIO有很多個寄存器,每一個都有特定的功能。每個寄存器為32bit,占四個字節(jié),在該外設(shè)的基地址上按照順序排列,寄存器的位置都以相對該外設(shè)基地址的偏移地址來描述。這里我們以GPIOH端口為例,來說明GPIO都有哪些寄存器,具體見表格 5-7。

表格 5-7 GPIOH端口的 寄存器地址列表

寄存器名稱寄存器地址相對GPIOH基址的偏移

GPIOH_MODER0x4002 1C000x00

GPIOH_OTYPER0x4002 1C040x04

GPIOH_OSPEEDR0x4002 1C080x08

GPIOH_PUPDR0x4002 1C0C0x0C

GPIOH_IDR0x4002 1C100x10

GPIOH_ODR0x4002 1C140x14

GPIOH_BSRR0x4002 1C180x18

GPIOH_LCKR0x4002 1C1C0x1C

GPIOH_AFRL0x4002 1C200x20

GPIOH_AFRH0x4002 1C240x24

有關(guān)外設(shè)的寄存器說明可參考《STM32F76xxx參考手冊》中具體章節(jié)的寄存器描述部分,在編程的時候我們需要反復(fù)的查閱外設(shè)的寄存器說明。

這里我們以“GPIO端口置位/復(fù)位寄存器”為例,教大家如何理解寄存器的說明,具體見圖 5-6。


寄存器長什么樣子

圖 5-6 GPIO端口置位/復(fù)位寄存器說明

q ①名稱

寄存器說明中首先列出了該寄存器中的名稱,“(GPIOx_BSRR)(x=A…I)”這段的意思是該寄存器名為“GPIOx_BSRR”其中的“x”可以為A-I,也就是說這個寄存器說明適用于GPIOA、GPIOB至GPIOI,這些GPIO端口都有這樣的一個寄存器。

q ②偏移地址

偏移地址是指本寄存器相對于這個外設(shè)的基地址的偏移。本寄存器的偏移地址是0x18,從參考手冊中我們可以查到GPIOA外設(shè)的基地址為0x4002 0000 ,我們就可以算出GPIOA的這個GPIOA_BSRR寄存器的地址為:0x4002 0000+0x18 ;同理,由于GPIOB的外設(shè)基地址為0x4002 0400,可算出GPIOB_BSRR寄存器的地址為:0x4002 0400+0x18 。其他GPIO端口以此類推即可。

q ③寄存器位表

緊接著的是本寄存器的位表,表中列出它的0-31位的名稱及權(quán)限。表上方的數(shù)字為位編號,中間為位名稱,最下方為讀寫權(quán)限,其中w表示只寫,r表示只讀,rw表示可讀寫。本寄存器中的位權(quán)限都是w,所以只能寫,如果讀本寄存器,是無法保證讀取到它真正內(nèi)容的。而有的寄存器位只讀,一般是用于表示STM32外設(shè)的某種工作狀態(tài)的,由STM32硬件自動更改,程序通過讀取那些寄存器位來判斷外設(shè)的工作狀態(tài)。

q ④位功能說明

位功能是寄存器說明中最重要的部分,它詳細(xì)介紹了寄存器每一個位的功能。例如本寄存器中有兩種寄存器位,分別為BRy及BSy,其中的y數(shù)值可以是0-15,這里的0-15表示端口的引腳號,如BR0、BS0用于控制GPIOx的第0個引腳,若x表示GPIOA,那就是控制GPIOA的第0引腳,而BR1、BS1就是控制GPIOA第1個引腳。

其中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是相反的操作。

5.5.2 C語言對寄存器的封裝

以上所有的關(guān)于存儲器映射的內(nèi)容,最終都是為大家更好地理解如何用C語言控制讀寫外設(shè)寄存器做準(zhǔn)備,此處是本章的重點內(nèi)容。

1. 封裝總線和外設(shè)基地址

在編程上為了方便理解和記憶,我們把總線基地址和外設(shè)基地址都以相應(yīng)的宏定義起來,總線或者外設(shè)都以他們的名字作為宏名,具體見代碼 54。

代碼 54 總線和外設(shè)基址宏定義

1 /* 外設(shè)基地址 */

2 #define PERIPH_BASE ((unsigned int)0x40000000)

3

4 /* 總線基地址 */

5 #define APB1PERIPH_BASE PERIPH_BASE

6 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)

7 #define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)

8 #define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000)

9

10 /* GPIO外設(shè)基地址 */

11 #define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)

12 #define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400)

13 #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)

14 #define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00)

15 #define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000)

16 #define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)

17 #define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800)

18 #define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)

19

20 /* 寄存器基地址,以GPIOH為例 */

21 #define GPIOH_MODER (GPIOH_BASE+0x00)

22 #define GPIOH_OTYPER (GPIOH_BASE+0x04)

23 #define GPIOH_OSPEEDR (GPIOH_BASE+0x08)

24 #define GPIOH_PUPDR (GPIOH_BASE+0x0C)

25 #define GPIOH_IDR (GPIOH_BASE+0x10)

26 #define GPIOH_ODR (GPIOH_BASE+0x14)

27 #define GPIOH_BSRR (GPIOH_BASE+0x18)

28 #define GPIOH_LCKR (GPIOH_BASE+0x1C)

29 #define GPIOH_AFRL (GPIOH_BASE+0x20)

30 #define GPIOH_AFRH (GPIOH_BASE+0x24)

代碼 54首先定義了 “片上外設(shè)”基地址PERIPH_BASE,接著在PERIPH_BASE上加入各個總線的地址偏移,得到APB1、APB2等總線的地址APB1PERIPH_BASE、APB2PERIPH_BASE,在其之上加入外設(shè)地址的偏移,得到GPIOA、GPIOH的外設(shè)地址,最后在外設(shè)地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具體地址,就可以用指針操作讀寫了,具體見代碼 55。

代碼 55 使用指針控制BSRR寄存器

1 /* 控制GPIOH 引腳10輸出低電平(BSRR寄存器的BR10置0) */

2 *(unsigned int *)GPIOH_BSRR = (0x01<<(16+10));

3

4 /* 控制GPIOH 引腳10輸出高電平(BSRR寄存器的BS10置1) */

5 *(unsigned int *)GPIOH_BSRR = 0x01<<10;

6

7 unsigned int temp;

8 /* 控制GPIOH 端口所有引腳的電平(讀IDR寄存器) */

9 temp = *(unsigned int *)GPIOH_IDR;

該代碼使用 (unsigned int *) 把GPIOH_BSRR宏的數(shù)值強制轉(zhuǎn)換成了地址,然后再用“*”號做取指針操作,對該地址的賦值,從而實現(xiàn)了寫寄存器的功能。同樣,讀寄存器也是用取指針操作,把寄存器中的數(shù)據(jù)取到變量里,從而獲取STM32外設(shè)的狀態(tài)。

2. 封裝寄存器列表

用上面的方法去定義地址,還是稍顯繁瑣,例如GPIOA-GPIOH都各有一組功能相同的寄存器,如GPIOA_MODER/GPIOB_MODER/GPIOC_MODER等等,它們只是地址不一樣,但卻要為每個寄存器都定義它的地址。為了更方便地訪問寄存器,我們引入C語言中的結(jié)構(gòu)體語法對寄存器進(jìn)行封裝,具體見代碼 56。

代碼 56 使用結(jié)構(gòu)體對GPIO寄存器組的封裝

1 typedef unsigned int uint32_t; /*無符號32位變量*/

2 typedef unsigned short int uint16_t; /*無符號16位變量*/

3

4 /* GPIO寄存器列表 */

5 typedef struct {

6 uint32_t MODER; /*GPIO模式寄存器 地址偏移: 0x00 */

7 uint32_t OTYPER; /*GPIO輸出類型寄存器 地址偏移: 0x04 */

8 uint32_t OSPEEDR; /*GPIO輸出速度寄存器 地址偏移: 0x08 */

9 uint32_t PUPDR; /*GPIO上拉/下拉寄存器 地址偏移: 0x0C */

10 uint32_t IDR; /*GPIO輸入數(shù)據(jù)寄存器 地址偏移: 0x10 */

11 uint32_t ODR; /*GPIO輸出數(shù)據(jù)寄存器 地址偏移: 0x14 */

12 uint16_t BSRR; /*GPIO置位/復(fù)位寄存器 地址偏移: 0x18 */

13 uint32_t LCKR; /*GPIO配置鎖定寄存器 地址偏移: 0x1C */

14 uint32_t AFR[2]; /*GPIO復(fù)用功能配置寄存器 地址偏移: 0x20-0x24 */

15 } GPIO_TypeDef;

這段代碼用typedef 關(guān)鍵字聲明了名為GPIO_TypeDef的結(jié)構(gòu)體類型,結(jié)構(gòu)體內(nèi)有8個 成員變量,變量名正好對應(yīng)寄存器的名字。C語言的語法規(guī)定,結(jié)構(gòu)體內(nèi)變量的存儲空間是連續(xù)的,其中32位的變量占用4個字節(jié),16位的變量占用2個字節(jié),具體見圖 5-7。


寄存器長什么樣子

圖 5-7 GPIO_TypeDef結(jié)構(gòu)體成員的地址偏移

也就是說,我們定義的這個GPIO_TypeDef ,假如這個結(jié)構(gòu)體的首地址為0x4002 1C00(這也是第一個成員變量MODER的地址), 那么結(jié)構(gòu)體中第二個成員變量OTYPER的地址即為0x4002 1C00 +0x04 ,加上的這個0x04 ,正是代表MODER所占用的4個字節(jié)地址的偏移量,其它成員變量相對于結(jié)構(gòu)體首地址的偏移,在上述代碼右側(cè)注釋已給出。

這樣的地址偏移與STM32 GPIO外設(shè)定義的寄存器地址偏移一一對應(yīng),只要給結(jié)構(gòu)體設(shè)置好首地址,就能把結(jié)構(gòu)體內(nèi)成員的地址確定下來,然后就能以結(jié)構(gòu)體的形式訪問寄存器了,具體見代碼 57。

代碼 57 通過結(jié)構(gòu)體指針訪問寄存器

1 GPIO_TypeDef * GPIOx; //定義一個GPIO_TypeDef型結(jié)構(gòu)體指針GPIOx

2 GPIOx = GPIOH_BASE; //把指針地址設(shè)置為宏GPIOH_BASE地址

3 GPIOx->BSRR = 0x0000FFFF; //通過指針訪問并修改GPIOH_BSRR寄存器

4 GPIOx->MODER = 0xFFFFFFFF; //修改GPIOH_MODER寄存器

5 GPIOx->OTYPER =0xFFFFFFFF; //修改GPIOH_OTYPER寄存器

6

7 uint32_t temp;

8 temp = GPIOx->IDR; //讀取GPIOH_IDR寄存器的值到變量temp中

這段代碼先用GPIO_TypeDef類型定義一個結(jié)構(gòu)體指針GPIOx,并讓指針指向地址GPIOH_BASE(0x4002 1C00),使用地址確定下來,然后根據(jù)C語言訪問結(jié)構(gòu)體的語法,用GPIOx->BSRR、GPIOx->MODER及GPIOx->IDR等方式讀寫寄存器。

最后,我們更進(jìn)一步,直接使用宏定義好GPIO_TypeDef類型的指針,而且指針指向各個GPIO端口的首地址,使用時我們直接用該宏訪問寄存器即可,具體代碼 58。

代碼 58 定義好GPIO端口首地址址針

1 /*使用GPIO_TypeDef把地址強制轉(zhuǎn)換成指針*/

2 #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)

3 #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)

4 #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)

5 #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)

6 #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)

7 #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)

8 #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)

9 #define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)

10

11

12

13 /*使用定義好的宏直接訪問*/

14 /*訪問GPIOH端口的寄存器*/

15 GPIOH->BSRR = 0xFFFF; //通過指針訪問并修改GPIOH_BSRR寄存器

16 GPIOH->MODER = 0xFFFFFFF; //修改GPIOH_MODER寄存器

17 GPIOH->OTYPER =0xFFFFFFF; //修改GPIOH_OTYPER寄存器

18

19 uint32_t temp;

20 temp = GPIOH->IDR; //讀取GPIOH_IDR寄存器的值到變量temp中

21

22 /*訪問GPIOA端口的寄存器*/

23 GPIOA->BSRR = 0xFFFF; //通過指針訪問并修改GPIOA_BSRR寄存器

24 GPIOA->MODER = 0xFFFFFFF; //修改GPIOA_MODER寄存器

25 GPIOA->OTYPER =0xFFFFFFF; //修改GPIOA_OTYPER寄存器

26

27 uint32_t temp;

28 temp = GPIOA->IDR; //讀取GPIOA_IDR寄存器的值到變量temp中

這里我們僅是以GPIO這個外設(shè)為例,給大家講解了C語言對寄存器的封裝。以此類推,其他外設(shè)也同樣可以用這種方法來封裝。好消息是,這部分工作都由固件庫幫我們完成了,這里我們只是分析了下這個封裝的過程,讓大家知其然,也只其所以然。

5.5.3 修改寄存器的位操作方法

使用C語言對寄存器賦值時,我們常常要求只修改該寄存器的某幾位的值,且其它的寄存器位不變,這個時候我們就需要用到C語言的位操作方法了。

1. 把變量的某位清零

此處我們以變量a代表寄存器,并假設(shè)寄存器中本來已有數(shù)值,此時我們需要把變量a的某一位清零,且其它位不變,方法見代碼清單 51。

代碼清單 51 對某位清零

1 //定義一個變量a = 1001 1111 b (二進(jìn)制數(shù))

2 unsigned char a = 0x9f;

3

4 //對bit2 清零

5

6 a &= ~(1<<2);

7

8 //括號中的1左移兩位,(1<<2)得二進(jìn)制數(shù):0000 0100 b

9 //按位取反,~(1<<2)得1111 1011 b

10 //假如a中原來的值為二進(jìn)制數(shù): a = 1001 1111 b

11 //所得的數(shù)與a作”位與&”運算,a = (1001 1111 b)&(1111 1011 b),

12 //經(jīng)過運算后,a的值 a=1001 1011 b

13 // a的bit2 位被被零,而其它位不變。

2. 把變量的某幾個連續(xù)位清零

由于寄存器中有時會有連續(xù)幾個寄存器位用于控制某個功能,現(xiàn)假設(shè)我們需要把寄存器的某幾個連續(xù)位清零,且其它位不變,方法見代碼清單 52。

代碼清單 52 對某幾個連續(xù)位清零

1

2 //若把a中的二進(jìn)制位分成2個一組

3 //即bit0、bit1為第0組,bit2、bit3為第1組,

4 // bit4、bit5為第2組,bit6、bit7為第3組

5 //要對第1組的bit2、bit3清零

6

7 a &= ~(3<<2*1);

8

9 //括號中的3左移兩位,(3<<2*1)得二進(jìn)制數(shù):0000 1100 b

10 //按位取反,~(3<<2*1)得1111 0011 b

11 //假如a中原來的值為二進(jìn)制數(shù): a = 1001 1111 b

12 //所得的數(shù)與a作”位與&”運算,a = (1001 1111 b)&(1111 0011 b),

13 //經(jīng)過運算后,a的值 a=1001 0011 b

14 // a的第1組的bit2、bit3被清零,而其它位不變。

15

16 //上述(~(3<<2*1))中的(1)即為組編號;如清零第3組bit6、bit7此處應(yīng)為3

17 //括號中的(2)為每組的位數(shù),每組有2個二進(jìn)制位;若分成4個一組,此處即為4

18 //括號中的(3)是組內(nèi)所有位都為1時的值;若分成4個一組,此處即為二進(jìn)制數(shù)“1111 b”

19

20 //例如對第2組bit4、bit5清零

21 a &= ~(3<<2*2);

3. 對變量的某幾位進(jìn)行賦值。

寄存器位經(jīng)過上面的清零操作后,接下來就可以方便地對某幾位寫入所需要的數(shù)值了,且其它位不變,方法見代碼清單 53,這時候?qū)懭氲臄?shù)值一般就是需要設(shè)置寄存器的位參數(shù)。

代碼清單 53 對某幾位進(jìn)行賦值

1 //a = 1000 0011 b

2 //此時對清零后的第2組bit4、bit5設(shè)置成二進(jìn)制數(shù)“01 b ”

3

4 a |= (1<<2*2);

5 //a = 1001 0011 b,成功設(shè)置了第2組的值,其它組不變

4. 對變量的某位取反

某些情況下,我們需要對寄存器的某個位進(jìn)行取反操作,即 1變0 ,0變1,這可以直接用如下操作,其它位不變,見代碼清單 54。

代碼清單 54 對某位進(jìn)行取反操作

1 //a = 1001 0011 b

2 //把bit6取反,其它位不變

3

4 a ^=(1<<6);

5 //a = 1101 0011 b

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉