LPC1114通用輸入/輸出端口(GPIO)
在第一節(jié)中已經(jīng)介紹過(guò),LPC1114處理器是一個(gè)32位結(jié)構(gòu)的處理器,但它的GPIO端口沒(méi)有把32根引腳都接出來(lái),而是每組只接出來(lái)12根引腳(注意,第四組只接出來(lái)6根引腳),共有4組,一共42根引腳。它們都具有如下特點(diǎn):
本文引用地址:http://www.ex-cimer.com/article/201611/316322.htm1.可通過(guò)軟件配置GPIO引腳為輸入或輸出
2.每個(gè)獨(dú)立的端口引腳均可作為外部中斷的輸入引腳(邊沿或電平觸發(fā))
3.邊沿觸發(fā)中斷可配置為上升沿觸發(fā)、下降沿觸發(fā)以及雙邊沿觸發(fā)
4.電平觸發(fā)中斷引腳可以配置為高電平或低電平觸發(fā)
5.所有GPIO引腳默認(rèn)情況下均為輸入
6.從端口讀取和寫入數(shù)據(jù)操作可以通過(guò)地址位13:2屏蔽
端口的具體使用配置會(huì)在后面一一進(jìn)行討論,這里先來(lái)看一下“通用輸入/輸出端口GPIO”的結(jié)構(gòu)體是如何定義的,代碼如下。
typedef struct
{
union {
__IO uint32_t MASKED_ACCESS[4096];/*!< Offset: 0x0000 to 0x3FFC Port data Register for pins PIOn_0 to PIOn_11 (R/W) */
struct {
uint32_t RESERVED0[4095];
__IO uint32_t DATA;/*!< Offset: 0x3FFC Port data Register (R/W) */
};
};
uint32_t RESERVED1[4096];
__IO uint32_t DIR;/*!< Offset: 0x8000 Data direction Register (R/W) */
__IO uint32_t IS;/*!< Offset: 0x8004 Interrupt sense Register (R/W) */
__IO uint32_t IBE;/*!< Offset: 0x8008 Interrupt both edges Register (R/W) */
__IO uint32_t IEV;/*!< Offset: 0x800C Interrupt event Register(R/W) */
__IO uint32_t IE;/*!< Offset: 0x8010 Interrupt mask Register (R/W) */
__I uint32_t RIS;/*!< Offset: 0x8014 Raw interrupt status Register (R/ ) */
__I uint32_t MIS;/*!< Offset: 0x8018 Masked interrupt status Register (R/ ) */
__O uint32_t IC;/*!< Offset: 0x801C Interrupt clear Register (R/W) */
} LPC_GPIO_TypeDef;
上述代碼對(duì)LPC1114的GPIO端口的進(jìn)行了結(jié)構(gòu)體定義,由于GPIO位于內(nèi)存地圖中的AHB部分,所以從代碼中可以看出,同前面討論的結(jié)構(gòu)體一樣,它定義的成員變量利用偏移地址與AHB中的GPIO寄存器進(jìn)行了對(duì)映。特殊的地方在于多了一個(gè)關(guān)于union的定義,要弄清這個(gè)定義,還必須回到AHB模塊部分的寄存器描述中去。下圖就給出了AHB模塊內(nèi)GPIO部分的寄存器分布情況。

GPIODATA寄存器是GPIO的數(shù)據(jù)寄存器,它存放的數(shù)據(jù)直接被輸出到GPIO的引腳上,引腳輸入的數(shù)據(jù)也會(huì)被放到該寄存器中。所以要對(duì)端口進(jìn)行電平控制(無(wú)論是輸入還是輸出),就要對(duì)GPIODATA寄存器進(jìn)行操作。同樣,GPIODATA寄存器也是一個(gè)32位的結(jié)構(gòu),其地址占用4個(gè)字節(jié)。而GPIO端口只有12個(gè)引腳,因此GPIODATA寄存器只用了低12位來(lái)對(duì)映GPIO端口引腳。
從上圖中還可以看出,GPIODATA寄存器的偏移地址是從0x0000~0x3FFC。最高地址是0x3FFC是因?yàn)?,每個(gè)GPIODATA寄存器單元占用4 字節(jié),所以共有0x3FFC/4=0xFFF個(gè)(即4095個(gè))GPIODATA寄存器單元,因?yàn)榈刂肥菑牡?個(gè)單元算起的,所以總共有4096個(gè)單元。而2的12次方剛好就等4096,所以它剛好可以表征12個(gè)引腳的容量。由此可以看出,每個(gè)引腳電平的值(無(wú)論是輸入還是輸出)都可以在GPIODATA寄存器中找到一個(gè)對(duì)映的單元(因?yàn)橛?096個(gè)引腳狀態(tài)就有4096個(gè)GPIODATA地址單元)。這樣做是為什么呢?為什么不把端口引腳統(tǒng)一用一個(gè)端口寄存器來(lái)描述(姑且稱為“方法一”),寫(或讀)這個(gè)端口寄存器不就行了?其實(shí),其它大部分單片機(jī)也確實(shí)是這樣做的。而這里L(fēng)PC1114卻要花費(fèi)4096個(gè)地址單元來(lái)把引腳狀態(tài)全部描述一遍(姑且稱為“方法二”),也肯定是有它的理由的。其實(shí),應(yīng)該說(shuō)LPC1114是兩種方法都包含的(因?yàn)閲?yán)格來(lái)說(shuō),“方法一”包含于“方法二”)。至于“方法二”的優(yōu)點(diǎn),其實(shí)就是可以在不改變其它引腳狀態(tài)下,單獨(dú)改變某一引腳上的電平。按理說(shuō),通過(guò)“與或”的邏輯操作方式,也可以讓“方法一”實(shí)現(xiàn)這一功能(其它單片機(jī)也是這樣做的),但它必須通過(guò)“讀——修改——寫”的步驟來(lái)實(shí)現(xiàn)。比如要實(shí)現(xiàn)僅對(duì)P0端口的第7個(gè)引腳輸出高電平,程序可寫為“P0 |= 1<<7”,其實(shí)就是“P0 =P0| 0b10000000”,這就可以看出,它先要把P0的值讀出來(lái),然后進(jìn)行修改(和0b10000000相與),最后再把結(jié)果寫回P0去。而“方法二”就簡(jiǎn)單多了,直接訪問(wèn)地址為0b10000000的單元就行了。下面詳細(xì)來(lái)討論方法二是如何實(shí)現(xiàn)位操作的。
為了實(shí)現(xiàn)“方法二”的位操作,在LPC1114中引入了一個(gè)新的概念——屏蔽(MASK)。它利用一個(gè)14位的結(jié)構(gòu)來(lái)描述屏蔽操作,只有在屏蔽結(jié)構(gòu)中值為1的位所對(duì)應(yīng)的端口引腳值才會(huì)生效。例如要改變端口第7個(gè)引腳的電平,那么在這個(gè)端口所對(duì)應(yīng)的屏蔽結(jié)構(gòu)中,與第7個(gè)引腳對(duì)應(yīng)的屏蔽位的值必須為1才行。同時(shí)要注意,屏蔽位并不是與端口位置一一對(duì)應(yīng)的,而是屏蔽結(jié)構(gòu)整體“左移”了兩位再來(lái)對(duì)應(yīng)!由于端口引腳數(shù)量為12,屏蔽結(jié)構(gòu)又整體“左移”兩位,所以它才需要用一個(gè)14位的結(jié)構(gòu)來(lái)描述。據(jù)此,那么剛才第7個(gè)引腳所對(duì)應(yīng)的屏蔽位應(yīng)該是第9位。要改變第7個(gè)引腳的電平,屏蔽結(jié)構(gòu)中的第9位的值要是1才行。此過(guò)程可用下圖來(lái)描述,至于為何屏蔽結(jié)構(gòu)要“左移”兩位,后面會(huì)進(jìn)行詳細(xì)討論。

從上圖還可以看出,GPIODATA的地址是GPIO基址+屏蔽地址(偏移地址)。在LPC1114中共有4組GPIO端口,它們的基址分別是:port0為0x50000000;port1為0x50010000;port2為0x50020000;port3為0x50030000。而每組端口都有自己的屏蔽地址,4組GPIO基址加上各自的屏蔽地址后就是:port0為0x50000000~0x50003FFC;port1為0x50010000~0x50013FFC;port2為0x50020000~0x50023FFC;port3為0x50030000~0x50033FFC。每組都有4096個(gè)32位的單元。
接下來(lái)討論為何屏蔽結(jié)構(gòu)要“左移”兩位再來(lái)對(duì)應(yīng)。關(guān)于這一點(diǎn),即便在管方文檔中也沒(méi)有給出解釋。但通過(guò)觀察可以看出,雖然屏蔽結(jié)構(gòu)只用了14位來(lái)描述,但由于處理器本身是32位結(jié)構(gòu)的,所以其實(shí)每個(gè)屏蔽結(jié)構(gòu)本身的長(zhǎng)度還是32位的,只不過(guò)它只用了低14位就足夠了。換句話說(shuō),一個(gè)屏蔽結(jié)構(gòu)要占用4個(gè)字節(jié)的地址。而全部(4096個(gè))屏蔽結(jié)構(gòu)就要占用4×4096個(gè)地址,因?yàn)榈刂肥菑?算起的,所以整個(gè)屏蔽結(jié)構(gòu)所占用的地址是4×4096-1=16383個(gè),也即十六進(jìn)制的0x3FFC個(gè)。對(duì)應(yīng)上面的“GPIO寄存器分布情況表”會(huì)發(fā)現(xiàn),這個(gè)值正好是GPIODATA寄存器地址偏移的最大值。那這兩個(gè)之間會(huì)有什么關(guān)系呢?其實(shí)只能證明一點(diǎn),在引入了屏蔽結(jié)構(gòu)的概念后,屏蔽結(jié)構(gòu)就是GPIODATA寄存器!更確切的說(shuō)是地址為0x000~0x3FF8這段的GPIODATA寄存器(因?yàn)樽詈笠粋€(gè)地址為0x3FFC的屏蔽結(jié)構(gòu)是全部不屏蔽(值全為1),可以認(rèn)為它無(wú)屏蔽作用,所以一般把它當(dāng)作端口寄存器用(即“方法一”))。
這時(shí)再回到“GPIO寄存器分布情況表”上來(lái)看,第一行的GPIODATA的地址為0x000~0x3FF8,它是實(shí)際的4095個(gè)屏蔽結(jié)構(gòu),每個(gè)占用4個(gè)字節(jié)地址,使用時(shí)對(duì)應(yīng)“方法二”;第二行的GPIODATA的地址為0x3FFC,它相當(dāng)于端口寄存器,共占用4個(gè)字節(jié)地址,使用時(shí)對(duì)應(yīng)“方法一”。但兩者只能選其一,不能兩種方法都同時(shí)用!由于C語(yǔ)言中的共用體(或稱聯(lián)合體)就具有地址空間復(fù)用的特點(diǎn),所以利用共用體來(lái)定義GPIODATA是最為合適的。下面單獨(dú)把這部分定義剔出來(lái)討論,代碼如下。
union {
__IO uint32_t MASKED_ACCESS[4096];/*!< Offset: 0x0000 to 0x3FFC Port data Register for pins PIOn_0 to PIOn_11 (R/W) */
struct {
uint32_t RESERVED0[4095];
__IO uint32_t DATA;/*!< Offset: 0x3FFC Port data Register (R/W) */
};
};
可以看出,在共用體中定義了兩個(gè)部分的復(fù)用內(nèi)容。第一個(gè)部分是一個(gè)“uint32_t”型的MASKED_ACCESS(屏蔽)數(shù)組,一共定義了4096個(gè)元素空間。每個(gè)數(shù)組元素占用4個(gè)字節(jié)地址,4096個(gè)MASKED_ACCESS數(shù)組共占用0x3FFC的地址空間,每個(gè)單元都具有可讀可寫的屬性(__IO)。從地址分配可以看出,這個(gè)數(shù)組包括了從屏蔽所有位(地址0x0000)到不屏蔽任何位(地址0x3FFC)的所有屏蔽結(jié)構(gòu)部分。MASKED_ACCESS[0]對(duì)應(yīng)地址0x0000,屏蔽所有位;MASKED_ACCESS[1]對(duì)應(yīng)地址是0x0004(每個(gè)占用4字節(jié)),二進(jìn)制數(shù)是0b00000000000100(14位,左移兩位來(lái)對(duì)映),即不屏蔽端口第0位引腳;MASKED_ACCESS[2]對(duì)應(yīng)地址是0x0008,二進(jìn)制數(shù)是0b00000000001000(14位,左移兩位來(lái)對(duì)映),即不屏蔽端口第1位引腳;MASKED_ACCESS[3]對(duì)應(yīng)地址是0x000C,二進(jìn)制數(shù)是0b00000000001100(14位,左移兩位來(lái)對(duì)映),即不屏蔽端口第0位和第1位引腳;MASKED_ACCESS[4]對(duì)應(yīng)地址是0x0010,二進(jìn)制數(shù)是0b00000000010000(14位,左移兩位來(lái)對(duì)映),即不屏蔽端口第2位引腳;如此等等;最后一個(gè)數(shù)組元素是MASKED_ACCESS[4095],對(duì)應(yīng)地址是0x3FFC,二進(jìn)制數(shù)是0b11111111111100(14位,左移兩位來(lái)對(duì)映),即不屏蔽任何端口引腳。
到此,就應(yīng)該可以來(lái)回答剛才的問(wèn)題“為何屏蔽結(jié)構(gòu)要用12位左移2位來(lái)表示了”。這是由于,每個(gè)MASKED_ACCESS數(shù)組元素之間差了4個(gè)字節(jié),為了讓每個(gè)屏蔽結(jié)構(gòu)都可以對(duì)應(yīng)到各自對(duì)映的數(shù)組元素,必須對(duì)每個(gè)屏蔽結(jié)構(gòu)乘以4。然而在位運(yùn)算中,左移2位就相當(dāng)于乘以4,所以用左移了2位的14位屏蔽結(jié)構(gòu),相當(dāng)于給每個(gè)屏蔽結(jié)構(gòu)都乘以4,這就免去了對(duì)4096個(gè)屏蔽結(jié)構(gòu)都乘以4的操作指令!這是屏蔽結(jié)構(gòu)要左移兩位真正原因所在!
共用體中的第二個(gè)部分是一個(gè)“uint32_t”型的變量DATA。由于其前面定義了4095個(gè)“uint32_t”型空數(shù)組,避開了屏蔽結(jié)構(gòu)中的前4095個(gè)單元。所以最后的變量DATA的起始地址就是0x3FFC,也就是最后一個(gè)屏蔽結(jié)構(gòu)的地址。前面說(shuō)過(guò),最后一個(gè)屏蔽結(jié)構(gòu)的值是全1,即不屏蔽。而這里用變量DATA來(lái)代替最后一個(gè)屏蔽結(jié)構(gòu),做法非常巧妙。相當(dāng)于對(duì)DATA寫什么值,在端口的引腳上就可以得到相應(yīng)的電平。此時(shí)可認(rèn)為它就是端口寄存器,而不是屏蔽結(jié)構(gòu)。DATA也必須具有可讀可寫的屬性(__IO)。
下面用一個(gè)例子來(lái)說(shuō)明一下整個(gè)過(guò)程。例如,要讓第0組GPIO的第0、3、10位輸出1,其它位保持不變,需要如何操作。
首先看,要這三位輸出1,先要給這三位對(duì)應(yīng)的屏蔽位寫1,則它們對(duì)應(yīng)的14位屏蔽結(jié)構(gòu)應(yīng)該是“0b01000000100100”,換算成十六進(jìn)制是“0x1024”。這個(gè)“0x1024”就是在0x0000~0x3FFC地址之間的一個(gè)單元,也即通過(guò)共用體對(duì)映到了4096個(gè)MASKED_ACCESS數(shù)組元素中的其中一個(gè)。但它到底是哪個(gè)MASKED_ACCESS數(shù)組元素呢?由于它們之間是4倍的關(guān)系,所以0x1024/4=0x409,十進(jìn)制為1033,即MASKED_ACCESS[1033]單元。而寫給端口的數(shù)據(jù)則是不左移的12位,即“0b010000001001”,換算成十六進(jìn)制剛好就是“0x409”,十進(jìn)制為1033,即該數(shù)組元素的編號(hào)。因此,通過(guò)執(zhí)行MASKED_ACCESS[1033]=0x409,就可以實(shí)現(xiàn)對(duì)第0、3、10位輸出1。而實(shí)際上,執(zhí)行MASKED_ACCESS[1033]=0xFFF效果也是一樣的,因?yàn)槌说?、3、10位為1以外,其它位可為任何值。同理,要對(duì)第0、3、10位輸出0,執(zhí)行MASKED_ACCESS[1033]=0x000和執(zhí)行MASKED_ACCESS[1033]=0xBF6是一樣的效果。
所以綜上所述,MASKED_ACCESS要引用的數(shù)組單元,就是要輸出到引腳上12位值的十進(jìn)制數(shù)。當(dāng)然,要實(shí)際引用,還要在程序預(yù)定義部分進(jìn)行地址對(duì)映,代碼如下。
#define LPC_AHB_BASE (0x50000000UL)
#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 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 )
有了上述預(yù)定義,剛才的例子就可以執(zhí)行LPC_GPIO0->MASKED_ACCESS[1033]=0x409來(lái)實(shí)現(xiàn)了。
再來(lái)回顧一下前面第一個(gè)演示示例中對(duì)端口2的操作,主要代碼如下。
while(1)
{
LPC_GPIO2->DATA = 0xAAA;
delay_ms(500);
LPC_GPIO2->DATA = 0x555;
delay_ms(500);
}
從中可以看出,它是通過(guò)“方法一”,即直接寫DATA變量來(lái)實(shí)現(xiàn)的。因?yàn)樗娜?2位都在變化,所以采用了這種方式。
剩于共用體定義中的其它部分,由于與SYSCON分析中的一樣,可自行參考前面的內(nèi)容來(lái)分析,這里就不再贅述了。最后不要忘記一點(diǎn),由于在程序中引入了共用體union,所以在預(yù)定義部分要加下一句“#pragma anon_unions”,這在前面章節(jié)已經(jīng)闡述過(guò)了。如果用包含頭文件的方式的話,該定義存在于頭文件LPC11xx.h中。
評(píng)論