寄存器
芯片內部
-
STM32芯片架構
-
芯片與外設總線連接,主控總線8條,被控總線7條
-
STM32三種啟動方式,FLASH、內部SRAM、外部RAM
存儲器映射
連接被控總線的是FLASH、RAM、片上外設
Boock0 用來設計成內部 FLASH, Block1 用來設計成內部 RAM, Block2 用來設計成片上的外設
AHB3 包含了 Block3/4/5/6,這四個 Block 用於擴展外部存儲器,如 SDRAM,NORFLASH 和 NANDFLASH 等
寄存器映射
給已經分配好地址的有特定功能的內存單元取別名的過程就叫寄存器映射。
通過 C 語言指針的操作方式,讓 GPIOH 的 16 個 IO 都輸出高電平
代碼 5-1 通過絕對地址訪問內存單元
// GPIOH 端口全部輸出 高電平
*(unsigned int*)(0x4002 1C14) = 0xFFFF;
0x4002 1C14 在我們看來是 GPIOH 端口 ODR 的地址,但是在編譯器看來,這只是一個普通的變量,是一個立即數,要想讓編譯器也認為是指針,我們得進行強制類型轉換,把它轉換成指針,即(unsigned int *)0x4002 1C14,然后再對這個指針進行 * 操作。
通過寄存器的方式來操作
代碼 5-2 通過寄存器別名方式訪問內存單元
// GPIOH 端口全部輸出 高電平
#define GPIOH_ODR (unsigned int*)(GPIOH_BASE+0x14)
*GPIOH_ODR = 0xFF;
把指針操作“*”也定義到寄存器別名里面
代碼 5-3 通過寄存器別名訪問內存單元
//GPIOH 端口全部輸出 高電平
#define GPIOH_ODR *(unsigned int*)(GPIOH_BASE+0x14)
GPIOH_ODR = 0xFF;
外設地址映射
片上外設四條總線,外設速度不同,掛在不同總線
總線的最低地址稱為總線的基地址
特定外設的首地址稱為XX外設基地址
外設寄存器
①名稱
寄存器說明中首先列出了該寄存器中的名稱,“(GPIOx_BSRR)(x=A…I)”這段的意思是該寄存器名為“GPIOx_BSRR”其中的“x”可以為 A-I,也就是說這個寄存器說明適用於 GPIOA、 GPIOB 至 GPIOI,這些 GPIO 端口都有這樣的一個寄存器。
②偏移地址
偏移地址是指本寄存器相對於這個外設的基地址的偏移。本寄存器的偏移地址是 0x18,從參考手冊中我們可以查到 GPIOA 外設的基地址為 0x4002 0000 ,我們就可以算出GPIOA 的這個 GPIOA_BSRR 寄存器的地址為: 0x4002 0000+0x18 ;同理,由於 GPIOB 的外設基地址為 0x4002 0400,可算出 GPIOB_BSRR 寄存器的地址為: 0x4002 0400+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 個引腳。
C語言對寄存器的封裝
封裝總線和外設基地址
代碼 5-4 總線和外設基址宏定義
/* 外設基地址 */
#define PERIPH_BASE ((unsigned int)0x40000000)
/* 總線基地址 */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000)
/* GPIO 外設基地址 */
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)
/* 寄存器基地址,以 GPIOH 為例 */
#define GPIOH_MODER (GPIOH_BASE+0x00)
#define GPIOH_OTYPER (GPIOH_BASE+0x04)
#define GPIOH_OSPEEDR (GPIOH_BASE+0x08)
#define GPIOH_PUPDR (GPIOH_BASE+0x0C)
#define GPIOH_IDR (GPIOH_BASE+0x10)
#define GPIOH_ODR (GPIOH_BASE+0x14)
#define GPIOH_BSRR (GPIOH_BASE+0x18)
#define GPIOH_LCKR (GPIOH_BASE+0x1C)
#define GPIOH_AFRL (GPIOH_BASE+0x20)
#define GPIOH_AFRH (GPIOH_BASE+0x24)
代碼 5-5 使用指針控制 BSRR 寄存器
/* 控制 GPIOH 引腳 10 輸出低電平(BSRR 寄存器的 BR10 置 1) */
*(unsigned int *)GPIOH_BSRR = (0x01<<(16+10));
/* 控制 GPIOH 引腳 10 輸出高電平(BSRR 寄存器的 BS10 置 1) */
*(unsigned int *)GPIOH_BSRR = 0x01<<10;
unsigned int temp;
/* 控制 GPIOH 端口所有引腳的電平(讀 IDR 寄存器) */
temp = *(unsigned int *)GPIOH_IDR;
封裝寄存器列表
代碼 5-6 使用結構體對 GPIO 寄存器組的封裝
typedef unsigned int uint32_t; /*無符號 32 位變量*/
typedef unsigned short int uint16_t; /*無符號 16 位變量*/
/* GPIO 寄存器列表 */
typedef struct {
uint32_t MODER; /*GPIO 模式寄存器 地址偏移: 0x00 */
uint32_t OTYPER; /*GPIO 輸出類型寄存器 地址偏移: 0x04 */
uint32_t OSPEEDR; /*GPIO 輸出速度寄存器 地址偏移: 0x08 */
uint32_t PUPDR; /*GPIO 上拉/下拉寄存器 地址偏移: 0x0C */
uint32_t IDR; /*GPIO 輸入數據寄存器 地址偏移: 0x10 */
uint32_t ODR; /*GPIO 輸出數據寄存器 地址偏移: 0x14 */
uint16_t BSRRL; /*GPIO 置位/復位寄存器低 16 位部分 地址偏移: 0x18 */
uint16_t BSRRH; /*GPIO 置位/復位寄存器高 16 位部分 地址偏移: 0x1A */
uint32_t LCKR; /*GPIO 配置鎖定寄存器 地址偏移: 0x1C */
uint32_t AFR[2]; /*GPIO 復用功能配置寄存器 地址偏移: 0x20-0x24 */
} GPIO_TypeDef;
這段代碼用 typedef 關鍵字聲明了名為 GPIO_TypeDef 的結構體類型,結構體內有 8 個成員變量,變量名正好對應寄存器的名字。 C 語言的語法規定,結構體內變量的存儲空間是連續的,其中 32 位的變量占用 4 個字節, 16 位的變量占用 2 個字節
也就是說,我們定義的這個 GPIO_TypeDef , 假如這個結構體的首地址為 0x40021C00(這也是第一個成員變量 MODER 的地址) , 那么結構體中第二個成員變量OTYPER 的地址即為 0x4002 1C00 +0x04 , 加上的這個 0x04 ,正是代表 MODER 所占用的4 個字節地址的偏移量,其它成員變量相對於結構體首地址的偏移,在上述代碼右側注釋已給出,其中的 BSRR 寄存器分成了低 16 位 BSRRL 和高 16 位 BSRRH, BSRRL 置 1 引腳輸出高電平, BSRRH 置 1 引腳輸出低電平,這里分開只是為了方便操作。
這樣的地址偏移與 STM32 GPIO 外設定義的寄存器地址偏移一一對應,只要給結構體設置好首地址,就能把結構體內成員的地址確定下來,然后就能以結構體的形式訪問寄存器了
代碼 5-7 通過結構體指針訪問寄存器
GPIO_TypeDef * GPIOx; //定義一個 GPIO_TypeDef 型結構體指針 GPIOx
GPIOx = GPIOH_BASE; //把指針地址設置為宏 GPIOH_BASE 地址
GPIOx->BSRRL = 0xFFFF; //通過指針訪問並修改 GPIOH_BSRRL 寄存器
GPIOx->MODER = 0xFFFFFFFF; //修改 GPIOH_MODER 寄存器
GPIOx->OTYPER =0xFFFFFFFF; //修改 GPIOH_OTYPER 寄存器
uint32_t temp;
temp = GPIOx->IDR; //讀取 GPIOH_IDR 寄存器的值到變量 temp 中
更進一步,直接使用宏定義好 GPIO_TypeDef 類型的指針,而且指針指向各個 GPIO 端口的首地址,使用時我們直接用該宏訪問寄存器即可
代碼 5-8 定義好 GPIO 端口首地址址針
/*使用 GPIO_TypeDef 把地址強制轉換成指針*/
#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)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
/*使用定義好的宏直接訪問*/
/*訪問 GPIOH 端口的寄存器*/
GPIOH->BSRRL = 0xFFFF; //通過指針訪問並修改
GPIOH_BSRRL 寄存器 GPIOH->MODER = 0xFFFFFFF; //修改 GPIOH_MODER 寄存器
GPIOH->OTYPER =0xFFFFFFF; //修改 GPIOH_OTYPER 寄存器
uint32_t temp; temp = GPIOH->IDR; //讀取 GPIOH_IDR 寄存器的值到變量 temp 中
/*訪問 GPIOA 端口的寄存器*/
GPIOA->BSRRL = 0xFFFF; //通過指針訪問並修改 GPIOA_BSRRL 寄存器
GPIOA->MODER = 0xFFFFFFF; //修改 GPIOA_MODER 寄存器
GPIOA->OTYPER =0xFFFFFFF; //修改 GPIOA_OTYPER 寄存器
uint32_t temp;
temp = GPIOA->IDR; //讀取 GPIOA_IDR 寄存器的值到變量 temp 中
修改寄存器的位操作方法
把變量某位清零
代碼清單 5-1 對某位清零
//定義一個變量 a = 1001 1111 b (二進制數)
unsigned char a = 0x9f;
//對 bit2 清零
a &= ~(1<<2);
//括號中的 1 左移兩位, (1<<2)得二進制數: 0000 0100 b
//按位取反, ~(1<<2)得 1111 1011 b
//假如 a 中原來的值為二進制數: a = 1001 1111 b
//所得的數與 a 作”位與&”運算, a = (1001 1111 b)&(1111 1011 b),
//經過運算后, a 的值 a=1001 1011 b
// a 的 bit2 位被被零,而其它位不變。
某幾個連續位清零
//若把 a 中的二進制位分成 2 個一組
//即 bit0、 bit1 為第 0 組, bit2、 bit3 為第 1 組,
// bit4、 bit5 為第 2 組, bit6、 bit7 為第 3 組
//要對第 1 組的 bit2、 bit3 清零
a &= ~(3<<2*1);
//括號中的 3 左移兩位, (3<<2*1)得二進制數: 0000 1100 b
//按位取反, ~(3<<2*1)得 1111 0011 b
//假如 a 中原來的值為二進制數: a = 1001 1111 b
//所得的數與 a 作”位與&”運算, a = (1001 1111 b)&(1111 0011 b),
//經過運算后, a 的值 a=1001 0011 b
// a 的第 1 組的 bit2、 bit3 被清零,而其它位不變。
//上述(~(3<<2*1))中的(1)即為組編號;如清零第 3 組 bit6、 bit7 此處應為 3
//括號中的(2)為每組的位數,每組有 2 個二進制位;若分成 4 個一組,此處即為 4
//括號中的(3)是組內所有位都為 1 時的值;若分成 4 個一組,此處即為二進制數“1111 b”
//例如對第 2 組 bit4、 bit5 清零
a &= ~(3<<2*2);
對變量的某幾位進行賦值
代碼清單 5-3 對某幾位進行賦值
//a = 1000 0011 b
//此時對清零后的第 2 組 bit4、 bit5 設置成二進制數“01 b ”
a |= (1<<2*2);
//a = 1001 0011 b,成功設置了第 2 組的值,其它組不變
對變量的某位取反
代碼清單 5-4 對某位進行取反操作
//a = 1001 0011 b
//把 bit6 取反,其它位不變
a ^=(1<<6);
//a = 1101 0011 b
參考引用:
- 野火---《零死角玩轉STM32-F429挑戰者》
- 《STM32F4xx中文參考手冊》
- 《Cortex-M4內核編程手冊》