写了一个独立按键 支持组合键、单键长按,连发功能的例子
本帖最后由 foxpro2005 于 2012-7-5 21:53 编辑学习了马老师的《AVR单片机嵌入式系统原理与应用实践书》 第2版,现也写了一个按键扫描的例子(用WINAVR写的)。
独立按键,支持组合键,单键长按,连发功能等应用,已通过实践测试。
KeyScan.h 部分
/********************************************************************************************************
* FileName....: KeyScan.h
* MCU.........: ATmega8 at 8MHz
* Compiler....: WinAVR-20100110
* Author......: foxpro2005
* Ver.........: V1.0
* Time........: 2012.07.04
* Description.: Key scan.
* History.....: 2012.07.04 V1.0
*
******************************************************************************************************/
#ifndef _KeyScan_H_
#define _KeyScan_H_
//------------------------------------------------------------------------------------------------------
#include <avr/io.h>
//-------------------------------------------------------------------------------------------------------
/*****************************硬件I/O引脚定义,根据实际应用修改******************************/
#define KEY_PORT PIND // 独立按键所占用端口.
#define K_STOP (1<<PD3) // "停止"键
#define K_START (1<<PD4) // "启动"键
#define K_SEL (1<<PD5) // "选择"键
#define K_ADD (1<<PD6) // "+ "键
#define K_DEC (1<<PD7) // "- "键
#define OneKeyTime 300 // 单键长按时长 = OneKeyTime * 10ms
#define MuxKeyTime 500 // 组合键长按时长 = MuxKeyTime * 10ms
#define RepeatTime 5 // 单键连发功能重复间隔时间 = RepeatTime * 10ms
#define Key_Mask (K_STOP|K_START|K_SEL|K_ADD|K_DEC) // 按键掩码值
//-------------------------------------------------------------------------------------------------------
#define GetKey() (KEY_PORT & Key_Mask) // 读取按键端口值
#define key_state_0 0 // 初始态.
#define key_state_1 1 // 确认态.
#define key_state_2 2 // 组合键,长按键确认态.
#define key_state_3 3 // 长按连发功能确认态.
#define key_state_4 4 // 等待释放态.
//-------------------------------------------------------------------------------------------------------
/****************************************数据类型定义**************************************/
typedef enum {NO_KEY,STOP,START,SEL,ADD,DEC,SETUP} eKEY; // 按键返回值类型定义.
//-------------------------------------------------------------------------------------------------------
/*****************************************函数声明****************************************/
eKEY ReadKey(void); //读取按键.
//-------------------------------------------------------------------------------------------------------
#endif
KeyScan.c 部分
/********************************************************************************************************
* FileName....: KeyScan.c
* MCU.........: ATmega8 at 8MHz
* Compiler....: WinAVR-20100110
* Author......: foxpro2005
* Ver.........: V1.0
* Time........: 2012.07.04
* Description.: Key scan.
* History.....: 2012.07.04 V1.0
*
******************************************************************************************************/
//------------------------------------------------------------------------------------------------------
#include "KeyScan.h"
//------------------------------------------------------------------------------------------------------
/***********************************全局变量定义区******************************************/
/********************************************************************************************
* eKEY ReadKey(void);
* 功能: 独立按键扫描,10ms扫描一次.
* 返回值:枚举类型,{NO_KEY,STOP,START,SEL,ADD,DEC,SETUP}.
* 参数:
********************************************************************************************/
eKEY ReadKey(void)
{
static unsigned int key_time = 0; // 长按键计时
static unsigned char key_state = 0; // 记录按键扫当前描状态
static unsigned char key_press_old = 0; // 记录上一次按键状态
static eKEY key_value_old = NO_KEY; // 保存上一次按键返回值
eKEY key_return = NO_KEY; // 按键功能返回值
unsigned char key_press;
key_press = GetKey() ^ Key_Mask; // 读按键I/O电平,只保留被按下的键(被按下的键位为1)
switch (key_state)
{
case key_state_0: // 1.按键初始态
if (key_press)
{
key_state = key_state_1; // 键被按下,状态转换到按键确认态
key_press_old = key_press; // 保存当前按键状态
}
break;
case key_state_1: // 2.按键确认态
if (key_press == key_press_old) // 与初始态的按键状态相同?
{
key_time = 0; // 清另按键时间计数器
switch(key_press)
{
case K_STOP:
key_state= key_state_4; // 转按键释放态
key_return = STOP; // "停止"键
break;
case K_START:
key_state= key_state_4; // 转按键释放态
key_return = START; // "起动"键
break;
case K_SEL:
key_state= key_state_2; // 转长按键态
key_return = SEL; // "选择"键
break;
case K_ADD:
key_state= key_state_2; // 转长按键态
key_return = ADD; // "+"键
break;
case K_DEC:
key_state= key_state_2; // 转长按键态
key_return = DEC; // "-"键
break;
case K_STOP|K_SEL: // "组合"键,长按键
key_state= key_state_2; // 组合键按键仍按下,状态转换到计时1
break;
default:
key_state= key_state_4; // 转按键释放态
break;
}
}
else if (!key_press)
key_state = key_state_0; // 按键已抬起(是干扰),转换到按键初始态
else
key_state = key_state_4; // 按键已发生变化,转到按键释放态
key_value_old = key_return; // 保存按键返回值
break;
case key_state_2: // 3.长按键确认态
if (key_press == key_press_old)
{
++key_time; // 按键计时
if (key_press == (K_STOP|K_SEL)) // "配置"键?
{
if(key_time >= MuxKeyTime) // 组合键长按计时
{
key_state = key_state_4; // 按下时间>=MuxKeyTime,转到按键释放状态
key_return = SETUP; // 组合键功能,"配置"键
}
}
else
{
if(key_time >= OneKeyTime) // 单键长按计时
{
key_state = key_state_3; // 按下时间>=OneKeyTime,转到连发功能态,用于触发连发功能
key_time = 0; // 清按键计数器
key_return = key_value_old; // 返回上一次按键值
}
}
}
else
key_state = key_state_4; // 按键已发生变化,转到按键释放态
break;
case key_state_3: // 4.按键连发功能
if (key_press == key_press_old)
{
if (++key_time >= RepeatTime) // 按键时间计数
{
key_time = 0; // 按下时间>=0.05s,清0按键计数器
key_return = key_value_old; // 返回上一次按键值
}
}
else
key_state = key_state_4; // 按键已发生变化,转到按键释放态
break;
case key_state_4: // 5.等待所有按键释放开
//if (!key_press) // 等待所有按键释放,才进入一次新的按键确认过程
if (key_press != key_press_old) // 按键发生变化,就进入一次新的确认过程
key_state = key_state_0; // 按键已释放,转换到初始态.
break;
}
return key_return;
}
你好,我在调试你贴上的程序,发现单个按键都正常,为什么组合键就不正常呢?代码如下:
#include <iom16v.h>
#define K_STOP (1<<PA0) // "停止"键
#define K_START (1<<PA1) // "启动"键
#define K_SEL (1<<PA2) // "选择"键
#define K_ADD (1<<PA3) // "+ "键
#define K_DEC (1<<PA4) // "- "键
#define OneKeyTime 300 // 单键长按时长 = OneKeyTime * 10ms
#define MuxKeyTime 500 // 组合键长按时长 = MuxKeyTime * 10ms
#define RepeatTime 5 // 单键连发功能重复间隔时间 = RepeatTime * 10ms
#define Key_Mask (K_STOP|K_START|K_SEL|K_ADD|K_DEC) // 按键掩码值
#define KEY_PORT PORTA // 独立按键所占用端口.
#define KEY_DDR DDRA
#define KEY_PIN PINA
//-------------------------------------------------------------------------------------------------------
#define GetKey() (KEY_PIN & Key_Mask) // 读取按键端口值
#define key_state_0 0 // 初始态.
#define key_state_1 1 // 确认态.
#define key_state_2 2 // 组合键,长按键确认态.
#define key_state_3 3 // 长按连发功能确认态.
#define key_state_4 4 // 等待释放态.
//-------------------------------------------------------------------------------------------------------
/****************************************数据类型定义**************************************/
typedef enum {NO_KEY,STOP,START,SEL,ADD,DEC,SETUP} eKEY; // 按键返回值类型定义.
//-------------------------------------------------------------------------------------------------------
/*****************************************函数声明****************************************/
eKEY ReadKey(void); //读取按键.
//-------------------------------------------------------------------------------------------------------
unsigned char time_counter,key_stime_counter; // 时间计数单元
unsigned char point_on, time_1s_ok,key_stime_ok;
/********************************************************************************************
* eKEY ReadKey(void);
* 功能: 独立按键扫描,10ms扫描一次.
* 返回值:枚举类型,{NO_KEY,STOP,START,SEL,ADD,DEC,SETUP}.
* 参数:
********************************************************************************************/
eKEY ReadKey(void)
{
static unsigned int key_time = 0; // 长按键计时
static unsigned char key_state = 0; // 记录按键扫当前描状态
static unsigned char key_press_old = 0; // 记录上一次按键状态
static eKEY key_value_old = NO_KEY; // 保存上一次按键返回值
eKEY key_return = NO_KEY; // 按键功能返回值
unsigned char key_press;
key_press = GetKey() ^ Key_Mask; // 读按键I/O电平,只保留被按下的键(被按下的键位为1)
switch (key_state)
{
case key_state_0: // 1.按键初始态
if (key_press)
{
key_state = key_state_1; // 键被按下,状态转换到按键确认态
key_press_old = key_press; // 保存当前按键状态
}
break;
case key_state_1: // 2.按键确认态
if (key_press == key_press_old) // 与初始态的按键状态相同?
{
key_time = 0; // 清另按键时间计数器
switch(key_press)
{
case K_STOP:
key_state= key_state_4; // 转按键释放态
key_return = STOP;
// "停止"键
break;
case K_START:
key_state= key_state_4; // 转按键释放态
key_return = START; // "起动"键
break;
case K_SEL:
key_state= key_state_2; // 转长按键态
key_return = SEL; // "选择"键
break;
case K_ADD:
key_state= key_state_2; // 转长按键态
key_return = ADD; // "+"键
break;
case K_DEC:
key_state= key_state_2; // 转长按键态
key_return = DEC; // "-"键
break;
case K_STOP|K_SEL: // "组合"键,长按键
key_state= key_state_2; // 组合键按键仍按下,状态转换到计时1
break;
default:
key_state= key_state_4; // 转按键释放态
break;
}
}
else if (!key_press)
key_state = key_state_0; // 按键已抬起(是干扰),转换到按键初始态
else
key_state = key_state_4; // 按键已发生变化,转到按键释放态
key_value_old = key_return; // 保存按键返回值
break;
case key_state_2: // 3.长按键确认态
if (key_press == key_press_old)
{
++key_time; // 按键计时
if (key_press == (K_STOP|K_SEL)) // "配置"键?
{
if(key_time >= MuxKeyTime) // 组合键长按计时
{
key_state = key_state_4; // 按下时间>=MuxKeyTime,转到按键释放状态
key_return = SETUP; // 组合键功能,"配置"键
}
}
else
{
if(key_time >= OneKeyTime) // 单键长按计时
{
key_state = key_state_3; // 按下时间>=OneKeyTime,转到连发功能态,用于触发连发功能
key_time = 0; // 清按键计数器
key_return = key_value_old; // 返回上一次按键值
}
}
}
else
key_state = key_state_4; // 按键已发生变化,转到按键释放态
break;
case key_state_3: // 4.按键连发功能
if (key_press == key_press_old)
{
if (++key_time >= RepeatTime) // 按键时间计数
{
key_time = 0; // 按下时间>=0.05s,清0按键计数器
key_return = key_value_old; // 返回上一次按键值
}
}
else
key_state = key_state_4; // 按键已发生变化,转到按键释放态
break;
case key_state_4: // 5.等待所有按键释放开
//if (!key_press) // 等待所有按键释放,才进入一次新的按键确认过程
if (key_press != key_press_old) // 按键发生变化,就进入一次新的确认过程
key_state = key_state_0; // 按键已释放,转换到初始态.
break;
}
return key_return;
}
void Key_init(void)
{
KEY_DDR&=~ (K_STOP)|(K_START)|(K_SEL)|(K_ADD)|(K_DEC);
KEY_PORT |=(K_STOP)|(K_START)|(K_SEL)|(K_ADD)|(K_DEC);
}
void Timer0_Init(void)
{
// T/C0 初始化
TCCR0 = 0x0C; // 内部时钟,64分频(4M/64=62.5KHz),CTC模式
TCNT0 = 0x00;
OCR0 = 0x08; // OCR0 = 0x7C(124),(124+1)/62.5=2ms
//TIMSK = 0x02; // 允许T/C0比较匹配中断
TIMSK |=(1<<OCIE0);//允许T/C0比较匹配中断
}
#pragma interrupt_handler Timer0_comp_isr:20
void Timer0_comp_isr(void)
{
//display(); // LED扫描显示
if (++key_stime_counter >=5)
{
key_stime_counter = 0;
key_stime_ok = 1; // 10ms到
if (++time_counter >= 100)
{
time_counter = 0;
time_1s_ok = 1; // 1s到
}
}
}
void main(void)
{
Buzz_init();
Key_init();
Timer0_Init();
SREG|=0x80;
while(1)
{
if (key_stime_ok)
{
key_stime_ok = 0; // 10ms到
//typedef enum {NO_KEY,STOP,START,SEL,ADD,DEC,SETUP} eKEY;
switch (ReadKey())
{
case STOP:
Beep(1,1);
break;
case START:
Beep(5,5);
break;
case SEL:
Beep(10,10);
break;
case ADD:
Beep(15,15);
break;
case DEC:
Beep(20,20);
break;
case SETUP:
Beep(130,130);
break;
}
}
}
} yupusong@qhd 发表于 2012-7-6 18:25 static/image/common/back.gif
你好,我在调试你贴上的程序,发现单个按键都正常,为什么组合键就不正常呢?代码如下:
#include
....
#define OneKeyTime 300 // 单键长按时长 = OneKeyTime * 10ms
#define MuxKeyTime 500 // 组合键长按时长 = MuxKeyTime * 10ms
#define RepeatTime 5 // 单键连发功能重复间隔时间 = RepeatTime * 10ms
...
case K_STOP|K_SEL: // "组合"键,长按键
...
本程序中,组合键,只定义了: STOP + SEL 键,并且要长按5S中 才会有效。 好的,多谢提示,我再试试看 好东西,正好要用,呵呵 多谢楼主的程序,已经调试通了,很实用{:victory:} 其实我是进来看看有没有其他平台的版本能借用的。{:loveliness:}
~伸手党~ 你这种风格不好,应该把按键扫描和按键功能执行分开,分别做成模块, 1.在这里只是按键的扫描部分,返回的仅是按键值而已(由枚举类型已定义好的,是只为了方便识别),是只代表是哪个按键按下(相当于一个标识符吧),并没有去具体定义按键的任何功能。
2.具体按键功能的实现(处理),是于由它的上层模块来完成。 {:smile:}{:smile:}{:smile:} 我试试看 嘿嘿!做次伸手党,谢谢大神,我好好理解一下! 标记下。。。。 我想知道这个函数执行完了要多长时间,为什么我这个15ms还跑不完呢? 标记下,用的时候再看. 长按键跟短按键值返回都是一样,怎么在应用区别开来呢? zbazba 发表于 2013-11-26 13:32
长按键跟短按键值返回都是一样,怎么在应用区别开来呢?
长按与短按返回的值不能一样,而持续按键实现连发功能 与 短按的返回的值是一样的。
foxpro2005 发表于 2013-11-26 17:45
长按与短按返回的值不能一样,而持续按键实现连发功能 与 短按的返回的值是一样的。
...
短按与长按肯定不能一样.但连发与短按返回值为什么能一样呢.应用怎么调用呢? 这个不解,呵呵.能告诉我下不?
可以这样处理,让短按的键值或上一个数值.返回这个.当然返回的数值不能跟别的冲突了. 连发就是相当于每次扫描的时候,把短按(正常操作)的值返回 学习。。。 foxpro2005 发表于 2013-11-27 12:04
连发就是相当于每次扫描的时候,把短按(正常操作)的值返回
提供一个我写的功能类似的按键程序给大家做参考
传送门:http://www.amobbs.com/forum.php?mod=viewthread&tid=5558434&pid=7142055&page=1&extra=page%3D1#pid7142055 您好,楼主!看了你的程序,有点疑问想请教一下,你的程序是不是一个按键要么只能短按,要么只能长按,而不能一个按键同时具有短按,长按组合按的功能啊? 支持一个啊啊啊啊 谢谢分享..... 谢谢分享,学习学习 参考一下 组合键时.甜腻问题 这个按键程序遇到一个问题,就是当按键按下就会返回短按的状态,而不是抬起才返回,这样子无法实现短按和长按实现不同的功能 mark,按键少的时候很有用 如果能够区分长按和短按就更好了 支持一下!!!!! 谢谢分享。。 #include "Main.h"
eKEY ReadKey(void) {
static U16 key_time =0; //长按键计时
static U8 key_state = 0; //记录按键当前扫描状态
static U8 key_press_old = 0; //记录上一次按键状态
static eKEY key_value_old = NO_KEY; //保存上一次按键返回值
eKEY key_return = NO_KEY; //按键功能返回值
U8 key_press;
key_press = GetKey()^ Key_Mask; //读按键IO电平,只保留被按下的键(被按下的键位为1)
switch (key_state) {
case key_state_0: //按键初始态
if(key_press) {
key_state = key_state_1; //键被按下,状态转换到按键确认态
key_press_old = key_press; //保存当前按键状态
}
break;
case key_state_1: //按键确认态
if(key_press == key_press_old) //与初始态按键状态相同?
{
key_time = 0; //清零
switch (key_press)
{
case OPEN: //case:K_OPEN ,这样写编译报错
key_state = key_state_4; //转按键释放态
key_return = OPEN;
break;
case CLOSE: //case:K_COLSE ,报错
key_state = key_state_4; //转按键释放态
key_return = CLOSE;
break;
default:
key_state = key_state_4; //转按键释放态
break;
}
}
else if(!key_press)
key_state = key_state_0; //按键已抬起(干扰),转初始态
else
key_state = key_state_4; //按键已发生变化,转到按键释放态
key_value_old = key_return; //保存按键返回值
break;
case key_state_4: //等待所有按键释放开
if(key_press != key_press_old) //按键发生变化,就进入一次新的确认过程
key_state = key_state_0; //按键已释放,转换到初始态
break;
}
return key_return;
}
void LockProc(void) {
switch(ReadKey()) {
case OPEN:
OpenLockOut();
break;
case CLOSE:
ClosLockOut();
break;
case NO_KEY:
default:
ResetLock();
}
}
void T10msEvent(void) {
LockProc();
}#ifndef _PPQC_H
#define _PPQC_H
#define OpenLockOut() do{OpenOut=1;ClosOut=0;}while(0)
#define ClosLockOut() do{OpenOut=0;ClosOut=1;}while(0)
#define ResetLock() do{OpenOut=0;ClosOut=0;}while(0)
#define OneKeyTime 300 // 单键长按时长 = OneKeyTime * 10ms
#define MuxKeyTime500 // 组合键长按时长 = MuxKeyTime * 10ms
#define RepeatTime 5 // 单键连发功能重复间隔时间 = RepeatTime * 10ms
#define Key_Mask (K_CLOSE|K_OPEN) //按键掩码值
#define GetKey() (KEY_PORT & Key_Mask) //读取按键端口值
#define key_state_0 0 //初始态
#define key_state_1 1 //确认态
#define key_state_2 2 //组合键,长按键确认态
#define key_state_3 3 //长按连发功能确认态
#define key_state_4 4 //等待释放态
typedef enum{NO_KEY,OPEN,CLOSE}eKEY; //按键返回值类型定义
eKEY ReadKey(void);
void LockProc(void);
void T10msEvent(void);
#endif
我把这个应用到PIC16F1824中,编译器9.83,proteus仿真结果不对。不知道哪里出错了。 CLOSEOUT一直有输出,K_CLOSE按下时,OPENOUT反而有输出。 问题解决了。
1、对于CLOSEOUT一直有输出,在端口初始化中,将LATC=0放在TRISC=0前面;
2、对于K_CLOSE按下时,OPENOUT反而有输出。将key_state_1里面的switch语句,case OPEN和case CLOSE改成:case 3,和case 1。
疑问:
1、#define K_CLOS (1<<RA2) 和 K_OPEN (1<<RA1),为什么PICC不能像楼主那样写成case K_CLOS 和K_OPEN;
2、为什么case OPEN和case CLOSE改成:case 3,和case 1就仿真成功?3和1是如何得到的?难道PICC将 K_OPEN (1<<RA1)转换成了3? 想了几天了,还是不明白#define K_CLOS (1<<RA2) ,为什么case :k_CLOS 通不过 zw_7627 发表于 2014-11-15 17:10
想了几天了,还是不明白#define K_CLOS (1
case 分支 必须是确定的常量 , 你好像是用的PIC单片机吧, RA2不是常量哦, 而是读取的引脚吧 foxpro2005 发表于 2014-11-15 20:13
case 分支 必须是确定的常量 , 你好像是用的PIC单片机吧, RA2不是常量哦, 而是读取的引脚吧...
一语中的。我以为PD3就是端口。查资料得知在AVR宏定义中有#define PD3 3。受教了,明白了。多谢。 本帖最后由 zw_7627 于 2014-11-17 14:16 编辑
发现key_press = GetKey()^ Key_Mask;只对相同电平输入有效,倘若有2路高入和2路低入同时存在,则无法判断。比如:RA1,RA2内部上拉,则PORTA= 000110;
而Key_Mask=110110; key_press = 110000;造成无法识别状态。 不一定好用啊 谢谢整理,值得学习。
页:
[1]