Merge crowpanel-p4-101: experimental Elecrow CrowPanel Advanced 10.1" ESP32-P4 port

This commit is contained in:
Kaj Schittecat committed 2026-10-17 20:56:50 +02:00
commit cff8bd4eea
40 files changed
+1587 -68

No files matched your search

+8
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.10)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(application)
+204
View File
@@ -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/<target>/*-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 <absolute path>`. 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<path> 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[@]}" "$@"
+1
View File
@@ -0,0 +1 @@
../../tdisplay_p4/components/RadioLib
+1
View File
@@ -0,0 +1 @@
../../tanmatsu/components/ardlibs
+1
View File
@@ -0,0 +1 @@
../../tanmatsu/components/esp_hosted
+1
View File
@@ -0,0 +1 @@
../../tanmatsu/components/lvgl
+1
View File
@@ -0,0 +1 @@
../../tdisplay_p4/components/meshcore
@@ -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)
+1
View File
@@ -0,0 +1 @@
../tanmatsu/esp-idf
+1
View File
@@ -0,0 +1 @@
../tanmatsu/esp-idf-tools
+94
View File
@@ -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 <RadioLib.h>
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).
+12
View File
@@ -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"
+238
View File
@@ -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 <Arduino.h>
#include <Mesh.h>
#include "MyMesh.h"
#include <new>
#include "esp_heap_caps.h"
#include "UITask.h"
#include "target.h"
#include <LittleFS.h>
#include <SD_MMC.h>
#include <WiFi.h>
#include "../../src/helpers/esp32/MultiTransportCompanionInterface.h"
#include <helpers/esp32/WifiRuntimeStore.h>
#include <helpers/esp32/TouchPrefsStore.h>
#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);
}
}
@@ -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,
1 # Elecrow CrowPanel P4 10.1: standalone 16 MB partition table (same layout as the T-Display P4).
2 # NOT an AppFS/launcher app (that's the Tanmatsu): this flashes bootloader+app directly.
3 # Dual OTA app slots (4 MB each; the P4 wadamesh image is ~2.5-3 MB) + a big FAT for
4 # identity/prefs/contacts/chat history + map tiles.
5 # Name, Type, SubType, Offset, Size, Flags
6 nvs, data, nvs, 0x9000, 24K,
7 otadata, data, ota, 0xF000, 8K,
8 phy_init, data, phy, 0x11000, 4K,
9 ota_0, app, ota_0, 0x20000, 4M,
10 ota_1, app, ota_1, 0x420000, 4M,
11 storage, data, fat, 0x820000, 8000K,
12 coredump, data, coredump, 0xFF0000, 64K,
@@ -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
+1
View File
@@ -0,0 +1 @@
../../tanmatsu/sdkconfigs/general
+9
View File
@@ -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
+10
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
../../tanmatsu/sdkconfigs/wadamesh
+1 -1
View File
@@ -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
+10 -10
View File
@@ -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 <SD_MMC.h>
#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
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -12,7 +12,7 @@
#endif
#ifdef ESP32
#include <esp_system.h> // 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 <soc/rtc_cntl_reg.h> // 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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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).
+1 -1
View File
@@ -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
@@ -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
+2 -2
View File
@@ -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
+96 -37
View File
@@ -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 <FFat.h> // internal FAT partition (Tanmatsu 'locfd' / P4 'storage')
#include <SD_MMC.h> // 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 <RM69A10Display.h> // RM69A10 MIPI-DSI AMOLED (default SKU) on the T-Display P4
#endif
#elif defined(HAS_CROWPANEL_P4_101)
#include <EK79007Display.h> // EK79007 MIPI-DSI IPS, 1024x600, on the CrowPanel P4 10.1
#else
#include <helpers/ui/ST7789LCDDisplay.h>
#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
+18 -3
View File
@@ -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
+2 -2
View File
@@ -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) */
+2 -2
View File
@@ -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) */
+2 -2
View File
@@ -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) */
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#if defined(HAS_CROWPANEL_P4_101)
#include "EK79007Display.h"
#include <Arduino.h>
#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
@@ -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 <stdint.h>
#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;
};
+306
View File
@@ -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 <helpers/input/HeltecV4CapTouch.h>
#include <Arduino.h>
#include <Wire.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <helpers/ui/MomentaryButton.h>
#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
@@ -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 <IPAddress.h>
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
+142
View File
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#if defined(HAS_CROWPANEL_P4_101)
#include <Arduino.h>
#include <Wire.h>
#include "target.h"
#include <helpers/ArduinoHelpers.h> // 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 <hal/wdt_hal.h>
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
+73
View File
@@ -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 <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/ESP32Board.h>
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include "../../src/helpers/ClockFloorRTC.h" // monotonic send-timestamp floor (issue #89)
#include <helpers/SensorManager.h>
#include <helpers/sensors/EnvironmentSensorManager.h>
#include "EK79007Display.h"
#include <esp_system.h>
// 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();