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()函数采用了状态机的思想。大概思想如下:

相关推荐
qq_4597300340 分钟前
4-3 MCU中ARM存储器的作用
arm开发·单片机·嵌入式硬件
嵌入式科普3 小时前
嵌入式科普(24)从SPI和CAN通信重新理解“全双工”
c语言·stm32·can·spi·全双工·ra6m5
重生之我是数学王子4 小时前
点亮核心板小灯 STM32U575
stm32·单片机·嵌入式硬件
end_SJ4 小时前
初学stm32 --- 定时器中断
stm32·单片机·嵌入式硬件
南城花随雪。4 小时前
单片机:实现数码管动态显示(0~99999999)74hc138驱动(附带源码)
单片机·嵌入式硬件
南城花随雪。6 小时前
单片机:实现信号发生器(附带源码)
单片机·嵌入式硬件
灵槐梦8 小时前
【速成51单片机】2.点亮LED
c语言·开发语言·经验分享·笔记·单片机·51单片机
三月七(爱看动漫的程序员)9 小时前
HiQA: A Hierarchical Contextual Augmentation RAG for Multi-Documents QA---附录
人工智能·单片机·嵌入式硬件·物联网·机器学习·语言模型·自然语言处理
新晨单片机设计9 小时前
【087】基于51单片机智能宠物喂食器【Proteus仿真+Keil程序+报告+原理图】
嵌入式硬件·51单片机·proteus·宠物·ad原理图
大风起兮1210 小时前
STM32HAL库中RTC闹钟设置时分秒,年月日
stm32·嵌入式硬件