打印

有那位大侠能说说中断扫描键盘和主程序扫描键盘的优缺点

[复制链接]
楼主: jack.king
手机看帖
扫描二维码
随时随地手机跟帖
41
jack.king|  楼主 | 2008-5-22 09:39 | 只看该作者 回帖奖励 |倒序浏览

ayb_ice具体说说让我们也感受下O耗时

使用特权

评论回复
42
machunshui| | 2008-5-22 10:03 | 只看该作者

hotpower的0耗时,没看过

hotpower的0耗时,没看过。
不知道是个啥概念。

但估计十之**是在消抖延时上做**,消抖延时在定时器里定时,完成后再进行键盘值的确定,除此外,想不出,还有什么地方可以接见时间。

如果是这种思路的话,主程序执行键盘扫描,一样可以0耗时.

使用特权

评论回复
43
农民讲习所| | 2008-5-22 10:08 | 只看该作者

最经典的是俺那个KEY模块

完全封装,做成标准库。
外部只提供和硬件有关的扫描标志数组。
可每个KEY单独识别PUSH、DOWN、DOUBLECLICK、LONGCLICK,强大啊。

使用特权

评论回复
44
computer00| | 2008-5-22 10:18 | 只看该作者

如果中断没有什么特殊要求,我肯定是放定时器中断里

这样可以保证扫描的周期性,尤其是对数码管。

如果放在主程序中,很难保证其周期性,处理事务是还要瞻前顾后,有点麻烦。

放在定时器中断中,很容易做到扫描的周期性,同时也很容易实现所长所说的“PUSH、DOWN、DOUBLECLICK、LONGCLICK”,
以及自动连击等。甚至还可以用一个队列保存按键,这样在主程序任务时间长时,按键也能很好的响应。
如果扫描放在主程序中,做这些就有难度了。

使用特权

评论回复
45
machunshui| | 2008-5-22 10:24 | 只看该作者

所长,技痒难忍啊

所长技痒难忍啊。

实现每个KEY单独识别PUSH、DOWN、DOUBLECLICK、LONGCLICK,
工作量很大啊。

使用特权

评论回复
46
fsaok| | 2008-5-22 10:28 | 只看该作者

我和00一样的习惯

当然,这和最早的电路是使用键盘扫描IO和LED复用有关。

我是一直使用键盘0耗时,这和我笨有关,我最早的时候,对着书本就是没学会写去抖程序。

其实,0耗时是检测keydown事件,然后把去抖放在检测keyup事件,如此而已。没什么神秘

扫描程序和键盘处理程序分开,那扫描放中断还是放主程序的问题就基本可以终结。

使用特权

评论回复
47
汽车电子| | 2008-5-22 10:30 | 只看该作者

一直在主程序里扫描键盘

    去抖也很方便的
    任何按键都有三个动作,即三个属性:按下、按住、弹起
    按下和弹起是瞬间的,按住可以任意时长...

使用特权

评论回复
48
machunshui| | 2008-5-22 10:30 | 只看该作者

为每个键,设置一个队列

为每个键,设置一个队列,记录每个键的动作,和持续的时间,
对队列进行分析,
各种键的状态,应该在主程序里面实现PUSH、DOWN、DOUBLECLICK、LONGCLICK,
问题不大吧?

使用特权

评论回复
49
HWM| | 2008-5-22 10:31 | 只看该作者

呵呵,一般我都会单独给键盘和显示单独配一个大脑——MCU

使用特权

评论回复
50
dld2| | 2008-5-22 10:32 | 只看该作者

21ic应该提供投票箱

使用特权

评论回复
51
computer00| | 2008-5-22 11:00 | 只看该作者

呵呵,发一段我在EDN 51板上做的键盘扫描的代码

具有按键按下、按住、弹起等功能。在这个代码的基础上,再增加几条语句,就可以实现长按、按键连发等功能。



/********************************************************************
函数功能:定时器0中断处理。
入口参数:约5ms中断一次。
返    回:无。
备    注:无。
********************************************************************/
void Timer0Isr(void) interrupt 1
{
 uint8 temp;
 
#ifndef LCD
 static uint8 LedDigit=0;
#endif
 
 TH0=(65536-Fclk/1000/12*5+22)/256;     //定时器0重装
 TL0=(65536-Fclk/1000/12*5+22)%256;
 
 SystemTick++;
 
#ifndef LCD  //没有定义LCD,则使用数码管显示
 switch(LedDigit)
 {
  case 0:
   LED_3=1;
   LED_SEG=Table[LedBuffer][0]];
   LED_0=0;
   LedDigit=1;
  break;
  case 1:
   LED_0=1;
   LED_SEG=Table[LedBuffer][1]];
   LED_1=0;
   LedDigit=2;
  break;
  case 2:
   LED_1=1;
   LED_SEG=Table[LedBuffer][2]];
   LED_2=0;
   LedDigit=3;
  break;
  case 3:
   LED_2=1;
   LED_SEG=Table[LedBuffer][3]];
   LED_3=0;
   LedDigit=0;
  break;
  default:
   LedDigit=0;
  break;
 }
#endif

 if(!KeyCanChange)return;     //如果正在处理按键,则不再扫描键盘 

 //开始键盘扫描
 //保存按键状态到当前按键情况
 //KeyCurrent总共有8个bit
 //当某个开关按下时,对应的bit为1
 
 P2_4=0; //扫描第一列
 temp=~KeyIO;
 P2_4=1;
 temp&=0x0F;
 P2_5=0; //扫描第二列
 KeyCurrent=~KeyIO;    
 P2_5=1;
 
 KeyCurrent<<=4;
 KeyCurrent|=temp;
 
 if(KeyCurrent!=KeyOld)  //说明按键情况发生了改变
  {
   KeyNoChangedTime=0;       //键盘按下时间为0
   KeyOld=KeyCurrent;        //保存当前按键情况
   return;
  }
 else
  {
   KeyNoChangedTime++;         //按下时间累计
   if(KeyNoChangedTime>=1)     //如果按下时间足够
    {
     KeyNoChangedTime=1;
     KeyPress=KeyOld;      //保存按键
     KeyDown|=(~KeyLast)&(KeyPress); //求出新按下的键
     KeyUp|=KeyLast&(~KeyPress);     //求出新释放的键
     KeyLast=KeyPress;                 //保存当前按键情况
     if(KeyDown)BeepOn(); //按键按下,蜂鸣器响
     else if(KeyUp)BeepOff();
    }
  }
}
/*******************************************************************/

使用特权

评论回复
52
ayb_ice| | 2008-5-22 14:00 | 只看该作者

所谓的0耗时

其实都是在消抖上做**,不要在那里死等,可以间隔调用扫键程序,利用间隔来完成延时,加上们简单的状态机就搞定了,很容易实现短按,长按,连按,组组合等功能。。。

使用特权

评论回复
53
gyt| | 2008-5-22 14:53 | 只看该作者

谢谢00分享程序!

使用特权

评论回复
54
jack.king|  楼主 | 2008-5-22 15:22 | 只看该作者

谢谢!让我有进步了

使用特权

评论回复
55
machunshui| | 2008-5-22 16:48 | 只看该作者

主程序扫描,定时器延时消抖

void ScanKey()
{
unsigned char tempKeyValue ;

if(keyScanState == 0)//预先扫描键盘
{
if(RA0 == 0 || RA1 == 0)
keyScanState = 1;//启动消抖延时
}
else if(keyScanState == 2)//延时完毕,确认扫描键盘
{
 tempKeyValue = 0;
 TRISA2 = 0;
 TRISA3 = 1;
 RA2 = 0;
 asm("nop"); asm("nop"); asm("nop");

if(RA0 == 0)  tempKeyValue = 4;
else if(RA1 == 0) {tempKeyValue = 1; }
else ;

TRISA2 = 1;
TRISA3 = 0;
RA3 = 0;
asm("nop"); asm("nop"); asm("nop");

if(RA0 == 0) tempKeyValue = 3; 
else if(RA1 == 0) {tempKeyValue = 2; }
else ;

if(tempKeyValue != 0)//扫描到键盘值,存到缓冲队列
{
if(keyDataLen < MAN_KEYBUF_LEN)
{
keyBuf[keyBufHead] = tempKeyValue;
keyDataLen++;
if(keyBufHead == MAN_KEYBUF_LEN-1)
keyBufHead = 0;
else
keyBufHead++;
}
}

TRISA2 = 0;
TRISA3 = 0;
RA2 = 0;
RA3 = 0;
keyScanState = 0;//恢复预先扫描状态
}
else
;
}

void interrupt ISR(void)
{
if(T0IE && T0IF) {//判TMR0 中断,8m

if(keyScanState == 1)
{
if(KeyDelayTime < 4)
KeyDelayTime++;
else
{
keyScanState = 2;
KeyDelayTime = 0;
}
}

TMR0 = 5;
T0IF = 0;}
}

如果检测键弹起事件,存入缓冲队列,实现长击,连击,也非难事

使用特权

评论回复
56
WIMHY| | 2008-5-22 16:52 | 只看该作者

建议

这个问题建议大家,到书店看看这本书《松翰SN8P2700系列单片机原理及应用技术》
 

使用特权

评论回复
57
hkap| | 2008-5-22 17:05 | 只看该作者

以前也在中断中做过,但现在全部在主程序中扫描,也挺好

使用特权

评论回复
58
int3| | 2008-5-23 14:52 | 只看该作者

我从来不放中断

按键处理在300毫秒内处理完就可以,有必要放中断吗?
还有如果下降沿触发进入中断,硬件电路也要处理。
一般可以不放中断处理的程序,就不放中断。

使用特权

评论回复
59
starlite_jason| | 2008-5-23 16:40 | 只看该作者

支持主程序扫描

我也不赞成放在中断,按键的响应速度要求不高,即使DELAY个几十MS也没有问题,在主程序里做扫描,定时中断只是作为启动扫描的钥匙,扫描不存在等待延时,同样也是零耗时

使用特权

评论回复
60
cheungman| | 2008-5-23 17:30 | 只看该作者

张教主blog上的关于键盘的程序,也许就可以解决这个问题了

//一个单片机系统的设计经常会用到多种不同目的和用图的定时,例如系统需要输出
//一个指示“心跳正常”的秒闪信号,间隔0.5s;按键检测时临时需要约20ms的消抖;
//蜂鸣器需要发声延时;用户菜单选择时可能需要对应的发光管或LCD点阵(字段)
//闪烁;通讯时需要设定应答超时判别,等等。是不是要抱怨一个单片机上的若干个
//定时器不够用了?其实,用一个定时器资源就可以搞定所有的这一切定时要求。

//1)首先,选定一个你喜欢的定时器,按所需应用的定时精度要求设定其定时中断频
//率。一般人机界面的定时精度为ms级就足够了,所以可以设定定时中断时间间隔为
//1ms,5ms或10ms;例如我的选择:

//==============================================================
// TPM2 overflow interrupt service routine
// Interrupt at every 1ms
//==============================================================
void interrupt 14 TPM2_Overflow_ISR(void)
{
    TPM2SC_TOF = 0;     //reset interrupt flag
    msTimeoutCount++;     //1ms increment
}

//变量msTimeoutCount是一个16位word型的静态变量,在中断服务程序中简单地对它
//递增,无需考虑溢出。如果你的中断时间间隔为Nms,则在中断中对其递增的方法为
//“msTimeoutCount += N”。它在程序模块的前面被声明,为了提高中断服务程序的效
//率,其被定位在直接寻址区:

//==============================================================
// Following data are declared in the direct addressing area
// for fast access (address < 0x100)
//==============================================================
#pragma DATA_SEG SHORT MY_ZEROPAGE //direct addressing data segment
volatile word msTimeoutCount;

//然后写一段独立的定时判别函数。这个函数有两个入口参数:特定定时实例的一个
//定时变量指针和所需的定时时间长度。若定时时间长度为0,则定时过程被复位,实
//际上是当前的定时计数器值(msTimeoutCount)被复制到定时实例的一个定时变量
//中。返回值为0则表明定时时间未到,0xff则为一次定时时间到并自动开始下一次的
//定时过程。具体代码如下:

//==============================================================
// Check for timeout occurance
// Input *timer - pointer of timer counter
// timeOutVal - timeout value, 0=forced timer update
// Return 0 - no timeout yet
// 0xff - timeout occured and timer updated
//==============================================================
byte TimeOutChk(word *timer, word timeOutVal)
{
    word shadow, diff;

    TPM2SC_TOIE = 0; //针对8位机必须禁止定时中断,16位机以上则无需如此
    shadow = msTimeoutCount; //将当前时间计数值复制一份以作后需
    TPM2SC_TOIE = 1; //对应上面的中断禁止,现在开放中断

    if (timeOutVal==0) 
    {    //复位定时过程
        *timer = shadow;
        return(0);
    }
    else 
    {
        diff = shadow - *timer; //计算定时时间间隔
        if (diff>=timeOutVal) 
        {     //定时时间到
            *timer += timeOutVal; //更新定时变量,开始下一次定时过程
            return(0xff);// 返回时间到标志
        } 
        else 
        {
            return(0); //定时时间未到
        }
    }
}


//剩下的就看具体应用的需要而开辟特定的定时实例了。每一个实例必须有一个word
//型的变量作为定时跟踪变量。

//例如产生500ms的定时(msCount变量在模块前面已经定义):

void main(void)
{

    ...

    TimeOutChk(&msCount, 0); //复位初始化定时实例

    ...

    while(1) 
    {

        Clock();

        KeyScan();

        ...

    }

}

//==============================================================
// Keep the system clock running
//==============================================================
void Clock(void)
{
    if (TimeOutChk(&msCount, 500)==0) 
        return; //wait for 0.5 second time out

    runFlag.halfSec = !runFlag.halfSec;
    dispCodeBuff[2] ^= 0x80;
    dispCodeBuff[3] ^= 0x80;

    if (runFlag.halfSec) 
    {
        return;
    }

    second++;
    if (second==30) 
    {     //sync soft clock with RTC value
        RTC_Read();
    }
    if (second>59) 
    {
        second = 0;
        minute++;
        if (minute>59) 
        {
            minute = 0;
            hour++;
            if (hour>23) 
                hour = 0;
        }
    }

    runFlag.clkDisp = 1;
}


//按键扫描时的消抖延时实现, keyDebounce在模块前面为局部静态变量定义

//==============================================================
// Scaning key input
//==============================================================
void KeyScan(void)
{
    byte keyInput;

    keyInput = (PTFD^0xff) & 0b00011111;

    switch (keyState) 
    {
        case 0: //idle
            if (keyInput) 
            {     //possible key input detected
                runFlag.keyCon = 0; //continuous key strike not allowed by default
                TimeOutChk(&keyDebounce, 0); //reset debounce timer
                keyState = 1;
            }
            break;
        case 1: //down debouncing
            if (TimeOutChk(&keyDebounce, 50)) 
            {     //50ms debounce timeout
                if (keyInput) 
                {
                    KeyFifoAdd(keyInput);
                    TimeOutChk(&keyDebounce, 0); //!复位定时准备实现按键持续按下时的连续激发功能
                    keyState = 2; //key is holding
                } 
                else 
                {
                    keyState = 0; //debounce check failed
                }
            }
            break;
        case 2: //hold
            if (keyInput==0) 
            {     //possible key release detected
                TimeOutChk(&keyDebounce, 0);
                keyState = 4;
            } 
            else 
            {
                if (runFlag.keyCon) 
                {     //continuous key strike allowed
                    if (TimeOutChk(&keyDebounce, 500)) 
                    {    //持续按下时间达0.5s
                        KeyFifoAdd(keyInput);
                        TimeOutChk(&keyDebounce, 0); //准备后续每隔0.1s激发一个按键值
                        keyState = 3; //invoke key continuous strike
                    }
                }
            }
            break;
        case 3: //continuous strike
            if (keyInput==0) 
            {     //possible key release detected
                TimeOutChk(&keyDebounce, 0);
                keyState = 4;
            } 
            else 
            {
                if (TimeOutChk(&keyDebounce, 100)) 
                {     //每隔0.1s激发一个按键值
                    KeyFifoAdd(keyInput);
                    TimeOutChk(&keyDebounce, 0);
                }
            }
            break;
        case 4: //up debouncing
            if (TimeOutChk(&keyDebounce, 50)) 
            {     //50ms debounce timeout
                if (keyInput) 
                {
                    keyState = 2; //key is still holding
                } 
                else 
                {
                    keyState = 0; //confirm released
                }
            }
            break;
        default:
            keyState = 0;
    }
}

//所以理论上只要你有足够多的内存作为定时跟踪变量,你就可以实现任意多个定时
//实例,无论什么时间和什么地点。当然上面的定时程序有一个局限,就是一次最大
//的定时时间为65535ms。如果要实现更长时间的定时,可以用一个实例产生1s
//(或更长)的定时基准,然后参照函数TimeOutChk另外写一个例如TimeOutChkSec,
//按1s的分辨率最多实现65535s的定时。


//采用状态机实现按键检测是最可靠最有效的方法。同时在单片机设计中实现多任务
//的并发和协调,状态机起着不可或缺的作用。对于按键处理,部分代码如下:
#define KEY_FIFO_LEN 4 
byte keyFifo[KEY_FIFO_LEN], keyPut, keyGet; 
//============================================================== 
// Add a key into FIFO 
//============================================================== 
void KeyFifoAdd(byte code) 

    keyFifo[keyPut++] = code; 
    keyPut &= (KEY_FIFO_LEN-1); 


//============================================================== 
// Fetch a key from FIFO 
//============================================================== 
byte KeyFifoGet(void) 

    byte tmp; 
    tmp = keyFifo[keyGet++]; 
    keyGet &= (KEY_FIFO_LEN-1); 
    return(tmp); 


//============================================================== 
// Do key function for primary task 
//============================================================== 
void KeyFuncMain(void) 

    byte keyCode; 
    if (keyPut==keyGet) 
        return; 
    keyCode = KeyFifoGet(); 
    switch (keyCode) 
    { 
        case KEY_CH1_CTL: 
            RELAY1_CTL = !RELAY1_CTL; 
            if (RELAY1_CTL) 
                dispCodeBuff[4] |= 0x10; 
            else dispCodeBuff[4] &= (0x10^0xff); 
                SetBeep(200); 
            break; 
        case KEY_CH2_CTL: 
            RELAY2_CTL = !RELAY2_CTL; 
            if (RELAY2_CTL) 
                dispCodeBuff[4] |= 0x08; 
            else dispCodeBuff[4] &= (0x08^0xff); 
                SetBeep(200); 
            break; 
        case KEY_SET: 
            SetBeep(50); 
            TimeOutChk(&menuTimeout, 0); 
            menuId = 0; 
            MainTaskEntry = SetupEnable; 
            MenuTaskEntry = ClockSetup; 
            dispCodeBuff[4] = 0x01; 
            break; 
        default: 
            return; 
    } 
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则