链表在嵌入式中的应用(二)—基于GitHub开源项目之MultiButton

1、项目描述

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

2、代码移植

本文使用的开发板是正点原子探索者F407,首先使用STM32CubMx初始化外设信息,要求:1、初始化按键输入引脚PE2、PE3、PE4任意一个,2、串口打印功能。
不熟悉的可以查阅之前的blog;
按键
串口
初始化配置界面如下所示:
在这里插入图片描述在这里插入图片描述

移植代码工程,添加MultiButton代码到新建工程文件目录下:
如图所示:

在这里插入图片描述
按键回调函数编写:

uint8_t read_button1_GPIO()
{
	return HAL_GPIO_ReadPin(K1_GPIO_Port,K1_Pin);
}

void btn1_press_down_Handler(void *btn)
{
	printf("K1 press down...\r\n");
	HAL_GPIO_TogglePin(L1_GPIO_Port,L1_Pin);
}

void btn1_press_up_Handler(void *btn)
{
	printf("K1 press up...\r\n");
	HAL_GPIO_TogglePin(L0_GPIO_Port,L0_Pin);
}

while(1)之前添加按键初始化以及启动按键功能:

	printf("MultiButton Test...\r\n");
	
	button_init(&button1,read_button1_GPIO,0);
	
	button_attach(&button1,PRESS_DOWN,btn1_press_down_Handler);
	
	button_attach(&button1,PRESS_UP,btn1_press_up_Handler);
	
	button_start(&button1);
	
	printf("MultiButton Test...\r\n");

在while(1)添加如下代码

while (1)
{
	button_ticks();
	HAL_Delay(5);
}

实验现象:
按下按键K1,LED0翻转,抬起K1LED1翻转。
在这里插入图片描述

MultiButton源码分析

首先,定义了按键的时间类型,包括按键按下、抬起、单击、重复按键按下、双击、长按单次触发、长按一直触发等事件。

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 {
	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  (*hal_button_Level)(void);
	BtnCallback  cb[number_of_event];
	struct Button* next;
}Button;

按键对象结构体的初始化,初始化成员包括按键句柄,绑定GPIO电平读取函数,设置有效触发电平

void button_init(struct Button* handle, uint8_t(*pin_level)(), uint8_t active_level)
{
	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();
	handle->active_level = active_level;
}

初始化按键完成之后,进行按键绑定操作,将绑定按键结构体成员,按键触发事件,按键回调函数

void button_attach(struct Button* handle, PressEvent event, BtnCallback cb)
{
	handle->cb[event] = cb;
}

按键启动:也就是将按键加入链表当中,启动按键。这里选择的插入方式是头部插入法,在链表的头部插入按键节点。效率高,时间复杂度为O(1)。

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;
}

按键删除,将按键从当前链表中删除。使用到了二级指针删除一个按键元素。与之前的多定时器删除方法相同。

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;
	}
}

按键滴答函数,每间隔5ms触发一次按键事件,驱动状态机运行。

void button_ticks()
{
	struct Button* target;
	//按键中断依次循环读取链表中所有的按键元素信息,并且驱动状态机工作
	for(target=head_handle; target; target=target->next) {
		button_handler(target);
	}
}

多按键设计灵魂:

状态机处理思想:读取当前引脚的状态,获取按键当前属于哪种状态。

PressEvent get_button_event(struct Button* handle)
{
	return (PressEvent)(handle->event);
}

状态机,按键处理核心函数,驱动状态机。

/**
  * @brief  Button driver core function, driver state machine.
  * @param  handle: the button handle strcut.
  * @retval None
  */
  
void button_handler(struct Button* handle)
{
	/*读取当前按键引脚电平*/
	uint8_t read_gpio_level = handle->hal_button_Level();

	//ticks counter working..如果有状态机在运行,那么滴答定时器继续工作
	if((handle->state) > 0) handle->ticks++;

	/*------------button debounce handle---------------*/
	/*读取句柄引脚电平,连续读取3次引脚电平实现按键消抖功能*/
	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 { //leved 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);
			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){ // long press up
			handle->state = 0;
		}
		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 { //releasd
			handle->event = (uint8_t)PRESS_UP;
			EVENT_CB(PRESS_UP);
			handle->state = 0; //reset
		}
		break;
	}
}

状态机流程图
在这里插入图片描述

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32菜单链表程序是一种用于嵌入式系统的程序设计结构。它利用链表数据结构来管理不同的菜单选项,并提供用户界面以方便用户进行选择和操作。 该程序的主要目的是实现一个可扩展的菜单系统,用户可以通过按键或触摸屏等输入设备来浏览不同的菜单选项,并选择相应的操作或子菜单。 在STM32菜单链表程序,每个菜单选项都被定义为一个菜单节点,节点包含菜单的名称、标识符、父菜单节点、子菜单节点以及操作函数等信息。这些节点之间通过链表的方式连接起来,形成一个菜单链表。 程序的运行流程如下:首先,程序初始化时会构建整个菜单链表,将所有的菜单选项按照一定的顺序连接起来。然后,程序会进入一个主循环,在主循环通过读取用户输入来判断用户的操作,并根据用户选择的菜单节点进行相应的操作。 用户输入的方式可以根据具体的应用场景进行设定,比如可以通过外部按键、触摸屏、串口或者无线通信等方式实现。程序会根据用户的操作来判断下一步应该显示哪个菜单节点,并调用相应的操作函数来执行相应的操作。 通过这种方式,STM32菜单链表程序可以灵活地管理和扩展菜单选项,方便用户进行各种操作和功能的选择。同时,菜单链表的实现也使得程序的结构清晰、易于维护和扩展。 总的来说,STM32菜单链表程序是一种基于链表数据结构嵌入式系统程序设计结构,它能够提供灵活的菜单系统,方便用户进行选择和操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值