本节讲述如何用外部IO的中断介入一个LED的点亮状态。
各位一定都使用过各种单片机的中断功能,其作用,意义以及工作过程我想都不用特别说明了。LPC1343的IO中断相对于低端8位单片机来说,有以下不一样的地方:
1、 每一个IO都可以随意定制成中断IO;
2、 可以设置为跳变沿触发和电平触发方式,其中的跳变沿模式中,可以设置成双跳变沿触发方式,原文如下:Interrupts can be configured on single falling or rising edges and on both edges.笔者理解,触发电平应该是一个脉冲的形式。
3、 这个不算IO中断的特点,但是比较重要,在此提出:IO中断的中断标志并不会硬件清除,需要在中断服务中软件清除,否则会一直在触发IO中断(似乎M3器件都是这样子的);
分析一下NXP给我们附带的例子程序“extint”,先是主程序:
#include "LPC13xx.h"
#include "gpio.h"
#include "config.h"
int main(void)
{
GPIOInit();
GPIOSetDir(PORT2, 1, 0);
GPIOSetInterrupt(PORT2, 1, 0, 0, 0);//第一个0表示边沿触发,第二个0表示单边沿触发, //第三个0表示负跳变触发
GPIOIntEnable(PORT2, 1);//取消中断屏蔽,即打开IO中断功能
GPIOSetDir(LED_PORT, LED_BIT, 1);
GPIOSetValue(LED_PORT, LED_BIT, LED_OFF );
while (1);
}
首先头两句句是GPIO的初始化,以及方向设置,这里它设置了P2口的第1位(注意是第1位不是第0位)作为输入口,明显将要用该口作为中断IO。
下面一句是本文的重点了,IO中断方式的设置:
GPIOSetInterrupt(PORT2, 1, 0, 0, 0);
我们在gpio.c中找到这个函数的原型:
/*****************************************************************************
** Function name: GPIOSetInterrupt
** Descriptions: Set interrupt sense, event, etc.
** edge or level, 0 is edge, 1 is level
** single or double edge, 0 is single, 1 is double
** active high or low, etc.
** parameters: port num, bit position, sense, single/doube, polarity
** Returned value: None
**
*****************************************************************************/
void GPIOSetInterrupt( uint32_t portNum, uint32_t bitPosi, uint32_t sense,
uint32_t single, uint32_t event )
{
switch ( portNum )
{
case PORT0:
if ( sense == 0 )//如果设置边沿触发
{
LPC_GPIO0->IS &= ~(0x1<<bitPosi);
/* single or double only applies when sense is 0(edge trigger). */
if ( single == 0 )
LPC_GPIO0->IBE &= ~(0x1<<bitPosi);//单跳变沿
else
LPC_GPIO0->IBE |= (0x1<<bitPosi);//双跳变沿
}
else //如果设置电平触发
LPC_GPIO0->IS |= (0x1<<bitPosi);
if ( event == 0 )
LPC_GPIO0->IEV &= ~(0x1<<bitPosi);//低电平或负跳变触发,取决于IBE
else
LPC_GPIO0->IEV |= (0x1<<bitPosi);//高电平或正跳变触发,取决于IBE
break;
case PORT1:
if ( sense == 0 )
{
LPC_GPIO1->IS &= ~(0x1<<bitPosi);
/* single or double only applies when sense is 0(edge trigger). */
if ( single == 0 )
LPC_GPIO1->IBE &= ~(0x1<<bitPosi);
else
LPC_GPIO1->IBE |= (0x1<<bitPosi);
}
else
LPC_GPIO1->IS |= (0x1<<bitPosi);
if ( event == 0 )
LPC_GPIO1->IEV &= ~(0x1<<bitPosi);
else
LPC_GPIO1->IEV |= (0x1<<bitPosi);
break;
case PORT2:
if ( sense == 0 )
{
LPC_GPIO2->IS &= ~(0x1<<bitPosi);
/* single or double only applies when sense is 0(edge trigger). */
if ( single == 0 )
LPC_GPIO2->IBE &= ~(0x1<<bitPosi);
else
LPC_GPIO2->IBE |= (0x1<<bitPosi);
}
else
LPC_GPIO2->IS |= (0x1<<bitPosi);
if ( event == 0 )
LPC_GPIO2->IEV &= ~(0x1<<bitPosi);
else
LPC_GPIO2->IEV |= (0x1<<bitPosi);
break;
case PORT3:
if ( sense == 0 )
{
LPC_GPIO3->IS &= ~(0x1<<bitPosi);
/* single or double only applies when sense is 0(edge trigger). */
if ( single == 0 )
LPC_GPIO3->IBE &= ~(0x1<<bitPosi);
else
LPC_GPIO3->IBE |= (0x1<<bitPosi);
}
else
LPC_GPIO3->IS |= (0x1<<bitPosi);
if ( event == 0 )
LPC_GPIO3->IEV &= ~(0x1<<bitPosi);
else
LPC_GPIO3->IEV |= (0x1<<bitPosi);
break;
default:
break;
}
return;
}
首先看文件头的说明中的
Descriptions: Set interrupt sense, event, etc.
** edge or level, 0 is edge, 1 is level
** single or double edge, 0 is single, 1 is double
** active high or low, etc.
说明了该函数的作用是:设置中断的各种方式(跳变沿触发,电平触发,跳变沿书目,还有电平触发方式),都是一些很常见的概念了。看看内部,大家先仔细看看这个函数,可以发现,这个函数作为一个switch分支结构,它的每一个case的内容都是很相似的,明显四个case分别对应四个PORT的设置,那么我们只要把某一个看懂,其他的也触类旁通了,我们看第一个,
case PORT0:
if ( sense == 0 )//如果设置边沿触发
{
LPC_GPIO0->IS &= ~(0x1<<bitPosi);//设置为边沿触发方式
/* single or double only applies when sense is 0(edge trigger). */
if ( single == 0 )
LPC_GPIO0->IBE &= ~(0x1<<bitPosi);//单跳变沿
else
LPC_GPIO0->IBE |= (0x1<<bitPosi);//双跳变沿
}
else //如果设置电平触发
LPC_GPIO0->IS |= (0x1<<bitPosi);
if ( event == 0 )
LPC_GPIO0->IEV &= ~(0x1<<bitPosi);//低电平或负跳变触发,取决于IBE
else
LPC_GPIO0->IEV |= (0x1<<bitPosi);//高电平或正跳变触发,取决于IBE
break;
再配上GPIO中有关中断的寄存器:
现在来分析程序:
首先进入case,判断sense的值,如果是0,则表示程序员要将IO设置为“跳变沿触发方式”
,接着将IO设置为“跳变沿触发方式”(详见GPIOnIS,因为接下去CM3的各个设备的寄存器会越来越多,笔者这里就无法将每一个寄存器详细描述了,希望NXP能出本书~),根据参数single来设置是使用单跳变沿还是双跳变沿(详见GPIOnIBE)。而在进入case之后,判断sense的值,如果是1,则设置IO设置为“电平触发方式”,接着判断是低电平触发还是高电平触发(详见GPIOnIBE),至此设置完毕,break。
这里有一点绕圈的地方关键在于IS,IBE和IEV三个寄存器:
1、 IS寄存器是设置触发方式的,跳变沿触发或者电平触发;
2、 IBE是设置跳变沿数目的,所以如果IS已经设置为电平触发,那IBE就没有意义了;
3、 如果IS设置为跳变沿触发,则IBE设置跳变沿书目,而IEV设置是上升沿触发还是下降沿触发;
4、 如果IS设置为电平触发,则IBE无用,IEV来设置是高电平触发还是低电平触发;
总结一下,GPIOSetInterrupt(PORT2, 1, 0, 0, 0);的作用是,设置P2口的第1位IO为跳变沿触发方式,单跳变沿,负跳变触发。
继续看程序,下一句是:GPIOIntEnable(PORT2, 1);这句很简单,原型如下:
/*****************************************************************************
** Function name: GPIOIntEnable
**
** Descriptions: Enable Interrupt Mask for a port pin.
**
** parameters: port num, bit position
** Returned value: None
**
*****************************************************************************/
void GPIOIntEnable( uint32_t portNum, uint32_t bitPosi )
{
switch ( portNum )
{
case PORT0:
LPC_GPIO0->IE |= (0x1<<bitPosi);
break;
case PORT1:
LPC_GPIO1->IE |= (0x1<<bitPosi);
break;
case PORT2:
LPC_GPIO2->IE |= (0x1<<bitPosi);
break;
case PORT3:
LPC_GPIO3->IE |= (0x1<<bitPosi);
break;
default:
break;
}
return;
}
笔者在此就不多阐述了,这个函数就是对GPIO寄存器中的IE寄存器操作,打开对应IO的中断功能(即取消中断屏蔽)。
最后两句是点灯程序,这个大家都知道了的。
我们继续看看中断的另外一半,中断服务函数:
/*****************************************************************************
** Function name: PIOINT2_IRQHandler
**
** Descriptions: Use one GPIO pin(port2 pin1) as interrupt source
**
** parameters: None
** Returned value: None
**
*****************************************************************************/
void PIOINT2_IRQHandler(void)
{
uint32_t regVal;
gpio2_counter++;
regVal = GPIOIntStatus( PORT2, 1 );//读取IO中断状态,若触发返回1
if ( regVal )
{
p2_1_counter++;
GPIOIntClear( PORT2, 1 );//这里很重要,一定要软件清除IO的中断触发标志!!!
}
// CodeRed - extend original interrupt handler so as to toggle the LED state
if (LEDvalue == LED_OFF)
{
LEDvalue = LED_ON;
}
else
{
LEDvalue = LED_OFF;
}
GPIOSetValue( LED_PORT, LED_BIT, LEDvalue );
return;
}
这个服务函数的流程也很清晰,首先判断是否是我们想要的IO触发的中断,为什么要判断呢?那是因为每个PORT共用同1个中断,即共用同一个中断服务函数。判断完之后,清除对应中断标志,否则中断不会被清除,再三强调一下。之后就是程序员想要在中断里面处理的事情了。特别提一下注意中断服务函数的函数名的写法~
编译,运行,灯亮,用杜邦线将P21接到GND上,灯灭,拔出再接上,灯亮……
当然这个现象是理想状态,你的手不可能那么稳那么准~呵呵,Enjoy
下一节,我们来看看如何使用中断的嵌套功能~
[ 本帖最后由 tiankai001 于 2010-5-6 10:55 编辑 ] |