参考:
感谢开源项目和其他作者的分享,本文为新手CH582F裸机移植MultiButton按键库的过程记录。
0. MultiButton
MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。
下载源码MultiButton
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 button_id;
uint8_t (*hal_button_Level)(uint8_t button_id_);
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_PRESS_START | 达到长按时间阈值时触发一次 |
LONG_PRESS_HOLD | 长按期间一直触发 |
1. 新建工程
这里我是在SysTick工程的基础上修改的,CH582使用SysTick滴答定时器
添加MultiButton源码到裸机工程中,并添加头文件路径:
2. 简单使用MultiButton
我这里是在SysTick_Handler()
中每隔5ms调用一次按键后台处理函数button_ticks()
的:
这里要注意!CH582官方库中的GPIOx_ReadPortPin()
函数的返回值是32位的,而button_init
中pin_level
函数的返回值需要是uint8_t
类型的。
// CH57x_gpio.h
/**
* @brief GPIOA端口引脚状态,0-引脚低电平,(!0)-引脚高电平
*
* @param pin - PA0-PA15
*
* @return GPIOA端口引脚状态
*/
#define GPIOA_ReadPortPin(pin) (R32_PA_PIN & (pin))
/**
* @brief GPIOB端口引脚状态,0-引脚低电平,(!0)-引脚高电平
*
* @param pin - PB0-PB23
*
* @return GPIOB端口引脚状态
*/
#define GPIOB_ReadPortPin(pin) (R32_PB_PIN & (pin))
需要将返回值做个转换,return (GPIOB_ReadPortPin(GPIO_Pin_13) == 0 ? 0 : 1);
/**
* @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.
* @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;
}
main.c
如下:
/*
* @Author : stark1898y gg1658608470@gmail.com
* @Date : 2022-11-26 09:54:23
* @LastEditors : stark1898y gg1658608470@gmail.com
* @LastEditTime : 2022-11-26 12:47:25
* @FilePath : \CH582F_MultiButton\src\Main.c
* @Description : CH582F使用MultiButton基于SysTick
*
* Copyright (c) 2022 by stark1898y gg1658608470@gmail.com, All Rights Reserved.
*/
#include "CH58x_common.h"
#include "multi_button.h"
uint8_t TxBuff[] = "This is a tx exam\r\n";
uint8_t RxBuff[100];
uint8_t trigB;
// 设定嘀嗒时间 5 ms
// 这里是在SysTick_Handler()中每隔5ms调用一次按键后台处理函数button_ticks();
#define SYSTICK_INTERVAL (5)
// SysTick完成一次计时中断的标志
volatile uint8_t systick_flag = 0;
#define BUTTON_1_ID 1
//先申请一个按键结构
struct Button button1;
/**
* @description: 按键读取GPIO电平
* @param {uint8_t} button_id 自己定义的按键id
* @return {uint8_t} 0-引脚低电平,(!0)-引脚高电平
*/
uint8_t Button_ReadPin(uint8_t button_id)
{
// you can share the GPIO read function with multiple Buttons
switch(button_id)
{
case BUTTON_1_ID:
return (GPIOB_ReadPortPin(GPIO_Pin_13) == 0 ? 0 : 1);
break;
default:
return 0;
break;
}
}
/**
* @description: 按键1单击回调函数
* @param {void*} btn
* @return {void}
*/
void Button1_SingleClickHandler(void* btn)
{
printf("Button1 SingleClick!\r\n");
}
/**
* @description: 按键1双击回调函数
* @param {void*} btn
* @return {void}
*/
void Button1_DoubleClickHandler(void* btn)
{
printf("Button1 DoubleClick!\r\n");
}
/**
* @description: 按键1长按开始回调函数
* @param {void*} btn
* @return {void}
*/
void Button1_LongStartHandler(void* btn)
{
printf("Button1 LongStart!\r\n");
}
/*********************************************************************
* @fn main
*
* @brief 主函数
*
* @return none
*/
int main()
{
SetSysClock(CLK_SOURCE_PLL_60MHz);
// 自动重新加载计数值,计数时钟60M,以1ms为例,参数是60000
SysTick_Config( GetSysClock() / 1000 * SYSTICK_INTERVAL); //设定嘀嗒时间1000ms
/* 配置串口1:先配置IO口模式,再配置串口 */
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU); // RXD-配置上拉输入
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出,注意先让IO口输出高电平
UART1_DefInit();
// 中断方式:接收数据后发送出去
UART1_ByteTrigCfg(UART_7BYTE_TRIG);
trigB = 7;
UART1_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
PFIC_EnableIRQ(UART1_IRQn);
PRINT("GetSysClock: %d\r\n", GetSysClock());
PRINT("sizeof GPIOB_ReadPortPin: %d\r\n", sizeof(GPIOB_ReadPortPin(GPIO_Pin_13)));
// 按键管脚设置为上拉输入
GPIOB_ModeCfg(GPIO_Pin_13, GPIO_ModeIN_PU);
// 初始化button1对象,绑定按键的GPIO电平读取接口Button_ReadPin() ,第三个参数设置有效触发电平
button_init(&button1, Button_ReadPin, 0, BUTTON_1_ID);
// 注册Button1的事件回调函数
button_attach(&button1, SINGLE_CLICK, Button1_SingleClickHandler);
button_attach(&button1, DOUBLE_CLICK, Button1_DoubleClickHandler);
button_attach(&button1, LONG_PRESS_START, Button1_LongStartHandler);
// 启动button1
button_start(&button1);
while(1)
{
// if(GPIOB_ReadPortPin(GPIO_Pin_13) == 0)
// {
// DelayMs(20);
// if(GPIOB_ReadPortPin(GPIO_Pin_13) == 0)
// {
// PRINT("KEY PRESS\r\n");
// }
// while(GPIOB_ReadPortPin(GPIO_Pin_13) == 0);
// PRINT("KEY up\r\n");
// }
}
}
// SysTick中断函数
__INTERRUPT
__HIGH_CODE
void SysTick_Handler()
{
systick_flag = 1;
SysTick->SR = 0; // 清除中断标志
//每隔5ms调用一次按键后台处理函数
button_ticks();
}
/*********************************************************************
* @fn UART1_IRQHandler
*
* @brief UART1中断函数
*
* @return none
*/
__INTERRUPT
__HIGH_CODE
void UART1_IRQHandler(void)
{
volatile uint8_t i;
switch(UART1_GetITFlag())
{
case UART_II_LINE_STAT: // 线路状态错误
{
// UART1_GetLinSTA();
break;
}
case UART_II_RECV_RDY: // 数据达到设置触发点
for(i = 0; i != trigB; i++)
{
RxBuff[i] = UART1_RecvByte();
UART1_SendByte(RxBuff[i]);
}
break;
case UART_II_RECV_TOUT: // 接收超时,暂时一帧数据接收完成
i = UART1_RecvString(RxBuff);
UART1_SendString(RxBuff, i);
break;
case UART_II_THR_EMPTY: // 发送缓存区空,可继续发送
break;
case UART_II_MODEM_CHG: // 只支持串口0
break;
default:
break;
}
}
测试效果如下: