【实现功能】
使用板载的按键,来实现一个按键计数器,每次单击按键时,计数器加1,长按按键,清除计数。
【实现步骤】
1、先确定原理图中按键的引脚:
image.png
因此我们初始化按键:PA_INIT_AS_INPUT(IO_BIT_PIN0);
2、首先我们引入按键的BSP驱动,实现面向对象的管理:
image.png
其中按键管理为:
  1. #ifndef __KEY_MANAGE_H__
  2. #define __KEY_MANAGE_H__

  3. #ifdef __cplusplus
  4. extern "C" {
  5. #endif

  6. #include "key_driver.h"
  7. #include <string.h>

  8. #define __KEY_EVENT_CALL(event)                 \
  9.     do {                                        \
  10.         if (key->event_callback[event]) {       \
  11.             key->event_callback[event](key);    \
  12.         }                                       \
  13.     } while (0U)


  14. #define CHECK_TICK                      5                      // 按键状态机检查间隔时间(ms)
  15. #define DEBOUNCE_TICK                   (15 / CHECK_TICK)      // 消抖时间(ms) [最大40/5且DEBOUNCE_TICK < SHORT_PRESS_START_TICK/2]
  16. #define SHORT_PRESS_START_TICK          (300  / CHECK_TICK)    // 连击态触发间隔时间(ms) [最大40 / 5]
  17. #define LONG_PRESS_START_TICK           (1000 / CHECK_TICK)    // 长按态触发时间(ms)
  18. #define LONG_HOLD_CYCLE_TICK            (500 / CHECK_TICK)     // 长按态保持下连续触发长按事件的间隔(ms)

  19. /**
  20. * @brief   按键对象状态
  21. */
  22. enum key_state {
  23.   Init_None_State = 0,        /* 初始未按下状态 */
  24.   Init_Press_State,           /* 初次按下状态 */
  25.   Press_Check_State,          /* 连击检查状态 */
  26.   Continuous_Press_State,     /* 连续按下状态 */
  27.   Long_Press_State,           /* 长按状态 */
  28. };

  29. /**
  30. * @brief   按键对象事件
  31. */
  32. enum key_event {
  33.   Press_Down = 0,             /* 按键按下,每次按下都触发 */
  34.   Press_Up,                   /* 按键弹起,每次松开都触发 */
  35.   Singe_Click,                /* 单击触发(仅触发一次) */
  36.   Double_Click,               /* 双击触发(仅触发一次) */
  37.   Short_Press_Repeat,         /* 每次短按时都会触发(按下次数>=2) */
  38.   Long_Press_Start,           /* 首次进入长按状态触发(仅触发一次) */
  39.   Long_Press_Hold,            /* 长按保持状态触发(每经过一个循环长按间隔触发一次) */
  40.   Event_Sum,                  /* 事件总数 */
  41.   None_Press                  /* 未按下 */
  42. };

  43. /**
  44. * @brief   按键对象句柄结构体
  45. */
  46. struct key_handle
  47. {
  48.   uint16_t tick;                                                    /* 按键系统时间片 */
  49.   uint8_t repeat_cnt     : 4;                                       /* 按键短按次数 */
  50.   uint8_t event                : 4;                                       /* 触发事件 */
  51.   uint8_t state          : 3;                                       /* 按键状态 */
  52.   uint8_t debounce_tick  : 3;                                       /* 消抖时间片 */
  53.   uint8_t active_level   : 1;                                   /* 按键有效按下电平 */
  54.   uint8_t key_level      : 1;                                       /* 按键引脚当前电平 */
  55.   uint8_t (* pin_read)(void);                                       /* 获取按键引脚电平 */
  56.   void (* event_callback[Event_Sum])(struct key_handle* key);   /* 按键事件回调函数 */
  57.   struct key_handle* next;                                      /* 单向链表next指针 */
  58. };
  59. typedef struct key_handle *key_handle_t;

  60. /* 按键管理函数接口 */
  61. int8_t key_init(struct key_handle *key, uint8_t (*gpio_pin_read)(void), uint8_t active_level);
  62. int8_t key_handle_register(struct key_handle *key);
  63. int8_t key_handle_detach(struct key_handle *key);
  64. int8_t key_event_callback_register(struct key_handle *key, uint8_t event, void (* event_callback)(key_handle_t key));
  65. void key_tick(void);

  66. #ifdef __cplusplus
  67. }
  68. #endif
  69. #endif /* __KEY_MANAGE_H */

按键管理C函数为:
  1. #include "key_manage.h"

  2. static key_handle_t _key_slist_head = NULL;  // 按键管理单链表头结点

  3. /**
  4. * @brief   初始化按键对象
  5. * @param   key             按键对象句柄
  6. * @param   gpio_pin_read   获取按键电平函数指针
  7. * @param   active_level    按键按下有效电平
  8. * @return  0: succeed -1: failed
  9. */
  10. int8_t key_init(struct key_handle *key, uint8_t (*gpio_pin_read)(void), uint8_t active_level)
  11. {
  12.   if (key == NULL)
  13.     return -1;
  14.   memset(key, 0, sizeof(struct key_handle));
  15.   key->event = None_Press;
  16.   key->active_level = active_level;
  17.   key->pin_read = gpio_pin_read;
  18.   key->key_level = key->pin_read();

  19.   return 0;
  20. }

  21. /**
  22. * @brief   注册按键:将按键对象插入到按键管理链表中
  23. * @param   key 按键对象句柄
  24. * @return  0: succeed -1: failed
  25. */
  26. int8_t key_handle_register(struct key_handle *key)
  27. {
  28.   struct key_handle *key_slist_node = _key_slist_head;  // 获取头指针的地址 (无头结点单链表)

  29.   if (key == NULL)
  30.     return -1;

  31.   // 尾插(不带头结点的单链表, 头指针需做特殊判断)
  32.   if (_key_slist_head == NULL) // 头指针为空==表空
  33.   {
  34.     _key_slist_head = key;
  35.     key->next = NULL;
  36.     return 0;
  37.   }

  38.   while(key_slist_node)
  39.   {
  40.     if (key_slist_node == key) return -1;       // 重复注册
  41.     if(key_slist_node->next == NULL) break;     // 已经遍历到最后一个节点,必须在此跳出循环, 否则key_slist_node==NULL
  42.     key_slist_node = key_slist_node->next;
  43.   }
  44.   key_slist_node->next = key;
  45.   key->next = NULL;

  46.   return 0;
  47. }

  48. /**
  49. * @brief   脱离按键:将按键对象从按键管理链表中脱离
  50. * @param   key 按键对象句柄
  51. * @return  0: succeed -1: failed
  52. */
  53. int8_t key_handle_detach(struct key_handle *key)
  54. {
  55.   // 解1级引用指向指针变量, 解2级引用指向指针变量所指向的变量
  56.   struct key_handle **key_slist_node = &_key_slist_head;  // 指向头指针, 直接操作原指针变量(不然最后无法修改头指针)
  57.   struct key_handle *node_temp;
  58.   if (key == NULL || _key_slist_head == NULL)
  59.     return -1;

  60.   while(*key_slist_node && *key_slist_node != key)
  61.   {
  62.     node_temp = *key_slist_node;
  63.     if((*key_slist_node)->next == NULL) break;
  64.     key_slist_node = &node_temp->next;     // 不能直接解1级引用赋值,会破坏原链表
  65.   }

  66.   if (*key_slist_node != key)
  67.     return -1;
  68.   *key_slist_node = (*key_slist_node)->next;

  69.   return 0;
  70. }

  71. /**
  72. * @brief   注册按键事件触发回调函数
  73. * @param   key             按键对象句柄
  74. * @param   event           触发事件类型
  75. * @param   event_callback  事件回调函数
  76. * @return  0: succeed -1: failed
  77. */
  78. int8_t key_event_callback_register(struct key_handle *key, uint8_t event, void (* event_callback)(key_handle_t key))
  79. {
  80.   if (key == NULL || event >= Event_Sum)
  81.     return -1;
  82.   key->event_callback[event] = event_callback;

  83.   return 0;
  84. }

  85. /**
  86. * @brief   处理所有按键对象的状态机
  87. * @param   key 按键对象句柄
  88. * @return  None
  89. */
  90. static void key_handler(struct key_handle *key)
  91. {
  92.   uint8_t key_level_temp = key->pin_read();

  93.   if(key->state != Init_None_State) key->tick++;

  94.   /* 按键消抖(按键状态发生变化保持DEBOUNCE_TICK时间片开始保存按键引脚电平) */
  95.   if(key_level_temp != key->key_level)
  96.   {
  97.     if(++(key->debounce_tick) >= Double_Click)
  98.     {
  99.       key->key_level = key_level_temp;
  100.       key->debounce_tick = 0;
  101.     }
  102.   }
  103.   else
  104.   {
  105.     key->debounce_tick = 0;
  106.   }

  107.   /* 按键状态机 */
  108.   switch (key->state)
  109.   {
  110.   case Init_None_State:
  111.     /* 初始态-> 初始按下态  Press_Down */
  112.     if(key->key_level == key->active_level)
  113.     {
  114.       key->event = (uint8_t)Press_Down;
  115.       __KEY_EVENT_CALL(Press_Down);
  116.       key->tick  = 0;
  117.       key->repeat_cnt = 1;
  118.       key->state  = Init_Press_State;
  119.     }
  120.     else
  121.     {
  122.       key->event = (uint8_t)None_Press;
  123.     }
  124.     break;

  125.   case Init_Press_State:
  126.     /* 第一次按下松开:初始按下态->连击检查态  Press_Up */
  127.     if(key->key_level != key->active_level)
  128.     {
  129.       key->event = (uint8_t)Press_Up;
  130.       __KEY_EVENT_CALL(Press_Up);
  131.       key->tick = 0;
  132.       key->state = Press_Check_State;

  133.     }
  134.     /* 第一次按下后长按(>LONG_PRESS_START_TICK):初始按下态->长按态  Long_Press_Start */
  135.     else if(key->tick > LONG_PRESS_START_TICK)
  136.     {
  137.       key->event = (uint8_t)Long_Press_Start;
  138.       __KEY_EVENT_CALL(Long_Press_Start);
  139.       key->state = Long_Press_State;
  140.     }
  141.     break;

  142.   case Press_Check_State:
  143.     /* 松开后再次按下:连击检查态->连击态 Press_Down & Short_Press_Repeat */
  144.     if(key->key_level == key->active_level)
  145.     {
  146.       key->event = (uint8_t)Press_Down;
  147.       __KEY_EVENT_CALL(Press_Down);
  148.       key->repeat_cnt++;
  149.       __KEY_EVENT_CALL(Short_Press_Repeat);
  150.       key->tick = 0;
  151.       key->state = Continuous_Press_State;
  152.     }
  153.     /* 松开后再次没有按下(>SHORT_PRESS_START_TICK):连击检查态->初始态 repeat_cnt=1: Singe_Click; repeat_cnt=2: Double_Click */
  154.     else if(key->tick > SHORT_PRESS_START_TICK)
  155.     {
  156.       if(key->repeat_cnt == 1)
  157.       {
  158.         key->event = (uint8_t)Singe_Click;
  159.         __KEY_EVENT_CALL(Singe_Click);
  160.       }
  161.       /* 连击态松开后会返回此条件下触发  todo: <可以做n连击判断> */
  162.       else if(key->repeat_cnt == 2)
  163.       {
  164.         key->event = (uint8_t)Double_Click;
  165.         __KEY_EVENT_CALL(Double_Click);
  166.       }
  167.       key->state = Init_None_State;
  168.     }
  169.     break;

  170.   case Continuous_Press_State:
  171.     /* 连击后松开:连击态->连击检查态(< SHORT_PRESS_START_TICK)) : 连击态->初始态(>= SHORT_PRESS_START_TICK) */
  172.     if(key->key_level != key->active_level)
  173.     {
  174.       key->event = (uint8_t)Press_Up;
  175.       __KEY_EVENT_CALL(Press_Up);

  176.       if(key->tick < SHORT_PRESS_START_TICK)
  177.       {
  178.         key->tick = 0;
  179.         key->state = Press_Check_State;
  180.       }
  181.       else
  182.       {
  183.         key->state = Init_None_State;
  184.       }
  185.     }
  186.     /* 连击后长按(>SHORT_TICKS): 连击态 -> 初始态 */
  187.     else if(key->tick > SHORT_PRESS_START_TICK)
  188.     {
  189.       key->state = Init_Press_State;  // 可以回到Init_None_State/Init_Press_State
  190.     }
  191.     break;

  192.   case Long_Press_State:
  193.     /* 长按保持  Long_Press_Hold */
  194.     if(key->key_level == key->active_level)
  195.     {
  196.       key->event = (uint8_t)Long_Press_Hold;
  197.       if (key->tick % LONG_HOLD_CYCLE_TICK == 0)
  198.       {
  199.         __KEY_EVENT_CALL(Long_Press_Hold);
  200.       }
  201.     }
  202.     /* 长按松开:长按态-> 初始态 */
  203.     else
  204.     {
  205.       key->event = (uint8_t)Press_Up;
  206.       __KEY_EVENT_CALL(Press_Up);

  207.       key->state = Init_None_State;
  208.     }
  209.     break;
  210.   }
  211. }

  212. /**
  213. * @brief   每经过一个滴答周期调用一次按键处理函数(裸机放1ms中断, OS放线程或中断)
  214. * @param   None
  215. * @return  None
  216. */
  217. void key_tick(void)
  218. {
  219.   struct key_handle *key_slist_node;
  220.   static uint8_t tick_cnt = 0;

  221.   if (++tick_cnt < CHECK_TICK)
  222.     return;

  223.   for (key_slist_node = _key_slist_head; key_slist_node != NULL; key_slist_node = key_slist_node->next)
  224.   {
  225.     key_handler(key_slist_node);
  226.   }
  227.   tick_cnt = 0;
  228. }
2、引入驱动层函数:
  1. #include "key_driver.h"
  2. #include "me32f103_gpio.h"
  3. uint8_t key_sw1_read_pin(void)
  4. {
  5.   return GPIO_GetPinState(PA, IO_BIT_PIN0);
  6. }
3、在Main.c中初始化对象,并实现按键中断回调函数:
  1. static void key1_sw2_callback(key_handle_t key)
  2. {
  3.   switch (key->event) {
  4.                 case Singe_Click:
  5.                         tick++;
  6.                         flag = 1;
  7.                         break;
  8.                 case Long_Press_Hold:
  9.                         tick = 0;
  10.                         flag = 1;
  11.                         break;
  12.         }
  13. }

4、注册按键:
  1. LED_Init (COM0_3,CATHODE,0);
  2.         key_init(&_key1_sw2, key_sw1_read_pin, 0);

  3.     key_event_callback_register(&_key1_sw2, Singe_Click,           key1_sw2_callback);

  4.     key_event_callback_register(&_key1_sw2, Long_Press_Hold,    key1_sw2_callback);
  5.   key_handle_register(&_key1_sw2);

5、为了实现按键检测,我这里使用了一个定时器,每1ms进入中断一次,实现代码如下:
  1. CTIM_Init(CTIM0, 100000); //timer counter clk as 10khz,
  2.        
  3.         CTIM_ConfigMatch0(CTIM0, 100, TIM_MATCH_TRIGGER_INT|TIM_MATCH_RESET_COUNTER,TIM_MATCH_OUT_DO_NOTHING); //trigger int at ever 10ms
  4.        
  5.         NVIC_EnableIRQ(CTIM0_IRQn);

  6.         TIM_START(CTIM0);
6、中断回调函数如下,在中断中对计数按键进行检测:

  1. void CTIM0_IRQHandler(void)
  2. {
  3.         key_tick();
  4.         return;
  5. }
工程编译后,即中实现功能,详细结果见视频。