LPC800前生今世-第八章 引脚中断和引脚组合逻辑 (Pin Interrupt & Pin Pattern)

描述

外部引脚可以触发芯片内部的中断,这是每一个通用MCU都具备的基本功能。

 

在LPC800中,所有外部引脚都可以配置为产生中断的触发源。每个引脚不但可以独立地触发中断,还可以和其它引脚的信号状态进行组合,由软件指定某种特定的组合触发中断。

 

前面几章已经介绍了引脚的特性配置由IOCON模块实现,开关矩阵则负责把引脚与片内外设对应起来。所有的数字信号,不管配置为输入还是输出,都可以被指定为引脚中断和引脚组合逻辑的一个输入选项。本章只介绍引脚中断和引脚组合逻辑模块,其它部分请参看对应章节。

 

 

下图给出了引脚中断和引脚组合逻辑模块(图中蓝色部分)与其它部分的关系示意。

 

 

mcu图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个寄存器用于控制中断触发源和触发方式:mcu          表1.控制中断触发源和触发方式的寄存器

下面这个表格是按照要求的触发方式,标示出应该如何设置寄存器控制位。

mcu   表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个按键的不同状态:

mcu

下面的PINT_Init()函数用于指定哪个按键,作为哪个中断的触发源,同时指定由按键的上升沿触发中断,最后使能对应的中断并设置中断优先级。

首先需要定义中断的编号:mcu

代码片段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不同,没有经过锁存,只持续一个时钟周期。

 

 

mcu                 图2.布尔项(slice)的构成逻辑

 

 

1.4.3 模式匹配引擎的实现

 

 

如果我们把8个布尔项按顺序排列,在每两个布尔项之间就可以指定“与”或“或”操作。下图给出了布尔项与操作(符)之间的关系。

 

 

mcu               图3.布尔项与操作(符)直接的关系

 

 

参看1.4.1布尔表达式的例子,下面是把它们映射到内部布尔项的示意图。

 

 

mcu               图4.布尔算式的内部实现示意图1

 

 

mcu                图5.布尔算式的内部实现示意图2

 

 

整个模式匹配引擎是由多级的“与-或”门的级联实现,下图是完整内部实现示意图,有经验的读者可以参照用户手册加深理解。

 

 

mcu                 图6.模式匹配引擎完整示意图

 

 

1.4.4引脚组合逻辑功能的配置

 

 

解释完布尔项的概念和布尔项的选项,以及它们之间的运算关系,接下来看看与配置寄存器的对应关系。

 

 

共有三个寄存器用于设置模式匹配引擎:

 

 

▲PMCTRL:模式匹配引擎的控制位和结果状态位。

 

 

mcu■ 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:指定每个布尔项使用哪个输入引脚

 

 

mcu■如果要指定PINTSEL[n]对应的引脚,作为布尔项i的输入,只需要设置SRC(i)=n即可。

 

 

▲PMCFG:指定每个布尔项如何参与运算,同时指定结果输出结点。

 

 

mcu■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节,取值使用下述定义的常量:

 

mcu

■第四行SliceEndp的每个位置,填写’0’表示这个位置执行“与”运算并没有结果输出;填写非’0’的值,表示这个位置执行“或”运算并输出结果。芯片中没有SliceEndp第7个位置对应的配置位,此处内容无效。

mcu

此表格的目的是在写代码之前有一个完整的概念,这样写代码时不会产生各个寄存器内容不匹配的问题。

 

 

下面再定义几个宏,配合上述表格就可以很容易地实现对模式匹配引擎的设置。

 

 

首先参考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)

 

 

接下来是涉及PMCFG的低6位的另一组移位操作的宏:

 

 

#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)

 

 

然后使用上述宏,定义SliceSrc如下,逐个填入上述表格第二行的所有内容,然后写入PMSRC寄存器进行初始化:

 

mcu定义SliceCfg如下,逐个填入上述表格第三行的所有内容:mcu定义ProdEndp如下,逐个填入上述表格第四行的所有内容,再和SliceCfg宏的结果相’或’ ,写入PMCFG寄存器进行初始化:mcu

 

下面再用实例说明初始化设置的过程。

 

 

 1.6.  模式匹配引擎的使用实例1.6.1异或的实现

假定有一个电梯控制器,它只有两个按钮,一个“向上”,一个“向下”,如果分别单独按下任一个按钮,电梯会按指定方向运行,如果两个按钮同时按下,则电梯不会运行。这就是“异或”操作逻辑。

 

 

下面的代码用USERKEY和ISPKEY,分别代表“向上”和“向下”按钮,用模式匹配引擎实现这个“异或”操作逻辑。

 

 

整个的布尔表达式是:

 

USERKEY * NOT(ISPKEY) + NOT(USERKEY) * ISPKEY

 

按照这个表达式填写前述表格如下:

 

mcu照前面的介绍,解读这个表格就很容易了:■第一行: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  #if ! PM_POLLING
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  #endif  // PM_POLLING

最后是主函数。代码片段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的状态可以直观地看到整个逻辑运算的结果。综合起来会有如下效果:mcu1.6.2三人表决器

 

三人表决器就是每人一个按钮,任意两人按下按钮则表示表决通过。

 

 

本实例使用开发板上的三个按键USERKEY、WAKEKEY和ISPKEY。首先填写初始化的表格:mcu

所有的布尔项都使用“锁存的上升沿”作为模式匹配的输入。电路的配置是按下按键会把引脚短接到地,因此抬起按键的动作会产生上升沿,经过内部锁存,可以保证不同按键的按下时间不同,但状态不会丢失。

 

 

下面是这个三人表决器的初始化函数,可以看出这个函数内容基本和上面例子的函数一样,只是代入的参数不同:

 

代码片段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  }

以下是这个例程演示的效果:

mcu

表中的“投”表示按键被按下并被释放。如果只按下按键,而保持按下而不释放,模式匹配模块不会判断它为按下状态,这种情况可以理解为,投票者还在犹豫。

 

 

当LED0亮时表示不够两个人以上投票,投票没有通过;LED0熄灭表示投票通过。

 

 

在使用这个例程的中断模式时,由于各个中断的优先级一致,同时由于电平中断的缘故,如果一旦进入了某个中断,有可能另一个布尔项的中断不能被响应,结果LED1/3/5中有些时候不能被同时点亮。这种现象,不会出现在轮询模式下。读者由此可以体会到轮询与中断的区别。


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分