amoBBS 阿莫电子论坛

 找回密码
 注册
搜索
bottom↓
楼主: machao

题目:多功能按键设计。利用一个I/O口,接一个按键,实现3功能操作:单击 + 双击 + 长按。

  [复制链接]
发表于 2012-11-9 17:48:45 | 显示全部楼层
很不错,正需要这个,mark!
发表于 2012-11-12 17:30:38 | 显示全部楼层
向马老师致敬
发表于 2012-11-18 12:35:34 | 显示全部楼层
学习了。正在琢磨,理解后,引进我做的小系统中。
发表于 2012-11-19 18:02:40 | 显示全部楼层
很好,谢谢。

发表于 2013-1-4 00:07:11 | 显示全部楼层
谢谢楼主!
发表于 2013-1-4 00:54:37 来自手机 | 显示全部楼层
mark一下
发表于 2013-1-15 07:46:45 | 显示全部楼层
mark  回来实践下
发表于 2013-1-15 19:06:47 | 显示全部楼层
mark一下  感谢马老师
发表于 2013-1-15 19:24:27 来自手机 | 显示全部楼层
学习学习。。。。。。
发表于 2013-1-16 10:34:41 来自手机 | 显示全部楼层
老贴标记下
发表于 2013-1-27 13:34:47 | 显示全部楼层
标记以消除浮躁的心!
发表于 2013-2-4 15:48:10 | 显示全部楼层
顶起,正在学习4x4的键盘程序,打算用状态机实现连发长按功能。
发表于 2013-2-4 20:19:16 来自手机 | 显示全部楼层
好帖留印
发表于 2013-2-6 11:10:29 | 显示全部楼层
学习了,好资料
发表于 2013-2-20 08:30:06 | 显示全部楼层
nice thought about key process
头像被屏蔽
发表于 2013-2-22 14:20:52 | 显示全部楼层
本帖最后由 oszp1688com 于 2013-2-22 14:24 编辑

马老师:  你这个按键 低层按键(I/0)扫描函数 中的那个 return key_return;   这一句不要  程序中只是多出了两个警告  对最终 结果无影响   
        在抄写你的程序  漏写了这句 才发现的  开始以为是变量 key_retun  与下面那段中的变量相同造成的  后面  我把其中的一个变量换成大写  发现还是和上面一样的现像  
       本人不才,请马老师 帮忙解释下原因 谢谢


uchar key_drive(void) // 读键盘
{
        static uchar key_state=key_state_0,key_time=0; // 状态、按键计时
        uchar key_press,Key_return=N_Key;            // 按键值、返回事件
       
        key_press=key_input;   //读按键IO 电平 :松开1,按下为0。
       
        switch(key_state)
        {
                case key_state_0:   // 按键初始态  
                if(!key_press) key_state=key_state_1; // 键被按下,状态转换到按键消抖和确认状态
                break;
               
                case key_state_1:  // 按键消抖与确认态  
                if(!key_press)
                {
                        key_time=0;
                        key_state=key_state_2;// 按键仍然处于按下,消抖完成,状态转换到按下键时间的计时状态
                }
                else key_state=key_state_0;// 按键已抬起,转换到按键初始态。此处完成和实现软件消抖,其实按键的按下和释放都在此消抖的。
                break;
               
                case key_state_2:
                if(key_press)    // 按键释放
                {
                        Key_return=S_Key;// 返回单击事件
                        key_state=key_state_0;// 转换到按键初始态
                }
                else if(++key_time>=250) // 继续按下,计时加40ms(40ms为本函数循环执行间隔)
                {
                        Key_return=L_Key;// 按下时间>2000ms,此按键为长按操作,返回长键事件
                        key_state=key_state_3; // 转换到等待按键释放状态
                }
                break;
               
                case key_state_3:  // 等待按键释放  
                if(key_press) key_state=key_state_0;//按键已释放,转换到按键初始态
                break;
        }
        return Key_return; //?????????没有这一句,程序只是多了两个警告 对最终函数效果并没有产生影响 百思不知其解???????????????
}

uchar key_read(void)
{
        static uchar key_m=key_state_0,key_time_1=0;// 状态、双击时限
        uchar key_return=N_Key,key_temp;//反回事件,读键码
       
        key_temp=key_drive();  //读键码
       
        switch(key_m)
        {
                case key_state_0:
                if(key_temp==S_Key) // 第1次单击,不返回,到下个状态判断后面是否出现双击
                {
                        key_time_1=0;// 初始化双击计时  
                        key_m=key_state_1;// 转下一步
                }
                else key_return=key_temp;// 对于无键、长键,返回原事件
                break;
               
                case key_state_1:
                if(key_temp==S_Key) // 又一次单击(间隔肯定<500ms)
                {
                        key_return=D_Key;  // 返回双击键事件,回初始状态
                        key_m=key_state_0; // 返回初始状态
                }
                else  
                {   // 这里500ms内肯定读到的都是无键事件,因为长键>1000ms,在1s前低层返回的都是无键
                        if(++key_time_1>=150)
                        {
                                key_return=S_Key; // 500ms内没有再次出现单键事件,返回上一次的单键事件
                                key_m=key_state_0; // 返回初始状态
                        }
                }
                break;
        }
        return key_return;//返回按键事件
}
发表于 2013-3-5 16:12:07 | 显示全部楼层
学习记号!!!!!!
发表于 2013-3-7 15:15:57 | 显示全部楼层
不错,看了一个小时还没看完
发表于 2013-3-30 11:57:38 | 显示全部楼层
学习了 受教了!!
发表于 2013-3-31 21:02:19 | 显示全部楼层
收益非浅,学习了 马老师
发表于 2013-4-5 16:59:45 | 显示全部楼层
用状态机算是新手级别,写了3个小时。手边没有硬件验证,先贴出来犒劳下辛苦
uint ms1000,ms500;
uchar s_m=0,ms10,keyval=0,short=0,long=0;
void key()
{
        if(s_m==0)
        {
                if(~(PINC&0X01))s_m=1;
        }
        else if(s_m==1)//按下       
        {
                ms10=0;
                ms500=0;
                ms1000=0;
                s_m=2;       
        }
        else if(s_m==2)
        {
                if(ms10==10)
                {
                        ms10=0;
                        if(~(PINC&0X01))s_m=3;//确认按下
                        else s_m=0;//没有按下
                }       
        }
        else if(s_m==3)
        {
                if((PINC&0X01))
                {               
                        s_m=4;//松手
                        ms10=0;
                }
        }       
        else if(s_m==4)
        {
                if(ms10==10)
                {
                        ms10=0;
                        if((ms1000>=1000)&&(PINC&0X01))
                        {
                                if((short==0)&&(long==0))
                                {
                                        keyval=1;//长按
                                        long=1;
                                }
                                else if(long==1)
                                {
                                        long=0;
                                        keyval=0;//本次无效
                                }
                                else if(short==1)
                                {
                                        short=0;
                                        keyval=0;//本次无效
                                }
                                s_m=0;
                        }
                        if((ms1000<1000)&&(PINC&0X01))
                        {
                                if((short==0)&&(long==0))
                                {
                                        keyval=2;//短按
                                        short=1;
                                        ms500=0;
                                        s_m=5;
                                }
                                else if(long==1)
                                {
                                        long=0;
                                        keyval=0;//本次无效
                                        s_m=0;
                                }
                                else if(short==1)
                                {
                                        short=2;
                                        ms500=0;
                                        s_m=5;
                                }
                        }
                       
                }
        }
        else//(s_m==5)
        {
                if((~(PINC&0X01))&&(ms500<500))
                {
                        if(short==2)
                        {
                                keyval=4;//双击
                        }
                        s_m=0;
                }               
                if(ms500>500)
                {
                        if(short==1)
                        {
                                short=0;
                                keyval=3;//单击
                        }               
                        s_m=0;
                }       
        }
}
SIG(SIG_OVERFLOW0)//1ms定时中断
{
        ms10++;
        ms500++;
        ms1000++;
        key();       
}
发表于 2013-4-14 09:13:42 | 显示全部楼层
记录一下
发表于 2013-5-17 10:10:57 | 显示全部楼层
标记,很好的帖子
发表于 2013-5-23 17:51:33 来自手机 | 显示全部楼层
不错的设计,值得借鉴!
发表于 2013-5-27 12:57:59 | 显示全部楼层
最实用的操作啊 好东西
发表于 2013-6-17 10:34:05 | 显示全部楼层
本帖最后由 sqmm 于 2013-6-17 10:50 编辑

我老早实现过单个按键实现导航的程序
能够单击双击,3击....9击...N击
能够识别长按,单击后长按,双击后长按....N击后长按

直接用button_get()返回不同键码.
可以很方便实现导航.
用一个定时器,主循环保证150ms内有查询就OK

  1. #include<avr/interrupt.h>
  2. #include<cpu/cpu.h>
  3. #include"avrbutton.h"

  4. static dwordVal_t tsKeyDown;
  5. static dwordVal_t tsKeyUp;
  6. static dwordVal_t* ptsKeyLast = &tsKeyDown;        // 最近按键动作计时

  7. static u8                        ButtonAction;        // 为主循环提供按钮事件信号
  8. static u8                        KeyIsMulti;        // 连击标识,1:有可能连击
  9. static u8                        KeyCount;        // 连击次数

  10. #define MULTI_CLIK        150
  11. #define LONG_PRESS        1000
  12. #define KEY_REPEAT        300



  13. u16 ms_diff(dwordVal_t* more, dwordVal_t* less)
  14. {
  15.         u16 ret = more->Val - less->Val;
  16.         return ( ret & 0x8000 )?0xFFFF:ret;
  17. }

  18. SIGNAL ( MutiFunction_ISR )
  19. {
  20.         if( !ms_passed(ptsKeyLast,21) )// 消抖
  21.                 return;
  22.         u8 state = PIN_H(MutiFunction_KEY);
  23.         if(!state)
  24.         {//按键弹起
  25.                 if(ButtonAction>=3)//重复后驱动复位
  26.                 {
  27.                         KeyCount = 0;
  28.                         ButtonAction = 0;
  29.                         KeyIsMulti = 0;
  30.                         return;
  31.                 }
  32.                 ButtonAction = 2;
  33.                 tsKeyUp.Val = SysClock.Val;
  34.                 ptsKeyLast = &tsKeyUp;
  35.                 if(KeyIsMulti)
  36.                 {
  37.                         KeyCount ++;
  38.                 }
  39.         }
  40.         else
  41.         {//按键按下
  42.                 ButtonAction = 1;
  43.                 tsKeyDown.Val = SysClock.Val;
  44.                 ptsKeyLast = &tsKeyDown;
  45.                 if( ms_diff(&tsKeyDown, &tsKeyUp)<MULTI_CLIK )// 有可能是连击
  46.                 {
  47.                         KeyIsMulti = 1;
  48.                         if(KeyCount==0)
  49.                                 KeyCount = 1;
  50.                 }
  51.         }
  52. }


  53. u8 button_get(void)
  54. {
  55.         u8 ret = 0;
  56.         if(ButtonAction==0)
  57.         {
  58.                 return ret;
  59.         }
  60.         else if(ButtonAction==2)
  61.         {
  62.                 if( ms_passed(ptsKeyLast, MULTI_CLIK) )
  63.                 {
  64.                         if(KeyIsMulti)
  65.                         {
  66.                                 ret = KeyCount;
  67.                         }
  68.                         else// 要么单击要么长按
  69.                         {
  70.                                 ret = 1;// 单击       
  71.                         }
  72.                         KeyCount = 0;
  73.                         ButtonAction = 0;
  74.                         KeyIsMulti = 0;                       
  75.                 }
  76.         }
  77.         else if(ButtonAction==1)
  78.         {// 很可能产生连击事件.
  79.                 if( ms_passed(ptsKeyLast, LONG_PRESS) )
  80.                 {
  81.                         ButtonAction = 3;
  82.                         ret = KeyCount?KeyCount:0xFF;
  83.                 }
  84.                
  85.         }
  86.         else if(ButtonAction==3)
  87.         {
  88.                 if( ms_passed(ptsKeyLast, KEY_REPEAT) )
  89.                 {
  90.                         if( PIN_L(MutiFunction_KEY) )//按键弹起确认
  91.                         {
  92.                                 KeyCount = 0;
  93.                                 ButtonAction = 0;
  94.                                 KeyIsMulti = 0;
  95.                                 return ret;
  96.                         }
  97.                         //ptsKeyLast->Val = SysClock.Val;
  98.                         //ret = KeyCount?KeyCount:0xFF;
  99.                 }
  100.         }
  101.         return ret;
  102. }



  103. // 初始化按钮端口,打开按钮电平跳变中断
  104. void button_init(u8 id)
  105. {
  106.         DRIVER(MutiFunction_KEY, IN);
  107.         CLR( MutiFunction_KEY );
  108.         MASK_1(EIMSK, MutiFunction_INT);
  109.         MASK(EICRB, 0x1/*0b01*/<<((MutiFunction_INT-4)<<1));
  110.         ButtonAction = 0;
  111. }
复制代码
发表于 2013-6-17 11:13:50 | 显示全部楼层
今天才看到这么火帖子,学习!
发表于 2013-6-22 01:12:14 | 显示全部楼层
mark,实在想睡觉了,不能熬夜。
发表于 2013-6-22 07:22:44 来自手机 | 显示全部楼层
先标记一下,回头细看
发表于 2013-7-17 13:34:14 | 显示全部楼层
标记一下
发表于 2013-7-19 14:46:10 | 显示全部楼层
MARK
学习应用中……
发表于 2013-7-19 16:39:50 | 显示全部楼层
,很好的帖子,
发表于 2013-7-19 17:05:05 | 显示全部楼层
下班了回去在看
发表于 2013-7-19 17:31:24 | 显示全部楼层
mark...........
发表于 2013-7-24 16:14:15 | 显示全部楼层
m a r k 看看留个标记
发表于 2013-7-30 15:13:45 | 显示全部楼层
多谢大家,学习了
发表于 2013-8-8 23:32:37 | 显示全部楼层
诶  好贴啊  多看多谢 多领悟  马老师应该多开点这种贴啊  思想上的  真正提高啊。。
发表于 2013-8-12 11:03:03 | 显示全部楼层
mark!!
发表于 2013-8-17 16:01:45 | 显示全部楼层
马老师 我这个长短按键程序对吗?
#define KEY_OFF_ON   RB5

unsigned int uiTimer1_Count;

unsigned char ucKey_Count_Start;
unsigned char ucSet_Down_Short;
unsigned char ucSet_Down_Long;
unsigned char ucSet_Short_Flag;
unsigned char ucSet_Long_Flag;

void Key_Long_Short(void)
{
    if(KEY_OFF_ON == 0)                                              //检测按键按下
     {
         ucKey_Count_Start = 1;
         if((KEY_OFF_ON == 0) && (uiTimer1_Count >= 8))              //按键500MS为短按
         {
              ucSet_Down_Short = 1;
              ucSet_Down_Long = 0;
         }  
         if((KEY_OFF_ON == 0) && (uiTimer1_Count >= 77))              //按键5S为长按
         {
              ucSet_Down_Short = 0;
              ucSet_Down_Long = 1;
         }
     }
     else
     {
          if((KEY_OFF_ON == 1) && (ucSet_Down_Short == 1))
          {
              ucSet_Down_Short = 0;
              ucSet_Short_Flag = 1;                                    //一个完整的短按键操作完成
          }
          if((KEY_OFF_ON == 1) && (ucSet_Down_Long == 1))
          {
              ucSet_Down_Long = 0;
              ucSet_Long_Flag = 1;                                      //一个完整的长按键操作完成
          }
          ucKey_Count_Start = 0;
     }

     if(ucKey_Count_Start == 1)                                         //按键开始定时器计数
     {
          TMR1ON = 1;
     }
     else if(ucKey_Count_Start == 0)
     {
          TMR1ON = 0;
     }
}

void Power_On_Off(void)
{
    if(ucSet_Long_Flag == 1)                                                             //是长按键
    {   
        ucSet_Long_Flag = 0;
        PCA9635LedoutSet(0x2a, 0xaa, 0xaa, 0x0a, 0x00);                                    //开机
        return;
    }
    else if(ucSet_Short_Flag == 1)                                                        //是短按键
    {   
        ucSet_Short_Flag = 0;
        PCA9635LedoutSet(0x2a, 0x00, 0x00, 0x00, 0x00);                                    //关机
        return;
    }   
}
发表于 2013-9-20 06:32:29 | 显示全部楼层
讨论太好了,有意思。
发表于 2013-9-20 06:46:08 | 显示全部楼层
谢谢 老师 希望再出点好书
发表于 2013-10-8 11:51:43 | 显示全部楼层
好帖子 顶起 哈哈
发表于 2013-10-8 13:09:12 | 显示全部楼层

学习了!
发表于 2014-2-13 17:54:52 | 显示全部楼层
看起来容易,真正自己独立整理思路去写确为不易
发表于 2014-2-13 20:53:38 | 显示全部楼层
呵呵,学习了!!
发表于 2014-2-13 20:54:36 | 显示全部楼层
呵呵!谢谢分析!
发表于 2014-2-21 13:19:47 | 显示全部楼层
耐心看了一遍,讨论的太好了。
发表于 2014-2-21 13:29:05 | 显示全部楼层
以后还得再看
发表于 2014-2-21 14:59:50 | 显示全部楼层

技术贴,mark
发表于 2014-2-21 17:24:06 | 显示全部楼层
mark                                 
发表于 2014-2-24 16:07:50 | 显示全部楼层
好贴  学习了
发表于 2014-3-18 22:09:57 | 显示全部楼层
mark           
发表于 2014-3-19 00:11:10 来自手机 | 显示全部楼层
手机mark下,改天细看
发表于 2014-3-19 08:56:27 | 显示全部楼层
先收藏,回头看
发表于 2014-3-19 09:08:25 | 显示全部楼层
mark。。。。。。。
发表于 2014-3-19 10:42:58 | 显示全部楼层
缺少面向对象的思想,小众编程。
发表于 2014-4-11 09:59:19 | 显示全部楼层
谢谢马老师,受教了!
Mark...
发表于 2014-4-11 10:43:30 | 显示全部楼层
看完这个帖子,自己增加了自信,马老师帖子中一直强调的“编程思想和方法是编程的核心”这个观点,我严重同意。
发表于 2014-4-11 11:20:58 | 显示全部楼层
八错
发表于 2014-4-11 16:26:44 | 显示全部楼层
好帖,收藏
发表于 2014-6-2 19:33:58 | 显示全部楼层
这个一个我看过的最简单&最复杂的帖子,琢磨很长时间啦
发表于 2014-7-28 18:09:29 | 显示全部楼层
学习了!
发表于 2014-7-28 18:35:17 | 显示全部楼层
谢谢分享!
发表于 2014-9-22 15:56:26 | 显示全部楼层
很好的状态机按键设计,目前很多按键设计都得益于这篇帖。
发表于 2014-11-11 11:13:02 | 显示全部楼层
mark!!!!
发表于 2014-11-11 11:34:34 | 显示全部楼层
好思路!
发表于 2014-11-11 12:19:47 | 显示全部楼层
Mark,谢谢分享
发表于 2014-11-11 12:55:33 | 显示全部楼层
其实不是不想做的好,而是根本没有能力做好!这句话在理,顶楼主~
发表于 2014-11-11 13:39:19 | 显示全部楼层
很好的东西,谢了
发表于 2014-11-11 14:19:23 | 显示全部楼层
谢谢,马克一下
发表于 2014-11-13 16:47:08 | 显示全部楼层
好贴子,学习了
发表于 2014-11-13 16:47:51 | 显示全部楼层
再简单的功能要是想写好了,写通用了,写稳定了,还真不是一件容易的事
发表于 2014-11-13 18:08:16 | 显示全部楼层
不mark了,果断打印珍藏
发表于 2014-11-13 18:16:13 | 显示全部楼层
多谢分享。
发表于 2014-11-13 18:18:01 | 显示全部楼层
好资料,收藏!
发表于 2015-4-30 15:26:46 | 显示全部楼层
非常有用  记一下
发表于 2015-5-3 10:42:01 | 显示全部楼层
好东西一定要顶
发表于 2015-5-14 18:51:11 | 显示全部楼层
mark,很不错
发表于 2015-5-14 19:28:10 | 显示全部楼层
好帖,收藏一下。
发表于 2015-5-14 19:36:23 | 显示全部楼层
MARK一下好了
发表于 2015-5-14 22:37:45 | 显示全部楼层
耐心看完,感慨!向马潮老师学习,将细节做到极致!
发表于 2015-5-15 14:32:41 | 显示全部楼层
mark,谢谢楼主的分享            
发表于 2015-8-29 08:50:35 | 显示全部楼层
马老师,这一个按键要是接在中断上要如何处理?中断发生后如何处理长按,短按??
发表于 2015-9-3 16:49:59 | 显示全部楼层

主程序执行过程中想从主程序初始化重头执行,gai

马老师,我向您请教一个关于C语言的问题:主程序执行过程中想从主程序初始化重头执行,该怎么编写指令或用什么其他办法实现这个功能呢?敬请您的回复,感谢!
 楼主| 发表于 2015-9-8 21:45:46 | 显示全部楼层
鹰凖明 发表于 2015-9-3 16:49
马老师,我向您请教一个关于C语言的问题:主程序执行过程中想从主程序初始化重头执行,该怎么编写指令或用 ...

简单的方法是嵌入一句汇编代码:jmp 0000h
 楼主| 发表于 2015-9-8 21:49:05 | 显示全部楼层
lnso 发表于 2015-8-29 08:50
马老师,这一个按键要是接在中断上要如何处理?中断发生后如何处理长按,短按?? ...

不建议用外部中断去处理
发表于 2015-9-8 22:10:43 | 显示全部楼层
machao 发表于 2015-9-8 21:49
不建议用外部中断去处理

项目只有一个按键,而且要做低功耗,逼不得已啊
发表于 2015-9-10 10:44:40 | 显示全部楼层
machao 发表于 2015-9-8 21:45
简单的方法是嵌入一句汇编代码:jmp 0000h

感谢指导!马老师,你说的这个跳转指令后面的数值是main函数的起始地址值吗?
发表于 2015-9-10 11:00:35 | 显示全部楼层
好复杂,如果用AD输入那种,一根线,多简单。
发表于 2015-9-10 14:09:47 | 显示全部楼层
学习下状态机。。。
发表于 2016-4-17 08:02:51 | 显示全部楼层
这个不错,曾经用过有些产品,按键程序就写的不好让人感觉按键坏了一样
发表于 2016-5-6 12:31:25 | 显示全部楼层
受益匪浅,mark
发表于 2016-8-26 14:59:19 | 显示全部楼层
Mark,以后可以多一个解决的思路了
发表于 2016-8-27 09:31:39 | 显示全部楼层
受教了,感谢楼主
发表于 2016-8-28 16:36:12 | 显示全部楼层
按键基于状态机算法,确实是一种非常好的思路,也不容易乱。
发表于 2016-10-3 06:53:23 | 显示全部楼层
学习学习,谢谢分享
发表于 2016-10-21 10:51:25 来自手机 | 显示全部楼层
学习谢谢老师
发表于 2016-10-21 10:55:19 | 显示全部楼层
收藏了,感觉特别好
发表于 2016-10-21 10:56:01 | 显示全部楼层
这个可以节省一些IO点啊,呵呵,学习了,谢谢
发表于 2016-12-18 18:11:02 | 显示全部楼层
代码好,说的也好,支持。
友情提示:标题不合格、重复发帖,将会被封锁ID。详情请参考:论坛通告:封锁ID、获得注册邀请码、恢复被封ID、投诉必读
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|阿莫电子论坛(原ourAVR/ourDEV) ( 公安备案:44190002001997(交互式论坛) 工信部备案:粤ICP备09047143号 )

GMT+8, 2019-8-25 22:39

阿莫电子论坛, 原"中国电子开发网"

© 2004-2018 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表