野火F1开发板STM32案例-MultiButton移植
硬件平台
- 野火STM32F103ZET6 霸道V2开发板
- 正点原子F1系列开发板
软件平台
- Keil MDK 5.31
- 串口调试助手
MultiButton
简介
开源项目 MultiButton,一个小巧简单易用的事件驱动型按键驱动模块,作者 0x1abin。
这个项目非常精简,只有两个文件,可无限量扩展按键,按键事件的回调异步处理方式可以简化程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。
MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。
使用方法
1.先申请一个按键结构
struct Button button1;
2.初始化按键对象,绑定按键的GPIO电平读取接口read_button_pin() ,后一个参数设置有效触发电平
button_init(&button1, read_button_pin, 0);
3.注册按键事件
button_attach(&button1, SINGLE_CLICK, Callback_SINGLE_CLICK_Handler);
button_attach(&button1, DOUBLE_CLICK, Callback_DOUBLE_Click_Handler);
...
4.启动按键
button_start(&button1);
5.设置一个5ms间隔的定时器循环调用后台处理函数
while(1) {
...
if(timer_ticks == 5) {
timer_ticks = 0;
button_ticks();
}
}
特性
MultiButton 使用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 (*hal_button_Level)(void);
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_RRESS_START | 达到长按时间阈值时触发一次 |
LONG_PRESS_HOLD | 长按期间一直触发 |
官方Examples
#include "button.h"
struct Button btn1;
int read_button1_GPIO()
{
return HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin);
}
int main()
{
button_init(&btn1, read_button1_GPIO, 0);
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_RRESS_START, BTN1_LONG_RRESS_START_Handler);
button_attach(&btn2, 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)
{
}
}
void BTN1_PRESS_DOWN_Handler(void* btn)
{
//do something...
}
void BTN1_PRESS_UP_Handler(void* btn)
{
//do something...
}
野火F1开发板STM32案例-MultiButton移植
MAIN.C
/**
******************************************************************************
* @file main.c
* 实验平台:野火 F103-霸道 STM32 开发板
******************************************************************************
* 第三方库文件导入 START THE FILE
******************************************************************************
*/
//基本库
#include "stm32f10x.h"
#include "bsp_SysTick.h"
#include "bsp_usart.h"
#include <stdio.h>
#include "delay.h"
//扩展库
#include "multi_button.h"//按键驱动
/********************************************************************************
变量定义
*********************************************************************************/
struct Button button1;
// 引脚定义
#define KEY1_GPIO_CLK RCC_APB2Periph_GPIOA
#define KEY1_GPIO_PORT GPIOA
#define KEY1_GPIO_PIN GPIO_Pin_0
#define KEY2_GPIO_CLK RCC_APB2Periph_GPIOC
#define KEY2_GPIO_PORT GPIOC
#define KEY2_GPIO_PIN GPIO_Pin_13
/********************************************************************************
函数部分
*********************************************************************************/
void KEY_Init(void) //IO初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
/*开启按键端口的时钟*/
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK|KEY2_GPIO_CLK,ENABLE);
//选择按键KEY1的引脚
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
// 设置按键的引脚为浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//使用结构体初始化按键
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
//选择按键的引脚
GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN;
//设置按键的引脚为浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//使用结构体初始化按键
GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure);
}
//按键状态读取接口 按键输入模式 ReadInputDataBit
uint8_t read_button1_GPIO()
{
return GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN);
}
//按键1按下事件回调函数
void btn1_press_down_Handler(void* btn)
{
printf("---> key1 press down! <---\r\n");
}
//按键1松开事件回调函数
void btn1_press_up_Handler(void* btn)
{
printf("***> key1 press up! <***\r\n");
}
void button_callback(void *button)
{
uint32_t btn_event_val;
btn_event_val = get_button_event((struct Button *)button);
switch(btn_event_val)
{
case PRESS_DOWN:
printf("---> key1 press down! <---\r\n");
break;
case PRESS_UP:
printf("***> key1 press up! <***\r\n");
break;
case PRESS_REPEAT:
printf("---> key1 press repeat! <---\r\n");
break;
case SINGLE_CLICK:
printf("---> key1 single click! <---\r\n");
break;
case DOUBLE_CLICK:
printf("***> key1 double click! <***\r\n");
break;
case LONG_PRESS_START:
printf("---> key1 long press start! <---\r\n");
break;
case LONG_PRESS_HOLD:
printf("***> key1 long press hold! <***\r\n");
break;
}
}
/**
* @brief HardWare_Iint
* @param 无
* @retval 无
*/
void HardWare_Iint(void)
{
SysTick_Init();
delay_init();
KEY_Init();
}
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/* 硬件端口初始化 */
HardWare_Iint();
printf("MultiButton Test...\r\n");
//初始化按键对象
button_init(&button1, read_button1_GPIO, 0);
//注册按钮事件回调函数
button_attach(&button1, PRESS_DOWN, button_callback);
button_attach(&button1, PRESS_UP, button_callback);
//启动按键
button_start(&button1);
while(1)
{
button_ticks();
delay_ms(5);
}
}
multi_button.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]((Button*)handle)
//button handle list head.
static struct Button* head_handle = NULL;
/**
* @brief Initializes the button struct handle.
* @param handle: the button handle strcut.
* @param pin_level: read the HAL GPIO of the connet button level.
* @param active_level: pressed GPIO level.
* @retval None
*/
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;
}
/**
* @brief Attach the button event callback function.
* @param handle: the button handle strcut.
* @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 strcut.
* @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 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---------------*/
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;
}
}
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;
}
}
/**
* @brief Start the button work, add the handle into work list.
* @param handle: target handle strcut.
* @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 strcut.
* @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);
} else
curr = &entry->next;
}
}
/**
* @brief background ticks, timer repeat invoking interval 5ms.
* @param None.
* @retval None
*/
void button_ticks()
{
struct Button* target;
for(target=head_handle; target; target=target->next) {
button_handler(target);
}
}
multi_button.h
/*
* 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"
#include "stm32f10x.h"
//According to your need to modify the constants.
#define TICKS_INTERVAL 5 //ms
#define DEBOUNCE_TICKS 3 //MAX 8
#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 {
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;
#ifdef __cplusplus
extern "C" {
#endif
void button_init(struct Button* handle, uint8_t(*pin_level)(), uint8_t active_level);
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);
#ifdef __cplusplus
}
#endif
#endif
bsp_usart.c
/**
******************************************************************************
* @file bsp_usart.c
* @version V1.0
* @date 2013-xx-xx
* @brief 调试用的printf串口,重定向printf到串口
******************************************************************************
* @attention
*
* 实验平台:野火 F103-霸道 STM32 开发板
* 论坛 :http://www.firebbs.cn
* 淘宝 :https://fire-stm32.taobao.com
*
******************************************************************************
*/
#include "./usart/bsp_usart.h"
/**
* @brief USART GPIO 配置,工作参数配置
* @param 无
* @retval 无
*/
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 打开串口外设的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
// 配置串口的工作参数
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 配置 针数据字长
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置校验位
USART_InitStructure.USART_Parity = USART_Parity_No ;
// 配置硬件流控制
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
// 配置工作模式,收发一起
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 完成串口的初始化配置
USART_Init(DEBUG_USARTx, &USART_InitStructure);
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
bsp_usart.h
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
#include <stdio.h>
/**
* 串口宏定义,不同的串口挂载的总线和IO不一样,移植时需要修改这几个宏
* 1-修改总线时钟的宏,uart1挂载到apb2总线,其他uart挂载到apb1总线
* 2-修改GPIO的宏
*/
// 串口1-USART1
#define DEBUG_USARTx USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
void USART_Config(void);
#endif /* __USART_H */