【开源移植】MultiButton_小型按键驱动模块移植

MultiButton

简介

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

使用方法

1.先申请一个按键结构

c 复制代码
struct Button button1;

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

c 复制代码
button_init(&button1, read_button_pin, 0, 0);

3.注册按键事件

c 复制代码
button_attach(&button1, SINGLE_CLICK, Callback_SINGLE_CLICK_Handler);
button_attach(&button1, DOUBLE_CLICK, Callback_DOUBLE_Click_Handler);
...

4.启动按键

c 复制代码
button_start(&button1);

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

c 复制代码
while(1) {
    ...
    if(timer_ticks == 5) {
        timer_ticks = 0;

        button_ticks();
    }
}

特性

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

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

c 复制代码
#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

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

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

c 复制代码
/*
 * 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

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

c 复制代码
/**
 ******************************************************************************
 * @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

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

参考资料

  • 1\] [【B@落子叶初LQ】MultiButton_小型按键驱动模块](https://www.bilibili.com/video/BV16J4m1n7mg/?spm_id_from=333.337.search-card.all.click&vd_source=b344881caf56010b57ef7c87acf3ec92)

相关推荐
冬奇Lab13 分钟前
一天一个开源项目(第17篇):ViMax - 多智能体视频生成框架,导演、编剧、制片人全包
开源·音视频开发
一个处女座的程序猿2 小时前
AI之Agent之VibeCoding:《Vibe Coding Kills Open Source》翻译与解读
人工智能·开源·vibecoding·氛围编程
一只大侠的侠3 小时前
React Native开源鸿蒙跨平台训练营 Day16自定义 useForm 高性能验证
flutter·开源·harmonyos
IvorySQL4 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
一只大侠的侠4 小时前
Flutter开源鸿蒙跨平台训练营 Day11从零开发商品详情页面
flutter·开源·harmonyos
一只大侠的侠4 小时前
React Native开源鸿蒙跨平台训练营 Day18自定义useForm表单管理实战实现
flutter·开源·harmonyos
一只大侠的侠4 小时前
React Native开源鸿蒙跨平台训练营 Day20自定义 useValidator 实现高性能表单验证
flutter·开源·harmonyos
晚霞的不甘5 小时前
Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示
人工智能·算法·flutter·架构·开源·音视频
晚霞的不甘6 小时前
Flutter for OpenHarmony 实现计算几何:Graham Scan 凸包算法的可视化演示
人工智能·算法·flutter·架构·开源·音视频
猫头虎6 小时前
OpenClaw-VSCode:在 VS Code 里玩转 OpenClaw,远程管理+SSH 双剑合璧
ide·vscode·开源·ssh·github·aigc·ai编程