寄存器映射与直接操作寄存器
一、存儲器映射與重映射
???存儲器本身不具有地址信息,它的地址是由芯片廠商或用戶分配,給物理存儲器分配邏輯地址的過程就稱為存儲器映射,通過這些邏輯地址就可以訪問到相應(yīng)的存儲器的物理存儲單元。如果給存儲器再分配一個(gè)地址就叫存儲器重映射。
???如STM32,對于片上外設(shè),它們以四個(gè)字節(jié)為一個(gè)單元,共32bit,每一個(gè)單元對應(yīng)不同的功能,當(dāng)我們控制這些單元時(shí)就可以驅(qū)動外設(shè)工作。我們可以找到每個(gè)單元的起始地址,然后通過C語言指針的操作方式來訪問這些單元,如果每次都是通過這種地址的方式來訪問,不僅不好記憶還容易出錯(cuò),這時(shí)我們可以根據(jù)每個(gè)單元功能的不同,以功能為名給這個(gè)內(nèi)存單元取一個(gè)別名,這個(gè)別名就是我們經(jīng)常說的寄存器,這個(gè)給已經(jīng)分配好地址的有特定功能的內(nèi)存單元取別名的過程就叫寄存器映射。
?
二、通過絕對地址訪問內(nèi)存單元
???對于外設(shè)的訪問實(shí)際就是對內(nèi)存地址的訪問。在C語言里,即指針操作。為了增加閱讀性,往往采用如下的宏定義。
????????#define MACRO_BASE 0x4001?0C0C
???????#define MACRO_NAME (*(volatile unsigned int *)MACRO_BASE)
???????1.首先看MACRO_BASE這僅僅是一個(gè)立即數(shù),一數(shù)值。
???????2.(volatile unsigned int *)MACRO_BASE:這里將進(jìn)行強(qiáng)制類型轉(zhuǎn)換,把它轉(zhuǎn)換成指針,這里即地址。
???需要注意的是有兩點(diǎn):
????????A)用unsigned:禁止算數(shù)移位,采用邏輯移位,因?yàn)榍度胧骄幊檀罅坎捎靡莆徊僮鳌H绻脦Х?#xff0c;會形成算術(shù)位移,即最高位符號不參與移位,這是錯(cuò)誤的。
???????B)用volatile:禁止編譯器對變量訪問優(yōu)化。即源碼中有多少讀寫操作,編譯后生產(chǎn)多少機(jī)器操作指令。
????????3.第一個(gè)*的作用,對這個(gè)指針的解引用。取這個(gè)地址對應(yīng)的內(nèi)容。
??這一長串就相當(dāng)于一個(gè)變量了。
?
??詳細(xì)說明volatile關(guān)鍵字:
??在 C 語言中該關(guān)鍵字用于表示變量是易變的,確保本條指令不會因C編譯器的優(yōu)化而被省略。且要求每次直接讀值。例如用while((unsigned char *)0x20)時(shí),有時(shí)系統(tǒng)可能不真正去讀0x20的值,而是用第一次讀出的值,如果這樣,那這個(gè)循環(huán)可能是個(gè)死循環(huán)。用了volatile則要求每次都去讀0x20的實(shí)際值。
??????volatile 類型是這樣的,其數(shù)據(jù)確實(shí)可能在未知的情況下發(fā)生變化。比如,硬件設(shè)備的終端更改了它,現(xiàn)在硬件設(shè)備往往也有自己的私有內(nèi)存地址,比如顯存,他們一般是通過映象的方式,反映到一段特定的內(nèi)存地址當(dāng)中,這樣,在某些條件下,程序就可以直接訪問這些私有內(nèi)存了。另外,比如共享的內(nèi)存地址,多個(gè)程序都對它操作的時(shí)候。你的程序并不知道,這個(gè)內(nèi)存何時(shí)被改變了。如果不加這個(gè)volatile修飾,程序是利用cache當(dāng)中的數(shù)據(jù),那個(gè)可能是過時(shí)的了,加了volatile,就在需要用的時(shí)候,程序重新去那個(gè)地址去提取,保證是最新的。歸納起來如下:
??????1. volatile變量可變允許除了程序之外的比如硬件來修改他的內(nèi)容???2.訪問該數(shù)據(jù)任何時(shí)候都會直接訪問該地址處內(nèi)容,即通過cache提高訪問速度的優(yōu)化被取消 。
?
三、在STM32中的應(yīng)用
#define ? ? __IO ? ?volatile
/**
? * @brief General Purpose I/O
? */
?
typedef struct
{
? __IO uint32_t CRL;
? __IO uint32_t CRH;
? __IO uint32_t IDR;
? __IO uint32_t ODR;
? __IO uint32_t BSRR;
? __IO uint32_t BRR;
? __IO uint32_t LCKR;
} 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)
詳細(xì)說明:
???外設(shè)寄存器結(jié)構(gòu)體定義僅僅是一個(gè)定義,要想實(shí)現(xiàn)給這個(gè)結(jié)構(gòu)體賦值就達(dá)到操作寄存器的效果,我們還需要找到該寄存器的地址,就把寄存器地址跟結(jié)構(gòu)體的地址對應(yīng)起來。
???這些結(jié)構(gòu)體內(nèi)的成員,都代表著寄存器,每個(gè)結(jié)構(gòu)體成員前增加了一個(gè)“__IO”前綴,它的原型代表了C 語言中的關(guān)鍵字“volatile”,而寄存器很多時(shí)候是由外設(shè)或STM32 芯片狀態(tài)修改的,也就是說即使CPU 不執(zhí)行代碼修改這些變量,變量的值也有可能被外設(shè)修改、更新,所以每次使用這些變量的時(shí)候,我們都要求CPU 去該變量的地址重新訪問。若沒有這個(gè)關(guān)鍵字修飾,在某些情況下,編譯器認(rèn)為沒有代碼修改該變量,就直接從CPU 的某個(gè)緩存獲取該變量值,這時(shí)可以加快執(zhí)行速度,但該緩存中的是陳舊數(shù)據(jù),與我們要求的寄存器最新狀態(tài)可能會有出入。
???定義好外設(shè)寄存器結(jié)構(gòu)體,實(shí)現(xiàn)完外設(shè)存儲器映射后,我們再把外設(shè)的基址強(qiáng)制類型轉(zhuǎn)換成相應(yīng)的外設(shè)寄存器結(jié)構(gòu)體指針,然后再把該指針聲明成外設(shè)名,這樣一來,外設(shè)名就跟外設(shè)的地址對應(yīng)起來了,而且該外設(shè)名還是一個(gè)該外設(shè)類型的寄存器結(jié)構(gòu)體指針,通過該指針可以直接操作該外設(shè)的全部寄存器。
???最終通過強(qiáng)制類型轉(zhuǎn)換把外設(shè)的基地址轉(zhuǎn)換成 GPIO_TypeDef類型的結(jié)構(gòu)體指針,然后通過宏定義把 GPIOA、GPIOB等定義成外設(shè)的結(jié)構(gòu)體指針,通過外設(shè)的結(jié)構(gòu)體指針我們就可以達(dá)到訪問外設(shè)的寄存器的目的。
?
???例子:GPIOB->ODR??等價(jià)于 ?(*(volatile unsigned int *)0x40010C0C)
---------------------?
原文:https://blog.csdn.net/wjgwrr/article/details/72848506?
總結(jié)
以上是生活随笔為你收集整理的寄存器映射与直接操作寄存器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jtag和swd的区别
- 下一篇: 数字电路可控门电路原理(三态/同相/反相