diff --git a/CMakeLists.txt b/CMakeLists.txt index 0396cf7..ea37741 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,15 @@ else() set(ENV{BUILD_BOARD} ${BUILD_BOARD}) endif() +# Add BSP-specific configurations +if(BUILD_BOARD STREQUAL "m5stack_tab5") + add_compile_definitions(BSP_CONFIG_NO_GRAPHIC_LIB=1) +endif() + include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(esp32-sdl3-swift-example) get_filename_component(configName "${CMAKE_BINARY_DIR}" NAME) list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/components/esp_littlefs") +list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/components") littlefs_create_partition_image(assets assets FLASH_IN_PROJECT) diff --git a/README.md b/README.md index 205819b..bd057a3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Test Status](https://github.com/georgik/esp32-sdl3-swift-example/actions/workflows/test.yml/badge.svg) -Example of graphical application for ESP32-C3, ESP32-P4. +Example of graphical application for ESP32-C3, ESP32-C6, ESP32-P4 using Swift programming language with SDL3 graphics library. Read more about Swift for ESP32 at [Espressif Developer Portal](https://developer.espressif.com/tags/swift/). @@ -23,15 +23,50 @@ Read more about Swift for ESP32 at [Espressif Developer Portal](https://develope ```shell source esp-idf/export.sh +``` + +If you want to use specific Swift toolchain, you can set the environment variable `TOOLCHAINS`. +The step is not required for Swift 6.1 and newer. +```shell export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a.xctoolchain/Info.plist) ``` -### Build for ESP32-P4-Function-Ev-Board +## Supported Boards + +This project supports multiple ESP32 development boards with different display configurations: + +| Board | MCU | Display | Resolution | Interface | Status | +|-------|-----|---------|------------|-----------|--------| +| ESP32-P4 Function Evaluation Board | ESP32-P4 | RGB LCD | 480x480 | RGB | ✅ Working | +| M5Stack Tab5 | ESP32-P4 | MIPI-DSI LCD | 720x1280 | MIPI-DSI | ✅ Working | +| ESP32-C3 LCD Kit | ESP32-C3 | SPI LCD | 240x240 | SPI | ✅ Working | +| ESP32-C6 DevKit | ESP32-C6 | SPI LCD | 320x240 | SPI | ✅ Working | +| Waveshare ESP32-C6-LCD-1.47 | ESP32-C6 | SPI LCD | 172x320 | SPI | ✅ Working | + +## Build Instructions + +### Build for ESP32-P4 Function Evaluation Board ```shell idf.py @boards/esp32_p4_function_ev_board.cfg flash monitor ``` +### Build for M5Stack Tab5 + +![M5Stack Tab5](docs/img/m5stack-tab5.webp) + +The M5Stack Tab5 is a premium ESP32-P4 tablet with a high-resolution 5-inch MIPI-DSI display (720x1280) and GT911 capacitive touch controller. + +- **Board**: [M5Stack Tab5](https://shop.m5stack.com/products/m5stack-tab5-esp32-p4-tablet) +- **MCU**: ESP32-P4 RISC-V dual-core +- **Display**: 5-inch IPS LCD, 720x1280 resolution +- **Touch**: GT911 capacitive touch controller +- **Interface**: MIPI-DSI for display + +```shell +idf.py @boards/m5stack_tab5.cfg flash monitor +``` + ### Build for ESP32-C3-LcdKit ![ESP32-C3-LcdKit](docs/img/esp32-c3-lcdkit.webp) diff --git a/boards/m5stack_tab5.cfg b/boards/m5stack_tab5.cfg new file mode 100644 index 0000000..357c4a7 --- /dev/null +++ b/boards/m5stack_tab5.cfg @@ -0,0 +1 @@ +-DSDKCONFIG_DEFAULTS="sdkconfig.defaults.m5stack_tab5;sdkconfig.defaults" -DBUILD_BOARD="m5stack_tab5" -DIDF_TARGET=esp32p4 -B build.m5stack_tab5 diff --git a/boards/m5stack_tab5/diagram.json b/boards/m5stack_tab5/diagram.json new file mode 100644 index 0000000..44b8319 --- /dev/null +++ b/boards/m5stack_tab5/diagram.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "author": "ESP32-SDL3-Swift Example", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-p4", + "id": "esp32p4", + "top": 0, + "left": 0, + "attrs": { + "psram": "16", + "flash": "16" + } + }, + { + "type": "wokwi-ili9341", + "id": "lcd", + "top": 0, + "left": 300, + "attrs": { + "width": "720", + "height": "1280" + } + } + ], + "connections": [ + ["esp32p4:GND", "lcd:GND", "black", []], + ["esp32p4:3V3", "lcd:VCC", "red", []] + ], + "dependencies": {} +} diff --git a/boards/m5stack_tab5/wokwi.toml b/boards/m5stack_tab5/wokwi.toml new file mode 100644 index 0000000..44797bd --- /dev/null +++ b/boards/m5stack_tab5/wokwi.toml @@ -0,0 +1,15 @@ +[wokwi] +version = 1 +elf = "../../build.m5stack_tab5/esp32-sdl3-swift-example.elf" +firmware = "../../build.m5stack_tab5/esp32-sdl3-swift-example.bin" + +[esp32] +# M5Stack Tab5 ESP32-P4 configuration +# - Dual-core RISC-V +# - 16MB Flash, 32MB PSRAM +# - 1280x720 MIPI-DSI display +# - GT911 touch controller + +[[esp32.chip]] +model = "esp32p4" +flash_size = "16MB" diff --git a/components/m5stack_tab5/CMakeLists.txt b/components/m5stack_tab5/CMakeLists.txt new file mode 100644 index 0000000..aa466f0 --- /dev/null +++ b/components/m5stack_tab5/CMakeLists.txt @@ -0,0 +1,13 @@ + +idf_component_register( + SRCS "m5stack_tab5.c" + INCLUDE_DIRS "include" + INCLUDE_DIRS "include/bsp" + PRIV_INCLUDE_DIRS "priv_include" + REQUIRES + driver + esp_lcd + esp_codec_dev + esp_lcd_ili9881c + PRIV_REQUIRES usb spiffs fatfs +) diff --git a/components/m5stack_tab5/Kconfig b/components/m5stack_tab5/Kconfig new file mode 100644 index 0000000..b3fe8ae --- /dev/null +++ b/components/m5stack_tab5/Kconfig @@ -0,0 +1,126 @@ +menu "Board Support Package (ESP32-P4)" + + config BSP_ERROR_CHECK + bool "Enable error check in BSP" + default y + help + Error check assert the application before returning the error code. + + menu "I2C" + config BSP_I2C_NUM + int "I2C peripheral index" + default 1 + range 0 1 + help + ESP32P4 has two I2C peripherals, pick the one you want to use. + + config BSP_I2C_FAST_MODE + bool "Enable I2C fast mode" + default y + help + I2C has two speed modes: normal (100kHz) and fast (400kHz). + + config BSP_I2C_CLK_SPEED_HZ + int + default 400000 if BSP_I2C_FAST_MODE + default 100000 + endmenu + + menu "I2S" + config BSP_I2S_NUM + int "I2S peripheral index" + default 1 + range 0 2 + help + ESP32P4 has three I2S peripherals, pick the one you want to use. + endmenu + + menu "uSD card - Virtual File System" + config BSP_SD_FORMAT_ON_MOUNT_FAIL + bool "Format uSD card if mounting fails" + default n + help + The SDMMC host will format (FAT) the uSD card if it fails to mount the filesystem. + + config BSP_SD_MOUNT_POINT + string "uSD card mount point" + default "/sdcard" + help + Mount point of the uSD card in the Virtual File System + + endmenu + + menu "SPIFFS - Virtual File System" + config BSP_SPIFFS_FORMAT_ON_MOUNT_FAIL + bool "Format SPIFFS if mounting fails" + default n + help + Format SPIFFS if it fails to mount the filesystem. + + config BSP_SPIFFS_MOUNT_POINT + string "SPIFFS mount point" + default "/spiffs" + help + Mount point of SPIFFS in the Virtual File System. + + config BSP_SPIFFS_PARTITION_LABEL + string "Partition label of SPIFFS" + default "storage" + help + Partition label which stores SPIFFS. + + config BSP_SPIFFS_MAX_FILES + int "Max files supported for SPIFFS VFS" + default 5 + help + Supported max files for SPIFFS in the Virtual File System. + endmenu + + menu "Display" + config BSP_LCD_DPI_BUFFER_NUMS + int "Set number of frame buffers" + default 1 + range 1 3 + help + Let DPI LCD driver create a specified number of frame-size buffers. Only when it is set to multiple can the avoiding tearing be turned on. + + config BSP_DISPLAY_LVGL_AVOID_TEAR + bool "Avoid tearing effect" + depends on BSP_LCD_DPI_BUFFER_NUMS > 1 + default "n" + help + Avoid tearing effect through LVGL buffer mode and double frame buffers of RGB LCD. This feature is only available for RGB LCD. + + choice BSP_DISPLAY_LVGL_MODE + depends on BSP_DISPLAY_LVGL_AVOID_TEAR + prompt "Select LVGL buffer mode" + default BSP_DISPLAY_LVGL_FULL_REFRESH + config BSP_DISPLAY_LVGL_FULL_REFRESH + bool "Full refresh" + config BSP_DISPLAY_LVGL_DIRECT_MODE + bool "Direct mode" + endchoice + + config BSP_DISPLAY_BRIGHTNESS_LEDC_CH + int "LEDC channel index" + default 1 + range 0 7 + help + LEDC channel is used to generate PWM signal that controls display brightness. + Set LEDC index that should be used. + + choice BSP_LCD_COLOR_FORMAT + prompt "Select LCD color format" + default BSP_LCD_COLOR_FORMAT_RGB565 + help + Select the LCD color format RGB565/RGB888. + + config BSP_LCD_COLOR_FORMAT_RGB565 + bool "RGB565" + config BSP_LCD_COLOR_FORMAT_RGB888 + bool "RGB888" + endchoice + + endmenu + +endmenu diff --git a/components/m5stack_tab5/LICENSE b/components/m5stack_tab5/LICENSE new file mode 100644 index 0000000..75b5248 --- /dev/null +++ b/components/m5stack_tab5/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/components/m5stack_tab5/README.md b/components/m5stack_tab5/README.md new file mode 100644 index 0000000..ba450b7 --- /dev/null +++ b/components/m5stack_tab5/README.md @@ -0,0 +1 @@ +# BSP: M5STACK_TAB5 diff --git a/components/m5stack_tab5/idf_component.yml b/components/m5stack_tab5/idf_component.yml new file mode 100644 index 0000000..5be6927 --- /dev/null +++ b/components/m5stack_tab5/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + espressif/esp_lcd_st7703: ^1.0.1 + espressif/esp_codec_dev: ^1.3.0 + # lvgl/lvgl: ^8.4 + # espressif/esp_lvgl_port: ^2.5.0 + espressif/esp_lcd_touch_gt911: ^1.1.1~2 + espressif/usb_host_hid: ^1.0.3 diff --git a/components/m5stack_tab5/include/bsp/config.h b/components/m5stack_tab5/include/bsp/config.h new file mode 100644 index 0000000..37dde18 --- /dev/null +++ b/components/m5stack_tab5/include/bsp/config.h @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/************************************************************************************************** + * BSP configuration + **************************************************************************************************/ +// By default, this BSP is shipped with LVGL graphical library. Enabling this option will exclude it. +// If you want to use BSP without LVGL, select BSP version with 'noglib' suffix. +#if !defined(BSP_CONFIG_NO_GRAPHIC_LIB) // Check if the symbol is not coming from compiler definitions (-D...) +#define BSP_CONFIG_NO_GRAPHIC_LIB (0) +#endif diff --git a/components/m5stack_tab5/include/bsp/display.h b/components/m5stack_tab5/include/bsp/display.h new file mode 100644 index 0000000..fec1b82 --- /dev/null +++ b/components/m5stack_tab5/include/bsp/display.h @@ -0,0 +1,181 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief BSP LCD + * + * This file offers API for basic LCD control. + * It is useful for users who want to use the LCD without the default Graphical Library LVGL. + * + * For standard LCD initialization with LVGL graphical library, you can call all-in-one function bsp_display_start(). + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_lcd_types.h" +#include "esp_lcd_mipi_dsi.h" +#include "sdkconfig.h" + +/* LCD color formats */ +#define ESP_LCD_COLOR_FORMAT_RGB565 (1) +#define ESP_LCD_COLOR_FORMAT_RGB888 (2) + +/* LCD display color format */ +// #if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888 +// #define BSP_LCD_COLOR_FORMAT (ESP_LCD_COLOR_FORMAT_RGB888) +// #else +#define BSP_LCD_COLOR_FORMAT (ESP_LCD_COLOR_FORMAT_RGB565) +//#endif +/* LCD display color bytes endianess */ +#define BSP_LCD_BIGENDIAN (0) +/* LCD display color bits */ +#define BSP_LCD_BITS_PER_PIXEL (16) +/* LCD display color space */ +#define BSP_LCD_COLOR_SPACE (ESP_LCD_COLOR_SPACE_RGB) + +/* LCD display definition 720x1280 */ +#define BSP_LCD_H_RES (720) +#define BSP_LCD_V_RES (1280) + +#define BSP_LCD_MIPI_DSI_LCD_HSYNC (10) +#define BSP_LCD_MIPI_DSI_LCD_HBP (40) +#define BSP_LCD_MIPI_DSI_LCD_HFP (40) +#define BSP_LCD_MIPI_DSI_LCD_VSYNC (4) +#define BSP_LCD_MIPI_DSI_LCD_VBP (16) +#define BSP_LCD_MIPI_DSI_LCD_VFP (16) + +#define BSP_LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes +#define BSP_LCD_MIPI_DSI_LANE_BITRATE_MBPS (730) // 720*1280 RGB24 60Hz //(900) // 900Mbps + +#define BSP_MIPI_DSI_PHY_PWR_LDO_CHAN (3) // LDO_VO3 is connected to VDD_MIPI_DPHY +#define BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) + +/** + * @brief BSP display configuration structure + * + */ +typedef struct { + int dummy; +} bsp_display_config_t; + +/** + * @brief BSP display return handles + * + */ +typedef struct { + esp_lcd_dsi_bus_handle_t mipi_dsi_bus; /*!< MIPI DSI bus handle */ + esp_lcd_panel_io_handle_t io; /*!< ESP LCD IO handle */ + esp_lcd_panel_handle_t panel; /*!< ESP LCD panel (color) handle */ + esp_lcd_panel_handle_t control; /*!< ESP LCD panel (control) handle */ +} bsp_lcd_handles_t; + +/** + * @brief Create new display panel + * + * For maximum flexibility, this function performs only reset and initialization of the display. + * You must turn on the display explicitly by calling esp_lcd_panel_disp_on_off(). + * The display's backlight is not turned on either. You can use bsp_display_backlight_on/off(), + * bsp_display_brightness_set() (on supported boards) or implement your own backlight control. + * + * If you want to free resources allocated by this function, you can use esp_lcd API, ie.: + * + * \code{.c} + * esp_lcd_panel_del(panel); + * esp_lcd_panel_io_del(io); + * esp_lcd_del_dsi_bus(mipi_dsi_bus); + * \endcode + * + * @param[in] config display configuration + * @param[out] ret_panel esp_lcd panel handle + * @param[out] ret_io esp_lcd IO handle + * @return + * - ESP_OK On success + * - Else esp_lcd failure + */ +esp_err_t bsp_display_new(const bsp_display_config_t *config, esp_lcd_panel_handle_t *ret_panel, + esp_lcd_panel_io_handle_t *ret_io); + +/** + * @brief Create new display panel + * + * For maximum flexibility, this function performs only reset and initialization of the display. + * You must turn on the display explicitly by calling esp_lcd_panel_disp_on_off(). + * The display's backlight is not turned on either. You can use bsp_display_backlight_on/off(), + * bsp_display_brightness_set() (on supported boards) or implement your own backlight control. + * + * If you want to free resources allocated by this function, you can use esp_lcd API, ie.: + * + * \code{.c} + * esp_lcd_panel_del(panel); + * esp_lcd_panel_del(control); + * esp_lcd_panel_io_del(io); + * esp_lcd_del_dsi_bus(mipi_dsi_bus); + * \endcode + * + * @param[in] config display configuration + * @param[out] ret_handles all esp_lcd handles in one structure + * @return + * - ESP_OK On success + * - Else esp_lcd failure + */ +esp_err_t bsp_display_new_with_handles(const bsp_display_config_t *config, bsp_lcd_handles_t *ret_handles); + +/** + * @brief Initialize display's brightness + * + * Brightness is controlled with PWM signal to a pin controlling backlight. + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t bsp_display_brightness_init(void); + +/** + * @brief Set display's brightness + * + * Brightness is controlled with PWM signal to a pin controlling backlight. + * Brightness must be already initialized by calling bsp_display_brightness_init() or bsp_display_new() + * + * @param[in] brightness_percent Brightness in [%] + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t bsp_display_brightness_set(int brightness_percent); + +/** + * @brief Turn on display backlight + * + * Brightness is controlled with PWM signal to a pin controlling backlight. + * Brightness must be already initialized by calling bsp_display_brightness_init() or bsp_display_new() + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t bsp_display_backlight_on(void); + +/** + * @brief Turn off display backlight + * + * Brightness is controlled with PWM signal to a pin controlling backlight. + * Brightness must be already initialized by calling bsp_display_brightness_init() or bsp_display_new() + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t bsp_display_backlight_off(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/m5stack_tab5/include/bsp/esp-bsp.h b/components/m5stack_tab5/include/bsp/esp-bsp.h new file mode 100644 index 0000000..c7abbc3 --- /dev/null +++ b/components/m5stack_tab5/include/bsp/esp-bsp.h @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include "bsp/m5stack_tab5.h" diff --git a/components/m5stack_tab5/include/bsp/ili9881_init_data.c b/components/m5stack_tab5/include/bsp/ili9881_init_data.c new file mode 100644 index 0000000..0def92f --- /dev/null +++ b/components/m5stack_tab5/include/bsp/ili9881_init_data.c @@ -0,0 +1,216 @@ + + +static const ili9881c_lcd_init_cmd_t tab5_lcd_ili9881c_specific_init_code_default[] = { + // {cmd, { data }, data_size, delay} + /**** CMD_Page 1 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0}, + {0xB7, (uint8_t[]){0x03}, 1, 0}, // set 2 lane + /**** CMD_Page 3 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x03}, 3, 0}, + {0x01, (uint8_t[]){0x00}, 1, 0}, + {0x02, (uint8_t[]){0x00}, 1, 0}, + {0x03, (uint8_t[]){0x73}, 1, 0}, + {0x04, (uint8_t[]){0x00}, 1, 0}, + {0x05, (uint8_t[]){0x00}, 1, 0}, + {0x06, (uint8_t[]){0x08}, 1, 0}, + {0x07, (uint8_t[]){0x00}, 1, 0}, + {0x08, (uint8_t[]){0x00}, 1, 0}, + {0x09, (uint8_t[]){0x1B}, 1, 0}, + {0x0a, (uint8_t[]){0x01}, 1, 0}, + {0x0b, (uint8_t[]){0x01}, 1, 0}, + {0x0c, (uint8_t[]){0x0D}, 1, 0}, + {0x0d, (uint8_t[]){0x01}, 1, 0}, + {0x0e, (uint8_t[]){0x01}, 1, 0}, + {0x0f, (uint8_t[]){0x26}, 1, 0}, + {0x10, (uint8_t[]){0x26}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 1, 0}, + {0x12, (uint8_t[]){0x00}, 1, 0}, + {0x13, (uint8_t[]){0x02}, 1, 0}, + {0x14, (uint8_t[]){0x00}, 1, 0}, + {0x15, (uint8_t[]){0x00}, 1, 0}, + {0x16, (uint8_t[]){0x00}, 1, 0}, + {0x17, (uint8_t[]){0x00}, 1, 0}, + {0x18, (uint8_t[]){0x00}, 1, 0}, + {0x19, (uint8_t[]){0x00}, 1, 0}, + {0x1a, (uint8_t[]){0x00}, 1, 0}, + {0x1b, (uint8_t[]){0x00}, 1, 0}, + {0x1c, (uint8_t[]){0x00}, 1, 0}, + {0x1d, (uint8_t[]){0x00}, 1, 0}, + {0x1e, (uint8_t[]){0x40}, 1, 0}, + {0x1f, (uint8_t[]){0x00}, 1, 0}, + {0x20, (uint8_t[]){0x06}, 1, 0}, + {0x21, (uint8_t[]){0x01}, 1, 0}, + {0x22, (uint8_t[]){0x00}, 1, 0}, + {0x23, (uint8_t[]){0x00}, 1, 0}, + {0x24, (uint8_t[]){0x00}, 1, 0}, + {0x25, (uint8_t[]){0x00}, 1, 0}, + {0x26, (uint8_t[]){0x00}, 1, 0}, + {0x27, (uint8_t[]){0x00}, 1, 0}, + {0x28, (uint8_t[]){0x33}, 1, 0}, + {0x29, (uint8_t[]){0x03}, 1, 0}, + {0x2a, (uint8_t[]){0x00}, 1, 0}, + {0x2b, (uint8_t[]){0x00}, 1, 0}, + {0x2c, (uint8_t[]){0x00}, 1, 0}, + {0x2d, (uint8_t[]){0x00}, 1, 0}, + {0x2e, (uint8_t[]){0x00}, 1, 0}, + {0x2f, (uint8_t[]){0x00}, 1, 0}, + {0x30, (uint8_t[]){0x00}, 1, 0}, + {0x31, (uint8_t[]){0x00}, 1, 0}, + {0x32, (uint8_t[]){0x00}, 1, 0}, + {0x33, (uint8_t[]){0x00}, 1, 0}, + {0x34, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x37, (uint8_t[]){0x00}, 1, 0}, + {0x38, (uint8_t[]){0x00}, 1, 0}, + {0x39, (uint8_t[]){0x00}, 1, 0}, + {0x3a, (uint8_t[]){0x00}, 1, 0}, + {0x3b, (uint8_t[]){0x00}, 1, 0}, + {0x3c, (uint8_t[]){0x00}, 1, 0}, + {0x3d, (uint8_t[]){0x00}, 1, 0}, + {0x3e, (uint8_t[]){0x00}, 1, 0}, + {0x3f, (uint8_t[]){0x00}, 1, 0}, + {0x40, (uint8_t[]){0x00}, 1, 0}, + {0x41, (uint8_t[]){0x00}, 1, 0}, + {0x42, (uint8_t[]){0x00}, 1, 0}, + {0x43, (uint8_t[]){0x00}, 1, 0}, + {0x44, (uint8_t[]){0x00}, 1, 0}, + + {0x50, (uint8_t[]){0x01}, 1, 0}, + {0x51, (uint8_t[]){0x23}, 1, 0}, + {0x52, (uint8_t[]){0x45}, 1, 0}, + {0x53, (uint8_t[]){0x67}, 1, 0}, + {0x54, (uint8_t[]){0x89}, 1, 0}, + {0x55, (uint8_t[]){0xab}, 1, 0}, + {0x56, (uint8_t[]){0x01}, 1, 0}, + {0x57, (uint8_t[]){0x23}, 1, 0}, + {0x58, (uint8_t[]){0x45}, 1, 0}, + {0x59, (uint8_t[]){0x67}, 1, 0}, + {0x5a, (uint8_t[]){0x89}, 1, 0}, + {0x5b, (uint8_t[]){0xab}, 1, 0}, + {0x5c, (uint8_t[]){0xcd}, 1, 0}, + {0x5d, (uint8_t[]){0xef}, 1, 0}, + + {0x5e, (uint8_t[]){0x11}, 1, 0}, + {0x5f, (uint8_t[]){0x02}, 1, 0}, + {0x60, (uint8_t[]){0x00}, 1, 0}, + {0x61, (uint8_t[]){0x07}, 1, 0}, + {0x62, (uint8_t[]){0x06}, 1, 0}, + {0x63, (uint8_t[]){0x0E}, 1, 0}, + {0x64, (uint8_t[]){0x0F}, 1, 0}, + {0x65, (uint8_t[]){0x0C}, 1, 0}, + {0x66, (uint8_t[]){0x0D}, 1, 0}, + {0x67, (uint8_t[]){0x02}, 1, 0}, + {0x68, (uint8_t[]){0x02}, 1, 0}, + {0x69, (uint8_t[]){0x02}, 1, 0}, + {0x6a, (uint8_t[]){0x02}, 1, 0}, + {0x6b, (uint8_t[]){0x02}, 1, 0}, + {0x6c, (uint8_t[]){0x02}, 1, 0}, + {0x6d, (uint8_t[]){0x02}, 1, 0}, + {0x6e, (uint8_t[]){0x02}, 1, 0}, + {0x6f, (uint8_t[]){0x02}, 1, 0}, + {0x70, (uint8_t[]){0x02}, 1, 0}, + {0x71, (uint8_t[]){0x02}, 1, 0}, + {0x72, (uint8_t[]){0x02}, 1, 0}, + {0x73, (uint8_t[]){0x05}, 1, 0}, + {0x74, (uint8_t[]){0x01}, 1, 0}, + {0x75, (uint8_t[]){0x02}, 1, 0}, + {0x76, (uint8_t[]){0x00}, 1, 0}, + {0x77, (uint8_t[]){0x07}, 1, 0}, + {0x78, (uint8_t[]){0x06}, 1, 0}, + {0x79, (uint8_t[]){0x0E}, 1, 0}, + {0x7a, (uint8_t[]){0x0F}, 1, 0}, + {0x7b, (uint8_t[]){0x0C}, 1, 0}, + {0x7c, (uint8_t[]){0x0D}, 1, 0}, + {0x7d, (uint8_t[]){0x02}, 1, 0}, + {0x7e, (uint8_t[]){0x02}, 1, 0}, + {0x7f, (uint8_t[]){0x02}, 1, 0}, + {0x80, (uint8_t[]){0x02}, 1, 0}, + {0x81, (uint8_t[]){0x02}, 1, 0}, + {0x82, (uint8_t[]){0x02}, 1, 0}, + {0x83, (uint8_t[]){0x02}, 1, 0}, + {0x84, (uint8_t[]){0x02}, 1, 0}, + {0x85, (uint8_t[]){0x02}, 1, 0}, + {0x86, (uint8_t[]){0x02}, 1, 0}, + {0x87, (uint8_t[]){0x02}, 1, 0}, + {0x88, (uint8_t[]){0x02}, 1, 0}, + {0x89, (uint8_t[]){0x05}, 1, 0}, + {0x8A, (uint8_t[]){0x01}, 1, 0}, + + /**** CMD_Page 4 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x04}, 3, 0}, + {0x38, (uint8_t[]){0x01}, 1, 0}, + {0x39, (uint8_t[]){0x00}, 1, 0}, + {0x6C, (uint8_t[]){0x15}, 1, 0}, + {0x6E, (uint8_t[]){0x1A}, 1, 0}, + {0x6F, (uint8_t[]){0x25}, 1, 0}, + {0x3A, (uint8_t[]){0xA4}, 1, 0}, + {0x8D, (uint8_t[]){0x20}, 1, 0}, + {0x87, (uint8_t[]){0xBA}, 1, 0}, + {0x3B, (uint8_t[]){0x98}, 1, 0}, + + /**** CMD_Page 1 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0}, + {0x22, (uint8_t[]){0x0A}, 1, 0}, + {0x31, (uint8_t[]){0x00}, 1, 0}, + {0x50, (uint8_t[]){0x6B}, 1, 0}, + {0x51, (uint8_t[]){0x66}, 1, 0}, + {0x53, (uint8_t[]){0x73}, 1, 0}, + {0x55, (uint8_t[]){0x8B}, 1, 0}, + {0x60, (uint8_t[]){0x1B}, 1, 0}, + {0x61, (uint8_t[]){0x01}, 1, 0}, + {0x62, (uint8_t[]){0x0C}, 1, 0}, + {0x63, (uint8_t[]){0x00}, 1, 0}, + + // Gamma P + {0xA0, (uint8_t[]){0x00}, 1, 0}, + {0xA1, (uint8_t[]){0x15}, 1, 0}, + {0xA2, (uint8_t[]){0x1F}, 1, 0}, + {0xA3, (uint8_t[]){0x13}, 1, 0}, + {0xA4, (uint8_t[]){0x11}, 1, 0}, + {0xA5, (uint8_t[]){0x21}, 1, 0}, + {0xA6, (uint8_t[]){0x17}, 1, 0}, + {0xA7, (uint8_t[]){0x1B}, 1, 0}, + {0xA8, (uint8_t[]){0x6B}, 1, 0}, + {0xA9, (uint8_t[]){0x1E}, 1, 0}, + {0xAA, (uint8_t[]){0x2B}, 1, 0}, + {0xAB, (uint8_t[]){0x5D}, 1, 0}, + {0xAC, (uint8_t[]){0x19}, 1, 0}, + {0xAD, (uint8_t[]){0x14}, 1, 0}, + {0xAE, (uint8_t[]){0x4B}, 1, 0}, + {0xAF, (uint8_t[]){0x1D}, 1, 0}, + {0xB0, (uint8_t[]){0x27}, 1, 0}, + {0xB1, (uint8_t[]){0x49}, 1, 0}, + {0xB2, (uint8_t[]){0x5D}, 1, 0}, + {0xB3, (uint8_t[]){0x39}, 1, 0}, + + // Gamma N + {0xC0, (uint8_t[]){0x00}, 1, 0}, + {0xC1, (uint8_t[]){0x01}, 1, 0}, + {0xC2, (uint8_t[]){0x0C}, 1, 0}, + {0xC3, (uint8_t[]){0x11}, 1, 0}, + {0xC4, (uint8_t[]){0x15}, 1, 0}, + {0xC5, (uint8_t[]){0x28}, 1, 0}, + {0xC6, (uint8_t[]){0x1B}, 1, 0}, + {0xC7, (uint8_t[]){0x1C}, 1, 0}, + {0xC8, (uint8_t[]){0x62}, 1, 0}, + {0xC9, (uint8_t[]){0x1C}, 1, 0}, + {0xCA, (uint8_t[]){0x29}, 1, 0}, + {0xCB, (uint8_t[]){0x60}, 1, 0}, + {0xCC, (uint8_t[]){0x16}, 1, 0}, + {0xCD, (uint8_t[]){0x17}, 1, 0}, + {0xCE, (uint8_t[]){0x4A}, 1, 0}, + {0xCF, (uint8_t[]){0x23}, 1, 0}, + {0xD0, (uint8_t[]){0x24}, 1, 0}, + {0xD1, (uint8_t[]){0x4F}, 1, 0}, + {0xD2, (uint8_t[]){0x5F}, 1, 0}, + {0xD3, (uint8_t[]){0x39}, 1, 0}, + + /**** CMD_Page 0 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x00}, 3, 0}, + {0x35, (uint8_t[]){0x00}, 0, 0}, + // {0x11, (uint8_t []){0x00}, 0}, + {0xFE, (uint8_t[]){0x00}, 0, 0}, + {0x29, (uint8_t[]){0x00}, 0, 0}, + //============ Gamma END=========== +}; diff --git a/components/m5stack_tab5/include/bsp/m5stack_tab5.h b/components/m5stack_tab5/include/bsp/m5stack_tab5.h new file mode 100644 index 0000000..af53e2d --- /dev/null +++ b/components/m5stack_tab5/include/bsp/m5stack_tab5.h @@ -0,0 +1,436 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP BSP: ESP32-P4 Function EV Board + */ + +#pragma once + +#include "sdkconfig.h" +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/sdmmc_host.h" +#include "driver/i2s_std.h" +#include "driver/i2s_tdm.h" +#include "bsp/config.h" +#include "bsp/display.h" +#include "esp_codec_dev.h" +#include "sdkconfig.h" + +// LVGL includes removed for SDL-based build - no LVGL functions called + +/************************************************************************************************** + * BSP Capabilities + **************************************************************************************************/ +#define BSP_CAPS_DISPLAY 1 +#define BSP_CAPS_TOUCH 1 +#define BSP_CAPS_BUTTONS 0 +#define BSP_CAPS_AUDIO 1 +#define BSP_CAPS_AUDIO_SPEAKER 1 +#define BSP_CAPS_AUDIO_MIC 1 +#define BSP_CAPS_SDCARD 1 +#define BSP_CAPS_IMU 0 + +/************************************************************************************************** + * ESP-BOX pinout + **************************************************************************************************/ +/* SYS I2C */ +#define BSP_I2C_NUM 0 +#define BSP_I2C_SCL (GPIO_NUM_32) +#define BSP_I2C_SDA (GPIO_NUM_31) + +/* EXT I2C */ +#define BSP_EXT_I2C_NUM 1 +#define BSP_EXT_I2C_SCL (GPIO_NUM_54) +#define BSP_EXT_I2C_SDA (GPIO_NUM_53) + +// /* Ext Keyboard */ +// #define TAB5_TCA8418_INT_PIN 50 // 中断输入 + +/* Audio */ +#define BSP_I2S_SCLK (GPIO_NUM_27) // 位时钟 BSP_I2S_BCLK <--> ES7210/ESP311 I2S_BCLK +#define BSP_I2S_MCLK (GPIO_NUM_30) // 主时钟 BSP_I2S_MCLK <--> ES7210/ESP311 I2S_MCLK +#define BSP_I2S_LCLK (GPIO_NUM_29) // 字(声道)选择 BSP_I2S_WR <--> ES7210/ESP311 I2S_WR +#define BSP_I2S_DOUT (GPIO_NUM_26) // 数据输出 BSP_I2S_DOUT ---> ES8388 I2S_DSIN +#define BSP_I2S_DSIN (GPIO_NUM_28) // 数据输入 BSP_I2S_DIN <--- ES7210 I2S_DOUT +#define BSP_POWER_AMP_IO (GPIO_NUM_NC) // (GPIO_NUM_53) + +/* Display */ +#define BSP_LCD_BACKLIGHT (GPIO_NUM_22) +#define BSP_LCD_RST (GPIO_NUM_NC) // +#define BSP_LCD_TOUCH_RST (GPIO_NUM_NC) // IO Exanpder 控制 +#define BSP_LCD_TOUCH_INT (GPIO_NUM_NC) // 23 + +/* uSD card */ +#define BSP_SD_D0 (GPIO_NUM_39) +#define BSP_SD_D1 (GPIO_NUM_40) +#define BSP_SD_D2 (GPIO_NUM_41) +#define BSP_SD_D3 (GPIO_NUM_42) +#define BSP_SD_CMD (GPIO_NUM_44) +#define BSP_SD_CLK (GPIO_NUM_43) + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t bsp_cam_osc_init(void); + +/************************************************************************************************** + * + * I2C interface + * + * There are multiple devices connected to I2C peripheral: + * - Codec ES8311 (configuration only) + * - LCD Touch controller + **************************************************************************************************/ + +/** + * @brief Init I2C driver + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG I2C parameter error + * - ESP_FAIL I2C driver installation error + * + */ +esp_err_t bsp_i2c_init(void); + +/** + * @brief Deinit I2C driver and free its resources + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG I2C parameter error + * + */ +esp_err_t bsp_i2c_deinit(void); + +/** + * @brief Get I2C driver handle + * + * @return + * - I2C handle + * + */ +i2c_master_bus_handle_t bsp_i2c_get_handle(void); + +esp_err_t bsp_i2c_scan(); + +esp_err_t bsp_ext_i2c_init(void); +esp_err_t bsp_ext_i2c_deinit(void); +i2c_master_bus_handle_t bsp_ext_i2c_get_handle(void); + +esp_err_t bsp_grove_i2c_init(void); +esp_err_t bsp_grove_i2c_deinit(void); +i2c_master_bus_handle_t bsp_grove_i2c_get_handle(void); + +/************************************************************************************************** + * + * I2S audio interface + * + * There are two devices connected to the I2S peripheral: + * - Codec ES8311 for output(playback) and input(recording) path + * + * For speaker initialization use bsp_audio_codec_speaker_init() which is inside initialize I2S with bsp_audio_init(). + * For microphone initialization use bsp_audio_codec_microphone_init() which is inside initialize I2S with + *bsp_audio_init(). After speaker or microphone initialization, use functions from esp_codec_dev for play/record audio. + * Example audio play: + * \code{.c} + * esp_codec_dev_set_out_vol(spk_codec_dev, DEFAULT_VOLUME); + * esp_codec_dev_open(spk_codec_dev, &fs); + * esp_codec_dev_write(spk_codec_dev, wav_bytes, bytes_read_from_spiffs); + * esp_codec_dev_close(spk_codec_dev); + * \endcode + **************************************************************************************************/ + +/** + * @brief Init audio + * + * @note There is no deinit audio function. Users can free audio resources by calling i2s_del_channel() + * @warning The type of i2s_config param is depending on IDF version. + * @param[in] i2s_config I2S configuration. Pass NULL to use default values (Mono, duplex, 16bit, 22050 Hz) + * @return + * - ESP_OK On success + * - ESP_ERR_NOT_SUPPORTED The communication mode is not supported on the current chip + * - ESP_ERR_INVALID_ARG NULL pointer or invalid configuration + * - ESP_ERR_NOT_FOUND No available I2S channel found + * - ESP_ERR_NO_MEM No memory for storing the channel information + * - ESP_ERR_INVALID_STATE This channel has not initialized or already started + */ +esp_err_t bsp_audio_init(const i2s_std_config_t *i2s_config); + +/** + * @brief Initialize speaker codec device + * + * @return Pointer to codec device handle or NULL when error occurred + */ +esp_codec_dev_handle_t bsp_audio_codec_speaker_init(void); + +/** + * @brief Initialize microphone codec device + * + * @return Pointer to codec device handle or NULL when error occurred + */ +esp_codec_dev_handle_t bsp_audio_codec_microphone_init(void); + +typedef esp_err_t (*bsp_i2s_read_fn)(void *audio_buffer, size_t len, size_t *bytes_read, uint32_t timeout_ms); +typedef esp_err_t (*bsp_i2s_write_fn)(void *audio_buffer, size_t len, size_t *bytes_written, uint32_t timeout_ms); +typedef esp_err_t (*bsp_codec_set_in_gain_fn)(float gain); +typedef esp_err_t (*bsp_codec_mute_fn)(bool enable); +typedef int (*bsp_codec_volume_fn)(int volume); +typedef esp_err_t (*bsp_codec_get_volume_fn)(void); +typedef esp_err_t (*bsp_codec_reconfig_fn)(uint32_t rate, uint32_t bps, i2s_slot_mode_t ch); +typedef esp_err_t (*bsp_i2s_reconfig_clk_fn)(uint32_t rate, uint32_t bits_cfg, i2s_slot_mode_t ch); + +typedef struct { + bsp_i2s_read_fn i2s_read; + bsp_i2s_write_fn i2s_write; + bsp_codec_mute_fn set_mute; + bsp_codec_volume_fn set_volume; + bsp_codec_get_volume_fn get_volume; + bsp_codec_set_in_gain_fn set_in_gain; + bsp_codec_reconfig_fn codec_reconfig_fn; + bsp_i2s_reconfig_clk_fn i2s_reconfig_clk_fn; +} bsp_codec_config_t; + +void bsp_codec_init(void); +bsp_codec_config_t *bsp_get_codec_handle(void); +uint8_t bsp_codec_feed_channel(void); + +/************************************************************************************************** + * + * SPIFFS + * + * After mounting the SPIFFS, it can be accessed with stdio functions ie.: + * \code{.c} + * FILE* f = fopen(BSP_SPIFFS_MOUNT_POINT"/hello.txt", "w"); + * fprintf(f, "Hello World!\n"); + * fclose(f); + * \endcode + **************************************************************************************************/ +#define BSP_SPIFFS_MOUNT_POINT CONFIG_BSP_SPIFFS_MOUNT_POINT + +/** + * @brief Mount SPIFFS to virtual file system + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if esp_vfs_spiffs_register was already called + * - ESP_ERR_NO_MEM if memory can not be allocated + * - ESP_FAIL if partition can not be mounted + * - other error codes + */ +esp_err_t bsp_spiffs_mount(void); + +/** + * @brief Unmount SPIFFS from virtual file system + * + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_FOUND if the partition table does not contain SPIFFS partition with given label + * - ESP_ERR_INVALID_STATE if esp_vfs_spiffs_unregister was already called + * - ESP_ERR_NO_MEM if memory can not be allocated + * - ESP_FAIL if partition can not be mounted + * - other error codes + */ +esp_err_t bsp_spiffs_unmount(void); + +/************************************************************************************************** + * + * uSD card + * + * After mounting the uSD card, it can be accessed with stdio functions ie.: + * \code{.c} + * FILE* f = fopen(BSP_MOUNT_POINT"/hello.txt", "w"); + * fprintf(f, "Hello %s!\n", bsp_sdcard->cid.name); + * fclose(f); + * \endcode + **************************************************************************************************/ +/** + * @brief Init SD crad + * + * @param mount_point Path where partition should be registered (e.g. "/sdcard") + * @param max_files Maximum number of files which can be open at the same time + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE If esp_vfs_fat_register was already called + * - ESP_ERR_NOT_SUPPORTED If dev board not has SDMMC/SDSPI + * - ESP_ERR_NO_MEM If not enough memory or too many VFSes already registered + * - Others Fail + */ +esp_err_t bsp_sdcard_init(char *mount_point, size_t max_files); + +/** + * @brief Deinit SD card + * + * @param mount_point Path where partition was registered (e.g. "/sdcard") + * @return + * - ESP_OK: Success + * - Others: Fail + */ +esp_err_t bsp_sdcard_deinit(char *mount_point); + +/************************************************************************************************** + * + * LCD interface + * + * ESP-BOX is shipped with 2.4inch ST7789 display controller. + * It features 16-bit colors, 320x240 resolution and capacitive touch controller. + * + * LVGL is used as graphics library. LVGL is NOT thread safe, therefore the user must take LVGL mutex + * by calling bsp_display_lock() before calling and LVGL API (lv_...) and then give the mutex with + * bsp_display_unlock(). + * + * Display's backlight must be enabled explicitly by calling bsp_display_backlight_on() + **************************************************************************************************/ +#define BSP_LCD_PIXEL_CLOCK_MHZ (80) + +#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0) + +#define BSP_LCD_DRAW_BUFF_SIZE (BSP_LCD_H_RES * 50) // Frame buffer size in pixels +#define BSP_LCD_DRAW_BUFF_DOUBLE (0) + +/** + * @brief BSP display configuration structure + * + */ +typedef struct { + lvgl_port_cfg_t lvgl_port_cfg; /*!< LVGL port configuration */ + uint32_t buffer_size; /*!< Size of the buffer for the screen in pixels */ + bool double_buffer; /*!< True, if should be allocated two buffers */ + struct { + unsigned int buff_dma : 1; /*!< Allocated LVGL buffer will be DMA capable */ + unsigned int buff_spiram : 1; /*!< Allocated LVGL buffer will be in PSRAM */ + unsigned int + sw_rotate : 1; /*!< Use software rotation (slower), The feature is unavailable under avoid-tear mode */ + } flags; +} bsp_display_cfg_t; + +/** + * @brief Initialize display + * + * This function initializes SPI, display controller and starts LVGL handling task. + * LCD backlight must be enabled separately by calling bsp_display_brightness_set() + * + * @return Pointer to LVGL display or NULL when error occured + */ +lv_display_t *bsp_display_start(void); + +/** + * @brief Initialize display + * + * This function initializes SPI, display controller and starts LVGL handling task. + * LCD backlight must be enabled separately by calling bsp_display_brightness_set() + * + * @param cfg display configuration + * + * @return Pointer to LVGL display or NULL when error occured + */ +lv_display_t *bsp_display_start_with_config(const bsp_display_cfg_t *cfg); + +/** + * @brief Get pointer to input device (touch, buttons, ...) + * + * @note The LVGL input device is initialized in bsp_display_start() function. + * + * @return Pointer to LVGL input device or NULL when not initialized + */ +lv_indev_t *bsp_display_get_input_dev(void); + +/** + * @brief Take LVGL mutex + * + * @param timeout_ms Timeout in [ms]. 0 will block indefinitely. + * @return true Mutex was taken + * @return false Mutex was NOT taken + */ +bool bsp_display_lock(uint32_t timeout_ms); + +/** + * @brief Give LVGL mutex + * + */ +void bsp_display_unlock(void); + +/** + * @brief Rotate screen + * + * Display must be already initialized by calling bsp_display_start() + * + * @param[in] disp Pointer to LVGL display + * @param[in] rotation Angle of the display rotation + */ +void bsp_display_rotate(lv_display_t *disp, lv_disp_rotation_t rotation); +#endif // BSP_CONFIG_NO_GRAPHIC_LIB == 0 + +void bsp_io_expander_pi4ioe_init(i2c_master_bus_handle_t bus_handle); + +void bsp_set_charge_qc_en(bool en); + +void bsp_set_charge_en(bool en); + +void bsp_set_usb_5v_en(bool en); + +void bsp_set_ext_5v_en(bool en); + +void bsp_generate_poweroff_signal(); + +bool bsp_headphone_detect(); + +void bsp_set_ext_antenna_enable(bool en); + +void bsp_set_wifi_power_enable(bool en); + +void bsp_reset_tp(); + +bool bsp_usb_c_detect(); + +bool bsp_usb_a_detect(); + +/************************************************************************************************** + * + * USB + * + **************************************************************************************************/ + +/** + * @brief Power modes of USB Host connector + */ +typedef enum bsp_usb_host_power_mode_t { + BSP_USB_HOST_POWER_MODE_USB_DEV, //!< Power from USB DEV port +} bsp_usb_host_power_mode_t; + +/** + * @brief Start USB host + * + * This is a one-stop-shop function that will configure the board for USB Host mode + * and start USB Host library + * + * @param[in] mode USB Host connector power mode (Not used on this board) + * @param[in] limit_500mA Limit output current to 500mA (Not used on this board) + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NO_MEM Memory cannot be allocated + */ +esp_err_t bsp_usb_host_start(bsp_usb_host_power_mode_t mode, bool limit_500mA); + +/** + * @brief Stop USB host + * + * USB Host lib will be uninstalled and power from connector removed. + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t bsp_usb_host_stop(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/m5stack_tab5/include/bsp/touch.h b/components/m5stack_tab5/include/bsp/touch.h new file mode 100644 index 0000000..fb9e019 --- /dev/null +++ b/components/m5stack_tab5/include/bsp/touch.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief BSP Touchscreen + * + * This file offers API for basic touchscreen initialization. + * It is useful for users who want to use the touchscreen without the default Graphical Library LVGL. + * + * For standard LCD initialization with LVGL graphical library, you can call all-in-one function bsp_display_start(). + */ + +#pragma once +#include "esp_lcd_touch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief BSP touch configuration structure + * + */ +typedef struct { + void *dummy; /*!< Prepared for future use. */ +} bsp_touch_config_t; + +/** + * @brief Create new touchscreen + * + * If you want to free resources allocated by this function, you can use esp_lcd_touch API, ie.: + * + * \code{.c} + * esp_lcd_touch_del(tp); + * \endcode + * + * @param[in] config touch configuration + * @param[out] ret_touch esp_lcd_touch touchscreen handle + * @return + * - ESP_OK On success + * - Else esp_lcd_touch failure + */ +esp_err_t bsp_touch_new(const bsp_touch_config_t *config, esp_lcd_touch_handle_t *ret_touch); + +#ifdef __cplusplus +} +#endif diff --git a/components/m5stack_tab5/m5stack_tab5.c b/components/m5stack_tab5/m5stack_tab5.c new file mode 100644 index 0000000..fd8fab5 --- /dev/null +++ b/components/m5stack_tab5/m5stack_tab5.c @@ -0,0 +1,1454 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "driver/gpio.h" +#include "driver/ledc.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_spiffs.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_ldo_regulator.h" +#include "esp_vfs_fat.h" +#include "usb/usb_host.h" +#include "sd_pwr_ctrl_by_on_chip_ldo.h" +#include "freertos/task.h" +#include "sdmmc_cmd.h" +#include "esp_lcd_st7703.h" +#include "esp_lcd_ili9881c.h" +#include "bsp/m5stack_tab5.h" +#include "bsp/display.h" +#include "bsp/touch.h" +#include "esp_lcd_touch_gt911.h" +#include "bsp_err_check.h" +#include "esp_codec_dev_defaults.h" + +static const char* TAG = "M5STACK_TAB5"; + +#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0) +static lv_indev_t* disp_indev = NULL; +#endif // (BSP_CONFIG_NO_GRAPHIC_LIB == 0) + +// Global uSD card handler +sdmmc_card_t* bsp_sdcard = NULL; + +// USB Host Library task +static TaskHandle_t usb_host_task; + +// sys i2c +static bool i2c_initialized = false; +static i2c_master_bus_handle_t i2c_handle = NULL; +// ext i2c +static bool ext_i2c_initialized = false; +static i2c_master_bus_handle_t ext_i2c_bus_handle = NULL; +// grove i2c +static bool grove_i2c_initialized = false; +static i2c_master_bus_handle_t grove_i2c_bus_handle = NULL; +// i2s +static i2s_chan_handle_t i2s_tx_chan = NULL; +static i2s_chan_handle_t i2s_rx_chan = NULL; +static const audio_codec_data_if_t* i2s_data_if = NULL; /* Codec data interface */ + +//================================================================================== +// camera 设置输出时钟 +//================================================================================== + +esp_err_t bsp_cam_osc_init(void) +{ + ledc_timer_config_t timer_conf; + timer_conf.duty_resolution = LEDC_TIMER_1_BIT; + timer_conf.freq_hz = 24000000; // <<<< change this to the frequency you want + timer_conf.speed_mode = LEDC_LOW_SPEED_MODE; + timer_conf.deconfigure = false; + timer_conf.clk_cfg = LEDC_AUTO_CLK; + timer_conf.timer_num = LEDC_TIMER_0; + esp_err_t err = ledc_timer_config(&timer_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_timer_config failed for freq %d, rc=%x", 24000000, err); + } + + ledc_channel_config_t ch_conf; + ch_conf.gpio_num = 36; // 摄像头时钟输入 + ch_conf.speed_mode = LEDC_LOW_SPEED_MODE; + ch_conf.channel = LEDC_CHANNEL_0; + ch_conf.intr_type = LEDC_INTR_DISABLE; + ch_conf.timer_sel = LEDC_TIMER_0; + ch_conf.duty = 1; + ch_conf.hpoint = 0; + ch_conf.sleep_mode = LEDC_SLEEP_MODE_KEEP_ALIVE; + err = ledc_channel_config(&ch_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_channel_config failed, rc=%x", err); + } + + return ESP_OK; +} + +//================================================================================== +// i2c +//================================================================================== +esp_err_t bsp_i2c_init(void) +{ + /* I2C was initialized before */ + if (i2c_initialized) { + return ESP_OK; + } + + i2c_master_bus_config_t i2c_bus_conf = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .sda_io_num = BSP_I2C_SDA, + .scl_io_num = BSP_I2C_SCL, + .i2c_port = BSP_I2C_NUM, + .flags.enable_internal_pullup = true, + }; + BSP_ERROR_CHECK_RETURN_ERR(i2c_new_master_bus(&i2c_bus_conf, &i2c_handle)); + + i2c_initialized = true; + + return ESP_OK; +} + +esp_err_t bsp_i2c_deinit(void) +{ + BSP_ERROR_CHECK_RETURN_ERR(i2c_del_master_bus(i2c_handle)); + i2c_initialized = false; + return ESP_OK; +} + +i2c_master_bus_handle_t bsp_i2c_get_handle(void) +{ + return i2c_handle; +} + +esp_err_t bsp_ext_i2c_init(void) +{ + if (ext_i2c_initialized) { + return ESP_OK; + } + + i2c_master_bus_config_t i2c_mst_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = BSP_EXT_I2C_NUM, + .scl_io_num = BSP_EXT_I2C_SCL, + .sda_io_num = BSP_EXT_I2C_SDA, + .flags.enable_internal_pullup = true, + }; + i2c_new_master_bus(&i2c_mst_config, &ext_i2c_bus_handle); + + ext_i2c_initialized = true; + + return ESP_OK; +} + +esp_err_t bsp_ext_i2c_deinit(void) +{ + ext_i2c_initialized = false; + return i2c_del_master_bus(ext_i2c_bus_handle); +} + +i2c_master_bus_handle_t bsp_ext_i2c_get_handle(void) +{ + return ext_i2c_bus_handle; +} + +esp_err_t bsp_grove_i2c_init(void) +{ + if (grove_i2c_initialized) { + return ESP_OK; + } + + i2c_master_bus_config_t i2c_mst_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = BSP_EXT_I2C_NUM, + .scl_io_num = 54, // BSP_EXT_I2C_SCL, + .sda_io_num = 53, // BSP_EXT_I2C_SDA, + .flags.enable_internal_pullup = true, + }; + i2c_new_master_bus(&i2c_mst_config, &grove_i2c_bus_handle); + + grove_i2c_initialized = true; + + return ESP_OK; +} + +esp_err_t bsp_grove_i2c_deinit(void) +{ + grove_i2c_initialized = false; + return i2c_del_master_bus(grove_i2c_bus_handle); +} + +i2c_master_bus_handle_t bsp_grove_i2c_get_handle(void) +{ + return grove_i2c_bus_handle; +} + +esp_err_t bsp_i2c_scan() +{ + esp_err_t ret; + uint8_t address; + + printf("scan i2c device\n"); + printf("\n 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + ret = i2c_master_probe(i2c_handle, address, 50); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + printf("\nscan i2c device finished\n"); + + return ESP_OK; +} + +//================================================================================== +// I/O Exapnder PI4IOE5V6416 +//================================================================================== +#define I2C_DEV_ADDR_PI4IOE1 0x43 // addr pin low +#define I2C_DEV_ADDR_PI4IOE2 0x44 // addr pin high +#define I2C_MASTER_TIMEOUT_MS 50 + +static i2c_master_dev_handle_t i2c_dev_handle_pi4ioe1; +static i2c_master_dev_handle_t i2c_dev_handle_pi4ioe2; + +// PI4IO registers +#define PI4IO_REG_CHIP_RESET 0x01 +#define PI4IO_REG_IO_DIR 0x03 +#define PI4IO_REG_OUT_SET 0x05 +#define PI4IO_REG_OUT_H_IM 0x07 +#define PI4IO_REG_IN_DEF_STA 0x09 +#define PI4IO_REG_PULL_EN 0x0B +#define PI4IO_REG_PULL_SEL 0x0D +#define PI4IO_REG_IN_STA 0x0F +#define PI4IO_REG_INT_MASK 0x11 +#define PI4IO_REG_IRQ_STA 0x13 + +#define setbit(x, y) x |= (0x01 << y) +#define clrbit(x, y) x &= ~(0x01 << y) + +void bsp_io_expander_pi4ioe_init(i2c_master_bus_handle_t bus_handle) +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + /* */ + i2c_device_config_t dev_cfg1 = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = I2C_DEV_ADDR_PI4IOE1, + .scl_speed_hz = 400000, + }; + ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg1, &i2c_dev_handle_pi4ioe1)); + + write_buf[0] = PI4IO_REG_CHIP_RESET; + write_buf[1] = 0xFF; + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, I2C_MASTER_TIMEOUT_MS); + write_buf[0] = PI4IO_REG_CHIP_RESET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe1, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + write_buf[0] = PI4IO_REG_IO_DIR; + write_buf[1] = 0b01111111; + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, I2C_MASTER_TIMEOUT_MS); // 0: input 1: output + write_buf[0] = PI4IO_REG_OUT_H_IM; + write_buf[1] = 0b00000000; + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, + I2C_MASTER_TIMEOUT_MS); // 使用到的引脚关闭 High-Impedance + write_buf[0] = PI4IO_REG_PULL_SEL; + write_buf[1] = 0b01111111; + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, + I2C_MASTER_TIMEOUT_MS); // pull up/down select, 0 down, 1 up + write_buf[0] = PI4IO_REG_PULL_EN; + write_buf[1] = 0b01111111; + + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, + I2C_MASTER_TIMEOUT_MS); // P7 中断使能 0 enable, 1 disable + /* Output Port Register P1(SPK_EN), P2(EXT5V_EN), P4(LCD_RST), P5(TP_RST), P6(CAM)RST 输出高电平 */ + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = 0b01110110; + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, I2C_MASTER_TIMEOUT_MS); + + /* */ + i2c_device_config_t dev_cfg2 = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = I2C_DEV_ADDR_PI4IOE2, + .scl_speed_hz = 400000, + }; + ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg2, &i2c_dev_handle_pi4ioe2)); + + write_buf[0] = PI4IO_REG_CHIP_RESET; + write_buf[1] = 0xFF; + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); + write_buf[0] = PI4IO_REG_CHIP_RESET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe2, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + write_buf[0] = PI4IO_REG_IO_DIR; + write_buf[1] = 0b10111001; + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); // 0: input 1: output + write_buf[0] = PI4IO_REG_OUT_H_IM; + write_buf[1] = 0b00000110; + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, + I2C_MASTER_TIMEOUT_MS); // 使用到的引脚关闭 High-Impedance + write_buf[0] = PI4IO_REG_PULL_SEL; + write_buf[1] = 0b10111001; + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, + I2C_MASTER_TIMEOUT_MS); // pull up/down select, 0 down, 1 up + write_buf[0] = PI4IO_REG_PULL_EN; + write_buf[1] = 0b11111001; + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, + I2C_MASTER_TIMEOUT_MS); // pull up/down enable, 0 disable, 1 enable + write_buf[0] = PI4IO_REG_IN_DEF_STA; + write_buf[1] = 0b01000000; + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); // P6 默认高电平 + write_buf[0] = PI4IO_REG_INT_MASK; + write_buf[1] = 0b10111111; + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, + I2C_MASTER_TIMEOUT_MS); // P6 中断使能 0 enable, 1 disable + /* Output Port Register P0(WLAN_PWR_EN), P3(USB5V_EN), P7(CHG_EN) 输出高电平 */ + write_buf[0] = PI4IO_REG_OUT_SET; + // write_buf[1] = 0b10001001; + write_buf[1] = 0b00001001; + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); +} + +void bsp_set_charge_qc_en(bool en) +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe2, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = read_buf[0]; + if (en) { + clrbit(write_buf[1], 5); + } else { + setbit(write_buf[1], 5); + } + + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); +} + +void bsp_set_charge_en(bool en) +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe2, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = read_buf[0]; + if (en) { + setbit(write_buf[1], 7); + } else { + clrbit(write_buf[1], 7); + } + + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); +} + +void bsp_set_usb_5v_en(bool en) +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe2, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = read_buf[0]; + if (en) { + setbit(write_buf[1], 3); + } else { + clrbit(write_buf[1], 3); + } + + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); +} + +void bsp_set_ext_5v_en(bool en) +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe1, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = read_buf[0]; + if (en) { + setbit(write_buf[1], 2); + } else { + clrbit(write_buf[1], 2); + } + + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, I2C_MASTER_TIMEOUT_MS); +} + +void bsp_generate_poweroff_signal() +{ + ESP_LOGW(TAG, "Generate poweroff signal!"); + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe2, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + write_buf[1] = read_buf[0]; + + // Try to generate poweroff signal 3 times to make sure it works :) + for (int i = 0; i < 3; i++) { + setbit(write_buf[1], 4); + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); + vTaskDelay(100 / portTICK_PERIOD_MS); + + clrbit(write_buf[1], 4); + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); + vTaskDelay(100 / portTICK_PERIOD_MS); + } +} + +bool bsp_headphone_detect() +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_IN_STA; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe1, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + // printf("get %02x\n", read_buf[0]); + + // Get bit 8 + bool ret = false; + if (read_buf[0] & 0b10000000) { + ret = true; + } + + return ret; +} + +bool bsp_usb_c_detect() +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_IN_STA; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe2, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + // printf("get %02x\n", read_buf[0]); + + // Get bit 6 + bool ret = false; + if (read_buf[0] & 0b01000000) { + ret = true; + } + + return ret; +} + +void bsp_set_ext_antenna_enable(bool en) +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe1, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = read_buf[0]; + if (en) { + setbit(write_buf[1], 0); + } else { + clrbit(write_buf[1], 0); + } + + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, I2C_MASTER_TIMEOUT_MS); +} + +void bsp_set_wifi_power_enable(bool en) +{ + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + ESP_LOGI(TAG, "set_wifi_power_enable: %d", en); + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe2, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = read_buf[0]; + if (en) { + setbit(write_buf[1], 0); + } else { + clrbit(write_buf[1], 0); + } + + i2c_master_transmit(i2c_dev_handle_pi4ioe2, write_buf, 2, I2C_MASTER_TIMEOUT_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe2, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + printf("0x%02X: %02x\n", PI4IO_REG_OUT_SET, read_buf[0]); +} + +void bsp_reset_tp() +{ + ESP_LOGI(TAG, "reset tp"); + + ESP_LOGI(TAG, "reset gpio %d", GPIO_NUM_23); + gpio_reset_pin(GPIO_NUM_23); + + uint8_t write_buf[2] = {0}; + uint8_t read_buf[1] = {0}; + + write_buf[0] = PI4IO_REG_OUT_SET; + i2c_master_transmit_receive(i2c_dev_handle_pi4ioe1, write_buf, 1, read_buf, 1, I2C_MASTER_TIMEOUT_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = read_buf[0]; + clrbit(write_buf[1], 4); + clrbit(write_buf[1], 5); + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, I2C_MASTER_TIMEOUT_MS); + vTaskDelay(100 / portTICK_PERIOD_MS); + + write_buf[0] = PI4IO_REG_OUT_SET; + write_buf[1] = read_buf[0]; + setbit(write_buf[1], 4); + setbit(write_buf[1], 5); + i2c_master_transmit(i2c_dev_handle_pi4ioe1, write_buf, 2, I2C_MASTER_TIMEOUT_MS); + vTaskDelay(100 / portTICK_PERIOD_MS); +} + +//================================================================================== +// sd card +//================================================================================== +#define BSP_LDO_PROBE_SD_CHAN 4 +#define BSP_LDO_PROBE_SD_VOLTAGE_MV 3300 + +#define SDMMC_BUS_WIDTH (4) // SDIO 4 线模式 +#define GPIO_SDMMC_DET (GPIO_NUM_NC) // SDIO 卡检测 +// M5Stack-Tab5-P4 +#define GPIO_SDMMC_CLK (GPIO_NUM_43) // SDIO 时钟 +#define GPIO_SDMMC_CMD (GPIO_NUM_44) // SDIO 命令 +#define GPIO_SDMMC_D0 (GPIO_NUM_39) // SDIO 数据 0 +#define GPIO_SDMMC_D1 (GPIO_NUM_40) // SDIO 数据 1 +#define GPIO_SDMMC_D2 (GPIO_NUM_41) // SDIO 数据 2 +#define GPIO_SDMMC_D3 (GPIO_NUM_42) // SDIO 数据 3 + +static sdmmc_card_t* card; + +esp_err_t bsp_sdcard_init(char* mount_point, size_t max_files) +{ + esp_err_t ret_val = ESP_OK; + + if (NULL != card) { + return ESP_ERR_INVALID_STATE; + } + + /** + * @brief Use settings defined above to initialize SD card and mount FAT filesystem. + * Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions. + * Please check its source code and implement error recovery when developing + * production applications. + * + */ + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.slot = SDMMC_HOST_SLOT_0; // + // host.slot = SDMMC_HOST_SLOT_1; // + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sd_pwr_ctrl_ldo_config_t ldo_config = { + .ldo_chan_id = BSP_LDO_PROBE_SD_CHAN, // `LDO_VO4` is used as the SDMMC IO power + }; + static sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL; + + if (pwr_ctrl_handle == NULL) { + ret_val = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle); + if (ret_val != ESP_OK) { + ESP_LOGE(TAG, "Failed to new an on-chip ldo power control driver"); + return ret_val; + } + } + host.pwr_ctrl_handle = pwr_ctrl_handle; + + /** + * @brief This initializes the slot without card detect (CD) and write protect (WP) signals. + * Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. + * + */ + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.width = SDMMC_BUS_WIDTH; + slot_config.clk = GPIO_SDMMC_CLK; + slot_config.cmd = GPIO_SDMMC_CMD; + slot_config.d0 = GPIO_SDMMC_D0; + slot_config.d1 = GPIO_SDMMC_D1; + slot_config.d2 = GPIO_SDMMC_D2; + slot_config.d3 = GPIO_SDMMC_D3; + // slot_config.cd = GPIO_SDMMC_DET; + // slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; + + /** + * @brief Options for mounting the filesystem. + * If format_if_mount_failed is set to true, SD card will be partitioned and + * formatted in case when mounting fails. + */ + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, .max_files = max_files, .allocation_unit_size = 16 * 1024}; + + ret_val = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card); + + /* Check for SDMMC mount result. */ + if (ret_val != ESP_OK) { + if (ret_val == ESP_FAIL) { + ESP_LOGE(TAG, + "Failed to mount filesystem. " + "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option."); + } else { + ESP_LOGE(TAG, + "Failed to initialize the card (%s). " + "Make sure SD card lines have pull-up resistors in place.", + esp_err_to_name(ret_val)); + } + return ret_val; + } + + /* Card has been initialized, print its properties. */ + sdmmc_card_print_info(stdout, card); + + return ret_val; +} + +esp_err_t bsp_sdcard_deinit(char* mount_point) +{ + if (mount_point == NULL) { + return ESP_ERR_INVALID_STATE; + } + + /* Unmount an SD card from the FAT filesystem and release resources acquired */ + esp_err_t ret_val = esp_vfs_fat_sdcard_unmount(mount_point, card); + + // ret_val = sd_pwr_ctrl_del_on_chip_ldo(card->host.pwr_ctrl_handle); + // if (ret_val != ESP_OK) { + // ESP_LOGE(TAG, "Failed to delete on-chip ldo power control driver"); + // } + + /* Make SD/MMC card information structure pointer NULL */ + card = NULL; + + return ret_val; +} + +//================================================================================== +// spiffs +//================================================================================== +esp_err_t bsp_spiffs_mount(void) +{ + esp_vfs_spiffs_conf_t conf = { + .base_path = CONFIG_BSP_SPIFFS_MOUNT_POINT, + .partition_label = CONFIG_BSP_SPIFFS_PARTITION_LABEL, + .max_files = CONFIG_BSP_SPIFFS_MAX_FILES, +#ifdef CONFIG_BSP_SPIFFS_FORMAT_ON_MOUNT_FAIL + .format_if_mount_failed = true, +#else + .format_if_mount_failed = false, +#endif + }; + + esp_err_t ret_val = esp_vfs_spiffs_register(&conf); + + BSP_ERROR_CHECK_RETURN_ERR(ret_val); + + size_t total = 0, used = 0; + ret_val = esp_spiffs_info(conf.partition_label, &total, &used); + if (ret_val != ESP_OK) { + ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret_val)); + } else { + ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); + } + + return ret_val; +} + +esp_err_t bsp_spiffs_unmount(void) +{ + return esp_vfs_spiffs_unregister(CONFIG_BSP_SPIFFS_PARTITION_LABEL); +} + +//================================================================================== +// audio es7210 + es8388 +//================================================================================== +static esp_codec_dev_handle_t play_dev_handle; +static esp_codec_dev_handle_t record_dev_handle; +static bsp_codec_config_t g_codec_handle; +static int volume; + +/* Can be used for `i2s_std_gpio_config_t` and/or `i2s_std_config_t` initialization */ +#define BSP_I2S_GPIO_CFG \ + { \ + .mclk = BSP_I2S_MCLK, .bclk = BSP_I2S_SCLK, .ws = BSP_I2S_LCLK, .dout = BSP_I2S_DOUT, .din = BSP_I2S_DSIN, \ + .invert_flags = { \ + .mclk_inv = false, \ + .bclk_inv = false, \ + .ws_inv = false, \ + }, \ + } + +/* This configuration is used by default in `bsp_extra_audio_init()` */ +#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate) \ + { \ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \ + .gpio_cfg = BSP_I2S_GPIO_CFG, \ + } + +esp_err_t bsp_audio_init(const i2s_std_config_t* i2s_config) +{ + if (i2s_tx_chan && i2s_rx_chan) { + /* Audio was initialized before */ + return ESP_OK; + } + + /* Setup I2S peripheral */ + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(CONFIG_BSP_I2S_NUM, I2S_ROLE_MASTER); + chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &i2s_tx_chan, &i2s_rx_chan)); + + /* Setup I2S channels */ + // const i2s_std_config_t std_cfg_default = BSP_I2S_DUPLEX_MONO_CFG(16000); + const i2s_std_config_t std_cfg_default = BSP_I2S_DUPLEX_MONO_CFG(48000); + const i2s_std_config_t* p_i2s_cfg = &std_cfg_default; + if (i2s_config != NULL) { + p_i2s_cfg = i2s_config; + } + + if (i2s_tx_chan != NULL) { + ESP_ERROR_CHECK(i2s_channel_init_std_mode(i2s_tx_chan, p_i2s_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(i2s_tx_chan)); + } + + // if (i2s_rx_chan != NULL) { + // ESP_ERROR_CHECK(i2s_channel_init_std_mode(i2s_rx_chan, p_i2s_cfg)); + // ESP_ERROR_CHECK(i2s_channel_enable(i2s_rx_chan)); + // } + + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = + { + .sample_rate_hz = (uint32_t)48000, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bclk_div = 8, + }, + .slot_cfg = {.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = (I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .ws_width = I2S_TDM_AUTO_WS_WIDTH, + .ws_pol = false, + .bit_shift = true, + .left_align = false, + .big_endian = false, + .bit_order_lsb = false, + .skip_mask = false, + .total_slot = I2S_TDM_AUTO_SLOT_NUM}, + .gpio_cfg = BSP_I2S_GPIO_CFG, + }; + + if (i2s_rx_chan != NULL) { + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(i2s_rx_chan, &tdm_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(i2s_rx_chan)); + } + + audio_codec_i2s_cfg_t i2s_cfg = { + .port = CONFIG_BSP_I2S_NUM, + .tx_handle = i2s_tx_chan, + .rx_handle = i2s_rx_chan, + }; + i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg); + + return ESP_OK; +} + +esp_codec_dev_handle_t bsp_audio_codec_speaker_init(void) +{ + static esp_codec_dev_handle_t codec = NULL; + if (codec) { + return codec; + } + + if (i2s_data_if == NULL) { + /* Initilize I2C */ + bsp_i2c_init(); + /* Configure I2S peripheral and Power Amplifier */ + bsp_audio_init(NULL); + } + assert(i2s_data_if); + + const audio_codec_gpio_if_t* gpio_if = audio_codec_new_gpio(); + + i2c_master_bus_handle_t i2c_bus_handle = bsp_i2c_get_handle(); + audio_codec_i2c_cfg_t i2c_cfg = { + .port = BSP_I2C_NUM, + .addr = ES8388_CODEC_DEFAULT_ADDR, + .bus_handle = i2c_bus_handle, + }; + const audio_codec_ctrl_if_t* i2c_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg); + BSP_NULL_CHECK(i2c_ctrl_if, NULL); + + esp_codec_dev_hw_gain_t gain = { + .pa_voltage = 5.0, + .codec_dac_voltage = 3.3, + }; + + es8388_codec_cfg_t es8388_cfg = { + .codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC, + .master_mode = false, + .ctrl_if = i2c_ctrl_if, + .pa_pin = -1, // PI4IOE1 P1 控制 + }; + const audio_codec_if_t* es8388_dev = es8388_codec_new(&es8388_cfg); + BSP_NULL_CHECK(es8388_dev, NULL); + + esp_codec_dev_cfg_t codec_dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = es8388_dev, + .data_if = i2s_data_if, + }; + codec = esp_codec_dev_new(&codec_dev_cfg); + BSP_NULL_CHECK(codec, NULL); + + return codec; +} + +esp_codec_dev_handle_t bsp_audio_codec_microphone_init(void) +{ + if (i2s_data_if == NULL) { + /* Initilize I2C */ + ESP_ERROR_CHECK(bsp_i2c_init()); + /* Configure I2S peripheral and Power Amplifier */ + ESP_ERROR_CHECK(bsp_audio_init(NULL)); + // i2s_data_if = bsp_get_codec_data_if(); + } + assert(i2s_data_if); + + i2c_master_bus_handle_t i2c_bus_handle = bsp_i2c_get_handle(); + audio_codec_i2c_cfg_t i2c_cfg = { + .port = BSP_I2C_NUM, + .addr = ES7210_CODEC_DEFAULT_ADDR, + .bus_handle = i2c_bus_handle, + }; + const audio_codec_ctrl_if_t* i2c_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg); + BSP_NULL_CHECK(i2c_ctrl_if, NULL); + + es7210_codec_cfg_t es7210_cfg = { + .ctrl_if = i2c_ctrl_if, // Codec Control interface + }; + es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4; + const audio_codec_if_t* es7210_dev = es7210_codec_new(&es7210_cfg); + BSP_NULL_CHECK(es7210_dev, NULL); + + esp_codec_dev_cfg_t codec_es7210_dev_cfg = { + .dev_type = + ESP_CODEC_DEV_TYPE_IN, // Codec device type: Codec input device like ADC (capture data from microphone) + .codec_if = es7210_dev, // Codec interface + .data_if = i2s_data_if, // Codec data interface + }; + + return esp_codec_dev_new(&codec_es7210_dev_cfg); +} + +static esp_err_t bsp_i2s_read(void* audio_buffer, size_t len, size_t* bytes_read, uint32_t timeout_ms) +{ + esp_err_t ret = ESP_OK; + ret = esp_codec_dev_read(record_dev_handle, audio_buffer, len); + *bytes_read = len; + return ret; +} + +static esp_err_t bsp_i2s_write(void* audio_buffer, size_t len, size_t* bytes_written, uint32_t timeout_ms) +{ + esp_err_t ret = ESP_OK; + ret = esp_codec_dev_write(play_dev_handle, audio_buffer, len); + *bytes_written = len; + return ret; +} + +static esp_err_t bsp_codec_set_in_gain(float gain) +{ + return esp_codec_dev_set_in_gain(record_dev_handle, gain); +} + +static esp_err_t bsp_codec_set_mute(bool enable) +{ + esp_err_t ret = ESP_OK; + ret = esp_codec_dev_set_out_mute(play_dev_handle, enable); + return ret; +} + +static esp_err_t bsp_codec_set_volume(int v) +{ + esp_err_t ret = ESP_OK; + + if (v <= 0) { + volume = 0; + ret = esp_codec_dev_set_out_mute(play_dev_handle, true); + } else { + volume = v; + ret = esp_codec_dev_set_out_mute(play_dev_handle, false); + ret |= esp_codec_dev_set_out_vol(play_dev_handle, volume); + } + + return ret; +} + +static int bsp_codec_get_volume(void) +{ + return volume; +} + +bsp_codec_config_t* bsp_get_codec_handle(void) +{ + return &g_codec_handle; +} + +static esp_err_t bsp_codec_es8388_set(uint32_t rate, uint32_t bps, i2s_slot_mode_t ch) +{ + esp_err_t ret = ESP_OK; + + esp_codec_dev_sample_info_t fs = { + .sample_rate = rate, + .channel = ch, + .bits_per_sample = bps, + }; + + if (play_dev_handle) { + ret = esp_codec_dev_close(play_dev_handle); + } + ret = esp_codec_dev_open(play_dev_handle, &fs); + + return ret; +} + +static esp_err_t bsp_codec_es7210_set(uint32_t rate, uint32_t bps, i2s_slot_mode_t ch) +{ + esp_err_t ret = ESP_OK; + + esp_codec_dev_sample_info_t fs = { + .sample_rate = rate, + .channel = ch, + .bits_per_sample = bps, + }; + + if (record_dev_handle) { + ret = esp_codec_dev_close(record_dev_handle); + } + ret = esp_codec_dev_open(record_dev_handle, &fs); + + // esp_codec_dev_set_in_gain(record_dev_handle, 80.0); // Set codec input gain + + return ret; +} + +void bsp_codec_init(void) +{ + play_dev_handle = bsp_audio_codec_speaker_init(); + assert((play_dev_handle) && "play_dev_handle not initialized"); + + record_dev_handle = bsp_audio_codec_microphone_init(); + assert((record_dev_handle) && "record_dev_handle not initialized"); + + // bsp_codec_es7210_set(16000, 16, 2); + // bsp_codec_es8388_set(16000, 16, 2); + // bsp_codec_es7210_set(48000, 16, 2); + bsp_codec_es7210_set(48000, 16, 4); + bsp_codec_es8388_set(48000, 16, 2); + + /* 初始化 codec handle */ + bsp_codec_config_t* codec_cfg = bsp_get_codec_handle(); // 获取 codec handle + codec_cfg->i2s_read = bsp_i2s_read; // I2S 读数据 + codec_cfg->i2s_write = bsp_i2s_write; // I2S 写数据 + codec_cfg->set_mute = bsp_codec_set_mute; // 静音设置 + codec_cfg->set_volume = bsp_codec_set_volume; // 音量设置 + codec_cfg->get_volume = bsp_codec_get_volume; + codec_cfg->set_in_gain = bsp_codec_set_in_gain; // 麦克风输入增益设置 + codec_cfg->codec_reconfig_fn = bsp_codec_es7210_set; + codec_cfg->i2s_reconfig_clk_fn = bsp_codec_es8388_set; + + codec_cfg->set_volume(80); +} + +uint8_t bsp_codec_feed_channel(void) +{ + return 3; // 2*mic_num + ref_num +} + +//================================================================================== +// lcd st7703 1280x720 gt911 +//================================================================================== +// Bit number used to represent command and parameter +#define LCD_LEDC_CH LEDC_CHANNEL_1 // CONFIG_BSP_DISPLAY_BRIGHTNESS_LEDC_CH +esp_err_t bsp_display_brightness_init(void) +{ + // gpio_config_t io_conf = {}; + + // io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt + // io_conf.mode = GPIO_MODE_OUTPUT; //set as output mode + // io_conf.pin_bit_mask = 1 << BSP_LCD_BACKLIGHT; //select pin + // io_conf.pull_down_en = 0; //disable pull-down mode + // io_conf.pull_up_en = 0; //disable pull-up mode + // gpio_config(&io_conf); //configure GPIO with the given settings + + // gpio_set_level(BSP_LCD_BACKLIGHT, 1); + + // Setup LEDC peripheral for PWM backlight control + const ledc_timer_config_t lcd_backlight_timer = {.speed_mode = LEDC_LOW_SPEED_MODE, + // .duty_resolution = LEDC_TIMER_10_BIT, + .duty_resolution = LEDC_TIMER_12_BIT, + .timer_num = LEDC_TIMER_0, + .freq_hz = 5000, + // .freq_hz = 20000, + .clk_cfg = LEDC_AUTO_CLK}; + ESP_ERROR_CHECK(ledc_timer_config(&lcd_backlight_timer)); + + const ledc_channel_config_t lcd_backlight_channel = {.gpio_num = BSP_LCD_BACKLIGHT, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = LCD_LEDC_CH, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LEDC_TIMER_0, + .duty = 0, + .hpoint = 0}; + + ESP_ERROR_CHECK(ledc_channel_config(&lcd_backlight_channel)); + + return ESP_OK; +} + +esp_err_t bsp_display_brightness_set(int brightness_percent) +{ + if (brightness_percent > 100) { + brightness_percent = 100; + } + if (brightness_percent < 0) { + brightness_percent = 0; + } + + ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent); + // uint32_t duty_cycle = (1023 * brightness_percent) / 100; // LEDC resolution set to 10bits, thus: 100% = 1023 + uint32_t duty_cycle = (4095 * brightness_percent) / 100; // LEDC resolution set to 12bits, thus: 100% = 4095 + BSP_ERROR_CHECK_RETURN_ERR(ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle)); + BSP_ERROR_CHECK_RETURN_ERR(ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH)); + return ESP_OK; +} + +esp_err_t bsp_display_backlight_off(void) +{ + return bsp_display_brightness_set(0); +} + +esp_err_t bsp_display_backlight_on(void) +{ + return bsp_display_brightness_set(100); +} + +static esp_err_t bsp_enable_dsi_phy_power(void) +{ +#if BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0 + // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state + static esp_ldo_channel_handle_t phy_pwr_chan = NULL; + esp_ldo_channel_config_t ldo_cfg = { + .chan_id = BSP_MIPI_DSI_PHY_PWR_LDO_CHAN, + .voltage_mv = BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + ESP_RETURN_ON_ERROR(esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan), TAG, "Acquire LDO channel for DPHY failed"); + ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); +#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0 + + return ESP_OK; +} + +esp_err_t bsp_display_new(const bsp_display_config_t* config, esp_lcd_panel_handle_t* ret_panel, + esp_lcd_panel_io_handle_t* ret_io) +{ + esp_err_t ret = ESP_OK; + bsp_lcd_handles_t handles; + ret = bsp_display_new_with_handles(config, &handles); + + *ret_panel = handles.panel; + *ret_io = handles.io; + + return ret; +} + +#define LCD_MIPI_DSI_USE_ILI9881C + +#if defined(LCD_MIPI_DSI_USE_ILI9881C) && !defined(LCD_MIPI_DSI_USE_ST7703) +#include "ili9881_init_data.c" +#endif + +esp_err_t bsp_display_new_with_handles(const bsp_display_config_t* config, bsp_lcd_handles_t* ret_handles) +{ + esp_err_t ret = ESP_OK; + esp_lcd_panel_io_handle_t io = NULL; + esp_lcd_panel_handle_t disp_panel = NULL; + + ESP_RETURN_ON_ERROR(bsp_display_brightness_init(), TAG, "Brightness init failed"); + ESP_RETURN_ON_ERROR(bsp_enable_dsi_phy_power(), TAG, "DSI PHY power failed"); + + /* create MIPI DSI bus first, it will initialize the DSI PHY as well */ + esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; + esp_lcd_dsi_bus_config_t bus_config = { + .bus_id = 0, + .num_data_lanes = BSP_LCD_MIPI_DSI_LANE_NUM, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = BSP_LCD_MIPI_DSI_LANE_BITRATE_MBPS, + }; + ESP_RETURN_ON_ERROR(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus), TAG, "New DSI bus init failed"); + + ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); + // we use DBI interface to send LCD commands and parameters + esp_lcd_dbi_io_config_t dbi_config = { + .virtual_channel = 0, + .lcd_cmd_bits = 8, // according to the LCD spec + .lcd_param_bits = 8, // according to the LCD spec + }; + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io), err, TAG, "New panel IO failed"); + +#if defined(LCD_MIPI_DSI_USE_ILI9881C) && !defined(LCD_MIPI_DSI_USE_ST7703) + ESP_LOGI(TAG, "Install LCD driver of ili9881c"); + esp_lcd_dpi_panel_config_t dpi_config = { + .virtual_channel = 0, + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = 60, // 720*1280 RGB24 60Hz RGB24 // 80, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .num_fbs = 1, + .video_timing = + { + .h_size = BSP_LCD_H_RES, + .v_size = BSP_LCD_V_RES, + .hsync_back_porch = 140, + .hsync_pulse_width = 40, + .hsync_front_porch = 40, + .vsync_back_porch = 20, + .vsync_pulse_width = 4, + .vsync_front_porch = 20, + }, + .flags.use_dma2d = true, + }; + + ili9881c_vendor_config_t vendor_config = { + .init_cmds = tab5_lcd_ili9881c_specific_init_code_default, + .init_cmds_size = sizeof(tab5_lcd_ili9881c_specific_init_code_default) / + sizeof(tab5_lcd_ili9881c_specific_init_code_default[0]), + .mipi_config = + { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + .lane_num = 2, + }, + }; + + const esp_lcd_panel_dev_config_t lcd_dev_config = { + .bits_per_pixel = 16, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .reset_gpio_num = -1, + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9881c(io, &lcd_dev_config, &disp_panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(disp_panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(disp_panel)); + // ESP_ERROR_CHECK(esp_lcd_panel_mirror(disp_panel, false, true)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(disp_panel, true)); + +#elif defined(LCD_MIPI_DSI_USE_ST7703) && !defined(LCD_MIPI_DSI_USE_ILI9881C) + ESP_LOGI(TAG, "Install LCD driver of ST7703"); + esp_lcd_dpi_panel_config_t dpi_config = { + .virtual_channel = 0, + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = 60, // LCD_MIPI_DSI_DPI_CLK_MHZ_ST7703, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, // LCD_COLOR_PIXEL_FORMAT_RGB888, + .num_fbs = 1, + .video_timing = + { + .h_size = BSP_LCD_H_RES, // lcd_param.width, + .v_size = BSP_LCD_V_RES, // lcd_param.height, + .hsync_back_porch = 40, + .hsync_pulse_width = 10, + .hsync_front_porch = 40, + .vsync_back_porch = 16, + .vsync_pulse_width = 4, + .vsync_front_porch = 16, + }, + //.flags.use_dma2d = true, // ??? 开启后需要等待 previous draw 完成 + }; + + st7703_vendor_config_t vendor_config = { + .flags.use_mipi_interface = 1, + .mipi_config = + { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + }, + }; + esp_lcd_panel_dev_config_t lcd_dev_config = { + .bits_per_pixel = 16, // 24, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .reset_gpio_num = -1, + .vendor_config = &vendor_config, + }; + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7703(io, &lcd_dev_config, &disp_panel), err, TAG, + "New LCD panel EK79007 failed"); + ESP_GOTO_ON_ERROR(esp_lcd_panel_init(disp_panel), err, TAG, "LCD panel init failed"); +#endif + + /* Return all handles */ + ret_handles->io = io; + ret_handles->mipi_dsi_bus = mipi_dsi_bus; + ret_handles->panel = disp_panel; + ret_handles->control = NULL; + + ESP_LOGI(TAG, "Display initialized with resolution %dx%d", BSP_LCD_H_RES, BSP_LCD_V_RES); + + return ret; + +err: + if (disp_panel) { + esp_lcd_panel_del(disp_panel); + } + if (io) { + esp_lcd_panel_io_del(io); + } + if (mipi_dsi_bus) { + esp_lcd_del_dsi_bus(mipi_dsi_bus); + } + return ret; +} + +esp_err_t bsp_touch_new(const bsp_touch_config_t* config, esp_lcd_touch_handle_t* ret_touch) +{ + /* Initilize I2C */ + BSP_ERROR_CHECK_RETURN_ERR(bsp_i2c_init()); + + /* Initialize touch */ + const esp_lcd_touch_config_t tp_cfg = { + .x_max = BSP_LCD_H_RES, + .y_max = BSP_LCD_V_RES, + .rst_gpio_num = -1, // BSP_LCD_TOUCH_RST, // NC + .int_gpio_num = 23, // BSP_LCD_TOUCH_INT, + .levels = + { + .reset = 0, + .interrupt = 0, + }, + .flags = + { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + tp_io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP; // 更改 GT911 地址 + tp_io_config.scl_speed_hz = CONFIG_BSP_I2C_CLK_SPEED_HZ; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c(i2c_handle, &tp_io_config, &tp_io_handle), TAG, ""); + return esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, ret_touch); +} + +#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0) +static lv_display_t* bsp_display_lcd_init(const bsp_display_cfg_t* cfg) +{ + assert(cfg != NULL); + bsp_lcd_handles_t lcd_panels; + BSP_ERROR_CHECK_RETURN_NULL(bsp_display_new_with_handles(NULL, &lcd_panels)); + + /* Add LCD screen */ + ESP_LOGD(TAG, "Add LCD screen"); + const lvgl_port_display_cfg_t disp_cfg = + {.io_handle = lcd_panels.io, + .panel_handle = lcd_panels.panel, + .control_handle = lcd_panels.control, + .buffer_size = cfg->buffer_size, + .double_buffer = cfg->double_buffer, + .hres = BSP_LCD_H_RES, + .vres = BSP_LCD_V_RES, + .monochrome = false, + /* Rotation values must be same as used in esp_lcd for initial settings of the screen */ + .rotation = + { + .swap_xy = false, + .mirror_x = false, + .mirror_y = false, + }, +#if LVGL_VERSION_MAJOR >= 9 +#if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888 + .color_format = LV_COLOR_FORMAT_RGB888, +#else + .color_format = LV_COLOR_FORMAT_RGB565, +#endif +#endif + .flags = { + .buff_dma = cfg->flags.buff_dma, + .buff_spiram = cfg->flags.buff_spiram, +#if LVGL_VERSION_MAJOR >= 9 + .swap_bytes = (BSP_LCD_BIGENDIAN ? true : false), +#endif +#if CONFIG_BSP_DISPLAY_LVGL_AVOID_TEAR + .sw_rotate = false, /* Avoid tearing is not supported for SW rotation */ +#else + .sw_rotate = cfg->flags.sw_rotate, /* Only SW rotation is supported for 90° and 270° */ +#endif +#if CONFIG_BSP_DISPLAY_LVGL_FULL_REFRESH + .full_refresh = true, +#elif CONFIG_BSP_DISPLAY_LVGL_DIRECT_MODE + .direct_mode = true, +#endif + } }; + + const lvgl_port_display_dsi_cfg_t dpi_cfg = {.flags = { +#if CONFIG_BSP_DISPLAY_LVGL_AVOID_TEAR + .avoid_tearing = true, +#else + .avoid_tearing = false, +#endif + }}; + + return lvgl_port_add_disp_dsi(&disp_cfg, &dpi_cfg); +} + +static esp_lcd_touch_handle_t _touch_handle; + +esp_lcd_touch_handle_t bsp_display_get_touch_handle(void) +{ + return _touch_handle; +} + +esp_lcd_touch_handle_t _lcd_touch_handle; + +static lv_indev_t* bsp_display_indev_init(lv_display_t* disp) +{ + esp_lcd_touch_handle_t tp; + BSP_ERROR_CHECK_RETURN_NULL(bsp_touch_new(NULL, &tp)); + esp_lcd_touch_exit_sleep(tp); // !!! + assert(tp); + _lcd_touch_handle = tp; + + /* Add touch input (for selected screen) */ + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = disp, + .handle = tp, + }; + + return lvgl_port_add_touch(&touch_cfg); +} + +lv_display_t* bsp_display_start(void) +{ + bsp_display_cfg_t cfg = {.lvgl_port_cfg = ESP_LVGL_PORT_INIT_CONFIG(), + .buffer_size = BSP_LCD_DRAW_BUFF_SIZE, + .double_buffer = BSP_LCD_DRAW_BUFF_DOUBLE, + .flags = { +#if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888 + .buff_dma = false, +#else + .buff_dma = true, +#endif + .buff_spiram = false, + .sw_rotate = true, + }}; + return bsp_display_start_with_config(&cfg); +} + +lv_display_t* bsp_display_start_with_config(const bsp_display_cfg_t* cfg) +{ + lv_display_t* disp; + + assert(cfg != NULL); + BSP_ERROR_CHECK_RETURN_NULL(lvgl_port_init(&cfg->lvgl_port_cfg)); + + BSP_ERROR_CHECK_RETURN_NULL(bsp_display_brightness_init()); + + BSP_NULL_CHECK(disp = bsp_display_lcd_init(cfg), NULL); + BSP_NULL_CHECK(disp_indev = bsp_display_indev_init(disp), NULL); + return disp; +} + +lv_indev_t* bsp_display_get_input_dev(void) +{ + return disp_indev; +} + +void bsp_display_rotate(lv_display_t* disp, lv_disp_rotation_t rotation) +{ + lv_disp_set_rotation(disp, rotation); +} + +bool bsp_display_lock(uint32_t timeout_ms) +{ + return lvgl_port_lock(timeout_ms); +} + +void bsp_display_unlock(void) +{ + lvgl_port_unlock(); +} +#endif // (BSP_CONFIG_NO_GRAPHIC_LIB == 0) + +//================================================================================== +// usb +//================================================================================== +static void usb_lib_task(void* arg) +{ + while (1) { + // Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + ESP_ERROR_CHECK(usb_host_device_free_all()); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ESP_LOGI(TAG, "USB: All devices freed"); + // Continue handling USB events to allow device reconnection + // The only way this task can be stopped is by calling bsp_usb_host_stop() + } + } +} + +esp_err_t bsp_usb_host_start(bsp_usb_host_power_mode_t mode, bool limit_500mA) +{ + // Install USB Host driver. Should only be called once in entire application + ESP_LOGI(TAG, "Installing USB Host"); + const usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + BSP_ERROR_CHECK_RETURN_ERR(usb_host_install(&host_config)); + + // Create a task that will handle USB library events + if (xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, 10, &usb_host_task) != pdTRUE) { + ESP_LOGE(TAG, "Creating USB host lib task failed"); + abort(); + } + + return ESP_OK; +} + +esp_err_t bsp_usb_host_stop(void) +{ + usb_host_uninstall(); + if (usb_host_task) { + vTaskSuspend(usb_host_task); + vTaskDelete(usb_host_task); + } + return ESP_OK; +} diff --git a/components/m5stack_tab5/priv_include/bsp_err_check.h b/components/m5stack_tab5/priv_include/bsp_err_check.h new file mode 100644 index 0000000..90fbda7 --- /dev/null +++ b/components/m5stack_tab5/priv_include/bsp_err_check.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_check.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Assert on error, if selected in menuconfig. Otherwise return error code. */ +#if CONFIG_BSP_ERROR_CHECK +#define BSP_ERROR_CHECK_RETURN_ERR(x) ESP_ERROR_CHECK(x) +#define BSP_ERROR_CHECK_RETURN_NULL(x) ESP_ERROR_CHECK(x) +#define BSP_ERROR_CHECK(x, ret) ESP_ERROR_CHECK(x) +#define BSP_NULL_CHECK(x, ret) assert(x) +#define BSP_NULL_CHECK_GOTO(x, goto_tag) assert(x) +#else +#define BSP_ERROR_CHECK_RETURN_ERR(x) \ + do { \ + esp_err_t err_rc_ = (x); \ + if (unlikely(err_rc_ != ESP_OK)) { \ + return err_rc_; \ + } \ + } while (0) + +#define BSP_ERROR_CHECK_RETURN_NULL(x) \ + do { \ + if (unlikely((x) != ESP_OK)) { \ + return NULL; \ + } \ + } while (0) + +#define BSP_NULL_CHECK(x, ret) \ + do { \ + if ((x) == NULL) { \ + return ret; \ + } \ + } while (0) + +#define BSP_ERROR_CHECK(x, ret) \ + do { \ + if (unlikely((x) != ESP_OK)) { \ + return ret; \ + } \ + } while (0) + +#define BSP_NULL_CHECK_GOTO(x, goto_tag) \ + do { \ + if ((x) == NULL) { \ + goto goto_tag; \ + } \ + } while (0) +#endif + +#ifdef __cplusplus +} +#endif diff --git a/docs/img/m5stack-tab5.webp b/docs/img/m5stack-tab5.webp new file mode 100644 index 0000000..16123b8 Binary files /dev/null and b/docs/img/m5stack-tab5.webp differ diff --git a/main/BridgingHeader.h b/main/BridgingHeader.h index a3ade9f..87318ce 100644 --- a/main/BridgingHeader.h +++ b/main/BridgingHeader.h @@ -7,6 +7,6 @@ #include "SDL3/SDL.h" #include "SDL3_ttf/SDL_ttf.h" #include "pthread.h" -#include "bsp/esp-bsp.h" +#include "bsp/display.h" #include "esp_vfs.h" #include "esp_littlefs.h" diff --git a/main/Main.swift b/main/Main.swift index 42abb66..be2e893 100644 --- a/main/Main.swift +++ b/main/Main.swift @@ -173,9 +173,9 @@ func sdl_thread_entry_point(arg: UnsafeMutableRawPointer?) -> UnsafeMutableRawPo running = false break } else if event.type == SDL_EVENT_FINGER_UP.rawValue { - // Get touch coordinates (normalized between 0 and 1) - let touchX = event.tfinger.x - let touchY = event.tfinger.y + // Get touch coordinates (normalized between 0 and 1) and convert to screen coordinates + let touchX = event.tfinger.x * screenWidth + let touchY = event.tfinger.y * screenHeight // Check if touch intersects any sprite for i in 0..