(stm32學習總結)—對寄存器的理解 _

芯片裏面有什麼
我們看到的 STM32 芯片是已經封裝好的成品,主要由內核和片上外設組成。若與電腦類比,內核與外設就如同電腦上的 CPU 與主板、內存、顯卡、硬盤的關係。STM32F103 採用的是 Cortex-M3 內核,內核即 CPU,由 ARM 公司設計。ARM 公司並不生產芯片,而是出售其芯片技術權。芯片生產廠商(SOC)如 ST、TI、Freescale,負責在內核之外設計部件並生產整個芯片,這些內核之外的部件被稱為核外外設或片上外設。如 GPIO、USART(串口)、I2C、SPI 等都叫做片上外設。

1. ICode 總線
ICode 中的 I 表示 Instruction,即指令。我們寫好的程序編譯之後都是一條條指令,存放在 FLASH 中,內核要讀取這些指令來執行程序就必須通過 ICode 總線,它幾乎每時每刻都需要被使用,它是專門用來取指的。
DCode 總線
DCode 中的 D 表示 Data,即數據,那說明這條總線是用來取數的。我們在寫程序的時候,數據有常量和變量兩種,常量就是固定不變的,用 C 語言中的 const 關鍵字修飾,是放到內部的 FLASH 當中的,變量是可變的,不管是全局變量還是局部變量都放在內部的SRAM。因為數據可以被 Dcode 總線和 DMA 總線訪問,所以為了避免訪問衝突,在取數的時候需要經過一個總線矩陣來仲裁,決定哪個總線在取數。 
S系統總線
系統總線主要是訪問外設的寄存器,我們通常說的寄存器編程,即讀寫寄存器都是通過這根系統總線來完成的。
DMA 總線
DMA 總線也主要是用來傳輸數據,這個數據可以是在某個外設的數據寄存器,可以在SRAM,可以在內部的 FLASH。因為數據可以被 Dcode 總線和 DMA 總線訪問,所以為了避免訪問衝突,在取數的時候需要經過一個總線矩陣來仲裁,決定哪個總線在取數。
2. 被動單元
內部的閃存存儲器
內部的閃存存儲器即 FLASH,我們編寫好的程序就放在這個地方。內核通過 ICode 總線來取裏面的指令。
內部的 SRAM
內部的 SRAM,即我們通常說的 RAM,程序的變量,堆棧等的開銷都是基於內部的SRAM。內核通過 DCode 總線來訪問它。
FSMC
FSMC 的英文全稱是 Flexible static memory controller,叫靈活的靜態的存儲器控制器,是 STM32F10xx 中一個很有特色的外設,通過 FSMC,我們可以擴展內存,如外部的SRAM,NANDFLASH 和 NORFLASH。但有一點我們要注意的是,FSMC 只能擴展靜態的內存,即名稱裏面的 S:static,不能是動態的內存,比如 SDRAM 就不能擴展。
AHB 到 APB 的橋
從 AHB 總線延伸出來的兩條 APB2 和 APB1 總線,上面掛載着 STM32 各種各樣的特色外設。我們經常說的 GPIO、串口、I2C、SPI 這些外設就掛載在這兩條總線上,這個是我們學習 STM32 的重點,就是要學會編程這些外設去驅動外部的各種設備。
3.總線矩陣
  總線矩陣協調內核系統總線和DMA主控總線之間的訪問仲裁,仲裁利用輪換算法。在互聯型產品中,總線矩陣包含5個驅動部件(CPU的DCode、系統總線、以太網DMA、DMA1總線和DMA2總線)和3個從部件(閃存存儲器接口(FLITF)、SRAM和AHB2APB橋)。在其它產品中總線矩陣包含4個驅動部件(CPU的DCode、系統總線、DMA1總線和DMA2總線)和4個被動部件(閃存存儲器接口(FLITF)、SRAM、FSMC和AHB2APB橋)。AHB外設通過總線矩陣與系統總線相連,允許DMA訪問。
4.AHB/APB橋(APB)
  兩個AHB/APB橋在AHB和2個APB總線間提供同步連接。APB1操作速度限於36MHz,APB2操作於全速(最高72MHz)。
有關連接到每個橋的不同外設的地址映射請參考表1。在每一次複位以後,所有除SRAM和FLITF以外的外設都被關閉,在使用一個外設之前,必須設置寄存器RCC_AHBENR來打開該外設的時鐘。
注意: 當對APB寄存器進行8位或者16位訪問時,該訪問會被自動轉換成32位的訪問:橋會自動將8位
或者32位的數據擴展以配合32位的向量。

存儲器組織
程序存儲器、數據存儲器、寄存器和輸入輸出端口被組織在同一個4GB的線性地址空間內。數據位元組以小端格式存放在存儲器中。一個字里的最低地址位元組被認為是該字的最低有效位元組,而最高地址位元組是最高有效位元組。
  

 存儲器區域功能劃分
在這 4GB 的地址空間中,ARM 已經粗線條的平均分成了 8 個塊,每塊 512MB,每個塊也都規定了用途,具體分類見表格 6-1。每個塊的大小都有 512MB,顯然這是非常大的,芯片廠商在每個塊的範圍內設計各具特色的外設時並不一定都用得完,都是只用了其中的一部分而已。

在這 8 個 Block 裏面,有 3 個塊非常重要,也是我們最關心的三個塊。
Block0 用來設計成內部 FLASH,Block1 用來設計成內部 RAM,Block2 用來設計成片上的外設,下面我們簡單的介紹下這三個 Block 裏面的具體區域的功能劃分。 
 
存儲器 Block0 內部區域功能劃分
Block0 主要用於設計片內的 FLASH, STM32F103ZET6是 512KB,屬於大容量。要在芯片內部集成更大的 FLASH 或者 SRAM 都意味着芯片成本的增加,往往片內集成的 FLASH 都不會太大,ST 能在追求性價比的同時做到 512KB,實乃良心之舉。Block 內部區域的功能劃分具體見
表格 6-2。 

 

 

儲存器 Block1 內部區域功能劃分
Block1 用 於 設 計 片 內 的 SRAM 。 STM32F103ZET6 的 SRAM 都是 64KB,Block 內部區域的功能劃分具體見表格6-3
表格 6-3 存儲器 Block1 內部區域功能劃分

 

儲存器 Block2 內部區域功能劃分
Block2 用於設計片內的外設,根據外設的總線速度不同,Block 被分成了 APB 和 AHB兩部分,其中 APB 又被分為 APB1 和 APB2,具體見表格 6-4。 

以上解釋了stm32f103的整體結構,也講解了存儲器地址如何分配,那我們怎麼操作這些存儲器內的內容單元呢?畢竟微控制器就是通過cpu讀取存儲器中的指令把最終運算得到的數據輸出的過程,而我們編程的過程就是告訴cpu怎麼樣讀取存儲器的指令執行的、存儲器中放了那些內容讓CPU去讀取、寄存器中內容是什麼要實現什麼功能等等,編程的過程就是對這些存儲器/寄存器賦值的過程,然後交給CPU去運算,所以我們必須給這些存儲器所對應的地址起個名方便我們對這些存儲器內容的操作。起名字的過程叫做寄存器映射。寄存器也是存儲器的一種。

寄存器映射
我們知道,存儲器本身沒有地址,給存儲器分配地址的過程叫存儲器映射,那什麼叫寄存器映射?寄存器到底是什麼?
在存儲器 Block2 這塊區域,設計的是片上外設,它們以四個位元組為一個單元,共32bit,每一個單元對應不同的功能,當我們控制這些單元時就可以驅動外設工作。我們可以找到每個單元的起始地址,然後通過 C 語言指針的操作方式來訪問這些單元,如果每次都是通過這種地址的方式來訪問,不僅不好記憶還容易出錯,這時我們可以根據每個單元功能的不同,以功能為名給這個內存單元取一個別名,這個別名就是我們經常說的寄存器,這個給已經分配好地址的有特定功能的內存單元取別名的過程就叫寄存器映射。 
 
下面以——讓GPIOB端口的16個引腳輸出高電平,要怎麼實現?——這個問題來看看寄存器映射的過程。
通過絕對地址訪問內存單元
1、0X40010C0C 是GPIOB輸出數據寄存器ODR的地址,如何找到?
2、(unsigned int*)的作用是什麼?
將0x40010c0c這個立即數 強制類型位無符號整形地址 (說白了就是告訴編譯器0x40010c0c這個對應的不是立即數而是一個無符號的整形地址) 
3、學會使用C語言的 * 號
*這的符號用法很多比方上面(unsigned int*)+立即數就是上面解釋的意思
那麼   *地址   是什麼意思呢就像上面 *(unsigned int*) (0x40010c0c) 這樣的形式就代表了這種無符號整形地址對應的內存空間
*(unsigned int*) (0x40010c0c) = 0xFFFF;    就是對這個內存空間賦值0xFFFF
那麼stm32中如何去對這些寄存器操作呢?
通過寄存器別名方式訪問內存單元 

上面就是stm32對寄存器內存單元的操作形式,對相應寄存器對應的地址進行宏定義 (已將將立即數強制轉換成無符號整形地址)之後就是  *(宏定義名)就代表是這個寄存器的內存單元了,但是這麼操作還不是很完美畢竟在宏定義寄存器的名字前面還加了一個*,不想在51單片機中直接P1就能代表P1這個I/o端口一樣 。
所以為了方便操作,我們乾脆把指針操作「*」也定義到寄存器別名裏面 
 
 當然stm32這麼多的寄存器我們都要這樣一個一個去命名嗎?

當然不會,這樣學習stm32開發成本高了些,不說別的,只是單純對寄存器命名就有很大的工作量,這樣誰還會去選擇這樣的芯片去開發,所以就有了庫函數這樣的東西,裏面把所有的寄存器都已經按照上面介紹的方式已經封裝好了,我們拿出來使用就ok了,庫函數不只是這點功能,不要着急以後的章節里會慢慢道來。

什麼是寄存器?
給有特定功能的內存單元取一個別名,這個別名就是我們經常說的寄存器,這個給已經分配好地址的有特定功能的內存單元
取別名的過程就叫寄存器映射
就像上面對  *(unsigned int*) (0x40010c0c) 命名GPIO_ODR ;
這個宏定義可以隨便命名,你可以命名成zhangsan/lisi,但是,這樣的命名方式肯定不行的,不好記憶也不好類比。
 
什麼叫存儲器映射? 
給存儲器分配地址的過程叫存儲器映射,再分配一個地址叫重映射。
 
既然說道對寄存器命名就要知道各個寄存器對應的地址是什麼呢?
 
總線基地址(對應總線的起始地址)
片上外設區分為三條總線,根據外設速度的不同,不同總線掛載着不同的外設,APB1掛載低速外設,APB2 和 AHB 掛載高速外設。相應總線的最低地址我們稱為該總線的基地址,總線基地址也是掛載在該總線上的首個外設的地址。其中 APB1 總線的地址最低,片上外設從這裡開始,也叫外設基地址。 

 

外設基地址
總線上掛載着各種外設,這些外設也有自己的地址範圍,特定外設的首個地址稱為「XX 外設基地址」,也叫 XX 外設的邊界地址。
這裏面我們以 GPIO 這個外設來講解外設的基地址,GPIO 屬於高速的外設 ,掛載到APB2 總線上。
GPIO基地址(外設是什麼)(掛接在總線上的設備)
 
 
外設寄存器
  在 XX 外設的地址範圍內,分佈着的就是該外設的寄存器。以 GPIO 外設為例,GPIO是通用輸入輸出端口的簡稱,簡單來說就是 STM32 可控制的引腳,基本功能是控制引腳輸出高電平或者低電平。
  GPIO 有很多個寄存器,每一個都有特定的功能。每個寄存器為 32bit,佔四個位元組,在該外設的基地址上按照順序排列,寄存器的位置都以相對該外設基地址的偏移地址來描述。這裡我們以 GPIOB 端口為例,來說明 GPIO 都有哪些寄存器
 
同一個外設也有不同的功能,那不同的功能是如何來實現的呢?
想要外設工作、想要外設以什麼樣的功能工作,就需要對外設內的寄存器賦特定的值,外設內的寄存器所賦的值不同,最終實現的功能也有所不同。
 
GPIOB端口的寄存器列表
 
上面是對GPIOB端口下的寄存器的命名,每個寄存器都是32位的,每一個寄存器都有不同的功能,,而且對同一個寄存器賦予不同的值時,最終的效果也不同,下面看看GPIOX_ODR(X可以代表A/B/C…)這個寄存器下的每一位都有什麼作用吧
GPIOx端口數據輸出寄存器ODR描述
 
如何理解上邊寄存器的說明呢?
①名稱
  寄存器說明中首先列出了該寄存器中的名稱,「(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語言對寄存器的封裝過程
 
總線和外設基址宏定義

讓PB0輸出低/高電平,要怎麼實現?

 
為什麼使用結構體封裝寄存器列表?

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

也就是說,我們定義的這個 GPIO_TypeDef ,假如這個結構體的首地址為 0x40010C00(這也是第一個成員變量 CRL 的地址), 那麼結構體中第二個成員變量 CRH 的地址即為 0x4001 0C00 +0x04 ,加上的這個 0x04 ,正是代表 CRL 所佔用的 4 個位元組地址的偏移量,其它成員變量相對於結構體首地址的偏移,在上述代碼右側注釋已給。這樣的地址偏移與 STM32 GPIO 外設定義的寄存器地址偏移一一對應,只要給結構體設置好首地址,就能把結構體內成員的地址確定下來,然後就能以結構體的形式訪問寄存器,具體見代碼 6-7。
使用結構體指針訪問寄存器
這段代碼先用 GPIO_TypeDef 類型定義一個結構體指針 GPIOx,並讓指針指向地址GPIOB_BASE(0x4001 0C00),使用地址確定下來,然後根據 C 語言訪問結構體的語法,用GPIOx->ODR 及 GPIOx->IDR 等方式讀寫寄存器。最後,我們更進一步,直接使用宏定義好 GPIO_TypeDef 類型的指針,而且指針指向各個 GPIO 端口的首地址,使用時我們直接用該宏訪問寄存器即可,具體代碼 6-8。

 

定義GPIOX端口基地址指針

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