This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 1953a7449d25c6280753c6fa0c00d219ed94dedd Author: Jorge Guzman <[email protected]> AuthorDate: Thu Jul 2 12:29:42 2026 -0300 boards/esp32s3: esp32s3-m5-cardputer: add keyboard and LVGL terminal Add the 56-key matrix keyboard driver for the M5Stack Cardputer. The keys form an 8x7 matrix behind a 74HC138 3-to-8 demultiplexer; the driver scans it on the low-priority work queue and registers a keyboard device at /dev/kbd0, reporting ASCII codes with SHIFT/CTRL and Fn (cursor) handling. Add an "lvglterm" configuration that runs the on-screen NuttShell terminal (apps/examples/lvglterm, physical-keyboard input variant) on the ST7789 display: the shell output is rendered in an LVGL text area and the keyboard feeds the input. Wi-Fi is included so the on-screen shell can associate with an access point using wapi. Signed-off-by: Jorge Guzman <[email protected]> --- .../esp32s3/boards/esp32s3-m5-cardputer/index.rst | 38 +- boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig | 29 ++ .../configs/lvglterm/defconfig | 126 +++++++ .../esp32s3/esp32s3-m5-cardputer/src/Make.defs | 4 + .../src/esp32s3-m5-cardputer.h | 20 ++ .../esp32s3-m5-cardputer/src/esp32s3_board_spi.c | 19 + .../esp32s3-m5-cardputer/src/esp32s3_bringup.c | 10 + .../esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c | 397 +++++++++++++++++++++ 8 files changed, 640 insertions(+), 3 deletions(-) diff --git a/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-m5-cardputer/index.rst b/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-m5-cardputer/index.rst index b96169482ed..a8258dec019 100644 --- a/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-m5-cardputer/index.rst +++ b/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-m5-cardputer/index.rst @@ -14,9 +14,9 @@ M5Stack Cardputer The `M5Stack Cardputer <https://docs.m5stack.com/en/core/Cardputer>`_ is a pocket-sized computer kit built around an M5Stamp S3 module (ESP32-S3FN8, dual -Xtensa LX7 @ 240 MHz, 8 MB flash, no PSRAM). It integrates a 1.14" ST7789 -TFT, an NS4168 I2S speaker, an SPM1423 microphone, an IR transmitter, a -microSD slot and a Grove port. +Xtensa LX7 @ 240 MHz, 8 MB flash, no PSRAM). It integrates a 56-key keyboard, +a 1.14" ST7789 TFT, an NS4168 I2S speaker, an SPM1423 microphone, an IR +transmitter, a microSD slot and a Grove port. .. figure:: esp32-cardputer-image-2.png :align: center @@ -29,6 +29,7 @@ Features * ESP32-S3FN8 (dual Xtensa LX7 @ 240 MHz), 8 MB flash, no PSRAM * Wi-Fi 4 (2.4 GHz) and Bluetooth LE (native ESP32-S3 radio) +* 56-key keyboard (8x7 matrix behind a 74HC138 demultiplexer) * 1.14" 240x135 ST7789v2 TFT on SPI2 * NS4168 mono I2S Class-D speaker amplifier * SPM1423 PDM microphone @@ -43,12 +44,33 @@ By default the NSH console runs over the **USB-Serial-JTAG** peripheral exposed on the USB Type-C connector. It enumerates on the host as ``/dev/ttyACM0`` (Linux). No external USB-to-UART bridge is required. +Keyboard +======== + +The 56-key keyboard is an 8x7 matrix driven by a 74HC138 3-to-8 demultiplexer: +three select lines (GPIO8/9/11) choose one of eight rows and seven column lines +(GPIO13/15/3/4/5/6/7) are read back. The ``esp32s3_kbd.c`` driver scans the +matrix on the low-priority work queue and registers a keyboard device at +``/dev/kbd0``, reporting ASCII key codes with SHIFT/CTRL handling. Enable it +with ``CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD`` (the ``lvglterm`` configuration +turns it on). + +.. note:: + + The key-position-to-character table follows the M5Cardputer layout; verify + it against the printed legends on first bring-up. Applications read + ``/dev/kbd0`` for key events (for example the ``lvglterm`` example, built + with its physical-keyboard input variant, feeds it into an on-screen + shell). + Pin Mapping =========== =========================== ========================================== Peripheral ESP32-S3 GPIO =========================== ========================================== +Keyboard demux select 8 (A0), 9 (A1), 11 (A2) +Keyboard columns 13, 15, 3, 4, 5, 6, 7 ST7789 SPI2 SCLK/MOSI/CS 36 / 35 / 37 ST7789 DC/RST/backlight 34 / 33 / 38 microSD SPI3 SCK/MISO 40 / 39 @@ -116,3 +138,13 @@ lvgl Graphics support with LVGL on the ST7789 display. Run the LVGL demo:: nsh> lvgldemo + +lvglterm + On-screen NuttShell terminal with LVGL and the physical keyboard. Runs + the ``lvglterm`` example built with its physical-keyboard input variant: + NSH output is rendered in an LVGL text area and the keyboard + (``/dev/kbd0``) feeds the input. Wi-Fi is included, so the on-screen + shell can associate with an access point using ``wapi``. ``Fn`` + ``;`` / + ``.`` scroll the output:: + + nsh> lvglterm diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig index a07197f7079..6fbd53d15cf 100644 --- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig +++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig @@ -5,4 +5,33 @@ if ARCH_BOARD_ESP32S3_M5_CARDPUTER +config ESP32S3_M5_CARDPUTER_KEYBOARD + bool "Cardputer matrix keyboard" + default n + select INPUT + select INPUT_KEYBOARD + select SCHED_LPWORK + ---help--- + Enable the M5Stack Cardputer 56-key matrix keyboard driver. The + driver scans the 74HC138-multiplexed key matrix and registers a + keyboard device (/dev/kbdN). + +if ESP32S3_M5_CARDPUTER_KEYBOARD + +config ESP32S3_M5_CARDPUTER_KBD_BUFNUM + int "Cardputer keyboard event buffer size" + default 8 + ---help--- + Number of key events buffered by the Cardputer matrix keyboard + driver before the oldest events are overwritten. + +config ESP32S3_M5_CARDPUTER_KBD_DEBUG + bool "Cardputer keyboard scan debug logging" + default n + ---help--- + Log every detected key matrix transition to the syslog. Useful to + bring up the keyboard and to verify the key map. + +endif # ESP32S3_M5_CARDPUTER_KEYBOARD + endif # ARCH_BOARD_ESP32S3_M5_CARDPUTER diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/configs/lvglterm/defconfig b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/configs/lvglterm/defconfig new file mode 100644 index 00000000000..349e37a7e95 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/configs/lvglterm/defconfig @@ -0,0 +1,126 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_NSH_ARGCAT is not set +# CONFIG_NSH_CMDOPT_HEXDUMP is not set +# CONFIG_SPI_CALLBACK is not set +CONFIG_ALLOW_BSD_COMPONENTS=y +CONFIG_ARCH="xtensa" +CONFIG_ARCH_BOARD="esp32s3-m5-cardputer" +CONFIG_ARCH_BOARD_COMMON=y +CONFIG_ARCH_BOARD_ESP32S3_M5_CARDPUTER=y +CONFIG_ARCH_CHIP="esp32s3" +CONFIG_ARCH_CHIP_ESP32S3=y +CONFIG_ARCH_CHIP_ESP32S3MINI1N8=y +CONFIG_ARCH_INTERRUPTSTACK=2048 +CONFIG_ARCH_IRQ_TO_NDX=y +CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC=y +CONFIG_ARCH_NUSER_INTERRUPTS=2 +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARCH_XTENSA=y +CONFIG_BOARDCTL_RESET=y +CONFIG_BOARD_LOOPSPERMSEC=16717 +CONFIG_BUILTIN=y +CONFIG_DEBUG_FULLOPT=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DRIVERS_IEEE80211=y +CONFIG_DRIVERS_WIRELESS=y +CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD=y +CONFIG_ESP32S3_SPI2=y +CONFIG_ESP32S3_SPI2_CLKPIN=36 +CONFIG_ESP32S3_SPI2_CSPIN=37 +CONFIG_ESP32S3_SPI2_MISOPIN=-1 +CONFIG_ESP32S3_SPI2_MOSIPIN=35 +CONFIG_ESP32S3_SPI3=y +CONFIG_ESP32S3_SPI3_CLKPIN=40 +CONFIG_ESP32S3_SPI3_CSPIN=12 +CONFIG_ESP32S3_SPI3_MISOPIN=39 +CONFIG_ESP32S3_SPI3_MOSIPIN=14 +CONFIG_ESP32S3_SPIFLASH=y +CONFIG_ESP32S3_SPIFLASH_SPIFFS=y +CONFIG_ESP32S3_SPI_SWCS=y +CONFIG_ESP32S3_USBSERIAL=y +CONFIG_ESPRESSIF_WIFI=y +CONFIG_EXAMPLES_LVGLTERM=y +CONFIG_EXAMPLES_LVGLTERM_FONT_UNSCII_8=y +CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD=y +CONFIG_FAT_LCNAMES=y +CONFIG_FAT_LFN=y +CONFIG_FS_FAT=y +CONFIG_FS_PROCFS=y +CONFIG_GRAPHICS_LVGL=y +CONFIG_IDLETHREAD_STACKSIZE=3072 +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_INIT_STACKSIZE=3072 +CONFIG_INTELHEX_BINARY=y +CONFIG_IOB_BUFSIZE=400 +CONFIG_IOB_NBUFFERS=100 +CONFIG_IOB_NCHAINS=32 +CONFIG_IOB_THROTTLE=40 +CONFIG_LCD=y +CONFIG_LCD_DEV=y +CONFIG_LCD_FRAMEBUFFER=y +CONFIG_LCD_ST7789=y +CONFIG_LCD_ST7789_DATA_ENDIAN_LITTLE=y +CONFIG_LCD_ST7789_FREQUENCY=40000000 +CONFIG_LCD_ST7789_XOFFSET=52 +CONFIG_LCD_ST7789_XRES=135 +CONFIG_LCD_ST7789_YOFFSET=40 +CONFIG_LCD_ST7789_YRES=240 +CONFIG_LIBC_EXECFUNCS=y +CONFIG_LIBC_PERROR_STDOUT=y +CONFIG_LIBC_STRERROR=y +CONFIG_LINE_MAX=64 +CONFIG_LV_FONT_UNSCII_16=y +CONFIG_LV_USE_CLIB_MALLOC=y +CONFIG_LV_USE_CLIB_SPRINTF=y +CONFIG_LV_USE_CLIB_STRING=y +CONFIG_LV_USE_DEMO_WIDGETS=y +CONFIG_LV_USE_LOG=y +CONFIG_LV_USE_NUTTX=y +CONFIG_LV_USE_NUTTX_LCD=y +CONFIG_MMCSD=y +CONFIG_NETDB_DNSCLIENT=y +CONFIG_NETDEV_LATEINIT=y +CONFIG_NETDEV_PHY_IOCTL=y +CONFIG_NETDEV_WIRELESS_IOCTL=y +CONFIG_NETUTILS_CJSON=y +CONFIG_NETUTILS_IPERF=y +CONFIG_NET_ETH_PKTSIZE=1514 +CONFIG_NET_ICMP_SOCKET=y +CONFIG_NET_TCP=y +CONFIG_NET_TCP_DELAYED_ACK=y +CONFIG_NET_TCP_WRITE_BUFFERS=y +CONFIG_NET_UDP=y +CONFIG_NET_UDP_WRITE_BUFFERS=y +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_DISABLE_DATE=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_MMCSDSPIPORTNO=3 +CONFIG_NSH_READLINE=y +CONFIG_PIPES=y +CONFIG_PREALLOC_TIMERS=4 +CONFIG_PTHREAD_MUTEX_TYPES=y +CONFIG_RR_INTERVAL=200 +CONFIG_RTC=y +CONFIG_RTC_DRIVER=y +CONFIG_SCHED_WAITPID=y +CONFIG_SPI_CMDDATA=y +CONFIG_START_DAY=29 +CONFIG_START_MONTH=11 +CONFIG_START_YEAR=2019 +CONFIG_SYSLOG_BUFFER=y +CONFIG_SYSTEM_DHCPC_RENEW=y +CONFIG_SYSTEM_NSH=y +CONFIG_SYSTEM_PING=y +CONFIG_TIMER=y +CONFIG_TLS_TASK_NELEM=4 +CONFIG_WIRELESS=y +CONFIG_WIRELESS_WAPI=y +CONFIG_WIRELESS_WAPI_CMDTOOL=y +CONFIG_WIRELESS_WAPI_INITCONF=y +CONFIG_WIRELESS_WAPI_STACKSIZE=4096 diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/Make.defs b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/Make.defs index 08c948f0b27..cf683d95699 100644 --- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/Make.defs +++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/Make.defs @@ -34,6 +34,10 @@ ifeq ($(CONFIG_LCD_ST7789),y) CSRCS += esp32s3_board_lcd_st7789.c endif +ifeq ($(CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD),y) +CSRCS += esp32s3_kbd.c +endif + DEPPATH += --dep-path board VPATH += :board CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)$(CONFIG_ARCH)$(DELIM)src$(DELIM)board$(DELIM)board diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3-m5-cardputer.h b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3-m5-cardputer.h index 299e6cd91e6..f9ddc1e14e9 100644 --- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3-m5-cardputer.h +++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3-m5-cardputer.h @@ -87,5 +87,25 @@ int esp32s3_bringup(void); int board_i2c_init(void); #endif +/**************************************************************************** + * Name: esp32s3_kbd_initialize + * + * Description: + * Initialize and register the M5Stack Cardputer matrix keyboard driver as + * a NuttX keyboard device (/dev/kbdN). + * + * Input Parameters: + * devpath - The full path to the keyboard device, e.g. "/dev/kbd0". + * + * Returned Value: + * Zero (OK) is returned on success; A negated errno value is returned on + * any failure. + * + ****************************************************************************/ + +#ifdef CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD +int esp32s3_kbd_initialize(FAR const char *devpath); +#endif + #endif /* __ASSEMBLY__ */ #endif /* __BOARDS_XTENSA_ESP32S3_ESP32S3_M5_CARDPUTER_SRC_ESP32S3_M5_CARDPUTER_H */ diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_board_spi.c b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_board_spi.c index 4bd10fcca08..c4fa00fb7dd 100644 --- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_board_spi.c +++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_board_spi.c @@ -92,6 +92,25 @@ int esp32s3_spi2_cmddata(struct spi_dev_s *dev, uint32_t devid, bool cmd) #endif +/**************************************************************************** + * Name: esp32s3_spi3_cmddata + * + * Description: + * SPI3 only drives the microSD card, which does not use the SPI cmd/data + * line. This stub is required to link when CONFIG_SPI_CMDDATA is enabled + * for the ST7789 display on SPI2 while SPI3 is also in use. + * + ****************************************************************************/ + +#if defined(CONFIG_ESP32S3_SPI3) && defined(CONFIG_SPI_CMDDATA) + +int esp32s3_spi3_cmddata(struct spi_dev_s *dev, uint32_t devid, bool cmd) +{ + return -ENODEV; +} + +#endif + /**************************************************************************** * Name: esp32s3_spi3_status * diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_bringup.c b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_bringup.c index 0d3f5c7afad..1c1c6db461d 100644 --- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_bringup.c +++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_bringup.c @@ -210,6 +210,16 @@ int esp32s3_bringup(void) } #endif +#ifdef CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD + /* Register the Cardputer matrix keyboard */ + + ret = esp32s3_kbd_initialize("/dev/kbd0"); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to initialize keyboard: %d\n", ret); + } +#endif + /* If we got here then perhaps not all initialization was successful, but * at least enough succeeded to bring-up NSH with perhaps reduced * capabilities. diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c new file mode 100644 index 00000000000..c4dfef46b13 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c @@ -0,0 +1,397 @@ +/**************************************************************************** + * boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <debug.h> +#include <syslog.h> + +#include <nuttx/arch.h> +#include <nuttx/clock.h> +#include <nuttx/wqueue.h> +#include <nuttx/kmalloc.h> +#include <nuttx/input/keyboard.h> + +#include <arch/board/board.h> + +#include "espressif/esp_gpio.h" +#include "esp32s3-m5-cardputer.h" + +#ifdef CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The 56-key keyboard is an 8x7 matrix. A 74HC138 3-to-8 demultiplexer + * drives one of eight rows low at a time (selected by SEL0..SEL2); the seven + * column inputs are read back (a pressed key pulls its column low). + * + * Electrically this gives an 8x7 grid. Following the M5Cardputer wiring, + * each scanned position (demux index i in 0..7, column j in 0..6) maps to + * the physical 4x14 key layout as: + * + * physical row y = i % 4 + * physical column x = j + (i / 4) * 7 + */ + +#define KBD_NSEL 3 +#define KBD_NCOL 7 +#define KBD_NROW_PHYS 4 +#define KBD_NCOL_PHYS 14 + +/* Scan/debounce period */ + +#define KBD_POLL_TICKS MSEC2TICK(20) + +/* Modifier key positions in the physical layout (row, col) */ + +#define KBD_SHIFT_Y 2 +#define KBD_SHIFT_X 1 +#define KBD_CTRL_Y 3 +#define KBD_CTRL_X 0 +#define KBD_FN_Y 2 +#define KBD_FN_X 0 + +/* Codes emitted for the Fn + navigation cluster (cursor keys). They are + * placed above the printable ASCII range so applications reading /dev/kbd0 + * can tell them apart from regular characters. + */ + +#define KBD_CODE_UP 0x80 +#define KBD_CODE_DOWN 0x81 +#define KBD_CODE_LEFT 0x82 +#define KBD_CODE_RIGHT 0x83 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct esp32s3_kbd_s +{ + struct keyboard_lowerhalf_s lower; /* Keyboard upper-half binding */ + struct work_s work; /* Scanning work item */ + + /* Debounced key state and the code emitted on each key press */ + + bool pressed[KBD_NROW_PHYS][KBD_NCOL_PHYS]; + uint8_t code[KBD_NROW_PHYS][KBD_NCOL_PHYS]; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const uint8_t g_sel_pins[KBD_NSEL] = +{ + CARDPUTER_GPIO_KB_A0, CARDPUTER_GPIO_KB_A1, CARDPUTER_GPIO_KB_A2 +}; + +static const uint8_t g_col_pins[KBD_NCOL] = +{ + CARDPUTER_GPIO_KB_C0, CARDPUTER_GPIO_KB_C1, CARDPUTER_GPIO_KB_C2, + CARDPUTER_GPIO_KB_C3, CARDPUTER_GPIO_KB_C4, CARDPUTER_GPIO_KB_C5, + CARDPUTER_GPIO_KB_C6 +}; + +/* Physical 4x14 layout. '\0' marks a modifier or unused position (these are + * handled by their fixed positions, not emitted as characters). This table + * follows the M5Cardputer key layout and should be verified against the + * hardware if the printed legends do not match. + */ + +static const char g_normal[KBD_NROW_PHYS][KBD_NCOL_PHYS] = +{ + {'`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b'}, + {'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'}, + {'\0', '\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '\r'}, + {'\0', '\0', '\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', ' '} +}; + +static const char g_shift[KBD_NROW_PHYS][KBD_NCOL_PHYS] = +{ + {'~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b'}, + {'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'}, + {'\0', '\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '\r'}, + {'\0', '\0', '\0', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', ' '} +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_kbd_select + * + * Description: + * Drive the 74HC138 select lines with the given row index (0..7). + * + ****************************************************************************/ + +static void esp32s3_kbd_select(uint8_t row) +{ + int i; + + for (i = 0; i < KBD_NSEL; i++) + { + esp_gpiowrite(g_sel_pins[i], (row >> i) & 1); + } +} + +/**************************************************************************** + * Name: esp32s3_kbd_resolve + * + * Description: + * Resolve a physical key position into a character, applying the SHIFT, + * CTRL and FN modifiers. Returns 0 for modifier/unused positions. + * + ****************************************************************************/ + +static uint8_t esp32s3_kbd_resolve(int y, int x, bool shift, bool ctrl, + bool fn) +{ + char ch; + + /* FN layer: the ; . , / cluster becomes the cursor (arrow) keys, reported + * as out-of-band codes so applications can act on them (e.g. scrolling). + */ + + if (fn) + { + if (y == 2 && x == 11) + { + return KBD_CODE_UP; /* Fn + ';' */ + } + + if (y == 3 && x == 11) + { + return KBD_CODE_DOWN; /* Fn + '.' */ + } + + if (y == 3 && x == 10) + { + return KBD_CODE_LEFT; /* Fn + ',' */ + } + + if (y == 3 && x == 12) + { + return KBD_CODE_RIGHT; /* Fn + '/' */ + } + } + + ch = shift ? g_shift[y][x] : g_normal[y][x]; + + if (ch == '\0') + { + return 0; + } + + /* CTRL turns a letter into its control code (e.g. CTRL-C -> 0x03) */ + + if (ctrl && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) + { + ch = (char)(ch & 0x1f); + } + + return (uint8_t)ch; +} + +/**************************************************************************** + * Name: esp32s3_kbd_worker + * + * Description: + * Periodically scan the matrix and report key transitions. + * + ****************************************************************************/ + +static void esp32s3_kbd_worker(FAR void *arg) +{ + FAR struct esp32s3_kbd_s *priv = arg; + bool scan[KBD_NROW_PHYS][KBD_NCOL_PHYS]; + bool shift; + bool ctrl; + bool fn; + int i; + int j; + int y; + int x; + + memset(scan, 0, sizeof(scan)); + + /* Read the full matrix into the physical layout */ + + for (i = 0; i < (1 << KBD_NSEL); i++) + { + esp32s3_kbd_select((uint8_t)i); + up_udelay(5); + + for (j = 0; j < KBD_NCOL; j++) + { + if (!esp_gpioread(g_col_pins[j])) + { + /* Map the (demux index i, column j) to the physical 4x14 grid + * exactly as the M5Cardputer firmware does: + * y = 3 - (i % 4) + * x = (i > 3) ? (2 * j) : (2 * j + 1) + */ + + y = (KBD_NROW_PHYS - 1) - (i % KBD_NROW_PHYS); + x = (i >= KBD_NROW_PHYS) ? (2 * j) : (2 * j + 1); + scan[y][x] = true; + } + } + } + + /* Latch the modifier state for this scan */ + + shift = scan[KBD_SHIFT_Y][KBD_SHIFT_X]; + ctrl = scan[KBD_CTRL_Y][KBD_CTRL_X]; + fn = scan[KBD_FN_Y][KBD_FN_X]; + + /* Emit press/release transitions */ + + for (y = 0; y < KBD_NROW_PHYS; y++) + { + for (x = 0; x < KBD_NCOL_PHYS; x++) + { + if (scan[y][x] == priv->pressed[y][x]) + { + continue; + } + + priv->pressed[y][x] = scan[y][x]; + + if (scan[y][x]) + { + uint8_t code = esp32s3_kbd_resolve(y, x, shift, ctrl, fn); +#ifdef CONFIG_ESP32S3_M5_CARDPUTER_KBD_DEBUG + syslog(LOG_INFO, "kbd: press y=%d x=%d code=0x%02x '%c'\n", + y, x, code, (code >= 0x20 && code < 0x7f) ? code : '.'); +#endif + if (code != 0) + { + priv->code[y][x] = code; + keyboard_event(&priv->lower, code, KEYBOARD_PRESS); + } + } + else if (priv->code[y][x] != 0) + { + keyboard_event(&priv->lower, priv->code[y][x], + KEYBOARD_RELEASE); + priv->code[y][x] = 0; + } + } + } + + work_queue(LPWORK, &priv->work, esp32s3_kbd_worker, priv, KBD_POLL_TICKS); +} + +/**************************************************************************** + * Name: esp32s3_kbd_open / esp32s3_kbd_close + ****************************************************************************/ + +static int esp32s3_kbd_open(FAR struct keyboard_lowerhalf_s *lower) +{ + return OK; +} + +static int esp32s3_kbd_close(FAR struct keyboard_lowerhalf_s *lower) +{ + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_kbd_initialize + * + * Description: + * Initialize and register the Cardputer matrix keyboard at devpath + * (e.g. "/dev/kbd0"). + * + ****************************************************************************/ + +int esp32s3_kbd_initialize(FAR const char *devpath) +{ + FAR struct esp32s3_kbd_s *priv; + int ret; + int i; + + priv = kmm_zalloc(sizeof(struct esp32s3_kbd_s)); + if (priv == NULL) + { + return -ENOMEM; + } + + priv->lower.open = esp32s3_kbd_open; + priv->lower.close = esp32s3_kbd_close; + priv->lower.write = NULL; + + /* Configure the demux select lines as outputs and the column inputs with + * pull-ups (a pressed key pulls the selected column low). + */ + + for (i = 0; i < KBD_NSEL; i++) + { + esp_configgpio(g_sel_pins[i], OUTPUT); + esp_gpiowrite(g_sel_pins[i], 1); + } + + for (i = 0; i < KBD_NCOL; i++) + { + esp_configgpio(g_col_pins[i], INPUT_PULLUP); + } + + ret = keyboard_register(&priv->lower, devpath, + CONFIG_ESP32S3_M5_CARDPUTER_KBD_BUFNUM); + if (ret < 0) + { + ierr("ERROR: keyboard_register(%s) failed: %d\n", devpath, ret); + kmm_free(priv); + return ret; + } + + /* Start the periodic scan */ + + ret = work_queue(LPWORK, &priv->work, esp32s3_kbd_worker, priv, + KBD_POLL_TICKS); + if (ret < 0) + { + ierr("ERROR: failed to start keyboard scan: %d\n", ret); + keyboard_unregister(&priv->lower, devpath); + kmm_free(priv); + return ret; + } + + return OK; +} + +#endif /* CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD */
