(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語言對暫存器的封裝。以此類推,其他外設也同樣可以用這種方法來封裝。好消息是,這部分工作都由韌體庫幫我們完成了,這裡我們只是分析了下這個封裝的過程,讓大家知其然,也只其所以然。