LPC11XX.h頭文件解析
在前面的示例中,給出了預(yù)定義部分的內(nèi)容,但沒(méi)有進(jìn)行解釋。這里就先來(lái)討論一下在第一個(gè)演示示例中預(yù)定義部分的內(nèi)容。
本文引用地址:http://m.butianyuan.cn/article/201611/316319.htm先看第一個(gè)部分,代碼如下:
#define__IOvolatile
#define__Ovolatile
#define__Ivolatile const
typedef unsignedchar uint8_t;
typedef unsigned shortint uint16_t;
typedef unsignedint uint32_t;
#pragma anon_unions
__I:定義輸入口。既然是輸入,那么寄存器的值就隨時(shí)會(huì)被外部修改,所以不能對(duì)它進(jìn)行優(yōu)化,每次都必須從寄存器中讀取。也不能寫(xiě)(即只讀),否則就不是輸入而是輸出了。
__O:定義輸出口,也不能對(duì)它進(jìn)行優(yōu)化,不然端口連續(xù)兩次輸出相同的值,編譯器就會(huì)認(rèn)為沒(méi)有變化,而忽略后那一次輸出,假如外部在兩次輸出中間修改了值,那就會(huì)影響輸出的正確性??蓪?xiě),否則就不能稱為輸出了。
__IO:定義輸入輸出口,也不能對(duì)它進(jìn)行優(yōu)化,原因同上??勺x可寫(xiě)。
第三至五行是類型的聲明,把無(wú)符號(hào)的字符型、短整型、整型分別用uint8_t、uint16_t、uint32_t來(lái)表示,以突出它們所占用的字節(jié)數(shù),方便查看。
在最后一行中,pragma是一個(gè)關(guān)鍵字,它的使用較為復(fù)雜,有興趣的讀者可自行上網(wǎng)查閱。這里只需要記住,在使用到帶union的結(jié)構(gòu)體定義時(shí),在預(yù)定義部分一定要有“#pragma anon_unions”這樣一句,否則編譯通不過(guò)。在第一個(gè)演示示例中,由于在后面定義了一個(gè)帶union的結(jié)構(gòu)體,所以在這里必須要寫(xiě)這一句。
接下來(lái)看第二個(gè)部分,這部分全部使用結(jié)構(gòu)體來(lái)對(duì)寄存器進(jìn)行描述。先來(lái)看對(duì)SYSCON結(jié)構(gòu)體的定義:
typedef struct
{
__IO uint32_t SYSMEMREMAP; /*!< Offset: 0x000 (R/W) System memory remap Register */
__IO uint32_t PRESETCTRL; /*!< Offset: 0x004 (R/W) Peripheral reset control Register */
__IO uint32_t SYSPLLCTRL; /*!< Offset: 0x008 (R/W) System PLL control Register */
__I uint32_t SYSPLLSTAT; /*!< Offset: 0x00C (R/ ) System PLL status Register */
uint32_t RESERVED0[4];
__IO uint32_t SYSOSCCTRL; /*!< Offset: 0x020 (R/W) System oscillator control Register */
__IO uint32_t WDTOSCCTRL; /*!< Offset: 0x024 (R/W) Watchdog oscillator control Register */
__IO uint32_t IRCCTRL; /*!< Offset: 0x028 (R/W) IRC control Register */
uint32_t RESERVED1[1];
__I uint32_t SYSRSTSTAT; /*!< Offset: 0x030 (R/ ) System reset status Register */
uint32_t RESERVED2[3];
__IO uint32_t SYSPLLCLKSEL; /*!< Offset: 0x040 (R/W) System PLL clock source select Register */
__IO uint32_t SYSPLLCLKUEN; /*!< Offset: 0x044 (R/W) System PLL clock source update enable Register */
uint32_t RESERVED3[10];
__IO uint32_t MAINCLKSEL; /*!< Offset: 0x070 (R/W) Main clock source select Register */
__IO uint32_t MAINCLKUEN; /*!< Offset: 0x074 (R/W) Main clock source update enable Register */
__IO uint32_t SYSAHBCLKDIV; /*!< Offset: 0x078 (R/W) System AHB clock divider Register */
uint32_t RESERVED4[1];
__IO uint32_t SYSAHBCLKCTRL; /*!< Offset: 0x080 (R/W) System AHB clock control Register */
uint32_t RESERVED5[4];
__IO uint32_t SSP0CLKDIV; /*!< Offset: 0x094 (R/W) SSP0 clock divider Register */
__IO uint32_t UARTCLKDIV; /*!< Offset: 0x098 (R/W) UART clock divider Register */
__IO uint32_t SSP1CLKDIV; /*!< Offset: 0x09C (R/W) SSP1 clock divider Register */
uint32_t RESERVED6[1];
uint32_t RESERVED7[11];
__IO uint32_t WDTCLKSEL; /*!< Offset: 0x0D0 (R/W) WDT clock source select Register */
__IO uint32_t WDTCLKUEN; /*!< Offset: 0x0D4 (R/W) WDT clock source update enable Register */
__IO uint32_t WDTCLKDIV; /*!< Offset: 0x0D8 (R/W) WDT clock divider Register */
uint32_t RESERVED9[1];
__IO uint32_t CLKOUTCLKSEL; /*!< Offset: 0x0E0 (R/W) CLKOUT clock source select Register */
__IO uint32_t CLKOUTUEN; /*!< Offset: 0x0E4 (R/W) CLKOUT clock source update enable Register */
__IO uint32_t CLKOUTDIV; /*!< Offset: 0x0E8 (R/W) CLKOUT clock divider Register */
uint32_t RESERVED10[5];
__I uint32_t PIOPORCAP0; /*!< Offset: 0x100 (R/ ) POR captured PIO status 0 Register */
__I uint32_t PIOPORCAP1; /*!< Offset: 0x104 (R/ ) POR captured PIO status 1 Register */
uint32_t RESERVED11[11];
uint32_t RESERVED12[7];
__IO uint32_t BODCTRL; /*!< Offset: 0x150 (R/W) BOD control Register */
__IO uint32_t SYSTCKCAL; /*!< Offset: 0x154 (R/W) System tick counter calibration Register */
uint32_t RESERVED13[1];
uint32_t RESERVED14[5];
uint32_t RESERVED15[2];
uint32_t RESERVED16[34];
__IO uint32_t STARTAPRP0; /*!< Offset: 0x200 (R/W) Start logic edge control Register 0 */
__IO uint32_t STARTERP0; /*!< Offset: 0x204 (R/W) Start logic signal enable Register 0 */
__O uint32_t STARTRSRP0CLR; /*!< Offset: 0x208 ( /W) Start logic reset Register 0 */
__I uint32_t STARTSRP0; /*!< Offset: 0x20C (R/ ) Start logic status Register 0 */
__IO uint32_t STARTAPRP1; /*!< Offset: 0x210 (R/W) Start logic edge control Register 1 (LPC11UXX only) */
__IO uint32_t STARTERP1; /*!< Offset: 0x214 (R/W) Start logic signal enable Register 1 (LPC11UXX only) */
__O uint32_t STARTRSRP1CLR; /*!< Offset: 0x218 ( /W) Start logic reset Register 1 (LPC11UXX only) */
__I uint32_t STARTSRP1; /*!< Offset: 0x21C (R/ ) Start logic status Register 1 (LPC11UXX only) */
uint32_t RESERVED17[4];
__IO uint32_t PDSLEEPCFG; /*!< Offset: 0x230 (R/W) Power-down states in Deep-sleep mode Register */
__IO uint32_t PDAWAKECFG; /*!< Offset: 0x234 (R/W) Power-down states after wake-up from Deep-sleep mode Register*/
__IO uint32_t PDRUNCFG; /*!< Offset: 0x238 (R/W) Power-down configuration Register*/
uint32_t RESERVED18[110];
__I uint32_t DEVICE_ID; /*!< Offset: 0x3F4 (R/ ) Device ID Register */
} LPC_SYSCON_TypeDef;
從中可以看出,大部分語(yǔ)句都加上了“__IO”的前綴,這是由于這部分寄存器單元訪問(wèn)的特殊性決定的。“uint32_t”則反映了定義的變量會(huì)占用4個(gè)字節(jié)的地址空間,因?yàn)樵谇懊娴暮甓x中已經(jīng)知道,uint32_t就是“unsignedint”型。同時(shí)要特別注意一點(diǎn),在這個(gè)結(jié)構(gòu)體中定義的各個(gè)變量的順序不能改變,也就是說(shuō)各個(gè)變量在結(jié)構(gòu)體中的位置是固定的。這是因?yàn)樵诮Y(jié)構(gòu)體內(nèi)定義的各個(gè)變量之間存在著嚴(yán)格的地址偏移量關(guān)系,這點(diǎn)從每一句后面的注解中也可以很清楚地看到。例如第一個(gè)變量定義的是“SYSMEMREMAP”,由于它被定義為“unsignedint”型的,所以占用4個(gè)字節(jié)的地址空間;而下一個(gè)定義的變量“PRESETCTRL”的地址,則是前面的變量“SYSMEMREMAP”地址再向后偏移4個(gè)字節(jié)。同理,第三個(gè)定義的變量“SYSPLLCTRL”的地址是第二個(gè)變量“SYSMEMREMAP”地址再向后偏移4個(gè)字節(jié)(因?yàn)榈诙€(gè)變量仍定義為“unsignedint”型),或者是第一個(gè)變量“SYSMEMREMAP”地址向后偏移8個(gè)字節(jié)。所以,如果不按照順序來(lái)定義,其對(duì)應(yīng)的地址將會(huì)出錯(cuò)。比如,如果把第二個(gè)變量“SYSMEMREMAP”刪除,由于地址偏移量不變,則原來(lái)的第三個(gè)變量“SYSPLLCTRL”的地址將會(huì)被對(duì)應(yīng)到原來(lái)第二個(gè)變量的地址(相對(duì)第一個(gè)變量偏移4字節(jié)而不是8字節(jié)),這將導(dǎo)致出錯(cuò)!這是因?yàn)樵贑PU中各個(gè)寄存器之間的地址是固定不變的,這一點(diǎn)目前可能會(huì)有些難理解,在后面討論了結(jié)構(gòu)體的指針以后就會(huì)明白的。
下面先來(lái)看一下,剛才定義在結(jié)構(gòu)體“SYSCON”中的各成員變量,是如何與LPC1114內(nèi)部的寄存器進(jìn)行一一對(duì)映的。為了方便討論,先看一下LPC1114內(nèi)部的Memory Map(內(nèi)存地圖)是怎么分配的,如下圖所示。
從上表中可以看出,因?yàn)閟ystem control模塊的起始地址是0x40048000,所以它的基址就是0x40048000。這樣它內(nèi)部各寄存器的地址就可以以基址為參考點(diǎn),用相對(duì)于基址的偏移量來(lái)進(jìn)行描述。比如,在前面討論時(shí)鐘配置時(shí)用到的寄存器SYSPLLCLKSEL、MAINCLKSEL等,它們相對(duì)于基址的偏移地址就是0x040和0x070(查上表中的Address offset一列),而其絕對(duì)地址則是0x40048040和0x40048070(分別加上基址的值)。
我們知道,要訪問(wèn)CPU內(nèi)部的硬件,最終只能通過(guò)它的地址進(jìn)行訪問(wèn),而我們對(duì)其進(jìn)行的命名(如剛才的SYSPLLCLKSEL、MAINCLKSEL等)都要通過(guò)一種對(duì)映關(guān)系把它們聯(lián)系起來(lái)。因?yàn)镃PU不知道SYSPLLCLKSEL、MAINCLKSEL是什么,但它知道地址0x40048040、0x40048070的單元。而在高級(jí)語(yǔ)言中,直接使用地址不僅不直觀,開(kāi)發(fā)者還要費(fèi)力去記住每個(gè)地址的寄存器功能,很不合適。為了適應(yīng)高級(jí)語(yǔ)言的特點(diǎn),我們就通過(guò)這種給寄存器命名,并讓該名稱對(duì)映到寄存器的實(shí)際地址的方式來(lái)處理。經(jīng)過(guò)這樣處理后,開(kāi)發(fā)者就可以在程序中直接引用寄存器名稱了,大大提高了程序的可讀性,方便了開(kāi)發(fā)。
從內(nèi)存地圖中可以看出,由于各設(shè)備的編址是分類的,所以使用高級(jí)語(yǔ)言中的“結(jié)構(gòu)體”來(lái)處理這種名稱與地址的對(duì)映關(guān)系是十分合適的。每一個(gè)結(jié)構(gòu)體可對(duì)應(yīng)一個(gè)分類,而分類中的寄存器就可以定義為這個(gè)結(jié)構(gòu)體內(nèi)的成員變量,各成員變量又嚴(yán)格對(duì)映到寄存器的實(shí)際地址。在前面示例部分的程序中,已經(jīng)在頭文件部分引入了這種結(jié)構(gòu)體并進(jìn)行了地址對(duì)映。
接下來(lái)再回到剛才定義的結(jié)構(gòu)體“SYSCON”的討論上來(lái)。從該結(jié)構(gòu)體定義中可以看到,它內(nèi)部定義的成員變量其實(shí)就是system control模塊內(nèi)的所有寄存器(見(jiàn)上表)。但是還沒(méi)完,因?yàn)槎x了“SYSCON”這個(gè)結(jié)構(gòu)體只相當(dāng)于對(duì)system control模塊進(jìn)行了“封裝”(即進(jìn)行了寄存器的按順序命名),還沒(méi)有對(duì)它進(jìn)行地址的對(duì)映。
下面給出預(yù)定義部分中第三部分的內(nèi)容,代碼如下:
#define LPC_APB0_BASE(0x40000000UL)
#define LPC_AHB_BASE(0x50000000UL)
#define LPC_IOCON_BASE(LPC_APB0_BASE + 0x44000)
#define LPC_SYSCON_BASE(LPC_APB0_BASE + 0x48000)
#define SCS_BASE(0xE000E000UL)
#define LPC_SYSCON((LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE)
#define LPC_IOCON((LPC_IOCON_TypeDef*) LPC_IOCON_BASE )
#define LPC_GPIO0_BASE(LPC_AHB_BASE+ 0x00000)
#define LPC_GPIO1_BASE(LPC_AHB_BASE+ 0x10000)
#define LPC_GPIO2_BASE(LPC_AHB_BASE+ 0x20000)
#define LPC_GPIO3_BASE(LPC_AHB_BASE+ 0x30000)
#define SysTick_BASE(SCS_BASE +0x0010UL)
#define LPC_GPIO0((LPC_GPIO_TypeDef*) LPC_GPIO0_BASE )
#define LPC_GPIO1((LPC_GPIO_TypeDef*) LPC_GPIO1_BASE )
#define LPC_GPIO2((LPC_GPIO_TypeDef*) LPC_GPIO2_BASE )
#define LPC_GPIO3((LPC_GPIO_TypeDef*) LPC_GPIO3_BASE )
#define SysTick((SysTick_Type*)SysTick_BASE)
可見(jiàn),這部分又全是用define進(jìn)行的宏定義。因這里只討論與“SYSCON”結(jié)構(gòu)體相關(guān)的內(nèi)容,所以只需看第一、四、六行,其它部分暫時(shí)不作說(shuō)明。前面說(shuō)過(guò),system control模塊位于APB設(shè)備區(qū),所以第一行先進(jìn)行了APB設(shè)備區(qū)的基址定義,第四行又進(jìn)行了SYSCON(即system control模塊區(qū))的基址定義。而真正進(jìn)行結(jié)構(gòu)體與地址對(duì)映的,是第六行的語(yǔ)句,現(xiàn)單獨(dú)把它剔出來(lái)進(jìn)行討論。該語(yǔ)句如下:
#define LPC_SYSCON((LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE)
首先來(lái)看,(LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE是把LPC_SYSCON_BASE(即SYSCON的基址)強(qiáng)行轉(zhuǎn)換為一個(gè)LPC_SYSCON_TypeDef結(jié)構(gòu)體的指針類型。根據(jù)前面的定義,LPC_SYSCON_BASE的值是0x40048000(LPC_APB0_BASE + 0x48000),而強(qiáng)行把它轉(zhuǎn)換為一個(gè)LPC_SYSCON_TypeDef結(jié)構(gòu)體的指針類型,則這個(gè)結(jié)構(gòu)體的首地址就是LPC_SYSCON_BASE的基址(0x40048000)。這樣一來(lái),結(jié)構(gòu)體LPC_SYSCON_TypeDef內(nèi)部各成員變量的地址,就是以這個(gè)基址(0x40048000)為參考點(diǎn)的偏移地址了。
首地址對(duì)映了,那偏移量怎么實(shí)現(xiàn)呢?這就與結(jié)構(gòu)體中成員變量定義的數(shù)據(jù)類型有關(guān)了。回顧一下上面的SYSCON這個(gè)結(jié)構(gòu)體中,成員變量都用的是“unsignedint”型來(lái)定義,占用4個(gè)字節(jié)的空間,而觀察上面的“system control模塊內(nèi)所有寄存器的分布情況表”可以看出,它的每個(gè)寄存器之間正好是4個(gè)字節(jié)(或是4的正數(shù)倍)的地址偏移,所以只要用“unsignedint”型來(lái)定義成員變量,寄存器的偏移地址就自動(dòng)適應(yīng)了。如果遇到保留地址,則可以通過(guò)定義“unsignedint”型的空數(shù)組來(lái)避開(kāi),以保證后面成員變量的地址偏移正確。另外,由于LPC1114是32的結(jié)構(gòu),所以它的寄存器也是32位的,剛好是4個(gè)字節(jié),這也是為何每個(gè)寄存器之間是4個(gè)字節(jié)地址偏移量的原因。在表中還可以看出寄存器的讀寫(xiě)屬性,這與前面結(jié)構(gòu)體定義中的“__I”、“__IO”、“__O”等就可以聯(lián)系起來(lái)了。
接下來(lái),通過(guò)define語(yǔ)句來(lái)把剛才的結(jié)構(gòu)體指針取個(gè)“別名”,即LPC_SYSCON。這時(shí)LPC_SYSCON就是這個(gè)結(jié)構(gòu)體指針類型了,通過(guò)“LPC_SYSCON->”的方式就可以來(lái)引用它內(nèi)部的成員變量(即system control模塊內(nèi)的各個(gè)寄存器)了。這樣一來(lái),就可以把底層的地址用高級(jí)語(yǔ)言的名稱來(lái)表示,非常直觀,比如前面例子中的要讓PLL輸入選擇外部晶體振蕩,執(zhí)行語(yǔ)句“LPC_SYSCON->SYSPLLCLKSEL = 0x00000001;”就可以了,但如果沒(méi)有這種地址對(duì)映,就必須寫(xiě)成“MOV0x40048040,#0x00000001”,這當(dāng)然就非常不直觀了,不查手冊(cè)還不知道地址0x40048040是什么寄存器。
上述只是通過(guò)SYSCON這個(gè)結(jié)構(gòu)體來(lái)進(jìn)行討論的,其它的結(jié)構(gòu)體定義沒(méi)有討論。但它們所采用的方法是一樣的,讀者可參考上面對(duì)SYSCON結(jié)構(gòu)體的分析方法來(lái)自行研究,這里就不再贅述了。
評(píng)論