一、儲存器對映
與重對映
儲存器本身不具有位址資訊,它的位址是由晶元廠商或使用者分配,給
物理儲存器分配
邏輯位址的過程就稱為儲存器對映
,通過這些邏輯位址就可以訪問到相應的儲存器的物理儲存單元。
如果給儲存器再分配乙個位址就叫儲存器重對映。
如stm32,對於
片上外設,它們以四個位元組為乙個單元,共32bit,每乙個單元對應不同的功能,當我們控制這些單元時就可以驅動外設工作。我們可以找到每個單元的起始位址,然後通過
c語言指標的操作方式來訪問這些單元,如果每次都是通過這種位址的方式來訪問,不僅不好記憶還容易出錯,這時我們可以根據每個單元功能的不同,以功能為名給這個記憶體單元取乙個別名,這個別名就是我們經常說的暫存器,這個給已經分配好位址的有特定功能的記憶體單元取別名的過程就叫暫存器對映。
二、通過絕對位址訪問記憶體單元
對於外設的訪問實際就是對記憶體位址的訪問
。在c語言裡,即指標操作。為了增加閱讀性,往往採用如下的巨集定義。
#define macro_base 0x4001 0c0c
#define macro_name (*(volatile unsigned int *)macro_base)
1.首先看macro_base這僅僅是乙個立即數,一數值。
2.(volatile unsigned int *)macro_base:這裡將進行強制型別轉換,把它轉換成指標,這裡即位址。
需要注意的是有兩點:
a)用unsigned:禁止算數移位,採用邏輯移位,因為嵌入式程式設計大量採用移位操作。如果用帶符號,會形成算術位移,即最高位符號不參與移位,這是錯誤的。
b)用volatile:禁止編譯器對變數訪問優化。即原始碼中有多少讀寫操作,編譯後生產多少機器操作指令。
3.第乙個*的作用,對這個指標的解引用。取這個位址對應的內容。
這一長串就相當於乙個變數了。
詳細說明volatile關鍵字:
在 c 語言中該關鍵字用於表示變數是易變的,
確保本條指令不會因c編譯器的優化而被省略
。且要求每次直接讀值。
例如用while((unsigned char *)0x20)時,有時系統可能不真正去讀
0x20
的值,而是
用第一次讀出的值,如果這樣,那這個迴圈可能是個死迴圈。用了volatile則要求每次都去讀
0x20
的實際值。
volatile 型別是這樣的,其資料確實可能在未知的情況下發生變化。比如,硬體裝置的終端更改了它,現在硬體裝置往往也有自己的私有記憶體位址,比如視訊記憶體,他們一般是通過映象的方式,反映到一段特定的記憶體位址當中,這樣,在某些條件下,程式就可以直接訪問這些私有記憶體了。另外,比如共享的記憶體位址,多個程式都對它操作的時候。你的程式並不知道,這個記憶體何時被改變了。如果不加這個
volatile
修飾,程式是利用
catch
當中的資料,那個可能是過時的了,加了
volatile
1. volatile變數可變允許除了程式之外的比如硬體來修改他的內容
2.訪問該資料任何時候都會直接訪問該位址處內容,即通過
cache
提高訪問速度的優化被取消
三、在stm32中的應用
#define __io volatile
/** * @brief general purpose i/o
*/typedef struct
gpio_typedef;
#define flash_base ((uint32_t)0x08000000)
#define sram_base ((uint32_t)0x20000000)
#define periph_base ((uint32_t)0x40000000)
#define sram_bb_base ((uint32_t)0x22000000)
#define periph_bb_base ((uint32_t)0x42000000)
#define fsmc_r_base ((uint32_t)0xa0000000)
/*!< peripheral memory map */
#define apb1periph_base periph_base
#define apb2periph_base (periph_base + 0x10000)
#define ahbperiph_base (periph_base + 0x20000)
#define afio_base (apb2periph_base + 0x0000)
#define exti_base (apb2periph_base + 0x0400)
#define gpioa_base (apb2periph_base + 0x0800)
#define gpiob_base (apb2periph_base + 0x0c00)
#define gpioc_base (apb2periph_base + 0x1000)
#define gpiod_base (apb2periph_base + 0x1400)
#define gpioe_base (apb2periph_base + 0x1800)
#define gpiof_base (apb2periph_base + 0x1c00)
#define gpiog_base (apb2periph_base + 0x2000)
#define gpioa ((gpio_typedef *) gpioa_base)
#define gpiob ((gpio_typedef *) gpiob_base)
#define gpioc ((gpio_typedef *) gpioc_base)
#define gpiod ((gpio_typedef *) gpiod_base)
#define gpioe ((gpio_typedef *) gpioe_base)
#define gpiof ((gpio_typedef *) gpiof_base)
#define gpiog ((gpio_typedef *) gpiog_base)
詳細說明:
外設暫存器結構體定義僅僅是乙個定義,要想實現給這個結構體賦值就達到操作暫存器的效果,我們還需要找到該暫存器的位址,就把暫存器位址跟結構體的位址對應起來。
這些結構體內的成員,都代表著暫存器,每個結構體成員前增加了乙個「__io」字首,它的原型代表了
c 語言中的關鍵字
「volatile」
,而暫存器很多時候是由外設或
stm32
晶元狀態修改的,也就是說即使
cpu
不執行**修改這些變數,變數的值也有可能被外設修改、更新,所以每次使用這些變數的時候,我們都要求
cpu
去該變數的位址重新訪問。若沒有這個關鍵字修飾,在某些情況下,編譯器認為沒有**修改該變數,就直接從
cpu
的某個快取獲取該變數值,這時可以加快執行速度,但該快取中的是陳舊資料,與我們要求的暫存器最新狀態可能會有出入。
定義好外設暫存器結構體,實現完外設儲存器對映後,我們再把外設的基址強制型別
轉換成相應的外設暫存器結構體指標,然後再把該指標宣告成外設名,這樣一來,外設名就跟外設的位址對應起來了,而且該外設名還是乙個該外設型別的暫存器結構體指標,通過該指標可以直接操作該外設的全部暫存器。
最終通過強制型別轉換把外設的基位址轉換成 gpio_typedef型別的結構體指標,然後通過巨集定義把
gpioa
、gpiob
等定義成外設的結構體指標,通過外設的結構體指標我們就可以達到訪問外設的暫存器的目的。
例子:gpiob->odr 等價於 (*(volatile unsigned int *)0x40010c0c)
暫存器直接操作
include include include include include include static unsigned int major static struct device led dev static struct class led class static volatile u...
暫存器對映
在block2這塊區域,設計的是片上外設,它們以4個位元組為1個單元,共32位,每乙個單元對應不同的功能,當控制這些單元時,就可以驅動外設工作,可以找到每個單元的起始位址,然後通過c語言指標的操作方式來訪問這些單元。根據每個單元功能的不同,以功能為名給這個記憶體單元取乙個別名,這個別名就是暫存器。給...
idr暫存器 嘿哈 暫存器對映
上一次我們已經實現了如何直接操作暫存器位址來點亮一盞led燈,但是這樣實在是太麻煩,那麼這次要講的就是,如何實現暫存器對映,更加快捷的實現點燈 怎麼又是點燈?我們先來看一下上一次的 圖中的這些位址都是看著官方給出的文件算出來的,這樣做的缺點很明顯,就是每一次都要算,算每乙個暫存器的位置,實際上我們可...