按键原理图
软件原理
1.使用IO外部中断,当按键有动作时开启定时器进行按键扫描并产生按键事件
环境
1.硬件环境:此代码是在昂瑞微SOC芯片OM6621d上运行的。(因此芯片有睡眠机制,所以定时器需在空闲时进行关闭让芯片进入睡眠状态,需根据自身平台做调整)
2.编译环境:keil
代码
按键扫描(根据自身平台配置IO中断以及定时器)
/*按键扫描代码*/
//key_scan.c
static key_gnd_config_t m_key_cfg;//按键定义信息
static co_timer_t m_key_scan_timer;//定时器
static bool m_key_scan_enable = false;//按键扫描状态
static void key_wakeup_enable(bool wakeup_en);
void key_scan_disable(void);
// 获取按键是按下还是释放,保存到结构体
static void key_get_action(void)
{
uint8_t i = 0;
uint32_t pins_state = gpio_read(m_key_pin_mask);
for(i = 0; i < m_key_cfg.key.numb; i++){
if(pins_state&BITMASK(m_key_cfg.key.p_pin[i])){//松开
m_key_cfg.key.key_config[i].key_action = KS_ACT_RELEASE;
}else{//按下
m_key_cfg.key.key_config[i].key_action = KS_ACT_PRESS;
}
}
}
// 获取按键动作
static bool key_get_status(void)
{
static uint8_t key_timeout_time = 0;
bool key_status = 0;
uint8_t i= 0;
key_get_action();
for(i = 0; i < m_key_cfg.key.numb; i++){
switch(m_key_cfg.key.key_config[i].key_status)
{
//状态:没有按键按下
case KS_STATUS_IDLE:
if(m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS)
{
m_key_cfg.key.key_config[i].key_status = KS_STATUS_DEBOUNCE;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
else
{
m_key_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
break;
//状态:消抖
case KS_STATUS_DEBOUNCE:
if(m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS)
{
m_key_cfg.key.key_config[i].key_status = KS_STATUS_PRESS;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
else
{
m_key_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
break;
//状态:继续按下
case KS_STATUS_PRESS:
if( (m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count >= m_key_cfg.key.key_long_press_time))//1s
{
m_key_cfg.key.key_config[i].key_status = KS_STATUS_LONG_PRESS;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
else if( (m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) && (m_key_cfg.key.key_config[i].key_count < m_key_cfg.key.key_long_press_time))
{
m_key_cfg.key.key_config[i].key_count++;
m_key_cfg.key.key_config[i].key_status = KS_STATUS_PRESS;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
else
{
m_key_cfg.key.key_config[i].key_count = 0;
m_key_cfg.key.key_config[i].key_status = KS_STATUS_WAIT_AGAIN_PRESS;// 按短了后释放
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
break;
//状态:一直长按着
case KS_STATUS_LONG_PRESS:
if(m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS)
{
if(m_key_cfg.key.key_config[i].key_count >= m_key_cfg.key.key_long_press_time){//
m_key_cfg.key.key_config[i].key_status = KS_STATUS_LONG_PRESS;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_LONGPRESS;
m_key_cfg.key.key_config[i].key_count = 0;
}else{
m_key_cfg.key.key_config[i].key_status = KS_STATUS_LONG_PRESS;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
m_key_cfg.key.key_config[i].key_count = 0;
}
}
else
{
m_key_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;//KEY_EVENT_LONGPRESS;
m_key_cfg.key.key_config[i].key_count = 0;
}
break;
//状态:等待是否再次按下
case KS_STATUS_WAIT_AGAIN_PRESS:
if((m_key_cfg.key.key_config[i].key_action != KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count >= m_key_cfg.key.key_wait_double_time))
{ // 第一次短按,且释放时间大于KEY_WAIT_DOUBLE_TIME
m_keygnd_cfg.key.key_config[i].key_count = 0;
m_keygnd_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
m_keygnd_cfg.key.key_config[i].key_event = KEY_EVENT_SINGLECLICK;
}
else if((m_key_cfg.key.key_config[i].key_action != KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count < m_key_cfg.key.key_wait_double_time))
{// 第一次短按,且释放时间还没到
m_key_cfg.key.key_config[i].key_count ++;
m_key_cfg.key.key_config[i].key_status = KS_STATUS_WAIT_AGAIN_PRESS;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
else // 第一次短按,且还没到KEY_WAIT_DOUBLE_TIME 第二次被按下
{
m_key_cfg.key.key_config[i].key_count = 0;
m_key_cfg.key.key_config[i].key_status = KS_STATUS_DOUBLE_PRESS;// 第二次按下
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
break;
case KS_STATUS_DOUBLE_PRESS:
if( (m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count >= m_key_cfg.key.key_long_press_time))
{
m_key_cfg.key.key_config[i].key_status = KS_STATUS_LONG_PRESS;// 第二次按的时间大于 KEY_LONG_PRESS_TIME
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_SINGLECLICK; // 先响应单击
m_key_cfg.key.key_config[i].key_count = 0;
}
else if( (m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count < m_key_cfg.key.key_long_press_time))
{
m_key_cfg.key.key_config[i].key_count ++;
m_key_cfg.key.key_config[i].key_status = KS_STATUS_DOUBLE_PRESS;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
}
else
{// 第二次按下后在 KEY_LONG_PRESS_TIME内释放
m_key_cfg.key.key_config[i].key_count = 0;
m_key_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
m_key_cfg.key.key_config[i].key_event = KEY_EVENT_DOUBLECLICK; // 响应双击
}
break;
default:
break;
}
//如果不是IDLE且仍在计时中 则判断为按键动作还未结束返回1
if(m_key_cfg.key.key_config[i].key_status != KS_GND_STATUS_IDLE && m_key_cfg.key.key_config[i].key_count != 0){
key_status = 1;
}
}
return key_status;
}
//按键事件处理
static void key_event_handler(void){
uint8_t i = 0;
for(i = 0; i < m_keygnd_cfg.key.numb; i++){
if(m_keygnd_cfg.key.key_config[i].key_event != KEY_EVENT_NULL){
m_keygnd_cfg.key_scan_code_handler_cb(i, m_keygnd_cfg.key.key_config[i].key_event);
}
}
}
//按键扫描定时器处理
static void key_scan_timer_handler(co_timer_t *timer, void *param)
{
static uint32_t key_timeout_time = 0;
bool key_status = 0;
//获取按键状态
if(key_get_status() == 0){
key_timeout_time ++;
}else{
key_timeout_time = 0;
}
//按键事件处理 --- 事件处理根据需求放置位置
key_event_handler();
//按键扫描超时
if(key_timeout_time >= 300){//300*20 = 6s
key_timeout_time = 0;
key_wakeup_enable(true);
co_timer_del(&m_key_scan_timer);
}
}
//按键IO中断使能接口
static void key_wakeup_enable(bool wakeup_en)
{
if(wakeup_en)
{
//设置上升沿和下降沿触发中断
gpio_set_interrupt(m_key_pin_mask, GPIO_BOTH_EDGE);
//读取IO当前状态,再设置对应相反电平唤醒
uint32_t pins_state=gpio_read(m_key_pin_mask);
pmu_wakeup_pin_set(pins_state, PMU_PIN_WAKEUP_LOW_LEVEL);
pmu_wakeup_pin_set((pins_state^(m_key_pin_mask)), PMU_PIN_WAKEUP_HIGH_LEVEL);
}
else
{
//关闭IO中断以及睡眠唤醒中断
gpio_set_interrupt(m_key_pin_mask, GPIO_TRIGGER_DISABLE);
pmu_wakeup_pin_set(m_key_pin_mask, PMU_PIN_WAKEUP_DISABLE);
}
}
//IO中断处理接口
void key_gpio_interrupt_handler(uint32_t pin_mask){
if(pin_mask&m_key_pin_mask){
key_wakeup_enable(false);
//获取按键状态
key_get_status();
co_timer_set(&m_key_scan_timer, m_key_cfg.scan_interval, TIMER_REPEAT, key_scan_timer_handler, NULL);
}
}
void key_scan_enable(void)
{
if(m_key_pin_mask == 0)
return;
if(m_key_scan_enable == false)
{
m_key_scan_enable=true;
key_wakeup_enable(false);
co_timer_set(&m_key_scan_timer, m_key_cfg.scan_interval, TIMER_REPEAT, key_scan_timer_handler, NULL);
}
}
void key_scan_disable(void)
{
if(m_key_scan_enable)
{
key_wakeup_enable(true);
co_timer_del(&m_key_scan_timer);
}
m_key_scan_enable = false;
}
void key_init(key_config_t *p_cfg)
{
uint32_t i;
if(p_cfg == NULL)
return;
m_key_cfg=*p_cfg;
if(m_key_cfg.key.numb==0)
return;
m_key_pin_mask=0;
for(i=0; i < m_key_cfg.key.numb; ++i)
{
if(m_key_cfg.key.p_pin[i]<MAX_GPIO_PIN_NUMBER)//限制IO进行配置,需根据自身平台做调整
{
m_key_pin_mask |= BITMASK(m_key_cfg.key.p_pin[i]);
pinmux_config(m_key_cfg.key.p_pin[i], PINMUX_GPIO_MODE_CFG);
}
}
if(m_key_pin_mask==0)
return;
//设置按键IO上拉输入
pmu_pin_mode_set(m_key_pin_mask, PMU_PIN_MODE_PU);
gpio_set_direction(m_key_pin_mask, GPIO_INPUT);
}
//key_sacn.h
typedef enum
{
KS_ACT_RELEASE,
KS_ACT_PRESS,
}key_action_t;
typedef enum
{
KS_STATUS_IDLE, // 空闲
KS_STATUS_DEBOUNCE,//消抖
KS_STATUS_PRESS,//按下
KS_STATUS_LONG_PRESS,//长按
KS_STATUS_WAIT_AGAIN_PRESS, //等待再次按下
KS_STATUS_DOUBLE_PRESS, //双击
}key_state_t;
typedef enum _KEY_EventList_TypeDef
{
KEY_EVENT_NULL = 0x00, // 无事件
KEY_EVENT_SINGLECLICK = 0x01, // 单击
KEY_EVENT_DOUBLECLICK = 0x02, // 双击
KEY_EVENT_LONGPRESS = 0x04 // 长按
}key_event_t;
typedef struct
{
key_action_t key_action;//按键动作
key_state_t key_status;//按键状态
key_event_t key_event;//按键事件
uint32_t key_count;//按键计数
}ks_config_t;
typedef struct
{
uint8_t numb;
uint8_t *p_pin;
uint32_t key_long_press_time;
uint32_t key_wait_double_time;
ks_config_t *key_config;
}ks_t;
typedef void (*ks_scan_code_handler_t)(uint8_t pin_num, key_event_t key_event);
typedef struct
{
ks_t key;
uint8_t scan_interval; //unit: ms
ks_scan_code_handler_t key_scan_code_handler_cb;
}key_config_t;
void key_gpio_interrupt_handler(uint32_t pin_mask);
void key_init(key_gnd_config_t *p_cfg);
void key_scan_enable(void);
void key_scan_disable(void);
按键初始化代码(应用层)
//app_keyboard.c
//应用层代码
#define KS_PIN {13}
#define KS_NUMB 1
static uint8_t m_key_pin[] = KS_PIN;
static ks_config_t m_key_config[KS_NUMB];
static void key_scan_code_handler(uint8_t pin_num, key_event_t key_event){
switch (pin_num)
{
case 0:
{
switch(key_event){
case KEY_EVENT_NULL:
break;
case KEY_EVENT_SINGLECLICK:
{
printf("KEY_EVENT_SINGLECLICK\n");
}
break;
case KEY_EVENT_DOUBLECLICK:
{
printf("KEY_EVENT_DOUBLECLICK\n");
}
break;
case KEY_EVENT_LONGPRESS:
{
printf("KEY_EVENT_LONGPRESS\n");
}
break;
}
}
break;
default:
break;
}
}
void keyboard_init(void)
{
key_config_t ks_cfg;
ks_cfg.scan_interval = 20;//20ms
ks_cfg.key.key_long_press_time = 150;// 20ms*50 = 3s
ks_cfg.key.key_wait_double_time = 25;// 20ms*25 = 500ms
ks_cfg.key.p_pin = &m_key_pin[0];
ks_cfg.key.numb = KS_NUMB;
ks_cfg.key.key_config = &m_key_config[0];
ks_cfg.key_scan_code_handler_cb = key_scan_code_handler;
key_init(&ks_cfg);
}
上文仅个人见解,敬请批评指正!