寄存器映射与直接操作寄存器
一、存儲器映射與重映射
???存儲器本身不具有地址信息,它的地址是由芯片廠商或用戶分配,給物理存儲器分配邏輯地址的過程就稱為存儲器映射,通過這些邏輯地址就可以訪問到相應的存儲器的物理存儲單元。如果給存儲器再分配一個地址就叫存儲器重映射。
???如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修飾,程序是利用cache當中的數據,那個可能是過時的了,加了volatile,就在需要用的時候,程序重新去那個地址去提取,保證是最新的。歸納起來如下:
??????1. volatile變量可變允許除了程序之外的比如硬件來修改他的內容???2.訪問該數據任何時候都會直接訪問該地址處內容,即通過cache提高訪問速度的優化被取消 。
?
三、在STM32中的應用
#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)
詳細說明:
???外設寄存器結構體定義僅僅是一個定義,要想實現給這個結構體賦值就達到操作寄存器的效果,我們還需要找到該寄存器的地址,就把寄存器地址跟結構體的地址對應起來。
???這些結構體內的成員,都代表著寄存器,每個結構體成員前增加了一個“__IO”前綴,它的原型代表了C 語言中的關鍵字“volatile”,而寄存器很多時候是由外設或STM32 芯片狀態修改的,也就是說即使CPU 不執行代碼修改這些變量,變量的值也有可能被外設修改、更新,所以每次使用這些變量的時候,我們都要求CPU 去該變量的地址重新訪問。若沒有這個關鍵字修飾,在某些情況下,編譯器認為沒有代碼修改該變量,就直接從CPU 的某個緩存獲取該變量值,這時可以加快執行速度,但該緩存中的是陳舊數據,與我們要求的寄存器最新狀態可能會有出入。
???定義好外設寄存器結構體,實現完外設存儲器映射后,我們再把外設的基址強制類型轉換成相應的外設寄存器結構體指針,然后再把該指針聲明成外設名,這樣一來,外設名就跟外設的地址對應起來了,而且該外設名還是一個該外設類型的寄存器結構體指針,通過該指針可以直接操作該外設的全部寄存器。
???最終通過強制類型轉換把外設的基地址轉換成 GPIO_TypeDef類型的結構體指針,然后通過宏定義把 GPIOA、GPIOB等定義成外設的結構體指針,通過外設的結構體指針我們就可以達到訪問外設的寄存器的目的。
?
???例子:GPIOB->ODR??等價于 ?(*(volatile unsigned int *)0x40010C0C)
---------------------?
原文:https://blog.csdn.net/wjgwrr/article/details/72848506?
總結
以上是生活随笔為你收集整理的寄存器映射与直接操作寄存器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jtag和swd的区别
- 下一篇: 数字电路可控门电路原理(三态/同相/反相