Bootstrap

富芮坤FR800X系列之按键检测模块设计


FR800X系列按键检测模块


读者对象:
本文档主要适用以下工程师:
嵌入式系统工程师
单片机软件工程师
IOT固件工程师
BLE固件工程师

1.概要

FR800X系列软件开发中基于官方SDK中如何设计按键检测模块。按键检测需满足两个要求分别为消除抖动干扰和快速响应。要想消除抖动干扰,就需要做滤波处理,比如间隔10ms持续检测3(2-5)次为低电平为按键按下有效。快速响应就需要中断触发,中断触发后开启定时器循环检测按键。那用户怎样设计按键检测模块呢?
设计按键模块化好处:
灵活性:模块化的按键设计能够提供更多的选择和灵活性。不同的按键模块可以根据需求进行组合和更换,从而满足不同的功能需求。

易于维护:按键模块化可以使维护更加简单。当一个按键出现故障时,只需替换该模块,而无需更换整个设备。这节省了时间和资源,并减少了停机时间。

可扩展性:按键模块化允许在需要时添加更多的按键。这种扩展性使得设备能够适应不断变化的需求和功能。

互换性:按键模块化使不同厂商的按键能够互相兼容。这意味着用户可以更容易地找到合适的按键模块,并使用它们来替换原始设备中的按键。

降低成本:按键模块化可以降低成本,因为只需购买需要的按键模块,而无需购买整个设备。此外,模块化还可以减少对专门技术的需求,从而降低维修和维护的成本。
在这里插入图片描述
按键模块框图
本文主要讲述:①用户如何设置GOIO中断唤醒、IO中断怎样处理;②如何设计软件检测多个独立按键的;③用户发送按键事件,短按、长按、超长按;④然后统一处理按键事件,在按键事件中定义业务功能。

2.用户如何设计按键检测模块

首先建立键模块化文件app_buttons.c和app_buttons.h。app_buttons.c存放程序内容,app_buttons.h存放相关接口。实现了正常模式和低功耗模式下的按键检测,代码如下:

2.1 GPIO初始化

// 按键 PD7 PC2 PC3 exti_enable
    system_set_port_mux(GPIO_PORT_D, GPIO_BIT_7, PORTD7_FUNC_D7);
    system_set_port_mux(GPIO_PORT_D, GPIO_BIT_6, PORTD6_FUNC_D6);
    system_set_port_mux(GPIO_PORT_C, GPIO_BIT_2, PORTC2_FUNC_C2);
    system_set_port_mux(GPIO_PORT_C, GPIO_BIT_3, PORTC3_FUNC_C3);
    system_set_port_pull(GPIO_PD7, GPIO_PULL_UP, true);
    system_set_port_pull(GPIO_PD6, GPIO_PULL_UP, true);
    system_set_port_pull(GPIO_PA4, GPIO_PULL_UP, true);
    system_set_port_pull(GPIO_PA5, GPIO_PULL_UP, true);
    exti_enable(GPIO_PD7|GPIO_PD6|GPIO_PC3|GPIO_PC2);
    NVIC_EnableIRQ(GPIO_IRQn);

	// 按键PD7 PC2 PC3 wakeup
	pmu_set_pin_pull(GPIO_PORT_D, GPIO_PIN_7, GPIO_PULL_UP);
    pmu_set_pin_pull(GPIO_PORT_D, GPIO_PIN_6, GPIO_PULL_UP);
	pmu_set_pin_pull(GPIO_PORT_C, GPIO_PIN_2, GPIO_PULL_UP);
    pmu_set_pin_pull(GPIO_PORT_C, GPIO_PIN_3, GPIO_PULL_UP);
    pmu_port_wakeup_func_set(GPIO_PORT_D, GPIO_PIN_7);
    pmu_port_wakeup_func_set(GPIO_PORT_D, GPIO_PIN_6);
    pmu_port_wakeup_func_set(GPIO_PORT_C, GPIO_PIN_2);
    pmu_port_wakeup_func_set(GPIO_PORT_C, GPIO_PIN_3);
    button_init(GPIO_PD7|GPIO_PD6|GPIO_PC3|GPIO_PC2);
    NVIC_EnableIRQ(PMU_IRQn);

2.2按键模块初始化

    uart_printf("buttons_init,%x\r\n",enable_io);
    /*  清除RAM */
    memset(&button_curr_list,0,sizeof(button_curr_list));
    memset(&button_last_list,0,sizeof(button_last_list));
     /*  检测的IO mask */
    button_io_mask = enable_io;
    /*  检测变量初始化 */
    for(uint8_t i=0; i<GPIO_DIO_MAX; i++)
    {
        if(enable_io & (0x01 <<i))
        {
            DEBUG_PRINTF("DIO num : %d\r\n",i);
            button_curr_list[i].io_enable = 1;
            button_curr_list[i].count     = 0;
            button_curr_list[i].flag      = 1;
            button_curr_list[i].io_mask   = (0x01 << i);

            button_last_list[i].io_enable = 1;
            button_last_list[i].count     = 0;
            button_last_list[i].flag      = 1;
            button_last_list[i].io_mask   = (0x01 << i);
        }
    }
    // 按键检测定时器初始化
    os_timer_init(&button_check_timer, button_timeout_handler, NULL);

2.3设计中断函数:

/*
 * @fn      pmu_gpio_isr_ram
 *
 * @brief   pmu_gpio_isr
 *
 * @param   None.
 *
 * @return  None.
 */
__attribute__((section("ram_code"))) void pmu_gpio_isr_ram(void)
{
    uint32_t gpio_value = ool_read32(PMU_REG_PORTA_LAST_STATUS);
    button_toggle_detected(gpio_value);
    ool_write32(PMU_REG_PORTA_LAST_STATUS, gpio_value);
}

2.4循环检测按键和输出事件

/*  中断检查IO状态  */
void BUTTON_CheckHandler(void)
{
    static uint8_t released_type=0;
    static uint32_t curr_button=0;
    static uint32_t button_check_sleep=0;

    //DEBUG_PRINTF("but: %04X \r\n",ool_read32(PMU_REG_PORTA_LAST_STATUS));

    curr_button = ool_read32(PMU_REG_PORTA_LAST_STATUS);

    for(uint8_t index=0; index<GPIO_DIO_MAX; index++)
    {
        if(button_curr_list[index].io_enable == 0) continue;

        //DEBUG_PRINTF("[%d]\r\n",index);
        button_curr_list[index].flag = (curr_button & (0x01<<index)) ? 1 : 0;

        //DEBUG_PRINTF("%d[%d][%d]\r\n",index,button_curr_list[index].flag,button_last_list[index].flag);

        if(button_curr_list[index].flag == button_last_list[index].flag)
        {
            //button_check_timer_begin |= button_curr_list[index].io_mask;
            //DEBUG_PRINTF("Count[%d]\r\n",button_curr_list[index].count);

            button_curr_list[index].count ++;
            if(button_curr_list[index].count > BUTTON_LONG_LONG_TIME)            // 超长按 5s
            {
                button_curr_list[index].count = BUTTON_LONG_LONG_TIME;

                button_check_sleep &= ~button_curr_list[index].io_mask;         // 清除进入 循环检查
            }
            else if(button_curr_list[index].count >= BUTTON_LONG_LONG_TIME)      // 超长按 5s
            {
                if(button_curr_list[index].flag == 0)
                {
                    button_send_event(BUTTON_LONG_LONG_PRESSED,index);
                    button_curr_list[index].out_type = BUTTON_LONG_LONG_PRESSED;
                }
            }else if(button_curr_list[index].count == BUTTON_LONG_TIME)         // 长按 1s
            {
                //DEBUG_PRINTF("BUTTON_LONG_TIME\r\n");
                if(button_curr_list[index].flag == 0)
                {
                    button_send_event(BUTTON_LONG_PRESSED,index);
                    button_curr_list[index].out_type = BUTTON_LONG_PRESSED;
                }else
                {
                    if(released_type == BUTTON_NOP)
                    {
                        // 无按下按键或者按下时间不够
                        button_send_event(BUTTON_NOP,index);
                    }
                }

            }else if(button_curr_list[index].count == BUTTON_SHORT_TIME)   // 短按 50ms
            {
                if(button_curr_list[index].flag == 0)
                {
                    button_send_event(BUTTON_PRESSED,index);
                    button_curr_list[index].out_type = BUTTON_PRESSED;
                    released_type = BUTTON_PRESSED;
                }else
                {
                    if(button_curr_list[index].out_type == BUTTON_PRESSED)
                    {
                        button_send_event(BUTTON_RELEASED,index);
                    }else if(button_curr_list[index].out_type == BUTTON_LONG_PRESSED)
                    {
                        button_send_event(BUTTON_LONG_RELEASED,index);
                    }else if(button_curr_list[index].out_type == BUTTON_LONG_LONG_PRESSED)
                    {
                        button_send_event(BUTTON_LONG_LONG_RELEASED,index);
                    }

                    button_check_sleep &= ~button_curr_list[index].io_mask;         // 清除进入 循环检查

                    //DEBUG_PRINTF("button_check %x %x\r\n",button_check_sleep,~button_curr_list[index].io_mask);
                }
            }
        }else
        {
            button_curr_list[index].count = 0;
            released_type = BUTTON_NOP;
            button_check_sleep |= button_curr_list[index].io_mask;
            button_last_list[index].flag = button_curr_list[index].flag;
        }
    }

    //DEBUG_PRINTF("button_check %x %x\r\n",button_check_sleep,button_io_mask);

    if((button_check_sleep & button_io_mask ) == 0)
    {
        DEBUG_PRINTF("button os_timer_stop\r\n");
        os_timer_stop(&button_check_timer);
    }
}

2.5按键任务处理

如何处理按键事件?
这里需要用到消息任务,如下:发送一个按键事件

void button_send_event(button_type_t type, uint8_t but_code)
{
    os_event_t button_event;
    struct button_msg_t msg;

    msg.button_index = (0x01<<but_code);
    msg.button_type  = type;
    msg.button_cnt   = 0;

    button_event.event_id = USER_EVT_BUTTON;
    button_event.src_task_id = user_task_id;
    button_event.param = (void *)&msg;
    button_event.param_len = sizeof(msg);
    DEBUG_PRINTF("button_send_event %d\r\n",type);
    os_msg_post(user_task_id, &button_event);
}

2.6 事件处理函数

最后在用户任务中,处理相关按键事件任务

static int user_task_func(os_event_t *param)
{
    switch(param->event_id)
    {
        case USER_EVT_AT_COMMAND:
            //app_at_cmd_recv_handler(param->param, param->param_len);
            break;

        case USER_EVT_BUTTON:
            {
                struct button_msg_t *button_msg;
                const char *button_type_str[] = {
                                                    "BUTTON_PRESSED",
                                                    "BUTTON_RELEASED",
                                                    "BUTTON_SHORT_RELEASED",
                                                    "BUTTON_MULTI_PRESSED",
                                                    "BUTTON_LONG_PRESSED",
                                                    "BUTTON_LONG_PRESSING",
                                                    "BUTTON_LONG_RELEASED",
                                                    "BUTTON_LONG_LONG_PRESSED",
                                                    "BUTTON_LONG_LONG_RELEASED",
                                                    "BUTTON_COMB_PRESSED",
                                                    "BUTTON_COMB_RELEASED",
                                                    "BUTTON_COMB_SHORT_PRESSED",
                                                    "BUTTON_COMB_LONG_PRESSED",
                                                    "BUTTON_COMB_LONG_PRESSING",
                                                    "BUTTON_COMB_LONG_RELEASED",
                                                    "BUTTON_COMB_LONG_LONG_PRESSED",
                                                    "BUTTON_COMB_LONG_LONG_RELEASED",
                                                };

                button_msg = (struct button_msg_t *)param->param;

                co_printf("%x TYPE:%s.\r\n", button_msg->button_index,button_type_str[button_msg->button_type]);

                switch( button_msg->button_type )
                {
                    case BUTTON_PRESSED:
                         ProcessPress(button_msg->button_index);
                         break;
                    case BUTTON_LONG_PRESSED:
                         ProcessLongPress(button_msg->button_index);
                         break;
                    case BUTTON_LONG_LONG_PRESSED:
                         ProcessLongLongPress(button_msg->button_index);
                         break;

                    case BUTTON_SHORT_RELEASED:
                         ProcessRelease(button_msg->button_index);
                         break;
                    case BUTTON_LONG_RELEASED:
                         ProcessLongRelease(button_msg->button_index);
                         break;
                    case BUTTON_LONG_LONG_RELEASED:
                         ProcessLongLongRelease(button_msg->button_index);
                         break;
                    default:
                        break;
                }
            }
            break;
        case DSP_EVT_COMMAND:
            break;
        default:
            break;
    }

    return EVT_CONSUMED;
}

3.按键模块调试细节

1)BLE工作在低功耗模式和正常工作模式下,按键皆可以正常使用
2)GPIO中断函数回调函数要处理好,放入相关的中断回调的接口
3)启动定时器轮询检测按键,检测超时关闭定时器,减少功耗

4.小结

按键检测处理方法有很多,这里采用中断+定时器检测并发送事件+事件处理的方法,好处有①可以实现按键的模块化;②中断按键响应速度快;③按键触发稳定,定时间隔滤波防止误处理。④支持32个独立按键检测。按键检测是软件开发必备知识,相信还有很多中方法;作者常用这种方法来开发按键检测,不知您还有哪种更好按键检测的方法,欢迎评论,谢谢!。

;