STM32移植嵌入式开源按键框架

目录

STM32移植嵌入式开源按键框架

MultiButton简介

multi_button.c文件

multi_button.h文件

按键事件

案例使用方法

学习剖析


STM32移植嵌入式开源按键框架

今天移植了一款嵌入式按键框架工程MultiButton,MultiButton是一个小巧简单易用的事件驱动型按键驱动模块。

Github地址:GitHub - 0x1abin/MultiButton: Button driver for embedded system

现于2024.04.15进行移植,具体就两个文件:multi_button的c文件和h文件。

MultiButton简介

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

multi_button.c文件

把license内容放到代码首段。

cpp 复制代码
/*
 * MIT License

 * Copyright (c) 2018 Zibin Zheng
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* github:https://github.com/0x1abin/MultiButton */

#include "driver_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, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id)
{
	memset(handle, 0, sizeof(struct Button));
	handle->event = (uint8_t)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;
}

/**
  * @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, BtnCallback 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)
{
	uint8_t read_gpio_level = handle->hal_button_Level(handle->button_id);

	//ticks counter working..
	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 = (uint8_t)PRESS_DOWN;
			EVENT_CB(PRESS_DOWN);
			handle->ticks = 0;
			handle->repeat = 1;
			handle->state = 1;
		} else {
			handle->event = (uint8_t)NONE_PRESS;
		}
		break;

	case 1:
		if(handle->button_level != handle->active_level) { //released press up
			handle->event = (uint8_t)PRESS_UP;
			EVENT_CB(PRESS_UP);
			handle->ticks = 0;
			handle->state = 2;
		} else if(handle->ticks > LONG_TICKS) {
			handle->event = (uint8_t)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 = (uint8_t)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 = (uint8_t)SINGLE_CLICK;
				EVENT_CB(SINGLE_CLICK);
			} else if(handle->repeat == 2) {
				handle->event = (uint8_t)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 = (uint8_t)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 = (uint8_t)LONG_PRESS_HOLD;
			EVENT_CB(LONG_PRESS_HOLD);
		} else { //released
			handle->event = (uint8_t)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文件

extern使用函数,供外部文件使用。

cpp 复制代码
/*
 * Copyright (c) 2016 Zibin Zheng <znbin@qq.com>
 * All rights reserved
 */

#ifndef _MULTI_BUTTON_H_
#define _MULTI_BUTTON_H_

#include <stdint.h>
#include <string.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)		// ms
#define LONG_TICKS        (1000 /TICKS_INTERVAL)	// ms


typedef void (*BtnCallback)(void*);

typedef enum {
	PRESS_DOWN = 0,		/* 按键按下,每次按下都触发 */
	PRESS_UP,			/* 按键弹起,每次松开都触发 */
	PRESS_REPEAT,		/* 重复按下触发,变量repeat计数连击次数 */
	SINGLE_CLICK,		/* 单击按键事件 */
	DOUBLE_CLICK,		/* 双击按键事件 */
	LONG_PRESS_START,	/* 达到长按时间阈值时触发一次 */
	LONG_PRESS_HOLD,	/* 长按期间一直触发 */
	number_of_event,	/* 只为记录在此之前的事件次数 */
	NONE_PRESS			/* 默认状态,无按键按下 */
}PressEvent;

typedef struct Button {
	uint16_t ticks;
	uint8_t  repeat : 4;								/* 重复按下次数,0~15 */
	uint8_t  event : 4;									/* 按键事件类型,默认NONE_PRESS,0~15 */
	uint8_t  state : 3;									/* 按键状态机状态:0~7 */
	uint8_t  debounce_cnt : 3;							/* 防抖次数,0~7 */
	uint8_t  active_level : 1;							/* 按键按下电平,0~1 */
	uint8_t  button_level : 1;							/* 按键电平,0~1 */
	uint8_t  button_id;									/* 按键id */
	uint8_t  (*hal_button_Level)(uint8_t button_id_);	/* 按键电平检测函数 */
	BtnCallback  cb[number_of_event];					/* 创建各事件处理函数数组 */
	struct Button* next;								/* 指向下一个按钮对象 */
}Button;

#ifdef __cplusplus
extern "C" {
#endif

extern void button_init(struct Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id);	/* 初始化按键对象 */
extern void button_attach(struct Button* handle, PressEvent event, BtnCallback cb);	/* 注册按键事件 */
extern PressEvent get_button_event(struct Button* handle);
extern int  button_start(struct Button* handle);	/* 开始按键检测 */
extern void button_stop(struct Button* handle);		/* 停止按键检测 */
extern void button_ticks(void);

#ifdef __cplusplus
}
#endif

#endif

按键事件

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

案例使用方法

结合自身项目需求实现。我使用到了FreeRTOS V2操作系统。

只需调用MultiButton_Init()函数初始化,调用xxx_Handler()函数处理。

cpp 复制代码
#include "driver_button.h"

enum Button_IDs 
{
	btn1_id,
	btn2_id,
};

static osTimerId_t 	multiButtonTimer;
struct Button btn1;
struct Button btn2;

static void MultiButtonTimerCallback (void *argument);
static uint8_t read_button_GPIO(uint8_t button_id);
static void BTN1_PRESS_CLICK_Handler(void* btn);
static void BTN2_PRESS_CLICK_Handler(void* btn);

static 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(OLED_KEY1_GPIO_Port, OLED_KEY1_Pin);
		case btn2_id:
			return !HAL_GPIO_ReadPin(USER_KEY_GPIO_Port, USER_KEY_Pin);
		default:
			return 0;
	}
}

static void BTN1_PRESS_CLICK_Handler(void* btn)
{
	/* 事件处理时间不宜过长 */
	//do something...
}

static void BTN2_PRESS_CLICK_Handler(void* btn)
{
	/* 事件处理时间不宜过长 */
	//do something...
}

void MultiButton_Init(void)
{
    multiButtonTimer = osTimerNew(MultiButtonTimerCallback, osTimerPeriodic, NULL, NULL);
	
	/* 单按键初始化 */
	button_init(&btn1, read_button_GPIO, 0, btn1_id);	
	button_init(&btn2, read_button_GPIO, 0, btn2_id);
	
	/* 单按键注册,可单按键多事件注册 */
	button_attach(&btn1, SINGLE_CLICK,         BTN1_PRESS_CLICK_Handler);
	button_attach(&btn2, SINGLE_CLICK,         BTN2_PRESS_CLICK_Handler);
	
	/* 单按键检测 */
	button_start(&btn1);
	button_start(&btn2);
	
    osTimerStart(multiButtonTimer, 5); 		//启动定时器,5ms每轮
}

/***********************************************************************
 *	5ms进一次定时器回调函数
 **********************************************************************/
static void MultiButtonTimerCallback (void *argument)
{
	button_ticks();
}

学习剖析

学习要点:

按键各种类型事件。

状态机思想。

单向链表语法。
button_init()函数很简单,默认记录了单按键句柄按键事件类型按键此刻电平按键按下电平按键ID按键电平检测函数

使用方法:

button_init(&btn1, read_button_GPIO, 0, btn1_id); // 低电平按下
button_attach()函数也很简单,注册按钮,即将按键处理函数 放入单按键句柄对应按键事件数组位置

使用方法:

button_attach(&btn1, SINGLE_CLICK, BTN1_PRESS_CLICK_Handler);
get_button_event()函数也很简单,获取当前单按键句柄的发生事件类型。
button_handler()函数采用了状态机的思想。大概思想如下:

相关推荐
学习路上_write16 分钟前
FPGA/Verilog,Quartus环境下if-else语句和case语句RT视图对比/学习记录
单片机·嵌入式硬件·qt·学习·fpga开发·github·硬件工程
非概念22 分钟前
stm32学习笔记----51单片机和stm32单片机的区别
笔记·stm32·单片机·学习·51单片机
jjjxxxhhh1232 小时前
FPGA,使用场景,相比于单片机的优势
单片机·嵌入式硬件·fpga开发
无敌最俊朗@2 小时前
stm32学习之路——八种GPIO口工作模式
c语言·stm32·单片机·学习
EterNity_TiMe_2 小时前
【论文复现】STM32设计的物联网智能鱼缸
stm32·单片机·嵌入式硬件·物联网·学习·性能优化
changingshow3 小时前
Arduino IDE Windows 系统 离线安装 esp32 开发板 亲测好用。
单片机·嵌入式硬件
7yewh5 小时前
嵌入式硬件杂谈(一)-推挽 开漏 高阻态 上拉电阻
驱动开发·stm32·嵌入式硬件·mcu·物联网·硬件架构·pcb工艺
Chervin13 小时前
Windows,虚拟机Ubuntu和开发板三者之间的NFS服务器搭建
linux·单片机·ubuntu·arm
TeYiToKu13 小时前
笔记整理—linux驱动开发部分(8)framebuffer类设备
linux·驱动开发·笔记·嵌入式硬件·arm
电子工程师UP学堂15 小时前
STM32 设计的较为复杂的物联网项目,包括智能家居控制系统,涵盖了硬件和软件的详细设计。
stm32