GPIO端口应用

通过前面的分析,我们知道在LPC824中,对GPIO端口的操作一共涉及到68个寄存器,那究竟该使用哪些寄存器,特别是对于具有相同功能的寄存器,应该如何选择,下面就来进行具体讨论。

虽然一共有68个寄存器,但其中的端口字节引脚寄存器B和端口字引脚寄存器W,由于每个对应一根引脚,所以就分别占用了29个寄存器,一共占用了58个,因此可以把B寄存器和W寄存器算成两个(两类),这样的话一共就只有12个寄存器了。在LPC824中,要实现对GPIO的同一个控制,操作的寄存器可有多种选择。例如:要控制某个引脚输出高电平,可以选择的寄存器就有PIN0、W、B、SET0及MPIN0等四个。那究竟应该如何选择,在LPC824的官方手册中给出了如下建议: 

(1)对于复位或重新初始化后的初始设置,写PORT寄存器(即PIN0寄存器)。
(2)如需更改某个引脚的状态,写字节引脚或字引脚寄存器。
(3)如需一次性更改多个引脚的状态,写SET和(或)CLR寄存器。
(4)如需在严格控制的环境(如软件状态机)中更改多个引脚的状态,可考虑使用NOT寄存器,这比SET和CLR需要的写操作更少。
(5)如需读取某个引脚的状态,读字节引脚或字引脚寄存器。
(6)如需根据多个引脚作出决定,读取并屏蔽PORT寄存器(即MPIN0寄存器)。

一般来说,当要控制的GPIO引脚是“一次性”操作的话,就操作PIN0寄存器,因为它会一次性把29位电平全部输出到对应的端口引脚上,或一次性把29根端口引脚上的电平全部读入到寄存器中,效率较高,比较适合芯片初始化时的引脚电平赋值、全端口电平取反等操作。而对于只控制单一的某一根引脚状态(读或写)时,可操作W或B寄存器,因为它只更改端口的其中一位的值而不会影响到其他位,不必像传统使用“与或”操作的方式那样麻烦。对于一次要控制多根引脚状态时,可根据情况来操作SET0和CLR0寄存器,其中SET0寄存器为指定的引脚输出高电平,而其他引脚的电平保持不变,CLR0则相反,为指定的引脚输出低电平,其他引脚的电平保持不变。但使用SET0和CLR0寄存器时要特别注意,它们中值为0的位所对应的引脚上的电平是保持原有不变,而不是输出低电平。当要对某些引脚进行电平取反时,可操作NOT0寄存器,值为1的位所对应的引脚电平取反,为0的保持原有电平不变。前面的操作均不受MASK0寄存器的影响,当要对端口的某些引脚确定其操作状态时,即只能针对某些引脚进行控制时,可操作MPIN0寄存器,而对其中能操作的引脚则通过MASK0寄存器来进行设置,MASK0中值为1的位所对应的引脚将被“固定”住其原来的状态不变,值为0的位所对应的引脚才能通过写MPIN0寄存器来更改。

同样的道理,对于方向寄存器DIR0、DIRSET0、DIRCLR0及DIRNOT0的使用也可参考上述原则进行。当要“一次性”确定所有引脚的方向时,就操作DIR0寄存器,原理就不再赘述了。而对于要一次更改多个(或单个)引脚的方向时,可根据情况来操作DIRSET0和DIRCLR0寄存器,其中DIRSET0寄存器为指定的引脚更改为输出方向,而其他引脚的方向保持不变,DIRCLR0则相反,为指定的引脚更改为输入方向,其他引脚的方向保持不变。同样,在使用DIRSET0和DIRCLR0寄存器时要注意,它们中值为0的位所对应的引脚方向是保持原来的不变,而不是变为输入。当只针对某些引脚的方向进行取反时,可操作DIRNOT0寄存器,值为1的位所对应的引脚方向取反,为0的保持原有方向不变。

通过上述分析,就可明确这12个端口寄存器的具体用法了。下面再通过修改前面第一个演示示例的例子,来看一下这12个端口寄存器可以怎样操作。为了简化,这里只罗列出需要修改的部分,其余部分照旧。下面给出的是原来的端口初始化函数。

void PORT_init(void)
{
 LPC_GPIO_PORT->DIR0 = 0x1FFFFFFF;        //设置端口为输出方向
 LPC_GPIO_PORT->PIN0 = 0x10090080;        //输出相应电平交替点亮LED
}

由于是端口初始化,即“一次性”操作,所以这里访问的是DIR0和PIN0两个寄存器,用来设置引脚的输出方向和电平高低。下面是定时器中断服务函数,用来把引脚的输出电平取反。

void SysTick_Handler(void)
{
  LPC_GPIO_PORT->PIN0 = ~LPC_GPIO_PORT->PIN0;    //取反赋值
}

在原来的程序中,访问的依然是PIN0寄存器,因为这里的取反也属于对端口的“一次性”操作。然而,该语句也可以更改为下面的样子。

void SysTick_Handler(void)
{
  LPC_GPIO_PORT->NOT0 = 0x1FFFFFFF;    //端口引脚电平取反
}

由于在NOT0寄存器中,值为1的位对应的引脚电平取反,所以把PIO0~PIO28的共29个位全部设置为1,也可以实现LPC_GPIO_PORT->PIN0 = ~LPC_GPIO_PORT->PIN0的取反效果。

除此以外,还可以通过访问MPIN0和MASK0两个寄存器来实现同样的效果。两个函数更改如下。

void PORT_init(void)
{
 LPC_GPIO_PORT->DIR0 = 0x1FFFFFFF;        //设置端口为输出方向
 LPC_GPIO_PORT->MPIN0 = 0x10090080;        //输出相应电平交替点亮LED
 // LPC_GPIO_PORT->MASK0 = 0x1FFFFFFF;   //屏蔽输出引脚
}
void SysTick_Handler(void)
{
  LPC_GPIO_PORT->MPIN0 = ~LPC_GPIO_PORT->MPIN0;    //取反赋值
}

按上述更改重新编译程序,会发现当注释掉对MASK0操作这一句的话,运行效果是与前面一致的。而当把注释掉的语句恢复后,则在运行时LED就不再闪烁了。原因是MASK0中值为1的位对应的引脚被屏蔽了,不会因MPIN0的赋值而改变。还可再进一步验证,把MASK0的赋值改成和MPIN0的值一样,即LPC_GPIO_PORT->MASK0 = 0x10090080,然后编译运行看看效果。可看到,LED变成了隔空闪烁而不是交替闪烁,这是为什么呢?仔细来分析一下,为了方便我们不防假设只有4个LED,而且是紧挨着的,依然是低电平点亮(即值为0时亮)。初始状态假设为“1010”,即MPIN0和MASK0的值都为“1010”。MASK0中值为1的位保持原来状态不变,即LED状态应为“灭X灭X”,其中X的状态由MPIN0的值确定。初始时X对应的值都为0(即低电平),所以初始状态的LED为“灭亮灭亮”。当MPIN0取反后值变为“0101”,X的值都由0变成了1(即高电平),所以X对应的LED状态为灭,因此加上MASK0中的屏蔽位后,LED的状态为“灭灭灭灭”,即全灭。而当MPIN0再次取反后,X的值又变为了0,所以加上MASK0中的屏蔽位后,LED的状态又变为了“灭亮灭亮”。可见,最终LED的效果就是隔空闪烁。

同理还可再做其他尝试,比如保持上述的端口初始化不变,而把中断服务函数改成下面的样子。

void SysTick_Handler(void)
{
  LPC_GPIO_PORT->PIN0 = ~LPC_GPIO_PORT->PIN0;    //取反赋值
// LPC_GPIO_PORT->NOT0 = 0x1FFFFFFF;    //端口引脚电平取反
}

编译后运行程序,会发现无论是操作PIN0取反,还是操作NOT0取反,都不影响LED的交替闪烁,这就证明了PIN0和NOT0两个寄存器确实不受MASK0寄存器的影响。

同样,为了验证使用SET0和CLR0两个寄存器的效果,还可以把中断服务函数改成下面的样子。

void SysTick_Handler(void)
{
 uint32_t temp;
 temp = LPC_GPIO_PORT->PIN0;
 LPC_GPIO_PORT->SET0 = ~temp;
 LPC_GPIO_PORT->CLR0 = temp;

可以看到,使用SET0和CLR0两个寄存器来实现同样的效果,就稍为麻烦一些。为了便于理解,我们仍然使用前面假设的4个LED。假设初始状态仍为“1010”,则LED状为“灭亮灭亮”,取反后的值为“0101”,赋值给SET0后,值为0的位对应的引脚电平保持不变,而值为1的位对应的引脚输出高电平,所以LED的状态变为了“灭灭灭灭”,即全灭。而给CLR0的赋值仍为“1010”,值为0的位对应的引脚电平保持不变,而值为1的位对应的引脚输出低电平,所以LED的状态又变为了“亮灭亮灭”,这样就实现了LED的交替闪烁。同时,为了便于操作端口,还定义了一个局部变量来存取上一次的端口值。需要强调的是,这里仅仅是为了学习而演示,此种方法在实际工程中是不建议采用的,不是因为复杂,而是因为它不可靠,细究会发现在端口上有一瞬间是输出了全1的(即LED全灭),虽然时间非常非常短,但仍然存在控制上的隐患,所以仅适用于学习。 

在上述验证中,还可以把temp = LPC_GPIO_PORT->PIN0改成temp = LPC_GPIO_PORT->MPIN0,并把端口初始化中的MASK0进行启用或禁用,逐步来验证效果,以提高对端口屏蔽的认识,具体操作在此就不赘述了。 

猜你喜欢

转载自www.cnblogs.com/fxzq/p/12822806.html