MultiButton

简介

MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。

使用方法

1.先申请一个按键结构

struct Button button1;

2.初始化按键对象,绑定按键的GPIO电平读取接口read_button_pin() ,后一个参数设置有效触发电平

button_init(&button1, read_button_pin, 0, 0);

3.注册按键事件

button_attach(&button1, SINGLE_CLICK, Callback_SINGLE_CLICK_Handler);
button_attach(&button1, DOUBLE_CLICK, Callback_DOUBLE_Click_Handler);
...

4.启动按键

button_start(&button1);

5.设置一个5ms间隔的定时器循环调用后台处理函数

while(1) {
    ...
    if(timer_ticks == 5) {
        timer_ticks = 0;

        button_ticks();
    }
}

特性

MultiButton 使用C语言实现,基于面向对象方式设计思路,每个按键对象单独用一份数据结构管理:

struct Button {
	uint16_t ticks;
	uint8_t  repeat: 4;
	uint8_t  event : 4;
	uint8_t  state : 3;
	uint8_t  debounce_cnt : 3;
	uint8_t  active_level : 1;
	uint8_t  button_level : 1;
	uint8_t  button_id;
	uint8_t  (*hal_button_Level)(uint8_t  button_id_);
	BtnCallback  cb[number_of_event];
	struct Button* next;
};

这样每个按键使用单向链表相连,依次进入 button_handler(struct Button* handle) 状态机处理,所以每个按键的状态彼此独立。

按键事件

事件说明
PRESS_DOWN按键按下,每次按下都触发
PRESS_UP按键弹起,每次松开都触发
PRESS_REPEAT重复按下触发,变量repeat计数连击次数
SINGLE_CLICK单击按键事件
DOUBLE_CLICK双击按键事件
LONG_PRESS_START达到长按时间阈值时触发一次
LONG_PRESS_HOLD长按期间一直触发

Examples

#include "button.h"

unit8_t btn1_id = 0;

struct Button btn1;

uint8_t read_button_GPIO(uint8_t button_id)
{
	// you can share the GPIO read function with multiple Buttons
	switch(button_id)
	{
		case btn1_id:
			return HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin);
			break;

		default:
			return 0;
			break;
	}
}
void BTN1_PRESS_DOWN_Handler(void* btn)
{
	//do something...
}

void BTN1_PRESS_UP_Handler(void* btn)
{
	//do something...
}

...

int main()
{
	button_init(&btn1, read_button_GPIO, 0, btn1_id);
	button_attach(&btn1, PRESS_DOWN,       BTN1_PRESS_DOWN_Handler);
	button_attach(&btn1, PRESS_UP,         BTN1_PRESS_UP_Handler);
	button_attach(&btn1, PRESS_REPEAT,     BTN1_PRESS_REPEAT_Handler);
	button_attach(&btn1, SINGLE_CLICK,     BTN1_SINGLE_Click_Handler);
	button_attach(&btn1, DOUBLE_CLICK,     BTN1_DOUBLE_Click_Handler);
	button_attach(&btn1, LONG_PRESS_START, BTN1_LONG_PRESS_START_Handler);
	button_attach(&btn1, LONG_PRESS_HOLD,  BTN1_LONG_PRESS_HOLD_Handler);
	button_start(&btn1);

	//make the timer invoking the button_ticks() interval 5ms.
	//This function is implemented by yourself.
	__timer_start(button_ticks, 0, 5);

	while(1)
	{}
}

开源移植 STM32

源文件 main.c

#include "stm32f10x.h"
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "multi_button.h"

int main()
{
	LED_Init(); // LED初始化
	KEY_Init(); // 按键初始化

	while (1)
	{
		Delay_ms(5);
		button_ticks();// 按键运行
	}
}


源文件multi_button.c

/*
 * Copyright (c) 2016 Zibin Zheng <znbin@qq.com>
 * All rights reserved
 */

#include "multi_button.h"

#define EVENT_CB(ev)   if(handle->cb[ev])handle->cb[ev]((void*)handle)
#define PRESS_REPEAT_MAX_NUM  15 /*!< The maximum value of the repeat counter */

//button handle list head.
static struct Button* head_handle = NULL;

static void button_handler(struct Button* handle);

/**
  * @brief  Initializes the button struct handle.
  * @param  handle: the button handle struct.
  * @param  pin_level: read the HAL GPIO of the connected button level.
  * @param  active_level: pressed GPIO level.
  * @param  button_id: the button id.
  * @retval None
  */
void button_init(struct Button* handle, u8(*pin_level)(u8), u8 active_level, u8 button_id)
{
	memset(handle, 0, sizeof(struct Button));
	handle->event = (u8)NONE_PRESS;
	handle->hal_button_Level = pin_level;
	handle->button_level = handle->hal_button_Level(button_id);
	handle->active_level = active_level;
	handle->button_id = button_id;
}

//  /*button1 init*/
//  button_init(&btn1, read_button_GPIO, 0, KEY1_id);                      // 初始化按键结构体
//  button_attach(&btn1, SINGLE_CLICK, BTN1_SINGLE_CLICK_Handler);         // 添加单击事件
//  button_attach(&btn1, DOUBLE_CLICK, BTN1_DOUBLE_CLICK_Handler);         // 添加双击事件
//  button_attach(&btn1, LONG_PRESS_START, BTN1_LONG_PRESS_START_Handler); // 添加长按事件
//  button_start(&btn1);                                                   // 启动按键  

/**
  * @brief  Attach the button event callback function.
  * @param  handle: the button handle struct.
  * @param  event: trigger event type.
  * @param  cb: callback function.
  * @retval None
  */
void button_attach(struct Button* handle, PressEvent event, BtnCallback7 cb)
{
	handle->cb[event] = cb;
}

/**
  * @brief  Inquire the button event happen.
  * @param  handle: the button handle struct.
  * @retval button event.
  */
PressEvent get_button_event(struct Button* handle)
{
	return (PressEvent)(handle->event);
}

/**
  * @brief  Button driver core function, driver state machine.
  * @param  handle: the button handle struct.
  * @retval None
  */
static void button_handler(struct Button* handle)
{
	u8 read_gpio_level = handle->hal_button_Level(handle->button_id);

	//ticks counter working..4
	if((handle->state) > 0) handle->ticks++;

	/*------------button debounce handle---------------*/
	if(read_gpio_level != handle->button_level) { //not equal to prev one
		//continue read 3 times same new level change
		if(++(handle->debounce_cnt) >= DEBOUNCE_TICKS) {
			handle->button_level = read_gpio_level;
			handle->debounce_cnt = 0;
		}
	} else { //level not change ,counter reset.
		handle->debounce_cnt = 0;
	}

	/*-----------------State machine-------------------*/
	switch (handle->state) {
	case 0:
		if(handle->button_level == handle->active_level) {	//start press down
			handle->event = (u8)PRESS_DOWN;
			EVENT_CB(PRESS_DOWN);
			handle->ticks = 0;
			handle->repeat = 1;
			handle->state = 1;
		} else {
			handle->event = (u8)NONE_PRESS;
		}
		break;

	case 1:
		if(handle->button_level != handle->active_level) { //released press up
			handle->event = (u8)PRESS_UP;
			EVENT_CB(PRESS_UP);
			handle->ticks = 0;
			handle->state = 2;
		} else if(handle->ticks > LONG_TICKS) {
			handle->event = (u8)LONG_PRESS_START;
			EVENT_CB(LONG_PRESS_START);
			handle->state = 5;
		}
		break;

	case 2:
		if(handle->button_level == handle->active_level) { //press down again
			handle->event = (u8)PRESS_DOWN;
			EVENT_CB(PRESS_DOWN);
			if(handle->repeat != PRESS_REPEAT_MAX_NUM) {
				handle->repeat++;
			}
			EVENT_CB(PRESS_REPEAT); // repeat hit
			handle->ticks = 0;
			handle->state = 3;
		} else if(handle->ticks > SHORT_TICKS) { //released timeout
			if(handle->repeat == 1) {
				handle->event = (u8)SINGLE_CLICK;
				EVENT_CB(SINGLE_CLICK);
			} else if(handle->repeat == 2) {
				handle->event = (u8)DOUBLE_CLICK;
				EVENT_CB(DOUBLE_CLICK); // repeat hit
			}
			handle->state = 0;
		}
		break;

	case 3:
		if(handle->button_level != handle->active_level) { //released press up
			handle->event = (u8)PRESS_UP;
			EVENT_CB(PRESS_UP);
			if(handle->ticks < SHORT_TICKS) {
				handle->ticks = 0;
				handle->state = 2; //repeat press
			} else {
				handle->state = 0;
			}
		} else if(handle->ticks > SHORT_TICKS) { // SHORT_TICKS < press down hold time < LONG_TICKS
			handle->state = 1;
		}
		break;

	case 5:
		if(handle->button_level == handle->active_level) {
			//continue hold trigger
			handle->event = (u8)LONG_PRESS_HOLD;
			EVENT_CB(LONG_PRESS_HOLD);
		} else { //released
			handle->event = (u8)PRESS_UP;
			EVENT_CB(PRESS_UP);
			handle->state = 0; //reset
		}
		break;
	default:
		handle->state = 0; //reset
		break;
	}
}

/**
  * @brief  Start the button work, add the handle into work list.
  * @param  handle: target handle struct.
  * @retval 0: succeed. -1: already exist.
  */
int button_start(struct Button* handle)
{
	struct Button* target = head_handle;
	while(target) {
		if(target == handle) return -1;	//already exist.
		target = target->next;
	}
	handle->next = head_handle;
	head_handle = handle;
	return 0;
}

/**
  * @brief  Stop the button work, remove the handle off work list.
  * @param  handle: target handle struct.
  * @retval None
  */
void button_stop(struct Button* handle)
{
	struct Button** curr;
	for(curr = &head_handle; *curr; ) {
		struct Button* entry = *curr;
		if(entry == handle) {
			*curr = entry->next;
//			free(entry);
			return;//glacier add 2021-8-18
		} else {
			curr = &entry->next;
		}
	}
}

/**
  * @brief  background ticks, timer repeat invoking interval 5ms.
  * @param  None.
  * @retval None
  */
void button_ticks(void)
{
	struct Button* target;
	for(target=head_handle; target; target=target->next) {
		button_handler(target);
	}
}

头文件multi_button.h

/*
 * Copyright (c) 2016 Zibin Zheng <znbin@qq.com>
 * All rights reserved
 */

#ifndef _MULTI_BUTTON_H_
#define _MULTI_BUTTON_H_

#include <string.h>
#include "stm32f10x.h"

//According to your need to modify the constants.
#define TICKS_INTERVAL    5	//ms 
#define DEBOUNCE_TICKS    3	//MAX 7 (0 ~ 7)
#define SHORT_TICKS       (300 /TICKS_INTERVAL)
#define LONG_TICKS        (1000 /TICKS_INTERVAL)


typedef void (*BtnCallback)(void*);

typedef enum {
	PRESS_DOWN = 0,
	PRESS_UP,
	PRESS_REPEAT,
	SINGLE_CLICK,
	DOUBLE_CLICK,
	LONG_PRESS_START,
	LONG_PRESS_HOLD,
	number_of_event,
	NONE_PRESS
}PressEvent;

typedef struct Button {
	u16 ticks;
	u8  repeat;
	u8  event;
	u8  state;
	u8  debounce_cnt;
	u8  active_level;
	u8  button_level;
	u8  button_id;
	u8  (*hal_button_Level)(u8 button_id_);
	BtnCallback  cb[number_of_event];
	struct Button* next;
}Button;


void button_init(struct Button* handle, u8(*pin_level)(u8), u8 active_level, u8 button_id);
void button_attach(struct Button* handle, PressEvent event, BtnCallback cb);
PressEvent get_button_event(struct Button* handle);
int  button_start(struct Button* handle);
void button_stop(struct Button* handle);
void button_ticks(void);


#endif


源文件KEY2.C

/**
 ******************************************************************************
 * @file    KEY2.c
 * @author  LQ
 * @version
 * @date    2024-4-29
 * @brief   multi_button
 ******************************************************************************
 * @attention
 *
 ******************************************************************************
 */

#include "stm32f10x.h"
#include "KEY.h"
#include "LED.h"

#ifdef USE_KEY2

struct Button btn1;
struct Button btn2;

// GPIO读取回调函数
u8 read_button_GPIO(u8 button_id)
{
  u8 read_v;
  switch (button_id)
  {
  case KEY1_id:
    read_v = GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN);
    break;
  case KEY2_id:
    read_v = GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN);
    break;
  default:
    break;
  }

  return read_v;
}

// 单击事件
void BTN1_SINGLE_CLICK_Handler(void *btn)
{
  switch (((Button *)btn)->button_id)
  {
  case KEY1_id:
    LED_ON(LED1);
    break;
  case KEY2_id:
    LED_OFF(LED1);
    break;

  default:
    break;
  }
}
// 双击事件
void BTN1_DOUBLE_CLICK_Handler(void *btn)
{
  LED_Toggle(LED1);
}

// 长按事件
void BTN1_LONG_PRESS_START_Handler(void *btn)
{
  LED_OFF(LED1);
}

/**
 * @brief  KEY初始化
 * @param  None
 * @retval None
 */
void KEY_Init(void)
{
  /* GPIO_KEY Clock */
  RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK, ENABLE);
  RCC_APB2PeriphClockCmd(KEY2_GPIO_CLK, ENABLE);
  /* GPIO_KEY Pin */
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
  GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN;
  GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure);

  /*button1 init*/
  button_init(&btn1, read_button_GPIO, 0, KEY1_id);                      // 初始化按键结构体
  button_attach(&btn1, SINGLE_CLICK, BTN1_SINGLE_CLICK_Handler);         // 添加单击事件
  button_attach(&btn1, DOUBLE_CLICK, BTN1_DOUBLE_CLICK_Handler);         // 添加双击事件
  button_attach(&btn1, LONG_PRESS_START, BTN1_LONG_PRESS_START_Handler); // 添加长按事件
  button_start(&btn1);                                                   // 启动按键                                                //

  /*button2 init*/
  button_init(&btn2, read_button_GPIO, 0, KEY2_id);
  button_attach(&btn2, SINGLE_CLICK, BTN1_SINGLE_CLICK_Handler);
  button_attach(&btn2, DOUBLE_CLICK, BTN1_DOUBLE_CLICK_Handler);
  button_start(&btn2);
}

#endif

头文件KEY.h

/**
 ******************************************************************************
 * @file    KEY.h
 * @author  LQ
 * @version V1.0
 * @date    2023-4-29
 * @brief   multi_button
 ******************************************************************************
 * @attention
 *
 ******************************************************************************
 */

#ifndef _KEY_H
#define _KEY_H

#define USE_KEY2 // 定义使用KEY2.c,注释则使用KEY1.c

#ifndef USE_KEY2  // 如果USE_KEY2没有被定义就定义名为USE_KEY1的宏
#define USE_KEY1
#endif


#include "multi_button.h"

/*GPIO宏定义*/
#define KEY1_GPIO_PORT GPIOA
#define KEY1_GPIO_PIN GPIO_Pin_8
#define KEY1_GPIO_CLK RCC_APB2Periph_GPIOA

#ifdef USE_KEY2  // 如果USE_KEY2被定定义
#define KEY2_GPIO_PORT GPIOA
#define KEY2_GPIO_PIN GPIO_Pin_9
#define KEY2_GPIO_CLK RCC_APB2Periph_GPIOA

enum Button_IDs
{
	KEY1_id,
	KEY2_id,
};
#endif

void KEY_Init(void);

#endif


源文件KEY1.C

/**
 ******************************************************************************
 * @file    KEY1.c
 * @author  LQ
 * @version
 * @date    2024-4-29
 * @brief   multi_button
 ******************************************************************************
 * @attention
 *
 ******************************************************************************
 */

#include "stm32f10x.h"
#include "KEY.h"
#include "LED.h"

#ifdef USE_KEY1

struct Button btn1;

// GPIO读取回调函数
u8 read_button_GPIO(u8 button_id)
{
  u8 led_sta;
  led_sta = GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN);
  return led_sta;
}

// 单击事件
void BTN1_SINGLE_CLICK_Handler(void *btn)
{
  LED_ON(LED1);
}

// 双击事件
void BTN1_DOUBLE_CLICK_Handler(void *btn)
{
  LED_Toggle(LED1);
}

// 长按事件
void BTN1_LONG_PRESS_START_Handler(void *btn)
{
  LED_OFF(LED1);
}

/**
 * @brief  KEY初始化
 * @param  None
 * @retval None
 */
void KEY_Init(void)
{
  /* GPIO_KEY Clock */
  RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK, ENABLE);

  /* GPIO_KEY pin */
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);

  /*button init*/
  button_init(&btn1, read_button_GPIO, 0, 0);                            // 初始化按键结构体
  button_attach(&btn1, SINGLE_CLICK, BTN1_SINGLE_CLICK_Handler);         // 添加单击事件
  button_attach(&btn1, DOUBLE_CLICK, BTN1_DOUBLE_CLICK_Handler);         // 添加双击事件
  button_attach(&btn1, LONG_PRESS_START, BTN1_LONG_PRESS_START_Handler); // 添加长按事件
  button_start(&btn1);                                                   // 启动按键
}

#endif


参考资料

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部