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

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > LPC1114通用輸入/輸出端口(GPIO)

          LPC1114通用輸入/輸出端口(GPIO)

          作者: 時間:2016-11-13 來源:網(wǎng)絡 收藏
          在前面的示例中,已經(jīng)用到了LPC1114GPIO端口。GPIO端口是CPU與外部設備打交道的基礎,為了更好地運用好它,下面將對LPC1114的通用輸入輸出端口(GPIO)的寄存器結構及用法做詳細深入地討論。

          在第一節(jié)中已經(jīng)介紹過,LPC1114處理器是一個32位結構的處理器,但它的GPIO端口沒有把32根引腳都接出來,而是每組只接出來12根引腳(注意,第四組只接出來6根引腳),共有4組,一共42根引腳。它們都具有如下特點:

          本文引用地址:http://www.ex-cimer.com/article/201611/316322.htm

          1.可通過軟件配置GPIO引腳為輸入或輸出
          2.每個獨立的端口引腳均可作為外部中斷的輸入引腳(邊沿或電平觸發(fā))
          3.邊沿觸發(fā)中斷可配置為上升沿觸發(fā)、下降沿觸發(fā)以及雙邊沿觸發(fā)
          4.電平觸發(fā)中斷引腳可以配置為高電平或低電平觸發(fā)
          5.所有GPIO引腳默認情況下均為輸入
          6.從端口讀取和寫入數(shù)據(jù)操作可以通過地址位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位于內(nèi)存地圖中的AHB部分,所以從代碼中可以看出,同前面討論的結構體一樣,它定義的成員變量利用偏移地址與AHB中的GPIO寄存器進行了對映。特殊的地方在于多了一個關于union的定義,要弄清這個定義,還必須回到AHB模塊部分的寄存器描述中去。下圖就給出了AHB模塊內(nèi)GPIO部分的寄存器分布情況。

          對應上圖與結構體中的內(nèi)容可以看出,除了GPIODATA以外,其它寄存器與結構體成員的對映關系與前面討論的SYSCON的一樣。但對于GPIODATA就不能用前面的方法來分析了。下面就著重來討論一下GPIODATA寄存器與其結構體成員之間是如何進行對映的。

          GPIODATA寄存器是GPIO的數(shù)據(jù)寄存器,它存放的數(shù)據(jù)直接被輸出到GPIO的引腳上,引腳輸入的數(shù)據(jù)也會被放到該寄存器中。所以要對端口進行電平控制(無論是輸入還是輸出),就要對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)下,單獨改變某一引腳上的電平。按理說,通過“與或”的邏輯操作方式,也可以讓“方法一”實現(xiàn)這一功能(其它單片機也是這樣做的),但它必須通過“讀——修改——寫”的步驟來實現(xiàn)。比如要實現(xiàn)僅對P0端口的第7個引腳輸出高電平,程序可寫為“P0 |= 1<<7”,其實就是“P0 =P0| 0b10000000”,這就可以看出,它先要把P0的值讀出來,然后進行修改(和0b10000000相與),最后再把結果寫回P0去。而“方法二”就簡單多了,直接訪問地址為0b10000000的單元就行了。下面詳細來討論方法二是如何實現(xiàn)位操作的。

          為了實現(xiàn)“方法二”的位操作,在LPC1114中引入了一個新的概念——屏蔽(MASK)。它利用一個14位的結構來描述屏蔽操作,只有在屏蔽結構中值為1的位所對應的端口引腳值才會生效。例如要改變端口第7個引腳的電平,那么在這個端口所對應的屏蔽結構中,與第7個引腳對應的屏蔽位的值必須為1才行。同時要注意,屏蔽位并不是與端口位置一一對應的,而是屏蔽結構整體“左移”了兩位再來對應!由于端口引腳數(shù)量為12,屏蔽結構又整體“左移”兩位,所以它才需要用一個14位的結構來描述。據(jù)此,那么剛才第7個引腳所對應的屏蔽位應該是第9位。要改變第7個引腳的電平,屏蔽結構中的第9位的值要是1才行。此過程可用下圖來描述,至于為何屏蔽結構要“左移”兩位,后面會進行詳細討論。

          在上圖中,第一行表示的就是屏蔽結構,可見它是一個14位的結構,并且低兩位沒有用。第二行表示要寫入到端口引腳上的值,如果第一行的屏蔽值都為1,那么第二行的這12個值就會被原封不動地寫到12位端口引腳上去。第三行表示的就是端口引腳上的值,可見由于屏蔽結構中只有第9位的值為1,所以對應到要寫入的數(shù)據(jù)第7位的值(1)就寫到了端口引腳的第7位上去了(第7腳輸出高電平),而端口引腳的其它位則保持不變(不論要寫入的數(shù)據(jù)中對應的位是何值),這就是屏蔽結構的作用。前面討論過,這個屏蔽結構涵蓋了12位的所有組合狀態(tài),所以從全部不屏蔽(0x000)到全部屏蔽(0xFFF),都包含在了其中。由此可以看出,當屏蔽結構的值為全部屏蔽(0xFFF)時,就是要寫入的數(shù)據(jù)值全部都關聯(lián)(輸入或輸出)到端口引腳,這也就是前面提到的“方法一”的做法,相當于直接寫數(shù)據(jù)到端口。所以說“方法二”包含了“方法一”。

          從上圖還可以看出,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ā)現(xiàn),這個值正好是GPIODATA寄存器地址偏移的最大值。那這兩個之間會有什么關系呢?其實只能證明一點,在引入了屏蔽結構的概念后,屏蔽結構就是GPIODATA寄存器!更確切的說是地址為0x000~0x3FF8這段的GPIODATA寄存器(因為最后一個地址為0x3FFC的屏蔽結構是全部不屏蔽(值全為1),可以認為它無屏蔽作用,所以一般把它當作端口寄存器用(即“方法一”))。

          這時再回到“GPIO寄存器分布情況表”上來看,第一行的GPIODATA的地址為0x000~0x3FF8,它是實際的4095個屏蔽結構,每個占用4個字節(jié)地址,使用時對應“方法二”;第二行的GPIODATA的地址為0x3FFC,它相當于端口寄存器,共占用4個字節(jié)地址,使用時對應“方法一”。但兩者只能選其一,不能兩種方法都同時用!

          由于C語言中的共用體(或稱聯(lián)合體)就具有地址空間復用的特點,所以利用共用體來定義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) */

          };

          };

          可以看出,在共用體中定義了兩個部分的復用內(nèi)容。第一個部分是一個“uint32_t”型的MASKED_ACCESS(屏蔽)數(shù)組,一共定義了4096個元素空間。每個數(shù)組元素占用4個字節(jié)地址,4096個MASKED_ACCESS數(shù)組共占用0x3FFC的地址空間,每個單元都具有可讀可寫的屬性(__IO)。從地址分配可以看出,這個數(shù)組包括了從屏蔽所有位(地址0x0000)到不屏蔽任何位(地址0x3FFC)的所有屏蔽結構部分。MASKED_ACCESS[0]對應地址0x0000,屏蔽所有位;MASKED_ACCESS[1]對應地址是0x0004(每個占用4字節(jié)),二進制數(shù)是0b00000000000100(14位,左移兩位來對映),即不屏蔽端口第0位引腳;MASKED_ACCESS[2]對應地址是0x0008,二進制數(shù)是0b00000000001000(14位,左移兩位來對映),即不屏蔽端口第1位引腳;MASKED_ACCESS[3]對應地址是0x000C,二進制數(shù)是0b00000000001100(14位,左移兩位來對映),即不屏蔽端口第0位和第1位引腳;MASKED_ACCESS[4]對應地址是0x0010,二進制數(shù)是0b00000000010000(14位,左移兩位來對映),即不屏蔽端口第2位引腳;如此等等;最后一個數(shù)組元素是MASKED_ACCESS[4095],對應地址是0x3FFC,二進制數(shù)是0b11111111111100(14位,左移兩位來對映),即不屏蔽任何端口引腳。

          到此,就應該可以來回答剛才的問題“為何屏蔽結構要用12位左移2位來表示了”。這是由于,每個MASKED_ACCESS數(shù)組元素之間差了4個字節(jié),為了讓每個屏蔽結構都可以對應到各自對映的數(shù)組元素,必須對每個屏蔽結構乘以4。然而在位運算中,左移2位就相當于乘以4,所以用左移了2位的14位屏蔽結構,相當于給每個屏蔽結構都乘以4,這就免去了對4096個屏蔽結構都乘以4的操作指令!這是屏蔽結構要左移兩位真正原因所在!

          共用體中的第二個部分是一個“uint32_t”型的變量DATA。由于其前面定義了4095個“uint32_t”型空數(shù)組,避開了屏蔽結構中的前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數(shù)組元素中的其中一個。但它到底是哪個MASKED_ACCESS數(shù)組元素呢?由于它們之間是4倍的關系,所以0x1024/4=0x409,十進制為1033,即MASKED_ACCESS[1033]單元。而寫給端口的數(shù)據(jù)則是不左移的12位,即“0b010000001001”,換算成十六進制剛好就是“0x409”,十進制為1033,即該數(shù)組元素的編號。因此,通過執(zhí)行MASKED_ACCESS[1033]=0x409,就可以實現(xiàn)對第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要引用的數(shù)組單元,就是要輸出到引腳上12位值的十進制數(shù)。當然,要實際引用,還要在程序預定義部分進行地址對映,代碼如下。

          #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來實現(xiàn)了。

          再來回顧一下前面第一個演示示例中對端口2的操作,主要代碼如下。

          while(1)
          {
          LPC_GPIO2->DATA = 0xAAA;
          delay_ms(500);
          LPC_GPIO2->DATA = 0x555;
          delay_ms(500);
          }
          從中可以看出,它是通過“方法一”,即直接寫DATA變量來實現(xiàn)的。因為它的全部12位都在變化,所以采用了這種方式。

          剩于共用體定義中的其它部分,由于與SYSCON分析中的一樣,可自行參考前面的內(nèi)容來分析,這里就不再贅述了。最后不要忘記一點,由于在程序中引入了共用體union,所以在預定義部分要加下一句“#pragma anon_unions”,這在前面章節(jié)已經(jīng)闡述過了。如果用包含頭文件的方式的話,該定義存在于頭文件LPC11xx.h中。



          評論


          技術專區(qū)

          關閉
          看屁屁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); })();