存儲器映射
對於Cortex-M3來講,有一塊4G大小的存儲器空間。存儲器映射指的是芯片廠商為這個空間分配地址的操作。這4G空間被均勻地划分為8個大小為512MB的存儲塊(block),並且每個塊都各具特色。下面主要介紹Block1~Block2。
Block0
Block0的地址范圍為0x0000_0000~0x1FFF_FFFF。它被設計用來存放代碼程序,其中主要有FLASH、SYSTEM MEMORY和OPTION BYTES:
FLASH:起始地址為0x0800_0000,存放用戶程序和掉電保存數據。FLASH容量從16k到512k不等,以STM32F10x8系列為例,8代表FLASH容量為64k,所以結束地址就為0x0801_FFFF。
SYSTEM MEMORY:系統存儲器,存放了BootLoader程序,禁止用戶改動,該程序主要用於串口下載。
OPTION BYTES:選項字節,可以配置讀寫保護、看門狗等。
這里簡單說一下地址分配。整塊4G存儲器開始地址標為0x0000_0000,結束地址為0xFFFF_FFFF,地址表示采用了十六進制,一共8*4=32bit,而2^32剛好就是4G。存儲器的基本單元是一個字節,每個地址都對應這樣一個單元,因此用32位地址來表示,其容量剛好就是4GB。同理,根據Block0的起止地址,也可以計算得到其容量為512MB。
Block1
Block1的地址范圍為0x2000_0000~0x3FFF_FFFF。其中0x2000_0000~0x2000_FFFF被划為SRAM,主要是用於程序運行的堆棧開銷。
Block2
Block2的地址范圍為0x4000_0000~0x5FFF_FFFF。這塊空間被充分用作外設寄存器。根據總線不同,將外設分為三大塊,第一塊是APB1總線外設,起始地址為0x4000_0000;第二塊是APB2總線外設,起始地址為0x4001_0000;最后一塊為AHB總線外設,起始地址為0x4001_8000。
寄存器映射
寄存器映射主要針對於Block2。這種映射不同於存儲器映射的分配地址操作,而是在程序中對具有特定功能的內存單元進行命名的過程,每個內存單元都是四個字節,稱作寄存器。例如GPIOA外設有個寄存器地址范圍為0x4001_0800~0x4001_0803,該寄存器是用來配置GPIOA部分端口工作模式的,因此被映射為GPIOA_CRL。由此可知,這種映射方便了對寄存器的訪問操作,因為如果每次訪問寄存器都要查地址范圍的話是很痛苦的。
GPIOA_CRL
如何實現這種映射的呢?以GPIOA_CRL為例,其映射地址 = 外設總基地址(塊基地址)+ 總線相對於外設總基地址的偏移 + 具體外設基地址相對於總線基地址的偏移 + 寄存器相對於具體外設基地址的偏移。
外設總基地址恰好就是Block2的起始地址0x4000_0000;
GPIO屬於APB2總線外設,因此查閱芯片手冊如下圖所示,我們其實直接可以得到APB2起始地址和GPIOA的起始地址,但是程序中一般不這么做,而是以偏移量來表示層次關系。從圖中可計算到總線相對於外設總基地址的偏移為0x1_0000,GPIOA相對於APB2基地址的偏移為0x800。
再查閱GPIO的寄存器組,如下圖所示。可以得到CRL寄存器相對於GPIO基地址偏移為0x00。綜上GPIOA_CRL的基地址為:0x4000_0000+0x1_0000+0x800+0x00。
程序中地址映射
STM32固件庫中,有個頭文件叫stm32f10x.h,其中就定義了寄存器的映射,部分代碼如下:
外設基地址PERIPH_BASE:
#define PERIPH_BASE ((uint32_t)0x40000000)
總線基地址,在外設基地址上加上偏移:
#define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
GPIO外設基地址,在APB2總線基地址上加上偏移:
#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)
定義GPIO外設結構體,因為結構體成員在內存中是連續的,這種形式與寄存器組非常類似,所以用結構體能夠很好的管理寄存器:
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;
定義GPIOA結構體指針,因為單單定義GPIO外設結構體,並不能確定其內存地址,因此用指針將其綁定到GPIOA外設基地址:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
訪問寄存器方式的對比,映射訪問明顯更為直觀:
//直接的地址訪問 *(unsigned int *)(0x4001_0800)|= 0x0001; //映射訪問 GPIOA->CRL |= 0x0001;