LPC11XX.h頭文件解析
在前面的示例中,給出了預定義部分的內容,但沒有進行解釋。這里就先來討論一下在第一個演示示例中預定義部分的內容。
本文引用地址:http://m.butianyuan.cn/article/201611/316319.htm先看第一個部分,代碼如下:
#define__IOvolatile
#define__Ovolatile
#define__Ivolatile const
typedef unsignedchar uint8_t;
typedef unsigned shortint uint16_t;
typedef unsignedint uint32_t;
#pragma anon_unions
__I:定義輸入口。既然是輸入,那么寄存器的值就隨時會被外部修改,所以不能對它進行優(yōu)化,每次都必須從寄存器中讀取。也不能寫(即只讀),否則就不是輸入而是輸出了。
__O:定義輸出口,也不能對它進行優(yōu)化,不然端口連續(xù)兩次輸出相同的值,編譯器就會認為沒有變化,而忽略后那一次輸出,假如外部在兩次輸出中間修改了值,那就會影響輸出的正確性??蓪懀駝t就不能稱為輸出了。
__IO:定義輸入輸出口,也不能對它進行優(yōu)化,原因同上??勺x可寫。
第三至五行是類型的聲明,把無符號的字符型、短整型、整型分別用uint8_t、uint16_t、uint32_t來表示,以突出它們所占用的字節(jié)數,方便查看。
在最后一行中,pragma是一個關鍵字,它的使用較為復雜,有興趣的讀者可自行上網查閱。這里只需要記住,在使用到帶union的結構體定義時,在預定義部分一定要有“#pragma anon_unions”這樣一句,否則編譯通不過。在第一個演示示例中,由于在后面定義了一個帶union的結構體,所以在這里必須要寫這一句。
接下來看第二個部分,這部分全部使用結構體來對寄存器進行描述。先來看對SYSCON結構體的定義:
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;
從中可以看出,大部分語句都加上了“__IO”的前綴,這是由于這部分寄存器單元訪問的特殊性決定的。“uint32_t”則反映了定義的變量會占用4個字節(jié)的地址空間,因為在前面的宏定義中已經知道,uint32_t就是“unsignedint”型。同時要特別注意一點,在這個結構體中定義的各個變量的順序不能改變,也就是說各個變量在結構體中的位置是固定的。這是因為在結構體內定義的各個變量之間存在著嚴格的地址偏移量關系,這點從每一句后面的注解中也可以很清楚地看到。例如第一個變量定義的是“SYSMEMREMAP”,由于它被定義為“unsignedint”型的,所以占用4個字節(jié)的地址空間;而下一個定義的變量“PRESETCTRL”的地址,則是前面的變量“SYSMEMREMAP”地址再向后偏移4個字節(jié)。同理,第三個定義的變量“SYSPLLCTRL”的地址是第二個變量“SYSMEMREMAP”地址再向后偏移4個字節(jié)(因為第二個變量仍定義為“unsignedint”型),或者是第一個變量“SYSMEMREMAP”地址向后偏移8個字節(jié)。所以,如果不按照順序來定義,其對應的地址將會出錯。比如,如果把第二個變量“SYSMEMREMAP”刪除,由于地址偏移量不變,則原來的第三個變量“SYSPLLCTRL”的地址將會被對應到原來第二個變量的地址(相對第一個變量偏移4字節(jié)而不是8字節(jié)),這將導致出錯!這是因為在CPU中各個寄存器之間的地址是固定不變的,這一點目前可能會有些難理解,在后面討論了結構體的指針以后就會明白的。
下面先來看一下,剛才定義在結構體“SYSCON”中的各成員變量,是如何與LPC1114內部的寄存器進行一一對映的。為了方便討論,先看一下LPC1114內部的Memory Map(內存地圖)是怎么分配的,如下圖所示。
從上表中可以看出,因為system control模塊的起始地址是0x40048000,所以它的基址就是0x40048000。這樣它內部各寄存器的地址就可以以基址為參考點,用相對于基址的偏移量來進行描述。比如,在前面討論時鐘配置時用到的寄存器SYSPLLCLKSEL、MAINCLKSEL等,它們相對于基址的偏移地址就是0x040和0x070(查上表中的Address offset一列),而其絕對地址則是0x40048040和0x40048070(分別加上基址的值)。
我們知道,要訪問CPU內部的硬件,最終只能通過它的地址進行訪問,而我們對其進行的命名(如剛才的SYSPLLCLKSEL、MAINCLKSEL等)都要通過一種對映關系把它們聯系起來。因為CPU不知道SYSPLLCLKSEL、MAINCLKSEL是什么,但它知道地址0x40048040、0x40048070的單元。而在高級語言中,直接使用地址不僅不直觀,開發(fā)者還要費力去記住每個地址的寄存器功能,很不合適。為了適應高級語言的特點,我們就通過這種給寄存器命名,并讓該名稱對映到寄存器的實際地址的方式來處理。經過這樣處理后,開發(fā)者就可以在程序中直接引用寄存器名稱了,大大提高了程序的可讀性,方便了開發(fā)。
從內存地圖中可以看出,由于各設備的編址是分類的,所以使用高級語言中的“結構體”來處理這種名稱與地址的對映關系是十分合適的。每一個結構體可對應一個分類,而分類中的寄存器就可以定義為這個結構體內的成員變量,各成員變量又嚴格對映到寄存器的實際地址。在前面示例部分的程序中,已經在頭文件部分引入了這種結構體并進行了地址對映。
接下來再回到剛才定義的結構體“SYSCON”的討論上來。從該結構體定義中可以看到,它內部定義的成員變量其實就是system control模塊內的所有寄存器(見上表)。但是還沒完,因為定義了“SYSCON”這個結構體只相當于對system control模塊進行了“封裝”(即進行了寄存器的按順序命名),還沒有對它進行地址的對映。
下面給出預定義部分中第三部分的內容,代碼如下:
#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)
可見,這部分又全是用define進行的宏定義。因這里只討論與“SYSCON”結構體相關的內容,所以只需看第一、四、六行,其它部分暫時不作說明。前面說過,system control模塊位于APB設備區(qū),所以第一行先進行了APB設備區(qū)的基址定義,第四行又進行了SYSCON(即system control模塊區(qū))的基址定義。而真正進行結構體與地址對映的,是第六行的語句,現單獨把它剔出來進行討論。該語句如下:
#define LPC_SYSCON((LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE)
首先來看,(LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE是把LPC_SYSCON_BASE(即SYSCON的基址)強行轉換為一個LPC_SYSCON_TypeDef結構體的指針類型。根據前面的定義,LPC_SYSCON_BASE的值是0x40048000(LPC_APB0_BASE + 0x48000),而強行把它轉換為一個LPC_SYSCON_TypeDef結構體的指針類型,則這個結構體的首地址就是LPC_SYSCON_BASE的基址(0x40048000)。這樣一來,結構體LPC_SYSCON_TypeDef內部各成員變量的地址,就是以這個基址(0x40048000)為參考點的偏移地址了。
首地址對映了,那偏移量怎么實現呢?這就與結構體中成員變量定義的數據類型有關了。回顧一下上面的SYSCON這個結構體中,成員變量都用的是“unsignedint”型來定義,占用4個字節(jié)的空間,而觀察上面的“system control模塊內所有寄存器的分布情況表”可以看出,它的每個寄存器之間正好是4個字節(jié)(或是4的正數倍)的地址偏移,所以只要用“unsignedint”型來定義成員變量,寄存器的偏移地址就自動適應了。如果遇到保留地址,則可以通過定義“unsignedint”型的空數組來避開,以保證后面成員變量的地址偏移正確。另外,由于LPC1114是32的結構,所以它的寄存器也是32位的,剛好是4個字節(jié),這也是為何每個寄存器之間是4個字節(jié)地址偏移量的原因。在表中還可以看出寄存器的讀寫屬性,這與前面結構體定義中的“__I”、“__IO”、“__O”等就可以聯系起來了。
接下來,通過define語句來把剛才的結構體指針取個“別名”,即LPC_SYSCON。這時LPC_SYSCON就是這個結構體指針類型了,通過“LPC_SYSCON->”的方式就可以來引用它內部的成員變量(即system control模塊內的各個寄存器)了。這樣一來,就可以把底層的地址用高級語言的名稱來表示,非常直觀,比如前面例子中的要讓PLL輸入選擇外部晶體振蕩,執(zhí)行語句“LPC_SYSCON->SYSPLLCLKSEL = 0x00000001;”就可以了,但如果沒有這種地址對映,就必須寫成“MOV0x40048040,#0x00000001”,這當然就非常不直觀了,不查手冊還不知道地址0x40048040是什么寄存器。
上述只是通過SYSCON這個結構體來進行討論的,其它的結構體定義沒有討論。但它們所采用的方法是一樣的,讀者可參考上面對SYSCON結構體的分析方法來自行研究,這里就不再贅述了。
評論