4*4按键,状态机思想:定时扫描+多键组合+连_发功能(具有实际应用意义)
1、该段程序是4*4键盘扫描程序,功能比较全,根据实际情况可在次基础上进行修改;2、已调试通过,但没有优化;
/*********************************************************************
文件:scankey.c
用途:通用4*4按键接口驱动程序
注意:
创建:2008.5.15
修改:2008.5.15
设计:raosibin
版本:ver1.3
功能说明:每隔10MS调用一次 ,实现了组合按键功能;连_发功能
**********************************************************************/
#include "..\config.h"
/*********************************************************************
COL0 COL1 COL2 COL3
* ROW4 0 1 2 3
* ROW5 4 5 6 7
* ROW6 8 9 A B
* ROW7 C D E F
*
* 普通按键:
* 2~E:数字按键
*
* 连_发按键:
* 0:
* 1:
*
* 组合按键
* F: 引导键
* F+0:
* F+1:
* 状态机思路
* F为组合引导键
* 每10MS扫描一次键盘
* 状态0:检测是否有键按下;
* 状态1:消抖处理
* 状态2:确认按键,同状态1比较,除F引导键外相同则返回键值,
* F引导键未按下转状态3;F引导键按下转状态5;
* 状态3:计数1,(按3S,连_发判断);
* 状态4:计数2,连_发功能
* 状态5:500MS内检测是否有F引导键+其他键按下;有转状态6,没有转状态8
* 状态6:消抖处理
* 状态7:同状态6比较,确认组合按键,返回组合键值,转状态3
* 状态8:消抖处理
* 状态9:先导键为单按键,返键值,转状态3.
**********************************************************************/
//按键初始化
void key_board_int1(void)
{
KEY_DDR_ROW |=KEY_ROW_ALL; //行输出,
KEY_PORT_ROW&=~ KEY_ROW_ALL;
KEY_DDR_COL &=~ KEY_COL_ALL; //列输入,并使能内部上拉
KEY_PORT_COL|=KEY_COL_ALL;
}
void key_board_int2(void) //翻转初始化
{
KEY_DDR_ROW &=~ KEY_ROW_ALL; //行输入,并使能内部上拉
KEY_PORT_ROW|=KEY_ROW_ALL;
KEY_DDR_COL |=KEY_COL_ALL; //列输出;
KEY_PORT_COL&=~ KEY_COL_ALL;
}
unsigned char key_board_scan(void)
{
unsigned key_value_buf;
key_board_int1();
KEY_PORT_ROW &=~ KEY_ROW_ALL; // 必须送2次!!!
key_value_buf = (~KEY_ROW_ALL) & KEY_PIN_COL; // 读列电平
key_board_int2(); // 翻转扫描
KEY_PORT_COL&=~ KEY_COL_ALL;
key_value_buf |= (~KEY_COL_ALL) & KEY_PIN_ROW; // 读行电平,并同翻转前的结果或
return key_value_buf;
}
char read_keyboard()
{
static char key_state = 0,key_value1,key_value2,key_value3,key_value4,key_value5;
static char key_value6,key_value7,key_value8,key_value9;
static unsigned int key_time = 0;
char key_return = No_key;
switch (key_state)
{
case 0: // 判断是否有键按下
if (key_board_scan() != No_key)
{
key_state = 1; // 有按键
break; // 转消抖确认状态
}
break;
case 1: // 消抖处理
if (++key_time >= 1) // 改变判断条件可改变键盘灵敏度
{
key_value1 = key_board_scan();
if (key_value1 != No_key)
{
key_state = 2;
break;
}
key_state = 0;
break;
}
break;
case 2: // 确认按键,同状态1比较
key_value2 = key_board_scan();
if (key_value2 == key_value1) // 再次扫描,
{
if (key_value2 != 0b01110111) // 是否为引导键F,是则转状态5
{
switch (key_value2) // 与状态1的相同,确认按键
{ // 键盘编码,返回编码值
case 0b11101110:
key_return = K1_1; // 0;
break;
case 0b11101101:
key_return = K1_2; // 1
break;
case 0b11101011:
key_return = K1_3;
break;
case 0b11100111:
key_return = K1_4;
break;
case 0b11011110:
key_return = K2_1;
break;
case 0b11011101:
key_return = K2_2;
break;
case 0b11011011:
key_return = K2_3;
break;
case 0b11010111:
key_return = K2_4;
break;
case 0b10111110:
key_return = K3_1;
break;
case 0b10111101:
key_return = K3_2;
break;
case 0b10111011:
key_return = K3_3;
break;
case 0b10110111:
key_return = K3_4;
break;
case 0b01111110:
key_return = K4_1;
break;
case 0b01111101:
key_return = K4_2;
break;
case 0b01111011:
key_return = K4_3;
break;
default:
break;
}
key_state = 3; // 转入等待按键释放状态
key_time = 0; // 清0按键时间计数器
break;
}
else
{
key_state = 5;
key_time = 0;
break;
}
}
else
key_state = 0; // 两次列电平不同返回状态0,(消抖处理)
break;
case 3: // 计数1,(按3S,连_发判断)
key_value3 = key_board_scan();
if ( key_value3 == No_key)
key_state=0; // 按键已释放,转换到按键初始态
else if ((key_value3 == 0b11100111)||(key_value3 == 0b11011110)) // 改变此判断条件,可以确定
{ // 哪些键具备加速功能,3,4加速
if (++key_time >= 300) // 按键时间计数
{
key_state = 4; // 按下时间>3s,状态转换到计时2
key_time = 0; // 清按键计数器
key_return = KJ; // 输出“18”
}
break;
}
break;
case 4: // 计数2,连_发功能
key_value4 = key_board_scan();
if ( key_value4 == No_key)
key_state=0; // 按键已释放,转换到按键初始态
else if (++key_time >= 20) // 按键时间计数
{
key_time = 0; // 清按键计数器
key_return = KJ; // 输出“18”
break;
}
break;
case 5: // 检测是否有F引导键+其他键按下;有转状态6,没有转状态8
key_value5 = key_board_scan();
if (key_value5 == key_value2)
{
if (++key_time >= 50) // 500MS到了吗?未到,状态5,不断扫描,到了,状态8
{
key_state = 8;
key_time = 0;
break;
}
break;
}
else
{
key_state = 6;
break;
}
break;
case 6: // 消抖处理
key_value6 = key_board_scan();
key_state = 7;
break;
case 7: // 同状态6比较,确认组合按键,返回组合键值,转状态3
key_value7 = key_board_scan();
if (key_value7 == key_value6)
{
switch (key_value7)
{
case 0b01100110:
key_return = KF_0; // 组合按键F+0
break;
case 0b01100101:
key_return = KF_1; // 组合按键F+1
break;
}
key_state = 3;
}
break;
case 8: //消抖处理
key_value8 = key_board_scan();
key_state = 9;
break;
case 9: // 同状态8比较,确认单按键,返键值,转状态3
key_value9 = key_board_scan();
if (key_value9 == key_value8)
{
key_return = K4_4;
key_state = 3;
break;
}
key_state = 0;
break;
default:
break;
}
return key_return;
} 先收下看看,谢谢楼主 记号 咋就没人讨论下呢?是太简单了吗?希望能听到些批评指点的声音................... case 2: // 确认按键,同状态1比较
key_value2 = key_board_scan();
if (key_value2 == key_value1) // 再次扫描,
{
if (key_value2 != 0b01110111) // 是否为引导键F,是则转状态5
{
switch (key_value2) // 与状态1的相同,确认按键
{ // 键盘编码,返回编码值
case 0b11101110:
key_return = K1_1; // 0;
break;
case 0b11101101:
key_return = K1_2; // 1
break;
case 0b11101011:
key_return = K1_3;
break;
case 0b11100111:
key_return = K1_4;
break;
case 0b11011110:
key_return = K2_1;
break;
case 0b11011101:
key_return = K2_2;
break;
case 0b11011011:
key_return = K2_3;
break;
case 0b11010111:
key_return = K2_4;
break;
case 0b10111110:
key_return = K3_1;
break;
case 0b10111101:
key_return = K3_2;
break;
case 0b10111011:
key_return = K3_3;
break;
case 0b10110111:
key_return = K3_4;
break;
case 0b01111110:
key_return = K4_1;
break;
case 0b01111101:
key_return = K4_2;
break;
case 0b01111011:
key_return = K4_3;
break;
default:
break;
}
key_state = 3; // 转入等待按键释放状态
key_time = 0; // 清0按键时间计数器
break;
}
else
{
key_state = 5;
key_time = 0;
break;
}
}
else
key_state = 0; // 两次列电平不同返回状态0,(消抖处理)
break;
编程能力需要提高,比如在上面的代码中,每个ELAE中,怎么都使用BREAK?
给个优化的参考:
#defien K1_1 0B11101110
#defien K1_2 0b11101101
......
......
#define K_pre 0b01110111
case 2: // 确认按键,同状态1比较
key_value2 = key_board_scan();
if (key_value2 == key_value1) // 再次扫描,
{
key_time = 0;
if (key_value2 != K_pre) // 是否为引导键F,是则转状态5
{
key_return = key_value2; // 与状态1的相同,确认按键
key_state = 3; // 转入等待按键释放状态
}
else
{
key_state = 5;
}
}
else
{
key_state = 0; // 两次列电平不同返回状态0,(消抖处理)
}
break;
==========================
主程序中:
key_temp = read_keyboard();
switch (key_temp)
{
case K1_1:
// K1_1的键处理
break;
case K1_2:
......
} 谢谢,老师,在体会之中............ 状态8没有必要,可以取消掉. 我是来学多键组合的,留个记号在这里方便回来查找 多键组合 记号 可不可以把按键编码那里的代码用查表的方式来解决呢?
我是这么做的:
//按键值表
const uchar key_value[] PROGMEM ={
0x0f,0xff,0xff,0xff,0x01,0xff,0x04,0x07,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00,0xff,0xff,0xff,0x02,0xff,0x05,0x08,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x0e,0xff,0xff,0xff,0x03,0xff,0x06,0x09,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x0d,0xff,0xff,0xff,0x0c,0xff,0x0b,0x0a
};
//按键检测,返回值为0xff表示无效按键
uchar key (void)
{
uchar key_x,key_y;
keyline_PORT=0x0f; //置为高4位输出0,低4位上拉输入
keyline_DDR=0xf0;
nop;nop;
key_x=keyline_PIN;
if ((key_x&0x0f)!=0x0f)
{
delay_ms (10); //延时10MS
key_x=keyline_PIN;
if ((key_x&0x0f)!=0x0f)
key_x&=0x0f; //保存低4位有效值
else
return 0xff;
keyline_PORT=0xf0; //置为高4位上拉输入,低4位输出0
keyline_DDR=0x0f;
nop;nop;
key_y=keyline_PIN;
if ((key_y&0xf0)!=0xf0)
{
key_y&=0xf0; //保存高4位有效值
//key_x|=key_y;
return (pgm_read_byte (key_value+(key_x|key_y)-0x77));
}
else
return 0xff;
}
else
return 0xff;
} 楼上
这也是一种思路,学习学习;
pgm_read_byte (key_value+(key_x|key_y)-0x77));
什么意思啊?你的键值表也太长了吧?呵呵
楼上你是不是省了几个大括号啊?这个方法不能实现连_发和组合功能吧? 学习了 1、该段程序是4*4键盘扫描程序, 功能比较全, 根据实际情况可在次基础上进行修改;
2、已调试通过,但没有优化;
/*********************************************************************
文件:scankey.c
用途:通用4*4按键接口驱动程序
注意:
创建:2008.5.15
修改:2008.5.15
设计:raosibin
版本:ver1.3
功能说明:每隔10MS调用一次 ,实现了组合按键功能;连_发功能
**********************************************************************/
#include "..\config.h"
/*********************************************************************
COL0 COL1 COL2 COL3
* ROW4 0 1 2 3
* ROW5 4 5 6 7
* ROW6 8 9 A B
* ROW7 C D E F
*
* 普通按键:
* 2~E:数字按键
*
* 连_发按键:
* 0:
* 1:
*
* 组合按键
* F: 引导键
* F+0:
* F+1:
* 状态机思路
* F为组合引导键
* 每10MS扫描一次键盘
* 状态0:检测是否有键按下;
* 状态1:消抖处理
* 状态2:确认按键,同状态1比较,除F引导键外相同则返回键值,
* F引导键未按下转状态3;F引导键按下转状态5;
* 状态3:计数1,(按3S,连_发判断);
* 状态4:计数2,连_发功能
* 状态5:500MS内检测是否有F引导键+其他键按下;有转状态6,没有转状态8
* 状态6:消抖处理
* 状态7:同状态6比较,确认组合按键,返回组合键值,转状态3
* 状态8:消抖处理
* 状态9:先导键为单按键,返键值,转状态3.
**********************************************************************/
//按键初始化
void key_board_int1( void )
{
KEY_DDR_ROW |= KEY_ROW_ALL; //行输出,
KEY_PORT_ROW &= ~ KEY_ROW_ALL;
KEY_DDR_COL &= ~ KEY_COL_ALL; //列输入,并使能内部上拉
KEY_PORT_COL |= KEY_COL_ALL;
}
void key_board_int2( void ) //翻转初始化
{
KEY_DDR_ROW &= ~ KEY_ROW_ALL; //行输入,并使能内部上拉
KEY_PORT_ROW |= KEY_ROW_ALL;
KEY_DDR_COL |= KEY_COL_ALL; //列输出;
KEY_PORT_COL &= ~ KEY_COL_ALL;
}
unsigned char key_board_scan( void )
{
unsigned key_value_buf;
key_board_int1();
KEY_PORT_ROW &= ~ KEY_ROW_ALL; // 必须送2次!!!
key_value_buf = ( ~KEY_ROW_ALL ) & KEY_PIN_COL; // 读列电平
key_board_int2(); // 翻转扫描
KEY_PORT_COL &= ~ KEY_COL_ALL;
key_value_buf |= ( ~KEY_COL_ALL ) & KEY_PIN_ROW; // 读行电平,并同翻转前的结果或
return key_value_buf;
}
uchar read_keypad( void )
{//10ms 读一次
static uchar preKey = 0, curKey, keytimes = 0;
uchar keyCode = noKey;
curKey = key_board_scan();
if ( curKey != noKey )
{ //可能有键按下
if ( preKey == curKey )
{ //同一按键消抖
keytimes++;
if ( ( keytimes % 5 ) == 0 )
{ //50ms 短按或加速键 时间自已把握
// keyCode = shortpush|preKey;
}
/*if ( keytimes > 100 )
{
keytimes = ( 101 ); //锁定 长按
// keyCode = longpush|preKey;
//keyCode =
}*/
return keycode;
}
else if ( preKey != curKey )
{ //不同按键按下
{
keytimes = 0;
preKey = curKey;
}
}
}
else
{ //键已释放,或无键按下
keytimes = 0;
return keyCode;
}
}
/*
实现短按键 长按键或加速键
能不能实现长按与加速功能呢
*/ 键值获得与处理就不写了,以上按键可以实现加速键功能,不知道能不能一起实现长按功能,希各高手指点 学习 F键就是包括了长按的功能啊 看了思路,程序还没仔细看。
提点建议:将“F引导键”作无效键,“F引导键+其他键”作有效键,等同单按键,“状态5的500MS”可以去掉。简化后如下:
* 状态0:检测是否有键按下;
* 状态1:消抖处理
* 状态2:确认按键(F引导键+其他键、单按键),同状态1比较
* 状态3:计数1,(按3S,连_发判断);按键放开,转状态0
* 状态4:计数2,连_发功能
* 状态5:返键值,转状态3 作记号,以后查询 我用到一个实际的产品,也是组合按键的,没有使用引导键,不论哪个先按都能及时反映出来,同时每个按键都具备单独的功能,反映都非常灵敏,真不知是采用了什么思路 猜测如下:
1.检测是否有键按下;
2.消抖处理:延时稍长(如0.2s),判断键值有无变化? 无变化,得到键值(单按键,多按键)
3.判断按键是否变化? 有变化,转步骤1。 那它的状态是怎么变化的呢? 主要是按键扫描程序的结束条件由“判断按键放开”,变为“判断按键变化”。
另外,修正在20楼的描述:
1.检测是否有键按下;
2.消抖处理:延时,判断键值有无变化? 无变化,得到键值(单按键,多按键);
3.判断按键是否变化? 变化为无效键,返回键值,转步骤1;变化为新按键,不返回键值,转步骤1重新判断;
(补:无键和不用的键,都当成无效键) 看看 看看 jihao MARK 以后要仔细看一下。 记号一下 up 消抖后,那就不能使用快速“双击”功能啦 楼上的,当然可以实现快速“双击”。
你先测试你的最快的“快速双击”操作,两次击键之间的时间最少是多少毫秒? 不错。 小时候玩跑表,最快的连击速度经测试不小于180ms 记号一下 support 记号 慢慢看 很好,经常能用得到 备用啊 mark 标记 看了一下楼主的程序,没有太明白,在read_keyboard()中定义了局部变量key_time=0,那么在每次调用该函数时,key_time==0,怎么会出现++key_time>=50等情况出现呢?是不是应将key_time定义成全局变量呢?小弟拙见 明白了,因为是静态局部变量 mark 定时10ms扫描按键? MARK AMRK 不错
MARK! 好帖,先领会。 学习!有空研究下 学习一下。做个记号 学习一下。。 jh 这个讨论有意思 楼主的编程功底的确要加强,不过思路基本上到位了。
需求不一样,在保证最高效率的前提下,方法可以不一样。
大部分情况下,状态机是比价好的思路,优点是比较清晰。 MARK MARK 按键 状态机 mark 学习下 mark mark
经常用到 看看学习中 mark mark mark mark 学习了 MARK 有了参考资料! 大家讨论的很棒
不过对赋值这块总感觉 太繁琐了。
想改进下
不过 想了半天无果
谁能提供个简化的思路不 简化思路:
1、有键按下,消抖,送通码(键值)
2、无键按下,消抖,送断码
3、在调用程序里处理通码和断码,可以实现连_发功能 mark 马尔克,谢谢 4*4扫描还搞这么复杂?
键扫提供基本信息:按下、弹起、组合键、REPEAT键,还有可能象手机那样的按法,都不用写这么复杂。 MARK 记录一下 学习 记号一个 mark mark 先标记一下,明天再看 mark mark 先记号,有空在看 mark 回复【楼主位】raosibin
-----------------------------------------------------------------------
牛 mark mark 回复【楼主位】raosibin
-----------------------------------------------------------------------
mark mark 学习一下 回复【4楼】machao
-----------------------------------------------------------------------
谢谢马老师 mark dddddddddddddddddddddddddddddd 好资料,值得研究 mark Study 这是个好东西,收藏1 看下 这个程序是不是没有松手检测? mark 多键组合为社么需要有引导键?单按键和组合按键返回的键值不同,直接检测键值,那个键先按下都无所谓