外部引脚可以触发芯片内部的中断,这是每一个通用MCU都具备的基本功能。
在LPC800中,所有外部引脚都可以配置为产生中断的触发源。每个引脚不但可以独立地触发中断,还可以和其它引脚的信号状态进行组合,由软件指定某种特定的组合触发中断。
前面几章已经介绍了引脚的特性配置由IOCON模块实现,开关矩阵则负责把引脚与片内外设对应起来。所有的数字信号,不管配置为输入还是输出,都可以被指定为引脚中断和引脚组合逻辑的一个输入选项。本章只介绍引脚中断和引脚组合逻辑模块,其它部分请参看对应章节。
下图给出了引脚中断和引脚组合逻辑模块(图中蓝色部分)与其它部分的关系示意。
图1.引脚中断和引脚组合逻辑模块与开关矩阵(SWM)和引脚配置模块(IOCON)的关系示意图
1.1. 引脚中断功能和使用任何引脚,只要它在开关矩阵或IOCON中被指定为数字引脚,不管是输入还是输出,这个引脚都可以被指定为引脚中断和引脚组合逻辑的输入端。所有的LPC800产品,都允许有最多8个引脚作为引脚中断和引脚组合逻辑的输入端。在SYSCON中通过8个PINTSEL寄存器,指定哪个引脚可以作为引脚中断的输入,软件只需把引脚编号写入对应的PINTSEL寄存器即可。对于PIO1_n的引脚,引脚编号为(32 + n)。
例如:
LPC_SYSCON->PINTSEL[0] = 20;// 指定PIO0_20为引脚中断0的输入源
LPC_SYSCON->PINTSEL[3] = 20;// 指定PIO0_20为引脚中断3的输入源
LPC_SYSCON->PINTSEL[6] = 33;// 指定PIO1_1为引脚中断6的输入源
1.1.1 指定中断触发源和触发方式
在LPC800中有8个引脚中断向量,它们分别为PININT0_IRQ ~ PININT7_IRQ,每个PINTSEL寄存器指定的引脚对应一个中断向量。以下10个寄存器用于控制中断触发源和触发方式: 表1.控制中断触发源和触发方式的寄存器
下面这个表格是按照要求的触发方式,标示出应该如何设置寄存器控制位。
表2.触发方式的配置
表2中可以看出,SIENR和CIENR都是只写寄存器,一个用于设置IENR寄存器位,另一个用于清除IENR寄存器位;这两个寄存器的目的是为了在修改IENR寄存器时的“读-修改-写”的操作,只需写操作,即可改变所有需要设置的位或需要清除的位。
同样,SIENF和CIENF也都是只写寄存器,用于设置IENF寄存器位。
由于独立的IENR和IENF寄存器,用户可以配置同一个信号的上升沿和下降沿都产生中断。在中断处理中,可以通过读出RISE和FALL寄存器判断是哪个边沿产生的中断。在RISE和/或FALL寄存器中写’1’可以清除中断状态,也可以在IST寄存器中写’1’ 清除中断状态。
1.1.2 电平触发方式的使用
因为通过软件本身,在MCU内部不能清除电平触发所产生的中断,软件必须执行某种操作,让外部电路改变信号线上的电平,才能使MCU不再产生中断,所以使用电平中断时要小心处理。
可以通过在IST寄存器中写’1’的方式,改变触发的电平,从而间接地清除中断状态。例如当高电平触发中断后,在IST寄存器中写’1’,控制器将变为有低电平时产生中断,同时清除中断状态。
一般情况下,建议用边沿触发方式,通过软件处理实现电平中断的效果。对于高电平中断的控制方式,改变位在上升沿中断的中断处理程序返回之前,检测该信号线是否为高电平的方式实现相同的逻辑功能。同理,低电平中断的控制方式,可以用下降沿中断,再加上检测低电平的方式实现相同逻辑功能。
1.2. 引脚中断的实用函数为了方便使用,这里呈现几个实用函数,方便使用PINT功能。
1.2.1复位所有引脚中断寄存器(PinInt_Reset)
PinInt_Reset()函数的功能就是清除所有未处理的引脚中断标志,同时关闭所有的引脚中断。
代码片段1.复位所有引脚中断函数
01 void PinInt_Reset()
02 {
03 LPC_PIN_INT->ISEL = 0; // 边沿触发
04 LPC_PIN_INT->IENR = 0; // 关闭上升沿或电平中断
05 LPC_PIN_INT->IENF = 0; // 关闭下降沿中断
06 LPC_PIN_INT->RISE = 0xFF; // 清除上升沿检测标志
07 LPC_PIN_INT->FALL = 0xFF; // 清除下降沿检测标志
08 }
1.2.2使能引脚的中断共有4个函数分别使能引脚的中断为上升沿触发、下降沿触发、高电平触发和低电平触发,这些函数的功能是按照给定的输入参数,使能对应的引脚中断,它的输入参数是需要设置的引脚中断的位域值。
输入参数的第0位为’1’表示需要设置PININT0,输入参数的第1位为’1’表示需要设置PININT1,依次类推直至第7位。
注意,不要把PININTn和引脚编号混淆,使用以下函数之前,需要指定PININTn和引脚的关系,由LPC_SYSCON->PINTSEL[0]定义,见1.1节。
下面分别是这4个函数的代码:
代码片段2.函数PinInt_Enable_Rising()
01 void PinInt_Enable_Rising(uint32_t pins_mask)
02 {
03 uint32_t Pint_mode;
04 Pint_mode = LPC_PIN_INT->ISEL;
05 if (Pint_mode & pins_mask) {
06 Pint_mode &= ~pins_mask;
07 LPC_PIN_INT->ISEL = Pint_mode; // 边沿触发
08 }
09 LPC_PIN_INT->SIENR = pins_mask; // 使能上升沿中断
10 }
代码片段3.函数PinInt_Enable_Falling()
01 void PinInt_Enable_Falling(uint32_t pins_mask)
02 {
03 uint32_t Pint_mode;
04 Pint_mode = LPC_PIN_INT->ISEL;
05 if (Pint_mode & pins_mask) {
06 Pint_mode &= ~pins_mask;
07 LPC_PIN_INT->ISEL = Pint_mode; // 边沿触发
08 }
09 LPC_PIN_INT->SIENF = pins_mask; // 使能下降沿中断
10 }
代码片段4.函数PinInt_Enable_High()
01 void PinInt_Enable_High(uint32_t pins_mask)
02 {
03 uint32_t Pint_mode;
04 Pint_mode = LPC_PIN_INT->ISEL;
05 if ((Pint_mode & pins_mask) != pins_mask) {
06 Pint_mode |= pins_mask;
07 LPC_PIN_INT->ISEL = Pint_mode; // 电平触发
08 }
09 LPC_PIN_INT->SIENF = pins_mask; // 使能高电平中断
10 }
代码片段5.函数PinInt_Enable_Low ()
01 void PinInt_Enable_Low(uint32_t pins_mask)
02 {
03 uint32_t Pint_mode;
04 Pint_mode = LPC_PIN_INT->ISEL;
05 if ((Pint_mode & pins_mask) != pins_mask) {
06 Pint_mode |= pins_mask;
07 LPC_PIN_INT->ISEL = Pint_mode; // 电平触发
08 }
09 LPC_PIN_INT->CIENF = pins_mask; // 使能低电平中断
10 }
1.2.3 关闭对应的引脚中断(PinInt_Disable)PinInt_Disable()函数的功能是按照给定的输入参数,关闭对应的引脚中断,它的输入参数和上面那些函数的输入参数意义一致,是需要设置的引脚中断的位域值。
代码片段6.函数PinInt_ Disable ()
01 void PinInt_Disable(uint32_t pins_mask)
02 {
03 uint32_t Pin_mask;
04 for (Pin_mask = 1; Pin_mask < 0x100; Pin_mask <<= 1) {
05 if ((pins_mask & Pin_mask) == 0)
06 continue; // 对未指定的位,不做任何操作
07 if (LPC_PIN_INT->ISEL & Pin_mask) { // 电平中断
08 LPC_PIN_INT->CIENR = Pin_mask; // 关闭对应中断
09 }
10 else { // 边沿触发
11 LPC_PIN_INT->CIENR = Pin_mask; // 关闭上升沿中断
12 LPC_PIN_INT->CIENF = Pin_mask; // 关闭下降沿中断
13 }
14 }
15 }
1.2.4清除对应的引脚中断标志(PinInt_Clear)
PinInt_Clear()函数的功能是按照给定的输入参数,清除对应的引脚中断标志,它的输入参数和上面那些函数的输入参数意义一致,是需要操作的引脚中断标志位的位域值。
通常这个函数是在中断处理函数中调用。
代码片段7.函数PinInt_Clear()
01 void PinInt_Clear(uint32_t pins_mask)
02 {
03 uint32_t Pin_mask;
04 for (Pin_mask = 1; Pin_mask < 0x100; Pin_mask <<= 1) {
05 if ((pins_mask & Pin_mask) == 0)
06 continue;
07 if (!(LPC_PIN_INT->ISEL & Pin_mask))
08 LPC_PIN_INT->IST = Pin_mask;
09 }
10 }
1.3. 引脚中断的使用实例下面借用GPIO章的例程,略作修改演示引脚中断的操作。本例程也是使用LPC824-Lite开发板,循环执行下列操作:
■通过GPIO循环点亮板上的八个红色LED
■分别点亮八个红色LED,模拟一个小虫爬行
设置USER(S4)、WAKEUP(S3)和ISP(S2)按键分别产生引脚中断0~2(PINTSEL0/1/2),对应的中断处理的意义如下:
A.按下USER键:循环体中跳过上述第2项操作,再按一次USER键恢复1-2循环。
B.按下WAKEUP键:循环体中跳过上述第1项操作,再按一次WAKEUP键恢复1-2循环。
C.按下ISP键:循环体中的操作速度变慢50%,再按一次ISP键恢复默认的速度。
D.如果上述2项操作都被跳过,则连续闪烁所有的LED灯。在例程中引入3个变量,分别向主循环传递3个按键的不同状态:
下面的PINT_Init()函数用于指定哪个按键,作为哪个中断的触发源,同时指定由按键的上升沿触发中断,最后使能对应的中断并设置中断优先级。
首先需要定义中断的编号:
代码片段8.引脚中断初始化函数
01 void PINT_Init(void)
02 {
03 LPC_GPIO_PORT->DIRCLR0 = PIN_KEYS_MASK;
04
05 LPC_SYSCON->PINTSEL[INT_USERKEY] = PIN_USERKEY;
06 LPC_SYSCON->PINTSEL[INT_WAKEKEY] = PIN_WAKEKEY;
07 LPC_SYSCON->PINTSEL[INT_ISPKEY] = PIN_ISPKEY;
08
09 PinInt_Reset();
10 PinInt_Enable_Rising((1<
11 | (1<
12 | (1<
13
14 NVIC_EnableIRQ(PININT0_IRQn);
15 NVIC_EnableIRQ(PININT1_IRQn);
16 NVIC_EnableIRQ(PININT2_IRQn);
17
18 NVIC_SetPriority(PININT0_IRQn, 3);
19 NVIC_SetPriority(PININT1_IRQn, 3);
20 NVIC_SetPriority(PININT2_IRQn, 3);
21 }
下面是本例程的主函数片断,和中断处理程序的代码,略去了上面描述的2项操作的具体实现,读者可以参考GPIO章节中的代码。
代码片段9.引脚中断主函数片断
01 PINT_Init();
02 skip_step1 = skip_step2 = 0;
03 leds_speed = 100;
04 while (1) {
05 if (skip_step1 == 0) {
06 LPC_GPIO_PORT->SET0 = PIN_LEDS_MASK; // 熄灭所有灯
07
08 }
09
10 if (skip_step2 == 0) {
11 LPC_GPIO_PORT->SET0 = PIN_LEDS_MASK; // 熄灭所有灯
12
13 }
14 }
15
16 if (skip_step1 && skip_step2) {
17 LPC_GPIO_PORT->NOT0 = PIN_LEDS_MASK; // 翻转所有灯
18 Wait1ms(leds_speed);
19 }
20 } // 结束while(1)循环
代码片段10.引脚中断的中断处理函数
01 void PININT0_IRQHandler(void)
02 {
03 if (LPC_PIN_INT->RISE & (1<
04 skip_step1 = !skip_step1;
05 PinInt_Clear(1 << INT_USERKEY);
06 }
07
08 void PININT1_IRQHandler(void)
09 {
10 if (LPC_PIN_INT->RISE & (1<
11 skip_step2 = !skip_step2;
12 PinInt_Clear(1<
13 }
14
15 void PININT2_IRQHandler(void)
16 {
17 if (LPC_PIN_INT->RISE & (1<
18 if (leds_speed == DEFAULT_SPEED)
19 leds_speed = DEFAULT_SPEED + DEFAULT_SPEED/2;
20 else
21 leds_speed = DEFAULT_SPEED;
22 PinInt_Clear(1<
23 }
上述三个中断处理程序的最后一个语句,都是调用PinInt_Clear ()函数清除对应的中断标志。这个清除标志的语句都是不受是否有上升沿中断影响,即使进入该中断处理函数时发生的事件不是上升沿,也会执行清除标志操作,这样安排是为防止有遗漏的中断,而造成反复进入中断处理程序而死机。
1.4 模式匹配引擎功能和使用模式匹配引擎实现引脚的组合逻辑,组合逻辑结果可以通过状态位由软件检测,也可以是引脚中断的延伸,产生中断请求,还可以向CPU核心发送事件信号和/或在某个引脚上输出。
前面介绍的引脚中断功能,每个中断只能由来自某个单个引脚的状态变化而产生;而通过模式匹配引擎,根据多个引脚的组合逻辑运算结果,可以产生对应的中断。例如可以实现一个键盘,键盘的每个按键可以单独产生中断,用于判断哪个键被按下,也可以使用组合逻辑功能,当某个特定的按键组合被按下,才能产生中断,这样可以更加方便地检测诸如Ctrl-C、Ctrl-V这样的组合功能。
1.4.1模式匹配引擎
一个逻辑运算表达式或布尔运算表达式,是由布尔变量经基本的”与”、”或”、”非”和”异或”等布尔运算构成。
下面是一些布尔运算表达式的例子:
例1. A*B + B*C + A*C
例2. D*C + A*B*C + A*D*E
例3. A + B*C*D + B*E*F + G*A*D
在LPC800的模式匹配引擎中,每个布尔变量与一个输入引脚一一对应,最多允许有8个布尔变量参与运算,同时所有变量出现次数的总和不能多于8个。
LPC800的模式匹配引擎可以直接支持”与”、”或”、”非”运算,但不能原生支持”异或”运算,需要由软件配置实现”异或”运算,如下:
A ^ B = A*/B + /A*B
在上述例1中总共有3个布尔变量:A、B和C,它们出现的次数总和为6次,可以由模式匹配引擎实现。例2有A~D共5个布尔变量,出现的总次数为8次,也可以由模式匹配引擎实现。但例3中有A~G共7个布尔变量,出现的总次数为10次,不能由模式匹配引擎实现。
1.4.2布尔项的实现
在模式匹配引擎中,布尔变量的每一次出现以一个布尔项来实现,在用户手册中“布尔项”以slice表示。
内部实现中,只有8个布尔项(slice),因此所有变量出现次数的总和不能多于8个。
对每一个布尔项,用户可以按照输入信号的不同变化,选择多达8种不同的条件。这些条件分别是:
1.恒为“高”。这种情况与输入信号无关,对应的布尔项始终为“高”。一般是按照应用逻辑,用于设置闲置的布尔项。
2.锁存的上升沿。从输入信号出现一个上升沿,到再次写入模式匹配引擎的控制寄存器之前,对应的布尔项为“高”。在此期间不管输入信号如何变化,对应的布尔项不再变化。写入控制寄存器会清除这个布尔项为“低”。
3.锁存的下降沿。从输入信号出现一个下降沿,到再次写入模式匹配引擎的控制寄存器之前,对应的布尔项为“高”。在此期间不管输入信号如何变化,对应的布尔项不再变化。写入控制寄存器会清除这个布尔项为“低”。
4.锁存的边沿。这是上面2个条件的结合,输入信号的上升沿或下降沿,都会使对应的布尔项为“高”。同样,写入控制寄存器会清除这个布尔项为“低”。
5.高电平。当输入信号为高电平时,对应的布尔项为“高”。注意这个条件没有经过锁存,即当输入信号变低时,对应的布尔项也变为“低”。
6.低电平。当输入信号为低电平时,对应的布尔项为“高”。注意这个条件没有经过锁存,即当输入信号变高时,对应的布尔项也变为“低”。这个条件相当于布尔“非”运算。
7.恒为“低”。这种情况与输入信号无关,对应的布尔项始终为“低”。一般是按照应用逻辑。用于设置闲置的布尔项。
8.实时的边沿事件。当检测到输入信号的上升沿或下降沿时,对应的布尔项为“高”,在一个时钟周期之后,对应的布尔项会自动变为“低”。下次再次检测到上升沿或下降沿时,重复这个“高-低”的过程。这个条件与上面的2、3、4不同,没有经过锁存,只持续一个时钟周期。
图2.布尔项(slice)的构成逻辑
1.4.3 模式匹配引擎的实现
如果我们把8个布尔项按顺序排列,在每两个布尔项之间就可以指定“与”或“或”操作。下图给出了布尔项与操作(符)之间的关系。
图3.布尔项与操作(符)直接的关系
参看1.4.1布尔表达式的例子,下面是把它们映射到内部布尔项的示意图。
图4.布尔算式的内部实现示意图1
图5.布尔算式的内部实现示意图2
整个模式匹配引擎是由多级的“与-或”门的级联实现,下图是完整内部实现示意图,有经验的读者可以参照用户手册加深理解。
图6.模式匹配引擎完整示意图
1.4.4引脚组合逻辑功能的配置
解释完布尔项的概念和布尔项的选项,以及它们之间的运算关系,接下来看看与配置寄存器的对应关系。
共有三个寄存器用于设置模式匹配引擎:
▲PMCTRL:模式匹配引擎的控制位和结果状态位。
■ SEL_PMATCH – 用于指定8个引脚中断是来自于引脚中断功能(见9.1节)还是来自模式匹配功能(见9.4节)。
■ENA_RXEV – 模式匹配结果为“真”时,用户可以配置该位使能向CPU核心发送RXEV事件并在引脚GPIO_INT_BMAT产生输出。
RXEV事件用于触发WFE(Wait For Event: 等待事件)指令结束等待状态。
■PMAT[7:0] – 这里每一位对应布尔组合(i)的输出,见图37。
▲PMSRC:指定每个布尔项使用哪个输入引脚
■如果要指定PINTSEL[n]对应的引脚,作为布尔项i的输入,只需要设置SRC(i)=n即可。
▲PMCFG:指定每个布尔项如何参与运算,同时指定结果输出结点。
■CFG[7:0] – 每一个分量用于选择对应布尔项与输入信号的关系,见1.4.2描述的8种情况。
■Prod_Endpts[6:0] – 如果某一位为‘0’,表示对应的操作为‘与’,见图3。某位为‘1’表示对应的操作为‘或’,并且需要输出前面各个布尔项相与的结果到PMAT[i],和对应的中断请求。
注意,不存在Prod_Endpts7。即使有这一位,它也会始终为‘1’。
1.4.5 模式匹配引擎的中断
可以用模式匹配(引脚组合逻辑)的结果产生中断,当PMATn为’1’时,如果使能了对应的PININTn中断,则这些分项(见图6中的绿色标注的信号),可以触发中断。这些中断的触发方式,在芯片内部固定设置为高电平触发,不能由软件配置。
既然是电平触发,这个中断不能由软件清除,只要这个信号为高,中断就会反复出现,直到该分项变为低。如果不希望反复进入中断,可以尝试使用“实时的边沿事件”作为组合条件(见1.4.2的第8个选项)。
1.5. 模式匹配引擎的设置在配置模式匹配引擎之前,建议用户先按照自己的布尔表达式,填写下面的表格。
■第一行PinInt的每个位置,填写对应PININT0~7的引脚编号。未用位置不填。
■第二行SliceSrc的每个位置,填写每个布尔项对应的PININTn编号n,取值范围是0~7。
■第三行SliceCfg的每个位置,填写如何配置每个布尔项,见1.4.2节,取值使用下述定义的常量:
■第四行SliceEndp的每个位置,填写’0’表示这个位置执行“与”运算并没有结果输出;填写非’0’的值,表示这个位置执行“或”运算并输出结果。芯片中没有SliceEndp第7个位置对应的配置位,此处内容无效。
此表格的目的是在写代码之前有一个完整的概念,这样写代码时不会产生各个寄存器内容不匹配的问题。
下面再定义几个宏,配合上述表格就可以很容易地实现对模式匹配引擎的设置。
首先参考1.4.4节的寄存器说明,定义一组移位操作的宏,每三位代码为一组进行移位:
#define PMPOS0(src) (((src)&0x7)<<8)
|
|
#define PMPOS1(src) (((src)&0x7)<<11)
|
|
#define PMPOS2(src) (((src)&0x7)<<14)
|
|
#define PMPOS3(src) (((src)&0x7)<<17)
|
|
#define PMPOS4(src) (((src)&0x7)<<20)
|
|
#define PMPOS5(src) (((src)&0x7)<<23)
|
|
#define PMPOS6(src) (((src)&0x7)<<26)
|
|
#define PMPOS7(src) (((src)&0x7)<<29)
|
#define PMep0(ep) (((ep)&0x1)<<0)
|
|
#define PMep1(ep) (((ep)&0x1)<<1)
|
|
#define PMep2(ep) (((ep)&0x1)<<2)
|
|
#define PMep3(ep) (((ep)&0x1)<<3)
|
|
#define PMep4(ep) (((ep)&0x1)<<4)
|
|
#define PMep5(ep) (((ep)&0x1)<<5)
|
|
#define PMep6(ep) (((ep)&0x1)<<6)
|
定义SliceCfg如下,逐个填入上述表格第三行的所有内容:定义ProdEndp如下,逐个填入上述表格第四行的所有内容,再和SliceCfg宏的结果相’或’ ,写入PMCFG寄存器进行初始化:
下面再用实例说明初始化设置的过程。
1.6. 模式匹配引擎的使用实例1.6.1异或的实现
假定有一个电梯控制器,它只有两个按钮,一个“向上”,一个“向下”,如果分别单独按下任一个按钮,电梯会按指定方向运行,如果两个按钮同时按下,则电梯不会运行。这就是“异或”操作逻辑。
下面的代码用USERKEY和ISPKEY,分别代表“向上”和“向下”按钮,用模式匹配引擎实现这个“异或”操作逻辑。
整个的布尔表达式是:
USERKEY * NOT(ISPKEY) + NOT(USERKEY) * ISPKEY
按照这个表达式填写前述表格如下:
参照前面的介绍,解读这个表格就很容易了:■第一行:USERKEY对应PININT0;ISPKEY对应PININT1。
■第二行:PININT0对应布尔项0、2;PININT1对应布尔项1、3。
■第三行:布尔项0、3的输入为“高电平”有效;布尔项1、2的输入为“低电平”有效。布尔项4~7的输入恒为“低”,在最终结果中不产生影响。
■第四行:PMAT[1]得到布尔项0、1相与的结果;PMAT[3]得到布尔项2、3相与的结果。
下面的初始化代码实现了上述表格的内容:
代码片段11.“异或”模式匹配初始化函数
01 void PM_Init(void)
02 {
03 LPC_SYSCON->PINTSEL[0] = PIN_USERKEY;
04 LPC_SYSCON->PINTSEL[1] = PIN_ISPKEY;
05
06 #if !PM_POLLING
07 NVIC_EnableIRQ(PININT1_IRQn);
08 NVIC_EnableIRQ(PININT3_IRQn);
09
10 NVIC_SetPriority(PININT1_IRQn, 3);
11 NVIC_SetPriority(PININT3_IRQn, 3);
12 #endif // PM_POLLING
13
14 LPC_PIN_INT->PMSRC = SliceSrc(0, 1, 1, 0, 0, 0, 0, 0);
15 LPC_PIN_INT->PMCFG = SliceCfg(SLICE_DIRECT,
16 SLICE_NOT,
17 SLICE_DIRECT,
18 SLICE_NOT,
19 SLICE_CONST0,
20 SLICE_CONST0,
21 SLICE_CONST0,
22 SLICE_CONST0) |
23 ProdEndp(0, 1, 0, 1, 0, 0, 0);
24 LPC_PIN_INT->PMCTRL = 0x03;
25
26 ConfigSWM(GPIO_INT_BMAT, PIN_LED0);
27 }
上述代码段的14、15行是对模式匹配寄存器的初始化,这里用到了上一节定义的宏。
本例程有两种操作模式:轮询和中断模式。在轮询模式中,软件循环地检测PMCTRL寄存器的PMAT域,当检测到布尔输出项1或3为’1’时,分别点亮LED1或LED3,否则熄灭LED。下面的PM_Polling()函数会被主循环调用,执行循环检测。代码片段12.循环检测PMAT状态函数
01 void PM_Polling(void)
02 {
03 uint32_t PMAT = LPC_PIN_INT->PMCTRL>>24;
04
05 if (PMAT & (1<<1))
06 LPC_GPIO_PORT->CLR0 = 1<
07 else if (PMAT & (1<<3))
08 LPC_GPIO_PORT->CLR0 = 1<
09 else
10 LPC_GPIO_PORT->SET0 = 1<
11 }
在中断模式下,需要安排两个中断处理程序PININT1_IRQHandler()和PININT3_IRQHandler(),分别接收布尔输出项1和3的中断。
代码片段13.中断处理函数
01
02 void PININT1_IRQHandler(void)
03 {
04 LPC_GPIO_PORT->CLR0 = 1<
05 }
06
07 void PININT3_IRQHandler(void)
08 {
09 LPC_GPIO_PORT->CLR0 = 1<
10 }
11
最后是主函数。代码片段14.模式匹配初始化函数
01 int main()
02 {
03 GPIO_Init();
04
05 LPC_GPIO_PORT->DIRSET0 = PIN_LEDS_MASK; // 设置LED对应的引脚为输出
06 LPC_GPIO_PORT->SET0 = PIN_LEDS_MASK; // 熄灭所有LED灯
07
08 PINT_Init(); // 初始化引脚中断模块
09 PM_Init(); // 初始化模式匹配模块,见代码片段21
10
11 while(1) {
12 #if PM_POLLING
13 PM_Polling();
14 #else
15 LPC_GPIO_PORT->SET0 = 1<
16 #endif
17 LPC_PIN_INT->RISE = 0xFF;
18 LPC_PIN_INT->FALL = 0xFF;
19 }
20 }
在中断模式下,当没有中断时,主函数的第15行会不断地熄灭LED。但当有某个按钮按下时,由于模式匹配的中断是电平中断,一旦产生中断的条件存在,代码片段23的中断处理函数就会不断地被调用,CPU将不能执行主函数中的指令。这样当仅按下一个按钮时,使用者就会看见对应的LED常亮,直到松开按钮。松开按钮后,产生中断的条件消失,CPU才能返回主函数,LED被熄灭。
在代码片段21的模式匹配初始化函数的第26行,模式匹配的结果输出信号GPIO_INT_BMAT被映射到对应LED0的引脚,通过LED0的状态可以直观地看到整个逻辑运算的结果。综合起来会有如下效果:1.6.2三人表决器
三人表决器就是每人一个按钮,任意两人按下按钮则表示表决通过。
本实例使用开发板上的三个按键USERKEY、WAKEKEY和ISPKEY。首先填写初始化的表格:
所有的布尔项都使用“锁存的上升沿”作为模式匹配的输入。电路的配置是按下按键会把引脚短接到地,因此抬起按键的动作会产生上升沿,经过内部锁存,可以保证不同按键的按下时间不同,但状态不会丢失。
下面是这个三人表决器的初始化函数,可以看出这个函数内容基本和上面例子的函数一样,只是代入的参数不同:
代码片段15.“三人表决器”模式匹配初始化函数
01 void PM_Init(void)
02 {
03 LPC_SYSCON->PINTSEL[0] = PIN_USERKEY;
04 LPC_SYSCON->PINTSEL[1] = PIN_WAKEKEY;
05 LPC_SYSCON->PINTSEL[2] = PIN_ISPKEY;
06
07 #if !PM_POLLING
08 NVIC_EnableIRQ(PININT1_IRQn);
09 NVIC_EnableIRQ(PININT3_IRQn);
10 NVIC_EnableIRQ(PININT5_IRQn);
11
12 NVIC_SetPriority(PININT1_IRQn, 3);
13 NVIC_SetPriority(PININT3_IRQn, 3);
14 NVIC_SetPriority(PININT5_IRQn, 3);
15 #endif // PM_POLLING
16
17 LPC_PIN_INT->PMSRC = SliceSrc(0, 1, 1, 2, 2, 0, 0, 0);
18 LPC_PIN_INT->PMCFG = SliceCfg(SLICE_RISE,
19 SLICE_RISE,
20 SLICE_RISE,
21 SLICE_RISE,
22 SLICE_RISE,
23 SLICE_RISE,
24 SLICE_CONST0,
25 SLICE_CONST0) |
26 ProdEndp(0, 1, 0, 1, 0, 1, 0);
27 LPC_PIN_INT->PMCTRL = 0x03;
29
30 ConfigSWM(GPIO_INT_BMAT, PIN_LED0);
31 }
以下是主函数中的主循环部分。
代码片段16.“三人表决器”主循环部分
01 while(1) {
02 TimeOut_Ctrl();
03 #if PM_POLLING
04 PM_Polling();
05 #else
06 LPC_GPIO_PORT->SET0 = 1<
07 #endif
08 }
在主循环下,同样有中断模式和轮询模式的区分。操作和前面的“异或”例程一致。
特殊的是,这个例程设置了一个超时处理,能够定期地清除投票状态。
代码片段17. “三人表决器”的超时处理
01 void TimeOut_Ctrl()
02 {
03 if (uwTick > 10) {
04 LPC_GPIO_PORT->NOT0 = 1<
05 uwTick = 0;
06
07 LPC_PIN_INT->PMCTRL = 0x00;
08 LPC_PIN_INT->PMCTRL = 0x03;
09 }
10 }
代码片段18.“三人表决器”中断模式的中断处理
01 void PININT1_IRQHandler(void)
02 {
03 LPC_GPIO_PORT->CLR0 = 1<
04 TimeOut_Ctrl();
05 }
以下是这个例程演示的效果:
表中的“投”表示按键被按下并被释放。如果只按下按键,而保持按下而不释放,模式匹配模块不会判断它为按下状态,这种情况可以理解为,投票者还在犹豫。
当LED0亮时表示不够两个人以上投票,投票没有通过;LED0熄灭表示投票通过。
在使用这个例程的中断模式时,由于各个中断的优先级一致,同时由于电平中断的缘故,如果一旦进入了某个中断,有可能另一个布尔项的中断不能被响应,结果LED1/3/5中有些时候不能被同时点亮。这种现象,不会出现在轮询模式下。读者由此可以体会到轮询与中断的区别。
全部0条评论
快来发表一下你的评论吧 !