lulugl 发表于 2023-10-25 09:32

【AT-START-F423测评】移植一个优秀的按键程序

本帖最后由 lulugl 于 2023-10-25 09:34 编辑

#申请原创# #有奖活动#@21ic小跑堂
【按键驱动】
单片机在输入设备中,按键是最简单经济可靠的设备之一。单按键可以直接由GPIO来驱动,采集他的高低电平来实现按键状态的功能,但是在实际中,按键的状态获取,要考虑到消抖情况,如果单一按键要通过单击、双击、连击、长按等来实现结合功能,那就实现起来就非常复杂,效果也不是非常好。
在github中我找到了一个开源的按键驱动,Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在 HYPERLINK "https://github.com/RT-Thread/rt-thread" 上测试过。 写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。https://github.com/jiejieTop/ButtonDrive
【实现方式】
下载github源码
复制一份示例的printf工程到一个新的文件夹中,重命名为printf_button。

解压源码后复制button.c/h到工程文件的middlewares下的button文件夹中。

在工程文件中新增button组,把button.c加入button组中。

添加头文件的引用。

在button.c中添加at32f423_board.h头文件的引用。

在button.c中实现按键电平的获取自定义函数,在模版工程中,官方在at32f423_board.c中实现的按键的初始化,我们直接引用就可以实现按键状态的获取:
/* 获取按键电平 */

uint8_t Read_KEY1_Level(void)

{

return at32_button_state();

}
在main.c中,我们实现用户的单击、双击、长按释放的功能回调函数,代码如下:
Button_t Button1;

void Btn1_Dowm_CallBack(void *btn)

{

printf("Button1 单击!\r\n");

}

void Btn1_Double_CallBack(void *btn)

{

printf("Button1 双击!\r\n");

}

void Btn1_Long_CallBack(void *btn)

{

printf("Button1 长按!\r\n");

}

void Btn1_ContinuosFree_CallBack(void *btn)

{

printf("Button1 连按释放!\r\n");

}
在主函中,我们注册需要实现的功能函数,我这里添加单击、双击、长按、长按释放,代码如下:
Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack); //单击

Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack); //双击

Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack); //长按BUTTON_LONG_FREE,

Button_Attach(&Button1,BUTTON_LONG_FREE,Btn1_ContinuosFree_CallBack);
在while中添加按键检测函数:
Button_Process(); //需要周期调用按键处理函数
到此,移植结束,下载到开发板上可以实现相应的功能,按下开发板的按键,就在串口打印出相应的状态:

button.c完整程序如下:
/************************************************************
* @brief   按键驱动
      * @param   NULL
* @returnNULL
* @authorjiejie
* @Githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @NOTE    button.c
***********************************************************/
#include "button.h"
#include "at32f423_board.h"
#include "stdio.h"
#include "string.h"
/*******************************************************************
*                        变量声明                              
*******************************************************************/

static struct button* Head_Button = NULL;


/*******************************************************************
*                         函数声明   
*******************************************************************/
static char *StrnCopy(char *dst, const char *src, uint32_t n);
static void Print_Btn_Info(Button_t* btn);
static void Add_Button(Button_t* btn);


/************************************************************
* @brief   按键创建
      * @param   name : 按键名称
      * @param   btn : 按键结构体
* @param   read_btn_level : 按键电平读取函数,需要用户自己实现返回uint8_t类型的电平
* @param   btn_trigger_level : 按键触发电平
* @returnNULL
* @authorjiejie
* @Githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @NOTE    NULL
***********************************************************/
void Button_Create(const char *name,
                  Button_t *btn,
                  uint8_t(*read_btn_level)(void),
                  uint8_t btn_trigger_level)
{
if( btn == NULL)
{
    printf("struct button is null!");
    //ASSERT(ASSERT_ERR);
}

memset(btn, 0, sizeof(struct button));//清除结构体信息,建议用户在之前清除

StrnCopy(btn->Name, name, BTN_NAME_MAX); /* 创建按键名称 */


btn->Button_State = NONE_TRIGGER;         //按键状态
btn->Button_Last_State = NONE_TRIGGER;      //按键上一次状态
btn->Button_Trigger_Event = NONE_TRIGGER;   //按键触发事件
btn->Read_Button_Level = read_btn_level;    //按键读电平函数
btn->Button_Trigger_Level = btn_trigger_level;//按键触发电平
btn->Button_Last_Level = btn->Read_Button_Level(); //按键当前电平
btn->Debounce_Time = 0;

printf("button create success!");

Add_Button(btn);          //创建的时候添加到单链表中

Print_Btn_Info(btn);   //打印信息

}

/************************************************************
* @brief   按键触发事件与回调函数映射链接起来
      * @param   btn : 按键结构体
      * @param   btn_event : 按键触发事件
* @param   btn_callback : 按键触发之后的回调处理函数。需要用户实现
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
***********************************************************/
void Button_Attach(Button_t *btn,Button_Event btn_event,Button_CallBack btn_callback)
{
      uint8_t i;
if( btn == NULL)
{
    printf("struct button is null!");
    //ASSERT(ASSERT_ERR);       //断言
}

if(BUTTON_ALL_RIGGER == btn_event)
{
    for(i = 0 ; i < number_of_event-1 ; i++)
      btn->CallBack_Function = btn_callback; //按键事件触发的回调函数,用于处理按键事件
}
else
{
    btn->CallBack_Function = btn_callback; //按键事件触发的回调函数,用于处理按键事件
}
}

/************************************************************
* @brief   删除一个已经创建的按键
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @note    NULL
***********************************************************/
void Button_Delete(Button_t *btn)
{
struct button** curr;
for(curr = &Head_Button; *curr;)
{
    struct button* entry = *curr;
    if (entry == btn)
    {
      *curr = entry->Next;
    }
    else
    {
      curr = &entry->Next;
    }
}
}

/************************************************************
* @brief   获取按键触发的事件
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
***********************************************************/
void Get_Button_EventInfo(Button_t *btn)
{
      uint8_t i;
//按键事件触发的回调函数,用于处理按键事件
for(i = 0 ; i < number_of_event-1 ; i++)
{
    if(btn->CallBack_Function != 0)
    {
      printf("Button_Event:%d",i);
    }      
}
}

uint8_t Get_Button_Event(Button_t *btn)
{
return (uint8_t)(btn->Button_Trigger_Event);
}

/************************************************************
* @brief   获取按键触发的事件
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
***********************************************************/
uint8_t Get_Button_State(Button_t *btn)
{
return (uint8_t)(btn->Button_State);
}

/************************************************************
* @brief   按键周期处理函数
* @param   btn:处理的按键
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @note    必须以一定周期调用此函数,建议周期为20~50ms
***********************************************************/
void Button_Cycle_Process(Button_t *btn)
{
uint8_t current_level = (uint8_t)btn->Read_Button_Level();//获取当前按键电平

if((current_level != btn->Button_Last_Level)&&(++(btn->Debounce_Time) >= BUTTON_DEBOUNCE_TIME)) //按键电平发生变化,消抖
{
      btn->Button_Last_Level = current_level; //更新当前按键电平
      btn->Debounce_Time = 0;               //确定了是按下
      
      //如果按键是没被按下的,改变按键状态为按下(首次按下/双击按下)
      if((btn->Button_State == NONE_TRIGGER)||(btn->Button_State == BUTTON_DOUBLE))
      {
      btn->Button_State = BUTTON_DOWM;
      }
      //释放按键
      else if(btn->Button_State == BUTTON_DOWM)
      {
      btn->Button_State = BUTTON_UP;
      TRIGGER_CB(BUTTON_UP);    // 触发释放
//      printf("释放了按键");
      }
}

switch(btn->Button_State)
{
    case BUTTON_DOWM :            // 按下状态
    {
      if(btn->Button_Last_Level == btn->Button_Trigger_Level) //按键按下
      {
      #if CONTINUOS_TRIGGER   //支持连续触发

      if(++(btn->Button_Cycle) >= BUTTON_CONTINUOS_CYCLE)
      {
          btn->Button_Cycle = 0;
          btn->Button_Trigger_Event = BUTTON_CONTINUOS;
          TRIGGER_CB(BUTTON_CONTINUOS);    //连按
          printf("连按");
      }
      
      #else
      
      btn->Button_Trigger_Event = BUTTON_DOWM;
      
      if(++(btn->Long_Time) >= BUTTON_LONG_TIME)//释放按键前更新触发事件为长按
      {
          #if LONG_FREE_TRIGGER
         
          btn->Button_Trigger_Event = BUTTON_LONG;
         
          #else
         
          if(++(btn->Button_Cycle) >= BUTTON_LONG_CYCLE)    //连续触发长按的周期
          {
            btn->Button_Cycle = 0;
            btn->Button_Trigger_Event = BUTTON_LONG;
            TRIGGER_CB(BUTTON_LONG);    //长按
          }
          #endif
         
          if(btn->Long_Time == 0xFF)//更新时间溢出
          {
            btn->Long_Time = BUTTON_LONG_TIME;
          }
//          printf("长按");
      }
         
      #endif
      }

      break;
    }
   
    case BUTTON_UP :      // 弹起状态
    {
      if(btn->Button_Trigger_Event == BUTTON_DOWM)//触发单击
      {
      if((btn->Timer_Count <= BUTTON_DOUBLE_TIME)&&(btn->Button_Last_State == BUTTON_DOUBLE)) // 双击
      {
          btn->Button_Trigger_Event = BUTTON_DOUBLE;
          TRIGGER_CB(BUTTON_DOUBLE);   
//          printf("双击");
          btn->Button_State = NONE_TRIGGER;
          btn->Button_Last_State = NONE_TRIGGER;
      }
      else
      {
            btn->Timer_Count=0;
            btn->Long_Time = 0;   //检测长按失败,清0
         
          #if (SINGLE_AND_DOUBLE_TRIGGER == 0)
            TRIGGER_CB(BUTTON_DOWM);    //单击
          #endif
            btn->Button_State = BUTTON_DOUBLE;
            btn->Button_Last_State = BUTTON_DOUBLE;
         
      }
      }
      
      else if(btn->Button_Trigger_Event == BUTTON_LONG)
      {
      #if LONG_FREE_TRIGGER
          TRIGGER_CB(BUTTON_LONG);    //长按
      #else
          TRIGGER_CB(BUTTON_LONG_FREE);    //长按释放
      #endif
      btn->Long_Time = 0;
      btn->Button_State = NONE_TRIGGER;
      btn->Button_Last_State = BUTTON_LONG;
      }
      
      #if CONTINUOS_TRIGGER
      else if(btn->Button_Trigger_Event == BUTTON_CONTINUOS)//连按
      {
          btn->Long_Time = 0;
          TRIGGER_CB(BUTTON_CONTINUOS_FREE);    //连发释放
          btn->Button_State = NONE_TRIGGER;
          btn->Button_Last_State = BUTTON_CONTINUOS;
      }
      #endif
      
      break;
    }
   
    case BUTTON_DOUBLE :
    {
      btn->Timer_Count++;   //时间记录
      if(btn->Timer_Count>=BUTTON_DOUBLE_TIME)
      {
      btn->Button_State = NONE_TRIGGER;
      btn->Button_Last_State = NONE_TRIGGER;
      }
      #if SINGLE_AND_DOUBLE_TRIGGER
      
      if((btn->Timer_Count>=BUTTON_DOUBLE_TIME)&&(btn->Button_Last_State != BUTTON_DOWM))
      {
          btn->Timer_Count=0;
          TRIGGER_CB(BUTTON_DOWM);    //单击
          btn->Button_State = NONE_TRIGGER;
          btn->Button_Last_State = BUTTON_DOWM;
      }
      
      #endif

      break;
    }

    default :
      break;
}

}

/************************************************************
* @brief   遍历的方式扫描按键,不会丢失每个按键
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @note    此函数要周期调用,建议20-50ms调用一次
***********************************************************/
void Button_Process(void)
{
struct button* pass_btn;
for(pass_btn = Head_Button; pass_btn != NULL; pass_btn = pass_btn->Next)
{
      Button_Cycle_Process(pass_btn);
}
}

/************************************************************
* @brief   遍历按键
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @note    NULL
***********************************************************/
void Search_Button(void)
{
struct button* pass_btn;
for(pass_btn = Head_Button; pass_btn != NULL; pass_btn = pass_btn->Next)
{
    printf("button node have %s",pass_btn->Name);
}
}

/************************************************************
* @brief   处理所有按键回调函数
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @note    暂不实现
***********************************************************/
void Button_Process_CallBack(void *btn)
{
uint8_t btn_event = Get_Button_Event(btn);

switch(btn_event)
{
    case BUTTON_DOWM:
    {
      printf("添加你的按下触发的处理逻辑");
      break;
    }
   
    case BUTTON_UP:
    {
      printf("添加你的释放触发的处理逻辑");
      break;
    }
   
    case BUTTON_DOUBLE:
    {
      printf("添加你的双击触发的处理逻辑");
      break;
    }
   
    case BUTTON_LONG:
    {
      printf("添加你的长按触发的处理逻辑");
      break;
    }
   
    case BUTTON_LONG_FREE:
    {
      printf("添加你的长按释放触发的处理逻辑");
      break;
    }
   
    case BUTTON_CONTINUOS:
    {
      printf("添加你的连续触发的处理逻辑");
      break;
    }
   
    case BUTTON_CONTINUOS_FREE:
    {
      printf("添加你的连续触发释放的处理逻辑");
      break;
    }
      
}
}


/**************************** 以下是内部调用函数 ********************/

/************************************************************
* @brief   拷贝指定长度字符串
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @note    NULL
***********************************************************/
static char *StrnCopy(char *dst, const char *src, uint32_t n)
{
if (n != 0)
{
    char *d = dst;
    const char *s = src;
    do
    {
      if ((*d++ = *s++) == 0)
      {
            while (--n != 0)
                *d++ = 0;
            break;
      }
    } while (--n != 0);
}
return (dst);
}

/************************************************************
* @brief   打印按键相关信息
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @note    NULL
***********************************************************/
static void Print_Btn_Info(Button_t* btn)
{

printf("button struct information:\n\
            btn->Name:%s \n\
            btn->Button_State:%d \n\
            btn->Button_Trigger_Event:%d \n\
            btn->Button_Trigger_Level:%d \n\
            btn->Button_Last_Level:%d \n\
            ",
            btn->Name,
            btn->Button_State,
            btn->Button_Trigger_Event,
            btn->Button_Trigger_Level,
            btn->Button_Last_Level);
Search_Button();
}
/************************************************************
* @brief   使用单链表将按键连接起来
      * @param   NULL
* @returnNULL
* @authorjiejie
* @githubhttps://github.com/jiejieTop
* @date    2018-xx-xx
* @version v1.0
* @note    NULL
***********************************************************/
static void Add_Button(Button_t* btn)
{
btn->Next = Head_Button;
Head_Button = btn;
}

/* 获取按键电平 */
uint8_t Read_KEY1_Level(void)
{
      return at32_button_state();
}



button.h完整代码:
#ifndef BUTTON_H
#define BUTTON_H

#include "at32f423_conf.h"

#define NULL          0
#define BTN_NAME_MAX32   //名字最大为32字节

/* 按键消抖时间40ms, 建议调用周期为20ms
只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件
*/

#define CONTINUOS_TRIGGER             0//是否支持连续触发,连发的话就不要检测单双击与长按了      

/* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应,
   因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。
   而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击,
   因为双击必须是有一次按下并且释放之后才产生的 */
#define SINGLE_AND_DOUBLE_TRIGGER   1

/* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按,
   否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */
#define LONG_FREE_TRIGGER             0

#define BUTTON_DEBOUNCE_TIME         2   //消抖时间      (n-1)*调用周期
#define BUTTON_CONTINUOS_CYCLE1          //连按触发周期时间(n-1)*调用周期
#define BUTTON_LONG_CYCLE       1          //长按触发周期时间(n-1)*调用周期
#define BUTTON_DOUBLE_TIME      15         //双击间隔时间(n-1)*调用周期建议在200-600ms
#define BUTTON_LONG_TIME               50                /* 持续n秒((n-1)*调用周期 ms),认为长按事件 */

#define TRIGGER_CB(event)   \
      if(btn->CallBack_Function) \
          btn->CallBack_Function((Button_t*)btn)

typedef void (*Button_CallBack)(void*);   /* 按键触发回调函数,需要用户实现 */



typedef enum {
BUTTON_DOWM = 0,
BUTTON_UP,
BUTTON_DOUBLE,
BUTTON_LONG,
BUTTON_LONG_FREE,
BUTTON_CONTINUOS,
BUTTON_CONTINUOS_FREE,
BUTTON_ALL_RIGGER,
number_of_event, /* 触发回调的事件 */
NONE_TRIGGER
}Button_Event;

/*
      每个按键对应1个全局的结构体变量。
      其成员变量是实现滤波和多种按键状态所必须的
*/
typedef struct button
{
      /* 下面是一个函数指针,指向判断按键手否按下的函数 */
      uint8_t (*Read_Button_Level)(void); /* 读取按键电平函数,需要用户实现 */

char Name;
         
uint8_t Button_State            :   4;          /* 按键当前状态(按下还是弹起) */
uint8_t Button_Last_State         :   4;          /* 上一次的按键状态,用于判断双击 */
uint8_t Button_Trigger_Level      :   2;    /* 按键触发电平 */
uint8_t Button_Last_Level         :   2;    /* 按键当前电平 */

uint8_t Button_Trigger_Event;   /* 按键触发事件,单击,双击,长按等 */

Button_CallBack CallBack_Function;

      uint8_t Button_Cycle;                   /* 连续按键周期 */

uint8_t Timer_Count;                        /* 计时 */
      uint8_t Debounce_Time;                /* 消抖时间 */

      uint8_t Long_Time;                  /* 按键按下持续时间 */

struct button *Next;

}Button_t;




/* 供外部调用的函数声明 */

void Button_Create(const char *name,
                  Button_t *btn,
                  uint8_t(*read_btn_level)(void),
                  uint8_t btn_trigger_level);
                  
void Button_Attach(Button_t *btn,Button_Event btn_event,Button_CallBack btn_callback);   
                  
void Button_Cycle_Process(Button_t *btn);   
                  
void Button_Process(void);
                  
void Button_Delete(Button_t *btn);   

void Search_Button(void);   
                  
void Get_Button_EventInfo(Button_t *btn);
uint8_t Get_Button_Event(Button_t *btn);
uint8_t Get_Button_State(Button_t *btn);
void Button_Process_CallBack(void *btn);
uint8_t Read_KEY1_Level(void);                  
#endif
main.c完整代码:
/**
**************************************************************************
* @file   main.c
* @brief    main program
**************************************************************************
*                     Copyright notice & Disclaimer
*
* The software Board Support Package (BSP) that is made available to
* download from Artery official website is the copyrighted work of Artery.
* Artery authorizes customers to use, copy, and distribute the BSP
* software and its related documentation for the purpose of design and
* development in conjunction with Artery microcontrollers. Use of the
* software is governed by this copyright notice and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
* GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
* TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
* STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
* INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
*
**************************************************************************
*/

#include "at32f423_board.h"
#include "at32f423_clock.h"
#include "button.h"

/** @addtogroup AT32F423_periph_examples
* @{
*/

/** @addtogroup 423_USART_printf USART_printf
* @{
*/

__IO uint32_t time_cnt = 0;
Button_t Button1;


void Btn1_Dowm_CallBack(void *btn)
{
printf("Button1 单击!\r\n");
}

void Btn1_Double_CallBack(void *btn)
{
printf("Button1 双击!\r\n");
}

void Btn1_Long_CallBack(void *btn)
{
printf("Button1 长按!\r\n");
}
void Btn1_ContinuosFree_CallBack(void *btn)
{
printf("Button1 连按释放!\r\n");
}

/**
* @briefmain function.
* @paramnone
* @retval none
*/
int main(void)
{
system_clock_config();
at32_board_init();
uart_print_init(115200);
         Button_Create("Button1",
            &Button1,
            Read_KEY1_Level,
            1);
Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack);                     //单击
Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack);                   //双击
Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack);                     //长按BUTTON_LONG_FREE,
      Button_Attach(&Button1,BUTTON_LONG_FREE,Btn1_ContinuosFree_CallBack);
/* output a message on hyperterminal using printf function */
printf("usart printf example: retarget the c library printf function to the usart\r\n");
      Get_Button_Event(&Button1);
while(1)
{
                Button_Process();   //需要周期调用按键处理函数
//    printf("usart printf counter: %u\r\n",time_cnt++);
//    delay_sec(1);
                delay_ms(20);
}
}

/**
* @}
*/

/**
* @}
*/

【试用心得】
1、此次是我第一次使用按键的驱动代码,实现了很多单按键的功能,这在我们以后面的编程工作中会有很大的帮助。
2、能够顺利的移植,也得益于雅特力的官方示例给了开发板的常规驱动,比如Uart\printf重定向、按键驱动、LED灯驱动都写好了,我们只要调用board.c中的就可以了。
3、在github上有很多优秀的开源软件,能向大佬们学习,也是提高自身技术水平的重要方式之一,这里要感觉这些无私奉献的大佬。

184592967 发表于 2023-10-25 15:32

谢谢分享,搞来试试

Aeddg 发表于 2023-10-25 16:32

谢谢分享!

lulugl 发表于 2023-10-25 18:23

184592967 发表于 2023-10-25 15:32
谢谢分享,搞来试试

挺好用的一个开源库,你值得拥有!

trucyw 发表于 2023-10-26 08:23

谢谢分享,搞来试试

jobszheng 发表于 2023-10-26 09:26

这个按键代码优秀在哪里了啊?

lulugl 发表于 2023-10-26 10:40

jobszheng 发表于 2023-10-26 09:26
这个按键代码优秀在哪里了啊?

不只是我说他优秀,其他的人也认为他优秀,而且这个驱动,移植方便,只要实现一下IO的驱动,电平状态的获取后就可以实现单击、双击、连击等等的。这样的不优秀,还有什么可以说他优秀?

ssy123321 发表于 2024-12-13 17:42

mark

呐咯密密 发表于 2024-12-13 20:06

这些开源的按键驱动真的好用
页: [1]
查看完整版本: 【AT-START-F423测评】移植一个优秀的按键程序