From e37258018a0fddc72f9a85c01ea08695ac7501fb Mon Sep 17 00:00:00 2001 From: Kaj Schittecat Date: Sat, 17 Oct 2026 20:22:03 +0200 Subject: [PATCH] crowpanel_p4_101: experimental Elecrow CrowPanel Advanced 10.1" ESP32-P4 port Standalone ESP-IDF target (crowpanel_p4_101/) modelled on tdisplay_p4/, sharing its ESP-IDF, components and esp-hosted 2.12.3 backend. Untested on hardware. - variants/crowpanel_p4_101: EK79007 MIPI-DSI 1024x600 display (Elecrow timings, LEDC backlight on GPIO31), GT911 touch on I2C0 45/46 (RST40/INT42), SX1262 on the J9/J11 header with a real DIO1 IRQ, LDO3/LDO4 bring-up, C6_EN reset callback on GPIO32, full-system-reset restart shim. - Board revision is a build option: WADA_CP_REV=1.0|1.1|1.2 (default 1.2). V1.2 moves LoRa DIO1/NRST from GPIO53/54 to 27/28; V1.0 has the C6 SDIO data lines reversed (run 1-bit like Elecrow's V1.0 lesson). hostedSetPins() overrides Arduino's generic esp32p4 SDIO pin defaults, which only match V1.0. - LVGL at native 1024x600 with the large-screen layouts; fixed landscape. - Shared src/: one CAP_* block in device_caps.h plus HAS_CROWPANEL_P4_101 at the P4 platform sites (storage law, SD_MMC, hosted BLE, no RTC_CNTL, no MQTT, fonts) and the CrowPanel no-unlock-control sites. navOpenChatPanel/navGoToMainTab get fallbacks for boards without CAP_KEYPAD_NAV. - build.sh merge writes a 0x0 merged image per revision. T-Display P4 still builds from the same tree. Co-Authored-By: Claude Opus 5.5 --- crowpanel_p4_101/.gitignore | 8 + crowpanel_p4_101/CMakeLists.txt | 3 + crowpanel_p4_101/build.sh | 204 ++++++++++++ crowpanel_p4_101/components/RadioLib | 1 + crowpanel_p4_101/components/ardlibs | 1 + crowpanel_p4_101/components/esp_hosted | 1 + crowpanel_p4_101/components/lvgl | 1 + crowpanel_p4_101/components/meshcore | 1 + .../components/wadamesh_app/CMakeLists.txt | 22 ++ crowpanel_p4_101/esp-idf | 1 + crowpanel_p4_101/esp-idf-tools | 1 + crowpanel_p4_101/main/CMakeLists.txt | 94 ++++++ crowpanel_p4_101/main/idf_component.yml | 12 + crowpanel_p4_101/main/main.cpp | 238 ++++++++++++++ .../partition_tables/crowpanel_p4_101_16M.csv | 12 + crowpanel_p4_101/sdkconfigs/crowpanel_p4_101 | 58 ++++ crowpanel_p4_101/sdkconfigs/general | 1 + crowpanel_p4_101/sdkconfigs/sdio_v10 | 9 + crowpanel_p4_101/sdkconfigs/sdio_v11 | 10 + crowpanel_p4_101/sdkconfigs/wadamesh | 1 + scripts/build/gen-touch-fonts.sh | 2 +- src/DataStore.cpp | 20 +- src/DataStore.h | 4 +- src/MyMesh.cpp | 4 +- src/helpers/esp32/BootTimeSync.cpp | 2 +- src/helpers/esp32/MqttBridge.cpp | 2 +- src/helpers/esp32/MqttBridge.h | 2 +- .../MultiTransportCompanionInterface.cpp | 4 +- src/helpers/esp32/WebFileTransferConfig.h | 4 +- src/ui-touch/UITask.cpp | 133 +++++--- src/ui-touch/device_caps.h | 21 +- src/ui-touch/ui_font_18.c | 4 +- src/ui-touch/ui_font_20.c | 4 +- src/ui-touch/ui_font_24.c | 4 +- variants/crowpanel_p4_101/EK79007Display.cpp | 178 ++++++++++ variants/crowpanel_p4_101/EK79007Display.h | 36 +++ variants/crowpanel_p4_101/Gt911Touch.cpp | 306 ++++++++++++++++++ variants/crowpanel_p4_101/ssl_client_stub.cpp | 31 ++ variants/crowpanel_p4_101/target.cpp | 142 ++++++++ variants/crowpanel_p4_101/target.h | 73 +++++ 40 files changed, 1587 insertions(+), 68 deletions(-) create mode 100644 crowpanel_p4_101/.gitignore create mode 100644 crowpanel_p4_101/CMakeLists.txt create mode 100755 crowpanel_p4_101/build.sh create mode 120000 crowpanel_p4_101/components/RadioLib create mode 120000 crowpanel_p4_101/components/ardlibs create mode 120000 crowpanel_p4_101/components/esp_hosted create mode 120000 crowpanel_p4_101/components/lvgl create mode 120000 crowpanel_p4_101/components/meshcore create mode 100644 crowpanel_p4_101/components/wadamesh_app/CMakeLists.txt create mode 120000 crowpanel_p4_101/esp-idf create mode 120000 crowpanel_p4_101/esp-idf-tools create mode 100644 crowpanel_p4_101/main/CMakeLists.txt create mode 100644 crowpanel_p4_101/main/idf_component.yml create mode 100644 crowpanel_p4_101/main/main.cpp create mode 100644 crowpanel_p4_101/partition_tables/crowpanel_p4_101_16M.csv create mode 100644 crowpanel_p4_101/sdkconfigs/crowpanel_p4_101 create mode 120000 crowpanel_p4_101/sdkconfigs/general create mode 100644 crowpanel_p4_101/sdkconfigs/sdio_v10 create mode 100644 crowpanel_p4_101/sdkconfigs/sdio_v11 create mode 120000 crowpanel_p4_101/sdkconfigs/wadamesh create mode 100644 variants/crowpanel_p4_101/EK79007Display.cpp create mode 100644 variants/crowpanel_p4_101/EK79007Display.h create mode 100644 variants/crowpanel_p4_101/Gt911Touch.cpp create mode 100644 variants/crowpanel_p4_101/ssl_client_stub.cpp create mode 100644 variants/crowpanel_p4_101/target.cpp create mode 100644 variants/crowpanel_p4_101/target.h diff --git a/crowpanel_p4_101/.gitignore b/crowpanel_p4_101/.gitignore new file mode 100644 index 0000000..411a6ef --- /dev/null +++ b/crowpanel_p4_101/.gitignore @@ -0,0 +1,8 @@ +# build output + component manager cache (project-local ESP-IDF comes in via the ../tanmatsu symlinks) +build/ +managed_components/ +sdkconfig +sdkconfig.old +dependencies.lock +# flashable images produced by build.sh merge +*.bin diff --git a/crowpanel_p4_101/CMakeLists.txt b/crowpanel_p4_101/CMakeLists.txt new file mode 100644 index 0000000..37ccc29 --- /dev/null +++ b/crowpanel_p4_101/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.10) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(application) diff --git a/crowpanel_p4_101/build.sh b/crowpanel_p4_101/build.sh new file mode 100755 index 0000000..bfff5e5 --- /dev/null +++ b/crowpanel_p4_101/build.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# wadamesh / Elecrow CrowPanel Advanced 10.1" ESP32-P4 HMI build wrapper. EXPERIMENTAL. +# Standalone ESP-IDF app modelled on tdisplay_p4/build.sh: reuses the Tanmatsu's project-local +# ESP-IDF 5.5.1 (esp32p4) via the ../tanmatsu symlinks and the shared P4 components. +# ./build.sh build # board revision V1.2 (default, newest) +# WADA_CP_REV=1.1 ./build.sh build # V1.1 (SX1262 DIO1/RST on GPIO53/54) +# WADA_CP_REV=1.0 ./build.sh build # V1.0 (same LoRa pins as V1.1, reversed 1-bit C6 SDIO) +# ./build.sh merge # build + one flashable image at 0x0 (build//*-merged.bin) +# ./build.sh flash -p /dev/cu.usbserial-XXXX +# ./build.sh fullclean # remove this revision's build tree only +set -e + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" + +CP_REV="${WADA_CP_REV:-1.2}" +case "$CP_REV" in + 1.2) CP_SDIO_FRAG="sdio_v11" ;; + 1.1) CP_SDIO_FRAG="sdio_v11" ;; + 1.0) CP_SDIO_FRAG="sdio_v10" ;; + *) echo "[build.sh] WADA_CP_REV must be 1.0, 1.1 or 1.2 (got '$CP_REV')" >&2; exit 1 ;; +esac +export WADA_CP_REV="$CP_REV" +P4_BUILD_TARGET="crowpanel_p4_101_v${CP_REV/./}" +P4_BUILD_DIR="$PROJECT_DIR/build/$P4_BUILD_TARGET" + +# This project intentionally applies compatibility fixes inside managed_components +# before every build. ESP-IDF's stock fullclean also runs remove_managed_components, +# which rejects those expected hash changes and aborts. A P4 full clean therefore +# removes only the generated CMake/Ninja tree; dependencies stay in place and the +# idempotent patches below remain valid. +if [ "$#" -eq 1 ] && [ "$1" = "fullclean" ]; then + # Plain rm -rf: cmake is not on PATH before export.sh. Each revision has its own build tree + # (and its own sdkconfig inside it), so this removes only the selected one. + if [ -d "$P4_BUILD_DIR" ]; then + rm -rf "${P4_BUILD_DIR:?}" || { echo "[build.sh] FAILED to remove $P4_BUILD_DIR" >&2; exit 1; } + fi + if [ -d "$P4_BUILD_DIR" ]; then + echo "[build.sh] FAILED to remove $P4_BUILD_DIR" >&2 + exit 1 + fi + echo "[build.sh] removed build/$P4_BUILD_TARGET (patched managed components preserved)" + exit 0 +fi + +# Keep the baked-in translation + Lua app tables in step with deploy/apps/. +# Same for the seeded Lua apps. The PlatformIO envs get this from a pre: hook; +# the IDF builds need it here or the P4 boards ship stale copies. +python3 "$(cd "$(dirname "$0")/.." && pwd)/scripts/build/pre_gen_baked.py" +# LVGL is vendored (gitignored) by fetch-deps.sh, so a copy vendored before the +# anim_timer use-after-free fix (#428) still needs it. Idempotent; fails on drift. +python3 "$(cd "$(dirname "$0")/.." && pwd)/scripts/build/patch_lvgl_anim_uaf.py" \ + --patch-file "$(cd "$(dirname "$0")" && pwd)/components/lvgl/upstream/src/misc/lv_anim.c" +python3 "$(cd "$(dirname "$0")/.." && pwd)/scripts/build/patch_lvgl_arabic.py" \ + --patch-file "$(cd "$(dirname "$0")" && pwd)/components/lvgl/upstream/src/misc/lv_txt_ap.c" +cd "$PROJECT_DIR" +export IDF_TOOLS_PATH="$PWD/esp-idf-tools" +# VS Code may launch this wrapper from PlatformIO's virtualenv. Pin the +# project-local IDF environment before export.sh inspects that unrelated Python. +if [ -z "${IDF_PYTHON_ENV_PATH:-}" ]; then + for idf_py_env in "$IDF_TOOLS_PATH"/python_env/idf5.5_py*_env; do + if [ -x "$idf_py_env/bin/python" ]; then + export IDF_PYTHON_ENV_PATH="$idf_py_env" + break + fi + done +fi +unset VIRTUAL_ENV CONDA_PREFIX +# shellcheck disable=SC1091 +source esp-idf/export.sh >/dev/null 2>&1 + +WADA_FW_TAG="${WADA_FW_TAG:-$(git describe --tags --match 'beta_*' --always 2>/dev/null || echo dev)}" +WADA_FW_DATE="$(date '+%-d %b %Y')" +IDF_ARGS=(-B "build/$P4_BUILD_TARGET" \ + -DDEVICE=crowpanel_p4_101 \ + -DSDKCONFIG="$P4_BUILD_DIR/sdkconfig" \ + -DSDKCONFIG_DEFAULTS="sdkconfigs/general;sdkconfigs/wadamesh;sdkconfigs/crowpanel_p4_101;sdkconfigs/$CP_SDIO_FRAG" \ + -DWADA_FW_TAG="$WADA_FW_TAG" -DWADA_FW_DATE="$WADA_FW_DATE" \ + -DIDF_TARGET=esp32p4) + +# A fresh clone has no managed_components yet. Configure once to download them, +# then apply the compatibility patches below before the first compilation. +if [ ! -f managed_components/espressif__libsodium/CMakeLists.txt ]; then + idf.py "${IDF_ARGS[@]}" reconfigure +fi + +# --- Build-time patch: libsodium forced includes in paths with spaces ----------------------------- +# The managed component emits `SHELL:-include `. CMake leaves that +# path unquoted in Ninja, so a checkout such as "T7 Shield" reaches GCC as two +# input files. GCC accepts the joined -include form, which cannot split. +SODIUM_CMAKE="managed_components/espressif__libsodium/CMakeLists.txt" +if [ -f "$SODIUM_CMAKE" ] && grep -q 'SHELL:-include' "$SODIUM_CMAKE"; then + sed -i '' -e 's|SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}|-include${CMAKE_CURRENT_SOURCE_DIR}|g' \ + -e 's|SHELL:-include${CMAKE_CURRENT_SOURCE_DIR}|-include${CMAKE_CURRENT_SOURCE_DIR}|g' "$SODIUM_CMAKE" + echo "[build.sh] patched libsodium forced includes (space-safe paths)" +fi + +# --- Build-time patch: SD_MMC internal pull-ups (P4 boards) ------------------------------------------ +# The board has no external pull-ups on the SD data lines; the IDF sdmmc host explicitly FLOATS the +# pads unless SDMMC_SLOT_FLAG_INTERNAL_PULLUP is set (so pre-begin gpio_pullup_en gets undone). +# Meck-P4's working SD init sets the flag; Arduino's SD_MMC never does and has no API for it. Patch +# the P4's own managed copy. Idempotent. +SDMMC_C="managed_components/espressif__arduino-esp32/libraries/SD_MMC/src/SD_MMC.cpp" +if [ -f "$SDMMC_C" ] && ! grep -q 'wadamesh P4: internal pullups' "$SDMMC_C"; then + sed -i '' 's|sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();|sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();\n slot_config.flags \|= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // wadamesh P4: internal pullups (no external ones on the SD lines)|' "$SDMMC_C" + echo "[build.sh] patched arduino SD_MMC.cpp (internal pull-ups on the SD slot)" +fi +# --- Build-time patch: a dead C6 must not spin the SDIO rx task ----------------------------------- +# With CONFIG_ESP_HOSTED_TRANSPORT_RESTART_ON_FAILURE=n (see sdkconfigs/tdisplay_p4 for why it is +# off), sdio_read_task posts ESP_HOSTED_EVENT_TRANSPORT_FAILURE and then `continue`s: straight back +# into a register read that is going to fail again. On a unit whose C6 runs factory ESP-AT the +# transport NEVER comes up, so that is an unbounded retry storm: measured at ~16 failed reads per +# second, forever, with the whole transport re-initialising on top of it. It floods the USB-CDC the +# companion app talks over and keeps a task busy for the life of the session. +# +# Turning the restart back on is not the answer: that is the boot loop this board had before. +# Both are the same mistake: a co-processor that cannot be reached is a permanent condition, and +# the driver treats it as transient. Park the task instead. The failure event has already been +# posted, Wi-Fi and BLE are simply unavailable, and everything else on the device keeps running. +# Idempotent. +HOSTED_SDIO="managed_components/nicolaielectronics__esp-hosted-tanmatsu/host/drivers/transport/sdio/sdio_drv.c" +if [ -f "$HOSTED_SDIO" ] && ! grep -q 'wadamesh: park instead of spinning' "$HOSTED_SDIO"; then + python3 - "$HOSTED_SDIO" <<'PYEOF' +import re, sys +p = sys.argv[1] +s = open(p).read() +# Both failure arms look identical apart from the log line: event post, optional restart, continue. +old = """#if H_TRANSPORT_RESTART_ON_FAILURE + ESP_LOGI(TAG, "Host is resetting itself, to avoid any sdio race condition"); + g_h.funcs->_h_restart_host(); +#endif + continue;""" +new = """#if H_TRANSPORT_RESTART_ON_FAILURE + ESP_LOGI(TAG, "Host is resetting itself, to avoid any sdio race condition"); + g_h.funcs->_h_restart_host(); +#else + /* wadamesh: park instead of spinning. The transport is down and we are + * not allowed to reboot the host, so retrying can only fail again at + * full speed. The failure event is already posted; stop here and let the + * rest of the device run without Wi-Fi/BLE. */ + ESP_LOGE(TAG, "transport down, host restart disabled: parking rx task (Wi-Fi/BLE unavailable)"); + while (1) g_h.funcs->_h_msleep(60000); +#endif + continue;""" +n = s.count(old) +if n == 0: + sys.exit("esp-hosted sdio_drv.c: failure arm not found; patch needs updating") +s = s.replace(old, new) +open(p, "w").write(s) +print(" patched %d failure arm(s)" % n) +PYEOF + echo "[build.sh] patched esp-hosted sdio_drv.c (park the rx task on a dead transport)" +fi + +# ⚠️ The P4/slot-0 branch REBUILDS slot_config from scratch with `.flags = 0`, silently discarding +# the flag added above (that's the branch that actually runs with BOARD_SDMMC_SLOT=0): so patch that +# struct literal too. Without the flag the IDF host explicitly FLOATS the pads -> deterministic 0x109 +# CRC at the first data read regardless of card/frequency. +if [ -f "$SDMMC_C" ] && ! grep -q 'wadamesh P4: slot0 pullups' "$SDMMC_C"; then + sed -i '' 's|^ .flags = 0,$| .flags = SDMMC_SLOT_FLAG_INTERNAL_PULLUP, // wadamesh P4: slot0 pullups (struct rebuilt here; the earlier flag is discarded)|' "$SDMMC_C" + echo "[build.sh] patched arduino SD_MMC.cpp (slot-0 struct pull-up flag)" +fi + +# --- Build-time patch: this board's C6 ships esp-hosted, so Arduino's hostedInit() must be the real +# one. A managed_components tree copied from a legacy ESP-AT T-Display build may carry the stub. +HOSTED_C="managed_components/espressif__arduino-esp32/cores/esp32/esp32-hal-hosted.c" +if [ -f "$HOSTED_C" ] && grep -q 'wadamesh P4: never start esp_hosted' "$HOSTED_C"; then + sed -i '' 's|^static bool hostedInit() { return false; // wadamesh P4: never start esp_hosted.*$|static bool hostedInit() {|' "$HOSTED_C" + echo "[build.sh] restored arduino hostedInit() (hosted C6 build)" +fi +BLEDEV_CPP="managed_components/espressif__arduino-esp32/libraries/BLE/src/BLEDevice.cpp" +if [ -f "$BLEDEV_CPP" ] && grep -q 'int rc = ble_gap_read_local_irk(irk);' "$BLEDEV_CPP"; then + sed -i '' 's|int rc = ble_gap_read_local_irk(irk);|int rc = 0; (void)irk; // wadamesh: arduino BLE unused; symbol renamed in IDF 5.5 NimBLE|' "$BLEDEV_CPP" + echo "[build.sh] patched arduino BLEDevice.cpp (stub renamed ble_gap_read_local_irk)" +fi + +# --- Build-time patch: neutralize esp-hosted's auto-init constructor (P4-ONLY) -------------------- +# Initialization must not run before app_main: the C6 is brought up lazily through Arduino +# hostedInit() after the UI, so a C6 that does not answer can only cost Wi-Fi/BLE. +# Two copies get pulled in (espressif__esp_hosted via arduino-esp32, and the nicolaielectronics__ +# esp-hosted-tanmatsu fork via tanmatsu-wifi/badge-bsp). The build actually links the fork, so patch +# BOTH so whichever is linked never grabs the SDIO. +for HDIR in espressif__esp_hosted nicolaielectronics__esp-hosted-tanmatsu; do + HINIT_C="managed_components/$HDIR/host/port/esp/freertos/src/port_esp_hosted_host_init.c" + if [ -f "$HINIT_C" ] && grep -q 'ESP_ERROR_CHECK(esp_hosted_init());' "$HINIT_C"; then + sed -i '' 's|ESP_ERROR_CHECK(esp_hosted_init());|/* wadamesh P4: C6 initialized lazily via hostedInit() after the UI */|' "$HINIT_C" + echo "[build.sh] neutralized $HDIR auto-init constructor (defer until board power)" + fi +done + +if [ "$#" -eq 1 ] && [ "$1" = "merge" ]; then + idf.py "${IDF_ARGS[@]}" build + OUT="$P4_BUILD_DIR/wadamesh-crowpanel-p4-101-v${CP_REV/./}-merged.bin" + # Same offsets and flash settings as scripts/build/p4-release.sh (bootloader 0x2000 on the P4). + python -m esptool --chip esp32p4 merge_bin --flash_mode dio --flash_size 16MB --flash_freq 80m \ + -o "$OUT" \ + 0x2000 "$P4_BUILD_DIR/bootloader/bootloader.bin" \ + 0x8000 "$P4_BUILD_DIR/partition_table/partition-table.bin" \ + 0xf000 "$P4_BUILD_DIR/ota_data_initial.bin" \ + 0x20000 "$P4_BUILD_DIR/application.bin" + echo "[build.sh] merged image: $OUT (flash at 0x0)" + exit 0 +fi + +exec idf.py "${IDF_ARGS[@]}" "$@" diff --git a/crowpanel_p4_101/components/RadioLib b/crowpanel_p4_101/components/RadioLib new file mode 120000 index 0000000..9fa22aa --- /dev/null +++ b/crowpanel_p4_101/components/RadioLib @@ -0,0 +1 @@ +../../tdisplay_p4/components/RadioLib \ No newline at end of file diff --git a/crowpanel_p4_101/components/ardlibs b/crowpanel_p4_101/components/ardlibs new file mode 120000 index 0000000..9d989e7 --- /dev/null +++ b/crowpanel_p4_101/components/ardlibs @@ -0,0 +1 @@ +../../tanmatsu/components/ardlibs \ No newline at end of file diff --git a/crowpanel_p4_101/components/esp_hosted b/crowpanel_p4_101/components/esp_hosted new file mode 120000 index 0000000..816835a --- /dev/null +++ b/crowpanel_p4_101/components/esp_hosted @@ -0,0 +1 @@ +../../tanmatsu/components/esp_hosted \ No newline at end of file diff --git a/crowpanel_p4_101/components/lvgl b/crowpanel_p4_101/components/lvgl new file mode 120000 index 0000000..2ddbce2 --- /dev/null +++ b/crowpanel_p4_101/components/lvgl @@ -0,0 +1 @@ +../../tanmatsu/components/lvgl \ No newline at end of file diff --git a/crowpanel_p4_101/components/meshcore b/crowpanel_p4_101/components/meshcore new file mode 120000 index 0000000..27e353b --- /dev/null +++ b/crowpanel_p4_101/components/meshcore @@ -0,0 +1 @@ +../../tdisplay_p4/components/meshcore \ No newline at end of file diff --git a/crowpanel_p4_101/components/wadamesh_app/CMakeLists.txt b/crowpanel_p4_101/components/wadamesh_app/CMakeLists.txt new file mode 100644 index 0000000..39778ff --- /dev/null +++ b/crowpanel_p4_101/components/wadamesh_app/CMakeLists.txt @@ -0,0 +1,22 @@ +# The wadamesh app layer (MyMesh + DataStore + helpers + transports + the LVGL UITask), compiled from +# the repo's src/ plus variants/crowpanel_p4_101/. Mirrors tdisplay_p4/components/wadamesh_app, minus +# the ESP-AT c6_at component (this board's C6 ships esp-hosted firmware). +set(REPO "${CMAKE_CURRENT_LIST_DIR}/../../..") +set(APP "${REPO}/src") +file(GLOB_RECURSE APP_SRCS "${APP}/*.cpp" "${APP}/*.c" "${REPO}/variants/crowpanel_p4_101/*.cpp" "${REPO}/variants/crowpanel_p4_101/*.c") +list(FILTER APP_SRCS EXCLUDE REGEX "/main\\.cpp$") +list(FILTER APP_SRCS EXCLUDE REGEX "/helpers/input/(HeltecV4CapTouch|TDeckKeyboard|TDeckTouch|TDeckTrackball|RakTapV2Touch)\\.cpp$") + +file(GLOB LUA_SRCS "${REPO}/lib/lua/src/*.c") + +idf_component_register( + SRCS ${APP_SRCS} ${LUA_SRCS} + INCLUDE_DIRS "${APP}" "${APP}/ui-touch" "${REPO}/include" "${REPO}/variants/crowpanel_p4_101" "${REPO}/lib/lua/src" + REQUIRES espressif__arduino-esp32 meshcore lvgl ardlibs RadioLib nvs_flash esp_lcd fatfs + esp_driver_i2c esp_driver_gpio esp_driver_ledc esp_hosted h2zero__esp-nimble-cpp bt + soc esp_hw_support app_update driver esp_timer esp_partition spi_flash esp_adc esp_pm espcoredump + espressif__esp_lcd_ek79007 esp_mm # esp_mm: esp_cache_msync (framebuffer clear) + WHOLE_ARCHIVE +) +target_compile_options(${COMPONENT_LIB} PRIVATE -w + -include "${REPO}/variants/tdisplay_p4/tdisplay_p4_compat.h") # adcAttachPin() shim (arduino 3.x dropped it) diff --git a/crowpanel_p4_101/esp-idf b/crowpanel_p4_101/esp-idf new file mode 120000 index 0000000..3ab3a91 --- /dev/null +++ b/crowpanel_p4_101/esp-idf @@ -0,0 +1 @@ +../tanmatsu/esp-idf \ No newline at end of file diff --git a/crowpanel_p4_101/esp-idf-tools b/crowpanel_p4_101/esp-idf-tools new file mode 120000 index 0000000..c628e21 --- /dev/null +++ b/crowpanel_p4_101/esp-idf-tools @@ -0,0 +1 @@ +../tanmatsu/esp-idf-tools \ No newline at end of file diff --git a/crowpanel_p4_101/main/CMakeLists.txt b/crowpanel_p4_101/main/CMakeLists.txt new file mode 100644 index 0000000..d24e746 --- /dev/null +++ b/crowpanel_p4_101/main/CMakeLists.txt @@ -0,0 +1,94 @@ +idf_build_get_property(idf_target IDF_TARGET) + +idf_component_register( + SRCS + "main.cpp" + PRIV_REQUIRES + esp_lcd # MIPI-DSI (EK79007) + esp_driver_i2c + esp_driver_gpio + fatfs # SD_MMC storage + nvs_flash + bt # NimBLE companion (BLE over the C6) + espressif__arduino-esp32 + esp_hosted # ESP32-C6 Wi-Fi/BLE over SDIO slot 1 + meshcore + lvgl + RadioLib # target.h (included by main.cpp) pulls + wadamesh_app + INCLUDE_DIRS + "." +) + +# Same warning policy as tdisplay_p4/main: vendored code must not fail the build on warnings. +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=unused-variable" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=unused-const-variable" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=stringop-truncation" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=stringop-overflow" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=array-bounds" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=restrict" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-Wno-error=maybe-uninitialized" APPEND) +idf_build_get_property(_co COMPILE_OPTIONS) +list(REMOVE_ITEM _co "-Werror=all") +idf_build_set_property(COMPILE_OPTIONS "${_co}") + +# ---- Elecrow CrowPanel Advanced 10.1" ESP32-P4 (EXPERIMENTAL) app define set. ------------------- +# Pins: Elecrow schematics V1.0/V1.1/V1.2 + their ESP-IDF lessons; per-pin status and sources in +# the pin table at the top of variants/crowpanel_p4_101/target.h. +# +# Board revision (build.sh WADA_CP_REV=1.0|1.1|1.2, default 1.2). Only two things differ: +# - the SX1262 DIO1/NRST pair (J9.1 / J11.1): GPIO53/54 on V1.0 + V1.1, GPIO27/28 on V1.2; +# - the C6 SDIO data-pin order (sdkconfig fragment, build.sh). +if(NOT DEFINED ENV{WADA_CP_REV} OR "$ENV{WADA_CP_REV}" STREQUAL "1.2") + set(CP_REV_DEFS CP_REV=12 P_LORA_DIO_1=27 P_LORA_RESET=28) + message(STATUS "wadamesh: CrowPanel P4 10.1 board revision V1.2") +elseif("$ENV{WADA_CP_REV}" STREQUAL "1.1") + set(CP_REV_DEFS CP_REV=11 P_LORA_DIO_1=53 P_LORA_RESET=54) + message(STATUS "wadamesh: CrowPanel P4 10.1 board revision V1.1") +elseif("$ENV{WADA_CP_REV}" STREQUAL "1.0") + set(CP_REV_DEFS CP_REV=10 P_LORA_DIO_1=53 P_LORA_RESET=54) + message(STATUS "wadamesh: CrowPanel P4 10.1 board revision V1.0") +else() + message(FATAL_ERROR "WADA_CP_REV must be 1.0, 1.1 or 1.2 (got '$ENV{WADA_CP_REV}')") +endif() + +set(WADA_DEFS + HAS_CROWPANEL_P4_101=1 ESP32_PLATFORM MC_VENDORED_TOUCH_APP UI_LVGL=1 HAS_TOUCH_UI=1 + ${CP_REV_DEFS} + USE_SX1262 RADIO_CLASS=CustomSX1262 WRAPPER_CLASS=CustomSX1262Wrapper + SX126X_RX_BOOSTED_GAIN=1 SX126X_CURRENT_LIMIT=140 + SX126X_DIO2_AS_RF_SWITCH=true SX126X_DIO3_TCXO_VOLTAGE=1.6f # Elecrow Lesson14: RadioLib defaults + 1.6 V TCXO + MULTI_TRANSPORT_COMPANION TCP_PORT=5000 WS_PORT=8765 BLE_PIN_CODE=123456 + LORA_FREQ=869.618 LORA_BW=62.5 LORA_SF=8 # EU 868 default like the other P4 boards; region is a setting + LORA_TX_POWER=22 MAX_LORA_TX_POWER=22 + MAX_CONTACTS=2000 MAX_GROUP_CHANNELS=40 # 32 MB PSRAM + ENABLE_PRIVATE_KEY_IMPORT=1 ENABLE_PRIVATE_KEY_EXPORT=1 + + # --- SX1262 on the external wireless header J9/J11 (SPI3). DIO1/NRST from the revision block. --- + P_LORA_NSS=10 P_LORA_BUSY=9 P_LORA_SCLK=8 P_LORA_MISO=7 P_LORA_MOSI=6 + + # --- Display: EK79007 1024x600 MIPI-DSI, native landscape, LVGL at native resolution. --- + DISPLAY_CLASS=EK79007Display SCREEN_WIDTH=1024 SCREEN_HEIGHT=600 + + # --- Touch GT911 + shared board I2C0 (codec, HY2.0 port). Wire is begun by ESP32Board::begin. --- + PIN_BOARD_SDA=45 PIN_BOARD_SCL=46 PIN_TOUCH_SDA=45 PIN_TOUCH_SCL=46 PIN_TOUCH_RST=40 PIN_TOUCH_INT=42 + + # --- microSD: SDMMC slot 0, 1-bit (only D0 is wired): CLK43 / CMD44 / D0 39. C6 is slot 1. --- + BOARD_HAS_SDMMC=1 BOARD_SDMMC_SLOT=0 +) +foreach(_d ${WADA_DEFS}) + idf_build_set_property(COMPILE_OPTIONS "-D${_d}" APPEND) +endforeach() + +# Firmware version/tag (build.sh passes WADA_FW_TAG/-DATE from git describe + date). +if(NOT WADA_FW_TAG) + set(WADA_FW_TAG "dev") +endif() +if(NOT WADA_FW_DATE) + set(WADA_FW_DATE "") +endif() +idf_build_set_property(COMPILE_OPTIONS "-DFIRMWARE_RELEASE_TAG=\"${WADA_FW_TAG}\"" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-DFIRMWARE_BUILD_DATE=\"${WADA_FW_DATE}\"" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-DFIRMWARE_VERSION=\"wadamesh ${WADA_FW_TAG}\"" APPEND) +# NOTE: no FIRMWARE_OTA_ENV on purpose. This is an experimental board with no release artifact on +# firmware.wadamesh.com yet, so the on-device update UI stays hidden (CAP_OTA 0 in device_caps.h). diff --git a/crowpanel_p4_101/main/idf_component.yml b/crowpanel_p4_101/main/idf_component.yml new file mode 100644 index 0000000..7b259c7 --- /dev/null +++ b/crowpanel_p4_101/main/idf_component.yml @@ -0,0 +1,12 @@ +dependencies: + idf: ">=5.5.1" + # Same component set as the T-Display P4 (esp-hosted 2.12.3 fork via tanmatsu-wifi, arduino-esp32 + # for the shared Arduino src/, esp-nimble-cpp for BLE over the C6). badge-bsp is not initialised on + # this board; it is here because tanmatsu-wifi depends on it. + badgeteam/badge-bsp: "^0.9.9" + nicolaielectronics/tanmatsu-wifi: "^1.1.2" + nicolaielectronics/tanmatsu-lora: "^0.3.0" + espressif/arduino-esp32: "3.3.10" + h2zero/esp-nimble-cpp: "^2.3.0" + # EK79007 MIPI-DSI panel driver (Elecrow's lessons pin ^1.0.2). + espressif/esp_lcd_ek79007: "^1.0.2" diff --git a/crowpanel_p4_101/main/main.cpp b/crowpanel_p4_101/main/main.cpp new file mode 100644 index 0000000..9852baf --- /dev/null +++ b/crowpanel_p4_101/main/main.cpp @@ -0,0 +1,238 @@ +// wadamesh on the Elecrow CrowPanel Advanced 10.1" ESP32-P4 HMI (1024x600). EXPERIMENTAL. +// +// Standalone ESP-IDF app, modelled on tdisplay_p4/main/main.cpp (same P4 + C6 esp-hosted pairing, +// same storage law: hot store on the internal LittleFS 'storage' partition, the microSD for tiles and +// bulk). The board has no I/O expander, so bring-up is: on-chip LDO rails -> EK79007 panel -> radio +// -> storage -> UI, and the C6 last. Board globals + radio_init() live in +// variants/crowpanel_p4_101/target.cpp. Pin map: the table in variants/crowpanel_p4_101/target.h. +#include "../../variants/tdisplay_p4/tdisplay_p4_compat.h" // adcAttachPin() shim, BEFORE target.h +#include +#include +#include "MyMesh.h" +#include +#include "esp_heap_caps.h" +#include "UITask.h" +#include "target.h" +#include +#include +#include +#include "../../src/helpers/esp32/MultiTransportCompanionInterface.h" +#include +#include +#include "../../src/helpers/esp32/BootTimeSync.h" +#include "esp_hosted.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "driver/gpio.h" +#include "lvgl.h" + +#ifndef TCP_PORT +#define TCP_PORT 5000 +#endif +#ifndef WS_PORT +#define WS_PORT 8765 +#endif + +#if defined(CONFIG_BT_NIMBLE_TRANSPORT_UART) +#error "CrowPanel P4 C6 requires NimBLE hosted HCI; disable CONFIG_BT_NIMBLE_TRANSPORT_UART" +#endif + +extern "C" bool hostedInitBLE(); // arduino-esp32 BLE controller bring-up over esp-hosted +extern "C" bool hostedSetPins(int8_t clk, int8_t cmd, int8_t d0, int8_t d1, int8_t d2, int8_t d3, int8_t rst); + +// C6 SDIO pin set. Arduino's hostedInit() does NOT take these from sdkconfig: it passes the generic +// esp32p4 variant's BOARD_SDIO_ESP_HOSTED_* (CLK18 CMD19 D0-D3 = 14,15,16,17, RST 54) to +// esp_hosted_sdio_set_config(), which overrides Kconfig. That order happens to match the T-Display +// P4 and the CrowPanel V1.0, but V1.1/V1.2 route D0..D3 to GPIO17..14, and C6_EN is GPIO32 on every +// revision. Must run before anything can start esp-hosted (Wi-Fi, BLE, boot time sync). +// The bus width (4-bit, or 1-bit on V1.0) still comes from the sdkconfig fragment. +static void crowpanelC6Pins() { +#if CP_REV == 10 + const bool ok = hostedSetPins(18, 19, 14, 15, 16, 17, 32); +#else + const bool ok = hostedSetPins(18, 19, 17, 16, 15, 14, 32); +#endif + printf("[BOOT] C6 SDIO pins set for V%d.%d: %s\n", CP_REV / 10, CP_REV % 10, ok ? "ok" : "FAILED"); +} + +// Boot-trace hooks the shared code defines on the S3 boards (src/main.cpp, excluded here). +volatile int g_boot_phase = 0; +extern "C" void set_boot_phase(int phase) { g_boot_phase = phase; } + +// App globals: src/main.cpp declares these on the S3 boards; this build owns them. +DataStore store(LittleFS, rtc_clock); +bool g_fs_ok = false; // internal 'storage' (LittleFS) mounted +bool g_sd_ok = false; // microSD mounted +MultiTransportCompanionInterface serial_interface; +StdRNG fast_rng; +SimpleMeshTables tables; +UITask ui_task(&board, &serial_interface); +static MyMesh& makeTheMesh() { + void* mem = heap_caps_malloc(sizeof(MyMesh), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (!mem) mem = malloc(sizeof(MyMesh)); + return *new (mem) MyMesh(radio_driver, fast_rng, rtc_clock, tables, store, &ui_task); +} +MyMesh& the_mesh = makeTheMesh(); + +static void wadameshSetup() { + Serial.begin(115200); + delay(150); + printf("[BOOT] wadamesh / CrowPanel P4 10.1 (board revision V%d.%d, EXPERIMENTAL)\n", CP_REV / 10, CP_REV % 10); + + // 1. On-chip LDO rails (DSI PHY + the 3.3 V pad domain of touch/LCD-reset/SD), backlight + amp off. + crowpanelP4PowerOn(); + + // 2. Shared I2C0 (GT911, codec, HY2.0 port) on 45/46. + board.begin(); + + // 3. EK79007 panel, before LVGL flushes to it. + if (!display.begin()) printf("[BOOT] EK79007 display init FAILED\n"); + + // 4. lwIP/tcpip + default event loop before any Wi-Fi/netif call (esp_wifi_remote needs it). + esp_netif_init(); + esp_event_loop_create_default(); + crowpanelC6Pins(); + printf("[BOOT] C6 = esp-hosted (lazy Wi-Fi/BLE initialization)\n"); + + // 5. Radio (SX1262 on J9/J11). + if (!radio_init()) printf("[BOOT] radio_init FAILED\n"); + + DisplayDriver* disp = &display; + + // 6. Storage: internal LittleFS 'storage' = the hot store; the microSD = tiles, backups, bulk. + g_fs_ok = LittleFS.begin(true, "/lfs", 10, "storage"); + printf("[storage] LittleFS(storage) = %s\n", g_fs_ok ? "OK" : "FAILED"); + if (g_fs_ok) { LittleFS.mkdir("/identity"); LittleFS.mkdir("/bl"); } + // microSD on SDMMC slot 0, 1-bit: the J5 slot only has D0 routed (CLK43 / CMD44 / D0 39), as in + // Elecrow's Lesson08. Pad power is LDO4, which crowpanelP4PowerOn() already holds, so SD_MMC must + // not try to claim it again (-1 = external power). The card has pull-ups on CMD/D0 (R27/R28); the + // build also enables the internal ones (build.sh patch), as Elecrow does. + SD_MMC.setPowerChannel(-1); + SD_MMC.setPins(43, 44, 39); + for (int p : {44, 39}) gpio_pullup_en((gpio_num_t)p); + { + struct { int khz; const char* tag; } tries[] = { + { SDMMC_FREQ_DEFAULT, "1-bit 20MHz" }, + { 10000, "1-bit 10MHz (Elecrow)" }, + { 5000, "1-bit 5MHz" }, + }; + sdMountDiagBegin(); + uint32_t mounted_hz = 0; + for (auto& t : tries) { + const bool begin_ok = SD_MMC.begin("/sdcard", true, false, t.khz); + g_sd_ok = begin_ok && SD_MMC.cardType() != CARD_NONE; + sdMountDiagAttempt((uint32_t)t.khz * 1000u, begin_ok, g_sd_ok); + printf("[storage] SD_MMC try %s -> %s\n", t.tag, g_sd_ok ? "OK" : "fail"); + if (g_sd_ok) { mounted_hz = (uint32_t)t.khz * 1000u; break; } + SD_MMC.end(); + delay(120); + } + sdMountDiagSetMounted(g_sd_ok, mounted_hz); + } + printf("[storage] SD_MMC = %s\n", g_sd_ok ? "OK" : "no card"); + store.begin(); // internal LittleFS is the DataStore root (P4 storage law) + + the_mesh.begin(disp != NULL); + + wifiConfigBegin(); // NVS only, no radio + + serial_interface.begin(Serial, TCP_PORT, WS_PORT); + serial_interface.setBroadcastResponses(true); + the_mesh.startInterface(serial_interface); + + sensors.begin(); + ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); + board.onBootComplete(); + printf("[BOOT] setup done\n"); + + // ---- C6 bring-up, AFTER the UI ---------------------------------------------------------------- + // A C6 that does not answer (wrong firmware, wrong SDIO pin set for the revision) must cost Wi-Fi + // and BLE only: display, touch and LoRa are already up by now. +#if defined(BLE_PIN_CODE) + char* nm = the_mesh.getNodePrefs()->node_name; + serial_interface.prepareBle("wadamesh-", nm, the_mesh.getBLEPin()); + if (wifiConfigGetBleEnabled()) { + if (hostedInitBLE()) serial_interface.beginBle("wadamesh-", nm, the_mesh.getBLEPin()); + else printf("[BOOT] hostedInitBLE FAILED\n"); + } else { + printf("[BOOT] BLE off in settings: C6 not started for it\n"); + } +#endif + { + uint32_t synced_epoch = 0; + const BootTimeSyncResult r = bootTimeSyncRun(rtc_clock.timeIsCurrent(), synced_epoch); + if (r == BootTimeSyncResult::Ok) { + rtc_clock.setCurrentTime(synced_epoch); + printf("[BOOT] cold-boot time sync ok: %lu\n", (unsigned long)synced_epoch); + } else if (r != BootTimeSyncResult::Skipped) { + printf("[BOOT] cold-boot time sync: %s\n", bootTimeSyncResultName(r)); + } + } +} + +extern "C" void app_main(void) { + crowpanelP4InstallFullRestart(); // before anything else registers a shutdown handler + initArduino(); + wadameshSetup(); + + // Wi-Fi state machine + SNTP + TCP/WS companion server, as in tdisplay_p4/main/main.cpp + // (esp-hosted backend: Arduino's real WiFi over esp_wifi_remote). + bool wifi_started = false, wifi_radio_prev = true, wifi_radio_inited = false; + bool sntp_kicked = false, sntp_pushed = false, modem_sleep_set = false; + uint32_t last_wifi_retry_ms = 0; + const uint32_t WIFI_RETRY_INTERVAL_MS = 10000; + + for (;;) { + ui_task.loop(); + + bool wifi_radio_en = wifiConfigWantsWifi(); + if (!wifi_radio_inited) { + wifi_radio_inited = true; + wifi_radio_prev = wifi_radio_en; + } else if (wifi_radio_en != wifi_radio_prev) { + wifi_radio_prev = wifi_radio_en; + if (!wifi_radio_en) { WiFi.disconnect(true); delay(50); WiFi.mode(WIFI_OFF); } + wifi_started = false; + } + if (wifiConfigConsumeApplyRequest()) { + if (wifi_started) { + if (!wifi_radio_en) { WiFi.disconnect(true); delay(50); WiFi.mode(WIFI_OFF); } + else { WiFi.disconnect(false, false); delay(50); } + } + wifi_started = false; last_wifi_retry_ms = 0; + } + if (wifi_radio_en) { + if (!wifi_started) { + wifi_started = true; + WiFi.mode(WIFI_STA); + if (wifiConfigHasRuntime()) { + char ssid[WIFI_CONFIG_SSID_MAX], pwd[WIFI_CONFIG_PWD_MAX]; + wifiConfigGetSsid(ssid, sizeof(ssid)); wifiConfigGetPwd(pwd, sizeof(pwd)); + if (strlen(ssid) > 0) { WiFi.begin(ssid, pwd[0] ? pwd : nullptr); last_wifi_retry_ms = millis(); } + } + } + if (wifiConfigHasRuntime() && WiFi.status() != WL_CONNECTED) { + uint32_t now = millis(); + if ((uint32_t)(now - last_wifi_retry_ms) >= WIFI_RETRY_INTERVAL_MS) { + last_wifi_retry_ms = now; + char ssid[WIFI_CONFIG_SSID_MAX], pwd[WIFI_CONFIG_PWD_MAX]; + wifiConfigGetSsid(ssid, sizeof(ssid)); wifiConfigGetPwd(pwd, sizeof(pwd)); + if (strlen(ssid) > 0) { WiFi.disconnect(false, true); WiFi.begin(ssid, pwd[0] ? pwd : nullptr); } + } + } + if (WiFi.status() == WL_CONNECTED) { + if (!modem_sleep_set) { WiFi.setSleep(true); modem_sleep_set = true; } + serial_interface.startTcpServer(true); + if (!sntp_pushed) { + if (!sntp_kicked) { configTime(0, 0, "pool.ntp.org", "time.nist.gov"); sntp_kicked = true; } + const uint32_t e = (uint32_t)time(nullptr); + if (e > 1700000000) { rtc_clock.setCurrentTime(e); sntp_pushed = true; printf("[TIME] SNTP -> rtc %lu\n", (unsigned long)e); } + } + } + } + serial_interface.tickWebSocketHandshake(); + sensors.loop(); + the_mesh.loop(); + vTaskDelay(1); + } +} diff --git a/crowpanel_p4_101/partition_tables/crowpanel_p4_101_16M.csv b/crowpanel_p4_101/partition_tables/crowpanel_p4_101_16M.csv new file mode 100644 index 0000000..3914fb1 --- /dev/null +++ b/crowpanel_p4_101/partition_tables/crowpanel_p4_101_16M.csv @@ -0,0 +1,12 @@ +# Elecrow CrowPanel P4 10.1: standalone 16 MB partition table (same layout as the T-Display P4). +# NOT an AppFS/launcher app (that's the Tanmatsu): this flashes bootloader+app directly. +# Dual OTA app slots (4 MB each; the P4 wadamesh image is ~2.5-3 MB) + a big FAT for +# identity/prefs/contacts/chat history + map tiles. +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 24K, +otadata, data, ota, 0xF000, 8K, +phy_init, data, phy, 0x11000, 4K, +ota_0, app, ota_0, 0x20000, 4M, +ota_1, app, ota_1, 0x420000, 4M, +storage, data, fat, 0x820000, 8000K, +coredump, data, coredump, 0xFF0000, 64K, diff --git a/crowpanel_p4_101/sdkconfigs/crowpanel_p4_101 b/crowpanel_p4_101/sdkconfigs/crowpanel_p4_101 new file mode 100644 index 0000000..ff6aaad --- /dev/null +++ b/crowpanel_p4_101/sdkconfigs/crowpanel_p4_101 @@ -0,0 +1,58 @@ +# Elecrow CrowPanel Advanced 10.1" ESP32-P4 HMI (1024x600) board sdkconfig fragment. EXPERIMENTAL. +# Modelled on sdkconfigs/tdisplay_p4 (same ESP32-P4NRW32 + ESP32-C6-MINI-1 esp-hosted pairing). The +# C6 SDIO pin/width set lives in a per-revision fragment (sdio_v10 / sdio_v11), layered after this. +CONFIG_IDF_TARGET="esp32p4" + +# 32 MB HEX PSRAM @ 200 MHz (in-package on the P4NRW32; Elecrow's V1.0/V1.1 lessons run HEX 200M). +# 200 MHz is behind CONFIG_IDF_EXPERIMENTAL_FEATURES on IDF 5.5; without it Kconfig drops to 20 MHz +# and the DPI panel underruns fetching its PSRAM framebuffer. +CONFIG_SPIRAM=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536 + +# 16 MB W25Q128 + our standalone dual-OTA partition table (same layout as the T-Display P4). +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_tables/crowpanel_p4_101_16M.csv" + +# MIPI-DSI (EK79007) +CONFIG_LCD_DSI_ISR_IRAM_SAFE=y + +# ESP32-C6-MINI-1 co-processor over SDIO slot 1 (CLK18 / CMD19; data pins per revision fragment). +# Factory C6 firmware is esp-hosted MCU 2.12.3 (Elecrow "Advance_P4_On-Board_ESP32C6_Firmware_V2.12.3"), +# the same version as the hosted fork tanmatsu-wifi pulls in. +CONFIG_SLAVE_IDF_TARGET_ESP32C6=y +CONFIG_ESP_HOSTED_CP_TARGET_ESP32C6=y +CONFIG_ESP_HOSTED_SDIO_CLOCK_FREQ_KHZ=40000 +# C6_EN = GPIO32 (schematic, all revisions). Reset through hosted_reset_slave_callback() in +# variants/crowpanel_p4_101/target.cpp, so the polarity and the GPIO are explicit in code. +CONFIG_ESP_HOSTED_SLAVE_RESET_ONLY_IF_NECESSARY=y +CONFIG_ESP_HOSTED_RESET_SLAVE_USING_CALLBACK=y +CONFIG_ESP_HOSTED_SDIO_RESET_SLAVE_USING_CALLBACK=y +CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y +# The P4 has no Bluetooth controller of its own: NimBLE's HCI runs over esp-hosted SDIO. +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_CONTROLLER_DISABLED=y +# CONFIG_BT_NIMBLE_TRANSPORT_UART is not set +# A C6 that cannot be reached must cost Wi-Fi and BLE, never the device (see sdkconfigs/tdisplay_p4). +CONFIG_ESP_HOSTED_HOST_RESTART_NO_COMMUNICATION_WITH_SLAVE=n +CONFIG_ESP_HOSTED_TRANSPORT_RESTART_ON_FAILURE=n + +# Panic coredump -> the 64K 'coredump' partition (ELF, decodable against the build's elf). +CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y +CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y +CONFIG_ESP_COREDUMP_CHECKSUM_CRC32=y +CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=64 + +# DPI/video panel: the 1.2 MB framebuffer lives in PSRAM and is streamed continuously, so the same +# underrun mitigations as the T-Display P4: run code from PSRAM (flash writes no longer drop the +# cache under the DSI DMA), a 256 KB L2 cache with 128 B lines, -O2. +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=n +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_CACHE_L2_CACHE_128KB=n +CONFIG_CACHE_L2_CACHE_256KB=y +CONFIG_CACHE_L2_CACHE_LINE_64B=n +CONFIG_CACHE_L2_CACHE_LINE_128B=y diff --git a/crowpanel_p4_101/sdkconfigs/general b/crowpanel_p4_101/sdkconfigs/general new file mode 120000 index 0000000..4f2d3dc --- /dev/null +++ b/crowpanel_p4_101/sdkconfigs/general @@ -0,0 +1 @@ +../../tanmatsu/sdkconfigs/general \ No newline at end of file diff --git a/crowpanel_p4_101/sdkconfigs/sdio_v10 b/crowpanel_p4_101/sdkconfigs/sdio_v10 new file mode 100644 index 0000000..462954a --- /dev/null +++ b/crowpanel_p4_101/sdkconfigs/sdio_v10 @@ -0,0 +1,9 @@ +# CrowPanel P4 10.1 V1.0: the C6 data lines run the other way round (schematic: GPIO14 = SD2_D0 -> +# C6 IO20 ... GPIO17 = SD2_D3 -> C6 IO23), and V1.0 has none of the series resistors / caps V1.1 +# added on those lines. Elecrow's V1.0 Lesson17 runs the link 1-bit (D0 = GPIO14, D1 = GPIO15 as the +# interrupt line), so this does the same. +CONFIG_ESP_HOSTED_SDIO_1_BIT_BUS=y +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=18 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=19 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=14 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_1BIT_BUS_SLOT_1=15 diff --git a/crowpanel_p4_101/sdkconfigs/sdio_v11 b/crowpanel_p4_101/sdkconfigs/sdio_v11 new file mode 100644 index 0000000..898eb80 --- /dev/null +++ b/crowpanel_p4_101/sdkconfigs/sdio_v11 @@ -0,0 +1,10 @@ +# CrowPanel P4 10.1 V1.1 and V1.2: C6 SDIO 4-bit, P4 GPIO17/16/15/14 = D0/D1/D2/D3. +# Source: V1.1/V1.2 schematics (net SD2_D0 = GPIO17 -> C6 IO20 ... SD2_D3 = GPIO14 -> C6 IO23) and +# Elecrow's V1.1/V1.2 Lesson17 sdkconfig (4-bit, same pins). +CONFIG_ESP_HOSTED_SDIO_4_BIT_BUS=y +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=18 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=19 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=17 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=16 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=15 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=14 diff --git a/crowpanel_p4_101/sdkconfigs/wadamesh b/crowpanel_p4_101/sdkconfigs/wadamesh new file mode 120000 index 0000000..5339545 --- /dev/null +++ b/crowpanel_p4_101/sdkconfigs/wadamesh @@ -0,0 +1 @@ +../../tanmatsu/sdkconfigs/wadamesh \ No newline at end of file diff --git a/scripts/build/gen-touch-fonts.sh b/scripts/build/gen-touch-fonts.sh index d1ba58b..a7bd3db 100755 --- a/scripts/build/gen-touch-fonts.sh +++ b/scripts/build/gen-touch-fonts.sh @@ -414,7 +414,7 @@ generate_fallback_icons() { # some translation units and not others. 18/20/24 are the bigger UI-size presets # (every board with a text-size setting; the V4 has none, its flash is full). # DOC_CAPTURE builds carry them all: they render other boards' sizes and scales for review. -ui_big="defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE)" +ui_big="defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE)" for size in 12 14 16 28; do generate_ui "$size" done diff --git a/src/DataStore.cpp b/src/DataStore.cpp index 9a49752..529d56b 100644 --- a/src/DataStore.cpp +++ b/src/DataStore.cpp @@ -10,7 +10,7 @@ #endif #include "helpers/esp32/WdtHeavyGuard.h" // suspend core-0 idle WDT during the (SPIFFS-GC-prone) contact write #endif -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) #include #endif @@ -474,7 +474,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no } bool DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_lon) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { struct A { DataStore* d; const NodePrefs* p; double la, lo; bool r; } a{ this, &_prefs, node_lat, node_lon, false }; p4StorageCall([](void* p){ auto* a = (A*)p; a->r = a->d->savePrefs(*a->p, a->la, a->lo); }, &a); @@ -733,7 +733,7 @@ bool DataStore::saveContactsInPlace(DataStoreHost* host, bool (*filter)(const Co #endif void DataStore::saveContacts(DataStoreHost* host, bool (*filter)(const ContactInfo& c)) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { struct A { DataStore* d; DataStoreHost* h; bool (*f)(const ContactInfo&); } a{ this, host, filter }; p4StorageCall([](void* p){ auto* a = (A*)p; a->d->saveContacts(a->h, a->f); }, &a); @@ -896,7 +896,7 @@ void DataStore::loadChannels(DataStoreHost* host) { } void DataStore::saveChannels(DataStoreHost* host) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { struct A { DataStore* d; DataStoreHost* h; } a{ this, host }; p4StorageCall([](void* p){ auto* a = (A*)p; a->d->saveChannels(a->h); }, &a); @@ -1073,7 +1073,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b } bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { struct A { DataStore* d; const uint8_t* k; int kl; const uint8_t* b; uint8_t l; bool r; } a{ this, key, key_len, src_buf, len, false }; p4StorageCall([](void* p){ auto* a = (A*)p; a->r = a->d->putBlobByKey(a->k, a->kl, a->b, a->l); }, &a); @@ -1117,7 +1117,7 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src return false; // error } bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { struct A { DataStore* d; const uint8_t* k; int kl; bool r; } a{ this, key, key_len, false }; p4StorageCall([](void* p){ auto* a = (A*)p; a->r = a->d->deleteBlobByKey(a->k, a->kl); }, &a); @@ -1150,7 +1150,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b } bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { struct A { DataStore* d; const uint8_t* k; int kl; const uint8_t* b; uint8_t l; bool r; } a{ this, key, key_len, src_buf, len, false }; p4StorageCall([](void* p){ auto* a = (A*)p; a->r = a->d->putBlobByKey(a->k, a->kl, a->b, a->l); }, &a); @@ -1172,7 +1172,7 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src } bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { struct A { DataStore* d; const uint8_t* k; int kl; bool r; } a{ this, key, key_len, false }; p4StorageCall([](void* p){ auto* a = (A*)p; a->r = a->d->deleteBlobByKey(a->k, a->kl); }, &a); @@ -1210,7 +1210,7 @@ bool DataStore::useSdStorage() { } #endif -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) // Full-store adoption on an SD_MMC slot. P4 callers migrate internal files // first; the Wio Tracker L2 mounts and adopts the card directly at boot. bool DataStore::useSdMmcStorage() { @@ -1234,7 +1234,7 @@ bool DataStore::useSdMmcStorage() { } #endif -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // #167: core-0 storage dispatcher. The DSI panel's frame-restart ISR lives on core 1 (where // Arduino's loop task also runs); SDMMC/FATFS write paths entered FROM core 1 mask that core's // interrupts inside their critical sections long enough to drop a display frame -- the diff --git a/src/DataStore.h b/src/DataStore.h index 489d439..428a5e3 100644 --- a/src/DataStore.h +++ b/src/DataStore.h @@ -47,7 +47,7 @@ public: // if the dir can't be created. No-op on non-ESP32. bool useSdStorage(); #endif -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) // Full-store adoption of an SD_MMC card. P4 callers migrate internal files // first; the Wio Tracker L2 adopts the card directly at boot. bool useSdMmcStorage(); @@ -121,7 +121,7 @@ private: FILESYSTEM* _getContactsChannelsFS() const { if (_fsExtra) return _fsExtra; return _fs;}; }; -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // #167: run fn(arg) on the dedicated CORE-1 storage task, blocking the caller. See DataStore.cpp. void p4StorageCall(void (*fn)(void*), void* arg); bool p4OnStorageTask(); // true when the current task IS the storage task (wrapper re-entry guard) diff --git a/src/MyMesh.cpp b/src/MyMesh.cpp index c20cb9b..9f71bf4 100644 --- a/src/MyMesh.cpp +++ b/src/MyMesh.cpp @@ -12,7 +12,7 @@ #endif #ifdef ESP32 #include // esp_restart for the "bootloader" CLI command -#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) +#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) && !defined(HAS_CROWPANEL_P4_101) #include // RTC_CNTL_OPTION1_REG / FORCE_DOWNLOAD_BOOT (S3-only; both P4 boards lack it) #endif #endif @@ -740,7 +740,7 @@ bool MyMesh::handleMeshcomodCommand(const char* text, int text_len) { // rest of RTC_CNTL_OPTION1 and wedges the RTC so esp_restart() hangs instead // of resetting (that was the earlier "freeze"). board.reboot() == esp_restart, // the proven reset path on this board; the RTC bit survives it. -#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) +#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) && !defined(HAS_CROWPANEL_P4_101) uint32_t opt1 = REG_READ(RTC_CNTL_OPTION1_REG); REG_WRITE(RTC_CNTL_OPTION1_REG, opt1 | RTC_CNTL_FORCE_DOWNLOAD_BOOT); #endif diff --git a/src/helpers/esp32/BootTimeSync.cpp b/src/helpers/esp32/BootTimeSync.cpp index 8aae0b6..e90154d 100644 --- a/src/helpers/esp32/BootTimeSync.cpp +++ b/src/helpers/esp32/BootTimeSync.cpp @@ -21,7 +21,7 @@ // The T-Display P4 joins on its esp-hosted build (see CAP_BOOT_TIME_SYNC), and the // Wio Tracker L2 (no RTC) joins too. #if defined(HAS_TDECK_GT911) || defined(HAS_THINKNODE_M9) || defined(HAS_WIO_TRACKER_L2) || \ - (defined(HAS_TDISPLAY_P4) && TDP4_C6_HOSTED) + (defined(HAS_TDISPLAY_P4) && TDP4_C6_HOSTED) || defined(HAS_CROWPANEL_P4_101) #define BOOT_TIME_SYNC_SUPPORTED 1 #else #define BOOT_TIME_SYNC_SUPPORTED 0 diff --git a/src/helpers/esp32/MqttBridge.cpp b/src/helpers/esp32/MqttBridge.cpp index 0422844..4e31d49 100644 --- a/src/helpers/esp32/MqttBridge.cpp +++ b/src/helpers/esp32/MqttBridge.cpp @@ -5,7 +5,7 @@ // makes MqttBridge a no-op stub on the Tanmatsu, and the real bridge below on all other boards. MqttBridge mqtt_bridge; -#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) // ---- real PubSubClient/WiFiClient implementation; NOT built on the P4 boards ---- +#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) && !defined(HAS_CROWPANEL_P4_101) // ---- real PubSubClient/WiFiClient implementation; NOT built on the P4 boards ---- #include "SdNvsPrefs.h" // NOT raw NVS Preferences: the touch firmware abandoned NVS (tiny, shared, // doesn't survive Launcher) for a file-backed store. MQTT was the last setting // still on NVS, so its writes silently failed / didn't persist (GH #128). diff --git a/src/helpers/esp32/MqttBridge.h b/src/helpers/esp32/MqttBridge.h index 18997ac..38aa60a 100644 --- a/src/helpers/esp32/MqttBridge.h +++ b/src/helpers/esp32/MqttBridge.h @@ -29,7 +29,7 @@ struct MqttBridgeStatus { uint32_t retryAtMs = 0; }; -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // ---- MQTT DISABLED on the ESP32-P4 boards (Tanmatsu + T-Display P4) ---- // The P4's Wi-Fi runs through the esp-hosted C6 co-processor; the Arduino WiFiClient / PubSubClient // path this bridge uses is not wired for that and crashed at boot. Provide a no-op bridge with the diff --git a/src/helpers/esp32/MultiTransportCompanionInterface.cpp b/src/helpers/esp32/MultiTransportCompanionInterface.cpp index 7a00c47..f70f38c 100644 --- a/src/helpers/esp32/MultiTransportCompanionInterface.cpp +++ b/src/helpers/esp32/MultiTransportCompanionInterface.cpp @@ -218,7 +218,7 @@ void MultiTransportCompanionInterface::beginBle(const char* prefix, char* name, if (!_ble_phone_paused) _ble.enable(); } -#if defined(HAS_TDISPLAY_P4) && defined(TDP4_C6_HOSTED) && TDP4_C6_HOSTED +#if (defined(HAS_TDISPLAY_P4) && defined(TDP4_C6_HOSTED) && TDP4_C6_HOSTED) || defined(HAS_CROWPANEL_P4_101) extern "C" bool hostedInitBLE(); // arduino-esp32 esp32-hal-hosted: C6 BLE controller bring-up extern "C" bool hostedIsBLEActive(); #endif @@ -259,7 +259,7 @@ void MultiTransportCompanionInterface::enableBle() { s_ble_fail = BLE_FAIL_HEAP; return; } -#if defined(HAS_TDISPLAY_P4) && defined(TDP4_C6_HOSTED) && TDP4_C6_HOSTED +#if (defined(HAS_TDISPLAY_P4) && defined(TDP4_C6_HOSTED) && TDP4_C6_HOSTED) || defined(HAS_CROWPANEL_P4_101) // The P4's Bluetooth radio is the C6's controller: at boot main.cpp brings it up only // when BLE is on, so a start from the toggles has to do it here, or NimBLE comes up // against no controller. The user asked for Bluetooth, so the esp-hosted handshake's diff --git a/src/helpers/esp32/WebFileTransferConfig.h b/src/helpers/esp32/WebFileTransferConfig.h index 8831f84..d726092 100644 --- a/src/helpers/esp32/WebFileTransferConfig.h +++ b/src/helpers/esp32/WebFileTransferConfig.h @@ -9,7 +9,7 @@ #if defined(MULTI_TRANSPORT_COMPANION) && \ (defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(TLORA_PAGER) || \ defined(HAS_THINKNODE_M9) || defined(HELTEC_LORA_V4_R8) || \ - defined(HAS_WIO_TRACKER_L2) || defined(HAS_CROWPANEL_35) || defined(HAS_TDISPLAY_P4) || \ + defined(HAS_WIO_TRACKER_L2) || defined(HAS_CROWPANEL_35) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || \ defined(HAS_TANMATSU) || defined(ATTAKY_MESH_SERIES) || defined(HAS_CROWPANEL_50)) #define WADA_WEB_FILE_TRANSFER 1 #else @@ -17,7 +17,7 @@ #endif #if WADA_WEB_FILE_TRANSFER && \ - (defined(HAS_WIO_TRACKER_L2) || defined(HAS_TDISPLAY_P4) || \ + (defined(HAS_WIO_TRACKER_L2) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || \ defined(HAS_TANMATSU)) #define WADA_WEB_FILE_TRANSFER_SDMMC 1 #else diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index f6f6792..1d6f75c 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -133,7 +133,7 @@ static void* wadaMp3Scratch() { return s_wada_mp3_scratch; } #ifndef PIN_I2S_SDIN #define PIN_I2S_SDIN 17 #endif -#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) #include // internal FAT partition (Tanmatsu 'locfd' / P4 'storage') #include // microSD on the P4-class boards' SDMMC slot 0; slot 1 = C6 radio extern bool g_fs_ok; // set in main.cpp once the internal FAT is mounted @@ -226,6 +226,8 @@ static_assert(ChannelSenderSplit::kMaxWireName >= (size_t)UITask::MAX_SENDER_NAM #else #include // RM69A10 MIPI-DSI AMOLED (default SKU) on the T-Display P4 #endif + #elif defined(HAS_CROWPANEL_P4_101) + #include // EK79007 MIPI-DSI IPS, 1024x600, on the CrowPanel P4 10.1 #else #include #endif @@ -293,6 +295,8 @@ static_assert(ChannelSenderSplit::kMaxWireName >= (size_t)UITask::MAX_SENDER_NAM extern LGFXDisplay display; #elif defined(HAS_TDISPLAY_P4) extern DISPLAY_CLASS display; // RM69A10Display (AMOLED) or HI8561Display (LCD) — set in CMakeLists + #elif defined(HAS_CROWPANEL_P4_101) + extern DISPLAY_CLASS display; // EK79007Display #else extern ST7789LCDDisplay display; #endif @@ -9556,7 +9560,7 @@ static void kbShowRotateArrows(bool show) { // control. The arrows are not created on these boards; every other reference to // the two pointers is null-guarded, so leaving them null is enough to remove // the buttons everywhere — the same pattern the retired s_kb_alt_btn uses. -#if !defined(HAS_WIO_TRACKER_L2) && !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) +#if !defined(HAS_WIO_TRACKER_L2) && !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) && !defined(HAS_CROWPANEL_P4_101) static void kbRotLeftCb(lv_event_t* e) { if (lv_event_get_code(e) != LV_EVENT_CLICKED) return; // Tap the left arrow: rotate landscape that way; tap again returns to portrait. @@ -15686,12 +15690,16 @@ static void screenTimeoutSliderReleasedCb(lv_event_t* e) { #if defined(ESP32) #if defined(HAS_TDISPLAY_P4) esp_reset_reason_t tdisplayP4ResetReason(); // variants/tdisplay_p4/target.cpp +#elif defined(HAS_CROWPANEL_P4_101) +esp_reset_reason_t crowpanelP4ResetReason(); // variants/crowpanel_p4_101/target.cpp #endif // esp_reset_reason(), except on the T-Display P4, where every software restart is made a full system // reset (so the panel does not come back dark) and would otherwise read as a watchdog crash. static esp_reset_reason_t bootResetReason() { #if defined(HAS_TDISPLAY_P4) return tdisplayP4ResetReason(); +#elif defined(HAS_CROWPANEL_P4_101) + return crowpanelP4ResetReason(); #else return esp_reset_reason(); #endif @@ -18087,7 +18095,7 @@ static void buildLockPage() { }); #endif } -#if !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) // no unlock control there, so no lock to put a PIN on +#if !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) && !defined(HAS_CROWPANEL_P4_101) // no unlock control there, so no lock to put a PIN on { // PIN lock: off until switched on here. On asks for a new PIN twice; off and Change // ask for the current one first. The switch follows the PIN, not the tap. @@ -24155,13 +24163,13 @@ static char s_fm_path[160] = {0}; // current dir within s_fm_fs (e.g. // fmRefresh, so they blip the LED too. #if CAP_SD || defined(TLORA_PAGER) static inline bool fmIsSd(fs::FS* fs) { return fs == &SD; } // hardware or software SPI SD -#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) static inline bool fmIsSd(fs::FS* fs) { return fs == &SD_MMC; } // microSD on SDMMC slot 0 #else static inline bool fmIsSd(fs::FS*) { return false; } // Heltec: no Arduino SD global in this FM path #endif static inline void fmMarkSdIo() { if (fmIsSd(s_fm_fs)) markSdIo(); } -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // Arduino loop-task WDT shim: the P4 IDF builds (Tanmatsu + T-Display P4) don't expose feedLoopWDT // (the long file-copy loop calls it). The IDF task WDT isn't armed on our LVGL loop, so a no-op is safe. static inline void feedLoopWDT() {} @@ -25641,7 +25649,7 @@ static void fmInternalClickCb(lv_event_t* e) { if (lv_event_get_code(e) != LV_EVENT_CLICKED) return; #if defined(HAS_TANMATSU) fmOpenStorage(&FFat, "Internal", "/"); // internal FAT data partition (locfd) -#elif defined(HAS_TDISPLAY_P4) +#elif defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) fmOpenStorage(&LittleFS, "Internal", "/"); // internal LittleFS 'storage' partition #else fmOpenStorage(&SPIFFS, "Internal", "/"); @@ -27088,7 +27096,7 @@ static bool fmOpenInReader(const char* name) { bool on_card = false; #if CAP_SD || defined(TLORA_PAGER) on_card = s_fm_fs == &SD; -#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) on_card = s_fm_fs == &SD_MMC; #endif if (!on_card) return false; @@ -27307,7 +27315,7 @@ static void fmRefresh() { } // Roots screen: list the available storages. -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) // ---- Tanmatsu / T-Display P4 microSD (SDMMC slot 0) ---------------------------------------------- // The card sits on the P4's SDMMC *slot 0* (IOMUX pins CLK43/CMD44/D0-3 39-42, selected by the // BOARD_SDMMC_SLOT=0 build define); slot 1 is the C6 radio (esp-hosted / ESP-AT SDIO), which we never @@ -27372,6 +27380,11 @@ static bool tanSdTryMount() { // not Arduino's 40 MHz HIGHSPEED default. attempted = true; begin_ok = SD_MMC.begin("/sdcard", false /*4-bit*/, false, SDMMC_FREQ_DEFAULT); +#elif defined(HAS_CROWPANEL_P4_101) + // Hot-insert path. The J5 slot only has D0 wired, so 1-bit; pins + external pad power were set by + // main.cpp (SD_MMC keeps them across end/begin). + attempted = true; + begin_ok = SD_MMC.begin("/sdcard", true /*1-bit*/, false, SDMMC_FREQ_DEFAULT); #else attempted = true; begin_ok = SD_MMC.begin("/sdcard", false /*4-bit*/); @@ -27430,7 +27443,10 @@ static void fmShowRoots() { if (s_fm_path_lbl) lv_label_set_text(s_fm_path_lbl, TR("Storage")); char sub[48], us[16], ts[16]; -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#if defined(HAS_CROWPANEL_P4_101) + fmFmtSize(LittleFS.usedBytes(), us, sizeof us); // "Internal" = the LittleFS 'storage' partition + fmFmtSize(LittleFS.totalBytes(), ts, sizeof ts); +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) fmFmtSize(FFat.usedBytes(), us, sizeof us); // "Internal" = the FAT data partition (locfd / 'storage') fmFmtSize(FFat.totalBytes(), ts, sizeof ts); #else @@ -27493,7 +27509,7 @@ static void fmShowRoots() { lv_obj_add_event_cb(sd, fmSdRetryMountCb, LV_EVENT_CLICKED, nullptr); } #endif -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) // microSD (SDMMC slot 0). Probe outside the mount-backoff window so an absent card doesn't grind // the bus on every render; tapping the row forces a fresh mount attempt (tanSdClickCb clears it). if ((s_tan_sd_mounted || millis() >= s_tan_sd_retry_after) && tanSdTryMount()) { @@ -28150,7 +28166,7 @@ static ReaderLocalResult readerReadLocal(const char* url, uint8_t* raw, size_t c storage_claimed = true; if (sdAdoptLiveMount()) storage = &SD; else if (!sdRuntimeLifecycleBusy() && fmSdTryMount()) storage = &SD; -#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) s_reader_sd_busy = true; s_reader_sd_owner = xTaskGetCurrentTaskHandle(); storage_claimed = true; @@ -34495,6 +34511,16 @@ static int wifiScanWatchdogSafe(uint32_t cap_ms, uint16_t per_chan_ms = 300) { #define WADA_BOARD_ID "tdisplay-p4" TDP4_C6_ID #endif #endif +#elif defined(HAS_CROWPANEL_P4_101) + // Experimental: no release artifact yet (CAP_OTA 0), so this only names the board in test + // reports. Per revision, because the LoRa pins and the C6 SDIO set differ between them. + #if CP_REV == 10 +#define WADA_BOARD_ID "crowpanel-p4-101-v10" + #elif CP_REV == 11 +#define WADA_BOARD_ID "crowpanel-p4-101-v11" + #else +#define WADA_BOARD_ID "crowpanel-p4-101-v12" + #endif #elif defined(HAS_TANMATSU) #define WADA_BOARD_ID "tanmatsu" #elif defined(HELTEC_LORA_V4_R8) // must precede the V4-TFT fallback @@ -39108,6 +39134,23 @@ static void mapPickUseCb(lv_event_t* e) { refreshMapInfoLabel(); } +#if !CAP_KEYPAD_NAV +// navOpenChatPanel / navGoToMainTab live in the CAP_KEYPAD_NAV block above, but two callers below +// (this map jump and the chat-header composer sync) run on every board. A board without keypad +// navigation (the CrowPanel P4 10.1 is the first) gets the same two helpers without the nav group. +static bool anyPopupOpen(); // defined further down (popup registry) +static LvChatPanel* navOpenChatPanel() { + if (g_lv.dm.detail_open && g_lv.dm.overlay && !lv_obj_has_flag(g_lv.dm.overlay, LV_OBJ_FLAG_HIDDEN)) return &g_lv.dm; + if (g_lv.ch.detail_open && g_lv.ch.overlay && !lv_obj_has_flag(g_lv.ch.overlay, LV_OBJ_FLAG_HIDDEN)) return &g_lv.ch; + return nullptr; +} +static bool navGoToMainTab(int tab) { + if (anyPopupOpen()) return false; // no keypad: leave an open popup to its own close control + goToTab(tab); + return true; +} +#endif + static void mapPickStart() { if (!g_lv.task || !s_map_canvas || !s_map_page) return; if (mapPickGpsOwnsPosition()) { // the Profile row was built before the fix arrived @@ -40032,10 +40075,12 @@ static bool crashDumpExport(char* out_path, size_t out_cap) { const char* path = "/wadamesh-crash.elf"; #if CAP_SD || defined(TLORA_PAGER) if (fmSdTryMount()) { dst = &SD; used_sd = true; } -#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (tanSdTryMount()) { dst = &SD_MMC; used_sd = true; } // microSD on SDMMC slot 0 #endif -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#if defined(HAS_CROWPANEL_P4_101) + if (!dst) dst = &LittleFS; // internal = the LittleFS 'storage' partition (P4 storage law) +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) if (!dst) dst = &FFat; // no SPIFFS on these boards; internal = the FAT data partition #else if (!dst) { SPIFFS.begin(false); dst = &SPIFFS; } @@ -48998,7 +49043,7 @@ static void backupAddPath(const char* stored, const char* disp) { // would fail on an already-mounted volume), plain SPIFFS on the T-Deck / V4. Mirrors the file // manager's "Internal" root so a backup lands where the user can actually see + restore it. static fs::FS* backupInternalFs() { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // The P4 hot store moved from FFat to LittleFS('storage') in beta_54 (#167); // FFat is never mounted here anymore, so opening it failed every backup (#231). return &LittleFS; @@ -49012,7 +49057,7 @@ static fs::FS* backupInternalFs() { static fs::FS* backupSdFs() { #if CAP_SD || defined(TLORA_PAGER) return fmSdTryMount() ? &SD : nullptr; -#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) return tanSdTryMount() ? &SD_MMC : nullptr; #else return nullptr; @@ -52464,7 +52509,7 @@ static void powerOffNow() { #endif // directly wakeable PIN_USER_BTN (powerOffCb) #if defined(ESP32) -#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) +#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) && !defined(HAS_CROWPANEL_P4_101) #include "soc/rtc_cntl_reg.h" // RTC_CNTL_FORCE_DOWNLOAD_BOOT (header-guarded) #endif // Force the ROM serial bootloader (USB download / flash mode) on the next reset, @@ -52473,7 +52518,7 @@ static void powerOffNow() { // esptool's post-flash reset — or a power cycle — clears it back to a normal boot. // (Tanmatsu/P4 has no RTC_CNTL_OPTION1_REG and the launcher manages flashing — plain restart.) static void rebootToDownloadMode() { -#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) +#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) && !defined(HAS_CROWPANEL_P4_101) REG_SET_BIT(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); #endif ESP.restart(); @@ -52996,7 +53041,7 @@ static void applyVolume(uint8_t pct) { if (pct > 100) pct = 100; s_volume_pct = pct; // play paths read the persisted pref; this keeps the slider live } -#elif defined(HAS_WIO_TRACKER_L2) || defined(HAS_CROWPANEL_35) || defined(HAS_CROWPANEL_50) +#elif defined(HAS_WIO_TRACKER_L2) || defined(HAS_CROWPANEL_35) || defined(HAS_CROWPANEL_50) || defined(HAS_CROWPANEL_P4_101) #define HAS_CC_BRIGHTNESS 1 static uint8_t s_brightness_pct = 63; static void applyBrightness(uint8_t pct) { @@ -59021,7 +59066,7 @@ static lv_obj_t* hwIcon(lv_obj_t* parent, const char* glyph, uint32_t col, bool static const lv_font_t* hwFitFont(const char* txt, int w, int h) { const lv_font_t* faces[] = { &lv_font_montserrat_28, -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(DOC_CAPTURE) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(DOC_CAPTURE) &lv_font_montserrat_24, &lv_font_montserrat_20, #endif &g_font_semi_16, &g_font_semi_14, &g_font_12, @@ -67171,7 +67216,7 @@ static void buildUiTree() { }; // Both buttons use the rotation/refresh glyph; left vs. right position // tells the user which way the display will turn. -#if !defined(HAS_WIO_TRACKER_L2) && !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) +#if !defined(HAS_WIO_TRACKER_L2) && !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) && !defined(HAS_CROWPANEL_P4_101) s_kb_rot_left_btn = makeRotBtn(LV_SYMBOL_REFRESH, kbRotLeftCb); s_kb_rot_right_btn = makeRotBtn(LV_SYMBOL_REFRESH, kbRotRightCb); #endif @@ -68592,7 +68637,7 @@ static bool uiDataFsReady() { return true; } if (SPIFFS.begin(false)) { s_ui_data_fs = &SPIFFS; s_ui_data_root[0] = '\0'; return true; } -#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#elif defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // Tanmatsu + T-Display P4: use each board's mounted internal data partition // (Tanmatsu FFat 'locfd', P4 LittleFS 'storage'), with SD_MMC as fallback. // #167: hot UI data (chat history) lives on the INTERNAL LittleFS -- SD write bursts @@ -68719,7 +68764,7 @@ bool luaHostScreenOn() { // It never comes out smaller than the regular 16, which on a scaled-up UI already is // Montserrat 24. Older firmware maps any size above 16 to 16, so apps stay readable. static const lv_font_t* luaHostFigureFont() { -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) if (lv_font_get_line_height(&lv_font_montserrat_24) > lv_font_get_line_height(&g_font_16)) return &lv_font_montserrat_24; #endif @@ -70243,7 +70288,7 @@ void luaHostTextPrompt(const char* title, const char* initial, void (*cb)(const // message ring (MAX_UI_MESSAGES_SD) since the card has room for the bigger file. static bool uiDataFsIsSdCard() { if (!uiDataFsReady()) return false; -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_WIO_TRACKER_L2) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_WIO_TRACKER_L2) return s_ui_data_fs == &SD_MMC; #elif CAP_SD || defined(TLORA_PAGER) return s_ui_data_fs == &SD; @@ -70851,7 +70896,7 @@ bool UITask::loadHistoryFromStorage() { bool UITask::saveThreadsToStorage() { #if defined(ESP32) -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // #167: the threads file is rewritten on every contact add/delete and chat-state change -- // hop the write to the core-0 storage task like every other hot writer (see p4StorageCall). if (!p4OnStorageTask()) { @@ -71034,7 +71079,7 @@ static bool uiSegWriteRecords(File& f, const UITask::UIMessage* recs, int n) { // the whole point of the segmented layout). static bool uiSegAppendRecords(uint32_t first_seq, bool create, const UITask::UIMessage* recs, int n) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // #167: history writes hop to the core-0 storage task (see p4StorageCall in DataStore.cpp) -- // a core-1 SD write can mask the DSI frame-restart ISR long enough to drop a display frame. if (!p4OnStorageTask()) { @@ -71095,7 +71140,7 @@ static bool uiSegAppendRecords(uint32_t first_seq, bool create, // lesson from the single-file store). static bool uiSegCompactWrite(uint32_t first_seq, const UITask::UIMessage* recs, int n, bool sync_writer) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { // #167: hop to core 0 (see uiSegAppendRecords) struct A { uint32_t fs; const UITask::UIMessage* r; int n; bool sw; bool ok; } a{ first_seq, recs, n, sync_writer, false }; p4StorageCall([](void* p){ auto* a = (A*)p; a->ok = uiSegCompactWrite(a->fs, a->r, a->n, a->sw); }, &a); @@ -71135,7 +71180,7 @@ static bool uiSegCompactWrite(uint32_t first_seq, const UITask::UIMessage* recs, // Remove a retired segment (ring grew past retention; its records are gone // from RAM too). static void uiSegRemoveFile(uint32_t first_seq) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) if (!p4OnStorageTask()) { // #167: hop to core 0 (see uiSegAppendRecords) p4StorageCall([](void* p){ uiSegRemoveFile((uint32_t)(uintptr_t)p); }, (void*)(uintptr_t)first_seq); return; @@ -72813,7 +72858,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no WIRE_DBG("[TILE] Tanmatsu: no SD card -> caching Wi-Fi tiles on FFat /tiles (best-effort)"); } #endif -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // T-Display P4: no dedicated "tiles" partition in its table. Prefer the microSD (SD_MMC slot 0, // mounted by main.cpp at boot — XL9535 SD_EN must be LOW, it gates the slot's power); else the // internal 8 MB FFat 'storage' partition — a standard FFat mount of OUR OWN partition, so none of @@ -72850,7 +72895,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no WIRE_DBG("[TILE] wio-l2: caching Wi-Fi tiles on SD_MMC /tiles\n"); } #endif -#if !defined(HAS_TDISPLAY_P4) && !defined(HAS_WIO_TRACKER_L2) +#if !defined(HAS_TDISPLAY_P4) && !defined(HAS_CROWPANEL_P4_101) && !defined(HAS_WIO_TRACKER_L2) else { s_tile_fs = nullptr; // no cache backend at all -> map shows the storage notice } @@ -73004,7 +73049,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #else s_lock_on_screen_off = touchPrefsGetLockOnScreenOff(); #endif -#if !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) // no unlock control there, so no lock to put a PIN on +#if !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) && !defined(HAS_CROWPANEL_P4_101) // no unlock control there, so no lock to put a PIN on s_pin_on = touchPrefsPinIsSet(); pinRestoreFails(); #endif @@ -73111,6 +73156,15 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no const size_t buf_bytes = sizeof(lv_color_t) * g_draw_buf_px; g_draw_buffer = (lv_color_t*)heap_caps_malloc(buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); if (!g_draw_buffer) g_draw_buffer = (lv_color_t*)malloc(buf_bytes); +#elif defined(HAS_CROWPANEL_P4_101) + // 1024-wide band at native resolution. Internal DMA RAM first (~48 KB): the DSI DMA already + // streams the 1.2 MB framebuffer out of PSRAM continuously, so a PSRAM band would add a + // third PSRAM stream to every flush (see the RM69A10 flush note). PSRAM as the fallback. + g_draw_buf_px = 1024 * LV_DRAW_BUF_LINES; + const size_t buf_bytes = sizeof(lv_color_t) * g_draw_buf_px; + g_draw_buffer = (lv_color_t*)heap_caps_malloc(buf_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + if (!g_draw_buffer) g_draw_buffer = (lv_color_t*)heap_caps_malloc(buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (!g_draw_buffer) g_draw_buffer = (lv_color_t*)malloc(buf_bytes); #elif defined(HAS_TDISPLAY_P4) // P4: LVGL renders at half res (upscaled 2x on flush) — full-width band in the abundant 32MB PSRAM. // AMOLED (RM69A10) = 284-wide; TFT-LCD (HI8561) = 270-wide. @@ -73257,8 +73311,8 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no // must match so LVGL renders the full 320x240 landscape surface. s_ui_rotation = LV_DISP_ROT_270; #endif - #if defined(HAS_WIO_TRACKER_L2) - // The panel driver exposes its fixed 320x240 landscape surface directly. + #if defined(HAS_WIO_TRACKER_L2) || defined(HAS_CROWPANEL_P4_101) + // The panel driver exposes its fixed landscape surface directly (320x240 / 1024x600). s_ui_rotation = LV_DISP_ROT_NONE; #endif #if defined(ATTAKY_MESH_SERIES) @@ -73361,6 +73415,11 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no // stays == the panel width, so it's never entered). g_lv.disp_drv.hor_res = TAN_PANEL_PW; // 480 g_lv.disp_drv.ver_res = TAN_PANEL_PH; // 800 +#elif defined(HAS_CROWPANEL_P4_101) + // Native 1024x600 landscape (low-density 10.1" panel: no upscale; the large-screen layouts and + // UI-size fonts carry the sizing). The GT911 driver reports in this same space. + g_lv.disp_drv.hor_res = 1024; + g_lv.disp_drv.ver_res = 600; #elif defined(HAS_TDISPLAY_P4) // Render at HALF the native panel; the P4 DisplayDriver upscales 2x on flush, so the whole UI is // uniformly 2x bigger on the high-DPI panel (simpler than per-element scaling). The touch driver @@ -74791,7 +74850,7 @@ static inline void touchScreenBacklight(bool on) { ledcWrite(kM9BlPwmChannel, 255); // inverted: 255 = 0% conduction = off touchPanelSleep(true); } -#elif defined(HAS_WIO_TRACKER_L2) || defined(HAS_CROWPANEL_35) || defined(HAS_CROWPANEL_50) +#elif defined(HAS_WIO_TRACKER_L2) || defined(HAS_CROWPANEL_35) || defined(HAS_CROWPANEL_50) || defined(HAS_CROWPANEL_P4_101) // The display owns its backlight (I2C on Wio and the CrowPanel 5.0, PWM on the 3.5). if (on) applyBrightness(s_brightness_pct); else display.setBrightness(0); @@ -74814,7 +74873,7 @@ static inline void touchScreenBacklight(bool on) { // Restored to 240 MHz on wake so tile decode / rendering stays fast. Only // switches when the target changes (the call is a hard DFS switch). static void setCpuForScreen(bool screen_on) { -#if defined(HAS_TDISPLAY_P4) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) // The P4's Arduino HAL only accepts 360 MHz (240/80 log a "could not be set" error every screen // toggle) — no DFS downclock available through this API on that chip, so skip entirely. (void)screen_on; @@ -75279,7 +75338,7 @@ void UITask::wakeScreen() { } void UITask::lockScreen() { -#if defined(HAS_CROWPANEL_35) || defined(HAS_CROWPANEL_50) +#if defined(HAS_CROWPANEL_35) || defined(HAS_CROWPANEL_50) || defined(HAS_CROWPANEL_P4_101) // No physical unlock button: imported lock preferences must still allow touch wake. sleepScreen(); return; @@ -76192,7 +76251,7 @@ void consoleHostRebootToUi() { } #endif -#if defined(HAS_TDISPLAY_P4) || defined(HAS_TANMATSU) +#if defined(HAS_TDISPLAY_P4) || defined(HAS_TANMATSU) || defined(HAS_CROWPANEL_P4_101) // gpsEnsureBigRxRing() is defined in src/main.cpp, which the two ESP32-P4 // targets never compile, while the calls to it live in UITask.cpp and // MyMesh.cpp which every target does. That left both P4 builds with an @@ -78192,7 +78251,7 @@ void UITask::loop() { // thing with or without it. ssaverStart(now, false); _screen_off = true; -#if !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) // no unlock control there; its lockScreen() only sleeps too +#if !defined(HAS_CROWPANEL_35) && !defined(HAS_CROWPANEL_50) && !defined(HAS_CROWPANEL_P4_101) // no unlock control there; its lockScreen() only sleeps too _manual_lock = s_lock_on_screen_off || s_pin_on; #endif } else diff --git a/src/ui-touch/device_caps.h b/src/ui-touch/device_caps.h index 1a81d2c..7d46707 100644 --- a/src/ui-touch/device_caps.h +++ b/src/ui-touch/device_caps.h @@ -173,6 +173,19 @@ // settings page carries the "Lock when screen off" switch (as on the L2). #define CAP_LOCK_SCREEN 1 +#elif defined(HAS_CROWPANEL_P4_101) // ===== Elecrow CrowPanel Advanced 10.1" (ESP32-P4 + C6) ===== + // EXPERIMENTAL. 1024x600 EK79007 IPS on MIPI-DSI, GT911 touch, SX1262 module on the wireless + // header, C6 Wi-Fi/BLE (esp-hosted 2.12.3 factory firmware), SD_MMC 1-bit. 32 MB PSRAM. + #define CAP_TOUCH 1 // GT911 capacitive touch + #define CAP_ROTATABLE 0 // native 1024x600 landscape; the DSI panel cannot rotate in hardware + #define CAP_LARGE_SCREEN 1 // large-panel layouts + the UI-size fonts, rendered at native resolution + #define CAP_SD 0 // card is SD_MMC (slot 0), exposed through CAP_FILESYSTEM like the T-Display P4 + #define CAP_FILESYSTEM 1 // internal LittleFS 'storage' + SD_MMC + #define CAP_GPS 0 // no GPS fitted + #define CAP_OTA 0 // no release artifact on the update server yet: keep self-update off + #define CAP_LOCK_SCREEN 0 // no wake/unlock path wired yet (the BOOT key on GPIO35 could become one) + #define CAP_BATTERY 0 // battery charging + sensing belong to the on-board STC8, not the P4 + #elif defined(ATTAKY_MESH_SERIES) #define CAP_TOUCH 1 #define CAP_ROTATABLE 1 // landscape (ROT_90) by default; portrait for the build without a keyboard @@ -202,7 +215,8 @@ // Physical removable microSD slot, independent of the filesystem API used to // drive it. CAP_SD means the SD-compatible SPI API (hardware or software); // SD_MMC boards still need the same user-facing card diagnostics. -#if CAP_SD || defined(HAS_WIO_TRACKER_L2) || defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) +#if CAP_SD || defined(HAS_WIO_TRACKER_L2) || defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || \ + defined(HAS_CROWPANEL_P4_101) #define CAP_MICROSD 1 #else #define CAP_MICROSD 0 @@ -237,7 +251,7 @@ // the T-Display P4's C6 firmware has no Bluetooth, and the Tanmatsu runs another // NimBLE wrapper. #if defined(BLE_PIN_CODE) && defined(MULTI_TRANSPORT_COMPANION) && \ - !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) + !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) && !defined(HAS_CROWPANEL_P4_101) #define CAP_BLE_KEYBOARD 1 #else #define CAP_BLE_KEYBOARD 0 @@ -300,7 +314,8 @@ // WiFi, which the legacy ESP-AT build must never touch. The Wio Tracker L2 has // no RTC and an ESP32-S3 radio, so it gets the same opt-in. #if defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(HAS_THINKNODE_M9) || \ - defined(HAS_WIO_TRACKER_L2) || (defined(HAS_TDISPLAY_P4) && TDP4_C6_HOSTED) + defined(HAS_WIO_TRACKER_L2) || (defined(HAS_TDISPLAY_P4) && TDP4_C6_HOSTED) || \ + defined(HAS_CROWPANEL_P4_101) #define CAP_BOOT_TIME_SYNC 1 #else #define CAP_BOOT_TIME_SYNC 0 diff --git a/src/ui-touch/ui_font_18.c b/src/ui-touch/ui_font_18.c index 229f74a..6dd0522 100644 --- a/src/ui-touch/ui_font_18.c +++ b/src/ui-touch/ui_font_18.c @@ -1,4 +1,4 @@ -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) /******************************************************************************* * Size: 18 px @@ -2683,4 +2683,4 @@ lv_font_t lv_font_montserrat_18 = { #endif /*#if WADA_UI_FONT_18_BODY*/ -#endif /* defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) */ +#endif /* defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) */ diff --git a/src/ui-touch/ui_font_20.c b/src/ui-touch/ui_font_20.c index 01567d2..b0388c6 100644 --- a/src/ui-touch/ui_font_20.c +++ b/src/ui-touch/ui_font_20.c @@ -1,4 +1,4 @@ -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) /******************************************************************************* * Size: 20 px @@ -3013,4 +3013,4 @@ lv_font_t lv_font_montserrat_20 = { #endif /*#if WADA_UI_FONT_20_BODY*/ -#endif /* defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) */ +#endif /* defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) */ diff --git a/src/ui-touch/ui_font_24.c b/src/ui-touch/ui_font_24.c index bdb2abe..bf34783 100644 --- a/src/ui-touch/ui_font_24.c +++ b/src/ui-touch/ui_font_24.c @@ -1,4 +1,4 @@ -#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) +#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) /******************************************************************************* * Size: 24 px @@ -3708,4 +3708,4 @@ lv_font_t lv_font_montserrat_24 = { #endif /*#if WADA_UI_FONT_24_BODY*/ -#endif /* defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) */ +#endif /* defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4) || defined(HAS_CROWPANEL_P4_101) || defined(HAS_CROWPANEL_50) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER) || defined(HAS_THINKNODE_M9) || defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(DOC_CAPTURE) */ diff --git a/variants/crowpanel_p4_101/EK79007Display.cpp b/variants/crowpanel_p4_101/EK79007Display.cpp new file mode 100644 index 0000000..a3c0a09 --- /dev/null +++ b/variants/crowpanel_p4_101/EK79007Display.cpp @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#if defined(HAS_CROWPANEL_P4_101) +#include "EK79007Display.h" +#include +#include "esp_heap_caps.h" +#include "esp_cache.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_ek79007.h" +#include "driver/ledc.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +// Panel geometry + DSI timing: Elecrow's validated BSP (idf-code Lesson07 bsp_illuminate.c, identical +// on V1.0, V1.1 and V1.2) and the V1.2 hardware driver guide. +#define CP_W 1024 +#define CP_H 600 +#define CP_LANES 2 +#define CP_LANE_MBPS 900 +#define CP_DPI_MHZ 51 +#define CP_HBP 160 +#define CP_HPW 70 +#define CP_HFP 160 +#define CP_VBP 23 +#define CP_VPW 10 +#define CP_VFP 12 +// J21 LCD_RESET is wired to GPIO41 (schematic, all revisions, pulled up through R69), but Elecrow's +// working BSP passes reset_gpio_num = -1 and relies on the power-on reset. Kept the same here; build +// with -DCP_LCD_RESET_GPIO=41 to let the driver pulse it if the panel stays dark after a restart. +#ifndef CP_LCD_RESET_GPIO +#define CP_LCD_RESET_GPIO -1 +#endif + +// Backlight: MT9201 EN on GPIO31, LEDC 30 kHz / 11-bit like Elecrow (PLL-divided clock; the 40 MHz +// crystal cannot reach 30 kHz at 11 bits). A timer/channel pair the Arduino ledc layer does not hand +// out first, so the two cannot collide. +#define CP_BL_GPIO 31 +#define CP_BL_HZ 30000 +#define CP_BL_TIMER LEDC_TIMER_3 +#define CP_BL_CHANNEL LEDC_CHANNEL_7 + +static SemaphoreHandle_t s_flush_sem = nullptr; +static bool s_bl_ok = false; + +// The DPI panel copies each draw_bitmap into its PSRAM framebuffer asynchronously (DMA2D). LVGL +// reuses its band on the next flush, so every flush waits for this (see RM69A10Display.cpp). +static bool IRAM_ATTR ek79007TransDone(esp_lcd_panel_handle_t, esp_lcd_dpi_panel_event_data_t*, void*) { + BaseType_t hpw = pdFALSE; + if (s_flush_sem) xSemaphoreGiveFromISR(s_flush_sem, &hpw); + return hpw == pdTRUE; +} + +static void backlightInit() { + ledc_timer_config_t t = {}; + t.speed_mode = LEDC_LOW_SPEED_MODE; + t.duty_resolution = LEDC_TIMER_11_BIT; + t.timer_num = CP_BL_TIMER; + t.freq_hz = CP_BL_HZ; + t.clk_cfg = LEDC_USE_PLL_DIV_CLK; + if (ledc_timer_config(&t) != ESP_OK) { Serial.println("[EK79007] backlight timer fail"); return; } + ledc_channel_config_t c = {}; + c.gpio_num = CP_BL_GPIO; + c.speed_mode = LEDC_LOW_SPEED_MODE; + c.channel = CP_BL_CHANNEL; + c.intr_type = LEDC_INTR_DISABLE; + c.timer_sel = CP_BL_TIMER; + c.duty = 0; + c.hpoint = 0; + s_bl_ok = (ledc_channel_config(&c) == ESP_OK); + if (!s_bl_ok) Serial.println("[EK79007] backlight channel fail"); +} + +static void backlightDuty(uint32_t duty) { + if (!s_bl_ok) return; + if (duty > 2047) duty = 2047; + ledc_set_duty(LEDC_LOW_SPEED_MODE, CP_BL_CHANNEL, duty); + ledc_update_duty(LEDC_LOW_SPEED_MODE, CP_BL_CHANNEL); +} + +bool EK79007Display::begin() { + // The LDO rails (LDO3 DSI PHY 2.5 V, LDO4 pad domain 3.3 V) are already up: crowpanelP4PowerOn() + // acquires them before anything else runs (target.cpp). + backlightInit(); // dark until the first frame is in the framebuffer + + esp_lcd_dsi_bus_handle_t dsi_bus = nullptr; + esp_lcd_dsi_bus_config_t bus_cfg = {}; + bus_cfg.bus_id = 0; + bus_cfg.num_data_lanes = CP_LANES; + bus_cfg.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT; + bus_cfg.lane_bit_rate_mbps = CP_LANE_MBPS; + if (esp_lcd_new_dsi_bus(&bus_cfg, &dsi_bus) != ESP_OK) { Serial.println("[EK79007] dsi_bus fail"); return false; } + + esp_lcd_panel_io_handle_t dbi_io = nullptr; + esp_lcd_dbi_io_config_t dbi_cfg = { .virtual_channel = 0, .lcd_cmd_bits = 8, .lcd_param_bits = 8 }; + if (esp_lcd_new_panel_io_dbi(dsi_bus, &dbi_cfg, &dbi_io) != ESP_OK) { Serial.println("[EK79007] dbi_io fail"); return false; } + + esp_lcd_dpi_panel_config_t dpi_cfg = {}; + dpi_cfg.virtual_channel = 0; + dpi_cfg.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT; + dpi_cfg.dpi_clock_freq_mhz = CP_DPI_MHZ; + dpi_cfg.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565; + dpi_cfg.num_fbs = 1; + dpi_cfg.video_timing.h_size = CP_W; + dpi_cfg.video_timing.v_size = CP_H; + dpi_cfg.video_timing.hsync_back_porch = CP_HBP; + dpi_cfg.video_timing.hsync_pulse_width = CP_HPW; + dpi_cfg.video_timing.hsync_front_porch = CP_HFP; + dpi_cfg.video_timing.vsync_back_porch = CP_VBP; + dpi_cfg.video_timing.vsync_pulse_width = CP_VPW; + dpi_cfg.video_timing.vsync_front_porch = CP_VFP; + dpi_cfg.flags.use_dma2d = true; + + ek79007_vendor_config_t vendor = {}; + vendor.init_cmds = nullptr; // the component's vendor_specific_init_default (as Elecrow uses) + vendor.init_cmds_size = 0; + vendor.mipi_config.dsi_bus = dsi_bus; + vendor.mipi_config.dpi_config = &dpi_cfg; + vendor.mipi_config.lane_num = CP_LANES; + esp_lcd_panel_dev_config_t dev = {}; + dev.reset_gpio_num = CP_LCD_RESET_GPIO; + dev.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + dev.bits_per_pixel = 16; + dev.vendor_config = &vendor; + if (esp_lcd_new_panel_ek79007(dbi_io, &dev, &_panel) != ESP_OK) { Serial.println("[EK79007] new_panel fail"); return false; } + + esp_lcd_panel_reset(_panel); + if (esp_lcd_panel_init(_panel) != ESP_OK) { Serial.println("[EK79007] panel_init fail"); return false; } + + // Blank the freshly allocated (garbage) PSRAM framebuffer before the backlight comes on. + void* fb = nullptr; + if (esp_lcd_dpi_panel_get_frame_buffer(_panel, 1, &fb) == ESP_OK && fb) { + const size_t fb_bytes = (size_t)CP_W * CP_H * 2; + memset(fb, 0, fb_bytes); + esp_cache_msync(fb, fb_bytes, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + } + esp_lcd_panel_disp_on_off(_panel, true); // EK79007 may report "not supported"; harmless + + s_flush_sem = xSemaphoreCreateBinary(); + esp_lcd_dpi_panel_event_callbacks_t cbs = {}; + cbs.on_color_trans_done = ek79007TransDone; + esp_lcd_dpi_panel_register_event_callbacks(_panel, &cbs, nullptr); + + _on = true; + setBrightness(_bright); // light it now so a dark panel reads as a DSI problem, not a backlight one + Serial.printf("[EK79007] up %dx%d, %d lanes @ %d Mbps, DPI %d MHz\n", CP_W, CP_H, CP_LANES, CP_LANE_MBPS, CP_DPI_MHZ); + return true; +} + +void EK79007Display::writeNativeRGB565(int x, int y, int w, int h, const uint16_t* px) { + writePixelsRGB565(x, y, w, h, px); +} + +void EK79007Display::writePixelsRGB565(int x, int y, int w, int h, const uint16_t* pixels) { + if (!_panel || !pixels || w <= 0 || h <= 0) return; + esp_lcd_panel_draw_bitmap(_panel, x, y, x + w, y + h, pixels); // exclusive end coords + if (s_flush_sem) xSemaphoreTake(s_flush_sem, pdMS_TO_TICKS(100)); +} + +void EK79007Display::setBrightness(uint8_t b) { + _bright = b ? b : _bright; + if (!_on && b) return; // remembered; applied by turnOn() + // Elecrow: duty = pct * 18 + 200 for pct 1..100, i.e. 218..2000 of 2047. Same band on 1..255. + backlightDuty(b ? 200u + (uint32_t)b * 1800u / 255u : 0u); +} + +void EK79007Display::turnOn() { + _on = true; + setBrightness(_bright); +} + +void EK79007Display::turnOff() { + _on = false; + backlightDuty(0); +} + +#endif // HAS_CROWPANEL_P4_101 diff --git a/variants/crowpanel_p4_101/EK79007Display.h b/variants/crowpanel_p4_101/EK79007Display.h new file mode 100644 index 0000000..af30ee0 --- /dev/null +++ b/variants/crowpanel_p4_101/EK79007Display.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once +// EK79007 IPS panel (MIPI-DSI 2-lane, DPI RGB565, 1024x600 landscape) on the Elecrow CrowPanel +// Advanced 10.1" ESP32-P4. begin() runs the DSI bring-up with Elecrow's validated parameters +// (900 Mbps lanes, 51 MHz DPI, porches 160/70/160 + 23/10/12) through Espressif's esp_lcd_ek79007 +// component. LVGL renders at the NATIVE 1024x600 (no upscale: at ~118 dpi the panel is low density, +// so a 2x upscale would leave a 512x300 canvas, smaller than the CrowPanel 5.0's). Backlight = +// MT9201 boost driver, EN/PWM on GPIO31 (LEDC); GPIO29 is the optional backlight power switch. +#include +#include "../tdisplay_p4/P4PanelPainter.h" // native-resolution fills/text for non-LVGL screens +#include "esp_lcd_types.h" + +class EK79007Display : public P4PanelPainter { + bool _on = true; + uint8_t _bright = 255; + esp_lcd_panel_handle_t _panel = nullptr; +public: + EK79007Display() : P4PanelPainter(1024, 600) {} + + bool begin(); // full DSI bring-up; returns false on failure (logs the stage) + + // LVGL flush hot path. Exclusive end coords, synchronous (waits for the DMA2D copy). + void writePixelsRGB565(int x, int y, int w, int h, const uint16_t* pixels); + void setDisplayRotation(int rot) { (void)rot; } // fixed landscape panel (CAP_ROTATABLE 0) + void startFrame() {} + void startFrame(ColorVal bkg) override { P4PanelPainter::startFrame(bkg); } + void endFrame() {} + + bool isOn() override { return _on; } + void turnOn() override; + void turnOff() override; + // 0 = backlight off, 1..255 = PWM duty band (Elecrow's 0..100 -> 200..2000 of 2047 curve). + void setBrightness(uint8_t b); +protected: + void writeNativeRGB565(int x, int y, int w, int h, const uint16_t* px) override; +}; diff --git a/variants/crowpanel_p4_101/Gt911Touch.cpp b/variants/crowpanel_p4_101/Gt911Touch.cpp new file mode 100644 index 0000000..1125e87 --- /dev/null +++ b/variants/crowpanel_p4_101/Gt911Touch.cpp @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Elecrow CrowPanel Advanced 10.1" ESP32-P4: GT911 capacitive touch, implementing the shared +// touch-input API (HeltecV4CapTouch.h). +// +// Wiring (schematic V1.0/V1.1/V1.2, all identical, and Elecrow's Lesson05 BSP): GT911 on the touch +// FPC, I2C0 SDA=GPIO45 / SCL=GPIO46 (shared with the codec and the HY2.0 I2C port; Wire is begun +// there by ESP32Board::begin via PIN_BOARD_SDA/SCL), RST=GPIO40, INT=GPIO42, both active low. +// Elecrow configures the controller for 1024x600 with no swap or mirror, so raw == screen; the +// driver still reads the GT911's own X/Y maximum at init and scales if it reports another grid. +// +// The gesture machine is the one shared with the T-Display P4 / CHSC6x / FT5x06 drivers; only the +// report read differs. +#if defined(HAS_CROWPANEL_P4_101) && defined(ESP32) + +#include +#include +#include +#include +#include +#include + +#ifndef PIN_TOUCH_RST + #define PIN_TOUCH_RST 40 +#endif +#ifndef PIN_TOUCH_INT + #define PIN_TOUCH_INT 42 +#endif + +static const int SCR_W = 1024; +static const int SCR_H = 600; + +static uint8_t s_addr = 0; // 0x5D or 0x14, whichever answered +static uint16_t s_native_w = SCR_W; // GT911 config X/Y maximum (read at init) +static uint16_t s_native_h = SCR_H; + +static bool s_init_ok = false; +static bool s_given_up = false; +static int s_retries = 0; +static char s_scan_str[128] = "scan: (not run)"; + +static volatile uint16_t s_dbg_rawx = 0, s_dbg_rawy = 0; + +static bool s_have_touch = false; +static uint16_t s_cur_x = 0, s_cur_y = 0; +static bool s_down = false; +static unsigned long s_down_at = 0; +static uint16_t s_start_x = 0, s_start_y = 0; +static uint16_t s_last_x = 0, s_last_y = 0; +static bool s_live = false; +static uint16_t s_live_x = 0, s_live_y = 0; +static bool s_tap_pending = false; +static uint16_t s_tap_x = 0, s_tap_y = 0; +static bool s_swiping_now = false; +static bool s_swipe_pending = false; +static int8_t s_swipe_x = 0, s_swipe_y = 0; + +static uint8_t s_ui_rotation = 0; +static uint8_t s_point_rotation = 0; + +// 10.1" at 1024x600 is ~118 px per inch, so the thresholds are in the same physical range as the +// T-Display P4's (60 px swipe at its 2x-scaled logical space). +#ifndef CP_TOUCH_SWIPE_MIN + #define CP_TOUCH_SWIPE_MIN 70 +#endif +#ifndef CP_TOUCH_TAP_MOVE_MAX + #define CP_TOUCH_TAP_MOVE_MAX 24 +#endif +#ifndef CP_TOUCH_LONG_MS + #define CP_TOUCH_LONG_MS 1000 +#endif + +// ---- GT911 register access (16-bit big-endian register addresses) ---- +static bool gtRead(uint16_t reg, uint8_t* out, uint8_t n) { + Wire.beginTransmission(s_addr); + Wire.write((uint8_t)(reg >> 8)); + Wire.write((uint8_t)reg); + if (Wire.endTransmission(false) != 0) return false; + if (Wire.requestFrom((int)s_addr, (int)n) != n) return false; + for (uint8_t i = 0; i < n; i++) out[i] = Wire.read(); + return true; +} +static bool gtWrite8(uint16_t reg, uint8_t v) { + Wire.beginTransmission(s_addr); + Wire.write((uint8_t)(reg >> 8)); + Wire.write((uint8_t)reg); + Wire.write(v); + return Wire.endTransmission() == 0; +} +static bool probe(uint8_t a) { + Wire.beginTransmission(a); + return Wire.endTransmission() == 0; +} + +// Hardware reset with INT held LOW through the rising edge of RST: the GT911 then latches I2C +// address 0x5D (Goodix datasheet power-on timing). INT is released to an input afterwards. +static void gtHardReset() { + pinMode(PIN_TOUCH_INT, OUTPUT); + digitalWrite(PIN_TOUCH_INT, LOW); + pinMode(PIN_TOUCH_RST, OUTPUT); + digitalWrite(PIN_TOUCH_RST, LOW); + delay(20); + digitalWrite(PIN_TOUCH_RST, HIGH); + delay(60); // address latched >= 5 ms after RST rises; keep INT low until the controller is up + pinMode(PIN_TOUCH_INT, INPUT); + delay(50); +} + +bool heltecV4CapTouchBegin() { + if (s_init_ok) return true; + if (s_given_up) return false; + if (++s_retries > 3) { + s_given_up = true; + Serial.println("[TOUCH] GT911 giving up after 3 attempts, booting without touch"); + return false; + } + + Wire.setClock(400000); + Wire.setTimeOut(20); + if (s_retries == 1) gtHardReset(); + + if (s_retries == 1) { // log the bus once: tells a tester exactly what answered on 45/46 + char sc[96]; int n = snprintf(sc, sizeof sc, "[TOUCH] i2c45/46 scan:"); + for (uint8_t a = 0x08; a < 0x78 && n < (int)sizeof sc - 6; a++) + if (probe(a)) n += snprintf(sc + n, sizeof sc - n, " %02X", a); + Serial.println(sc); + } + + s_addr = probe(0x5D) ? 0x5D : (probe(0x14) ? 0x14 : 0); + if (!s_addr) { + snprintf(s_scan_str, sizeof s_scan_str, "i2c45/46: no GT911 at 0x5D/0x14"); + Serial.printf("[TOUCH] GT911 not found (retry %d/3)\n", s_retries); + return false; + } + + // Product ID "911" at 0x8140, config X/Y maximum at 0x8048..0x804B (little-endian). + uint8_t id[4] = {0}, cfg[4] = {0}; + gtRead(0x8140, id, 4); + if (gtRead(0x8048, cfg, 4)) { + const uint16_t xm = (uint16_t)(cfg[0] | (cfg[1] << 8)); + const uint16_t ym = (uint16_t)(cfg[2] | (cfg[3] << 8)); + if (xm >= 100 && xm <= 4096 && ym >= 100 && ym <= 4096) { s_native_w = xm; s_native_h = ym; } + } + gtWrite8(0x814E, 0); // clear any stale "buffer ready" + + s_init_ok = true; + snprintf(s_scan_str, sizeof s_scan_str, "0x%02X gt911 '%c%c%c%c' %ux%u", + s_addr, id[0] ? id[0] : '?', id[1] ? id[1] : '?', id[2] ? id[2] : '?', id[3] ? id[3] : ' ', + (unsigned)s_native_w, (unsigned)s_native_h); + Serial.printf("[TOUCH] %s\n", s_scan_str); + return true; +} + +static void mapRaw(uint16_t rx, uint16_t ry, uint16_t* ox, uint16_t* oy) { + int sx = (int)rx, sy = (int)ry; + if (s_native_w != SCR_W) sx = sx * SCR_W / s_native_w; + if (s_native_h != SCR_H) sy = sy * SCR_H / s_native_h; + if (sx < 0) sx = 0; if (sx > SCR_W - 1) sx = SCR_W - 1; + if (sy < 0) sy = 0; if (sy > SCR_H - 1) sy = SCR_H - 1; + *ox = (uint16_t)sx; + *oy = (uint16_t)sy; +} + +// 1 = finger down (rx/ry raw), 0 = no touch, -1 = I2C error. +static int gtReadPoint(uint16_t* rx, uint16_t* ry) { + uint8_t st = 0; + if (!gtRead(0x814E, &st, 1)) return -1; + if (!(st & 0x80)) return s_have_touch ? 1 : 0; // no new frame yet: keep the last state + const uint8_t n = st & 0x0F; + int ret = 0; + if (n >= 1 && n <= 5) { + uint8_t p[8] = {0}; // point 1: id, xL, xH, yL, yH, sizeL, sizeH, res + if (gtRead(0x8150, p, 8)) { + *rx = (uint16_t)(p[0] | (p[1] << 8)); + *ry = (uint16_t)(p[2] | (p[3] << 8)); + ret = 1; + } + } + gtWrite8(0x814E, 0); // hand the buffer back to the controller + return ret; +} + +static void gtPoll() { + uint16_t rx = s_dbg_rawx, ry = s_dbg_rawy; + const int r = gtReadPoint(&rx, &ry); + if (r == 1) { + s_dbg_rawx = rx; + s_dbg_rawy = ry; + mapRaw(rx, ry, &s_cur_x, &s_cur_y); + s_have_touch = true; + } else if (r == 0) { + s_have_touch = false; + } +} + +void heltecV4CapTouchGetRaw(uint16_t* rx, uint16_t* ry) { + if (rx) *rx = s_dbg_rawx; + if (ry) *ry = s_dbg_rawy; +} + +int heltecV4CapTouchCheck() { + if (!s_init_ok) return BUTTON_EVENT_NONE; + gtPoll(); + + if (s_have_touch) { + s_live = true; + s_live_x = s_cur_x; + s_live_y = s_cur_y; + if (!s_down) { + s_down = true; + s_down_at = millis(); + s_start_x = s_cur_x; + s_start_y = s_cur_y; + s_swiping_now = false; + } + s_last_x = s_cur_x; + s_last_y = s_cur_y; + if (!s_swiping_now) { + int adx = abs((int)s_last_x - (int)s_start_x); + int ady = abs((int)s_last_y - (int)s_start_y); + if (adx >= CP_TOUCH_SWIPE_MIN && adx > ady) s_swiping_now = true; + } + return BUTTON_EVENT_NONE; + } + + if (s_down) { + s_down = false; + s_live = false; + s_swiping_now = false; + unsigned long dur = millis() - s_down_at; + int adx = abs((int)s_last_x - (int)s_start_x); + int ady = abs((int)s_last_y - (int)s_start_y); + if (adx >= CP_TOUCH_SWIPE_MIN && adx > (ady + 8)) { + bool left = ((int)s_last_x - (int)s_start_x) < 0; + s_swipe_x = left ? -1 : 1; + s_swipe_y = 0; + s_swipe_pending = true; + return left ? BUTTON_EVENT_DOUBLE_CLICK : BUTTON_EVENT_TRIPLE_CLICK; + } + if (ady >= CP_TOUCH_SWIPE_MIN && ady > (adx + 8)) { + s_swipe_x = 0; + s_swipe_y = ((int)s_last_y - (int)s_start_y) < 0 ? -1 : 1; + s_swipe_pending = true; + return BUTTON_EVENT_NONE; + } + if (dur >= 12 && dur < (unsigned long)CP_TOUCH_LONG_MS && + adx <= CP_TOUCH_TAP_MOVE_MAX && ady <= CP_TOUCH_TAP_MOVE_MAX) { + s_tap_x = s_last_x; + s_tap_y = s_last_y; + s_tap_pending = true; + return BUTTON_EVENT_CLICK; + } + if (dur >= (unsigned long)CP_TOUCH_LONG_MS) return BUTTON_EVENT_LONG_PRESS; + } else { + s_live = false; + } + return BUTTON_EVENT_NONE; +} + +bool heltecV4CapTouchGetLive(uint16_t* x, uint16_t* y) { + if (!s_live) return false; + if (x) *x = s_live_x; + if (y) *y = s_live_y; + return true; +} +bool heltecV4CapTouchPopTap(uint16_t* x, uint16_t* y) { + if (!s_tap_pending) return false; + s_tap_pending = false; + if (x) *x = s_tap_x; + if (y) *y = s_tap_y; + return true; +} +bool heltecV4CapTouchPopSwipe(int8_t* xd, int8_t* yd) { + if (!s_swipe_pending) return false; + s_swipe_pending = false; + if (xd) *xd = s_swipe_x; + if (yd) *yd = s_swipe_y; + return true; +} + +static TaskHandle_t s_poll_task = nullptr; +static volatile bool s_async = false; +static uint32_t s_period_ms = 8; + +static void touchPollTask(void*) { + for (;;) { + heltecV4CapTouchCheck(); + vTaskDelay(pdMS_TO_TICKS(s_period_ms)); + } +} + +bool heltecV4CapTouchStartBackgroundPoll(uint32_t period_ms) { + if (s_async || !s_init_ok) return false; + s_period_ms = period_ms < 4 ? 4 : (period_ms > 100 ? 100 : period_ms); + if (xTaskCreatePinnedToCore(touchPollTask, "gt911_touch", 3072, nullptr, 2, &s_poll_task, 0) == pdPASS) { + s_async = true; + return true; + } + return false; +} +bool heltecV4CapTouchIsAsyncPolling() { return s_async; } +bool heltecV4CapTouchIsSwiping() { return s_swiping_now; } +void heltecV4CapTouchSetRotation(uint8_t r) { s_ui_rotation = r & 3; } +void heltecV4CapTouchSetPointRotation(uint8_t r) { s_point_rotation = r & 3; } +const char* heltecV4CapTouchDebug() { return s_scan_str; } + +#endif // HAS_CROWPANEL_P4_101 && ESP32 diff --git a/variants/crowpanel_p4_101/ssl_client_stub.cpp b/variants/crowpanel_p4_101/ssl_client_stub.cpp new file mode 100644 index 0000000..b5329f7 --- /dev/null +++ b/variants/crowpanel_p4_101/ssl_client_stub.cpp @@ -0,0 +1,31 @@ +// Pragmatic stub for arduino-esp32 3.3.10's ssl_client functions on the CrowPanel P4 (copy of variants/tdisplay_p4/ssl_client_stub.cpp). +// +// On the P4 / IDF 5.5.1, arduino's NetworkClientSecure.cpp call signatures don't match +// ssl_client.cpp's definitions (an arduino-internal / toolchain ABI mismatch: e.g. the port +// param mangles `unsigned long` at the call site but `uint32_t` at the definition), so they're +// undefined at link. wadamesh doesn't use HTTPS yet (the companion link is USB/BLE/LoRa and map +// tiles are plain HTTP), so stub the referenced symbols to let the app link. Identical to the +// Tanmatsu's stub (same IDF + arduino), just guarded for this board. +// +// Symbols + signatures are taken verbatim from the linker's undefined-reference list. These are +// STRONG (not weak): a weak def in a .a doesn't get pulled to resolve an undefined ref, and there's +// no conflict because arduino's real defs mangle differently (that mismatch is why they're undefined). +// TODO: revisit for the HTTPS version-check / OTA-over-Wi-Fi (the P4's 32 MB PSRAM CAN do TLS). +#if defined(HAS_CROWPANEL_P4_101) +#include +struct sslclient_context; +#define WK + +WK int start_ssl_client(sslclient_context*, const IPAddress&, unsigned long, const char*, int, + const char*, bool, const char*, const char*, const char*, const char*, + bool, const char**) { return -1; } +WK void stop_ssl_socket(sslclient_context*) {} +WK int data_to_read(sslclient_context*) { return 0; } +WK int send_net_data(sslclient_context*, const unsigned char*, unsigned int) { return -1; } +WK int get_net_receive(sslclient_context*, unsigned char*, int) { return -1; } +WK int peek_net_receive(sslclient_context*, int) { return -1; } +WK int send_ssl_data(sslclient_context*, const unsigned char*, unsigned int) { return -1; } +WK int get_ssl_receive(sslclient_context*, unsigned char*, int) { return -1; } +WK void ssl_init(sslclient_context*) {} +WK int ssl_starttls_handshake(sslclient_context*) { return -1; } +#endif diff --git a/variants/crowpanel_p4_101/target.cpp b/variants/crowpanel_p4_101/target.cpp new file mode 100644 index 0000000..e585eb1 --- /dev/null +++ b/variants/crowpanel_p4_101/target.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#if defined(HAS_CROWPANEL_P4_101) +#include +#include +#include "target.h" +#include // RadioNoiseListener / StdRNG +#include "esp_ldo_regulator.h" +#include "driver/gpio.h" + +CrowPanelP4Board board; + +// SX1262 module on the external wireless header J9/J11, SPI3 SCLK=8 / MISO=7 / MOSI=6, NSS=10, +// BUSY=9. DIO1 and NRST moved between board revisions (see main/CMakeLists.txt, CP_REV): GPIO53/54 on +// V1.0/V1.1, GPIO27/28 on V1.2. All are direct P4 GPIOs, so RadioLib takes a real DIO1 interrupt. +static SPIClass spi(FSPI); +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +WRAPPER_CLASS radio_driver(radio, board); + +DISPLAY_CLASS display; + +// No battery-backed RTC on this board: ESP32 system time, guarded by the monotonic floor. +ESP32RTCClock fallback_clock; +ClockFloorRTC rtc_clock(fallback_clock); + +EnvironmentSensorManager sensors; // no GPS fitted (an external UART GPS could be added later) + +// ---- Board power --------------------------------------------------------------------------------- +// Elecrow's every display/wireless lesson acquires LDO3 = 2.5 V and LDO4 = 3.3 V before touching any +// peripheral. LDO3 feeds the MIPI D-PHY; LDO4 (net ESP32_LDO4 -> VDDPST_5 in the schematic) is the pad +// supply for the GT911 I2C/RST/INT lines, LCD_RESET and the microSD lines. Both are held for the life +// of the session. Because LDO4 is ours, SD_MMC is told to use "external" pad power (main.cpp: +// SD_MMC.setPowerChannel(-1)); otherwise Arduino's SD driver would try to acquire the same channel +// as an adjustable one and fail. +static esp_ldo_channel_handle_t s_ldo3 = nullptr; +static esp_ldo_channel_handle_t s_ldo4 = nullptr; + +void crowpanelP4PowerOn() { + esp_ldo_channel_config_t ldo4 = {}; + ldo4.chan_id = 4; + ldo4.voltage_mv = 3300; + if (esp_ldo_acquire_channel(&ldo4, &s_ldo4) != ESP_OK) printf("[BOOT] LDO4 (3.3 V pads) acquire FAILED\n"); + esp_ldo_channel_config_t ldo3 = {}; + ldo3.chan_id = 3; + ldo3.voltage_mv = 2500; + if (esp_ldo_acquire_channel(&ldo3, &s_ldo3) != ESP_OK) printf("[BOOT] LDO3 (DSI PHY 2.5 V) acquire FAILED\n"); + + // GPIO29 = LCD_BK_POWER, gate of the optional P-MOSFET Q11 in front of the backlight boost. Q11 is + // marked "AO3401_NC" (not fitted) with R184 = 0R bypassing it on V1.1/V1.2, so this is normally a + // no-op; LOW is the "on" level should a unit have Q11 fitted. + gpio_reset_pin(GPIO_NUM_29); + gpio_set_direction(GPIO_NUM_29, GPIO_MODE_OUTPUT); + gpio_set_level(GPIO_NUM_29, 0); + // GPIO31 = LCD_BK_EN: hold the MT9201 off until the display has blanked its framebuffer. + gpio_reset_pin(GPIO_NUM_31); + gpio_set_direction(GPIO_NUM_31, GPIO_MODE_OUTPUT); + gpio_set_level(GPIO_NUM_31, 0); + // GPIO30 = AUDIO_OUT_SD, the speaker amplifier's shutdown (active low in Elecrow's code: LOW = on). + // No audio support yet, so keep the amplifier OFF to avoid hiss and pops. + gpio_reset_pin(GPIO_NUM_30); + gpio_set_direction(GPIO_NUM_30, GPIO_MODE_OUTPUT); + gpio_set_level(GPIO_NUM_30, 1); + delay(20); +} + +void CrowPanelP4Board::begin() { + ESP32Board::begin(); // Wire on PIN_BOARD_SDA/SCL = 45/46 (GT911, codec, HY2.0 port) +} + +bool radio_init() { + fallback_clock.begin(); + // No battery-backed RTC chip on this board, so no generic rtc_clock.begin(Wire) probe (#383): + // CAP_HARDWARE_RTC is 0 and the opt-in cold-boot network time sync covers it. + rtc_clock.noteHardwareClock(false); + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, P_LORA_NSS); + // std_init resets the module through P_LORA_RESET, sets DIO3 TCXO (SX126X_DIO3_TCXO_VOLTAGE, + // 1.6 V as in Elecrow's Lesson14) and DIO2 as the RF switch. + if (!radio.std_init(&spi)) { + printf("[crowpanel-p4] SX1262 not found (NSS%d BUSY%d DIO1%d RST%d). Seat the SX1262 module in " + "J9/J11, and check the build matches the board revision (CP_REV).\n", + P_LORA_NSS, P_LORA_BUSY, P_LORA_DIO_1, P_LORA_RESET); + return false; + } + return true; +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} + +// ---- ESP32-C6 (esp-hosted) reset ----------------------------------------------------------------- +// C6_EN = GPIO32 on every revision (schematic net C6_EN -> IC1.EN, 10k pull-up R77 + 100 nF C15). +// Elecrow's V1.0/V1.1 Wi-Fi lessons reset the slave through GPIO32; the V1.2 lesson's sdkconfig names +// GPIO54, which the V1.2 schematic routes to header J7 pin 12, not to the C6, so GPIO32 is used here +// for all revisions. Pulse LOW 50 ms, release, give the hosted firmware time to boot. +extern "C" int hosted_reset_slave_callback() { + gpio_reset_pin(GPIO_NUM_32); + gpio_set_direction(GPIO_NUM_32, GPIO_MODE_OUTPUT); + gpio_set_level(GPIO_NUM_32, 1); + delay(10); + gpio_set_level(GPIO_NUM_32, 0); + delay(50); + gpio_set_level(GPIO_NUM_32, 1); + delay(1000); + return 0; +} + +// ---- Software restart = full system reset --------------------------------------------------------- +// esp_restart() on the ESP32-P4 does not reset the MIPI-DSI host or its DPI path, so the panel can +// come back dark after a settings reboot (seen on the T-Display P4; same silicon here). A shutdown +// handler turns every software restart into an RTC-watchdog system reset; an RTC_NOINIT marker lets +// the next boot report it as a software restart rather than a watchdog crash. +#include + +static constexpr uint32_t kFullRestartMagic = 0x50345253u; // "P4RS" +RTC_NOINIT_ATTR static uint32_t s_full_restart_marker; +static bool s_boot_was_full_restart = false; + +static void crowpanelP4FullRestart() { + s_full_restart_marker = kFullRestartMagic; + wdt_hal_context_t rwdt = RWDT_HAL_CONTEXT_DEFAULT(); + wdt_hal_write_protect_disable(&rwdt); + wdt_hal_config_stage(&rwdt, WDT_STAGE0, 20000, WDT_STAGE_ACTION_RESET_SYSTEM); + wdt_hal_enable(&rwdt); + wdt_hal_write_protect_enable(&rwdt); + while (true) { + } +} + +void crowpanelP4InstallFullRestart() { + static bool s_installed = false; + if (s_installed) return; + s_boot_was_full_restart = (s_full_restart_marker == kFullRestartMagic) && esp_reset_reason() == ESP_RST_WDT; + s_full_restart_marker = 0; + s_installed = (esp_register_shutdown_handler(crowpanelP4FullRestart) == ESP_OK); +} + +esp_reset_reason_t crowpanelP4ResetReason() { + return s_boot_was_full_restart ? ESP_RST_SW : esp_reset_reason(); +} + +#endif // HAS_CROWPANEL_P4_101 diff --git a/variants/crowpanel_p4_101/target.h b/variants/crowpanel_p4_101/target.h new file mode 100644 index 0000000..3d79be1 --- /dev/null +++ b/variants/crowpanel_p4_101/target.h @@ -0,0 +1,73 @@ +#pragma once +#ifdef __cplusplus +extern "C" void set_boot_phase(int phase); +#endif + +// Elecrow CrowPanel Advanced 10.1" ESP32-P4 HMI (1024x600 IPS, EK79007 MIPI-DSI, GT911 touch). +// EXPERIMENTAL port, built from Elecrow's schematics (Elecrow-RD/CrowPanel-Advanced-10.1inch- +// ESP32-P4-HMI-AI-Display-1024x600-IPS-Touch-Screen, Eagle_SCH&PCB/1.0, 1.1, 1.2) and their +// idf-code lessons; not yet run on hardware. +// +// Pin map. [S] = traced in the schematic netlist, [E] = used by Elecrow's ESP-IDF lesson code. +// Display EK79007, MIPI-DSI 2 lanes (dedicated pins), 900 Mbps, DPI 51 MHz [E] +// LCD_RESET GPIO41 [S], unused like Elecrow (reset_gpio_num = -1) [E] +// Backlight LCD_BK_EN GPIO31 (MT9201 EN, LEDC 30 kHz) [S][E] +// LCD_BK_POWER GPIO29 (Q11 gate; Q11 not fitted, R184 0R bypass) [S] +// LDOs LDO3 2.5 V (DSI PHY), LDO4 3.3 V (VDDPST_5: touch, LCD reset, SD pads) [S][E] +// Touch GT911 I2C0 SDA45 SCL46, RST40, INT42 [S][E] +// C6 SDIO slot 1 CLK18 CMD19; C6_EN GPIO32 [S][E] +// V1.1/V1.2: D0..D3 = GPIO17,16,15,14 (4-bit) [S][E] +// V1.0: D0..D3 = GPIO14,15,16,17 (run 1-bit like Elecrow's V1.0 lesson) [S][E] +// LoRa SX1262 module on J9/J11: SCK8 MISO7 MOSI6 NSS10 BUSY9 [S][E] +// V1.0/V1.1: DIO1 = GPIO53 (J9.1), NRST = GPIO54 (J11.1) [S][E] +// V1.2: DIO1 = GPIO27 (J9.1), NRST = GPIO28 (J11.1) [S][E] +// TCXO 1.6 V, DIO2 RF switch (Elecrow Lesson14 begin() values) [E] +// (V1.2 net names call GPIO10 "W_BUSY"; the lesson code uses 9 = BUSY, 10 = NSS) +// microSD SDMMC slot 0, 1-bit: CLK43 CMD44 D0 39 [S][E] +// BOOT key GPIO35 (SPI_BOOT, K3), not used yet [S] +// Audio I2S LRCK21 BCLK22 DOUT23, amp shutdown GPIO30 (held off), not used yet [S][E] +// Battery TP4059 charger + VBAT ADC on the STC8 helper MCU, no P4 link [S] +// +// Unlike the T-Display P4 there is no I/O expander: every control line is a plain P4 GPIO. The +// SX1262 sits on the external wireless header (J9/J11) with a real DIO1 interrupt line, so the +// stock RadioLib ISR path is used (no MESH_RADIO_DIO1_POLLED). +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include "../../src/helpers/ClockFloorRTC.h" // monotonic send-timestamp floor (issue #89) +#include +#include +#include "EK79007Display.h" +#include + +// Power the board's on-chip LDO rails (LDO3 = DSI PHY, LDO4 = the 3.3 V pad domain that carries the +// touch I2C, LCD reset and microSD lines) and park the backlight dark. Call first in app_main, before +// Wire / the display / the SD card touch any of those pads. +void crowpanelP4PowerOn(); + +// Makes every software restart a full system reset, so the MIPI-DSI host comes back clean (same +// P4 silicon behaviour, and the same fix, as the T-Display P4). Call first thing in app_main. +void crowpanelP4InstallFullRestart(); +// esp_reset_reason(), except that a restart made by the above reports ESP_RST_SW, not ESP_RST_WDT. +esp_reset_reason_t crowpanelP4ResetReason(); + +class CrowPanelP4Board : public ESP32Board { +public: + void begin(); + // The battery charger and its ADC belong to the on-board STC8 helper MCU, which has no link to the + // P4 in the schematic, so there is no battery reading to report (CAP_BATTERY 0). + const char* getManufacturerName() const override { return "Elecrow CrowPanel P4 10.1"; } +}; + +extern CrowPanelP4Board board; +extern WRAPPER_CLASS radio_driver; +extern RADIO_CLASS radio; // raw SX1262 (also driven directly by the Spectrum sweep) +extern ClockFloorRTC rtc_clock; +extern EnvironmentSensorManager sensors; +extern DISPLAY_CLASS display; // EK79007 MIPI-DSI, LVGL flush target + +bool radio_init(); +mesh::LocalIdentity radio_new_identity();