|
前面介紹了存儲器映射、寄存器和寄存器映射,這些都是為了介紹使用 C語言封裝寄存器做鋪墊。這里我們通過一個實例來對 C 語言封裝寄存器進行介紹。
具體實例:控制 GPIOC 端口的第 0 管腳輸出一個低電平。首先我們需要知道GPIOC 端口外設是掛接在哪個總線上的,然后根據總線基地址和本身的偏移地址得到 GPIOC 外設基地址,最后通過這個外設基地址得到里面各種寄存器基地址。
總線和外設基地址封裝
根據寄存器的概念,我們可以使用 C 語言中的宏定義對寄存器進行定義。具體代碼如下:
//定義外設基地址
#define PERIPH_BASE ((unsigned int)0x40000000) 1)
//定義 APB2 總線基地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 2)
//定義 GPIOC 外設基地址
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) 3)
//定義寄存器基地址 這里以 GPIOC 為例
#define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00) 4)
#define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04)
#define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08)
#define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C)
#define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10)
#define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14)
#define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18)
上述代碼中我們在后面備注了數字,下面對其進行簡單介紹下其功能:
·
定義外設的基地址,這個地址也是 Block2 的基地址。
·
·
定義 APB2 總線基地址,因為 Block2 的第一個總線是 APB1,而 APB2 總線地址只需要加上對應的地址偏移量即可。
·
·
定義 GPIO 外設基地址,因為 GPIOC 是掛接在 APB2 總線上的,所以找到對應的端口地址偏移量即可知道 GPIOC 端口基地址。
·
·
定義 GPIO 外設寄存器基地址,這里以 GPIOC 端口為例,因GPIOC_CRL是 GPIOC 外設的第一個寄存器,所以基地址就是 GPIOC 地址,其他寄存器地址只需要在 GPIOC 基地址上加上相應的偏移量即可。
·
我們得到了寄存器具體的地址,那么就可以使用 C 語言指針來操作讀寫。例如我們需要 GPIOC0 輸出一個低電平或者高電平,可以使用下面語句來操作。
//控制 GPIOC 第 0 管腳輸出一個低電平
GPIOC_BSRR = (0x01<<(16+0));
//控制 GPIOC 第 0 管腳輸出一個高電平
GPIOC_BSRR = (0x01<<0);
我們知道 GPIOC_BSRR 的值是這個寄存器的地址,但是編譯器不知道它是地址,而是把它當做立即數,所以我們必須要強制轉換為(unsigned int *)指針類型才可以對其操作,這一點特別要注意。嵌入式物聯網智能硬件等系統學習企鵝意義氣嗚嗚吧久零就易,然后再在前面加上一個“*”作取指針操作,表示對該地址內內容進行寫,讀操作也同樣使用“*”取指針操作。如下:
unsigned int temp;
temp =GPIOC_IDR;
將寄存器內的數據保存在變量 temp 中,使用到變量時一定要進行定義。
寄存器封裝
通過前面講解,我們已經可以對寄存器進行操作,但是還稍有不足,因為STM32的GPIO比較多, 我們不可能每使用一個GPIO都做前面一樣的一大堆定義。根據GPIO寄存器的特點,我們知道不論GPIOA還是GPIOB等都擁有一組功能相同的寄存器,如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR等等,它們只是地址不一樣。
為了更方便地訪問寄存器,我們引入C語言中的結構體對寄存器進行封裝,具體代碼如下:
typedef unsigned int uint32_t; /*無符號 32 位變量*/
typedef unsigned short int uint16_t; /*無符號 16 位變量*/
/* GPIO 寄存器列表 */
typedef struct
{
uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
uint32_t IDR; /*GPIO 數據輸入寄存器 地址偏移: 0x08 */
uint32_t ODR; /*GPIO 數據輸出寄存器 地址偏移: 0x0C */
uint32_t BSRR; /*GPIO 位設置/清除寄存器 地址偏移: 0x10 */
uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
uint16_t LCKR; /*GPIO 端口配置鎖定寄存器 地址偏移: 0x18 */
}GPIO_TypeDef;
這段代碼用 typedef 關鍵字聲明了名為GPIO_TypeDef的結構體類型,結構體內有7 個成員變量,變量名正好對應寄存器的名字。C 語言的語法規定,結構體內變量的存儲空間是連續的,其中32位的變量占用4個字節,16 位的變量占用2個字節。
于是,我們定義的GPIO_TypeDef,假如這個結構體的首地址為0x4001 1000(這也是第一個成員變量 CRL的地址),那么結構體中第二個成員變量CRH的地址即為0x4001 1000 +0x04,加上的這個0x04,正是代表CRH所占用的4個字節地址的偏移量,其它成員變量相對于結構體首地址的偏移,在上述代碼右側注釋已給出。
這樣的地址偏移與STM32 GPIO外設定義的寄存器地址偏移一一對應,只要給結構體設置好首地址,就能把結構體內成員的地址確定下來,然后就能以結構體的形式訪問寄存器了,比如我們還是將GPIOC0輸出低電平,具體代碼如下:
GPIO_TypeDef * GPIOx; //定義一個GPIO_TypeDef型結構體指針GPIOx
GPIOx = GPIOC_BASE; //把指針地址設置為宏 GPIOC_BASE 地址
GPIOx->BSRR =(1<<(16+0)); //通過指針訪問并修改 GPIOC_BSRR 寄存器
這段代碼先用GPIO_TypeDef類型定義一個結構體指針GPIOx,并讓指針指向GPIOC基地址GPIOC_BASE,地址確定下來,然后根據C語言訪問結構體的內容,用GPIOx->BSRR寫寄存器。為了操作更簡便靈活,我們直接使用宏定義好GPIO_TypeDef類型的指針,而且指針指向各個GPIO端口的首地址,使用時我們直接用該宏訪問寄存器即可。具體代碼如下:
#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)
GPIOC->BSRR = (1<<(16+0));
我們這里僅僅以GPIO這個外設為例,給大家講解了如何使用C語言對寄存器封裝,對于其他的外設也是使用同樣方法。其實到了后面的實驗程序的編寫,我們都是使用ST公司提供的固件庫,他們把STM32所有外設都已經封裝好了,我們只需要調用即可。 我們這里分析這個封裝過程只是想讓大家更加清楚理解如何使用C來封裝寄存器的。