我們知道,存儲器本身沒有地址,給存儲器分配地址的過程叫存儲器映射,那什么叫寄存器映射?寄存器到底是什么?
在存儲器Block2 這塊區域,設計的是片上外設,它們以四個字節為一個單元,共32bit,每一個單元對應不同的功能,當我們控制這些單元時就可以驅動外設工作。我們可以找到每個單元的起始地址,然后通過C 語言指針的操作方式來訪問這些單元,如果每次都是通過這種地址的方式來訪問,不僅不好記憶還容易出錯,這時我們可以根據每個單元功能的不同,以功能為名給這個內存單元取一個別名,這個別名就是我們經常說的寄存器,這個給已經分配好地址的有特定功能的內存單元取別名的過程就叫寄存器映射。
比如,我們找到GPIOB 端口的輸出數據寄存器ODR 的地址是0x4001 0C0C(至於這個地址如何找到可以先跳過,后面我們會有詳細的講解),ODR 寄存器是32bit,低16bit有效,對應着16 個外部IO,寫0/1 對應的的IO 則輸出低/高電平。現在我們通過C 語言指針的操作方式,讓GPIOB 的16 個IO 都輸出高電平。
1 // GPIOB 端口全部輸出 高電平
2 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;
0x4001 0C0C 在我們看來是GPIOB 端口ODR 的地址,但是在編譯器看來,這只是一個普通的變量,是一個立即數,要想讓編譯器也認為是指針,我們得進行強制類型轉換,把它轉換成指針,即(unsigned int *)0x4001 0C0C,然后再對這個指針進行 * 操作。剛剛我們說了,通過絕對地址訪問內存單元不好記憶且容易出錯,我們可以通過寄存器的方式來操作。
1 // GPIOB 端口全部輸出 高電平
2 #define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C)
3 * GPIOB_ODR = 0xFF;
為了方便操作,我們干脆把指針操作“*”也定義到寄存器別名里面。
1 // GPIOB 端口全部輸出 高電平
2#define GPIOB_ODR * (unsigned int*)(GPIOB_BASE+0x0C)
3 GPIOB_ODR = 0xFF;
STM32 的外設地址映射
片上外設區分為三條總線,根據外設速度的不同,不同總線掛載着不同的外設,APB1掛載低速外設,APB2 和AHB 掛載高速外設。相應總線的最低地址我們稱為該總線的基地址,總線基地址也是掛載在該總線上的首個外設的地址。其中APB1 總線的地址最低,片上外設從這里開始,也叫外設基地址。
1. 總線基地址

2. 外設基地址
總線上掛載着各種外設,這些外設也有自己的地址范圍,特定外設的首個地址稱為“XX 外設基地址”,也叫XX 外設的邊界地址。具體有關STM32F10xx 外設的邊界地址請參考《STM32F10xx 參考手冊》的2.3 小節的存儲器映射的表1:STM32F10xx 寄存器邊界地址。
這里面我們以GPIO 這個外設來講解外設的基地址,GPIO 屬於高速的外設 ,掛載到APB2 總線上。

3. 外設寄存器
在XX 外設的地址范圍內,分布着的就是該外設的寄存器。以GPIO 外設為例,GPIO是通用輸入輸出端口的簡稱,簡單來說就是STM32 可控制的引腳,基本功能是控制引腳輸出高電平或者低電平。最簡單的應用就是把GPIO 的引腳連接到LED 燈的陰極,LED 燈的陽極接電源,然后通過STM32 控制該引腳的電平,從而實現控制LED 燈的亮滅。
GPIO 有很多個寄存器,每一個都有特定的功能。每個寄存器為32bit,占四個字節,在該外設的基地址上按照順序排列,寄存器的位置都以相對該外設基地址的偏移地址來描述。這里我們以GPIOB 端口為例,來說明GPIO都有哪些寄存器。

有關外設的寄存器說明可參考《STM32F10xx 參考手冊》中具體章節的寄存器描述部分,在編程的時候我們需要反復的查閱外設的寄存器說明。
這里我們以“GPIO 端口置位/復位寄存器”為例,告訴大家如何理解寄存器的說明。

①名稱
寄存器說明中首先列出了該寄存器中的名稱,“(GPIOx_BSRR)(x=A…E)”這段的意思是該寄存器名為“GPIOx_BSRR”其中的“x”可以為A-E,也就是說這個寄存器說明適用於GPIOA、GPIOB 至GPIOE,這些GPIO端口都有這樣的一個寄存器。
②偏移地址
偏移地址是指本寄存器相對於這個外設的基地址的偏移。本寄存器的偏移地址是0x18,從參考手冊中我們可以查到GPIOA 外設的基地址為0x4001 0800 ,我們就可以算出GPIOA的這個GPIOA_BSRR 寄存器的地址為:0x4001 0800+0x18 ;同理,由於GPIOB 的外設基地址為0x4001 0C00,可算出GPIOB_BSRR 寄存器的地址為:0x4001 0C00+0x18 。其他GPIO端口以此類推即可。
③寄存器位表
緊接着的是本寄存器的位表,表中列出它的0-31 位的名稱及權限。表上方的數字為位編號,中間為位名稱,最下方為讀寫權限,其中w 表示只寫,r 表示只讀,rw 表示可讀寫。本寄存器中的位權限都是w,所以只能寫,如果讀本寄存器,是無法保證讀取到它真正內容的。而有的寄存器位只讀,一般是用於表示STM32 外設的某種工作狀態的,由STM32硬件自動更改,程序通過讀取那些寄存器位來判斷外設的工作狀態。
④位功能說明
位功能是寄存器說明中最重要的部分,它詳細介紹了寄存器每一個位的功能。例如本寄存器中有兩種寄存器位,分別為BRy 及BSy,其中的y 數值可以是0-15,這里的0-15表示端口的引腳號,如BR0、BS0 用於控制GPIOx 的第0 個引腳,若x 表示GPIOA,那就是控制GPIOA的第0 引腳,而BR1、BS1 就是控制GPIOA 第1 個引腳。
其中BRy 引腳的說明是“0:不會對相應的ODRx 位執行任何操作;1:對相應ODRx位進行復位”。這里的“復位”是將該位設置為0 的意思,而“置位”表示將該位設置為1;說明中的ODRx 是另一個寄存器的寄存器位,我們只需要知道ODRx 位為1 的時候,對應的引腳x 輸出高電平,為0 的時候對應的引腳輸出低電平即可(感興趣的讀者可以查詢該寄存器GPIOx_ODR 的說明了解)。所以,如果對BR0 寫入“1”的話,那么GPIOx 的第0 個引腳就會輸出“低電平”,但是對BR0 寫入“0”的話,卻不會影響ODR0 位,所以引
腳電平不會改變。要想該引腳輸出“高電平”,就需要對“BS0”位寫入“1”,寄存器位BSy 與BRy 是相反的操作。
C 語言對寄存器的封裝
1. 封裝總線和外設基地址
在編程上為了方便理解和記憶,我們把總線基地址和外設基地址都以相應的宏定義起來,總線或者外設都以他們的名字作為宏名。

首先定義了 “片上外設”基地址PERIPH_BASE,接着在PERIPH_BASE 上加入各個總線的地址偏移, 得到APB1 、APB2 總線的地址APB1PERIPH_BASE 、APB2PERIPH_BASE,在其之上加入外設地址的偏移,得到GPIOA-G的外設地址,最后在外設地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具體地址,就可以用指針讀寫。

該代碼使用 (unsigned int *) 把GPIOB_BSRR 宏的數值強制轉換成了地址,然后再用“*”號做取指針操作,對該地址的賦值,從而實現了寫寄存器的功能。同樣,讀寄存器也是用取指針操作,把寄存器中的數據取到變量里,從而獲取STM32 外設的狀態。
2. 封裝寄存器列表
用上面的方法去定義地址,還是稍顯繁瑣,例如GPIOA-GPIOE 都各有一組功能相同的寄存器,如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR 等等,它們只是地址不一樣,但卻要為每個寄存器都定義它的地址。為了更方便地訪問寄存器,我們引入C 語言中的結構體語法對寄存器進行封裝。

這段代碼用typedef 關鍵字聲明了名為GPIO_TypeDef 的結構體類型,結構體內有7 個成員變量,變量名正好對應寄存器的名字。C 語言的語法規定,結構體內變量的存儲空間是連續的,其中32 位的變量占用4 個字節,16 位的變量占用2 個字節。

也就是說,我們定義的這個GPIO_TypeDef ,假如這個結構體的首地址為0x40010C00(這也是第一個成員變量CRL 的地址), 那么結構體中第二個成員變量CRH 的地址即為0x4001 0C00 +0x04 ,加上的這個0x04 ,正是代表CRL 所占用的4 個字節地址的偏移量,其它成員變量相對於結構體首地址的偏移,在上述代碼右側注釋已給。
這樣的地址偏移與STM32 GPIO 外設定義的寄存器地址偏移一一對應,只要給結構體設置好首地址,就能把結構體內成員的地址確定下來,然后就能以結構體的形式訪問寄存器。

這段代碼先用GPIO_TypeDef 類型定義一個結構體指針GPIOx,並讓指針指向地址GPIOB_BASE(0x4001 0C00),使用地址確定下來,然后根據C 語言訪問結構體的語法,用GPIOx->ODR 及GPIOx->IDR 等方式讀寫寄存器。
最后,我們更進一步,直接使用宏定義好GPIO_TypeDef 類型的指針,而且指針指向各個GPIO端口的首地址,使用時我們直接用該宏訪問寄存器即可。

這里我們僅是以GPIO 這個外設為例,給大家講解了C 語言對寄存器的封裝。以此類推,其他外設也同樣可以用這種方法來封裝。好消息是,這部分工作都由固件庫幫我們完成了,這里我們只是分析了下這個封裝的過程,讓大家知其然,也只其所以然。
stm32視頻學習資料
STM32可以這樣玩
http://www.makeru.com.cn/live/4034_1460.html?s=45051
分析STM32的的開發方式
http://www.makeru.com.cn/live/3523_1673.html?s=45051
釋放潛能:學習效率提升、編程能力提升
http://www.makeru.com.cn/live/3507_1276.html?s=45051
從單片機到嵌入式linux我們需要做什么
http://www.makeru.com.cn/live/5413_1994.html?s=45051
stm32 如何用DMA搬運數據
http://www.makeru.com.cn/live/detail/1484.html?s=45051
(STM32中斷系統)
http://www.makeru.com.cn/live/1392_1124.html?s=45051
pdf以及相關文檔下載群:830802928