amoBBS 阿莫电子论坛

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

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

  [复制链接]
发表于 2011-8-29 09:14:17 | 显示全部楼层
回复【96楼】packer  
回复【95楼】ufbycd
-----------------------------------------------------------------------
这么打个比方,有个项目招标,为了可维护性和可扩展性,我需要用16krom的芯片,价格20元。而放弃可维护性和可扩展性可以用8k芯片,价格10元。(仅仅是个比方)
这时候你说我应该把谁放第一位?
而在项目资源足够的情况下,我不考虑可维护性和可扩展性,只能说我弱智。
所以在我看来,一切前提是完成项目。可能是在严酷的市场竞争中拼打多年,变的太现实了。
再回顾 81楼
-----------------------------------------------------------------------
igoal:这也就是我想讲的,下面一层的代码不要帮助上层的代码做本来属于上层代码的工作,否则就破坏了代码的分层机制
......
-----------------------------------------------------------------------
你选8K芯片并不代表没有任何维护性和扩展性。
只要你稍微考虑一下函数如何拆分、什么函数应该实现什么功能就会提高维护性和扩展性。
代码承载了两部分作用:实现功能和被人维护,也就是代码的功能和代码的质量。
不应过分追求代码的功能也不应过分地追求代码的质量,而应取折中,两手都要抓、两手都要硬,不偏废彼此。
Do one thing, and do it well!是最重要的思想,但实现起来一点都不难,也不会耗费过多的资源,只要多考虑下函数是什么层就行了

用过 MVC 设计模式的人就会知道,前期为了实现MVC进度会比不用设计模式的要慢点,但它的扩展性非常强,以后添加什么或需求有什么
变化都是非常容易实现的。
就如同《笑傲江湖》里所说的,“剑宗与气宗的区别”:
“剑宗功夫易於速成,见效极快。大家都练十年,定是剑宗占上风;各练二十年,那是各擅胜场,难分上下;要到二十年之后,练气宗功夫的才渐渐的越来越强;到得三十年时,练剑宗功夫的便再也不能望气宗之项背了。”
发表于 2011-8-29 09:43:33 | 显示全部楼层
回复【楼主位】machao
题目:多功能按键设计。利用一个i/o口,接一个按键,实现3功能操作:单击 + 双击 + 长按。   
============================================================================  
        else if (++key_time >= 100)     // 继续按下,计时加10ms(10ms为本函数循环执行间隔)  
        {  
             key_return = L_key;        // 按下时间>1000ms,此按键为长按操作,返回长键事件  
             key_state = key_state_3;   // 转换到等待按键释放状态  
        }  
        break;  
-----------------------------------------------------------------------

        else if (++key_time >= 100)     //是不是应该将key_time置回0哈?!不然一次长按后,都成长按了~~~~
        {  
             key_return = L_key;        
             key_state = key_state_3;   
                                        //Key_time = 0;   
        }  
        break;
发表于 2011-8-29 09:50:57 | 显示全部楼层
mark
发表于 2011-8-29 10:07:45 | 显示全部楼层
mark下
发表于 2011-8-29 10:39:16 | 显示全部楼层
回复【101楼】popwolf
-----------------------------------------------------------------------

不用。仔细分析第一层状态图,state3只能回到state0,而state0唯一的出路是去state1,所以在state1清time就行
发表于 2011-8-29 11:15:10 | 显示全部楼层
真精彩!
发表于 2011-8-29 11:47:06 | 显示全部楼层
记号!
发表于 2011-8-29 12:22:21 | 显示全部楼层
MARK
发表于 2011-8-29 13:16:02 | 显示全部楼层
回复【61楼】eduhf_123 经历
回复【32楼】machao   
-----------------------------------------------------------------------
...
--------------------------------------------------------------------------------
给马老师纠个错,这里的key_time是全局非静态变量(因为没有加static修饰符,全局可见),如果一定要认为static与auto两个存储类别互斥,那么它就是个“全局自动变量”。

----------------------------

此时key_time确实是 全局静态变量 ;因为此时key_time具有文件作用域,对于文件作用域,static只表明内部链接,因此无论前面有无static,都具有静态存储时期...
发表于 2011-8-29 14:05:39 | 显示全部楼层
关于上个帖子中马老师出的题目,抽空做了一下,是在多键(低电平和阵列扫描2种)下做的,本来想交作业的。。。

看完马老师这贴,看起来还是有些漏洞,有些繁复了。。。下决心重做了。。。

学不可以已!!!
发表于 2011-8-29 14:55:47 | 显示全部楼层
mark
 楼主| 发表于 2011-8-29 19:24:26 | 显示全部楼层
回复【108楼】popwolf
回复【61楼】eduhf_123 经历
回复【32楼】machao   
-----------------------------------------------------------------------  
...
--------------------------------------------------------------------------------
给马老师纠个错,这里的key_time是全局非静态变量(因为没有加static修饰符,全局可见),如果一定要认为static与auto两个存储类别互斥,那么它就是个“全局自动变量”。  
----------------------------
此时key_time确实是 全局静态变量 ;因为此时key_time具有文件作用域,对于文件作用域,static只表明内部链接,因此无论前面有无stat......
-----------------------------------------------------------------------

eduhf_123 经历的解释是可以的,他考虑到的是更大范围:多个文件是否可以使用key_time变量。

如果只考虑本文件的范围,key_time 就可以简单的认为是全局静态变量,
如果考虑是多文本的范围,那么key_time的定义还可以分成2种:

(1)  static unsigned char key_time; (2)auto unsigned char key_time;----注意,通常auto是省掉的。

前者表示此变量只能在本文中使用,称做:全局外部静态变量
后者表示此变量允许其它文件使用,称做:全局外部非静态变量,或简称全局外部变量

这里的“静态”和“非静态”不是表示它的存储方式和位置,是表示变量的存在时间。

那么在内存中静态存储区中分配的变量是静态存储型变量,静态存储型变量是整个程序运行时间都存在的,不会释放掉。三种变量将定义分配在这个区域:

1。局部静态变量(在一个函数内部用static定义的变量)
2。全局外部静态变量(在本文件中,定义在函数外部的,用static声明的变量)
3。全局外部非静态变量(在本文件中,定义在函数外部的,用auto声明的变量)

这里我们看到,全局外部静态变量和全局外部非静态变量都是“静态”分配存储的变量。而名称中静态则表示的是该变量的存在时间,“静态”表示有时间限制,“非静态”表示无时间限制。

从存储角度,“静态”的另一面是“动态”;从存在时间角度,“静态”的另一面是“非静态”。

因此在名称上容易使人混淆,重要的是当看到一个变量的定义,要能马上知道它的含义和特点:比如什么时间分配内存,什么时间赋初值,在什么地方可以使用等。

记得N年前,曾使用教课书上这样的例题作为测试,回答的正确率不高:

int a=3,b=5;

max(a,b)
{
   int c;
   c=a>b?a:b;
   return (c);
}

main()
{
   int a=8;
   printf("%d",max(a,b));
}

问运行结果是什么?
发表于 2011-8-30 10:00:45 | 显示全部楼层
按照这种方式在单片机上实验按键的单击、双击和长按没能通过,总提示没有按键按下,即使按了键也显示没有键按下。不知道哪位大神在单片机上将这种方式实验成功过?还是这种方式不适合单片机?
发表于 2011-8-30 10:27:58 | 显示全部楼层
有仿真器吗,单步走,看看每步寄存器输出是否符合设计要求。还有你硬件怎么接的,把资料发上来看看。
这个方法绝对适合单片机。
当然,我没实际验证,仅通过读程序没发现明显逻辑问题。
发表于 2011-8-30 11:55:25 | 显示全部楼层
回复【114楼】packer  
-----------------------------------------------------------------------

我用的STM32单片机,按键电路图如图1

图1.按键图 (原文件名:11.gif)

代码如下
/* filename: usart.c */
#include "STM32f10x.h"
#include "key.h"

#define key_input    GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)    // 按键输入口

#define N_key    0             //无键  
#define S_key    1             //单键  
#define D_key    2             //双键  
#define L_key    3             //长键  

#define key_state_0 0  
#define key_state_1 1  
#define key_state_2 2  
#define key_state_3 3

void KEY_PIN_INIT(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  //使能按键的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

  //配置按键为浮空输入
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
}

u8 key_driver(void)
{
  static u8 key_state = key_state_0,key_time = 0;
  u8 key_press,key_return;
  
  key_press = key_input;
  
  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 >= 100)      
    {  
       key_return = L_key;         
       key_state = key_state_3;     
    }  
    break;
  
  case key_state_3:
    if(key_press)
    {
      key_state = key_state_0;
    }
    break;
  }
  return key_return;
}

u8 key_read(void)
{
  static u8 key_m = key_state_0, key_time_1 = 0;  
  u8 key_return = N_key,key_temp;  
  
  key_temp = key_driver();  
      
    switch(key_m)  
    {  
        case key_state_0:  
            if (key_temp == S_key )  
            {  
              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 >= 50)  
                 {  
                      key_return = S_key;      // 500ms内没有再次出现单键事件,返回上一次的单键事件  
                      key_m = key_state_0;     // 返回初始状态  
                 }  
             }  
             break;  
    }
    return key_return;
}
第一个函数是配置PC2为浮空输入,用于读取按键的电平,下面两个函数就是一样的。
主函数中调用
while (1)
  {
     if(user_key_time==0)
     {
      
       key_value = key_read();
       switch(key_value)
       {
          case 0:
            USART1_Puts("没有键盘按下的消息");
            USART1_Putc(0x0D);USART1_Putc(0x0A);
          break;  
          case 1:
            USART1_Puts("单击键盘按下的消息");
            USART1_Putc(0x0D);USART1_Putc(0x0A);
          break;
          case 2:
            USART1_Puts("双击键盘按下的消息");
            USART1_Putc(0x0D);USART1_Putc(0x0A);
          break;
          case 3:
            USART1_Puts("长按键盘按下的消息");
            USART1_Putc(0x0D);USART1_Putc(0x0A);
          break;
        default:break;
        }
     }
  }
用串口显示是单键,双键还是长按还是没有键盘按下,目前是无论是否按键都提示没有键盘按下。
发表于 2011-8-30 12:00:40 | 显示全部楼层
回复【114楼】packer  
-----------------------------------------------------------------------

我用IAR软件单步调试的,key_read()的返回值永远是0.
发表于 2011-8-30 12:14:54 | 显示全部楼层
都建议你单步调,怎么上来只说key_read()的返回值。要从根查起。

u8 key_driver(void)
{
  
  key_press = key_input;
}

看过按键按下时,key_driver里的key_press值是否正确?如果IO电平都没读对,何谈后面的。
发表于 2011-8-30 13:08:13 | 显示全部楼层
又花了一个小时,原来我定时器时间设置错了,10ms搞成了10s,我单步跟踪总是返回零,改过来就好了。谢谢packer的批评与指正。

STM32上面单键实现单击、双击和长按串口输出显示图 (原文件名:11.gif)
发表于 2011-8-30 13:18:46 | 显示全部楼层
马老师的题目非常明确:多功能按键设计。利用一个I/O口,接一个按键,实现3功能操作:单击 + 双击 + 长按。
隐含条件就是在普通的8位单片机上能够实现的。方法也告诉大家了:用状态机。并且也给出了一个实用的程序。
所以大家应该按马老师的要求尽量多做(使之能用在自己所用的单片机上,或者修改程序使之更完美、更通用)。
不要去瞎争辩,比如去和手机等带OS的比,更不要去和MS或者windows去比,不是一个数量级的,比到最后你会发现这么简单的功能无法在单片机中实现,哈哈。

争辩的焦点主要在双击如何实现上。
下面我给一个图,比程序状态图应该更容易看,应该对如何判断双击有帮助。


(原文件名:按键分类.jpg)
发表于 2011-8-30 13:34:51 | 显示全部楼层
楼上最好解释下你的图,什么是单击,什么是短击。长击后为什么还要跟个连击。另外你的双击是怎么实现的我实在是看不懂。
发表于 2011-8-30 13:39:46 | 显示全部楼层
mark
发表于 2011-8-30 13:48:57 | 显示全部楼层
只是一个按键状态图,你的按键再怎么按,也逃不出我的这个按键状态图。
然后你想用按键的什么状态,就随便了。
由图中可以看出,双击是由两个单击(或短击)组合的,所以在程序上判断必须比读键值程序高一层才能实现。

单击和短击:一次短暂的按键,按下就反应曰“单击”,松开才反应曰“短击”。
长击:      就是长按,一直按着到了规定时间,发出长击码。
连击:      也是长按的一种,一直按着,就不停的发出连击码。
都是可选的,用不着就忽略不发码,在程序上就是没有这个状态。
发表于 2011-8-30 13:49:45 | 显示全部楼层
mark!自己先考虑好了再对比下答案
发表于 2011-8-30 13:51:41 | 显示全部楼层
在图上还能看出,即使是在双击判断过程中,单击和短击也是可以用的,但是你要考虑到不至于引起冲突。
发表于 2011-8-30 14:07:23 | 显示全部楼层
“单击和短击:一次短暂的按键,按下就反应曰“单击”,松开才反应曰“短击”。”不好意思,头一回听说这么个解释方法,孤陋寡闻了。

“由图中可以看出,双击是由两个单击(或短击)组合的,所以在程序上判断必须比读键值程序高一层才能实现”,又不好意思,实在在你的图上看不出程序是怎么分层的。
另外,如果你仔细看看我还有ifree64与马老师、packer讨论的是什么你就该清楚马老师的想法就是把你所说的“高一层”程序放到下面一层来,直接给出“是不是单击?是不是双击?是不是连按?”尽量减少分层,而我和ifree64都不同意这么做,我们希望由更高层次的代码完成这个判断工作,所以才出了这么多长篇大论。

“长击:      就是长按,一直按着到了规定时间,发出长击码。
连击:      也是长按的一种,一直按着,就不停的发出连击码”,搞不懂有了长击,连击还有必要吗?这么做程序不是更复杂?你不停的发长击码不就行了?
发表于 2011-8-30 14:36:49 | 显示全部楼层
我来说说我的理解

(原文件名:2种按键消息.JPG)

注意,第一种我用了中间输出。

两种方法的优缺点
第一种
优点:如果仅有单击,或单击和双击有逻辑延续(严格讲应是双击时允许先出现单击反应),那么单击可以及时响应。
缺点:如果双击前不许出现单击响应,要增加额外处理(代价)。第一次单击时不处理,启动一个定时器,当双击时限到时再处理。
第二种
优点:上层程序直接用,不用考虑单击和双击的逻辑性。
缺点:所有单击按键有延时。

我非常感谢马老师出的这个题。当刚做这题时,我给的答案和马老师一样。而在后面的讨论中,才发现对于不同应用,应该有更多不同的处理方法。
还是那句话,打下良好基础,灵活应用。
发表于 2011-8-30 14:38:11 | 显示全部楼层
wk,画个图大家说了这么多话,先re再看
发表于 2011-8-30 14:45:01 | 显示全部楼层
回【126楼】 packer

其实你的第一种方法中的:“额外处理(代价)”,在第二种方法中也是有的:“所有单击按键有延时”,这两种代价的结果是一样的,都是最后造成了延时,不过是引起这种延时的代码所处的层次不一样罢了。
发表于 2011-8-30 14:51:15 | 显示全部楼层
回复【125楼】igoal
“单击和短击:一次短暂的按键,按下就反应曰“单击”,松开才反应曰“短击”。”不好意思,头一回听说这么个解释方法,孤陋寡闻了。
-----------------------------------------------------------------------
我15年前做遥控器客户的规格书就这么要求的。

另外,如果你仔细看看我还有ifree64与马老师、packer讨论的是什么你就该清楚马老师的想法就是把你所说的“高一层”程序放到下面一层来,直接给出“是不是单击?是不是双击?是不是连按?”尽量减少分层,而我和ifree64都不同意这么做,我们希望由更高层次的代码完成这个判断工作,所以才出了这么多长篇大论。
-----------------------------------------------------------------------
哈哈,我觉得啊,其实马老师不是尽量减少分层,而是这么小的东西没必要分那么细。当初马老师出题也是觉得现在的人太浮躁,基础太差,有感而发。

搞不懂有了长击,连击还有必要吗?这么做程序不是更复杂?你不停的发长击码......
-----------------------------------------------------------------------
调时钟长用的方法之一。第一次单击,时间+1,长按不放一定时间后,时间+10变化。
注意,119楼长击后面叫连击,不是一个概念。
发表于 2011-8-30 15:14:32 | 显示全部楼层
“单击”,“短击”的说法我确实第一次听到

另外我的说法是可以用长击码代替连击码。
发表于 2011-8-30 15:27:33 | 显示全部楼层
回复【130楼】igoal
“单击”,“短击”的说法我确实第一次听到
另外我的说法是可以用长击码代替连击码。
-----------------------------------------------------------------------

sigh,我说毛-泽_东你知道是谁,我说毛润之你就不知道了?名称而已,我都没注意到他用“单击”“短击”,但我一看图就理解他想表达什么。
长击能不能代替连击要看应用了,肯定不是一定的。
发表于 2011-8-30 15:42:36 | 显示全部楼层
我其实并不想咬文嚼字,不过对于我这种没听过“单击”,“短击”说法的人来说这个“击”字我的理解就是按键被按下的意思。所以无论是“单击”也好“短击”也罢,从字面上确实分不清哪个是按下反映哪个是抬起反映。


另外“长击能不能代替连击要看应用”这个我同意,毕竟应用领域千差万别,不能说长击就一定能代替连击,只是我还没有发现两者的区别罢了。
发表于 2011-8-30 16:51:23 | 显示全部楼层
mark谢谢
发表于 2011-8-30 17:11:10 | 显示全部楼层
回复【132楼】igoal
-----------------------------------------------------------------------

呵呵,其实要不是你说,我都没注意他用的什么词。只是看了图,明白他要表达的意思。

另外,关于长按,我说个曾做过项目要求。用1个键调某个范围巨大的数。
一按下按键,数字加1;
继续长按2秒后,以3次/1秒速度数字加1;
继续长按5秒后,以10次/1秒速度数字加1;
继续长按5秒后,以3次/1秒速度数字加10.
发表于 2011-8-30 18:02:13 | 显示全部楼层
把按键状态分得这么细可不止我一个,我也是跟“程序匠人”学的。
你怎么按,再加上按的时间长短,就构成了按键的不同状态,用读键程序来读,就是不同的键值。
键值分的细,有利于高一层的程序处理,只用switch case 跳转 或 if 判断即可。

马老师的程序,如果算上main 应该是三层:底层读键,中间层处理双击,顶层是键值应用。一般的说,中间层和顶层可以合在一起。马老师的也没错,通过分层,把程序写的非常清楚,好让不太会写按键程序的学生明白。因为我看到就不止一个学生要求马老师写具体程序了,实际上这个程序很多初学者就缺少一个细节分析。

状态机,我跟傻孩子学的,我也是马老师的学生。状态表的形式可不一定仅仅是状态编号,计时器的值也表明一定的状态,比如119楼我的图上,长击、连击都是看计时器的值是多少,转换成不同的键值的。

134楼,packer,能得到你的认可,谢谢!
发表于 2011-8-30 18:13:16 | 显示全部楼层
马老师有空给我评点一下,看我是否学到家了,谢谢!
发表于 2011-8-30 19:58:23 | 显示全部楼层
我又来捣乱了,从zhanan 的图中还真看不到按键状态分的有多细,因为连击之后只要一直按着键发出的都是连击事件,这种做法并没有比只发长按键有什么根本上的改变,至少在这个帖子中的所有讨论都看不出这种做法的优势。


另外回复【134楼】 packer

一按下按键,数字加1;
继续长按2秒后,以3次/1秒速度数字加1;
继续长按5秒后,以10次/1秒速度数字加1;
继续长按5秒后,以3次/1秒速度数字加10

这种做法只有一个长按状态也是可以实现的,并不比用长按+连击的状态复杂多少。倒是在长按后还要多出个连击状态,程序还要在长按后开定时器判断是不是有连击事件发生,如果有连击发生还要处理“连击”到“无键”这个状态的切换。比只处理“长按”到“无键”的状态切换又多了一次处理。
发表于 2011-8-30 21:20:59 | 显示全部楼层
大家正常讨论,何来捣乱一说。

我感觉我们的分歧在任务如何划分上。
像上例,我是分成 单击,长按,连击1,连击2 (不要纠结于名字啦)四个状态。
而你只在本层出长按一种状态,在上层再细分。是不是这样?

另外,我为什么要处理“连击”或“长按”到无键状态?不理解
发表于 2011-8-30 21:52:12 | 显示全部楼层
回【138楼】 packer

确实如你所说,我们的分歧在任务如何划分上,当然这个仁者见仁智者见智了,也没什么好讨论了,“连击”或“长按”到无键状态,其实也是涉及到任务分层上,按我的分层方式,“连击”或“长按”发生后是要切换到无键状态,才能继续工作的,不然下一次的单击或者双击操作发生时就没法判断,可以参见我在【48楼】画的图,但是你说你的做法不需要这种切换,这个我也不是很理解,如果没有无键这个状态的话,那按键就只能在双击,单击,长按三种状态间转换,如一开始就没按键呢?按键处于什么状态呢?
发表于 2011-8-30 21:53:01 | 显示全部楼层
Mark先,有空再看。
发表于 2011-8-30 22:43:49 | 显示全部楼层
回复【139楼】igoal
-----------------------------------------------------------------------

明白你的意思了。是要到无键。
发表于 2011-8-30 22:53:43 | 显示全部楼层
马老师讲的很实在,中国就是缺少像马老师这样踏踏实实做事的人。
发表于 2011-8-30 23:10:50 | 显示全部楼层
回复【139楼】igoal
-----------------------------------------------------------------------

你是否把简单问题复杂化了。
按照马老师的题目,实现3种功能:单击+双击+长按。(这里的单击实际上是指短击,无键状态是隐含的,因为没有任务处理所以不把它作为一种功能)
因为要判断双击,所以松开按键后要作一个判断:第一次松开还是第二次松开。在双击时限内第二次松开,这个肯定是双击了。
第一次松开后没有再按键,超过双击时限,那么要作为一次短击处理。不论是双击处理还是短击处理,完了都回到无键状态
看我的图,按键状态可以分得很细,但不一定全都用上。
在这个题目里实际只用了短击+长按,然后两次短击又生出一个双击。
短击会吧,长按也不难吧,这两个都是在底层直接取得键值。那么就剩下双击了:第一次取得底层的短击键值,先扣着不放,然后启动双击计时器。若超时,说明没有第二次按键,就把扣着的那个短击键值放出来。若又来一个短击键值,并且在双击时限内,显然这是一个标准的双击了,给出双击键值。
 楼主| 发表于 2011-8-30 23:15:13 | 显示全部楼层
回复【137楼】igoal
我又来捣乱了,从zhanan 的图中还真看不到按键状态分的有多细,因为连击之后只要一直按着键发出的都是连击事件,这种做法并没有比只发长按键有什么根本上的改变,至少在这个帖子中的所有讨论都看不出这种做法的优势。
另外回复【134楼】 packer  
一按下按键,数字加1;  
继续长按2秒后,以3次/1秒速度数字加1;  
继续长按5秒后,以10次/1秒速度数字加1;  
继续长按5秒后,以3次/1秒速度数字加10
这种做法只有一个长按状态也是可以实现的,并不比用长按+连击的状态复杂多少。倒是在长按后还要多出个连击状态,程序还要在长按后开定时器判断是不是有连击事件发生,如果有连击发生还要处理“连击”到“无键”这个状态的切换。比只处理“长按”到“无键”的状态切换又多了一次处理。
-----------------------------------------------------------------------

单键的连_发功能,是单键长按的一种变化。

基本长按,是按键按下超过1秒,一旦超过1秒则返回一个“长按事件”,然后必须等待本次按键释放。如果按键按下3秒才释放,返回的也只是一次“长按事件”。

连_发是建立在长按的基础上,当按键按下超过1秒,一旦超过1秒则返回一个“连_发事件”,如果此时按键仍然按下,则每隔300ms返回一个“连_发事件”。因此如果按键也是按下3秒才释放,则在1秒后返回了大约7个“连_发事件”,每次间隔300ms。

应用事例1:PC的按键,在编辑输入状态,按下A健不放手,稍微等待后便输入一串“A”

应用事例2:手机键盘,短信输入,英文字符输入方式,按下2键不放,延时一段时间后,输入字母A-B-C-2,不停的自动转换,放开按键,则最后一个字母为确认的输入符号。

应用事例3:时钟时、分、秒的设置,比如设置在秒时,单击按键,秒的个位加1,按下按键不放,超过1秒后,每300ms秒的十位加1(参考我编写教材的例9.2)

以下是书里面的参考代码:(这个只用一个最底层,完成单击和连_发的判断)

下面是基于状态机方式编写的带“连_发”功能按键接口函数read_key_n(),每10ms调用一次。

#define key_input        PIND.7        // 按键输入口
#define key_state_0        0
#define key_state_1        1
#define key_state_2        2
#define key_state_3        3

unsigned char read_key_n(void)
{
    static unsigned char key_state = 0, key_time = 0;
    unsigned char key_press, key_return = 0;

    key_press = key_input;                                      // 读按键I/O电平
    switch (key_state)
    {
        case key_state_0:                                       // 按键初始态
            if (!key_press) key_state = key_state_1;            // 键被按下,状态转换到键确认态
            break;

        case key_state_1:                                       // 按键确认态
            if (!key_press)
            {
                key_state = key_state_2;                        // 按键仍按下,状态转换到计时1
                key_time = 0;                                   // 清另按键时间计数器
            }
            else key_state = key_state_0;                       // 按键已抬起,转换到按键初始态(此处完成按键消抖,按下和释放其实都在这里完成的)
            break;

        case key_state_2:
            if (key_press)
            {
                key_state = key_state_0;                        // 按键已释放,转换到按键初始态
                key_return = 1;                                 // 输出“单击”
            }
            else if (++key_time >= 100)                         // 按键时间计数
            {
                key_state = key_state_3;                        // 按下时间>1s,状态转换到计时2
                key_time = 0;                                   // 清按键计数器
                key_return = 2;                                 // 输出第一次“连_发事件”
            }
            break;
       
        case key_state_3:
            if (key_press)
                key_state = key_state_0;                        //按键已释放,转换到按键初始态
            else
            {
                if (++key_time >= 50)                           // 按键时间计数
                {
                    key_time = 0;                               // 按下时间>0.5s,清按键计数器
                    key_return = 2;                             // 输出又一次“连_发事件”
                 }
            }
            break;
    }       
    return key_return;
}

=====================================================

可以发现,与LZ位的代码变化不大,增加了一个状态,适当调整以下就行了,变量都不多一个。

你变我也变,但是我的基本结构不变,因此状态机+按键的扫描这样的结构,可以做到“以不变应万变”。
发表于 2011-8-30 23:29:34 | 显示全部楼层
回头看了下48楼你的状态图,确实搞复杂了,正规军的干活,小小的单片机还不够你布阵的。
马老师的这个题目非常好,把状态机用活了,可以活的很富态(堆大量的程序),也可以活的很精炼。
同学们一定要做好这道题,一举两得啊(状态机+多功能按键)。
 楼主| 发表于 2011-8-30 23:54:45 | 显示全部楼层
我编写的教材,尽管里面的代码是在AVR上跑的,但里面大量的分析方法、设计理念、编程思路是其它众多介绍51、AVR、甚至是32位单片机的书所没有的,处理和思考的角度都是非常全面的,如果认真学习和体会,收获会大大超过看其它的参考书。

    学习单片机没有任何的“捷径”,但在打基础的阶段,入门学习的开始,使用一本好书,可以让你在整个技术人生和发展生涯中少走很多的弯路,有着正确和坚实的基础,从事实际的设计应用和向高层次发展。

    由于我编写教材中,决大部分代码是C的代码,因此,不管你使用什么型号的单片机,哪怕你使用STM32,都可以参考我编写的教材。其实代码并不十分重要,关键是理解和掌握思想方法,和分析与解决问题的能力。

     我第2版教材中的应用,涉及到了SD卡、FAT文件系统、WAV文件结构、数字音频回放,这些都能在8位AVR上实现,而且与在32位系统上实现的方法是一样的。
发表于 2011-8-31 07:53:03 | 显示全部楼层
回【145楼】 machao
“应用事例1:PC的按键,在编辑输入状态,按下A健不放手,稍微等待后便输入一串“A” ”
这个至少在PS2键盘协议里面是没有区分长按和连_发的,因为你只要按下键发的都是按键的通码,只不过第一次发按键通码是在你按下按键以后,第二次发出就是所谓的长按,第三次之后才是所谓的连_发,但不管是哪个状态发出都只有一个,那就是按键的通码,而且长按的时间间隔和连_发的时间间隔其实是下载到PC机键盘中执行的,并不是PC机本身的操作系统控制的。

【144楼】 zhanan
头一回听说状态机还有隐藏状态,如果这样的话大家都不用画状态图了,直接就说我这个状态图都是隐藏的好了。无键状态在这个状态机中是个非常重要的状态,甚至比单击,连击这种状态还要重要,没有它真不知道后续按键怎么处理了。
发表于 2011-8-31 09:12:17 | 显示全部楼层
回复【148楼】igoal
回【145楼】 machao  
这个至少在ps2键盘协议里面是没有区分长按和连_发的,因为你只要按下键发的都是按键的通码,只不过第一次发按键通码是在你按下按键以后,第二次发出就是所谓的长按,第三次之后才是所谓的连_发,但不管是哪个状态发出都只有一个,那就是按键的通码,而且长按的时间间隔和连_发的时间间隔其实是下载到pc机键盘中执行的,并不是pc机本身的操作系统控制的。
-----------------------------------------------------------------------

1、“pc键盘芯片”处理按键是否要按马老师的程序思想做?ps2协议已经是我们这个程序的输出级了。每个按键松手还要发个“断码”呢
2、ps2协议只是现实世界万千按键应用的1种,你不能只按照这一种思路思考啊。
发表于 2011-8-31 10:23:48 | 显示全部楼层
对于俺这个初学者,这个帖子太及时了。
发表于 2011-8-31 11:17:25 | 显示全部楼层
先mark,谢谢
发表于 2011-8-31 22:04:04 | 显示全部楼层
mark
发表于 2011-9-1 00:38:54 | 显示全部楼层
回复【147楼】machao
  我第2版教材中的应用,涉及到了sd卡、fat文件系统、wav文件结构、数字音频回放,
-----------------------------------------------------------------------

您第2版教材全称是什么?谢谢
发表于 2011-9-1 01:07:53 | 显示全部楼层
回复【153楼】packer
回复【147楼】machao  
  我第2版教材中的应用,涉及到了sd卡、fat文件系统、wav文件结构、数字音频回放,
-----------------------------------------------------------------------
您第2版教材全称是什么?谢谢
-----------------------------------------------------------------------

马老师专栏的置顶帖里就有写了
《AVR单片机嵌入式系统原理与应用实践》(第2版)新书正式发行了。书号为:978-7-5124-0434-2,北京航空航天大学出版
发表于 2011-9-1 02:13:37 | 显示全部楼层
mark
发表于 2011-9-1 15:12:33 | 显示全部楼层
mark,支持马老师
发表于 2011-9-1 17:53:40 | 显示全部楼层
收藏学习
发表于 2011-9-1 19:01:56 | 显示全部楼层
学习中...
发表于 2011-9-2 12:31:46 | 显示全部楼层
受益!
发表于 2011-9-2 14:32:12 | 显示全部楼层
mark
发表于 2011-9-2 14:41:23 | 显示全部楼层
感谢马老师,我等茅塞顿开,凡是看透的帖子MARK!没看透的收藏夹伺候!
发表于 2011-9-2 15:18:55 | 显示全部楼层
mARK,感谢马老师。以前只会用中断实现过此功能,现在茅塞顿开啦。。
发表于 2011-9-2 15:33:12 | 显示全部楼层
双击一下子把按键复杂了一点,大家应该积极探讨此贴才更有意义。
按照马老师的程序,如果双击的时候,第二击在过了双击时限才松手,将会得到什么结果呢?

(原文件名:短击双击.jpg)
发表于 2011-9-2 16:01:36 | 显示全部楼层
这个问题分两步考虑
首先,要定义好这种情况应该按什么处理。这里就有用户体验感,不同的人会有不同要求。可以双击,可以长按,甚至无效。
第二步,才是如何实现。其实如能完全理解马老师的程序,这步是极其easy的。

我个人认为,这是一次双击。
发表于 2011-9-2 16:45:26 | 显示全部楼层
判断长按和短按比较实际,三种状态的没意义,容易产生误操作。设计要跟现实使用相结合。
发表于 2011-9-2 16:54:36 | 显示全部楼层
mark
发表于 2011-9-2 16:58:27 | 显示全部楼层
回复【164楼】packer
-----------------------------------------------------------------------

现在想深入探讨一下马老师的程序,引出一些有意义的东西。马老师的程序在于抛玉引砖,希望引来更多的砖头,好解决更多的实际问题。
如果第一次短击后,按下按键等到发生短击反应后再松手(在长按之前松手,分别将双击时限和长按时限调到1S和2S便于观察),将会发生又一次短击,这样的结果可能是不需要的吧。
另外,在这个题目里,双击里面判断短击,会造成延迟,可能还会比较严重。有办法改进吗?


【144楼】 zhanan  
头一回听说状态机还有隐藏状态,如果这样的话大家都不用画状态图了,直接就说我这个状态图都是隐藏的好了。无键状态在这个状态机中是个非常重要的状态,甚至比单击,连击这种状态还要重要,没有它真不知道后续按键怎么处理了。
-----------------------------------------------------------------------

我所说隐含的,是指功能隐含,你总不能把无键也作为一个功能吧。但是无键状态是必须的,每次按键判断都是从无键状态开始的。
发表于 2011-9-2 18:39:26 | 显示全部楼层
回复【167楼】zhanan
按下按键等到发生短击反应后再松手
-----------------------------------------------------------------------

如不是笔误就是逻辑错误。短击是松手后才能得出的结论
发表于 2011-9-2 18:50:35 | 显示全部楼层
回复【168楼】packer
-----------------------------------------------------------------------

可能是我描述的不清楚,因为把按键状态这样细分大概是史无前例的,我也是在探讨中,用词主要是用于区分不同的按键状态。

我所说的是马老师的程序:一次短击、时限内两次短击、长按都是没有问题的,符合题目要求。
            现在的问题:一次短击,然后再按下不放,从上次松开到双击时限,这时会发一个短击码。过了这个时限(当然短于长按)松手,等到双击时限的时候又会发一个短击码。就是说,这种按法,会发出两个短击码。

对还是不对?有必要讨论一下。
双击码肯定是要在时限内实现,这种犹豫的双击按法最好产生一次短击码,或者产生一个无键码。
发表于 2011-9-2 23:42:09 | 显示全部楼层
回复【169楼】zhanan
-----------------------------------------------------------------------

太困了,脑子不太清楚,大概说说我的看法。

第一次按键,因不知是短击还是长按,所以要等松手。所以第一次是“短击”。在双击时限内,第二次按下时,直接判定为双击,输出双击码,并等松手回无键状态。
貌似现在程序就这么做。

如果,第二次的按键也要判时间长度,看是否是长按,那就要事先规定好各种情况,分别如何处理。然后才能继续详细讨论下去。
 楼主| 发表于 2011-9-3 02:41:21 | 显示全部楼层
回复【163楼】zhanan
双击一下子把按键复杂了一点,大家应该积极探讨此贴才更有意义。
按照马老师的程序,如果双击的时候,第二击在过了双击时限才松手,将会得到什么结果呢?

(原文件名:短击双击.jpg)
-----------------------------------------------------------------------

此时我的代码返回的是两个单击事件。

仔细看底层代码,它返回的是“短按”和“长按”,区别在于
1:短按按下时间<1S,长按的按下时间>1s
2:短按返回是在按键释放,长按的返回在按下时间达到1S时就返回了(不用等释放)

而中间层是根据2次返回的“短按”间隔判断是否有双击的,由于底层的短按都是在按键释放才返回,因此这个双击间隔0.5s实际是两次按键释放的间隔<0.5s为一次“双击”。

你的这中按法,在第一次短按释放后,尽管马上又按下了按键,但在0.5s内没有释放,不返回第二短击,所以第1次为单击,后面的那个“比较长的”短按,也是作为1次单击的。

====================================================================
看看与定义有矛盾吗?
1。两次都是短按,因为按下时间<1s。程序不会出现长按事件

2。那么这两次的短按,只有2种可能:1次双击或2次单击。

在看我在LZ位的“双击”定义:
“双击事件:2次短按操作间隔时间<0.5s,则2次短按操作为1次双击事件,且2次短按都取消 ”
这个定义是面向用户的,表述也是明确的“2次短按操作间隔时间<0.5s”。对于用户“短按操作”是按一个键的整个过程(释放掉)。因此zhanan的按法,第2个操作没有在0.5秒内完成,所以不能作为双击操作。

实际使用中,这样的按键操作属于用户处在犹豫状态或思考问题状态,返回2次单击是合理的,也符合定义。

======================================================
由于是多功能键,要判断长按,那么短按只能在释放时才能被确认。如果没有长按功能的定义,这个延误可以没有。

由于要判断双击,那么第1次单击的确认需要在短按发生后延时0.5s。我的定义和代码为1个双击替代2个单击。你也可以将两次间隔时间短的短按操作定义成1次单击和1次双击,那么这个延误就可以取消。

以上通过调整代码都是可以做到的。
===========================================================

所以,我指出多功能按键的设计,首先在于你是否能明确的理清楚各个功能之间的关系,然后才是程序的设计。
发表于 2011-9-3 19:52:23 | 显示全部楼层
“我指出多功能按键的设计,首先在于你是否能明确的理清楚各个功能之间的关系,然后才是程序的设计。”马老师所言极是,所以按键处理没有通用程序可以直接抄来就用,特别是少键多功能,都是从各种按法中选择几种和功能挂钩。
比如双击功能,packer却是这样定义的:
第一次按键,因不知是短击还是长按,所以要等松手。所以第一次是“短击”。在双击时限内,第二次按下时,直接判定为双击,输出双击码,并等松手回无键状态。
分析:第一次单击,先扣下,如果是长按,发长按码,然后等放开。
                          如果在双击时限内又一次单击,发双击码,然后等放开。
                          如果在双击时限内没有再一次单击,时限到时,发单击码。
      其他情况均为无键。
      单击和双击限制在双击时限内。

不多说了,抛砖。
/***************/
uchar key_driver(void)   // 读键盘
{
  static uchar key_state=0, key_time=0;  // 状态、按键计时
  uchar key_press,key_return;  // 按键值、返回事件

  if(key_input) key_press=0; else key_press=1; // 读引脚:松开0,按下为1。
  key_return=0;    // 返回事件清0

  switch (key_state)
  {  
      case 0:    // 按键初始态  
        if (key_press==1) key_state=1;      // 键被按下,状态转换到按键消抖和确认状态  
        break;  
      case 1:    // 按键消抖与确认态  
        if (key_press==1)  
        {  
             key_time =0;   //   
             key_state=2;   // 按键仍然处于按下,消抖完成,状态转换到按下键时间的计时状态
             key_return=11; // 【返回单击事件11】  
        }  
        else  
             key_state=0;   // 按键已抬起,转换到按键初始态。此处完成和实现软件消抖,其实按键的按下和释放都在此消抖的。  
        break;  
      case 2:
        if(key_press!=1)    // 按键释放
        {  
             key_return=1;  // 返回按键释放事件
             key_state =0;  // 转换到按键初始态  
        }  
        else if(++key_time >= 50)  // 继续按下,计时加40ms(40ms为本函数循环执行间隔)  
        {  
             key_return=3;  // 按下时间>2000ms,此按键为长按操作,返回长键事件  
             key_state =3;  // 转换到等待按键释放状态
        }  
        break;  

      case 3:               // 等待按键释放  
        if (key_press!=1) key_state =0; //按键已释放,转换到按键初始态  
        break;  
    }  
    return key_return;  
}  
/*=============  
中间层按键处理函数,调用低层函数一次,处理双击事件的判断,返回上层正确的无键、单键、双键、长键4个按键事件。  
本函数由上层循环调用,间隔40ms  
===============*/  
unsigned char key_read(void)  
{  
    static unsigned char key_m =0, key_time_1 = 0;  
    unsigned char key_return =0,key_temp;  
      
    key_temp = key_driver();  // 读键码
      
    switch(key_m)  
    {  
        case 0:  
            if (key_temp ==11 )    // 第1次单击
            {  
                 key_time_1 = 0;   // 初始化双击计时  
                 key_m = 1;        // 转下一步
            }  
            break;  

        case 1:                    // 第一次单击后的按键变化
            if(++key_time_1<12)    // 双击时限内12x40=480mS
            {
              if (key_temp ==1)    // 按键松开  
              {  
                 key_m=2;          // 切换到双击判断
              }  
            }
            else                   // 双击时限外
            {
              if(key_temp==1) {key_m=0;}                // 按键松开,回初始态
              if(key_temp==3) {key_return=3; key_m=0;}  // 有长按,返回长键码,然后回初始态
            }
            break;

        case 2:                    // 双击判断
            if(++key_time_1<12)    // 时限内480mS
            {
              if(key_temp==11)     // 再一次单击
              {
                 key_return=2;     // 返回双击码
                 key_m=0;
              }
            }
            else                   // 时限外
            {
              key_return=1;        // 无第二次单击,返回单击码
              key_m=0;
            }
            break;
    }
    return key_return;  
}
/***************/

(程序改编自马老师的程序)
发表于 2011-9-3 20:25:21 | 显示全部楼层
图示,反应要快些。

(原文件名:单击双击.jpg)
 楼主| 发表于 2011-9-3 21:20:34 | 显示全部楼层
楼上中间的图,如果第2次按下的时间超过1秒才释放,算什么?1次双击还是1次单击+1次长按?
发表于 2011-9-3 22:30:33 | 显示全部楼层
我觉得,这个帖子的目的已经达到,初学者如果能认真看马老师的程序,并融会贯通,就能掌握核心概念,以后灵活应用。

象马老师楼上问的,我觉得不能一概而论,要根据实际系统情况,选择最合适的处理方式。
我想到有这么几种
1、双击
2、单击+长按
3、仅长按
4、无效(这个好像不太合理)
还有一种,仅单击无长按,我觉得更不合理,就不列其中
但不论什么处理方式,只要定义好规则,我们都能应用本帖的概念,轻松实现。
发表于 2011-9-3 22:34:06 | 显示全部楼层
回复【3楼】linghu2 令狐二中
-----------------------------------------------------------------------

正解,用一个定时器,什么都搞定了
发表于 2011-9-3 22:44:15 | 显示全部楼层
楼上的真有意思,就用1个定时器裸奔,你能实现?
来来来,给我们演示演示。
发表于 2011-9-4 06:46:49 | 显示全部楼层
回复【175楼】machao
楼上中间的图,如果第2次按下的时间超过1秒才释放,算什么?1次双击还是1次单击+1次长按?
-----------------------------------------------------------------------

按照packer的要求,双击是判断按键的前沿,第二击只要按下就算双击,作出双击反应,过了时限(即使过了长按时限)松开,算无键。

回复【176楼】packer
我觉得,这个帖子的目的已经达到,初学者如果能认真看马老师的程序,并融会贯通,就能掌握核心概念,以后灵活应用。
象马老师楼上问的,我觉得不能一概而论,要根据实际系统情况,选择最合适的处理方式。
我想到有这么几种
1、双击
2、单击+长按
3、仅长按
4、无效(这个好像不太合理)
还有一种,仅单击无长按,我觉得更不合理,就不列其中
但不论什么处理方式,只要定义好规则,我们都能应用本帖的概念,轻松实现。
-----------------------------------------------------------------------

我觉得该帖才刚入正题,帖子前部分妄想和windows及手机带os的系统去比较,这样比下去只有无解。我想把探讨引入到按键状态图和程序上,针对具体功能,实现程序,对初学者才真正有帮助,我想这也是马老师特开此帖并置顶的初衷。
当然我一个人的能力是有限的,需要众人支持,特别的初学者应该踊跃参与,现在条件对初学者很有利:
1. 马老师目前将此帖作为一个重点,半夜三更还回帖,多令人感动。
2. 此程序不挑单片机,资源要求简单之极,状态变化又多,多好的编程实例。现在还只是单键多功能,多键多功能呢?如何优化?
3. 这个也是学习状态机的好帖,如何从各种状态中找到满足功能需求的状态,然后用程序来实现。
发表于 2011-9-4 07:12:41 | 显示全部楼层
172楼的程序应该是比较实用的,快速按两下肯定是双击,只按一下松手<0.5S算单击,一犹豫过了0.5S算无效(无键),一直按着到长按时限算长键(为方便实验,把长按改为2秒)。双击时限从按键前沿开始算,感觉反应比较快。
和马老师的原程序比,把按键的前沿利用起来了,按键一旦确定按下了,底层程序先发一个11。( key_return=11; // 【返回单击事件11】 )
发表于 2011-9-5 15:29:13 | 显示全部楼层
怎么没有人拍砖?我拍几块金砖大家看看成色如何?

在135楼,我说过:状态表的形式可不一定仅仅是状态编号,计时器的值也表明一定的状态!在按键的底层和中层程序都用到了计时器,可以利用这个特点对程序优化一下:
【172楼】的底层程序优化一下:

uchar key_driver(void)   // 读键盘
{
  static uchar key_last, key_time;  // 上次键值、按键计时
  uchar key_press,key_return=0;  // 按键值、返回码

  if(key_input) key_press=0; else key_press=1; // 读引脚:松开0,按下为1。

  if(key_press!=key_last) {key_last=key_press; key_time=0;}  // 新键值
  if(key_time==1) key_return=(key_press==1)?11:1;  // 按下及松开
  if(key_time==50 && key_press==1) key_return=3;   // 长按
//  if(key_time==55 && key_press==1) key_return=4;   // 连按
  if(++key_time>60) key_time=55;  // 最长限制(或连按间隔)
  return key_return;  
}  

看起来还像是用状态机吗?只用了一句就把连按加上了,按键前后都带消抖的。
发表于 2011-9-5 15:31:35 | 显示全部楼层
在占一楼加分。

把【172】楼的中层程序也优化一下:

/*=============  
中间层按键处理函数,调用低层函数一次,处理双击事件的判断,返回上层正确的无键、单键、双键、长键4个按键事件。  
本函数由上层循环调用,间隔40ms  
===============*/  
unsigned char key_read(void)  
{  
    static unsigned char key_last =0, key_time_1 = 0;  
    unsigned char key_return =0,key_temp;  
      
    key_temp = key_driver();  // 读键码

    if(key_time_1==0)         // 初始态
    {
      if(key_temp==11) key_time_1++;  // 启动双击计时
    }
    else if(++key_time_1<12) // 双击时限内
    {
      if(key_temp==11) {key_temp=key_time_1=0; key_return=2;}  // 时限内又按下,双击
    }
    else                     // 双击时限到
    {
      if(key_last==1) key_return=1;  // 单击
      key_time_1=0;
    }
    if(key_temp==3&&key_last==11) key_return=3;   // 长按,直接返回
    if(key_temp) key_last=key_temp; // 保存本次键码

    return key_return;  
}      
/***/
发表于 2011-9-5 15:35:16 | 显示全部楼层
斗胆把马老师的程序也优化一下吧!

马老师的底层程序优化:
uchar key_driver(void)   // 读键盘
{
  static uchar key_last=0, key_time=2;  // 上次键值、按键计时
  uchar key_press,key_return=0;  // 按键值、返回码

   if(key_input) key_press=0; else key_press=1; // 读引脚:松开0,按下为1。

  if(key_press!=key_last) {key_last=key_press; key_time=0;}  // 新键值
  if(key_time==1) key_return=(key_press==1)?11:1;  // 按下及松开
  if(key_time==50 && key_press==1) key_return=3;   // 长按
//  if(key_time==55 && key_press==1) key_return=4;   // 连按
  if(++key_time>60) key_time=55;  // 最长限制(或连按间隔)
  return key_return;  
}

改了一个变量名:key_state改为key_last,为了更贴切些。
发表于 2011-9-5 15:40:01 | 显示全部楼层
马老师的中层程序优化:

/*=============  
中间层按键处理函数,调用低层函数一次,处理双击事件的判断,返回上层正确的无键、单键、双键、长键4个按键事件。  
本函数由上层循环调用,间隔40ms  
===============*/  
unsigned char key_read(void)  // 单击双击都是后沿起作用
{   
    static unsigned char key_last =0, key_time_1 = 0;   
    unsigned char key_return =0,key_temp;   
      
    key_temp = key_driver();  // 读键码

    if(key_time_1==0)         // 初始态
    {
      if(key_temp==1&&key_last==11) key_time_1++;    // 启动双击计时
    }
    else if(++key_time_1<12) // 双击时限内
    {
      if(key_temp==1) {key_return=2; key_time_1=0;} // 时限内松开,双击
    }
    else                     // 双击时限到
    {
      if(key_last==1) key_return=1;  // 单击
      key_last=key_time_1=0;
    }
    if(key_temp==3&&key_last==11) key_return=3;   // 长按,直接返回
    if(key_temp) key_last=key_temp; // 保存本次键码

    return key_return;   
}
/***/

也改了一个变量名:key_m改为key_last,为了更贴切些。并且第二击,如果过了双击时限才松手,算无效。长按必须一直按着直到长键时刻,中间不能松开。
发表于 2011-9-5 15:45:47 | 显示全部楼层
【181楼】和【183楼】都是按键底层程序,竟然是一样的了,通用了!用不到的功能把对应的那句删掉即可。多键也能在这个基础上很容易实现。

终于60分及格了。
发表于 2011-9-5 19:19:56 | 显示全部楼层
马老师的双击的第二击改为用前沿,这样双击反应能快些。

unsigned char key_read(void)  // 双击的第二击前沿起作用
{  
    static unsigned char key_last =0, key_time_1 = 0;  
    unsigned char key_return =0,key_temp;  
      
    key_temp = key_driver();  // 读键码

    if(key_time_1==0)         // 初始态
    {
      if(key_temp==1&&key_last==11) key_time_1++;    // 启动双击计时
    }
    else if(++key_time_1<12) // 双击时限内
    {
      if(key_temp==11) {key_return=2; key_temp=key_time_1=0;} // 时限内又按下,双击
    }
    else                     // 双击时限到
    {
      key_return=1;          // 单击
      key_time_1=0;
    }
    if(key_temp==3&&key_last==11) key_return=3;   // 长按,直接返回
    if(key_temp) key_last=key_temp; // 保存本次键码

    return key_return;  
}      

/***/
发表于 2011-9-5 22:40:25 | 显示全部楼层
mark
发表于 2011-9-5 23:04:24 | 显示全部楼层
多谢,mark!
发表于 2011-10-5 19:37:54 | 显示全部楼层
单击 + 双击 + 长按
如何实现连_发
发表于 2011-10-5 22:16:05 | 显示全部楼层
这个不错。 按键功能。 双击,单击,长按。
发表于 2011-10-5 23:02:15 | 显示全部楼层
发表于 2011-10-6 17:47:25 | 显示全部楼层
回复【1楼】yuzr
mark
-----------------------------------------------------------------------
发表于 2011-10-9 19:35:55 | 显示全部楼层
谢谢马老师,认真学习!
发表于 2011-10-9 20:41:18 | 显示全部楼层
G00D
发表于 2011-10-9 21:35:16 | 显示全部楼层
mark 双击,单击,长按
发表于 2011-10-9 22:01:57 | 显示全部楼层
学习了
发表于 2011-10-9 22:20:56 | 显示全部楼层
mark!!!以后看!!!!谢谢楼主分享!!!!
发表于 2011-10-9 22:59:58 | 显示全部楼层
马克了再细看
发表于 2011-10-9 23:01:27 | 显示全部楼层
mark
友情提示:标题不合格、重复发帖,将会被封锁ID。详情请参考:论坛通告:封锁ID、获得注册邀请码、恢复被封ID、投诉必读
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2019-9-15 18:51

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

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

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