在前面的示例中,现已用到了LPC1114的GPIO端口。GPIO端口是CPU与外部设备打交道的根底,为了更好地运用好它,下面将对LPC1114的通用输入输出端口(GPIO)的存放器结构及用法做具体深化地评论。
在榜首节中现已介绍过,LPC1114处理器是一个32位结构的处理器,但它的GPIO端口没有把32根引脚都接出来,而是每组只接出来12根引脚(留意,第四组只接出来6根引脚),共有4组,总共42根引脚。它们都具有如下特色:
1.可经过软件装备GPIO引脚为输入或输出
2.每个独立的端口引脚均可作为外部中止的输入引脚(边缘或电平触发)
3.边缘触发中止可装备为上升沿触发、下降沿触发以及双边缘触发
4.电平触发中止引脚能够装备为高电平或低电平触发
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以外,其它存放器与结构体成员的对映联系与前面评论的SYSCON的相同。但关于GPIODATA就不能用前面的办法来剖析了。下面就着重来评论一下GPIODATA存放器与其结构体成员之间是怎么进行对映的。
GPIODATA存放器是GPIO的数据存放器,它存放的数据直接被输出到GPIO的引脚上,引脚输入的数据也会被放到该存放器中。所以要对端口进行电平操控(无论是输入仍是输出),就要对GPIODATA存放器进行操作。相同,GPIODATA存放器也是一个32位的结构,其地址占用4个字节。而GPIO端口只要12个引脚,因而GPIODATA存放器只用了低12位来对映GPIO端口引脚。
从上图中还能够看出,GPIODATA存放器的偏移地址是从0x0000~0x3FFC。最高地址是0x3FFC是因为,每个GPIODATA存放器单元占用4 字节,所以共有0x3FFC/4=0xFFF个(即4095个)GPIODATA存放器单元,因为地址是从第0个单元算起的,所以总共有4096个单元。而2的12次方刚好就等4096,所以它刚好能够表征12个引脚的容量。由此能够看出,每个引脚电平的值(无论是输入仍是输出)都能够在GPIODATA存放器中找到一个对映的单元(因为有4096个引脚状况就有4096个GPIODATA地址单元)。这样做是为什么呢?为什么不把端口引脚统一用一个端口存放器来描绘(权且称为“办法一”),写(或读)这个端口存放器不就行了?其实,其它大部分单片机也确实是这样做的。而这儿LPC1114却要花费4096个地址单元来把引脚状况悉数描绘一遍(权且称为“办法二”),也肯定是有它的理由的。其实,应该说LPC1114是两种办法都包括的(因为严格来说,“办法一”包括于“办法二”)。至于“办法二”的长处,其实便是能够在不改动其它引脚状况下,独自改动某一引脚上的电平。按理说,经过“与或”的逻辑操作办法,也能够让“办法一”完成这一功用(其它单片机也是这样做的),但它有必要经过“读——修正——写”的进程来完成。比如要完成仅对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才行。此进程可用下图来描绘,至于为何屏蔽结构要“左移”两位,后边会进行具体评论。
在上图中,榜首行表明的便是屏蔽结构,可见它是一个14位的结构,而且低两位没有用。第二行表明要写入到端口引脚上的值,假如榜首行的屏蔽值都为1,那么第二行的这12个值就会被原封不动地写到12位端口引脚上去。第三行表明的便是端口引脚上的值,可见因为屏蔽结构中只要第9位的值为1,所以对应到要写入的数据第7位的值(1)就写到了端口引脚的第7位上去了(第7脚输出高电平),而端口引脚的其它位则坚持不变(不管要写入的数据中对应的位是何值),这便是屏蔽结构的作用。前面评论过,这个屏蔽结构涵盖了12位的一切组合状况,所以从悉数不屏蔽(0x000)到悉数屏蔽(0xFFF),都包括在了其间。由此能够看出,当屏蔽结构的值为悉数屏蔽(0xFFF)时,便是要写入的数据值悉数都相关(输入或输出)到端口引脚,这也便是前面说到的“办法一”的做法,相当于直接写数据到端口。所以说“办法二”包括了“办法一”。
从上图还能够看出,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个字节的地址。而悉数(4096个)屏蔽结构就要占用4×4096个地址,因为地址是从0算起的,所以整个屏蔽结构所占用的地址是4×4096-1=16383个,也即十六进制的0x3FFC个。对应上面的“GPIO存放器散布状况表”会发现,这个值正好是GPIODATA存放器地址偏移的最大值。那这两个之间会有什么联系呢?其实只能证明一点,在引入了屏蔽结构的概念后,屏蔽结构便是GPIODATA存放器!更切当的说是地址为0x000~0x3FF8这段的GPIODATA存放器(因为最终一个地址为0x3FFC的屏蔽结构是悉数不屏蔽(值全为1),能够以为它无屏蔽作用,所以一般把它当作端口存放器用(即“办法一”))。
这时再回到“GPIO存放器散布状况表”上来看,榜首行的GPIODATA的地址为0x000~0x3FF8,它是实践的4095个屏蔽结构,每个占用4个字节地址,运用时对应“办法二”;第二行的GPIODATA的地址为0x3FFC,它相当于端口存放器,共占用4个字节地址,运用时对应“办法一”。但两者只能选其一,不能两种办法都一起用!
因为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个字节地址,4096个MASKED_ACCESS数组共占用0x3FFC的地址空间,每个单元都具有可读可写的特点(__IO)。从地址分配能够看出,这个数组包括了从屏蔽一切位(地址0x0000)到不屏蔽任何位(地址0x3FFC)的一切屏蔽结构部分。MASKED_ACCESS[0]对应地址0x0000,屏蔽一切位;MASKED_ACCESS[1]对应地址是0x0004(每个占用4字节),二进制数是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个字节,为了让每个屏蔽结构都能够对应到各自对映的数组元素,有必要对每个屏蔽结构乘以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,即该数组元素的编号。因而,经过履行MASKED_ACCESS[1033]=0x409,就能够完成对第0、3、10位输出1。而实践上,履行MASKED_ACCESS[1033]=0xFFF作用也是相同的,因为除了第0、3、10位为1以外,其它位可为任何值。同理,要对第0、3、10位输出0,履行MASKED_ACCESS[1033]=0x000和履行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 )
有了上述预界说,方才的比如就能够履行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”,这在前面章节现已论述过了。假如用包括头文件的办法的话,该界说存在于头文件LPC11xx.h中。