野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 34193|回复: 11

STM32按键的另一种实现——状态机

[复制链接]
发表于 2016-9-8 12:54:34 | 显示全部楼层 |阅读模式
  1. #ifndef _KEY_H_
  2. #define _KEY_H_

  3. #include "HAL_gpio.h" // 换成STM32F103对应的GPIO库
  4. #include "type.h"     // type.h主要是一些类型的重命名

  5. #define KEY_UP_GRP         GPIOA
  6. #define KEY_UP_IDX         GPIO_Pin_9
  7. #define KEY_UP_IS_DOWN()   GPIO_ReadInputDataBit(KEY_UP_GRP, KEY_UP_IDX)
  8. #define KEY_UP_CONFIG()    GPIOConfig(KEY_UP_GRP, KEY_UP_IDX, GPIO_Mode_IPU) // 这个函数我在之前帖子里面写过

  9. #define KEY_DOWN_GRP       GPIOA
  10. #define KEY_DOWN_IDX       GPIO_Pin_10
  11. #define KEY_DOWN_IS_DOWN() GPIO_ReadInputDataBit(KEY_DOWN_GRP, KEY_DOWN_IDX)
  12. #define KEY_DOWN_CONFIG()  GPIOConfig(KEY_DOWN_GRP, KEY_DOWN_IDX, GPIO_Mode_IPU)

  13. #define KEY_FUNC_GRP       GPIOA
  14. #define KEY_FUNC_IDX       GPIO_Pin_11
  15. #define KEY_FUNC_IS_DOWN() GPIO_ReadInputDataBit(KEY_FUNC_GRP, KEY_FUNC_IDX)
  16. #define KEY_FUNC_CONFIG()  GPIOConfig(KEY_FUNC_GRP, KEY_FUNC_IDX, GPIO_Mode_IPU)

  17. #define KEY_TURN_GRP       GPIOA
  18. #define KEY_TURN_IDX       GPIO_Pin_12 | GPIO_Pin_13
  19. #define KEY_TURN_IS_DOWN() GPIO_ReadInputDataBit(KEY_TURN_GRP, KEY_TURN_IDX)
  20. #define KEY_TURN_CONFIG()  GPIOConfig(KEY_TURN_GRP, KEY_TURN_IDX, GPIO_Mode_IPU)

  21. //====================================================================================
  22. typedef enum
  23. {
  24.     CONFIRM_KEY = 1,
  25.     FUNC_KEY,
  26.     UP_KEY,
  27.     DOWN_KEY
  28. } key_event_t;

  29. #define state_keyUp      0       //初始状态,未按键
  30. #define state_keyDown       1       //键被按下
  31. #define state_keyLong  2       //长按
  32. #define state_keyTime   3       //按键计时态

  33. #define return_keyUp         0x00    //初始状态
  34. #define return_keyPressed   0x01    //键被按过,普通按键
  35. #define return_keyLong     0x02    //长按
  36. #define return_keyAuto     0x04    //自动连发

  37. #define key_down              0       //按下
  38. #define key_up                  0xf0    //未按时的key有效位键值
  39. #define key_longTimes      100     //10ms一次,200次即2秒,定义长按的判定时间
  40. #define key_autoTimes      20      //连发时间定义,20*10=200,200毫秒发一次


  41. #define KEYS1_VALUE          0xe0    //keyS1 按下
  42. #define KEYS2_VALUE          0xd0    //keyS2 按下
  43. #define KEYS3_VALUE          0xb0    //keyS3 按下
  44. #define KEYS4_5_VALUE      0x70    //keyS4_5 按下

  45. //====================================================================================
  46. void KeyProcess(void);  //T0定时器调用的工作函数
  47. void KeyTimerInit(void);

  48. #endif /* _KEY_H_ */
复制代码
  1. #include <stdio.h>
  2. #include "key.h"
  3. #include "timer.h" // STM32F103定时器的配置

  4. static uint8_t
  5. key_get(void)       //获取P3口值
  6. {
  7.     if(KEY_UP_IS_DOWN() == key_down)
  8.     {
  9.          return KEYS1_VALUE;
  10.     }
  11.     if(KEY_DOWN_IS_DOWN() == key_down)
  12.     {
  13.         return KEYS2_VALUE;
  14.     }
  15.     if(KEY_FUNC_IS_DOWN() == key_down)
  16.     {
  17.          return KEYS3_VALUE;
  18.     }
  19.     if(KEY_TURN_IS_DOWN() == key_down)
  20.     {
  21.         return KEYS4_5_VALUE;
  22.     }

  23.     return key_up ;    //0xf0  没有任何按键
  24. }

  25. //函数每20ms被调用一次,而我们弹性按键过程时一般都20ms以上
  26. //所以每次按键至少调用本函数2次
  27. static uint8_t
  28. key_read(uint8_t* pKeyValue)
  29. {
  30.     static uint8_t  s_u8keyState = 0;        //未按,普通短按,长按,连发等状态
  31.     static uint16_t s_u16keyTimeCounts = 0;  //在计时状态的计数器
  32.     static uint8_t  s_u8LastKey = key_up ; //保存按键释放时的P3口数据

  33.     uint8_t keyTemp = 0;              //键对应io口的电平
  34.     int8_t key_return = 0;          //函数返回值
  35.     keyTemp = key_up & key_get();  //提取所有的key对应的io口

  36.     switch(s_u8keyState)           //这里检测到的是先前的状态
  37.     {
  38.     case state_keyUp:   //如果先前是初始态,即无动作
  39.     {
  40.         if(key_up != keyTemp) //如果键被按下
  41.         {
  42.             s_u8keyState = state_keyDown; //更新键的状态,普通被按下
  43.         }
  44.     }
  45.     break;

  46.     case state_keyDown: //如果先前是被按着的
  47.     {
  48.          if(key_up != keyTemp) //如果现在还被按着
  49.          {
  50.               s_u8keyState = state_keyTime; //转换到计时态
  51.               s_u16keyTimeCounts = 0;
  52.               s_u8LastKey = keyTemp;     //保存键值
  53.          }
  54.          else
  55.          {
  56.                s_u8keyState = state_keyUp; //键没被按着,回初始态,说明是干扰
  57.          }
  58.     }
  59.     break;

  60.     case state_keyTime:  //如果先前已经转换到计时态(值为3)
  61.     {
  62.         //如果真的是手动按键,必然进入本代码块,并且会多次进入
  63.         if(key_up == keyTemp) //如果未按键
  64.         {
  65.             s_u8keyState = state_keyUp;
  66.             key_return = return_keyPressed;    //返回1,一次完整的普通按键
  67.            //程序进入这个语句块,说明已经有2次以上10ms的中断,等于已经消抖
  68.           //那么此时检测到按键被释放,说明是一次普通短按
  69.        }
  70.        else  //在计时态,检测到键还被按着
  71.        {
  72.            if(++s_u16keyTimeCounts > key_longTimes) //时间达到2秒
  73.            {
  74.                  s_u8keyState = state_keyLong;  //进入长按状态
  75.                  s_u16keyTimeCounts = 0;      //计数器清空,便于进入连发重新计数
  76.                  key_return = return_keyLong;   //返回state_keyLong
  77.            }
  78.            //代码中,在2秒内如果我们一直按着key的话,返回值只会是0,不会识别为短按或长按的
  79.        }
  80.     }
  81.     break;

  82.     case state_keyLong:  //在长按状态检测连发  ,每0.2秒发一次
  83.     {
  84.         if(key_up == keyTemp)
  85.         {
  86.             s_u8keyState = state_keyUp;
  87.          }
  88.          else //按键时间超过2秒时
  89.         {
  90.              if(++s_u16keyTimeCounts > key_autoTimes)//10*20=200ms
  91.              {
  92.                   s_u16keyTimeCounts = 0;
  93.                   key_return = return_keyAuto;  //每0.2秒返回值的第2位置位(1<<2)
  94.              }//连发的时候,肯定也伴随着长按
  95.         }
  96.         key_return |= return_keyLong;  //0x02是肯定的,0x04|0x02是可能的
  97.     }
  98.     break;

  99.     default:
  100.         break;
  101.     }
  102.     *pKeyValue = s_u8LastKey ; //返回键值
  103.     return key_return;
  104. }

  105. // 这个函数就是要在中断中调用的。主要是使用事件队列的方式。
  106. void
  107. KeyProcess(void)
  108. {
  109.     uint8_t key_stateValue;
  110.     uint8_t keyValue = 0;
  111.     uint8_t* pKeyValue = &keyValue;

  112.     key_stateValue = key_read(pKeyValue);

  113.     if ((return_keyPressed == key_stateValue) && (*pKeyValue == KEYS1_VALUE))
  114.     {
  115.         //短按keyS1时改变对时状态,将其加入队列,队列的基本操作在将队列的时候写过。
  116.         if(QueueEventIsEmpty(g_state_manager.process->key_event) ||
  117.          (!QueueEventIsEmpty(g_state_manager.process->key_event) &&
  118.          !QUEUE_EVENT_IS_EQUEL(g_state_manager.process->key_event, UP_KEY)))
  119.          {
  120.              QueueEventPush(g_state_manager.process->key_event, UP_KEY);
  121.          }
  122.      }
  123. }

  124. //======================================================================================
  125. void
  126. KeyTimerInit(void)
  127. {
  128.     KEY_UP_CONFIG();
  129.     KEY_DOWN_CONFIG();
  130.     KEY_FUNC_CONFIG();
  131.     KEY_TURN_CONFIG();

  132.     // 20ms定时器中断配置
  133.     TimerConfig(KEY_TIMER, KEY_TIMER_DIV, KEY_TIMER_PERIOD);
  134.     TimerDisable(KEY_TIMER);
  135.     TimerEnable(KEY_TIMER);
  136. }
复制代码
主要是描述一下按键状态机的思维,使用定时器中断的方法,按键按下将其加入队列中,在主函数的循环中实现出队。亲测可用。
【blog.csdn.net/wqx521】
回复

使用道具 举报

发表于 2016-9-8 13:47:39 | 显示全部楼层
不错不错,之前也有人发过状态机的实现,也可以结合这篇来学习:
有限状态机的使用
http://www.firebbs.cn/forum.php? ... 3881&fromuid=64
(出处: 野火论坛)
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-9-8 14:24:12 | 显示全部楼层
flyleaf 发表于 2016-9-8 13:47
不错不错,之前也有人发过状态机的实现,也可以结合这篇来学习:
有限状态机的使用
http://www.firebbs.c ...

看到了。写的也是不错的。
回复 支持 反对

使用道具 举报

发表于 2016-9-8 15:10:39 | 显示全部楼层
咦,学习下,用状态机真是一个很好的思路
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-9-8 15:29:56 | 显示全部楼层
左丘冰 发表于 2016-9-8 15:10
咦,学习下,用状态机真是一个很好的思路

不需要做延时的操作。
回复 支持 反对

使用道具 举报

发表于 2016-9-9 12:11:44 | 显示全部楼层
v5,最近正在看这方面的资料
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-9-9 12:45:18 | 显示全部楼层
满地落叶est 发表于 2016-9-9 12:11
v5,最近正在看这方面的资料

按键按下,触发中断,然后加入队列。在主循环中出队就行了。
回复 支持 反对

使用道具 举报

发表于 2016-9-12 12:14:14 | 显示全部楼层
1838039453 发表于 2016-9-9 12:45
按键按下,触发中断,然后加入队列。在主循环中出队就行了。

嗯嗯,其实很多东西都是在无形中用了状态机,比如ucos的进程调度
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-9-12 18:04:05 | 显示全部楼层
满地落叶est 发表于 2016-9-12 12:14
嗯嗯,其实很多东西都是在无形中用了状态机,比如ucos的进程调度

是啊。状态机这东西很好。这个思想好。
回复 支持 反对

使用道具 举报

发表于 2016-9-12 19:01:59 | 显示全部楼层
状态机主要就是用的查询吧,那如果中断够用 为什么还要使用状态机呢?
回复 支持 反对

使用道具 举报

发表于 2016-9-12 20:43:05 | 显示全部楼层
好东西,谢谢分享!
回复 支持 反对

使用道具 举报

发表于 2021-8-21 08:27:58 | 显示全部楼层
谢谢楼主分享
回复 支持 反对

使用道具 举报

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

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-4-29 18:21 , Processed in 0.030177 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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