模式匹配中断是引脚外部中断功能的扩展,它可以把多个引脚的状态进行逻辑运算后再产生中断,支持“与”、“或”、“非”等逻辑运算,但不原生支持“异或”运算。模式匹配中断的实现方式是,可创建一个或多个布尔表达式,每个布尔表达式都可以产生中断请求,一共有8个输入变量,每个变量可以与任一个PININT输入配接,即使同一变量也可以在一个表达式中出现多次。
模式匹配特性允许根据针对GPIO引脚中断选择的同一组8个GPIO引脚构建复杂的布尔表达式。布尔表达式中的每一项都部署为模式匹配引擎的一个逻辑片。一个逻辑片由输入选择器和检测逻辑组成。逻辑片输入选择器从可用的8个输入中选择某个输入,每个输入均通过PINTSEL寄存器连接一根引脚。
检测逻辑连续监控选定输入,若输入满足检测条件则建立高电平输出。通过指定某个逻辑片为表达式的端点,某些项可合并为一个最小项。最小项被认为为真时,该逻辑片的引脚中断会被置位。
所有模式匹配引擎的输入均在SYSCON模块中选定,并且取决于不同的开关矩阵配置,可以是GPIO端口引脚或另一个引脚功能。
模式匹配逻辑可连续监控8个输入,并在指定布尔表达式的任意一个或多个最小项(乘积项)匹配时产生中断。针对每一个最小项,都会产生一个独立的中断请求。此外,当布尔表达式为真时,可使能模式匹配模块生成输出至ARM内核的接收事件(RXEV)。RXEV输出可路由至GPIO_INT_BMAT引脚。因此,GPIO模块能提供可部署多达8个输入和1个输出的基本可编程逻辑功能。
模式匹配功能利用相同的8路中断请求线路作为引脚中断,因此只要和中断产生有关,这两种特性便是互斥的。提供控制位是为了选择是否产生中断请求以响应标准引脚中断或模式匹配。需要注意的是,如果选定引脚中断,则发送至CPU的RXEV请求依然能被使能用于模式匹配。模式匹配功能可用于创建复杂的软件状态机。每个最小项(及其相关专用中断)都代表不同的新状态转换事件。软件之后便可建立可转换出当前状态的一组新条件(即新的布尔表达式)。
在LPC824中,模式匹配引擎具有如下特性:
1、可从所有GPIO引脚中最多选择8个引脚,组成布尔表达式。布尔表达式由特定电平和/或这些引脚的多种组合转换所组成。
2、组成特定布尔表达式的每一个位逻辑片最小项(乘数项)都可以产生其自身的专用中断请求。
3、还可编程任意模式匹配事件,生成ARM CPU的RXEV通知。RXEV信号可连接引脚。
4、模式匹配能与软件配合使用,根据引脚输入创建复合状态机。
每个逻辑片上的检测逻辑可检测选定输入的下列事件:
1、存储器边沿(粘滞):边沿检测机制清零后的任意时刻检测到的上升沿、下降沿或上升/下降沿。在模式匹配引擎检测逻辑被再次清零之前,输入都满足检测条件(检测逻辑输出保持高电平)。
2、事件(非粘滞):每次检测到边沿(上升沿或下降沿),该引脚的检测逻辑输出都会变为高电平。该位在一个时钟周期后会被清零,因此检测逻辑可检测另一个边沿。
3、电平:选定输入端上的高电平或低电平。
在一次验证边沿事件后,无论此时为上升沿或是下降沿,都可将粘滞事件与非粘滞事件相结合,创建一次引脚中断。
模式匹配用到的寄存器位于外部中断寄存器组中,一共有3个,下面就来分别讨论一下。
先看模式匹配中断控制寄存器PMCTRL,下表给出了它的全部位结构,其字节地址为0xA0004028。
(1)第0位为引脚中断模式或模式匹配模式选择位,置0时,选择为引脚中断模式,置1时,选择为模式匹配模式,默认为引脚中断模式。
(2)第1位为使能RXEV输出选择位,置0时,禁止RXEV输出至CPU,置1时,使能RXEV输出至CPU。
(3)第2到23位为保留位。
(4)第24到31位为用来获取模式匹配的当前状态,值为1的位表示相应乘积项匹配适当输入的当前状态。
接下来看模式匹配位逻辑片源寄存器PMSRC,其字节地址为0xA000402C。由于PMSRC太长,且每个SRC部分的位结构是完全一样的,所以下表只给出了它的SRC0的位结构,其余的SRC1~SRC7可参考SRC0的位结构,它们完全一样。
(1)第0到7位为保留位,写入时不应对它们写1。
(2)第8到10位为逻辑片0的输入源选择位,值为0x0时选择PINTSEL0寄存器所选定的引脚作为位逻辑片0的源,值为0x1时选择PINTSEL1寄存器所选定的引脚作为位逻辑片0的源,以此类推,值为0x7时选择PINTSEL7寄存器所选定的引脚作为位逻辑片0的源。
(3)后续的SRC1~SRC7分别为逻辑片1~7的输入源选择位,选择的方式与第2项一样。
接下来是模式匹配位逻辑片配置寄存器PMCFG,其字节地址为0xA0004030。下表给出了它的部分位结构。
(1)第0到7位为决定逻辑片0~7是否为端点,置0时无影响,表明该逻辑片不是端点,置1时,表明该逻辑片是端点,当布尔表达式的最小项评估为真时,可触发NVIC中相应的引脚中断。
(2)第8到10位CFG0为指定位逻辑片0的匹配贡献条件,值为0x0时恒定高电平,表明该位逻辑片始终贡献乘积项匹配,值为0x1时粘滞上升沿,值为0x2时粘滞下降沿,值为0x3时粘滞上升或下降沿,值为0x4时高电平,值为0x5时低电平,值为0x6时常数0,值为0x7时事件,非粘滞上升或下降沿。
(3)后续的CFG1~CFG7分别为逻辑片1~7的相应设置,选择的方式与第2项一样。
上述3个寄存器中,PMSRC寄存器和PMCFG寄存器的具体作用可通过下图来分析。
上图即为一个逻辑片(slice), 在LPC824中一共有8个这样的逻辑片(slice0~7)。从最左边来看,IN0~IN7一共8个输入端,其实对应的就是PINTSEL0~PINTSEL7,而具体使用的引脚,则在具体的引脚中断选择寄存器PINTSEL中进行选择,这一点和外部引脚中断是一样的。在该逻辑片中,输入的8个IN(即PINTSEL0~PINTSEL7)只能有一个作为本逻辑片的输入信号,可通过逻辑片源寄存器PMSRC中相应的位来进行选择。如果该逻辑片是第0号逻辑片(slice0),则在PMSRC寄存器中的第8~10位进行选择,如果是其它号的逻辑片,则在PMSRC的其他位进行相应选择。可见,PMSRC寄存器就是用来在逻辑片slice0~slice7中选择每个slice的输入信号(PINTSEL0~PINTSEL7)的。在选择了具体的输入信号之后 ,接下来要选择对该信号进行8种方式中的某1种方式的处理,这就是上图中位于中间位置的那个选择器(MUX)的工作,该MUX受控于模式匹配位逻辑片配置寄存器PMCFG中相关的位。
下面先来看一下8种方式,它们分别是恒定高电平、粘滞上升沿、粘滞下降沿、粘滞上升或下降沿、输入信号直通电平、输入信号取反电平、恒定低电平、非粘滞上升或下降沿等8种。这8种信号对该逻辑片最终参与的贡献是不同的,当信号为恒高电平时,该逻辑片永远贡献1,即在后面参与“与”逻辑时恒为真,所以相当于没用,可以理解为没有本逻辑片参与模式匹配。当信号为粘滞上升沿时,该逻辑片在刚才PMSRC选择了的输入信号上(某个PINTSEL上)检测到上升沿时,才贡献1,否则贡献0,而且是带粘滞的,即使检测之后输入信号发生了变化,贡献值仍然不变,直到给寄存器清零。当信号为粘滞下降沿时,该逻辑片在刚才PMSRC选择了的输入信号上(某个PINTSEL上)检测到下降沿时,才贡献1,否则贡献0,同样是带粘滞的,即使检测之后输入信号发生了变化,贡献值仍然不变,直到给寄存器清零。当信号为粘滞上升或下降沿时,该逻辑片在刚才PMSRC选择了的输入信号上(某个PINTSEL上)检测到边沿(上升或下降)时,才贡献1,否则贡献0,同样是带粘滞的,即使检测之后输入信号发生了变化,贡献值仍然不变,直到给寄存器清零。当信号为输入直通电平时,该逻辑片把刚才PMSRC选择了的输入信号(某个PINTSEL)直接连接过来参与贡献,输入为高电平时贡献1,为低电平时贡献0。当信号为输入取反电平时,该逻辑片把刚才PMSRC选择了的输入信号(某个PINTSEL)取反后再连接过来参与贡献,输入为高电平时贡献0,为低电平时贡献1。当信号为恒定低电平时,该逻辑片永远贡献0,即在后面参与“与”逻辑时恒为假,所以相当于把该逻辑片参与的最小项值置0,可以理解为禁用了本逻辑片及与其相关的所有乘积项。当信号为非粘滞上升或下降沿时,该逻辑片在刚才PMSRC选择了的输入信号上(某个PINTSEL上)检测到边沿(上升或下降)时,才贡献1,否则贡献0,但是为非粘滞的,即在输入信号变化时,贡献值就会改变。
接下来继续看上图中的右边部分,从MUX选择出来的信号和来自前一级的逻辑片输出信号一起进入一个“与门”电路,相与之后的输出信号分为两路,一路和PMCFG寄存器中设置的是否为端点的位(PMCFG的0:7位)进入一个“或门”相或,然后输出到下一级的逻辑片。另一路则和PMCFG寄存器中设置的是否为端点的位进入到一个“与门”相与,然后申请本部分最小项的NVIC引脚中断,一般只在最小项的最后一个乘积项中来触发中断。
上述逻辑片在LPC824中一共有8个,把它们的输入端并接在一起,输出端级连串接起来,就形成了8位输入的模式匹配方式,如下图所示。
在上图中,输入端可通过PINTSEL寄存器来在PIO0_0到PIO0_28中任意选取8根引脚作为布尔表达式的输入量,输出端可申请PIN_INT0到PIN_INT7的8个NVIC外部引脚中断,用来表征最多8个最小项的“条件为真”。对于第0号逻辑片(slice0),其中的来至前一级的输入值应设置为1。另外,在所有的输量进行模式匹配后,条件为真时还可触发CPU的RXEV事件,并可把该事件输出至GPIO_INT_BMAT引脚上。
接下来通过一个布尔表达式来具体讨论一下模式匹配模块的工作机制。
假设有一个布尔表达式为:(A) + (A * B) + (~B * ~C * Efe) + (D * Fev)
上式中的A表示输入变量1,~B表示输入变量2取反,Efe表示输入变量5上的粘滞下降沿,Fev表示输入变量6上的非粘滞上升或下降沿。每一个大写字母表示一位输入变量(同名的要重复计),所以上述布尔表达式一共有8个输入变量,表征的是4个最小项的和,A为第1个最小项, (A * B)为第2个最小项, (~B * ~C * Efe)为第3个最小项,(D * Fev)为第4个最小项。
为了方便识别记忆,这里把上述输入变量再重新描述一下:A、B、C、D、E、F共6个输入变量分别接逻辑片(slice)的IN0、IN1、IN2、IN3、IN4、IN5共6个输入端,这6个输入端又分别接PIO0_1、PIO0_2、PIO0_3、PIO0_4、PIO0_5、PIO0_6共6根引脚。第1个最小项(A)引用第0号逻辑片(slice0)进行处理,第2个最小项(A*B)引用第1号逻辑片(slice1)和第2号逻辑片(slice2)进行处理,第3个最小项(~B * ~C * Efe)引用第3号逻辑片(slice3)、第4号逻辑片(slice4)和第5号逻辑片(slice5)进行处理,第4 个最小项(D * Fev)引用第6号逻辑片(slice6)和第7号逻辑片(slice7)进行处理。一共引入了8块逻辑片来处理6个输入变量,其中有两片逻辑片使用了相同的输入端及引脚。
根据以上描述进行连接,把slice0用于第1个最小项(A)作为信号直通输入,并在PMCFG寄存器中设置为端点(PMCFG第0位置1),以便在该最小项条件为真时申请PIN_INT0的NVIC引脚中断。把slice1和slice2用于第2个最小项(A * B)作为两个信号直通输入,并在PMCFG寄存器中设置slice2为端点(PMCFG第2位置1),以便在该最小项条件为真时申请PIN_INT2的NVIC引脚中断。把第slice3~slice5用于第3个最小项(~B * ~C * Efe)作为三个信号的输入,其中的slice3、slice4为输入电平取反,slice5为粘滞下降沿,并在PMCFG寄存器中设置slice5为端点(PMCFG第5位置1),以便在该最小项条件为真时申请PIN_INT5的NVIC引脚中断。把slice6和slice7用于第4个最小项(D * Fev)作为两个输入,其中slice6为信号直通输入,slice7为非粘滞上升或下降沿,本来还应该在PMCFG寄存器中设置slice7为端点,但默认slice7就为最后一个端点,所以默认它在最小项条件为真时就能申请PIN_INT7的NVIC引脚中断。
从以上连接中可以看出,同一个输入变量分布在不同的最小项中时,都要占用不同的逻辑片。上述的6个输入变量却占用了8个slice(多出了1个A和1个~B)。实际使用时,这6个输入变量通过PINTSEL寄存器分别分配到PIO0_1~PIO0_6共6根引脚上去。
把上述布尔表达式与上图中的电路接合起来看,就能明白为什么要这样接了。
先看第1个最小项(A),要把PIO0_1引脚用作变量A的输入,则先需要配置PINTSEL0寄存器,把PIO0_1引脚挂接到PINTSEL0上去(执行LPC_SYSCON->PINTSEL0 = 0x1;)。然后,再通过配置PMSCR寄存器选取slice0的IN0(对应PINTSEL0)作为输入端(执行LPC_PIN_INT->PMSRC &= ~(7<<8);)。接下来配置PMCFG寄存器把slice0的IN0的信号设置为输入信号直通(先执行LPC_PIN_INT->PMCFG &= ~(7<<8);,再执行LPC_PIN_INT->PMCFG |= (4<<8);)。最后,再配置PMCFG寄存器把slice0设置为端点(执行LPC_PIN_INT->PMCFG |= 0x01;)。这样,slice0作为第1个最小项(A)的输入端就配置好了。这里需要说明一下,本来还应该输入一个“来自前一级的slice输出信号”,但由于本级slice已是第0号,所以默认来自前一级的slice输出信号就已经为1了,这样一来,经过“与门”时,有一个输入端始终为1,所以输入信号IN0就相当于直通了,而通过“与门”后的IN0信号,再和被配置为端点的1相与,若IN0为高电平(条件为真)则相与后为高电平,直接申请了PIN_INT0的NVIC引脚中断,若IN0条件为假则不申请中断。即当第1项最小项为真(引脚POI0_1为高电平 )时,可申请中断。
接下来看第2个最小项(A * B),要把PIO0_2引脚用作变量B的输入(PIO0_1引脚仍为变量A的输入),则先需要配置PINTSEL1寄存器,把PIO0_2引脚挂接到PINTSEL1上去(执行LPC_SYSCON->PINTSEL1 = 0x2;)。然后,再通过配置PMSCR寄存器先选取slice1的IN0(对应PINTSEL0)作为输入端(执行LPC_PIN_INT->PMSRC &= ~(7<<11);),再选取slice2的IN1(对应PINTSEL1)作为输入端(先执行LPC_PIN_INT->PMSRC &= ~(7<<14);再执行LPC_PIN_INT->PMSRC |= (1<<14);)。接下来配置PMCFG寄存器把第1号逻辑片的IN0的信号设置为输入信号直通(先执行LPC_PIN_INT->PMCFG &= ~(7<<11);,再执行LPC_PIN_INT->PMCFG |= (4<<11);),把slice2的IN1的信号设置为输入信号直通(先执行LPC_PIN_INT->PMCFG &= ~(7<<14);,再执行LPC_PIN_INT->PMCFG |= (4<<14);)。最后,再配置PMCFG寄存器把slice2设置为端点(执行LPC_PIN_INT->PMCFG |= 0x04;)。这样,slice1作为第2 个最小项中的变量A的输入端,slice2作为变量B的输入端就配置好了。
接下来讨论一下通过以上连接是如何实现(A*B)的最小项的。为了方便讨论,这里把前面的那个最小项A也纳入进来一并讨论,也就是说讨论一下布尔表达式的前半部分,即:(A) + (A * B)具体是如何实现的。
首先,slice0用作第1个最小项(A)的匹配逻辑,由于本级slice0设置为了端点,所以在IN0的最终输出值和端点值1进行了“相或”的操作(见上面的连接图),然后输出一个信号至下一级逻辑片(即slice1)。由于有端点值1的参与,所以“相或”的结果恒为1。
接下来,slice1用作第2个最小项中变量A的匹配逻辑,在经过检测逻辑之后,输出的信号与前一级输入的信号进行“与”操作,由于刚才slice0过来的信号值恒为1,所以“相与”的操作相当于“直通”。又由于本级slice1并没有设置为端点,即端点值为0,所以刚才“直通”过来的信号又和端点值0进行“或”操作,和值0“相或”又相当于“直通”,所以直通的信号输出一个信号(即变量A)至下一级逻辑片(即slice2)。
然后,slice2用作第2个最小项中变量B的匹配逻辑,在经过检测逻辑之后,输出的信号与前一级输入的信号进行“与”操作,由于前一级的输出信号即为slice1中的变量A,而且无论是在前一级slice1中还是在本级slice2中,检测逻辑都设置为“输入直通电平”(即在PMCFG中CFG1和CFG2都配置为0x4),所以本级(slice2)的信号与前一级(slice1)输入的信号进行“与”操作,即就实现了A*B的操作了。同样,由于本级slice2设置为了端点,相“与”后的输出信号又与本级设置的端点值1进行了“相或”操作,然后又输出至下一级(即slice3),显然输出的结果恒为1。同时,A*B之后也和端点值1进行“相与”的操作,然后申请PIN_INT2的NVIC中断,即第2个最小项A*B值为真时,可触发本中断。
通过上述分析可知,在实现(A) + (A *B)表达式时,真正使用了“与门”来实现“*”运算,但并没有使用或门来真正的参与“+”运算。 这是因为在表达式(A) + (A * B)中,只要2个最小项中的1个条件为真,整个表达式的值即为真,而每个最小项为真时都有相应的NVIC中断被触发,所以只需要检测中断情况就可以确定整个表达式的值了。比如两个中断都没被触发,则表达式的值为假,只要有1个中断被触发了,表达式的值即为真,因此没有必要进行真正的“+”运算。在参与每个最小项的最后一个逻辑片(即被设置为端点的slice)中,输出至下一级的信号电平始终为1,这不仅标志着本最小项的结束,也标志着下一级slice是下一个最小项的开始。
明白了上述原理之后,接着再来看第3个最小项(~B * ~C * Efe)。要把PIO0_3引脚用作变量C的输入、PIO0_5引脚用作变量Efe的输入(PIO0_2引脚仍为变量B的输入),则先需要配置PINTSEL2、PINTSEL4寄存器,把PIO0_3引脚挂接到PINTSEL2上去(执行LPC_SYSCON->PINTSEL2= 0x3;),把PIO0_5引脚挂接到PINTSEL4上去(执行LPC_SYSCON->PINTSEL4 = 0x5;)。然后,再通过配置PMSCR寄存器先选取slice3的IN1(对应PINTSEL1)作为输入端(先执行LPC_PIN_INT->PMSRC &= ~(7<<17);再执行LPC_PIN_INT->PMSRC |= (1<<17);),再选取slice4的IN2(对应PINTSEL2)作为输入端(先执行LPC_PIN_INT->PMSRC &= ~(7<<20);再执行LPC_PIN_INT->PMSRC |= (2<<20);),最后选取slice5的IN4(对应PINTSEL4)作为输入端(先执行LPC_PIN_INT->PMSRC &= ~(7<<23);再执行LPC_PIN_INT->PMSRC |= (4<<23);)。接下来配置PMCFG寄存器把slice3的IN1的信号设置为输入信号取反(先执行LPC_PIN_INT->PMCFG &= ~(7<<17);,再执行LPC_PIN_INT->PMCFG |= (5<<17);),把slice4的IN2的信号设置为输入信号取反(先执行LPC_PIN_INT->PMCFG &= ~(7<<20);,再执行LPC_PIN_INT->PMCFG |= (5<<20);),把slice5的IN4的信号设置为粘滞下降沿(先执行LPC_PIN_INT->PMCFG &= ~(7<<23);,再执行LPC_PIN_INT->PMCFG |= (7<<23);)。最后,再配置PMCFG寄存器把slice5设置为端点(执行LPC_PIN_INT->PMCFG |= 0x20;)。这样,slice3作为第3个最小项中的变量~B的输入端,slice4作为变量~C的输入端,slice5作为变量Efe的输入端就配置完成了。
最后来看第4个最小项(D * Fev)。要把PIO0_4引脚用作变量D的输入、PIO0_6引脚用作变量Fev的输入 ,则先需要配置PINTSEL3、PINTSEL5寄存器,把PIO0_4引脚挂接到PINTSEL3上去(执行LPC_SYSCON->PINTSEL3= 0x4;),把PIO0_6引脚挂接到PINTSEL5上去(执行LPC_SYSCON->PINTSEL5 = 0x6;)。然后,再通过配置PMSCR寄存器先选取slice6的IN3(对应PINTSEL3)作为输入端(先执行LPC_PIN_INT->PMSRC &= ~(7<<26);再执行LPC_PIN_INT->PMSRC |= (3<<26);),选取slice7的IN5(对应PINTSEL5)作为输入端(先执行LPC_PIN_INT->PMSRC &= ~(7<<29);再执行LPC_PIN_INT->PMSRC |= (5<<29);)。接下来配置PMCFG寄存器把slice6的IN3的信号设置为输入信号直通(先执行LPC_PIN_INT->PMCFG &= ~(7<<26);,再执行LPC_PIN_INT->PMCFG |= (4<<26);),把slice7的IN5的信号设置为非粘滞上升或下降沿(先执行LPC_PIN_INT->PMCFG &= ~(7<<29);,再执行LPC_PIN_INT->PMCFG |= (2<<29);)。最后要注意,slice7默认就为端点,不能再写PMCFG寄存器的第7位。这样,slice6作为第4个最小项中的变量D的输入端,slice7作为变量Fev的输入端就配置完成了。
当某些最小项为真时,会触发相对应的NVIC外部引脚中断,要清零这些中断标记(即清零模式匹配引擎检测逻辑),可将任意数值写入PMCFG寄存器或PMSRC寄存器,或者禁用模式匹配特性(清零PMCTRL寄存器中的SEL_PMATCH和ENA_RXEV位),这会清除所有边沿检测历史数据。此外,当整个布尔表达式的值为真时,可以选择去触发ARM内核的RXEV接收事件,RXEV输出事件可路由至GPIO_INT_BMAT引脚,通过配置PINASSIGN11寄存器把GPIO_INT_BMAT连接到任意一根引脚。下表给出了PINASSIGN11寄存器的全部位结构,其中的第24~31位即为GPIO_INT_BMAT分配物理引脚的选择位,可在PIO0_0~PIO0_28中选取某一根做为输出引脚。