dedicated gpio: matrix keyboard example

This commit is contained in:
morris 2020-05-11 18:59:42 +08:00
parent bb1369b922
commit d51a62e33a
14 changed files with 537 additions and 3 deletions

View File

@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(gpio)
project(generic-gpio)

View File

@ -3,7 +3,7 @@
# project subdirectory.
#
PROJECT_NAME := gpio
PROJECT_NAME := generic-gpio
include $(IDF_PATH)/make/project.mk

View File

@ -10,7 +10,7 @@ import ttfw_idf
@ttfw_idf.idf_example_test(env_tag="Example_TWAI1", target=['esp32', 'esp32s2'], ci_target=['esp32'])
def test_examples_gpio(env, extra_data):
app_name = "gpio"
dut = env.get_dut(app_name, "examples/peripherals/gpio")
dut = env.get_dut(app_name, "examples/peripherals/gpio/generic_gpio")
dut.start_app()
res = dut.expect(ttfw_idf.MINIMUM_FREE_HEAP_SIZE_RE)
if not res:

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(matrix-keyboard)

View File

@ -0,0 +1,79 @@
| Supported Targets | ESP32-S2 |
| ----------------- | -------- |
# Matrix Keyboard Example (based on Dedicated GPIO)
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## Overview
This example mainly illustrates how to drive a common matrix keyboard using the dedicated GPIO APIs, including:
* Manipulate the level on a group of GPIOs
* Trigger edge interrupt
* Read level on a group of GPIOs
Dedicated GPIO is designed to speed up CPU operations on one or a group of GPIOs by writing assembly codes with Espressif customized instructions (please refer TRM to get more information about these instructions).
This matrix keyboard driver is interrupt-driven, supports a configurable debounce time. GPIOs used by row and column lines are also configurable during driver installation stage.
## How to use example
### Hardware Required
This example can run on any target that has the dedicated feature (e.g. ESP32-S2). It's not necessary for your matrix board to have pull-up resisters on row/column lines. The driver has enabled internal pull-up resister by default. A typical matrix board should look as follows:
```
row_0 +--------+-------------------+------------------------------+-----------------+
| | |
| + | + | +
| +-+-+ | +-+-+ ...... | +-+-+
. +-----+ +-----+ +-----+ +-----+ +-----+ +-----+
. | | |
. . | . | . |
. | . | ...... . |
. | . | . |
. | . | . |
| | |
row_n +--------+-------------------+------------------------------+-----------------+
| | | | | |
| + | | + | | + |
| +-+-+ | | +-+-+ | ...... | +-+-+ |
+-----+ +-----+ +-----+ +-----+ +-----+ +-----+
| | |
| | |
| | |
+ + +
col_0 col_1 ...... col_m
```
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (2883) example: press event, key code = 0002
I (3003) example: release event, key code = 0002
I (5053) example: press event, key code = 0001
I (5203) example: release event, key code = 0001
I (6413) example: press event, key code = 0000
I (6583) example: release event, key code = 0000
I (7963) example: press event, key code = 0003
I (8113) example: release event, key code = 0003
I (8773) example: press event, key code = 0103
I (8923) example: release event, key code = 0103
I (9543) example: press event, key code = 0203
I (9683) example: release event, key code = 0203
```

View File

@ -0,0 +1,7 @@
set(component_srcs "src/matrix_keyboard.c")
idf_component_register(SRCS "${component_srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS ""
PRIV_REQUIRES "driver"
REQUIRES "")

View File

@ -0,0 +1,142 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
#define MAKE_KEY_CODE(row, col) ((row << 8) | (col))
#define GET_KEY_CODE_ROW(code) ((code >> 8) & 0xFF)
#define GET_KEY_CODE_COL(code) (code & 0xFF)
/**
* @brief Type defined for matrix keyboard handle
*
*/
typedef struct matrix_kbd_t *matrix_kbd_handle_t;
/**
* @brief Matrix keyboard event ID
*
*/
typedef enum {
MATRIX_KBD_EVENT_DOWN, /*!< Key is pressed down */
MATRIX_KBD_EVENT_UP /*!< Key is released */
} matrix_kbd_event_id_t;
/**
* @brief Type defined for matrix keyboard event handler
*
* @note The event handler runs in a OS timer context
*
* @param[in] mkbd_handle Handle of matrix keyboard that return from `matrix_kbd_install`
* @param[in] event Event ID, refer to `matrix_kbd_event_id_t` to see all supported events
* @param[in] event_data Data for corresponding event
* @param[in] handler_args Arguments that user passed in from `matrix_kbd_register_event_handler`
* @return Currently always return ESP_OK
*/
typedef esp_err_t (*matrix_kbd_event_handler)(matrix_kbd_handle_t mkbd_handle, matrix_kbd_event_id_t event, void *event_data, void *handler_args);
/**
* @brief Configuration structure defined for matrix keyboard
*
*/
typedef struct {
const int *row_gpios; /*!< Array, contains GPIO numbers used by row line */
const int *col_gpios; /*!< Array, contains GPIO numbers used by column line */
uint32_t nr_row_gpios; /*!< row_gpios array size */
uint32_t nr_col_gpios; /*!< col_gpios array size */
uint32_t debounce_ms; /*!< Debounce time */
} matrix_kbd_config_t;
/**
* @brief Default configuration for matrix keyboard driver
*
*/
#define MATRIX_KEYBOARD_DEFAULT_CONFIG() \
{ \
.row_gpios = NULL, \
.col_gpios = NULL, \
.nr_row_gpios = 0, \
.nr_col_gpios = 0, \
.debounce_ms = 20, \
}
/**
* @brief Install matrix keyboard driver
*
* @param[in] config Configuration of matrix keyboard driver
* @param[out] mkbd_handle Returned matrix keyboard handle if installation succeed
* @return
* - ESP_OK: Install matrix keyboard driver successfully
* - ESP_ERR_INVALID_ARG: Install matrix keyboard driver failed because of some invalid argument
* - ESP_ERR_NO_MEM: Install matrix keyboard driver failed because there's no enough capable memory
* - ESP_FAIL: Install matrix keyboard driver failed because of other error
*/
esp_err_t matrix_kbd_install(const matrix_kbd_config_t *config, matrix_kbd_handle_t *mkbd_handle);
/**
* @brief Uninstall matrix keyboard driver
*
* @param[in] mkbd_handle Handle of matrix keyboard that return from `matrix_kbd_install`
* @return
* - ESP_OK: Uninstall matrix keyboard driver successfully
* - ESP_ERR_INVALID_ARG: Uninstall matrix keyboard driver failed because of some invalid argument
* - ESP_FAIL: Uninstall matrix keyboard driver failed because of other error
*/
esp_err_t matrix_kbd_uninstall(matrix_kbd_handle_t mkbd_handle);
/**
* @brief Start matrix keyboard driver
*
* @param[in] mkbd_handle Handle of matrix keyboard that return from `matrix_kbd_install`
* @return
* - ESP_OK: Start matrix keyboard driver successfully
* - ESP_ERR_INVALID_ARG: Start matrix keyboard driver failed because of some invalid argument
* - ESP_FAIL: Start matrix keyboard driver failed because of other error
*/
esp_err_t matrix_kbd_start(matrix_kbd_handle_t mkbd_handle);
/**
* @brief Stop matrix kayboard driver
*
* @param[in] mkbd_handle Handle of matrix keyboard that return from `matrix_kbd_install`
* @return
* - ESP_OK: Stop matrix keyboard driver successfully
* - ESP_ERR_INVALID_ARG: Stop matrix keyboard driver failed because of some invalid argument
* - ESP_FAIL: Stop matrix keyboard driver failed because of other error
*/
esp_err_t matrix_kbd_stop(matrix_kbd_handle_t mkbd_handle);
/**
* @brief Register matrix keyboard event handler
*
* @param[in] mkbd_handle Handle of matrix keyboard that return from `matrix_kbd_install`
* @param[in] handler Event handler
* @param[in] args Arguments that will be passed to the handler
* @return
* - ESP_OK: Register event handler successfully
* - ESP_ERR_INVALID_ARG: Register event handler failed because of some invalid argument
* - ESP_FAIL: Register event handler failed because of other error
*/
esp_err_t matrix_kbd_register_event_handler(matrix_kbd_handle_t mkbd_handle, matrix_kbd_event_handler handler, void *args);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,245 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_compiler.h"
#include "esp_log.h"
#include "driver/dedic_gpio.h"
#include "driver/gpio.h"
#include "matrix_keyboard.h"
#include "esp_rom_sys.h"
static const char *TAG = "mkbd";
#define MKBD_CHECK(a, msg, tag, ret, ...) \
do { \
if (unlikely(!(a))) { \
ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
ret_code = ret; \
goto tag; \
} \
} while (0)
typedef struct matrix_kbd_t matrix_kbd_t;
struct matrix_kbd_t {
dedic_gpio_bundle_handle_t row_bundle;
dedic_gpio_bundle_handle_t col_bundle;
uint32_t nr_row_gpios;
uint32_t nr_col_gpios;
TimerHandle_t debounce_timer;
matrix_kbd_event_handler event_handler;
void *event_handler_args;
uint32_t row_state[0];
};
static IRAM_ATTR bool matrix_kbd_row_isr_callback(dedic_gpio_bundle_handle_t row_bundle, uint32_t row_index, void *args)
{
BaseType_t high_task_wakeup = pdFALSE;
matrix_kbd_t *mkbd = (matrix_kbd_t *)args;
// temporarily disable interrupt
dedic_gpio_bundle_set_interrupt_and_callback(row_bundle, (1 << mkbd->nr_row_gpios) - 1, DEDIC_GPIO_INTR_NONE, NULL, NULL);
// get row id, start to check the col id
dedic_gpio_bundle_write(row_bundle, 1 << row_index, 0);
dedic_gpio_bundle_write(mkbd->col_bundle, (1 << mkbd->nr_col_gpios) - 1, (1 << mkbd->nr_col_gpios) - 1);
xTimerStartFromISR(mkbd->debounce_timer, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
static void matrix_kbd_debounce_timer_callback(TimerHandle_t xTimer)
{
matrix_kbd_t *mkbd = (matrix_kbd_t *)pvTimerGetTimerID(xTimer);
uint32_t row_out = dedic_gpio_bundle_read_out(mkbd->row_bundle);
uint32_t col_in = dedic_gpio_bundle_read_in(mkbd->col_bundle);
row_out = (~row_out) & ((1 << mkbd->nr_row_gpios) - 1);
ESP_LOGD(TAG, "row_out=%x, col_in=%x", row_out, col_in);
int row = -1;
int col = -1;
uint32_t key_code = 0;
while (row_out) {
row = __builtin_ffs(row_out) - 1;
uint32_t changed_col_bits = mkbd->row_state[row] ^ col_in;
while (changed_col_bits) {
col = __builtin_ffs(changed_col_bits) - 1;
ESP_LOGD(TAG, "row=%d, col=%d", row, col);
key_code = MAKE_KEY_CODE(row, col);
if (col_in & (1 << col)) {
mkbd->event_handler(mkbd, MATRIX_KBD_EVENT_UP, (void *)key_code, mkbd->event_handler_args);
} else {
mkbd->event_handler(mkbd, MATRIX_KBD_EVENT_DOWN, (void *)key_code, mkbd->event_handler_args);
}
changed_col_bits = changed_col_bits & (changed_col_bits - 1);
}
mkbd->row_state[row] = col_in;
row_out = row_out & (row_out - 1);
}
// row lines set to high level
dedic_gpio_bundle_write(mkbd->row_bundle, (1 << mkbd->nr_row_gpios) - 1, (1 << mkbd->nr_row_gpios) - 1);
// col lines set to low level
dedic_gpio_bundle_write(mkbd->col_bundle, (1 << mkbd->nr_col_gpios) - 1, 0);
dedic_gpio_bundle_set_interrupt_and_callback(mkbd->row_bundle, (1 << mkbd->nr_row_gpios) - 1,
DEDIC_GPIO_INTR_BOTH_EDGE, matrix_kbd_row_isr_callback, mkbd);
}
esp_err_t matrix_kbd_install(const matrix_kbd_config_t *config, matrix_kbd_handle_t *mkbd_handle)
{
esp_err_t ret_code = ESP_OK;
matrix_kbd_t *mkbd = NULL;
MKBD_CHECK(config, "matrix keyboard configuration can't be null", err, ESP_ERR_INVALID_ARG);
MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
mkbd = calloc(1, sizeof(matrix_kbd_t) + (config->nr_row_gpios) * sizeof(uint32_t));
MKBD_CHECK(mkbd, "allocate matrix keyboard context failed", err, ESP_ERR_NO_MEM);
mkbd->nr_col_gpios = config->nr_col_gpios;
mkbd->nr_row_gpios = config->nr_row_gpios;
// GPIO pad configuration
// Each GPIO used in matrix key board should be able to input and output
// In case the keyboard doesn't design a resister to pull up row/col line
// We enable the internal pull up resister, enable Open Drain as well
gpio_config_t io_conf = {
.mode = GPIO_MODE_INPUT_OUTPUT_OD,
.pull_up_en = 1
};
for (int i = 0; i < config->nr_row_gpios; i++) {
io_conf.pin_bit_mask = 1ULL << config->row_gpios[i];
gpio_config(&io_conf);
}
dedic_gpio_bundle_config_t bundle_row_config = {
.gpio_array = config->row_gpios,
.array_size = config->nr_row_gpios,
.flags = {
.in_en = 1,
.out_en = 1,
},
};
MKBD_CHECK(dedic_gpio_new_bundle(&bundle_row_config, &mkbd->row_bundle) == ESP_OK,
"create row bundle failed", err, ESP_FAIL);
for (int i = 0; i < config->nr_col_gpios; i++) {
io_conf.pin_bit_mask = 1ULL << config->col_gpios[i];
gpio_config(&io_conf);
}
dedic_gpio_bundle_config_t bundle_col_config = {
.gpio_array = config->col_gpios,
.array_size = config->nr_col_gpios,
.flags = {
.in_en = 1,
.out_en = 1,
},
};
MKBD_CHECK(dedic_gpio_new_bundle(&bundle_col_config, &mkbd->col_bundle) == ESP_OK,
"create col bundle failed", err, ESP_FAIL);
// Disable interrupt
dedic_gpio_bundle_set_interrupt_and_callback(mkbd->row_bundle, (1 << config->nr_row_gpios) - 1,
DEDIC_GPIO_INTR_NONE, NULL, NULL);
dedic_gpio_bundle_set_interrupt_and_callback(mkbd->col_bundle, (1 << config->nr_col_gpios) - 1,
DEDIC_GPIO_INTR_NONE, NULL, NULL);
// Create a ont-shot os timer, used for key debounce
mkbd->debounce_timer = xTimerCreate("kb_debounce", pdMS_TO_TICKS(config->debounce_ms), pdFALSE, mkbd, matrix_kbd_debounce_timer_callback);
MKBD_CHECK(mkbd->debounce_timer, "create debounce timer failed", err, ESP_FAIL);
* mkbd_handle = mkbd;
return ESP_OK;
err:
if (mkbd) {
if (mkbd->debounce_timer) {
xTimerDelete(mkbd->debounce_timer, 0);
}
if (mkbd->col_bundle) {
dedic_gpio_del_bundle(mkbd->col_bundle);
}
if (mkbd->row_bundle) {
dedic_gpio_del_bundle(mkbd->row_bundle);
}
free(mkbd);
}
return ret_code;
}
esp_err_t matrix_kbd_uninstall(matrix_kbd_handle_t mkbd_handle)
{
esp_err_t ret_code = ESP_OK;
MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
xTimerDelete(mkbd_handle->debounce_timer, 0);
dedic_gpio_del_bundle(mkbd_handle->col_bundle);
dedic_gpio_del_bundle(mkbd_handle->row_bundle);
free(mkbd_handle);
return ESP_OK;
err:
return ret_code;
}
esp_err_t matrix_kbd_start(matrix_kbd_handle_t mkbd_handle)
{
esp_err_t ret_code = ESP_OK;
MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
// row lines set to high level
dedic_gpio_bundle_write(mkbd_handle->row_bundle, (1 << mkbd_handle->nr_row_gpios) - 1, (1 << mkbd_handle->nr_row_gpios) - 1);
// col lines set to low level
dedic_gpio_bundle_write(mkbd_handle->col_bundle, (1 << mkbd_handle->nr_col_gpios) - 1, 0);
for (int i = 0; i < mkbd_handle->nr_row_gpios; i++) {
mkbd_handle->row_state[i] = (1 << mkbd_handle->nr_col_gpios) - 1;
}
// only enable row line interrupt
dedic_gpio_bundle_set_interrupt_and_callback(mkbd_handle->row_bundle, (1 << mkbd_handle->nr_row_gpios) - 1,
DEDIC_GPIO_INTR_BOTH_EDGE, matrix_kbd_row_isr_callback, mkbd_handle);
return ESP_OK;
err:
return ret_code;
}
esp_err_t matrix_kbd_stop(matrix_kbd_handle_t mkbd_handle)
{
esp_err_t ret_code = ESP_OK;
MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
xTimerStop(mkbd_handle->debounce_timer, 0);
// Disable interrupt
dedic_gpio_bundle_set_interrupt_and_callback(mkbd_handle->row_bundle, (1 << mkbd_handle->nr_row_gpios) - 1,
DEDIC_GPIO_INTR_NONE, NULL, NULL);
dedic_gpio_bundle_set_interrupt_and_callback(mkbd_handle->col_bundle, (1 << mkbd_handle->nr_col_gpios) - 1,
DEDIC_GPIO_INTR_NONE, NULL, NULL);
return ESP_OK;
err:
return ret_code;
}
esp_err_t matrix_kbd_register_event_handler(matrix_kbd_handle_t mkbd_handle, matrix_kbd_event_handler handler, void *args)
{
esp_err_t ret_code = ESP_OK;
MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
mkbd_handle->event_handler = handler;
mkbd_handle->event_handler_args = args;
return ESP_OK;
err:
return ret_code;
}

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "matrix_keyboard_example_main.c"
INCLUDE_DIRS "")

View File

@ -0,0 +1,53 @@
/* Matrix Keyboard (based on dedicated GPIO) example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "esp_log.h"
#include "matrix_keyboard.h"
const static char *TAG = "example";
/**
* @brief Matrix keyboard event handler
* @note This function is run under OS timer task context
*/
esp_err_t example_matrix_kbd_event_handler(matrix_kbd_handle_t mkbd_handle, matrix_kbd_event_id_t event, void *event_data, void *handler_args)
{
uint32_t key_code = (uint32_t)event_data;
switch (event) {
case MATRIX_KBD_EVENT_DOWN:
ESP_LOGI(TAG, "press event, key code = %04x", key_code);
break;
case MATRIX_KBD_EVENT_UP:
ESP_LOGI(TAG, "release event, key code = %04x", key_code);
break;
}
return ESP_OK;
}
void app_main(void)
{
matrix_kbd_handle_t kbd = NULL;
// Apply default matrix keyboard configuration
matrix_kbd_config_t config = MATRIX_KEYBOARD_DEFAULT_CONFIG();
// Set GPIOs used by row and column line
config.col_gpios = (int[]) {
10, 11, 12, 13
};
config.nr_col_gpios = 4;
config.row_gpios = (int[]) {
14, 15, 16, 17
};
config.nr_row_gpios = 4;
// Install matrix keyboard driver
matrix_kbd_install(&config, &kbd);
// Register keyboard input event handler
matrix_kbd_register_event_handler(kbd, example_matrix_kbd_event_handler, NULL);
// Keyboard start to work
matrix_kbd_start(kbd);
}