Manual Mode

该示例展示了使用 KeyScan 的手动扫描模式来实现矩阵键盘扫描的过程。

当检测到有按键按下时,Keyscan 开始扫描键盘矩阵。在单次扫描完成后触发 KeyScan 单次扫描完成中断,在中断处理函数内进行按键信息读取。

在示例中,通过软件定时器来模拟扫描间隔(scan interval)和所有按键释放(all release)时间检测的流程,以模拟 KeyScan 自动扫描模式(Auto Mode)的扫描流程。

这里应该是 KEYSCAN manual diagram

KeyScan 使用DMA 主设备发送从设备接收框图

环境需求

该示例的环境需求,可参考 环境需求

硬件连线

EVB 外接矩阵键盘,连接 P2_3 和 ROW0,P2_4 和 ROW1,P4_0 和 COLUMN0,P4_1 和 COLUMN1。

外接矩阵键盘的原理图如下图所示:

这里应该是外接矩阵键盘的图片

外接矩阵键盘

配置选项

  1. 可配置如下宏修改引脚定义。

    #define KEYBOARD_ROW_0                  P2_3
    #define KEYBOARD_ROW_1                  P2_4
    #define KEYBOARD_COLUMN_0               P4_0
    #define KEYBOARD_COLUMN_1               P4_1
    
  2. 可配置如下宏修改软件定时器的定时时间。

    #define KEYSCAN_SW_INTERVAL             (200)/* 200ms */
    #define KEYSCAN_SW_RELEASE_TIMEOUT      (10)/* 10ms */
    

编译和下载

该示例的编译和下载流程,可参考 编译和下载

测试验证

  1. EVB 启动后,在 Debug Analyzer 工具内观察 log。

    Start keyscan manual test!
    
  2. 初始化软件定时器,打印 log。

    [io_keyscan] timer_keyscan_init: keyscan timer init
    
  3. 当有按键按下时,Keyscan 开始单次扫描。单次扫描结束后,进入 Keyscan 中断,打印按键信息。如果一直有按键按下,则会一直打印按键信息。

    1. 若检测到单个按键按下,打印如下 log。

      [io_keyscan] io_keyscan_handle_keys: Single key press. key: (x, x)
      
    2. 若检测到两个按键按下,打印如下 log。

      [io_keyscan] io_keyscan_handle_keys: Two key press. key0: (x, x), key1: (x, x)
      
  4. 当按键全部松开后,在软件定时器的回调函数内打印如下 log。

    [io_keyscan] io_keyscan_handle_keys: All keys release.
    

代码介绍

该章节主要介绍示例中的初始化和相应功能实现的代码和流程说明。

源码路径

工程文件和源码路径如下:

  • 工程路径: sdk\samples\peripheral\keyscan\keyscan_manual\proj

  • 源码路径: sdk\samples\peripheral\keyscan\keyscan_manual\src

初始化

外设的初始化流程可参考 General Introduction 中的 初始化流程 部分。

  1. 调用 Pad_Config()Pinmux_Config(),配置对应引脚的 PAD 和 PINMUX。

    void board_keyboard_init(void)
    {
        /* Keypad pad config */
        Pad_Config(KEYBOARD_ROW_0, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW);
        Pad_Config(KEYBOARD_ROW_1, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW);
        Pad_Config(KEYBOARD_COLUMN_0, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE, PAD_OUT_LOW);
        Pad_Config(KEYBOARD_COLUMN_1, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE, PAD_OUT_LOW);
    
        /* keypad pinmux config */
        Pinmux_Config(KEYBOARD_ROW_0, KEY_ROW_0);
        Pinmux_Config(KEYBOARD_ROW_1, KEY_ROW_1);
        Pinmux_Config(KEYBOARD_COLUMN_0, KEY_COL_0);
        Pinmux_Config(KEYBOARD_COLUMN_1, KEY_COL_1);
    }
    
  2. 调用 RCC_PeriphClockCmd() ,开启 KeyScan 时钟。

  3. 对 KeyScan 外设进行初始化:

    1. 定义 KEYSCAN_InitTypeDef 类型 KEYSCAN_InitStruct ,调用 KEYSCAN_StructInitKEYSCAN_InitStruct 预填默认值。

    2. 根据需求修改 KEYSCAN_InitStruct 参数,KeyScan 的初始化参数配置如下表。

    3. 调用 KeyScan_Init(),初始化 KeyScan 外设。

KeyScan 初始化参数

KeyScan Hardware Parameters

Setting in the KEYSCAN_InitStruct

KeyScan

Row Size

KEYSCAN_InitTypeDef::rowSize

2

Column Size

KEYSCAN_InitTypeDef::colSize

2

Scan Mode

KEYSCAN_InitTypeDef::scanmode

KeyScan_Manual_Scan_Mode

Debounce Enable

KEYSCAN_InitTypeDef::debounceEn

ENABLE

  1. 配置 KeyScan 单次扫描完成中断 KEYSCAN_INT_SCAN_END,配置 NVIC。NVIC 相关配置可参考 中断配置

  2. 调用 KeyScan_Cmd(),使能 KeyScan 扫描。

  3. 调用 os_timer_create 函数,创建软件定时器,定时时间为 KEYSCAN_SW_INTERVAL ,用于模拟两次扫描的间隔。

    os_timer_create(&KeyScan_Timer_Handle, "keyscan_timer",  1, KEYSCAN_SW_INTERVAL, false, timer_keyscan_callback);
    

功能实现

该示例中的 KeyScan 扫描流程如图所示:

这里应该是 KEYSCAN scan flow

KeyScan 扫描流程

在该示例中,KeyScan 配置为手动扫描模式,通过软件定时器模拟 interval time 和 release time,来模拟 KeyScan 的自动扫描模式流程。

  1. 初始化完毕之后,调用 os_sched_start 函数,开始任务调度。

  2. 当有按键按下时,KeyScan 触发手动扫描。单次扫描完成后,触发 KeyScan 单次扫描完成中断。

  3. 在 KeyScan 中断函数内,在 KeyScan 中断处理中,调用 KeyScan_GetFifoDataNum()KeyScan_Read() 读取 KeyScan FIFO 中的数据,并将 Key_Pressed_Flag 设为 true ,以表明有按键按下。

  4. 调用 os_timer_restart,开启软件定时器,定时时间为 KEYSCAN_SW_INTERVAL,模拟 KeyScan 自动扫描流程中的 interval time。

    void KEYSCAN_Handler(void)
    {
        uint32_t fifo_length;
    
        if (KeyScan_GetFlagState(KEYSCAN, KEYSCAN_INT_FLAG_SCAN_END) == SET)
        {
            /* Read current keyscan interrupt status and mask interrupt */
            KeyScan_INTMask(KEYSCAN, KEYSCAN_INT_SCAN_END, ENABLE);
            memset(&Current_Key_Data, 0, sizeof(KeyScan_Data_TypeDef));
    
            /* KeyScan fifo not empty */
            if (KeyScan_GetFlagState(KEYSCAN, KEYSCAN_FLAG_EMPTY) != SET)
            {
                fifo_length = (uint32_t)KeyScan_GetFifoDataNum(KEYSCAN);
                KeyScan_Read(KEYSCAN, (uint16_t *)&Current_Key_Data.key[0], fifo_length);
                Current_Key_Data.length = fifo_length;
                Key_Pressed_Flag = true;
    
                /* Start sw timer to check press status */
                if (!os_timer_restart(&KeyScan_Timer_Handle, KEYSCAN_SW_INTERVAL))
                {
                    APP_PRINT_ERROR0("[io_keyscan] Keyscan_Handler: Restart keyscan_timer failed!");
                    /* Set flag to default status and reinit keyscan module with debounce enabled */
                    global_data_keyscan_init();
                    driver_keyboard_init(ENABLE);
                    return;
                }
    
                ...
            }
            KeyScan_ClearINTPendingBit(KEYSCAN, KEYSCAN_INT_SCAN_END);
            KeyScan_INTMask(KEYSCAN, KEYSCAN_INT_SCAN_END, DISABLE);
        }
    }
    
  5. 当软件定时器定时时间到( KEYSCAN_SW_INTERVAL 时间)后,在回调函数内判断 Key_Pressed_Flag 的状态。

    1. 如果 Key_Pressed_Flagtrue,表明上次扫描有按键按下,并经过了 scan interval 的时间。在回调函数内将 Key_Pressed_Flag 置为 false,重新初始化 KeyScan 进行扫描。并开启定时器定时 KEYSCAN_SW_RELEASE_TIMEOUT 用于模拟 all release time。

    2. 如果 Key_Pressed_Flagfalse,表明经过了 KEYSCAN_SW_RELEASE_TIMEOUT 时间之后,再次进入到了回调函数内,此时没有按键按下,代表按键全部松开。清空按键信息并重新初始化 KeyScan 重新开启一轮扫描。

    void timer_keyscan_callback(void *p_xTimer)
    {
        if (true == Key_Pressed_Flag)
        {
            Key_Pressed_Flag = false;
            driver_keyboard_init(ENABLE);
    
            /* Start timer to check key status */
            os_timer_restart(&p_xTimer, KEYSCAN_SW_RELEASE_TIMEOUT);
        }
        else
        {
            global_data_keyscan_init();
            driver_keyboard_init(ENABLE);
        }
    }
    
  6. 在开启 KEYSCAN_SW_RELEASE_TIMEOUT 定时时间后,如果期间内检测到了按键状态,会再次进入 KeyScan 中断函数,在中断函数内会重新启动 KEYSCAN_SW_INTERVAL 时间定时器,以覆盖掉 KEYSCAN_SW_RELEASE_TIMEOUT 定时。在 KEYSCAN_SW_INTERVAL 定时时间内,由于没有重新初始化 KeyScan,因此有按键按下时也不会触发扫描。