LPC1114通用輸入/輸出端口(GPIO)
在第一節(jié)中已經介紹過,LPC1114處理器是一個32位結構的處理器,但它的GPIO端口沒有把32根引腳都接出來,而是每組只接出來12根引腳(注意,第四組只接出來6根引腳),共有4組,一共42根引腳。它們都具有如下特點:
本文引用地址:http://m.butianyuan.cn/article/201611/316322.htm1.可通過軟件配置GPIO引腳為輸入或輸出
2.每個獨立的端口引腳均可作為外部中斷的輸入引腳(邊沿或電平觸發(fā))
3.邊沿觸發(fā)中斷可配置為上升沿觸發(fā)、下降沿觸發(fā)以及雙邊沿觸發(fā)
4.電平觸發(fā)中斷引腳可以配置為高電平或低電平觸發(fā)
5.所有GPIO引腳默認情況下均為輸入
6.從端口讀取和寫入數據操作可以通過地址位13:2屏蔽
端口的具體使用配置會在后面一一進行討論,這里先來看一下“通用輸入/輸出端口GPIO”的結構體是如何定義的,代碼如下。
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;
上述代碼對LPC1114的GPIO端口的進行了結構體定義,由于GPIO位于內存地圖中的AHB部分,所以從代碼中可以看出,同前面討論的結構體一樣,它定義的成員變量利用偏移地址與AHB中的GPIO寄存器進行了對映。特殊的地方在于多了一個關于union的定義,要弄清這個定義,還必須回到AHB模塊部分的寄存器描述中去。下圖就給出了AHB模塊內GPIO部分的寄存器分布情況。
GPIODATA寄存器是GPIO的數據寄存器,它存放的數據直接被輸出到GPIO的引腳上,引腳輸入的數據也會被放到該寄存器中。所以要對端口進行電平控制(無論是輸入還是輸出),就要對GPIODATA寄存器進行操作。同樣,GPIODATA寄存器也是一個32位的結構,其地址占用4個字節(jié)。而GPIO端口只有12個引腳,因此GPIODATA寄存器只用了低12位來對映GPIO端口引腳。
從上圖中還可以看出,GPIODATA寄存器的偏移地址是從0x0000~0x3FFC。最高地址是0x3FFC是因為,每個GPIODATA寄存器單元占用4 字節(jié),所以共有0x3FFC/4=0xFFF個(即4095個)GPIODATA寄存器單元,因為地址是從第0個單元算起的,所以總共有4096個單元。而2的12次方剛好就等4096,所以它剛好可以表征12個引腳的容量。由此可以看出,每個引腳電平的值(無論是輸入還是輸出)都可以在GPIODATA寄存器中找到一個對映的單元(因為有4096個引腳狀態(tài)就有4096個GPIODATA地址單元)。這樣做是為什么呢?為什么不把端口引腳統(tǒng)一用一個端口寄存器來描述(姑且稱為“方法一”),寫(或讀)這個端口寄存器不就行了?其實,其它大部分單片機也確實是這樣做的。而這里LPC1114卻要花費4096個地址單元來把引腳狀態(tài)全部描述一遍(姑且稱為“方法二”),也肯定是有它的理由的。其實,應該說LPC1114是兩種方法都包含的(因為嚴格來說,“方法一”包含于“方法二”)。至于“方法二”的優(yōu)點,其實就是可以在不改變其它引腳狀態(tài)下,單獨改變某一引腳上的電平。按理說,通過“與或”的邏輯操作方式,也可以讓“方法一”實現這一功能(其它單片機也是這樣做的),但它必須通過“讀——修改——寫”的步驟來實現。比如要實現僅對P0端口的第7個引腳輸出高電平,程序可寫為“P0 |= 1<<7”,其實就是“P0 =P0| 0b10000000”,這就可以看出,它先要把P0的值讀出來,然后進行修改(和0b10000000相與),最后再把結果寫回P0去。而“方法二”就簡單多了,直接訪問地址為0b10000000的單元就行了。下面詳細來討論方法二是如何實現位操作的。
為了實現“方法二”的位操作,在LPC1114中引入了一個新的概念——屏蔽(MASK)。它利用一個14位的結構來描述屏蔽操作,只有在屏蔽結構中值為1的位所對應的端口引腳值才會生效。例如要改變端口第7個引腳的電平,那么在這個端口所對應的屏蔽結構中,與第7個引腳對應的屏蔽位的值必須為1才行。同時要注意,屏蔽位并不是與端口位置一一對應的,而是屏蔽結構整體“左移”了兩位再來對應!由于端口引腳數量為12,屏蔽結構又整體“左移”兩位,所以它才需要用一個14位的結構來描述。據此,那么剛才第7個引腳所對應的屏蔽位應該是第9位。要改變第7個引腳的電平,屏蔽結構中的第9位的值要是1才行。此過程可用下圖來描述,至于為何屏蔽結構要“左移”兩位,后面會進行詳細討論。
從上圖還可以看出,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個32位的單元。
接下來討論為何屏蔽結構要“左移”兩位再來對應。關于這一點,即便在管方文檔中也沒有給出解釋。但通過觀察可以看出,雖然屏蔽結構只用了14位來描述,但由于處理器本身是32位結構的,所以其實每個屏蔽結構本身的長度還是32位的,只不過它只用了低14位就足夠了。換句話說,一個屏蔽結構要占用4個字節(jié)的地址。而全部(4096個)屏蔽結構就要占用4×4096個地址,因為地址是從0算起的,所以整個屏蔽結構所占用的地址是4×4096-1=16383個,也即十六進制的0x3FFC個。對應上面的“GPIO寄存器分布情況表”會發(fā)現,這個值正好是GPIODATA寄存器地址偏移的最大值。那這兩個之間會有什么關系呢?其實只能證明一點,在引入了屏蔽結構的概念后,屏蔽結構就是GPIODATA寄存器!更確切的說是地址為0x000~0x3FF8這段的GPIODATA寄存器(因為最后一個地址為0x3FFC的屏蔽結構是全部不屏蔽(值全為1),可以認為它無屏蔽作用,所以一般把它當作端口寄存器用(即“方法一”))。
這時再回到“GPIO寄存器分布情況表”上來看,第一行的GPIODATA的地址為0x000~0x3FF8,它是實際的4095個屏蔽結構,每個占用4個字節(jié)地址,使用時對應“方法二”;第二行的GPIODATA的地址為0x3FFC,它相當于端口寄存器,共占用4個字節(jié)地址,使用時對應“方法一”。但兩者只能選其一,不能兩種方法都同時用!由于C語言中的共用體(或稱聯合體)就具有地址空間復用的特點,所以利用共用體來定義GPIODATA是最為合適的。下面單獨把這部分定義剔出來討論,代碼如下。
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”型的MASKED_ACCESS(屏蔽)數組,一共定義了4096個元素空間。每個數組元素占用4個字節(jié)地址,4096個MASKED_ACCESS數組共占用0x3FFC的地址空間,每個單元都具有可讀可寫的屬性(__IO)。從地址分配可以看出,這個數組包括了從屏蔽所有位(地址0x0000)到不屏蔽任何位(地址0x3FFC)的所有屏蔽結構部分。MASKED_ACCESS[0]對應地址0x0000,屏蔽所有位;MASKED_ACCESS[1]對應地址是0x0004(每個占用4字節(jié)),二進制數是0b00000000000100(14位,左移兩位來對映),即不屏蔽端口第0位引腳;MASKED_ACCESS[2]對應地址是0x0008,二進制數是0b00000000001000(14位,左移兩位來對映),即不屏蔽端口第1位引腳;MASKED_ACCESS[3]對應地址是0x000C,二進制數是0b00000000001100(14位,左移兩位來對映),即不屏蔽端口第0位和第1位引腳;MASKED_ACCESS[4]對應地址是0x0010,二進制數是0b00000000010000(14位,左移兩位來對映),即不屏蔽端口第2位引腳;如此等等;最后一個數組元素是MASKED_ACCESS[4095],對應地址是0x3FFC,二進制數是0b11111111111100(14位,左移兩位來對映),即不屏蔽任何端口引腳。
到此,就應該可以來回答剛才的問題“為何屏蔽結構要用12位左移2位來表示了”。這是由于,每個MASKED_ACCESS數組元素之間差了4個字節(jié),為了讓每個屏蔽結構都可以對應到各自對映的數組元素,必須對每個屏蔽結構乘以4。然而在位運算中,左移2位就相當于乘以4,所以用左移了2位的14位屏蔽結構,相當于給每個屏蔽結構都乘以4,這就免去了對4096個屏蔽結構都乘以4的操作指令!這是屏蔽結構要左移兩位真正原因所在!
共用體中的第二個部分是一個“uint32_t”型的變量DATA。由于其前面定義了4095個“uint32_t”型空數組,避開了屏蔽結構中的前4095個單元。所以最后的變量DATA的起始地址就是0x3FFC,也就是最后一個屏蔽結構的地址。前面說過,最后一個屏蔽結構的值是全1,即不屏蔽。而這里用變量DATA來代替最后一個屏蔽結構,做法非常巧妙。相當于對DATA寫什么值,在端口的引腳上就可以得到相應的電平。此時可認為它就是端口寄存器,而不是屏蔽結構。DATA也必須具有可讀可寫的屬性(__IO)。
下面用一個例子來說明一下整個過程。例如,要讓第0組GPIO的第0、3、10位輸出1,其它位保持不變,需要如何操作。
首先看,要這三位輸出1,先要給這三位對應的屏蔽位寫1,則它們對應的14位屏蔽結構應該是“0b01000000100100”,換算成十六進制是“0x1024”。這個“0x1024”就是在0x0000~0x3FFC地址之間的一個單元,也即通過共用體對映到了4096個MASKED_ACCESS數組元素中的其中一個。但它到底是哪個MASKED_ACCESS數組元素呢?由于它們之間是4倍的關系,所以0x1024/4=0x409,十進制為1033,即MASKED_ACCESS[1033]單元。而寫給端口的數據則是不左移的12位,即“0b010000001001”,換算成十六進制剛好就是“0x409”,十進制為1033,即該數組元素的編號。因此,通過執(zhí)行MASKED_ACCESS[1033]=0x409,就可以實現對第0、3、10位輸出1。而實際上,執(zhí)行MASKED_ACCESS[1033]=0xFFF效果也是一樣的,因為除了第0、3、10位為1以外,其它位可為任何值。同理,要對第0、3、10位輸出0,執(zhí)行MASKED_ACCESS[1033]=0x000和執(zhí)行MASKED_ACCESS[1033]=0xBF6是一樣的效果。
所以綜上所述,MASKED_ACCESS要引用的數組單元,就是要輸出到引腳上12位值的十進制數。當然,要實際引用,還要在程序預定義部分進行地址對映,代碼如下。
#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 )
有了上述預定義,剛才的例子就可以執(zhí)行LPC_GPIO0->MASKED_ACCESS[1033]=0x409來實現了。
再來回顧一下前面第一個演示示例中對端口2的操作,主要代碼如下。
while(1)
{
LPC_GPIO2->DATA = 0xAAA;
delay_ms(500);
LPC_GPIO2->DATA = 0x555;
delay_ms(500);
}
從中可以看出,它是通過“方法一”,即直接寫DATA變量來實現的。因為它的全部12位都在變化,所以采用了這種方式。
剩于共用體定義中的其它部分,由于與SYSCON分析中的一樣,可自行參考前面的內容來分析,這里就不再贅述了。最后不要忘記一點,由于在程序中引入了共用體union,所以在預定義部分要加下一句“#pragma anon_unions”,這在前面章節(jié)已經闡述過了。如果用包含頭文件的方式的話,該定義存在于頭文件LPC11xx.h中。
評論