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

  • #ifdef __cplusplus
  • extern "C" {
  • #endif

  • #include "key_driver.h"
  • #include <string.h>

  • #define __KEY_EVENT_CALL(event)                 \
  •     do {                                        \
  •         if (key->event_callback[event]) {       \
  •             key->event_callback[event](key);    \
  •         }                                       \
  •     } while (0U)


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

  • /**
  • * @brief   按键对象状态
  • */
  • enum key_state {
  •   Init_None_State = 0,        /* 初始未按下状态 */
  •   Init_Press_State,           /* 初次按下状态 */
  •   Press_Check_State,          /* 连击检查状态 */
  •   Continuous_Press_State,     /* 连续按下状态 */
  •   Long_Press_State,           /* 长按状态 */
  • };

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

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

  • /* 按键管理函数接口 */
  • int8_t key_init(struct key_handle *key, uint8_t (*gpio_pin_read)(void), uint8_t active_level);
  • int8_t key_handle_register(struct key_handle *key);
  • int8_t key_handle_detach(struct key_handle *key);
  • int8_t key_event_callback_register(struct key_handle *key, uint8_t event, void (* event_callback)(key_handle_t key));
  • void key_tick(void);

  • #ifdef __cplusplus
  • }
  • #endif
  • #endif /* __KEY_MANAGE_H */

  • 复制代码
    按键管理C函数为:
    #include "key_manage.h"

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

  • /**
  • * @brief   初始化按键对象
  • * @param   key             按键对象句柄
  • * @param   gpio_pin_read   获取按键电平函数指针
  • * @param   active_level    按键按下有效电平
  • * @return  0: succeed -1: failed
  • */
  • int8_t key_init(struct key_handle *key, uint8_t (*gpio_pin_read)(void), uint8_t active_level)
  • {
  •   if (key == NULL)
  •     return -1;
  •   memset(key, 0, sizeof(struct key_handle));
  •   key->event = None_Press;
  •   key->active_level = active_level;
  •   key->pin_read = gpio_pin_read;
  •   key->key_level = key->pin_read();

  •   return 0;
  • }

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

  •   if (key == NULL)
  •     return -1;

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

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

  •   return 0;
  • }

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

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

  •   if (*key_slist_node != key)
  •     return -1;
  •   *key_slist_node = (*key_slist_node)->next;

  •   return 0;
  • }

  • /**
  • * @brief   注册按键事件触发回调函数
  • * @param   key             按键对象句柄
  • * @param   event           触发事件类型
  • * @param   event_callback  事件回调函数
  • * @return  0: succeed -1: failed
  • */
  • int8_t key_event_callback_register(struct key_handle *key, uint8_t event, void (* event_callback)(key_handle_t key))
  • {
  •   if (key == NULL || event >= Event_Sum)
  •     return -1;
  •   key->event_callback[event] = event_callback;

  •   return 0;
  • }

  • /**
  • * @brief   处理所有按键对象的状态机
  • * @param   key 按键对象句柄
  • * @return  None
  • */
  • static void key_handler(struct key_handle *key)
  • {
  •   uint8_t key_level_temp = key->pin_read();

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

  •   /* 按键消抖(按键状态发生变化保持DEBOUNCE_TICK时间片开始保存按键引脚电平) */
  •   if(key_level_temp != key->key_level)
  •   {
  •     if(++(key->debounce_tick) >= Double_Click)
  •     {
  •       key->key_level = key_level_temp;
  •       key->debounce_tick = 0;
  •     }
  •   }
  •   else
  •   {
  •     key->debounce_tick = 0;
  •   }

  •   /* 按键状态机 */
  •   switch (key->state)
  •   {
  •   case Init_None_State:
  •     /* 初始态-> 初始按下态  Press_Down */
  •     if(key->key_level == key->active_level)
  •     {
  •       key->event = (uint8_t)Press_Down;
  •       __KEY_EVENT_CALL(Press_Down);
  •       key->tick  = 0;
  •       key->repeat_cnt = 1;
  •       key->state  = Init_Press_State;
  •     }
  •     else
  •     {
  •       key->event = (uint8_t)None_Press;
  •     }
  •     break;

  •   case Init_Press_State:
  •     /* 第一次按下松开:初始按下态->连击检查态  Press_Up */
  •     if(key->key_level != key->active_level)
  •     {
  •       key->event = (uint8_t)Press_Up;
  •       __KEY_EVENT_CALL(Press_Up);
  •       key->tick = 0;
  •       key->state = Press_Check_State;

  •     }
  •     /* 第一次按下后长按(>LONG_PRESS_START_TICK):初始按下态->长按态  Long_Press_Start */
  •     else if(key->tick > LONG_PRESS_START_TICK)
  •     {
  •       key->event = (uint8_t)Long_Press_Start;
  •       __KEY_EVENT_CALL(Long_Press_Start);
  •       key->state = Long_Press_State;
  •     }
  •     break;

  •   case Press_Check_State:
  •     /* 松开后再次按下:连击检查态->连击态 Press_Down & Short_Press_Repeat */
  •     if(key->key_level == key->active_level)
  •     {
  •       key->event = (uint8_t)Press_Down;
  •       __KEY_EVENT_CALL(Press_Down);
  •       key->repeat_cnt++;
  •       __KEY_EVENT_CALL(Short_Press_Repeat);
  •       key->tick = 0;
  •       key->state = Continuous_Press_State;
  •     }
  •     /* 松开后再次没有按下(>SHORT_PRESS_START_TICK):连击检查态->初始态 repeat_cnt=1: Singe_Click; repeat_cnt=2: Double_Click */
  •     else if(key->tick > SHORT_PRESS_START_TICK)
  •     {
  •       if(key->repeat_cnt == 1)
  •       {
  •         key->event = (uint8_t)Singe_Click;
  •         __KEY_EVENT_CALL(Singe_Click);
  •       }
  •       /* 连击态松开后会返回此条件下触发  todo: <可以做n连击判断> */
  •       else if(key->repeat_cnt == 2)
  •       {
  •         key->event = (uint8_t)Double_Click;
  •         __KEY_EVENT_CALL(Double_Click);
  •       }
  •       key->state = Init_None_State;
  •     }
  •     break;

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

  •       if(key->tick < SHORT_PRESS_START_TICK)
  •       {
  •         key->tick = 0;
  •         key->state = Press_Check_State;
  •       }
  •       else
  •       {
  •         key->state = Init_None_State;
  •       }
  •     }
  •     /* 连击后长按(>SHORT_TICKS): 连击态 -> 初始态 */
  •     else if(key->tick > SHORT_PRESS_START_TICK)
  •     {
  •       key->state = Init_Press_State;  // 可以回到Init_None_State/Init_Press_State
  •     }
  •     break;

  •   case Long_Press_State:
  •     /* 长按保持  Long_Press_Hold */
  •     if(key->key_level == key->active_level)
  •     {
  •       key->event = (uint8_t)Long_Press_Hold;
  •       if (key->tick % LONG_HOLD_CYCLE_TICK == 0)
  •       {
  •         __KEY_EVENT_CALL(Long_Press_Hold);
  •       }
  •     }
  •     /* 长按松开:长按态-> 初始态 */
  •     else
  •     {
  •       key->event = (uint8_t)Press_Up;
  •       __KEY_EVENT_CALL(Press_Up);

  •       key->state = Init_None_State;
  •     }
  •     break;
  •   }
  • }

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

  •   if (++tick_cnt < CHECK_TICK)
  •     return;

  •   for (key_slist_node = _key_slist_head; key_slist_node != NULL; key_slist_node = key_slist_node->next)
  •   {
  •     key_handler(key_slist_node);
  •   }
  •   tick_cnt = 0;
  • }
  • 复制代码
    2、引入驱动层函数:
    #include "key_driver.h"
  • #include "me32f103_gpio.h"
  • uint8_t key_sw1_read_pin(void)
  • {
  •   return GPIO_GetPinState(PA, IO_BIT_PIN0);
  • }
  • 复制代码
    3、在Main.c中初始化对象,并实现按键中断回调函数:
    static void key1_sw2_callback(key_handle_t key)
  • {
  •   switch (key->event) {
  •                 case Singe_Click:
  •                         tick++;
  •                         flag = 1;
  •                         break;
  •                 case Long_Press_Hold:
  •                         tick = 0;
  •                         flag = 1;
  •                         break;
  •         }
  • }
  • 复制代码

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

  •     key_event_callback_register(&_key1_sw2, Singe_Click,           key1_sw2_callback);

  •     key_event_callback_register(&_key1_sw2, Long_Press_Hold,    key1_sw2_callback);
  •   key_handle_register(&_key1_sw2);
  • 复制代码

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

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

  • void CTIM0_IRQHandler(void)
  • {
  •         key_tick();
  •         return;
  • }
  • 复制代码
    工程编译后,即中实现功能,详细结果见视频。