引言
在嵌入式系统中,按键和键盘是一种基本和常用的接口,它是构成人机对话通道的一种常用方式。按键和键盘能实现向嵌入式系统输入数据、传输命令等功能,是人工干预、设置和控制系统运行的主要手段。众所周知,传统的按键接口程序采用软件延时来检测按键输入,这种算法不仅使MCU的效率大大降低,而且也不容易同系统中其他功能模块协调工作,系统的实时性极差。除非当前系统搭载了实时操作系统,比如μC/OSII,一旦当前任务要进行延时操作,系统就会自动进行任务调度,执行其他任务,当之前的任务延时完毕,系统会自动执行之前的任务。遗憾的是单片机系统资源有限,不适合搭载μC/OSII实时操作系统,另外μC/OSII用在商业上是要收费的,这又间接增加了系统开发的成本。因此本文针对以上不足,在不搭载实时操作系统的前提下提出一种新的算法来编写键盘和按键接口的处理程序。实验表明该算法能够极大地提高CPU的利用率,在实际产品设计中具有一定的实用性。
1 研究背景
嵌入式系统开发过程中,无论是针对低端的8051系列、AVR系列8位单片机,还是TI的MSP430系列16位单片机,亦或是更为高端的ARM、DSP,总是少不了通过按键完成人与微处理器直接的对话工作,即所谓的“人机交互”。 由于按键的开关为机械弹性触点开关,加上人们按键时的力度、方向的不同,按键开关从按下到接触稳定要经过数ms级的弹跳抖动,即在按下的几十ms时间里,会连续产生多个脉冲。释放按键时,电路也不会一下断开,同样会产生抖动。因此,为了确保MCU对一次按键动作只确认一次,在保证系统具有较好实时性的前提下,必须要进行消抖处理。
为了解决以上问题,人们进行了大量的研究,在硬件和软件方面都下了不少功夫,在硬件方面有人提出采用RS触发器或RC积分电路,但是在硬件设计上增加了系统的成本;在软件方面有人提出采用中断输入按键接口,的确能够提高系统实时性,但是不能解决消抖问题,还是会出现输入控制不稳定的现象。因此,在考虑节约成本的情况下,提出一种新的软件算法来解决上述问题具有实际意义。
2 研究方法
2.1 有限状态机分析设计的基本原理
有限状态机(FSM)是实时系统设计中的一种数学模型,是一种重要的、易于建立的、应用比较广泛的、以描述控制特性为主的建模方法,它可以应用于从系统分析到设计(包括硬件、软件)的所有阶段。
很多实时系统,特别是实时控制系统,其整个系统的分析机制和功能与系统的状态有相当大的关系。有限状态机由有限的状态和相互之间的状态转移构成,在任何时候只能处于给定数目的状态中的一个。当接收到一个输入事件时,状态机产生一个输出,同时也可能伴随着状态的转移。 一个有限状态机在数学上可以描述如下:
① 一个有限系统状态的集合为:
其中:i=1,2,3,…,q。该式表明,系统可能存在的状态有q个,而在tk时刻,系统的状态为其中之一Si(唯一性)。
② 一个有限系统输入信号的集合为:
其中:j=1,2,3,…,m,表示系统共有m个输入信号。该式表示在tk时刻,系统的输入信号为输入集合的全集或子集(集合性)。
③ 一个状态转移函数F为:
状态转移函数也是一个状态函数,它表示对于tk时刻,系统在某一状态Si下,相对给定输入Ij后,FSM转入该函数产生的新状态。这个新状态就是系统在下一时刻的状态,也是唯一确定的(唯一性)。
④ 一个有限的输出信号集合为:
其中:l=1,2,3,…,n,表示系统共有n个输入信号。该式表示在tk时刻,系统的状态为Si时,其输出信号为输出集合的全集或子集(集合性)。这里需要注意的是,系统的输出只与系统所处的状态有关。
⑤ 时间序列为:
在状态机中,时间序列是非常重要的一个因素,从硬件的角度看,时间序列如同一个触发脉冲序列或同步信号;而从软件的角度看,时间序列就是一个定时器。状态机由时间序列同步触发,定时检测输入,根据当前的状态输出相应的信号,并确定下一次系统状态的转移。在时间序列进入下一次触发时,系统的状态将根据前一次的状态和输入情况发生状态的转移。其次,时间序列可能是一个系统的输入信号,影响到状态的改变,进而影响到系统的输出。所以对于时间序列,正确分析和考虑选择合适的时间段的间隔也是非常重要的。若间隔太短,对系统的速度、频率响应要求高,并且可能降低系统的效率;若间隔太长,系统的实时性差,响应慢,还有可能造成外部输入信号的丢失。一般情况下,时间序列的时间间隔的选取,应稍微小于外部输入信号中变化最快的周期值。
通常主要有两种方法来建立有限状态机,一种是“状态转移图”,另一种是“状态转移表”,分别用图形方式和表格方式建立有限状态机。实时系统经常会应用在比较大型的系统中,这时采用图形或表格方式对理解复杂的系统具有很大的帮助。总的来说,有限状态机的优点在于简单易用,状态间的关系能够直观看到。应用在实时系统中时,便于对复杂系统进行分析。
2.2 基于有限状态机分析的按键检测程序设计
图1 按键状态机转换图
图1给出了一个简单按键状态机的状态转换图。在图1中,将一次按键完整的操作过程分解为3个状态,时间序列周期为10 ms,即每隔10 ms检测一次按键的输入信号,并输出一次按键的确认信号,同时按键的状态也发生一次转换。 图1中“状态0”为按键的初始状态,当按键输入为“1”时,表示按键处于开放状态,输出“0”(1/0),下一状态仍旧为“状态0”。当按键输入为“0”时,表示按键闭合,但输出还是“0”(0/0)(没有经过消抖,不能确认按键真正按下),下一状态进入“状态1”。
“状态1”为按键闭合确认状态,它表示在10 ms前按键为闭合状态,因此当再次检测到按键输入为“0”时,可以确认按键被按下了(经过10 ms的消抖),输出“1”表示确认按键闭合(0/1),下一状态进入“状态2”。而当再次检测到按键的输入为“1”时,表示按键可能处在抖动干扰状态,输出为“0”(1/0),下一状态返回到“状态0”。
这样,利用状态1,实现了按键的消抖处理。“状态2”为等待按键释放状态,因为只有等按键释放后,一次完整的按键操作过程才算完成。 从对图1的分析中可以知道,在一次按键操作的整个过程中,按键的状态是从“状态0”→“状态1”→“状态2”,最后返回到“状态0”的。并且在整个过程中,按键的输出信号仅在“状态1”时给出了唯一的一次确认按键闭合状态的信号“1”(其他状态均输出“0”)。所以,上面状态机所表示的按键系统,不仅解决了按键抖动的问题,而且也确保在一次按键整个的过程中,系统只输出一次按键闭合信号(“1”)。
一旦有了正确的状态转换图,就可以根据状态转换图编写软件了。在软件中状态机的程序结构,通常使用多分支结构(IFELSEIFELSE、CASE等)实现。基于状态机方式编写的简单按键接口函数如下:
advanced_read_key(void)
#define key_input PIND.7//按键输入口
#define key_state_0 0
#define key_state_1 1
#define key_state_2 2
char advanced_read_key(void) {
static char key_state = 0;
char key_press, key_return = 0;
key_press = key_input; //读按键I/O电平
switch (key_state){
case key_state_0: //按键初始态
if (!key_press) key_state = key_state_1; //键被按下,状态转换到按键确认态
break;
case key_state_1: //按键确认态
if (!key_press){
key_return = 1;//按键仍按下,按键确认输出为“1”
key_state = key_state_2; //状态转换到按键释放态
}
else
key_state = key_state_0; //按键已抬起,转换到按键初始态
break;
case key_state_2:
if (key_press) key_state = key_state_0;//按键已释放,转换到按键初始态
break;
}
return key_return;
}
…
3 实验结果与分析
在Keil uVision3开发坏境中提供了性能分析工具,利用该工具,可以了解程序中哪部分的执行时间最长,调用次数最多,从而了解影响整个程序中执行的瓶颈。下面将传统按键检测算法和改进的基于FSM状态机算法的CPU占用率情况通过性能分析窗口给予展示,如图2、图3所示。
图2 传统按键检测算法的CPU性能分析图
图3 FSM改进算法的CPU性能分析图
结语
可以看出传统算法的CPU占用率为100%,即CPU一直在按键检测子程序中“死等”,CPU根本无法做其他事,而改进算法中CPU占用率仅为0.4%,在完成按键检测的同时,CPU大多时间都在做其他有用的工作。由此可知,改进设计在按键检测过程中具有很好的实用价值。