<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          LPC11XX.h頭文件解析

          作者: 時間:2016-11-13 來源:網(wǎng)絡(luò) 收藏
          從前面的第一個演示示例中可以看出,只實現(xiàn)一個LED的閃爍,其代碼量似乎要比51單片的多很多。仔細(xì)觀察后會發(fā)現(xiàn),其實除了多了時鐘配置以外,就數(shù)預(yù)定義部分的代碼數(shù)量最多,而且這部分大多是以結(jié)構(gòu)體的形式出現(xiàn)的。在正規(guī)的開發(fā)過程中,這部分預(yù)定義的內(nèi)容被是放在一些頭文件內(nèi)并包含進來的,前面的代碼只是為了編譯方便,所以把全部代碼都給出來,不太正規(guī)。下面就來討論一下在ARM-MDK環(huán)境中開發(fā)LPC1114的頭文件配置。

          在前面的示例中,給出了預(yù)定義部分的內(nèi)容,但沒有進行解釋。這里就先來討論一下在第一個演示示例中預(yù)定義部分的內(nèi)容。

          本文引用地址:http://www.ex-cimer.com/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

          第一、二、三行是三個宏定義,通過define語句把__IO等效為volatile,把__O等效為volatile,把__I等效為volatile const。一般來說宏定義都是大寫,但因為這里用的字母比較少(只有I或O或IO),所以再在其前面添加下劃線來進行區(qū)分,這樣做可以有效避免命名沖突問題。而volatile本身則是一個關(guān)鍵字,表示其后面定義的變量不讓編譯器進行優(yōu)化,即每次讀取或者修改值的時候,都必須重新從內(nèi)存或者寄存器中讀取或者修改。比如在單片機開發(fā)中,經(jīng)常會用到軟件延時,但若要軟件延時不被編譯器優(yōu)化掉,就必須在變量定義前加上關(guān)鍵字volatile,如“for(volatile unsigned int k=0;k<60000;k++);”。而volatile const則表示其后面定義的變量是只讀的,比如用它來定義一個只讀的狀態(tài)寄存器,定義為volatile是因為它的值可能會被硬件意想不到地改變,而定義為const是因為程序不應(yīng)該試圖去修改它的值。通俗的說,就是它定義的是一個“只讀變量”而不是常量,它的值是由硬件來改變的,不能通過程序?qū)懭雭砀淖?。總結(jié)一下:

          __I:定義輸入口。既然是輸入,那么寄存器的值就隨時會被外部修改,所以不能對它進行優(yōu)化,每次都必須從寄存器中讀取。也不能寫(即只讀),否則就不是輸入而是輸出了。
          __O:定義輸出口,也不能對它進行優(yōu)化,不然端口連續(xù)兩次輸出相同的值,編譯器就會認(rèn)為沒有變化,而忽略后那一次輸出,假如外部在兩次輸出中間修改了值,那就會影響輸出的正確性??蓪懀駝t就不能稱為輸出了。
          __IO:定義輸入輸出口,也不能對它進行優(yōu)化,原因同上??勺x可寫。

          第三至五行是類型的聲明,把無符號的字符型、短整型、整型分別用uint8_t、uint16_t、uint32_t來表示,以突出它們所占用的字節(jié)數(shù),方便查看。

          在最后一行中,pragma是一個關(guān)鍵字,它的使用較為復(fù)雜,有興趣的讀者可自行上網(wǎng)查閱。這里只需要記住,在使用到帶union的結(jié)構(gòu)體定義時,在預(yù)定義部分一定要有“#pragma anon_unions”這樣一句,否則編譯通不過。在第一個演示示例中,由于在后面定義了一個帶union的結(jié)構(gòu)體,所以在這里必須要寫這一句。

          接下來看第二個部分,這部分全部使用結(jié)構(gòu)體來對寄存器進行描述。先來看對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;

          從中可以看出,大部分語句都加上了“__IO”的前綴,這是由于這部分寄存器單元訪問的特殊性決定的。“uint32_t”則反映了定義的變量會占用4個字節(jié)的地址空間,因為在前面的宏定義中已經(jīng)知道,uint32_t就是“unsignedint”型。同時要特別注意一點,在這個結(jié)構(gòu)體中定義的各個變量的順序不能改變,也就是說各個變量在結(jié)構(gòu)體中的位置是固定的。這是因為在結(jié)構(gòu)體內(nèi)定義的各個變量之間存在著嚴(yán)格的地址偏移量關(guān)系,這點從每一句后面的注解中也可以很清楚地看到。例如第一個變量定義的是“SYSMEMREMAP”,由于它被定義為“unsignedint”型的,所以占用4個字節(jié)的地址空間;而下一個定義的變量“PRESETCTRL”的地址,則是前面的變量“SYSMEMREMAP”地址再向后偏移4個字節(jié)。同理,第三個定義的變量“SYSPLLCTRL”的地址是第二個變量“SYSMEMREMAP”地址再向后偏移4個字節(jié)(因為第二個變量仍定義為“unsignedint”型),或者是第一個變量“SYSMEMREMAP”地址向后偏移8個字節(jié)。所以,如果不按照順序來定義,其對應(yīng)的地址將會出錯。比如,如果把第二個變量“SYSMEMREMAP”刪除,由于地址偏移量不變,則原來的第三個變量“SYSPLLCTRL”的地址將會被對應(yīng)到原來第二個變量的地址(相對第一個變量偏移4字節(jié)而不是8字節(jié)),這將導(dǎo)致出錯!這是因為在CPU中各個寄存器之間的地址是固定不變的,這一點目前可能會有些難理解,在后面討論了結(jié)構(gòu)體的指針以后就會明白的。

          下面先來看一下,剛才定義在結(jié)構(gòu)體“SYSCON”中的各成員變量,是如何與LPC1114內(nèi)部的寄存器進行一一對映的。為了方便討論,先看一下LPC1114內(nèi)部的Memory Map(內(nèi)存地圖)是怎么分配的,如下圖所示。

          從內(nèi)存地圖中可以看出,由于LPC1114是32位結(jié)構(gòu)的,所以其尋址空間達(dá)到了4GB(從0x00000000~0xFFFFFFFF),且不管是什么模塊,都統(tǒng)一編址在其中,而并不像51單片機那樣,程序存儲和數(shù)據(jù)存儲是各自獨立編址的。通過觀察內(nèi)存地圖會發(fā)現(xiàn),地址分配是分類的,即:從0x00000000~0x10002000的區(qū)域是內(nèi)存區(qū)(包含了FLASH ROM和SRAM);從0x1FFF0000~0x1FFF4000的區(qū)域是引導(dǎo)區(qū)(即BOOT ROM區(qū));從0x40000000~0x40080000的區(qū)域是APB設(shè)備區(qū),它包含了除IO端口以外的所有外圍設(shè)備資源;從0x50000000~0x50200000的區(qū)域是AHB設(shè)備區(qū),它包含了所有的IO端口資源;從0xE0000000~0xE0100000區(qū)域是私有外圍設(shè)備總線區(qū);其它剩余區(qū)域為保留區(qū),便于將來升級擴展。
          剛才定義的結(jié)構(gòu)體“SYSCON”所對映的設(shè)備,就是在內(nèi)存地圖中位于APB區(qū)內(nèi)的system control模塊部分(地址為0x40048000~0x4004C000)。為了便于討論,下面把這部分的內(nèi)容單獨剔出來進行說明。下圖給出了system control模塊內(nèi)所有寄存器的分布情況。

          從上表中可以看出,因為system control模塊的起始地址是0x40048000,所以它的基址就是0x40048000。這樣它內(nèi)部各寄存器的地址就可以以基址為參考點,用相對于基址的偏移量來進行描述。比如,在前面討論時鐘配置時用到的寄存器SYSPLLCLKSEL、MAINCLKSEL等,它們相對于基址的偏移地址就是0x040和0x070(查上表中的Address offset一列),而其絕對地址則是0x40048040和0x40048070(分別加上基址的值)。

          我們知道,要訪問CPU內(nèi)部的硬件,最終只能通過它的地址進行訪問,而我們對其進行的命名(如剛才的SYSPLLCLKSEL、MAINCLKSEL等)都要通過一種對映關(guān)系把它們聯(lián)系起來。因為CPU不知道SYSPLLCLKSEL、MAINCLKSEL是什么,但它知道地址0x40048040、0x40048070的單元。而在高級語言中,直接使用地址不僅不直觀,開發(fā)者還要費力去記住每個地址的寄存器功能,很不合適。為了適應(yīng)高級語言的特點,我們就通過這種給寄存器命名,并讓該名稱對映到寄存器的實際地址的方式來處理。經(jīng)過這樣處理后,開發(fā)者就可以在程序中直接引用寄存器名稱了,大大提高了程序的可讀性,方便了開發(fā)。

          從內(nèi)存地圖中可以看出,由于各設(shè)備的編址是分類的,所以使用高級語言中的“結(jié)構(gòu)體”來處理這種名稱與地址的對映關(guān)系是十分合適的。每一個結(jié)構(gòu)體可對應(yīng)一個分類,而分類中的寄存器就可以定義為這個結(jié)構(gòu)體內(nèi)的成員變量,各成員變量又嚴(yán)格對映到寄存器的實際地址。在前面示例部分的程序中,已經(jīng)在頭文件部分引入了這種結(jié)構(gòu)體并進行了地址對映。

          接下來再回到剛才定義的結(jié)構(gòu)體“SYSCON”的討論上來。從該結(jié)構(gòu)體定義中可以看到,它內(nèi)部定義的成員變量其實就是system control模塊內(nèi)的所有寄存器(見上表)。但是還沒完,因為定義了“SYSCON”這個結(jié)構(gòu)體只相當(dāng)于對system control模塊進行了“封裝”(即進行了寄存器的按順序命名),還沒有對它進行地址的對映。

          下面給出預(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)

          可見,這部分又全是用define進行的宏定義。因這里只討論與“SYSCON”結(jié)構(gòu)體相關(guān)的內(nèi)容,所以只需看第一、四、六行,其它部分暫時不作說明。前面說過,system control模塊位于APB設(shè)備區(qū),所以第一行先進行了APB設(shè)備區(qū)的基址定義,第四行又進行了SYSCON(即system control模塊區(qū))的基址定義。而真正進行結(jié)構(gòu)體與地址對映的,是第六行的語句,現(xiàn)單獨把它剔出來進行討論。該語句如下:

          #define LPC_SYSCON((LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE)

          首先來看,(LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE是把LPC_SYSCON_BASE(即SYSCON的基址)強行轉(zhuǎn)換為一個LPC_SYSCON_TypeDef結(jié)構(gòu)體的指針類型。根據(jù)前面的定義,LPC_SYSCON_BASE的值是0x40048000(LPC_APB0_BASE + 0x48000),而強行把它轉(zhuǎn)換為一個LPC_SYSCON_TypeDef結(jié)構(gòu)體的指針類型,則這個結(jié)構(gòu)體的首地址就是LPC_SYSCON_BASE的基址(0x40048000)。這樣一來,結(jié)構(gòu)體LPC_SYSCON_TypeDef內(nèi)部各成員變量的地址,就是以這個基址(0x40048000)為參考點的偏移地址了。

          首地址對映了,那偏移量怎么實現(xiàn)呢?這就與結(jié)構(gòu)體中成員變量定義的數(shù)據(jù)類型有關(guān)了。回顧一下上面的SYSCON這個結(jié)構(gòu)體中,成員變量都用的是“unsignedint”型來定義,占用4個字節(jié)的空間,而觀察上面的“system control模塊內(nèi)所有寄存器的分布情況表”可以看出,它的每個寄存器之間正好是4個字節(jié)(或是4的正數(shù)倍)的地址偏移,所以只要用“unsignedint”型來定義成員變量,寄存器的偏移地址就自動適應(yīng)了。如果遇到保留地址,則可以通過定義“unsignedint”型的空數(shù)組來避開,以保證后面成員變量的地址偏移正確。另外,由于LPC1114是32的結(jié)構(gòu),所以它的寄存器也是32位的,剛好是4個字節(jié),這也是為何每個寄存器之間是4個字節(jié)地址偏移量的原因。在表中還可以看出寄存器的讀寫屬性,這與前面結(jié)構(gòu)體定義中的“__I”、“__IO”、“__O”等就可以聯(lián)系起來了。

          接下來,通過define語句來把剛才的結(jié)構(gòu)體指針取個“別名”,即LPC_SYSCON。這時LPC_SYSCON就是這個結(jié)構(gòu)體指針類型了,通過“LPC_SYSCON->”的方式就可以來引用它內(nèi)部的成員變量(即system control模塊內(nèi)的各個寄存器)了。這樣一來,就可以把底層的地址用高級語言的名稱來表示,非常直觀,比如前面例子中的要讓PLL輸入選擇外部晶體振蕩,執(zhí)行語句“LPC_SYSCON->SYSPLLCLKSEL = 0x00000001;”就可以了,但如果沒有這種地址對映,就必須寫成“MOV0x40048040,#0x00000001”,這當(dāng)然就非常不直觀了,不查手冊還不知道地址0x40048040是什么寄存器。

          上述只是通過SYSCON這個結(jié)構(gòu)體來進行討論的,其它的結(jié)構(gòu)體定義沒有討論。但它們所采用的方法是一樣的,讀者可參考上面對SYSCON結(jié)構(gòu)體的分析方法來自行研究,這里就不再贅述了。



          關(guān)鍵詞: LPC11XX.h頭文

          評論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();