Manual Scan Mode

This example implements matrix keyboard scanning using Keyscan manual scanning mode.

In the example, key press debounce and key release debounce are implemented using software timers.

Once a single scan is completed, the Keyscan single scan completion interrupt is triggered, and the interrupt handler prints the scanned key information.

Requirements

The sample supports the following development kits:

Development Kits

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

For more requirements, please refer to Quick Start.

Wiring

The EVB is connected to a matrix keyboard module, connect P2_3 to ROW0, P2_4 to ROW1, P4_0 to COLUMN0, and P4_1 to COLUMN1.

External matrix keyboard is shown below:

Here should be a picture of the external matrix keyboard

External Matrix keyboard

Building and Downloading

This sample can be found in the SDK folder:

Project file: board\evb\io_sample\KEYSCAN\Keyscan_manual\mdk

Project file: board\evb\io_sample\KEYSCAN\Keyscan_manual\gcc

Please follow these steps to build and run the example:

  1. Open sample project file.

  2. To build the target, follow the steps listed on the Generating App Image in Quick Start.

  3. After a successful compilation, the app bin app_MP_xxx.bin will be generated in the directory mdk\bin or gcc\bin.

  4. To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.

  5. Press reset button on EVB board and it will start running.

Experimental Verification

  1. Initialize the keyscan software timer, print log.

    [io_keyscan] timer_keyscan_init: keyscan timer init
    
  2. Print key information when a key press is detected.

    1. If a single key press is detected, print the following log.

      [io_keyscan] io_keyscan_handle_keys: Single key press. key: (x, x)
      
    2. If two key presses are detected, print the following log.

      [io_keyscan] io_keyscan_handle_keys: Two key press. key0: (x, x), key1: (x, x)
      
  3. When all keys are released, print the following log.

    [io_keyscan] io_keyscan_handle_keys: All keys release.
    

Code Overview

This chapter will be introduced according to the following several parts:

  1. Source Code Directory.

  2. Peripheral initialization will be introduced in chapter Initialization.

  3. Functional implementation after initialization will be introduced in chapter Function Implementation.

Source Code Directory

  • Project directory: sdk\board\evb\io_sample\KEYSCAN\Keyscan_manual

  • Source code directory: sdk\src\sample\io_sample\KEYSCAN\Keyscan_manual

Source files are currently categorized into several groups as below.

└── Project: keyscan_manual
    └── secure_only_app
        └── include
            ├── app_define.h
            └── rom_uuid.h
        ├── cmsis                    includes CMSIS header files and startup files
            ├── overlay_mgr.c
            ├── system_rtl876x.c
            └── startup_rtl876x.s
        ├── lib                      includes all binary symbol files that user application is built on
            ├── rtl8752h_sdk.lib
            ├── gap_utils.lib
            └── ROM.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl876x_rcc.c
            ├── rtl876x_pinmux.c
            ├── rtl876x_nvic.c
            ├── rtl876x_gdma.c
            └── rtl876x_keyscan.c
        ├── profile
        └── app                      includes the ble_peripheral user application implementation
            ├── main.c
            ├── ancs.c
            ├── app.c
            ├── app_task.c
            ├── app_timer.c
            └── io_keyscan.c

Initialization

When the EVB reset is initiated, the main() function is called, and the following process will be executed:

int main(void)
{
    extern uint32_t random_seed_value;
    srand(random_seed_value);
    global_data_init();
    board_init();
    le_gap_init(APP_MAX_LINKS);
    gap_lib_init();
    app_le_gap_init();
    app_le_profile_init();
    pwr_mgr_init();
    sw_timer_init();
    task_init();
    os_sched_start();

    return 0;
}

Note

le_gap_init(), gap_lib_init(), app_le_gap_init, and app_le_profile_init are related to the initialization of the privacy management module. Refer to the initialization process description in LE Peripheral Privacy.

The specific initialization process related to peripherals is as follows:

  1. In global_data_init, execute global_data_keyscan_init to initialize the global variable Current_Key_Data.

    void global_data_keyscan_init(void)
    {
        /* Data struct init */
        memset(&Current_Key_Data, 0, sizeof(KeyScan_Data_TypeDef));
    }
    
  2. In board_init, execute board_keyboard_init, which is responsible for PAD/PINMUX settings and includes the following processes:

    1. Configure PAD: Set the pins, PINMUX mode, and PowerOn. The ROW-related pins are set with internal pull-up and output disabled, while the COL-related pins have internal pull-none and output low.

    2. Configure PINMUX: Assign the pins to KEY_ROW_0, KEY_ROW_1, KEY_COL_0, and KEY_COL_1 functions respectively.

    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);
    }
    
  3. After executing os_sched_start() to start task scheduling, in the app_main_task main task, execute driver_init to initialize and configure the peripheral drivers.

  4. In driver_init, execute driver_keyboard_init, which is the initialization function for the Keyscan peripheral, including the following process:

    1. Enable the RCC clock.

    2. Configure the Keyscan rowSize and colSize to (2*2).

    3. Set the scan mode to Manual Mode.

    4. Enable the hardware debounce feature of Keyscan.

    5. Configure the Keyscan single scan end interrupt KEYSCAN_INT_SCAN_END.

    6. Enable the Keyscan.

    void driver_keyboard_init(uint32_t vDebounce_En)
    {
        /* Turn on keyscan clock */
        RCC_PeriphClockCmd(APBPeriph_KEYSCAN, APBPeriph_KEYSCAN_CLOCK, ENABLE);
    
        KEYSCAN_InitTypeDef KEYSCAN_InitStruct;
        KeyScan_StructInit(&KEYSCAN_InitStruct);
    
        KEYSCAN_InitStruct.rowSize  = KEYBOARD_ROW_SIZE;
        KEYSCAN_InitStruct.colSize  = KEYBOARD_COLUMN_SIZE;
        KEYSCAN_InitStruct.scanmode     = KeyScan_Manual_Scan_Mode;
        KEYSCAN_InitStruct.debounceEn   = vDebounce_En;
    
        KeyScan_Init(KEYSCAN, &KEYSCAN_InitStruct);
    
        KeyScan_INTConfig(KEYSCAN, KEYSCAN_INT_SCAN_END, ENABLE);
        KeyScan_ClearINTPendingBit(KEYSCAN, KEYSCAN_INT_SCAN_END);
        KeyScan_INTMask(KEYSCAN, KEYSCAN_INT_SCAN_END, DISABLE);  /* Unmask keyscan interrupt */
        KeyScan_Cmd(KEYSCAN, ENABLE);
    
        /* Keyscan IRQ */
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel         = KeyScan_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelCmd      = (FunctionalState)ENABLE;
        NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
        NVIC_Init(&NVIC_InitStruct);
    }
    
  5. Execute timer_keyscan_init in sw_timer_init, which is the initialization of the software timer and includes the following process:

    1. Execute the os_timer_create() function to create a software timer with a duration of 200ms. Once the timer expires, the timer_keyscan_callback callback function is executed to simulate the Debounce of the Keyscan button press.

    void timer_keyscan_init(void)
    {
        APP_PRINT_INFO0("[io_keyscan] timer_keyscan_init: keyscan timer init");
        if (false == os_timer_create(&KeyScan_Timer_Handle, "keyscan_timer",  1, \
                                    KEYSCAN_SW_INTERVAL, false, timer_keyscan_callback))
        {
            APP_PRINT_ERROR0("[io_keyscan] timer_keyscan_init: timer create failed!");
        }
    }
    

Functional Implementation

At the start of the program, a 200ms software timer is initiated, and the Keyscan peripheral is enabled for a manual scan. After the scan is completed, it will enter the single scan complete interrupt. If a key press is detected, the Key_Pressed_Flag is set to 1, the 200ms timer is restarted, and the Keyscan is re-enabled to begin the next scan.

This process is repeated, and when the 200ms timer expires, a 10ms software timer is restarted to simulate key release debounce. Once the 10ms timer expires, the Key_Pressed_Flag is checked. If it is 0, it indicates that all keys have been released; otherwise, it indicates that keys are still pressed.

  1. After initialization is complete, execute the os_sched_start() function to start task scheduling.

  2. When a key is pressed, the manual scan mode is activated. After a single scan is completed, the Keyscan interrupt function is entered.

    1. Mask the Keyscan single scan complete interrupt.

    2. Read the data length and data content in the Keyscan FIFO.

    3. Set Key_Pressed_Flag to true, indicating that a key is currently pressed.

    4. Execute os_timer_restart() to restart the software timer, setting the timer to 200ms, to continue detecting key press information and simulate the Debounce function.

    5. Define the message type IO_MSG_TYPE_KEYSCAN, define the message subtype as IO_MSG_KEYSCAN_RX_PKT, and send the message to the task. When the main task detects the message type, it executes io_keyscan_handle_keys to print the corresponding key information.

    void Keyscan_Handler(void)
    {
        uint32_t fifo_length;
        T_IO_MSG int_keyscan_msg;
    
        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(KeyScan_Debounce_Enable);
                    return;
                }
    
                /* Send event to app task */
                int_keyscan_msg.type = IO_MSG_TYPE_KEYSCAN;
                int_keyscan_msg.subtype = IO_MSG_KEYSCAN_RX_PKT;
                int_keyscan_msg.u.buf = (void *)&Current_Key_Data;
                if (false == app_send_msg_to_apptask(&int_keyscan_msg))
                {
                    APP_PRINT_ERROR0("[io_keyscan] Keyscan_Handler: Send IO_MSG_KEYSCAN_RX_PKT failed!");
                    //Add user code here!
                    return;
                }
            }
            KeyScan_ClearINTPendingBit(KEYSCAN, KEYSCAN_INT_SCAN_END);
            KeyScan_INTMask(KEYSCAN, KEYSCAN_INT_SCAN_END, DISABLE);
        }
    }
    
    static void io_keyscan_handle_keys(T_IO_MSG *io_keyscan_msg)
    {
        uint16_t subtype = io_keyscan_msg->subtype;
    
        if (subtype == IO_MSG_KEYSCAN_RX_PKT)
        {
            KeyScan_Data_TypeDef *p_key_data = (KeyScan_Data_TypeDef *)io_keyscan_msg->u.buf;
            /* Single key press */
            if (p_key_data->length == 1)
            {
                APP_PRINT_INFO2("[io_keyscan] io_keyscan_handle_keys: Single key press. key: (%d, %d)",
                                p_key_data->key[0].row, p_key_data->key[0].column);
            }
    
            /* two keys press */
            if (p_key_data->length == 2)
            {
                APP_PRINT_INFO4("[io_keyscan] io_keyscan_handle_keys: Two key press. key0: (%d, %d), key1: (%d, %d)",
                                p_key_data->key[0].row, p_key_data->key[0].column, p_key_data->key[1].row,
                                p_key_data->key[1].column);
            }
        }
        else if (subtype == IO_MSG_KEYSCAN_ALLKEYRELEASE)
        {
            APP_PRINT_INFO0("[io_keyscan] io_keyscan_handle_keys: All keys release.");
    
        }
        else
        {
            APP_PRINT_INFO0("[io_keyscan] io_keyscan_handle_keys: Wrong key event!");
        }
    }
    
  3. When the 200ms timer ends, the callback function timer_keyscan_callback is entered, and the flag status bit Key_Pressed_Flag is checked.

    If the flag bit is true, it indicates that a key is currently pressed.

    1. Set the flag bit to false.

    2. Execute driver_keyboard_init to reinitialize Keyscan, enable the Keyscan function, and restart keyboard scanning.

    3. Execute os_timer_restart() to restart the software timer, change the timer duration to 10ms, and simulate the debounce of key release.

    If the flag bit is false, it indicates that no key is currently pressed.

    1. Define the message subtype as IO_MSG_KEYSCAN_ALLKEYRELEASE and print the message that all keys are released in the message handling function.

    2. Execute global_data_keyscan_init to reset the key information.

    3. Execute driver_keyboard_init to reinitialize Keyscan.

    void timer_keyscan_callback(void *p_xTimer)
    {
        if (true == Key_Pressed_Flag)
        {
    //        APP_PRINT_INFO0("[io_keyscan] timer_keyscan_callback: start release timer");
            Key_Pressed_Flag = false;
            driver_keyboard_init(KeyScan_Debounce_Disable);
    
            /* Start timer to check key status */
            os_timer_restart(&p_xTimer, KEYSCAN_SW_RELEASE_TIMEOUT);
        }
        else
        {
            /* Keyscan release event detected */
    //        APP_PRINT_INFO0("[io_keyscan] timer_keyscan_callback: keyscan release event detected ");
            T_IO_MSG int_keyscan_msg;
            int_keyscan_msg.type = IO_MSG_TYPE_KEYSCAN;
            int_keyscan_msg.subtype = IO_MSG_KEYSCAN_ALLKEYRELEASE;
    
            if (false == app_send_msg_to_apptask(&int_keyscan_msg))
            {
                APP_PRINT_ERROR0("[io_keyscan] timer_keyscan_callback: Send IO_MSG_KEYSCAN_ALLKEYRELEASE failed!");
            }
    
            global_data_keyscan_init();
            driver_keyboard_init(KeyScan_Debounce_Enable);
        }
    }