stm32外设笔记-按键状态机


本文用到的实验平台:

  • 野火MINI-stm32开发板
  • STM32CUBE-IDE开发工具

平时写一些程序的时候如果涉及到一些选择的话,经常需要用到按键什么的,不同的方法写的按键又不一样,有时候需要实现长按短按什么的也不太方便,因此我最近就从github上找了些例程,用起来还是很不错的,下面我对按键的使用进行一些总结,这里要特别感谢开源贡献者。

1、配置io口实现按键功能

野火MINIstm32开发板上的按键如下所示,可以看到这个按键做的还是很好的,加上了电容,电阻也有,可以很好的减缓按键按下时候的毛刺。
在这里插入图片描述
同时我们注意到这里按键是被硬件下拉了的,所以我们在stm32的配置中就无需设置上下拉输入了,直接设置为既不上拉也不下拉即可,不过一旦初始化之后就会被外部硬件拉低,所以我们直接读取肯定就是低电平,这个时候按下按键就会变为高电平了!
在这里插入图片描述
除此之外比较常用的还有中断的方式,中断其实分的挺细的,有事件中断,外部中断两种,也都是上升沿,下降沿,然后上升沿和下降沿,这样组合就是六种了,我们根据实际需求配置即可!
在这里插入图片描述
下面是我配置的框图,下降沿中断,引脚上拉

在这里插入图片描述
这里注意如果开启了中断,一定要去NVIC这里去把中断勾上,其他大部分外设都可以直接选,GPIO的比较隐藏,需要自己去勾选,不要忘了,不然中断不起来
在这里插入图片描述
中断函数都在it.c的文件中,可以查阅中文参考手册,因为我这个几个引脚都是10-15的,所以公用一根中断线
在这里插入图片描述
一般我们写中断后的事情也就是在中断回调函数里面来写了

在这里插入图片描述

2、第一种按键状态机及其实现

按键状态机使得我们可以傻瓜式的使用按键,对按键绑定事件,触发情况等,方便按键触发,下面的这种支持单击,连续按下,短按,长按等功能,具体使用如下:

绑定按键对象,并将按键处理函数放入循环中

在这里插入图片描述
编写回调函数,他是所有的按键都给了回调函数,所以就是在回调函数里面来选按键
在这里插入图片描述
回调函数代码如下

Button btn_k1,btn_k2;
#define BUTTON_KEY1_ID	1
#define BUTTON_KEY2_ID	2

//void button_pressing_callback(uint8_t button_id)
//{
//	switch(button_id)
//	{
//		case BUTTON_KEY1_ID:
//			printf("KEY1被按下\r\n");
//			break;
//		case BUTTON_KEY2_ID:
//			printf("KEY2被按下\r\n");
//			break;
//	}
//}
//void button_release_callback(uint8_t button_id)
//{
//	switch(button_id)
//	{
//		case BUTTON_KEY1_ID:
//			printf("KEY1被松开\r\n");
//			break;
//		case BUTTON_KEY2_ID:
//			printf("KEY2被松开\r\n");
//			break;
//	}
//}
void button_press_short_callback(uint8_t button_id)
{
//	switch(button_id)
//	{
//		case BUTTON_KEY1_ID:
//			printf("KEY1短按\r\n");
//			break;
//		case BUTTON_KEY2_ID:
//			printf("KEY2短按\r\n");
//			break;
//	}
}
void button_pressing_timeout_callback(uint8_t button_id)
{
//	switch(button_id)
//	{
//		case BUTTON_KEY1_ID:
//			printf("KEY1长按\r\n");
//			break;
//		case BUTTON_KEY2_ID:
//			printf("KEY2长按\r\n");
//			break;
//	}
}
void button_pressing_accel_callback(uint8_t button_id) //就感觉这个也有点像长按,基本功能是一样的
{
	switch(button_id)
	{
		case BUTTON_KEY1_ID:
			printf("KEY1快速\r\n");
			break;
		case BUTTON_KEY2_ID:
			printf("KEY2快速\r\n");
			break;
	}
}

代码全文如下

button.c

#include "button.h"

__weak void button_pressing_callback(uint8_t button_id)
{

}
__weak void button_release_callback(uint8_t button_id)
{

}
__weak void button_press_short_callback(uint8_t button_id)
{

}
__weak void button_pressing_timeout_callback(uint8_t button_id)
{

}
__weak void button_pressing_accel_callback(uint8_t button_id)
{

}
void handle_button(Button *btn)
{
	btn->current_status = HAL_GPIO_ReadPin(btn->gpio_port,btn->gpio_pin);
	if(btn->button_active == BUTTON_ACTIVE_HIGH)
	{
		 btn->current_status = !btn->current_status;
	}
	switch(btn->button_state)
	{
		case BUTTON_READ:
		{
			if((btn->current_status == 0 && btn->last_status == 1) )
			{
						btn->time_debounce = HAL_GetTick();
						btn->button_state = BUTTON_WAIT_DEBOUND;
			}
		}
		break;
		case BUTTON_WAIT_DEBOUND:
		{
			if(HAL_GetTick() - btn->time_debounce>= TIME_DEBOUND_BUTTON)
			{
				if(btn->current_status ==0 && btn->last_status ==1)//nhan xuong
				{
					button_pressing_callback(btn->button_id);
					btn->t_long_press = HAL_GetTick();
					btn->last_status = 0;
					btn->t_accel_press = HAL_GetTick();
					btn->t_accel_call = TIME_ACCEL_MAX;
					btn->button_state = BUTTON_WAIT_RELEASE_AND_CHECK_LONG_PRESS;
				}
				else if(btn->current_status ==1 && btn->last_status ==0)//nha ra
				{
					btn->t_long_press = HAL_GetTick() - btn->t_long_press;
					if(btn->t_long_press <= TIME_SHORT_PRESS)
					{
						button_press_short_callback(btn->button_id);
					}
					button_release_callback(btn->button_id);
					btn->last_status = 1;
					btn->button_state = BUTTON_READ;
				}
				else //khong dung
				{
					btn->last_status = 1;
					btn->button_state = BUTTON_READ;
				}
			}
		}
		break;
		case BUTTON_WAIT_RELEASE_AND_CHECK_LONG_PRESS:
		{
				if(btn->current_status == 1 && btn->last_status == 0)
				{
					btn->button_state = BUTTON_WAIT_DEBOUND;
					btn->time_debounce = HAL_GetTick();
				}
				else if(HAL_GetTick() - btn->t_long_press >= TIME_LONG_PRESS)
				{
					button_pressing_timeout_callback(btn->button_id);
					btn->button_state = BUTTON_WAIT_RELEASE;
				}
				else if(HAL_GetTick() -  btn->t_accel_press >= btn->t_accel_call)
				{
					btn->t_accel_call -=TIME_ACCEL_DELTA;
					if(btn->t_accel_call <= TIME_ACCEL_MIN)
					{
						btn->t_accel_call = TIME_ACCEL_MIN;
					}
					button_pressing_accel_callback(btn->button_id);
					btn->t_accel_press = HAL_GetTick();
				}
		}
		break;
		case BUTTON_WAIT_RELEASE:
		{
			if(btn->current_status == 1 && btn->last_status == 0)
			{
				btn->button_state = BUTTON_WAIT_DEBOUND;
				btn->time_debounce = HAL_GetTick();
			}
			else if(HAL_GetTick() -  btn->t_accel_press >= btn->t_accel_call)
			{
				btn->t_accel_call -=TIME_ACCEL_DELTA;
				if(btn->t_accel_call <= TIME_ACCEL_MIN)
				{
					btn->t_accel_call = TIME_ACCEL_MIN;
				}
				button_pressing_accel_callback(btn->button_id);
				btn->t_accel_press = HAL_GetTick();
			}
		}
		break;
		default:
			break;
	}
}

//button_active: BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_HIGH
void Button_init(Button *btn,GPIO_TypeDef *port,uint16_t pin,uint8_t button_active,uint8_t button_id)
{
	btn->gpio_port = port;
	btn->gpio_pin = pin;
	btn->button_active = button_active;
	btn->button_state = BUTTON_READ;
	btn->button_id = button_id;
	btn->current_status = 1;
	btn->last_status = 1;
}

button.h文件

#include "main.h"

#define TIME_DEBOUND_BUTTON	20
#define TIME_SHORT_PRESS	1000
#define TIME_LONG_PRESS	2000
#define TIME_ACCEL_MIN	100
#define TIME_ACCEL_MAX	1000
#define TIME_ACCEL_DELTA	200

#define BUTTON_ACTIVE_LOW	0
#define BUTTON_ACTIVE_HIGH	1

typedef enum
{
	BUTTON_READ,
	BUTTON_WAIT_DEBOUND,
	BUTTON_WAIT_RELEASE_AND_CHECK_LONG_PRESS,
	BUTTON_WAIT_RELEASE
}Button_State;
typedef struct
{
	GPIO_TypeDef *gpio_port;
	uint16_t gpio_pin;
	uint8_t current_status;
	uint8_t last_status;
	uint32_t time_debounce;
	uint32_t t_long_press;
	Button_State button_state;
	uint8_t button_active;
	uint8_t button_id;
	int16_t t_accel_call;
	uint32_t t_accel_press;
}Button;

void handle_button(Button *btn);
void Button_init(Button *btn,GPIO_TypeDef *port,uint16_t pin,uint8_t button_active,uint8_t button_id);

3、第二种按键状态机及其实现

第二种方式我觉得就更加强大了,不仅支持短按长按,还有双击,连续按下的次数等,用起来也很方便

首先注册按键并设置按键模式,他这个就不是对所有按键都统一了,要看你设置的模式,有没有加入相关功能,之后就是编写回调函数了

在这里插入图片描述
在初始化部分我们初始化按键对象,之后这个按键需要提供一个5ms的心跳,使用while循环或者定时器都是可以的,这里偷懒直接使用while循环
在这里插入图片描述
把结果通过串口打印,可以看到还是很正常的!!!

在这里插入图片描述

第二种按键状态机的代码如下

button02.c

#include "Button02.h"

#define EVENT_CB(ev)    \
	if (handle->cb[ev]) \
	handle->cb[ev]((Button_t)handle,(PressEvent)handle->event,(uint8_t)handle->repeat)

typedef void (*BtnCallback)(void*, PressEvent, uint8_t);

typedef struct {
	uint16_t ticks;
	uint8_t repeat :4; // max 15
	uint8_t event :4;
	uint8_t state :3;
	uint8_t debounce_cnt :3;
	uint8_t active_level :1;
	uint8_t button_level :1;
	GPIO_TypeDef *GPIOx;
	uint16_t GPIO_PIN_x;
	BtnCallback cb[EVENT_NUM];
	struct Button *next;
} Button;

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

/**
 初始化按键结构体
 */
Button_t button_init(const Btn_init_attr *attr) {
	// allocate memory.
	Button *thisHandle = (Button*) malloc(sizeof(Button));
	if (!thisHandle) {
		return NULL; // allocate memory failed.
	}

	memset(thisHandle, 0, sizeof(Button));
	thisHandle->event = (uint8_t) NONE_PRESS;
	thisHandle->GPIOx = attr->GPIOx;
	thisHandle->GPIO_PIN_x = attr->GPIO_PIN_x;
	thisHandle->active_level = (uint8_t) attr->active_level;
	thisHandle->button_level = !thisHandle->active_level;

	// Attach the button event callback function
	for (uint8_t i = 0; i < attr->event_num; i++) {
		thisHandle->cb[attr->event[i]] =
				(void (*)(void*, PressEvent, uint8_t)) button_callback;
	}

	// start button
	thisHandle->next = head_handle;
	head_handle = thisHandle;
	return (Button_t) thisHandle; // success
}

/**
 按键驱动状态机
 */
static void button_handler(Button *handle) {
	uint8_t read_gpio_level = (uint8_t) HAL_GPIO_ReadPin(handle->GPIOx,
			handle->GPIO_PIN_x);

	// 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 { // 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++;
			handle->event = (uint8_t) PRESS_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) {
			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 { // released
			handle->event = (uint8_t) PRESS_UP;
			EVENT_CB(PRESS_UP);
			handle->state = 0; // reset
		}
		break;
	}
}

/**
 移除按键实例化对象
 */
void button_deInit(Button_t handle) {
	Button **curr;
	for (curr = &head_handle; *curr;) {
		Button *entry = *curr;
		if (entry == (Button*) handle) {
			*curr = entry->next;
			free(entry);
		} else
			curr = &entry->next;
	}
}

/**
 定时器重复5ms进行
 */
void button_ticks() {
	Button *target;
	for (target = head_handle; target; target = target->next) {
		button_handler(target);
	}
}

/**
  回调函数,输入实例化按键,事件,次数
 */
void __attribute__((weak)) button_callback(Button_t btn, PressEvent event,
		uint8_t repeat) {
	// nothing to do,please rewrite.
}

button02.h

#include "main.h"

#define TIME_DEBOUND_BUTTON	20
#define TIME_SHORT_PRESS	1000
#define TIME_LONG_PRESS	2000
#define TIME_ACCEL_MIN	100
#define TIME_ACCEL_MAX	1000
#define TIME_ACCEL_DELTA	200

#define BUTTON_ACTIVE_LOW	0
#define BUTTON_ACTIVE_HIGH	1

typedef enum
{
	BUTTON_READ,
	BUTTON_WAIT_DEBOUND,
	BUTTON_WAIT_RELEASE_AND_CHECK_LONG_PRESS,
	BUTTON_WAIT_RELEASE
}Button_State;
typedef struct
{
	GPIO_TypeDef *gpio_port;
	uint16_t gpio_pin;
	uint8_t current_status;
	uint8_t last_status;
	uint32_t time_debounce;
	uint32_t t_long_press;
	Button_State button_state;
	uint8_t button_active;
	uint8_t button_id;
	int16_t t_accel_call;
	uint32_t t_accel_press;
}Button;

void handle_button(Button *btn);
void Button_init(Button *btn,GPIO_TypeDef *port,uint16_t pin,uint8_t button_active,uint8_t button_id);
  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桃成蹊2.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值