打印

凑热闹我也发个使用状态机的键盘程序,支持单键和双键

[复制链接]
15936|71
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
mohanwei|  楼主 | 2010-1-21 16:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1-头文件:KeyBoard.h
#ifndef _Key_Board_h_
    #define _Key_Board_h_

    #define    Key_Up        0x3E    //上箭头
    #define    Key_Down    0x3D    //下箭头
    #define    Key_Add        0x3B    //加
    #define    Key_Sub        0x37    //减
    #define    Key_Enter    0x2F    //回车
    #define    Key_Return    0x1F    //返回
    #define Key_LR        0x33    //左右组合键
    //可以在此定义其它组合键……
   
    #define Key_NULL    0xFF    //无任何按键

    //声明几个需要在外部调用的函数
    extern void Key_Board_Init(void);//键盘接口初始化
    extern u8 Read_Key(void);    //读键函数,返回键盘缓冲区中的键值
    extern void Key_In(void);//处理按键输入函数,在定时器中断里调用

    extern void Key_Board_Test(void);    //键盘接口测试函数(仅在调试阶段使用)

#endif

2-源文件:KeyBoard.c
//#include "includes.h"    //公共头文件
#include "Key_Board.h"

//键盘缓冲区相关常量定义
#define KeyBuffLen 8    //定义键值环形缓冲区长度为8(缓冲区大小可自由定义,只要大于0即可)

//定义一个键盘缓冲区结构体
struct Struct_KeyBoardBuff
{
    u8 buff[KeyBuffLen];//键值环形缓冲区
    u8 in;    //写键值指示(定时器中断写)
    u8 out;    //读键值指示(用户读)
}Key;


/*************************************************************************
函数原型:void Key_Board_Init(void)
函数功能:对键盘接口进行初始化,即把键值缓冲区清零
传入参数:无
返回参数:无
全局变量:直接操作键盘缓冲区结构体
设    计:莫汉伟 amo73@126.com
修改日期:2007-9-5
备    注:仅在上电初始化的时候被调用一次
**************************************************************************/
void Key_Board_Init(void)
{
    memset(&Key,0,sizeof(Key));
}

/*************************************************************************
函数原型:u8 Read_Key(void)
函数功能:从键值缓冲区读取一个键值
传入参数:无
返回参数:u8型按键值,如果缓冲区为空,则返回-1
设    计:莫汉伟 amo73@126.com
修改日期:2007-9-5
备    注:供用户程序调用;用户不用关心键盘底层硬件。
**************************************************************************/
u8 Read_Key(void)
{
    u8 Value;
    if(Key.out != Key.in)
    {
        Value=Key.buff[Key.out++];//"读"还没有追上"写",缓冲区有键值,读之
        if(Key.out >= KeyBuffLen) //如果"读"跑到了队列尾部,则重新跳回原点
        {
            Key.out=0;
        }
    }
    else
    {
        Value=Key_NULL;//"读"追上了"写",缓冲区没有键值,返回空键值
    }
    return(Value);
}

//判断一个键值的键数(在本硬件里1bit对应一个按键,0表示按键按下)
u8 JudgeKey(u8 key)
{
    u8 i,count=0;
    for(i=0;i<6;i++)
    {
        if((key & 0x01)==0)
            count++;
        key >>= 1;
    }
    return(count);
}
/*************************************************************************
函数原型:void Key_In(void)
函数功能:在定时器中断里每10ms调用1次,处理按键输入,并将按键值放入键值缓冲区中。
          支持单键、双键组合键
传入参数:无
返回参数:无
设    计:莫汉伟 amo73@126.com
修改日期:2007-10-12
备    注:详细功能和处理算法请参照本文件相关的流程图和文档
**************************************************************************/
enum KeyFSM_Enum //按键状态有限状态机
{
    _Key_Idle=0,    //空闲
    _Key1_down,    //单键按下消抖
    _Key1_press,    //单键按下
    _Key1_up,    //单键抬起消抖
    _Key2_down,    //双键按下消抖
    _Key2_press,    //双键按下
    _Key2_up,    //双键抬起消抖
    _Key_confirm,    //按键确认成功
};

//处理扫描按键、判断按键、保存键值等。在定时器中断里(周期为10ms以上即可)调用本函数。
void Key_In(void)
{
    static u8 Key_Input=0;
    //在此添加获取键值代码(参照硬件电路图)
    static u8 volatile step=0;//步骤
    u8 NewKey = uPSD.DATAIN_A & 0x3F;//读取按键
    //获取键值代码结束
    u8 num=JudgeKey(NewKey);//判断当前有几个按键按下
    switch(step)//状态机
    {
        case _Key_Idle://空闲
        {
            switch(num)
            {
                case 1://单键
                {
                    Key_Input = NewKey;//保存键值
                    step = _Key1_down;//单键按下消抖
                    SoundLight_ms(_TypeLcdBkLED,9000);//LCD背光亮一段时间
                    break;
                }
                case 2://双键
                {
                    Key_Input = NewKey;//保存键值
                    step = _Key2_down;//双键按下消抖
                    SoundLight_ms(_TypeLcdBkLED,9000);//LCD背光亮一段时间
                    break;
                }
                default://其它
                {
                    break;
                }
            }
            break;
        }
        case _Key1_down://单键按下消抖
        {
            switch(num)
            {
                case 1://单键
                {
                    if(NewKey == Key_Input)//键值不变
                        step = _Key1_press;//单键按下
                    else
                        step = _Key_Idle;//空闲
                    break;
                }
                case 2://双键
                {
                    Key_Input = NewKey;//保存键值
                    step = _Key2_down;//双键按下消抖
                    break;
                }
                default://其它
                {
                    step = _Key_Idle;//空闲
                    break;
                }
            }
            break;
        }
        case _Key1_press://单键按下
        {
            switch(num)
            {
                case 1://单键
                {
                    if(NewKey != Key_Input)//键值改变
                        step = _Key1_up;//单键抬起消抖
                    break;
                }
                case 2://双键
                {
                    Key_Input = NewKey;//保存键值
                    step = _Key2_down;//双键按下消抖
                    break;
                }
                default://其它
                {
                    step = _Key1_up;//单键抬起消抖
                    break;
                }
            }
            break;
        }
        case _Key1_up://单键抬起消抖
        {
            switch(num)
            {
                case 1://单键
                {
                    if(NewKey == Key_Input)//键值不变
                        step = _Key1_press;//单键按下
                    else
                        step = _Key_confirm;//按键确认成功
                    break;
                }
                case 2://双键
                {
                    Key_Input = NewKey;//保存键值
                    step = _Key2_down;//双键按下消抖
                    break;
                }
                default://其它
                {
                    step = _Key_confirm;//按键确认成功
                    break;
                }
            }
            break;
        }
        case _Key2_down://双键按下消抖
        {
            if(NewKey == Key_Input)//键值不变
                step = _Key2_press;//双键按下
            else
                step = _Key_Idle;//空闲
            break;
        }
        case _Key2_press://双键按下
        {
            if(num==0)//无键
                step = _Key2_up;//双键抬起消抖
            break;
        }
        case _Key2_up://双键抬起消抖
        {
            if(num==0)//无键
                step = _Key_confirm;//按键确认成功
            break;
        }
        default://出现异常,流程复位
        {
            step = _Key_Idle;//空闲
            break;
        }
    }

    if(step == _Key_confirm)//按键确认成功
    {
        step = _Key_Idle;//空闲

    //    SoundLight_ms(_TypeLcdBkLED,9000);//LCD背光亮一段时间
        //放入键值缓冲区中
        if(Key_Input != 0)//如果键值有效,则放入缓冲区中
        {
        //    SoundLight_ms(_TypeBeep,30);//蜂鸣器发声30ms
        //    menu_auto_return_s = 60;//60秒后菜单自动返回
            Key.buff[Key.in++] = Key_Input;
            if(Key.in >= KeyBuffLen)//如果"写"跑到了队列尾部,则重新跳回原点
            {
                Key.in=0;
            }
        }
    }

    //"写"只管在前面跑,不用管"读"在后面怎么追;如果"写"跑了一圈又超过了"读"
    //就算是自动将缓冲区清空了,这样的算法具有天生的容错性,只会丢掉前面的键值,
    //而不会崩溃。
}

相关帖子

沙发
mohanwei|  楼主 | 2010-1-21 16:51 | 只看该作者
测试程序:
/*************************************************************************
本文件所有模块的公用测试函数。您可以把本函数放在main()函数的开头里,然后在
本函数内添加您编写的各个模块函数的测试函数,这样你就可以单独测试自己的函数,
而不会影响别的组员的工作(因为main函数一开始就执行了您的测试函数,没有执行
别人的函数!)
**************************************************************************/
/*
void Key_Board_Test(void)//
{
    //如果您想测试函数My_Fun(),那么您就编写相应的My_Fun_Test(),然后把它
    //放到这里来执行。测试完成以后,根据实际情况决定是否保留该测试函数。
    //在本函数前应已初始化串口、定时器0中断!
    u8 key;
    Key_Board_Init();//初始化键盘缓冲区
    while(1)
    {
        key=Read_Key();//从缓冲区读取键值
        if(key == -1)
        {
            continue;
        }
        SoundLight_ms(_TypeBeep,30);//蜂鸣器发声30ms
        switch(key)
        {
            case Key_Up://上箭头
            {
                printf("您按下了“上箭头”按键。\n\n");
                break;
            }
            case Key_Down://下箭头
            {
                printf("您按下了“下箭头”按键。\n\n");
                break;
            }
            case Key_Add: //加
            {
                printf("您按下了“加”按键。\n\n");
                break;
            }
            case Key_Sub://减
            {
                printf("您按下了“减”按键。\n\n");
                break;
            }
            case Key_Enter://回车
            {
                printf("您按下了“回车”按键。\n\n");
                break;
            }
            case Key_Return://返回
            {
                printf("您按下了“返回”按键。\n\n");
                break;
            }
            case Key_LR://左右组合键
            {
                printf("您同时按下了“左、右”按键。\n\n");
                break;
            }
            default://未定义按键
            {
                printf("不知道您按下了什么按键...\n\n");
                break;
            }
        }
    }
}*/

使用特权

评论回复
板凳
mohanwei|  楼主 | 2010-1-21 16:55 | 只看该作者
程序效率不能看文件大小的……这段代码其实效率还是较高的,每次定时器中断,进去跳转几下就出来了。更高效的方式是将那些switch-case直接换成状态表,每次只需查一次表(二维数组)即可,但是那种方式不直观……

还有更“高级”一点的,每个按键支持单击、双击、长按……不过作为一个状态机应用的示例,上面的代码足够了。

使用特权

评论回复
地板
红金龙吸味| | 2010-1-21 17:50 | 只看该作者
很好,谢谢您贴出来的代码。可否讲一下定时器的使用,从您的代码中我感觉定时器用的很巧妙。

使用特权

评论回复
5
mohanwei|  楼主 | 2010-1-21 18:21 | 只看该作者
//每10ms中断一次
void Timer0_Isr(void)        interrupt        TF0_VECTOR
{
        //先处理各种时间片分配
        ……
       
        Key_In();//最后顺便扫描一下按键输入
}

没什么巧妙的……这种按键程序从12CLK的51到ARM,我都一直沿用,唯一要改的只是读取键值这个片断:
   //在此添加获取键值代码(参照硬件电路图)
//    u8 NewKey = uPSD.DATAIN_A & 0x3F;//读取按键(uPSD单片机)
//   u8 NewKey = P1 & 0x3F;//读取按键(STC,按键接在P1口)
      STM32则是用GPIO_ReadInputDataBit()一类的库函数
    //获取键值代码结束

使用特权

评论回复
6
oksmn| | 2010-1-21 21:02 | 只看该作者
标记学习!

使用特权

评论回复
7
fover| | 2010-1-21 21:24 | 只看该作者
脚丫子留下

使用特权

评论回复
8
ecomputer| | 2010-1-21 21:29 | 只看该作者
程序写多了就好,状态机思想,缓冲队列来存储按键,多谢LZ共享,单片机使用状态机效率还是很搞的

使用特权

评论回复
9
chl00100| | 2010-1-21 21:29 | 只看该作者
mark

使用特权

评论回复
10
itelectron| | 2010-1-21 21:40 | 只看该作者
程序效率不能看文件大小的……这段代码其实效率还是较高的,每次定时器中断,进去跳转几下就出来了。更高效的方式是将那些switch-case直接换成状态表,每次只需查一次表(二维数组)即可,但是那种方式不直观……

...
mohanwei 发表于 2010-1-21 16:55



3楼你说的是不是 类似 UCOS 里的 优先 级的2维 数组

使用特权

评论回复
11
Karlshen| | 2010-1-21 22:45 | 只看该作者
虽然不知道状态机是干嘛的,最近我们也要编个键盘程序,收下了

使用特权

评论回复
12
linfuchi| | 2010-1-22 11:07 | 只看该作者
学习,哈哈

使用特权

评论回复
13
反质子| | 2010-1-22 13:22 | 只看该作者
学习 好东西

使用特权

评论回复
14
badbird1234| | 2010-1-22 13:54 | 只看该作者
MARK

使用特权

评论回复
15
淡淡的茶香| | 2010-1-22 15:28 | 只看该作者
刚进来,觉得有点神奇。我好像不懂。学习。再学习。

使用特权

评论回复
16
zjw5000| | 2010-1-22 15:32 | 只看该作者
标记

使用特权

评论回复
17
mohanwei|  楼主 | 2010-1-22 15:35 | 只看该作者

写程序前先有这个状态迁移图,就好编写了……

使用特权

评论回复
18
mohanwei|  楼主 | 2010-1-22 15:37 | 只看该作者
红色是单键的处理,蓝色是双键的处理,黑色是公共的……足够清晰了,但是也造成了代码的“臃肿”。

使用特权

评论回复
19
mohanwei|  楼主 | 2010-1-22 15:41 | 只看该作者
不知是好习惯还是坏习惯,写多了通信程序(多次握手处理),现在再简单的事也要考虑到所有的可能情况……

使用特权

评论回复
20
hqgboy| | 2010-1-22 16:35 | 只看该作者
ddddd

使用特权

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

本版积分规则

177

主题

9320

帖子

24

粉丝