diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2758611a..7d13af36 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -139,6 +139,7 @@ - { env: "waveshare-esp32-c5-touch-lcd-35"} - { env: "waveshare-esp32-c6-lcd-147" } - { env: "waveshare-esp32-c6-lcd-130" } + - { env: "waveshare-esp32-p4-wifi6-touch-lcd-5" } - { env: "waveshare-esp32-s3-touch-amoled-206"} - { env: "ES3C28P" } - { env: "ES3C40P" } diff --git a/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/connections.md b/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/connections.md new file mode 100644 index 00000000..6e860d70 --- /dev/null +++ b/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/connections.md @@ -0,0 +1,257 @@ +Waveshare ESP32-P4-WIFI6-Touch-LCD-5 -- ESP32-P4 (P4NRW32X: 32MB PSRAM +stacked in-package, 32MB external NOR flash) main SoC + ESP32-C6-MINI-1 +co-processor (SDIO, WiFi 6/BLE 5), 720x1280 portrait MIPI-DSI HX8394 panel +(2 lanes), GT911 I2C capacitive touch, microSD (SDMMC 4-bit, slot 0), +ES8311/ES7210 audio codec (unused by this port), CH343 USB-serial console on +one USB-C and a second USB-C wired to the P4's own USB 2.0 high-speed OTG +pads. + +**Bench-tested** on a P4 rev v1.3 unit: display, GT911 touch, microSD, the +ESP32-C6 WiFi bridge and installing an app from SD into the OTA slot all +work. Every pin and timing below started from Waveshare's own schematic, +audited BSP and docs (waveshareteam/ESP32-P4-WIFI6-Touch-LCD-5: the +schematic PDF, docs/IO.md, the esp_lcd_hx8394 driver, and +examples/arduino/libraries/displays/displays_config.h); anything still +marked "unconfirmed" was not exercised on hardware, and the audio codec is +unused by this port. + +## Flash, PSRAM and partitions + +- 32MB external NOR flash; 32MB in-package PSRAM (`BOARD_HAS_PSRAM`), same + P4NRW32X silicon the other P4 boards here run on. +- The shared `boards/_jsonfiles/esp32p4_es.json` defaults to a 16MB flash + declaration, so `platformio.ini` sets `board_upload.flash_size`/ + `maximum_size` to 32MB explicitly. At runtime the launcher detects the + real chip size with `esp_flash_get_size()` (`partition_table_model.cpp`) + and lays its OTA/data slots out in the free flash past the static + partitions -- the CSV only has to place the launcher's own slot. +- `support_files/custom_32Mb_p4.csv` (new file, sibling of the 16MB P4 one): + `app0` subtype **factory** 0x210000 @ 0x10000, coredump @ 0x220000. On the + P4, `partitioner.cpp` installs firmwares into + `ESP_PARTITION_SUBTYPE_APP_FACTORY` (unlike the S3's TEST), so reusing the + pre-existing `custom_32Mb.csv` (subtype `test`) would have left this board + unable to install anything. Everything past 0x230000 is managed by the + runtime partition model. + +## Display -- HX8394 DSI panel, 720x1280, 2 lanes + +| Signal | Value | +| --- | :---: | +| Databus | `Arduino_ESP32DSIPanel` (`TFT_DATABUS_N=4`) | +| Driver | `Arduino_DSI_Display` (`TFT_DISPLAY_DRIVER_N=50`) | +| Reset | GPIO27 (`TFT_RST`, 0R R60 -> panel `RESET`) | +| Backlight PWM | GPIO26 (`TFT_BL`) -- **AC-coupled**, see below | +| Backlight enable | GPIO33 (`TFT_BL_EN`, AP3032 boost `EN` through 0R R32, active HIGH) | +| DPI clock | 58 MHz, 30 Hz refresh | +| HSYNC pulse/back/front| 20 / 20 / 40 | +| VSYNC pulse/back/front| 4 / 10 / 24 | +| Lane rate | 700 Mbps, 2 lanes (`TFT_DSI_LANE_BIT_RATE=700`) | + +Timings are Waveshare's own `HX8394_720_1280_PANEL_30HZ_DPI_CONFIG`. + +**Backlight is PWM-only**: Waveshare's displays_config.h documents that the +panel's backlight path AC-couples the PWM signal, so a static GPIO level +lights nothing -- the pin has to be driven by PWM. The shared `hal_bright` +HAL's 5 kHz ledc output satisfies this (Waveshare's own BSP drives 5 +kHz/10-bit). `TFT_BL_EN` (boost enable) is driven HIGH once in +`_setup_gpio()` before the PWM starts -- unlike Elecrow's `TFT_BL_POWER`, +this one is active HIGH. + +**Init table**: `waveshareHx8394InitOperations` in `interface.cpp`, +transcribed verbatim from Waveshare's official +`vendor_specific_init_default` (25 commands). `TFT_DSI_INIT` in +`platformio.ini` only names the stock Arduino_GFX `hx8394_init_operations` +array to satisfy the build-time seeding in `DisplayConfig.cpp` -- that +array belongs to a different panel revision (different power/gamma/VCOM/GIP +tables) and would likely not bring this glass up. `_setup_gpio()` re-aims +`displayConfig` at the Waveshare table via `TFT_SET_DSI_INIT()` before +`tft->begin()`, the same runtime-swap pattern `boards/lilygo-t-display-p4` +uses for its AMOLED variant. No Arduino_GFX fork/submodule change is +needed (unlike Elecrow, whose `ek79007` table only exists in a pinned +fork). + +Rotation is set to `ROTATION=3` (native panel is portrait, UI wants +landscape), the same value the other portrait-glass P4/DSI boards here +settled on (`lilygo-t-display-p4`, `seeedstudio-reterminal-d1001`). +`ROTATION=3` comes up upright on a rev v1.3 unit; both 1 and 3 give a +landscape 1280x720 UI differing by a 180-degree flip, so a different panel +revision that shows the image upside-down needs `ROTATION=1` (touch follows +automatically via the per-rotation mirror table in `touchCfg()`). + +## Touch -- GT911, I2C (shared board bus) + +| Signal | GPIO | +| --- | :---: | +| SDA | 7 (board I2C bus; ES8311/ES7210 codec hang off it too) | +| SCL | 8 | +| RST | 23 (0R R37) | +| INT | 2 -- **only reaches the touch glass through optional 0R R108, not fitted by default** | +| I2C addr | 0x5D and 0x14 both legal (latched from INT at reset) | + +The GT911 latches one of two I2C addresses from the INT pin level at reset. +On this board GPIO2 -> R108 -> touch INT, and R108 is an optional, by +default **not fitted** part, so the address can't be forced from the P4 +side -- the chip floats to whatever its internal pull presents (0x5D by +convention). The port calls `hal_touch_init(touchCfg(), 0x5D)` once with +`gt911_int_sync=true`: that path pulses RST and performs the INT sync +dance (harmless when R108 is unfitted -- it drives a pad that goes +nowhere), then hands SensorLib stripped pins so `TouchDrvGT911`'s +`probeAddress()` tries both 0x5D and 0x14 on its own. Either latch state +ends up working, without the manual 0x5D->0x14 retry other boards carry. + +Touch coordinates follow the launcher rotation via the standard +SwapXY/MirrorX/MirrorY table (same as Elecrow's). + +RST (GPIO23) is driven through `DeviceTouch.reset_cb` with `pin_rst = -1` +instead of the plain `pin_rst` field. That makes `hal_touch_init()` pass -1 +as the driver's own reset pin, so the `touch.reset()` that +`hal_touch_read()` runs after every reported point no longer pulses RST. +With a real pin_rst, `TouchDrvInterface::reset()` re-initialised the GT911 +after each read and the still-down finger got re-latched as a new touch -- +which made every on-screen keyboard key type twice. `InputHandler()` also +applies a point only on its press edge (hold >500 ms repeats at 250 ms), +following `seeedstudio-wio-tracker-l2`, so a finger resting on a key cannot +re-trigger it. + +Note: GPIO2 is also broken out on the 40-pin HAT header (pin 8). If a HAT +drives it while R108 is fitted, it fights the touch INT line -- leave it +alone on this port. + +## WiFi/BT -- ESP32-C6-MINI-1 over SDIO + +| Signal | GPIO | +| --- | :---: | +| CLK | 18 | +| CMD | 19 | +| D0 | 14 | +| D1 | 15 | +| D2 | 16 | +| D3 | 17 | +| C6 enable/reset | 54 (0R R34 -> C6 `EN`) | + +Each SDIO line has its own 51K pull-up and the pin order matches +Espressif's reference wiring for a P4+C6 pair -- deliberately NOT the +reversed D0..D3 order `elecrow-esp32p4-7in`'s hardware revisions forced +(see that board's `interface.cpp` header). All pins can be overridden at +runtime from NVS (the `sdio` serial console command) rather than requiring +a rebuild, same mechanism as Elecrow's. + +Built with the `SDIO2_*` macros and `launcherWifiInitSdioAuto()` +(`src/idf/idf_wifi.cpp`) -- ESP-Hosted firmware on the C6 (what Waveshare +ships). `ENABLE_ESP_AT_INTERFACE` is not set. If bring-up shows the +intermittent SDIO CRC errors Elecrow's wiring hit +(`patch_hosted_sdio_freq.py` / `LAUNCHER_HOSTED_SDIO_FREQ_KHZ=20000`), that +same workaround is the fix -- it has not been needed/known here yet. + +GPIO6 also routes to `C6_IO2` through R33 (purpose unknown, ignore it). +The board carries dedicated 4-pin 2.54mm pads to reflash the C6's own +firmware (see Waveshare's "Firmware Flashing" docs page). + +**If WiFi suddenly reports `ESP-Hosted unavailable: no WiFi`**: the hosted +bring-up takes ~8 s at boot, and the guard in `idf_wifi.cpp` latches +"co-processor broken" if the board loses power or is reset inside that +window (a normal power cycle right after boot is enough). It then skips +bring-up on every later boot until cleared. Fix from the serial console: + +``` +wifi hosted # clears the latched verdict +``` + +then power-cycle the board so bring-up runs again. Do not use the `reboot` +command for this: it is a CPU reset, and the installed app (not the +Launcher) is what boots from it. + +Not board-specific -- every ESP-Hosted port (Elecrow, LilyGO P4, ...) +behaves the same way. This board was hit by it once during bring-up +testing; after clearing, the C6 scanned/connected normally. + +**Benign boot warning**: the C6's esp_hosted firmware reports its version as +`0.0.0`, so esp_hosted logs a version-mismatch and a `rpc_core: Response not +received for [0x15e](Req_GetCoprocessorFwVersion)` line at every boot. The +radio still comes up (1476 ms here) and connects; the same unit running +Espressif's own P4 demo firmware shows `coprocessor=0.0.0` / "the C6 did not +say its firmware version" and then gets a DHCP lease, so it is a co-processor +firmware reporting quirk, not a wiring or port problem. + +## microSD -- SDMMC 4-bit, slot 0 + +| Signal | GPIO | +| --- | :---: | +| CLK | 43 | +| CMD | 44 | +| D0 | 39 | +| D1 | 40 | +| D2 | 41 | +| D3 | 42 | +| Power | on-chip LDO VO4 (`BOARD_SDMMC_POWER_CHANNEL=4`) | + +The slot is documented "SDIO 3.0" by Waveshare; this port drives it as +4-bit `SD_MMC` on the P4's dedicated SDMMC-slot-0 pads, same macro set as +`elecrow-esp32p4-7in` (`USE_SD_MMC=1`, slot 0). + +**Checked on hardware**: ESP-Hosted has a known bug where SDIO1 (the SD +card in 4-bit SD_MMC mode) and SDIO2 (the C6) cannot run concurrently on +the P4 -- `lilygo-t-display-p4` and `seeedstudio-reterminal-d1001` avoid it +by driving the card over plain SPI on the same pins instead. On a rev v1.3 +unit the card stays mounted while the C6 scans and connects, so the +conflict does not show up with this wiring; if another revision errors the +card out once WiFi is up (or vice versa), the documented fix is switching +to SPI mode like those two boards. + +## USB + +- H1 (silkscreen "USB TO UART", CH343): power + flashing + console. UART0 + on the P4's dedicated pins; `monitor_dtr=0`/`monitor_rts=0` so the + monitor doesn't toggle the CH343's DTR/RTS and reset the board. +- H2 (silkscreen "USB OTG"): wired straight to the P4's dedicated + `USBD_N`/`USBD_P` pads (chip pins 49/50, not on the GPIO matrix -- + confirmed by the docs' own 40-pin header note). Drives + `USB_MSC_HIGH_SPEED=1` (massStorage.cpp's rhport 0 vs 1), same as + `lilygo-t-display-p4`'s "High Speed USB" port. +- `ARDUINO_USB_MODE=1`, `ARDUINO_USB_CDC_ON_BOOT=0` (console is UART0 via + the CH343, not USB-CDC). + +Flashing for serial-command tests uses `esptool` directly (per CLAUDE.md). +ESP32-P4, so the bootloader goes at **0x2000** (the offset +`support_files/merge.py` maps for esp32p4): + +``` +esptool --chip esp32p4 write-flash \ + 0x2000 .pio/build/waveshare-esp32-p4-wifi6-touch-lcd-5/bootloader.bin \ + 0x8000 .pio/build/waveshare-esp32-p4-wifi6-touch-lcd-5/partitions.bin \ + 0x10000 .pio/build/waveshare-esp32-p4-wifi6-touch-lcd-5/firmware.bin +``` + +The build also emits the same three files pre-merged with those offsets as +`Launcher-waveshare-esp32-p4-wifi6-touch-lcd-5.bin` -- flashing that single +image at 0x0 brings a bare board up in one shot. + +Note on what each flash wipes: the Launcher's own settings (including the +WiFi list, passwords encrypted) live in `config.conf` on the microSD, so +they survive any flash. The image at 0x0 also overwrites the NVS partition +(0x9000-0xE000), which resets the ESP-Hosted guard and the NVS mirror of +those settings -- harmless, the SD copy is reloaded on boot. Flashing just +`firmware.bin` at 0x10000 leaves NVS untouched. + +## Battery + +MX1.25 2-pin header for a 3.7V LiPo (charge/discharge supported by the +board's own charger), but there is **no fuel gauge** and no documented ADC +divider -- `getBattery()` is not overridden and no battery icon is wired +in. If a divider turns out to exist on a future revision, override +`ANALOG_BAT_PIN`/`ANALOG_BAT_MULTIPLIER` then. + +## Not wired up / out of scope + +- ES8311 codec + ES7210 echo-cancellation + speaker amp: I2S on GPIO + 9 (`I2S_DSDIN`), 10 (`I2S_LRCK`), 11 (`I2S_ASDOUT`), 12 (`I2S_SCLK`), + 13 (`I2S_MCLK`), PA enable GPIO53. No audio path exists in this + launcher; all left untouched. +- MIPI-CSI camera connector (OV5647) -- no camera support in this + codebase. +- RTC battery header, speaker header, onboard mic array. +- 40-pin Raspberry-Pi-HAT header GPIOs (3, 4, 5, 21, 22, 28-32, 34, 35, + 37, 38, 46-52, plus GPIO24/25 as USB1P1 full-speed USB) -- untouched. +- POWER button (hard-wired 2s power-off) and BOOT button (strap/download + mode) are board functions, not launcher inputs -- no `DeviceButtons` + config, matching the other P4 display boards. diff --git a/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/interface.cpp b/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/interface.cpp new file mode 100644 index 00000000..ffa66d5e --- /dev/null +++ b/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/interface.cpp @@ -0,0 +1,252 @@ +#include "hal/bright/bright.h" +#include "hal/device.h" +#include "hal/inputs/touch.h" +#include "idf/idf_wifi.h" +#include "idf/launcher_platform.h" +#include "nvs_helpers.h" +#include "powerSave.h" +#include +#include + +// --------------------------------------------------------------------------- +// HX8394 init sequence (720x1280, 2 MIPI-DSI lanes) +// +// Waveshare's official register sequence for this panel, transcribed from +// their audited BSP (waveshareteam/ESP32-P4-WIFI6-Touch-LCD-5, +// HX8394_720_1280_PANEL_30HZ_DPI_CONFIG -- the vendor_specific_init_default +// table in examples/arduino/libraries/displays/displays_config.h). The +// stock Arduino_GFX hx8394_init_operations array that TFT_DSI_INIT names in +// platformio.ini belongs to a different panel revision (different power, +// gamma, VCOM and GIP tables) and is only there to satisfy the build-time +// checks; _setup_gpio() below re-aims displayConfig at this table before +// tft->begin(), following the runtime-swap pattern boards/lilygo-t-display-p4 +// uses for its AMOLED variant. +// --------------------------------------------------------------------------- +static const lcd_init_cmd_t waveshareHx8394InitOperations[] = { + // {cmd, { data }, data_size, delay_ms} + // sleep out, MADCTL, 16-bit COLMOD and 2-lane DSI config, sent by the + // BSP ahead of the vendor sequence + {0x11, (uint8_t[]){0x00}, 1, 120}, + {0x36, (uint8_t[]){0x00}, 1, 0 }, + {0x3A, (uint8_t[]){0x55}, 1, 0 }, + {0xBA, (uint8_t[]){0x61}, 1, 0 }, + // vendor sequence + {0xB9, (uint8_t[]){0xFF, 0x83, 0x94}, 3, 0 }, + {0xB1, (uint8_t[]){0x48, 0x0A, 0x6A, 0x09, 0x33, 0x54, 0x71, 0x71, 0x2E, 0x45}, 10, 0 }, + {0xBA, (uint8_t[]){0x61, 0x03, 0x68, 0x6B, 0xB2, 0xC0}, 6, 0 }, + {0xB2, (uint8_t[]){0x00, 0x80, 0x64, 0x0C, 0x06, 0x2F}, 6, 0 }, + {0xB4, + (uint8_t[]){0x1C, 0x78, 0x1C, 0x78, 0x1C, 0x78, 0x01, 0x0C, 0x86, 0x75, 0x00, + 0x3F, 0x1C, 0x78, 0x1C, 0x78, 0x1C, 0x78, 0x01, 0x0C, 0x86}, + 21, 0 }, + {0xD3, + (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x32, 0x10, 0x05, + 0x00, 0x05, 0x32, 0x13, 0xC1, 0x00, 0x01, 0x32, 0x10, 0x08, 0x00, + 0x00, 0x37, 0x03, 0x07, 0x07, 0x37, 0x05, 0x05, 0x37, 0x0C, 0x40}, + 33, 0 }, + {0xD5, + (uint8_t[]){0x18, 0x18, 0x18, 0x18, 0x22, 0x23, 0x20, 0x21, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, + 0x03, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19}, + 44, 0 }, + {0xD6, + (uint8_t[]){0x18, 0x18, 0x19, 0x19, 0x21, 0x20, 0x23, 0x22, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, + 0x04, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x18, 0x18}, + 44, 0 }, + {0xE0, + (uint8_t[]){0x07, 0x08, 0x09, 0x0D, 0x10, 0x14, 0x16, 0x13, 0x24, 0x36, 0x48, 0x4A, 0x58, 0x6F, 0x76, + 0x80, 0x97, 0xA5, 0xA8, 0xB5, 0xC6, 0x62, 0x63, 0x68, 0x6F, 0x72, 0x78, 0x7F, 0x7F, 0x00, + 0x02, 0x08, 0x0D, 0x0C, 0x0E, 0x0F, 0x10, 0x24, 0x36, 0x48, 0x4A, 0x58, 0x6F, 0x78, 0x82, + 0x99, 0xA4, 0xA0, 0xB1, 0xC0, 0x5E, 0x5E, 0x64, 0x6B, 0x6C, 0x73, 0x7F, 0x7F}, + 58, 0 }, + {0xCC, (uint8_t[]){0x0B}, 1, 0 }, + {0xC0, (uint8_t[]){0x1F, 0x73}, 2, 0 }, + {0xB6, (uint8_t[]){0x6B, 0x6B}, 2, 0 }, + {0xD4, (uint8_t[]){0x02}, 1, 0 }, + {0xBD, (uint8_t[]){0x01}, 1, 0 }, + {0xB1, (uint8_t[]){0x00}, 1, 0 }, + {0xBD, (uint8_t[]){0x00}, 1, 0 }, + {0xBF, (uint8_t[]){0x40, 0x81, 0x50, 0x00, 0x1A, 0xFC, 0x01}, 7, 0 }, + {0x3A, (uint8_t[]){0x50}, 1, 0 }, + {0x11, (uint8_t[]){0x00}, 1, 200}, + {0xB2, (uint8_t[]){0x00, 0x80, 0x64, 0x0C, 0x06, 0x2F, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x18}, 12, 0 }, + {0x29, (uint8_t[]){0x00}, 1, 80 }, // display on +}; + +// The SDIO pinout of the onboard ESP32-C6 co-processor follows Espressif's +// reference wiring for a P4+C6 pair and works on a rev v1.3 board (scan and +// connect both OK). Rather than require a rebuild+reflash if a revision needs +// something else, every pin can still be overridden at runtime from NVS (see +// the "sdio" serial console command in serial_console.cpp), falling back to +// the build-time SDIO2_* macros when no override is stored. +static int8_t sdioPinOverride(const char *key, int8_t buildDefault) { + lnvs::Handle h(SDIO_OVERRIDE_NVS_NS, false); + if (!h) return buildDefault; + int value; + if (!lnvs::getInt(h.raw(), key, value)) return buildDefault; + return (int8_t)value; +} + +#define TOUCH_SDA_PIN GT911_I2C_CONFIG_SDA_IO_NUM +#define TOUCH_SCL_PIN GT911_I2C_CONFIG_SCL_IO_NUM +#define TOUCH_RST_PIN GT911_TOUCH_CONFIG_RST_GPIO_NUM +#define TOUCH_INT_PIN GT911_TOUCH_CONFIG_INT_GPIO_NUM +#define TOUCH_ADDR \ + 0x5D // GT911 default I2C address (0x14 is the other legal + // one -- SensorLib's probe tries both, and R108 that + // wires GPIO2 to the touch INT is optional/not fitted + // by default, so the address can't be forced here) + +static bool touchReady = false; + +// GT911 RST is a plain GPIO here, but it is driven through reset_cb (leaving +// pin_rst at -1) on purpose: hal_touch_init() then hands SensorLib -1 as its +// own reset pin, so hal_touch_read()'s touch.reset() after every reported +// point becomes a no-op. With a real pin_rst the base TouchDrvInterface:: +// reset() pulses RST after each read, re-initialising the controller while +// the finger is still down -- the chip re-latches that finger as a brand-new +// touch and every on-screen keyboard key types twice. +static void touchResetCb(bool level) { + launcherGpioOutput(TOUCH_RST_PIN); + launcherGpioWrite(TOUCH_RST_PIN, level ? HIGH : LOW); +} + +static DeviceTouch touchCfg() { + DeviceTouch cfg; + cfg.pin_rst = -1; // RST goes through reset_cb instead, see above + cfg.pin_irq = TOUCH_INT_PIN; + cfg.reset_cb = touchResetCb; + cfg.gt911_int_sync = true; + // rotation: 0 1 2 3 + bool swapXY[4] = {false, true, false, true}; + bool mirrorX[4] = {false, false, true, true}; + bool mirrorY[4] = {false, true, true, false}; + for (int i = 0; i < 4; i++) { + cfg.SwapXY[i] = swapXY[i]; + cfg.MirrorX[i] = mirrorX[i]; + cfg.MirrorY[i] = mirrorY[i]; + } + return cfg; +} + +void _setup_gpio() { + // Board I2C bus (GPIO7/GPIO8): GT911 touch here, the ES8311/ES7210 + // audio codec hangs off it too (unused by this port). + Wire.begin(TOUCH_SDA_PIN, TOUCH_SCL_PIN); + + // Backlight boost converter enable (AP3032 EN through 0R R32), active + // HIGH. Must be on before TFT_BL's PWM does anything. + pinMode(TFT_BL_EN, OUTPUT); + digitalWrite(TFT_BL_EN, HIGH); + + // Aim displayConfig at Waveshare's own HX8394 sequence. Runs before + // tft->begin() -- the window where the struct is still writable and + // nothing has been built from it yet. + TFT_SET_DSI_INIT(waveshareHx8394InitOperations); +} + +void _post_setup_gpio() { + // Brightness control must be initialized after tft in this case @Pirata + hal_bright_attach(TFT_BL); + hal_bright_set(TFT_BL, bright); + + touchReady = hal_touch_init(touchCfg(), TOUCH_ADDR); + if (!touchReady) { + launcherConsolePrintln("Touch IC not Started"); + log_i("Touch IC not Started"); + } else launcherConsolePrintln("Touch IC Started"); + + // ESP32-P4 has no native radio; WiFi/BT come from the onboard ESP32-C6 over + // SDIO. These pins work on a P4 rev v1.3 board (scan and connect both OK); + // a different revision can be retried through an NVS override (see the + // "sdio" serial command) without rebuilding. + int8_t sdioClk = sdioPinOverride("clk", SDIO2_CLK); + int8_t sdioCmd = sdioPinOverride("cmd", SDIO2_CMD); + int8_t sdioD0 = sdioPinOverride("d0", SDIO2_D0); + int8_t sdioD1 = sdioPinOverride("d1", SDIO2_D1); + int8_t sdioD2 = sdioPinOverride("d2", SDIO2_D2); + int8_t sdioD3 = sdioPinOverride("d3", SDIO2_D3); + int8_t sdioRst = sdioPinOverride("rst", SDIO2_RST); + launcherConsolePrintf( + "SDIO pins: clk=%d cmd=%d d0=%d d1=%d d2=%d d3=%d rst=%d\n", + sdioClk, + sdioCmd, + sdioD0, + sdioD1, + sdioD2, + sdioD3, + sdioRst + ); + if (!launcherWifiInitSdioAuto(sdioClk, sdioCmd, sdioD0, sdioD1, sdioD2, sdioD3, sdioRst)) { + launcherConsolePrintln("WIFI unavailable"); + } +} + +void _setBrightness(uint8_t brightval) { hal_bright_set(TFT_BL, brightval); } + +void InputHandler(void) { + if (!touchReady) return; + // Touch is edge-triggered: one applied press per finger-down. The input + // task clears touchPoint every pass and the menus/keyboard act on each + // applied press without waiting for a release, so re-applying a held + // finger every pass typed the next key while the previous one was still + // down. A held finger repeats only after kHoldRepeatDelay, then every + // kHoldRepeatRate (hold-to-scroll on the +/- rows and long lists). + // + // A new press is accepted only after the panel has reported "up" for + // kReleaseDebounceMs. The GT911 occasionally drops a sample mid-press; + // without this the drop reads as a fresh tap and fires far faster than + // the repeat timers below. + static constexpr uint32_t kReleaseDebounceMs = 60; + static constexpr uint32_t kHoldRepeatDelay = 800; + static constexpr uint32_t kHoldRepeatRate = 400; + + static bool touchWasDown = false; // panel reported a contact last pass + static bool touchActive = false; // that contact was accepted by the UI + static bool touchArmed = true; // false until a stable release is seen + static uint32_t touchUpAt = 0; + static uint32_t touchDownAt = 0; + static uint32_t touchAppliedAt = 0; + + LTouchPoint t; + const uint32_t now = launcherMillis(); + const bool touchDown = hal_touch_read(touchCfg(), t); + + if (!touchDown) { + if (touchWasDown) touchUpAt = now; + touchWasDown = false; + touchActive = false; + if (!touchArmed && now - touchUpAt >= kReleaseDebounceMs) touchArmed = true; + return; + } + + const bool newContact = !touchWasDown; + touchWasDown = true; + + // sd_functions' long-press detector polls launcherSelectHeld() and needs + // the contact re-asserted while LongPress is set. Only for a contact the + // UI already accepted -- never for one still inside the release window. + if (LongPress && touchActive) { + hal_touch_apply(t); + return; + } + + if (newContact) { + if (!touchArmed) return; // started inside the release debounce: ignore + touchArmed = false; + touchActive = true; + touchDownAt = now; + touchAppliedAt = now; + hal_touch_apply(t); + return; + } + + // Continuation of an accepted contact: repeat only after the initial + // delay, then at the slower hold rate. + if (!touchActive) return; + if (now - touchDownAt <= kHoldRepeatDelay) return; + if (now - touchAppliedAt < kHoldRepeatRate) return; + touchAppliedAt = now; + hal_touch_apply(t); +} diff --git a/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/platformio.ini b/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/platformio.ini new file mode 100644 index 00000000..ef2c629e --- /dev/null +++ b/boards/waveshare-esp32-p4-wifi6-touch-lcd-5/platformio.ini @@ -0,0 +1,139 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +; Waveshare ESP32-P4-WIFI6-Touch-LCD-5. +; +; ESP32-P4 main SoC + ESP32-C6 co-processor bridged over SDIO (WiFi/BT, same +; scheme as lilygo-t-display-p4/elecrow-esp32p4-7in), 720x1280 portrait +; MIPI-DSI HX8394 panel (2 lanes), GT911 I2C capacitive touch, microSD +; (SDMMC 4-bit, slot 0), 32MB NOR flash, CH343 USB-serial console on the H1 +; USB-C and a second USB-C (H2) wired to the P4's own high-speed OTG pads. +; +; Pins and timings come from Waveshare's own schematic, BSP and docs +; (waveshareteam/ESP32-P4-WIFI6-Touch-LCD-5: docs/IO.md, the audited +; esp_lcd_hx8394 driver and examples/arduino/libraries/displays/ +; displays_config.h) -- see connections.md for the full table. Verified on a +; P4 rev v1.3 unit: display, touch, microSD, the C6 WiFi bridge and app +; installs from SD all work; the audio codec is not used by this port. + +[env:waveshare-esp32-p4-wifi6-touch-lcd-5] +board = esp32p4_es +board_build.partitions = support_files/custom_32Mb_p4.csv +board_upload.flash_size = 32MB +board_upload.maximum_size = 33554432 +monitor_dtr = 0 +monitor_rts = 0 +build_src_filter =${env.build_src_filter} +<../boards/waveshare-esp32-p4-wifi6-touch-lcd-5> +build_flags = + ${env.build_flags} + -DBOARD_HAS_PSRAM + -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=0 + + -DDEVICE_NAME='"Waveshare ESP32-P4-WIFI6-Touch-LCD-5"' + -DOTA_TAG='"waveshare-esp32-p4-wifi6-touch-lcd-5"' + + # Native panel is 720(w)x1280(h) portrait; the UI is driven in landscape, + # same convention as the other portrait-glass P4/DSI boards in this repo + # (lilygo-t-display-p4, seeedstudio-reterminal-d1001). 3 is the value that + # comes up upright on hardware; use 1 if a different panel revision shows + # the image upside-down. + -DROTATION=3 + -DHAS_TOUCH=1 + -DDIMMER_SETUP=60 + -DFP=3 + -DFM=5 + -DFG=7 + + # WiFi/BT comes from an onboard ESP32-C6 co-processor bridged over SDIO, + # same scheme as m5stack-tab5 / lilygo-t-display-p4 / elecrow-esp32p4-7in. + # CLK=GPIO18, CMD=GPIO19, D0..D3=14..17 (each with its 51K pull-up) and + # the C6's own enable/reset (GPIO54 through 0R R34) follow Espressif's + # reference wiring for a P4+C6 pair per the schematic -- unlike Elecrow's + # board, whose D0..D3 ended up reversed on real hardware. If a revision + # disagrees, override at runtime with e.g. + # `sdio set 18 19 14 15 16 17 54` (see the "sdio" serial console command) + # rather than rebuilding. + -D SDIO2_CLK=GPIO_NUM_18 + -D SDIO2_CMD=GPIO_NUM_19 + -D SDIO2_D0=GPIO_NUM_14 + -D SDIO2_D1=GPIO_NUM_15 + -D SDIO2_D2=GPIO_NUM_16 + -D SDIO2_D3=GPIO_NUM_17 + -D SDIO2_RST=GPIO_NUM_54 + + # Display: HX8394 720x1280 MIPI-DSI panel, 2 data lanes, driven over + # Arduino_ESP32DSIPanel. Timings per Waveshare's own BSP + # (HX8394_720_1280_PANEL_30HZ_DPI_CONFIG: 58 MHz DPI clock, 700 Mbps + # lanes). TFT_DSI_INIT names the stock Arduino_GFX hx8394 table only to + # satisfy the build-time checks -- that array belongs to a different + # panel revision -- so interface.cpp re-aims displayConfig at Waveshare's + # official register sequence in _setup_gpio() (before tft->begin()), + # following the runtime-swap pattern boards/lilygo-t-display-p4 uses for + # its AMOLED variant. + -DTFT_DATABUS_N=4 # Arduino_ESP32DSIPanel + -DTFT_DISPLAY_DRIVER_N=50 # Arduino_DSI_Display + -DTFT_WIDTH=720 + -DTFT_HEIGHT=1280 + -DTFT_RST=27 + -DTFT_HSYNC_PULSE_WIDTH=20 + -DTFT_HSYNC_BACK_PORCH=20 + -DTFT_HSYNC_FRONT_PORCH=40 + -DTFT_VSYNC_PULSE_WIDTH=4 + -DTFT_VSYNC_BACK_PORCH=10 + -DTFT_VSYNC_FRONT_PORCH=24 + -DTFT_PREF_SPEED=58000000 + -DTFT_DSI_LANE_BIT_RATE=700 + -DTFT_DSI_INIT=hx8394_init_operations + # Backlight PWM, AC-coupled into the boost converter's feedback path: a + # static GPIO level lights nothing, it has to be driven by PWM (hal_bright + # uses 5 kHz ledc; Waveshare's own BSP drives 5 kHz/10-bit). + -DTFT_BL=26 + # Backlight boost converter enable (AP3032 EN through 0R R32), active + # HIGH. Driven once in _setup_gpio() before the PWM starts. + -DTFT_BL_EN=33 + + # Touch: GT911, capacitive, I2C, wired directly to the P4 on the shared + # board I2C bus (GPIO7/GPIO8, shared with the ES8311/ES7210 audio codec). + # RST=GPIO23 (0R R37); INT=GPIO2 reaches the touch glass only through an + # optional 0R (R108, not fitted by default) -- the GT911 latches one of + # two addresses (0x5D/0x14) and SensorLib's probe tries both, see + # interface.cpp. + -DTOUCH_CTRL_GT911=1 # hal_touch_init/hal_touch_read (src/hal/inputs/touch.cpp) + -DGT911_I2C_CONFIG_SDA_IO_NUM=7 + -DGT911_I2C_CONFIG_SCL_IO_NUM=8 + -DGT911_TOUCH_CONFIG_RST_GPIO_NUM=23 + -DGT911_TOUCH_CONFIG_INT_GPIO_NUM=2 + + # microSD: SDMMC 4-bit, slot 0, on the P4's dedicated SDMMC0 pads, fed by + # the on-chip LDO VO4 -- same wiring and macro set as + # elecrow-esp32p4-7in. See connections.md for the SDIO1+SDIO2 + # co-existence caveat. + -DUSE_SD_MMC=1 + -D SDM=SD_MMC + -DPIN_SD_CLK=43 + -DPIN_SD_CMD=44 + -DPIN_SD_D0=39 + -D BOARD_SDMMC_SLOT=0 + -D BOARD_HAS_SDMMC=1 + -D BOARD_SDMMC_POWER_CHANNEL=4 + + # The board's H2 USB-C port (the one NOT wired through the CH343 used + # for flashing/console) goes straight to the ESP32-P4's dedicated DM/DP + # pads per the schematic -- the same "own UTMI PHY, fixed pads" + # high-speed OTG controller lilygo-t-display-p4's "High Speed USB" port + # uses (see massStorage.cpp's rhport 0 vs 1 comment). + -D USB_MSC_HIGH_SPEED=1 + +lib_deps = + ${env.lib_deps} + +lib_ignore = + XPowersLib diff --git a/platformio.ini b/platformio.ini index b3163407..240bb982 100644 --- a/platformio.ini +++ b/platformio.ini @@ -102,6 +102,7 @@ default_envs = ; waveshare-esp32-c5-touch-lcd-35 ; waveshare-esp32-c6-lcd-147 ; waveshare-esp32-c6-lcd-130 + ; waveshare-esp32-p4-wifi6-touch-lcd-5 ; waveshare-esp32-s3-touch-amoled-206 ; xteink-x3-x4 ; xteink-x4pro diff --git a/support_files/custom_32Mb_p4.csv b/support_files/custom_32Mb_p4.csv new file mode 100644 index 00000000..d58f1a5e --- /dev/null +++ b/support_files/custom_32Mb_p4.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xE000, 0x2000, +app0, app, factory, 0x10000, 0x210000, +coredump, data, coredump, 0x220000, 0x10000,