精妙的按键扫描函数,不采用延时消抖,实现长短按,创...
本按键实现移植机制云的stm32按键扫描,不过也有些不同。不同点:
在定时器中断中扫描,不用10ms延时,动态响应极好。实现长按短按。
创建对象极度方便。
用户只需要根据自己需要修改keyInit( )函数和下图中的变量即可。
接下来是51的介绍.................................................
上传.c文件。51附例程,32只有只有.c文件。
观摩下这个按键算法。 状态机法,都是这么处理的 几年前做过C++的按键类,可创建多实例,线程计时,不用阻塞delay,长短按、消抖参数可独立设置,按下、弹起、click等多种事件支持 楼主大家都是这么干的 xouou_53320 发表于 2017-9-11 12:06
楼主大家都是这么干的
高手是怎么干的。新手进阶高手可以看看。{:lol:} 顶 batsong 发表于 2017-9-11 11:58
几年前做过C++的按键类,可创建多实例,线程计时,不用阻塞delay,长短按、消抖参数可独立设置,按下、弹起 ...
厉害了{:lol:},我没学过C++ 按键扫描还用delay死等,是没真正做过项目的,,, 看不懂,额额 乐鑫的也不错:
中断方式,回调函数。
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2016 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "ets_sys.h"
#include "os_type.h"
#include "osapi.h"
#include "mem.h"
#include "gpio.h"
#include "user_interface.h"
#include "driver/key.h"
LOCAL void key_intr_handler(void *arg);
/******************************************************************************
* FunctionName : key_init_single
* Description: init single key's gpio and register function
* Parameters : uint8 gpio_id - which gpio to use
* uint32 gpio_name - gpio mux name
* uint32 gpio_func - gpio function
* key_function long_press - long press function, needed to install
* key_function short_press - short press function, needed to install
* Returns : single_key_param - single key parameter, needed by key init
*******************************************************************************/
struct single_key_param *ICACHE_FLASH_ATTR
key_init_single(uint8 gpio_id, uint32 gpio_name, uint8 gpio_func, key_function long_press, key_function short_press)
{
struct single_key_param *single_key = (struct single_key_param *)os_zalloc(sizeof(struct single_key_param));
//os_printf("+++ %s single_key: 0x%x gpio_id:%d gpio_name:%d gpio_func:%d lone_press:0x%x short_press : 0x%x \n",__FUNCTION__,single_key,gpio_id,gpio_name,gpio_func,long_press,short_press);
single_key->gpio_id = gpio_id;
single_key->gpio_name = gpio_name;
single_key->gpio_func = gpio_func;
single_key->long_press = long_press;
single_key->short_press = short_press;
return single_key;
}
/******************************************************************************
* FunctionName : key_init
* Description: init keys
* Parameters : key_param *keys - keys parameter, which inited by key_init_single
* Returns : none
*******************************************************************************/
void ICACHE_FLASH_ATTR
key_init(struct keys_param *keys)
{
uint8 i;
ETS_GPIO_INTR_ATTACH(key_intr_handler, keys);
ETS_GPIO_INTR_DISABLE();
os_printf("+++ %s key_num: %d \n",__FUNCTION__,keys->key_num);
for (i = 0; i < keys->key_num; i++) {
keys->single_key->key_level = 1;
PIN_FUNC_SELECT(keys->single_key->gpio_name, keys->single_key->gpio_func);
gpio_output_set(0, 0, 0, GPIO_ID_PIN(keys->single_key->gpio_id));
gpio_register_set(GPIO_PIN_ADDR(keys->single_key->gpio_id), GPIO_PIN_INT_TYPE_SET(GPIO_PIN_INTR_DISABLE)
| GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_DISABLE)
| GPIO_PIN_SOURCE_SET(GPIO_AS_PIN_SOURCE));
//clear gpio14 status
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(keys->single_key->gpio_id));
//enable interrupt
gpio_pin_intr_state_set(GPIO_ID_PIN(keys->single_key->gpio_id), GPIO_PIN_INTR_NEGEDGE);
}
ETS_GPIO_INTR_ENABLE();
}
/******************************************************************************
* FunctionName : key_5s_cb
* Description: long press 5s timer callback
* Parameters : single_key_param *single_key - single key parameter
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
key_5s_cb(struct single_key_param *single_key)
{
os_printf("\n\n+++++++++++++++++ %s\n\n\n",__FUNCTION__);
os_timer_disarm(&single_key->key_5s);
// low, then restart
if (0 == GPIO_INPUT_GET(GPIO_ID_PIN(single_key->gpio_id))) {
if (single_key->long_press) {
single_key->long_press();
os_printf("\n\n+++++++++++++++++ 0 == GPIO_INPUT_GET(GPIO_ID_PIN(single_key->gpio_id))%s\n\n\n",__FUNCTION__);
}
}
}
/******************************************************************************
* FunctionName : key_50ms_cb
* Description: 50ms timer callback to check it's a real key push
* Parameters : single_key_param *single_key - single key parameter
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
key_50ms_cb(struct single_key_param *single_key)
{
os_printf("\n\n+++++++++++++++++ %s\n\n\n",__FUNCTION__);
os_timer_disarm(&single_key->key_50ms);
// high, then key is up
if (1 == GPIO_INPUT_GET(GPIO_ID_PIN(single_key->gpio_id))) {
os_timer_disarm(&single_key->key_5s);
single_key->key_level = 1;
gpio_pin_intr_state_set(GPIO_ID_PIN(single_key->gpio_id), GPIO_PIN_INTR_NEGEDGE);
if (single_key->short_press) {
single_key->short_press();
}
} else {
gpio_pin_intr_state_set(GPIO_ID_PIN(single_key->gpio_id), GPIO_PIN_INTR_POSEDGE);
}
}
/******************************************************************************
* FunctionName : key_intr_handler
* Description: key interrupt handler
* Parameters : key_param *keys - keys parameter, which inited by key_init_single
* Returns : none
*******************************************************************************/
LOCAL void
key_intr_handler(void *arg)
{
uint8 i;
uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
struct keys_param *keys = (struct keys_param *)arg;
//os_printf("+++++++++++++++++ %s gpio_status:%d key_num: %d\n",__FUNCTION__,gpio_status,keys->key_num);
for (i = 0; i < keys->key_num; i++) {
//os_printf("+++++++++++++++++ gpio_status:%d keys->single_key[%d]->gpio_id:%d BIT(keys->single_key[ %d ]->gpio_id) : %d \n",gpio_status,i,keys->single_key->gpio_id,i,BIT(keys->single_key->gpio_id));
if (gpio_status & BIT(keys->single_key->gpio_id)) {
//os_printf("+++++++++++++++++ i: %d \n",i);
//disable interrupt
gpio_pin_intr_state_set(GPIO_ID_PIN(keys->single_key->gpio_id), GPIO_PIN_INTR_DISABLE);
//clear interrupt status
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & BIT(keys->single_key->gpio_id));
if (keys->single_key->key_level == 1) {
// 5s, restart & enter softap mode
os_timer_disarm(&keys->single_key->key_5s);
os_timer_setfn(&keys->single_key->key_5s, (os_timer_func_t *)key_5s_cb, keys->single_key);
os_timer_arm(&keys->single_key->key_5s, 5000, 0);
keys->single_key->key_level = 0;
gpio_pin_intr_state_set(GPIO_ID_PIN(keys->single_key->gpio_id), GPIO_PIN_INTR_POSEDGE);
} else {
// 50ms, check if this is a real key up
os_timer_disarm(&keys->single_key->key_50ms);
os_timer_setfn(&keys->single_key->key_50ms, (os_timer_func_t *)key_50ms_cb, keys->single_key);
os_timer_arm(&keys->single_key->key_50ms, 50, 0);
}
}
}
}
回调函数和初始化:
void key_down50ms_func(void)
{
os_printf("\n\n+++++++++++++++++ %s\n\n\n",__FUNCTION__);
os_printf("key down 50ms!\n");
}
void key_down5s_func(void)
{
os_printf("+++++++++++++++++ %s\n",__FUNCTION__);
os_printf("start smartconfig............!\n");
smartconfig_set_type(SC_TYPE_ESPTOUCH); //SC_TYPE_ESPTOUCH,SC_TYPE_AIRKISS,SC_TYPE_ESPTOUCH_AIRKISS
wifi_set_opmode(STATION_MODE);
smartconfig_start(smartconfig_done);
}
struct keys_param key;
struct single_key_param* single_key;
void my_key_init(void)
{
struct single_key_param *psingle_key;// = //&single_key;
psingle_key =
key_init_single(GPIO_ID_PIN(2),PERIPHS_IO_MUX_GPIO2_U,FUNC_GPIO2,key_down5s_func,key_down50ms_func);
key.key_num = 1;
//key.single_key = key_init_single(2,2,(key_function)NULL,(key_function)NULL,(key_function)NULL);
single_key = psingle_key;
key.single_key = single_key;//&psingle_key;
key_init(&key);
}
发一个马老师的:
//machao key
/*=============
低层按键(I/0)扫描函数,即低层按键设备驱动,只返回无键、短按和长按。具体双击不在此处判断。参考本人教材的例9-1,稍微有变化。教材中为连_发。
===============*/
unsigned char key_driver(void)
{
static unsigned char key_state = key_state_0, key_time = 0;
unsigned char key_press, key_return = N_key;
key_press = GPIO_ReadInputDataBit(KEY1_BANK,KEY1_PIN); // 读按键I/O电平
switch (key_state) {
case key_state_0: // 按键初始态
if (!key_press)
key_state = key_state_1; // 键被按下,状态转换到按键消抖和确认状态
break;
case key_state_1: // 按键消抖与确认态
if (!key_press) {
key_time = 0; //
key_state = key_state_2; // 按键仍然处于按下,消抖完成,状态转换到按下键时间的计时状态,但返回的还是无键事件
} else
key_state = key_state_0; // 按键已抬起,转换到按键初始态。此处完成和实现软件消抖,其实按键的按下和释放都在此消抖的。
break;
case key_state_2:
if(key_press) {
key_return = S_key; // 此时按键释放,说明是产生一次短操作,回送S_key
key_state = key_state_0; // 转换到按键初始态
} else if (++key_time >= 100) { // 继续按下,计时加10ms(10ms为本函数循环执行间隔)
key_return = L_key; // 按下时间>1000ms,此按键为长按操作,返回长键事件
key_state = key_state_3; // 转换到等待按键释放状态
}
break;
case key_state_3: // 等待按键释放状态,此状态只返回无按键事件
if (key_press)
key_state = key_state_0; //按键已释放,转换到按键初始态
break;
}
return key_return;
}
/*=============
中间层按键处理函数,调用低层函数一次,处理双击事件的判断,返回上层正确的无键、单键、双键、长键4个按键事件。
本函数由上层循环调用,间隔10ms
===============*/
unsigned char key_read(void)
{
static unsigned char key_m = key_state_0, key_time_1 = 0;
unsigned char key_return = N_key,key_temp;
key_temp = key_driver();
switch(key_m) {
case key_state_0:
if (key_temp == S_key ) {
key_time_1 = 0; // 第1次单击,不返回,到下个状态判断后面是否出现双击
key_m = key_state_1;
} else
key_return = key_temp; // 对于无键、长键,返回原事件
break;
case key_state_1:
if (key_temp == S_key) { // 又一次单击(间隔肯定<500ms)
key_return = D_key; // 返回双击键事件,回初始状态
key_m = key_state_0;
}
else { // 这里500ms内肯定读到的都是无键事件,因为长键>1000ms,在1s前低层返回的都是无键
if(++key_time_1 >= 50) {
key_return = S_key; // 500ms内没有再次出现单键事件,返回上一次的单键事件
key_m = key_state_0; // 返回初始状态
}
}
break;
}
return key_return;
}
恕我愚钝,真没看出来哪里精妙了? 座机呀 发表于 2017-9-13 20:36
恕我愚钝,真没看出来哪里精妙了?
高手肯定觉得不咋地,适合初学者。 本帖最后由 ailingg 于 2017-11-23 11:08 编辑
一个按键程序弄得的好繁琐,ram占用的也多。
我自己用的,弄个表格就好.
/**
* 按键程序
* PIC16单片机
*/
#define KEY_QTY 6
#define KEY_DOWN 1
#define KEY_UP 0
#define TRUE 1
#define FALSE 0
// Key pins position, // PORTB : I I x xx O O O
#define KEY_READ_PINS_SET 0xc0 // Read pin : RB7 ~ RB6
#define KEY_SCAN_PINS_SET 0x07 // Scan pin : RB2 ~ RB0 经过NPN反向后作为键盘的行扫描,所以判断是否有按键按下是将所有扫描引脚置高
#define KEY_PORT PORTB
/** 按键扫描变量 */
typedef struct
{
unsigned GetKey :1;
unsigned KeyState :1;
UINT8 FiltCnt;
UINT8 LongCnt;
UINT8 PressTimes; //纪录按键进入的次数
UINT8 SetPoint;
}KeyVar_TypeDef;
extern KeyVar_TypeDef KeyVar;
typedef struct
{
void (*IsKeyDownFunc)(void); // 按键事件函数
UINT8 KeyCode; // 键值,由硬件决定
UINT8 LongTime; // 长按键生效时间
UINT8 RenewTime; // 长按键重新开始时间
UINT8 FiltTime; // 去抖滤波时间
UINT8 KeyID;
}KeyElem_TypeDef;
extern KeyElem_TypeDef const KeyElem;
// 按键元素表
KeyElem_TypeDef const KeyElem = {
{ EmptyFunc, 0 ,0, 0, 4,0 },
{ StopKey, 0x81,0, 0, 4,1 },
{ VacuumParamSet,0x82,0, 0, 4,2 },
{ SealingParamSet, 0x84,0, 0, 4,3 },
{ DigitIncrease, 0x41,100,80,4,4 },
{ DigitDecrease, 0x42,100,80,4,5 },
{ HeatLevelSet, 0x44,0, 0, 4,6 }
};
KeyVar_TypeDef KeyVar;
KeyMsg_TypeDef KeyMsg;
/***********************************************************************************************************
* @brief空函数
************************************************************************************************************/
void EmptyFunc(void)
{
NOP();
}
/***********************************************************************************************************
* @brief 获取按键参数元素
* @retval找到对应键则返回对应结构元素指针,否则返回空键结构元素指针
************************************************************************************************************/
KeyElem_TypeDef const* get_KeyElem(UINT8 var)
{
UINT8 i;
for( i = 0; i < ( KEY_QTY + 1 ); i++ )
{
if( var == KeyElem.KeyCode )
{
return( &KeyElem );
}
}
return( &KeyElem );
}
/***********************************************************************************************************
* @brief按键参数初始化
************************************************************************************************************/
void Key_Init(void)
{
KeyMode.bMode = 0; //0=直接加减方式,1=位选加减方式
KeyVar.GetKey = 0;
KeyVar.KeyState = KEY_UP;
KeyVar.FiltCnt = 0;
KeyVar.LongCnt = 0;
KeyVar.SetPoint = NO;
KeyVar.PressTimes = 0;
}
/**************************************************************************************************************
* @brief键盘扫描函数
* 矩阵式扫描,RB7,RB6 为扫描读取口;RB2,RB1,RB0反向后作为键盘的行扫描。
* @NOTE RB2,RB1,RB0反向后也是数码管和 LED的位驱动口,因此键盘扫描前要先保存
* 扫描端口的位值,扫描结束后还原。
* @Param*PORTx 键盘扫描所在端口
* @retval KeyID
**************************************************************************************************************/
UINT8 KeyScan( PORT_TypeDef *PORTx )
{
UINT8 retval, i;
UINT8 PortKeep;
UINT8 KeyPortVal;
UINT8 MultiKeyCnt = 0;
KeyElem_TypeDef const *pKeyElem;
HC595_OEN_Pin = 1; // Disable 74HC595 out,RA2
PortKeep = *PORTx; // Save Port Value , PORTB
*PORTx |= KEY_SCAN_PINS_SET; // 使能所有扫描引脚;RB2 ~ RB0通过UNL2003反向后作为键盘的行扫描,所以所有扫描引脚置高电平
pKeyElem = &KeyElem;
retval = KeyElem->KeyID;
if( ( *PORTx & KEY_READ_PINS_SET) == KEY_READ_PINS_SET ) //判断按键释放
{
if( KeyVar.FiltCnt == 0 )
{
KeyVar.FiltCnt = 0;
KeyVar.LongCnt = 0;
KeyVar.GetKey = FALSE;
KeyVar.KeyState = KEY_UP;
}
else
KeyVar.FiltCnt--;
}
else
{
for( i = 0; i < KEY_SCAN_PINS_COUNT; i++ )
{
*PORTx &= ~KEY_SCAN_PINS_SET; // 扫描端口全禁止
NOP( );
NOP( );
*PORTx |= 1 << i;
NOP( );
NOP( );
if( ( *PORTx & KEY_READ_PINS_SET) != KEY_READ_PINS_SET )
{
KeyPortVal = *PORTx & ( KEY_READ_PINS_SET | KEY_SCAN_PINS_SET ); // *PORTx & 0xc7
continue;
}
}
if( ++KeyVar.FiltCnt > pKeyElem->FiltTime )
{
KeyVar.FiltCnt = pKeyElem->FiltTime;
KeyVar.KeyState = KEY_DOWN;
pKeyElem = get_KeyElem( KeyPortVal );
if( pKeyElem->LongTime > 0 )
{
if( ++KeyVar.LongCnt == pKeyElem->LongTime )
{
KeyVar.LongCnt = pKeyElem->RenewTime; // 长按键重新开始计时值,按键重新生效的的时间间隔等于长按生效时间减去重新开始时间
retval = pKeyElem->KeyID; // 返回对应键值的按键 ID
}
}
if( KeyVar.GetKey == FALSE )
{
KeyVar.GetKey = TRUE;
retval =pKeyElem->KeyID;
}
}
}
*PORTx = PortKeep; // 还原端口值,因按键扫描引脚与数显引脚共用
HC595_OEN_Pin = 0; // 允许 74595 三态输出口输出
return retval;
}
/********************************************************************************************
* @brief按键事件函数回调
*
********************************************************************************************/
void KeyEventFunc( void )
{
UINT8 FuncID;
KeyElem_TypeDef const *pFunc;
FuncID = KeyScan( &KEY_PORT );
pFunc = &KeyElem;
pFunc->IsKeyDownFunc();
}
不错,机智云那个按键写的真的很棒,上次做项目研究了他们的代码,有好多地方需要学习。
页:
[1]
2