历史上的今天
今天是:2024年08月24日(星期六)
2019年08月24日 | STM32按键长短按:超强移植性,回调函数按键处理机制
2019-08-24 来源:eefocus
1.1 实验简介
按键长按与按键短按在很多产品中都需要应用到,在我们生活中,例如:手机开关机用到的就是按键长按,手机设置音量用的是按键的短按。在本实验平台的综合实验中,也需要用到按键的长短按,所以,我们很有必要学习如何实现按键的程序设计。
设计按键长短按的思路其实很简单,就是计数原理。假设,定时器定时10ms中断一次,在中断函数中,判断按键是否按下,如果按下,然后统计按键按下的时间长度是多少个10ms,如果按下了100个10ms,则表明长按了1秒;如果按下了300个10ms,表示按下了3秒。
1.2 硬件设计
1) KEY2连接到PA8,稳定按下是低电平,稳定松开是高电平。
2) KEY3连接到PB10,稳定按下是低电平,稳定松开是高电平。
1.3 软件设计
1.构造按键参数结构体
typedef void (*keyCallBackFuction)(void); //定义回调函数指针
__packed typedef struct
{
uint8_t keyNum; //按键编号
uint32_t keyRccPeriph; //按键时钟
GPIO_TypeDef *keyPort; //按键所在端口
uint32_t keyGpio; //按键所在的IO口
keyCallBackFuction shortPress; //回调函数成员,短按回调函数
keyCallBackFuction longPress; //回调函数成员,长按回调函数
}keyTypedef_t;
__packed typedef struct
{
uint8_t keyTotolNum; //按键总数
keyTypedef_t *pSingleKey; //该指针单个按键的结构参数
}keysTypedef_t;
首先,我们一起学习一下keyTypedef_t和keysTypedef_t这两个结构体。
keyTypedef_t的作用是:用于保存一个按键的结构参数,结构参数包括:按键的外设时钟、按键所在的端口、按键所对应的IO口、按键长按的回调函数指针、按键短按的回调函数指针。其中,需要注意的参数是:keyNum,keyNum的作用是为每个按键参数结构体提供一个“编号”,也就是说,每个按键都有自己唯一的“编号”。
本实验主要想实现识别按键的长按与短按,并执行相应的回调函数。回调函数的定义请参考上文代码块的“行1”的定义,该函数指针是无返回值且是无形参的。
keysTypedef_t的作用是:如果有多个按键,首先需要定义类型为keyTypedef_t的结构体数组,而定义一个keysTypedef_t变量来统一管理所有的按键结构体参数。其中16行的指针pSingleKey在初始化按键后,会指向“keyTypedef_t的结构体数组”,通过指针偏移来简介访问所有的按键结构参数,从而实现“统一管理所有的按键”。
在本章节,需要实现两个按键的长短按的识别,keyTypedef_t和keysTypedef_t的使用如下:
#define GPIO_KEY_NUM 2 //定义按键总数
keyTypedef_t singleKey[GPIO_KEY_NUM]; //定义单按键结构体数组
keysTypedef_t keys; //定义总体按键模型结构变量
行1:定义一个宏,决定按键的总数
行2:定义单按键结构参数数组,有多少个按键,则该数组需要有多少个成员。
行3:定义总体按键结构体参数变量,该结构体变量用于统一管理所有所有的按键。
2.初始化按键参数
定义好“单按键结构参数数组singleKey”以及“总体按键结构变量keys”后,需要初始化按键的结构参数,结构参数包括:编号、按键的外设时钟、按键所在的端口、按键所对应的IO口、按键长按的回调函数指针、按键短按的回调函数指针。用到的函数是keyInitOne,keyInitOne函数原形如下:
static uint8_t keyTotolNum = 0; //有与统计有多少个按键
/**
* @brief key init function 按键初始化函数
* @param [in] keyRccPeriph :APB2外设时钟
* @param [in] keyPort:IO端口
* @param [in] keyGpio:IO管脚
* @param [in] short_press :按键短按回调函数
* @param [in] long_press :按键长按回到函数
* @return key structure pointer
*/
keyTypedef_t keyInitOne(uint32_t keyRccPeriph,
GPIO_TypeDef * keyPort,
uint32_t keyGpio,
keyCallBackFuction shortPress,
keyCallBackFuction longPress)
{
static uint8_t key_total = 0;
keyTypedef_t singleKey; //结构体变量,用于保存按键IO的参数
//平台定义IO口
singleKey.keyRccPeriph = keyRccPeriph; //保存按键IO口所在的外设时钟总线
singleKey.keyPort = keyPort; //保存按键所在的端口
singleKey.keyGpio = keyGpio; //保存按键所在的IO口
singleKey.keyNum = key_total++; //保存每个按键都有单独的编号
//回调函数定义,按键长按和短按各有自己的回调函数
singleKey.longPress = longPress; //保存按键长按函数指针
singleKey.shortPress = shortPress; //保存按键短按函数指针
keyTotolNum++; //全局静态变量,统计有多少个按键
return singleKey; //返回初始化的按键IO的参数的结构体变量
}
首先,该keyInitOne函数的形参有:keyRccPeriph →APB2外设时钟, keyPort→IO端口,keyGpio→IO管脚,short_press →按键短按回调函数指针,long_press →按键长按回调函数指针。
假设,PA8管脚上挂在有一个按键,则需要将PA8的参数保存起来,则形参keyRccPeriph 需要传入的实参是RCC_APB2Periph_GPIOA, 形参keyPort需要传入的是GPIOA,形参keyGpio需要传入的实数是GPIO_Pin_8,形参short_press 需要传入的是按键短按回调函数指针,long_press →按键长按回调函数指针。
下面,我们一起学习一下上文的代码块:
行1:全局静态变量,用于统计总共有初始化了多少个按键
行17:静态变量,每初始化一个按键,该变量自增1。
行19:定义keyTypedef_t类型的局部变量 singleKey,用于保存按键的的结构参数(外设时钟、端口、IO管脚、按键短按回调函数指针、按键长按回调函数指针)。
行22~24:保存按键所对应的外设时钟总线、端口、IO管脚。
行25:保存按键的标号。
行27~28:保存按键所对应的长按函数指针和短按函数指针。
行30:初始化了的按键总数自增1
行32:返回定义keyTypedef_t类型的局部变量 singleKey,返回保存按键的的结构参数(外设时钟、端口、IO管脚、按键短按回调函数指针、按键长按回调函数指针)。
假设PA8挂有一个按键Key2、PB10挂有一个按键Key3,则Key2和Key3的初始化代码如下所示:
#define GPIO_KEY_NUM 2 //定义按键总数
keyTypedef_t singleKey[GPIO_KEY_NUM]; //定义单按键结构体数组
keysTypedef_t keys; //定义总体按键模型结构变量
/**
* 按键3短按回调函数
* @param none
* @return none
*/
void key3ShortPress(void)
{
Led_Reverse(1); //短按KEY3,则取反LED1
}
/**
* 按键3长按回调函数
* @param none
* @return none
*/
void key3LongPress(void)
{
Led_Reverse(3); //长按KEY3,则取反LED3
}
/**
* KEY2短按回调函数
* @param none
* @return none
*/
void key2ShortPress(void)
{
Led_Reverse(1); //短按KEY2,则取反LED1
}
/**
* 按键2长按回调函数
* @param none
* @return none
*/
void key2LongPress(void)
{
Led_Reverse(2); //长按KEY2,则取反LED2
}
/**
* 按键初始化函数
* @param none
* @return none
*/
void keyInit(void)
{
/*将按键端口参数保存到singleKey结构体数组中,
参数包括:外设端口、IO端口、IO管脚、长按函数指针、短按函数指针*/
singleKey[0] = keyInitOne(RCC_APB2Periph_GPIOB,
GPIOB, GPIO_Pin_10,
key3ShortPress, key3LongPress);
singleKey[1] = keyInitOne(RCC_APB2Periph_GPIOA,
GPIOA, GPIO_Pin_8,
key2ShortPress, key2LongPress);
keys.pSingleKey = (keyTypedef_t *)&singleKey; //pSingKey指针指向singleKey数组
keyParaInit(&keys); //初始化按键所在的IO口并初始化定时器
}
2行:定义一个keyTypedef_t类型的数组 singleKey[GPIO_KEY_NUM],GPIO_KEY_NUM的值为2。一个按键对应一个数组元素。
11~15行:Key3短按回调函数。
22~25行:Key3长按回调函数。
32~35行:Key2短按回调函数。
42~45行:Key2长按回调函数。
3行:定义一个keysTypedef_t 类型的变量keys,用于统一管理所有的按键参数(外设时钟、端口、IO管脚、按键短按回调函数指针、按键长按回调函数指针)。
56~58行:keyInitOne函数原型在前文已经描述。调用keyInitOne函数,将Key3的按键参数保存到元素singleKey[0] 中。参数包括:外设时钟RCC_APB2Periph_GPIOB、端口GPIOB、IO管脚GPIO_Pin_10、按键短按回调函数指针key3ShortPress、按键长按回调函数指针 key3LongPress。
59~61行:keyInitOne函数原型在前文已经描述。调用keyInitOne函数,将Key2的按键参数保存到元素singleKey[1] 中。参数包括:外设时钟RCC_APB2Periph_GPIOA、端口GPIOA、IO管脚GPIO_Pin_8、按键短按回调函数指针key2ShortPress、按键长按回调函数指针key2LongPress。
本实验最终要实现的是,获知按键时长按还是短按,根据长按还是短按执行相应的回调函数,每个按键有单独的长按回调函数和短按回调函数。
62行:定义一个keysTypedef_t 类型的变量keys的指针成员pSingleKey指向keyTypedef_t类型的数组 singleKey[GPIO_KEY_NUM],作用是为了实现用变量keys的指针成员pSingleKey来管理所有按键的参数。
63行:按键IO口的初始化需要配置端口上下拉模式、端口速度等,前文讨论的keyInitOne函数只是用于保存按键IO口的参数,并不是用来初始化IO口,真正初始化IO口的是keyParaInit函数。
下面,我们一起来学习keyParaInit函数。
/**
* @brief 按键参数初始化函数:按键GPIO初始化,并启动定时器来检测按键状态
* @param [in] pkeyS :按键全局结构体,改指针包含了所有的按键的参数
* @return none
*/
void keyParaInit(keysTypedef_t *pkeyS)
{
uint8_t i = 0;
if(NULL == pkeyS) //判断传入的指针是否有指向
{
return ; //如果没有指向,则直接返回,不往下执行
}
pkeyS->keyTotolNum = keyTotolNum; //获取按键总数
/*误差判断,限制最多12个按键,可以通过修改宏KEY_MAX_NUMBER来实现支持更多的按键*/
if( pkeyS->keyTotolNum > KEY_MAX_NUMBER)
{
pkeyS->keyTotolNum = KEY_MAX_NUMBER;
}
for(i = 0; i < pkeyS->keyTotolNum; i++) //取出所有的按键的参数,采用的是下标偏移法
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体,用于初始化IO参数
//取出某按键的外设端口时钟,并使能其时钟
RCC_APB2PeriphClockCmd(pkeyS->pSingleKey[i].keyRccPeriph, ENABLE);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //使能IO口速度
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //使能IO口的模式:上拉模式
GPIO_InitStructure.GPIO_Pin = pkeyS->pSingleKey[i].keyGpio; //取出某按键的管脚编号
GPIO_Init(pkeyS->pSingleKey[i].keyPort,&GPIO_InitStructure);//取出某按键的端口号,进行按键初始化
}
timer4Init(); //初始化定时器2,每隔1ms进入一次更新中断
}
6行:keyParaInit(keysTypedef_t *pkeyS)函数的形参是keysTypedef_t类型的指针,该指针包含了所有按键的结构参数。
10~13行:判断传入的指针是不是空指针,如果是空指针,则返回,不继续往下执行。
15行: keyTotolNum记录了有多少个按键需要进行初始化,将 keyTotolNum的值保存到pkeyS->keyTotolNum的成员中。
17~20行:误差滤除,最多支持的按键不能超过12个。
22行:通过循环,改变下表i的值,来获得每个按键的结构体参数。
26~31行:取出按键的结构参数来进行按键IO口的初始化。
34行:定时器4初始化,使得1ms产生一次中断。
3.按键长短按管理
按键的长短的原理就是通过判断按键按下的时间长度,所以必须使用到定时器。本实验的做法是,通过定时器定时产生中断:每1ms触发一次定时器更新中断,然后在更新中断中进行进行按键长短按的管理。定时4的初始化代码如下:
/**
* @brief 定时器2初始化函数1ms中断一次,TIM2的
*
* @param none
* @return none
*/
void timer4Init(void)
{
u16 arr = 7199; //自动重装载值
u16 psc = 9; //预分频值
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //定时器4时钟使能
/*定时器4初始化*/
TIM_TimeBaseStructure.TIM_Period = arr; //设置重载值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置分频因子
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //递增计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //将结构参数用于初始化定时器4
上一篇:stm32中按键长短按实现方法
下一篇:STM32按键的短按/长按/双击
史海拾趣
|
CE.Net 4.2 中,修改了 platform.reg 文件后,是 Make Image? 还是 Build Platform? 如题,修改了注册表文件后,是不是 Make Image 即可?… 查看全部问答> |
|
显示器的Power Saving Mode是通过软体还是硬件来实现的? 显示器的Power Saving Mode即是显示器休眠省电模式,主要是在电脑没有运行任何程序长时间无人使用时的一种节省电源的一种模式,它是通过软体程序还是通过硬件来控制的呢?… 查看全部问答> |
|
大二学生一枚,现为完成一个小实验项目需用tps61085驱动led。 需实现: 1、100mA以上电流(按道理对于61085来说应该是小菜一碟的); 2、3mA步进可控,精度0.2mA(电流采样INA282); 3、瞬时大电流闪光; 4、添加光敏电阻实现自动调光(不过这 ...… 查看全部问答> |
|
ADP50xx系列电路, 4.5至15V的输入范围,集成开关型1.2A~4A的BUCK电路,以及LDO等。 是一款性能优良,功能完备的集成电源解决方案。 … 查看全部问答> |
|
最近在调一个开关电源 用的是34063 好不容易把纹波调到10mv 但是用示波器测试地电位的时候会有类似于电感输出端形状的 波形 之前有人告诉我是共模噪声 。。。。。。。。。。 我用的极性反转的拓扑 理论上确实是电感通过地线把电流送到开关二极管 ...… 查看全部问答> |




