找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 12956|回复: 22
收起左侧

基于状态机的按键长按,短按,双击 单片机源程序

  [复制链接]
ID:573901 发表于 2019-8-23 14:41 | 显示全部楼层 |阅读模式
先前根据以为前辈的程序理解了一段时间,之后自己写了一份控制led灯的简单按键程序

单片机源程序如下:
  1. #include<stc15w202s.h>
  2. #include<stdio.h>
  3. #define key_state_0 0
  4. #define key_state_1 1
  5. #define key_state_2 2
  6. #define key_state_3 3
  7. #define key_no 0
  8. #define key_click 1
  9. #define key_double 2
  10. #define key_long 3
  11. sbit KEY = P3^3;
  12. sbit LED_E1P = P3^1;
  13. sbit LED_G1 = P3^2;
  14. sbit LED2 = P5^5;        //W2OUT
  15. sbit LED3 = P5^4;  
  16. unsigned char flag;
  17. unsigned char cnt = 0;


  18. static unsigned char key_driver(void)
  19. {
  20.         static unsigned char key_state_buffer1 = key_state_0;
  21.         static unsigned char key_timer_cnt1 = 0;
  22.         unsigned char key_return = key_no;
  23.         unsigned char key;
  24.        
  25.         key = KEY;  //read the I/O states
  26.        
  27.         switch(key_state_buffer1)
  28.         {
  29.                 case key_state_0:
  30.                         if(key == 0)
  31.                                 key_state_buffer1 = key_state_1;
  32.                                 //按键被按下,状态转换到按键消抖和确认状态//
  33.                         break;
  34.                 case key_state_1:
  35.                         if(key == 0)
  36.                         {
  37.                                 key_timer_cnt1 = 0;
  38.                                 key_state_buffer1 = key_state_2;
  39.                                 //按键仍然处于按下状态
  40.                                 //消抖完成,key_timer开始准备计时
  41.                                 //状态切换到按下时间计时状态
  42.                         }
  43.                         else
  44.                                 key_state_buffer1 = key_state_0;
  45.                                 //按键已经抬起,回到按键初始状态
  46.                         break;  //完成软件消抖
  47.                 case key_state_2:
  48.                         if(key == 1)
  49.                         {
  50.                                 key_return = key_click;  //按键抬起,产生一次click操作
  51.                                 key_state_buffer1 = key_state_0;  //转换到按键初始状态
  52.                         }
  53.                         else if(++key_timer_cnt1 >= 100)  //按键继续按下,计时超过1000ms
  54.                         {
  55.                                 key_return = key_long;  //送回长按事件
  56.                                 key_state_buffer1 = key_state_3;  //转换到等待按键释放状态
  57.                         }
  58.                         break;
  59.                 case key_state_3:  //等待按键释放
  60.                         if(key == 1)  //按键释放
  61.                                 key_state_buffer1 = key_state_0;  //切回按键初始状态
  62.                         break;
  63.         }
  64.         return key_return;
  65. }

  66. unsigned char key_read(void)
  67. {
  68.         static unsigned char key_state_buffer2 = key_state_0;
  69.         static unsigned char key_timer_cnt2 = 0;
  70.         unsigned char key_return = key_no;
  71.         unsigned char key;
  72.        
  73.         key = key_driver();
  74.        
  75.         switch(key_state_buffer2)
  76.         {
  77.                 case key_state_0:
  78.                         if(key == key_click)
  79.                         {
  80.                                 key_timer_cnt2 = 0;  //第一次单击,不返回,到下个状态判断是否会出现双击
  81.                                 key_state_buffer2 = key_state_1;
  82.                         }
  83.                         else
  84.                                 key_return = key;  //对于无键、长按,返回原事件
  85.                         break;
  86.                 case key_state_1:
  87.                         if(key == key_click)  //又一次单击,时间间隔小于500ms
  88.                         {
  89.                                 key_return = key_double;  //返回双击事件,回到初始状态
  90.                                 key_state_buffer2 = key_state_0;
  91.                         }
  92.                         else if(++key_timer_cnt2 >= 50)
  93.                         {   
  94.                         //这里在下一次的按键来临之前,并且时间是小于500ms的时候,就会一直执行的是这个key_timer_cnt2++.直到下一次的按键到来,再判断看是双击还是单击。
  95.                                 //这里500ms内肯定读到的都是无键事件,因为长按大于1000ms
  96.                                 //在1s前底层返回的都是无键
  97.                                                                        
  98.                                 key_return = key_click;  //500ms内没有再次出现单击事件,返回单击事件
  99.                                 key_state_buffer2 = key_state_0;  //返回初始状态
  100.                                        
  101.                         }
  102.                         break;
  103.         }
  104.        
  105.         return key_return;
  106. }

  107. void Timer0Init(void)                //1毫秒@11.0592MHz
  108. {
  109.         AUXR |= 0x80;                //定时器时钟1T模式
  110.         TMOD &= 0xF0;                //设置定时器模式
  111.         TL0 = 0xCD;                //设置定时初值
  112.         TH0 = 0xD4;                //设置定时初值
  113.         TF0 = 0;                //清除TF0标志
  114.         TR0 = 1;                //定时器0开始计时
  115.         ET0 = 1;

  116. }
  117. void IO_init()
  118. {
  119.    
  120.     P3M0 = 0x01;
  121.     P3M1 = 0x01;
  122.     P5M0 = 0x00;
  123.     P5M1 = 0x00;
  124.    
  125. }


  126. void main(void)
  127. {          
  128.        unsigned char key = 1;
  129.              Timer0Init();
  130.            IO_init();
  131.            LED_E1P = 1;
  132.            LED_G1 = 1;
  133.            LED2 = 1;
  134.            LED3 = 0;
  135.                    EA = 1;
  136.            while(1)
  137.            {
  138.              if(flag)
  139.                  {
  140.                    flag=0;
  141.            key = key_read();
  142.                    switch(key)
  143.                    {
  144.                             case key_click:LED2 = !LED2; break;
  145.                          case key_double:LED2 = !LED2;LED3 = !LED3; break;
  146.                          case key_long: LED2 = 1; LED3 = 1; LED_G1 = 0; LED_E1P = 0; break;
  147.                          default : break;
  148.                    }
  149.                  }
  150.            }
  151. }

  152. void Timr0_ISR() interrupt 1
  153. {
  154.    cnt++;
  155.    if(cnt>=10)
  156.    {
  157.        cnt = 0;
  158.               flag = 1;
  159.    }
  160. }
复制代码

以上资料51hei提供下载:
基于状态机的按键长按短按双击控制程序.zip (1.87 KB, 下载次数: 367)


评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:405604 发表于 2020-1-13 16:51 | 显示全部楼层
程序写的非常完美,没毛病。提一个存在的小问题,在定时器中断函数Timr0_ISR()里面要重装定时器初值,不然主函数第一次初始化定时器以后定时器溢出,会从0~65535计数,并且一直这样,这样的cnt++就不能计时10ms了,导致前面的按键都不能用,以上本人STC89C52亲测,这个问题头疼了2天后来发现是没有重装初值的问题。其他不懂也没学过!希望后来的朋友不要走弯路,楼主加油!
回复

使用道具 举报

ID:377382 发表于 2020-2-9 17:22 | 显示全部楼层
51需要改不少的地方
回复

使用道具 举报

ID:334781 发表于 2020-2-15 21:20 | 显示全部楼层
谢谢楼主,下载试一下。对于按键的长按与短按,一直是困或我的问题,先谢谢了!
回复

使用道具 举报

ID:465279 发表于 2020-3-7 12:39 | 显示全部楼层
wxqhigh 发表于 2020-1-13 16:51
程序写的非常完美,没毛病。提一个存在的小问题,在定时器中断函数Timr0_ISR()里面要重装定时器初值,不然 ...

仔细看了代码并测试,回复没毛病。正在学习按键处理这块,向你学习。
回复

使用道具 举报

ID:448556 发表于 2020-3-10 15:48 | 显示全部楼层
是啊不重装初值 定时中断只运行一次就停止了
回复

使用道具 举报

ID:573901 发表于 2020-6-1 10:37 | 显示全部楼层
wxqhigh 发表于 2020-1-13 16:51
程序写的非常完美,没毛病。提一个存在的小问题,在定时器中断函数Timr0_ISR()里面要重装定时器初值,不然 ...

这个是stc15w系列的单片机,定时器用的是16位自动重装载,所以不需要从新赋初值了,不过还是谢谢提醒,一起加油!
回复

使用道具 举报

ID:573901 发表于 2020-6-1 10:39 | 显示全部楼层
liyezhao2019 发表于 2020-3-10 15:48
是啊不重装初值 定时中断只运行一次就停止了

嗯嗯 ,怪我没注意,51系列的要装初值,我这个是stc系列的芯片,定时器0是16位自动重装载的,一起加油
回复

使用道具 举报

ID:573901 发表于 2020-6-1 10:42 | 显示全部楼层

嗯,是的,手上没有51的板子,所以用了stc的程序写的,不过这个按键的状态机是可以用的,其他注意51的重装初值问题。
回复

使用道具 举报

ID:389488 发表于 2020-9-22 15:27 | 显示全部楼层
wxqhigh 发表于 2020-1-13 16:51
程序写的非常完美,没毛病。提一个存在的小问题,在定时器中断函数Timr0_ISR()里面要重装定时器初值,不然 ...

stc15不用重装初值
回复

使用道具 举报

ID:610086 发表于 2020-11-27 10:59 | 显示全部楼层
单击的时候500ms检测,会不会有些卡顿感?
回复

使用道具 举报

ID:367948 发表于 2021-2-18 12:04 | 显示全部楼层
谢谢楼主,正需要单片机检测连续按下按键程序
回复

使用道具 举报

ID:275826 发表于 2021-2-26 18:05 | 显示全部楼层
程序可以,就是太复杂了,可以简化很多
回复

使用道具 举报

ID:508724 发表于 2021-3-10 09:51 | 显示全部楼层
这个状态机写的很不错,nice
回复

使用道具 举报

ID:430492 发表于 2021-3-23 11:40 | 显示全部楼层
写得不错,是个很好的参考例程。
回复

使用道具 举报

ID:839438 发表于 2021-6-3 21:38 | 显示全部楼层
谢谢,正在学习状态机
回复

使用道具 举报

ID:935260 发表于 2021-6-10 10:03 | 显示全部楼层
针对特定的单片机写程序和通用的单片机程序,还是有差别
回复

使用道具 举报

ID:601234 发表于 2021-10-20 16:53 | 显示全部楼层
key_read  和  key_drive可以合并吗
回复

使用道具 举报

ID:601234 发表于 2021-10-20 16:54 | 显示全部楼层
key_read和key_drive可以合并吗
回复

使用道具 举报

ID:821788 发表于 2022-8-28 15:19 | 显示全部楼层
感谢!感谢!
key_read和key_drive可以尝试合并,少用一个状态机、少占用一个byte RAM
回复

使用道具 举报

ID:59300 发表于 2023-9-11 09:28 | 显示全部楼层
很好的文章,对我正在学习状态机非常有帮助,谢谢
回复

使用道具 举报

ID:1127891 发表于 2024-7-2 14:35 | 显示全部楼层
楼主这么nb
回复

使用道具 举报

ID:786399 发表于 2024-7-2 21:11 | 显示全部楼层
受益匪浅
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

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