Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Waveshare ESP32-P4-WIFI6-DEV-KIT with a KeDei 5" DSI display (800x480) - MicroPython

The display is a Raspberry Pi-style DSI panel: a Chipone ICN6211 DSI-to-RGB
bridge that the panel's own microcontroller configures, so nothing here writes
to the bridge. That controller sits at I2C 0x45 and speaks the official Pi
display's protocol (0x85 powers the panel, 0x86 sets the backlight). It also
acknowledges every I2C address, so an I2C scan of this bus is meaningless. The
touch controller is a FocalTech part at 0x38.

What the bridge needs, found on the bench (2026-10-06): non-burst video with
sync pulses and a continuous HS clock, one lane, and the timing it holds in its
own registers (0x20-0x29). Without non_burst/continuous_clock the panel stays
white; with Linux's TC358762 timing the picture shears.
"""

import time

from ft6x36 import FT6x36
from machine import I2C, Pin

from displaydev.fbdisplay import FBDisplay

try:
from mipidsi import Bus, Display
except ImportError as exc:
raise NotImplementedError("MIPI DSI requires displayif mipidsi cmod (esp32p4 port)") from exc

I2C_SCL = 8
I2C_SDA = 7
PANEL_CTRL = 0x45 # the panel's microcontroller
_POWERON = 0x85
_PWM = 0x86

i2c = I2C(1, scl=Pin(I2C_SCL), sda=Pin(I2C_SDA), freq=100_000)

# Panel off while the DSI link starts; the controller configures the bridge
# when it powers the panel, and that wants the clock already running.
i2c.writeto_mem(PANEL_CTRL, _POWERON, b"\x00")
time.sleep_ms(300)

display_bus = Bus(frequency=528_000_000, num_lanes=1, ldo_chan=3, ldo_voltage_mv=2500)

fb = Display(
display_bus,
b"",
width=800,
height=480,
color_depth=16,
pixel_clock_frequency=33_000_000, # 16 bits x 33 MHz = 528 Mbps on one lane
hsync_front_porch=46,
hsync_pulse_width=20,
hsync_back_porch=210,
vsync_front_porch=22,
vsync_pulse_width=10,
vsync_back_porch=23,
non_burst=True,
continuous_clock=True,
)

i2c.writeto_mem(PANEL_CTRL, _POWERON, b"\x01")
time.sleep_ms(300)
i2c.writeto_mem(PANEL_CTRL, _PWM, b"\xff") # backlight, 0-255

touch = FT6x36(i2c, address=0x38)
# The touch panel reads 180 degrees from the display: the default table
# shifted by two rotations.
touch_rotation_table = (0b110, 0b011, 0b000, 0b101)

display_drv = FBDisplay(fb)

touch_read = touch.read_points

from board_peripherals import PERIPHERALS, load_peripherals

load_peripherals(globals())
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Lazy constructors for the DEV-KIT's non-UI devices. PERIPHERALS = lazy roles only.

Only the C6 radio so far; the board's ES8311 codec, SD slot and camera aren't
described here yet.
"""
import boarddev
import sys

PERIPHERALS = frozenset({"wlan", "ble"})


def load_peripherals(ns):
boarddev.bind_lazy(ns, sys.modules[__name__])


def wlan():
import network

return network.WLAN(network.STA_IF)


def ble():
# A bledev adapter (docs/ble.md §2). It also answers every bluetooth.BLE
# method, so code written for the raw radio keeps working. Without bledev
# (or aioble) installed, the raw radio, as before.
try:
import bledev.mpble
except ImportError:
import bluetooth

return bluetooth.BLE()
return bledev.mpble.get()
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"urls": [
[
"board_config.py",
"github:PyDevices/pydevices/board_configs/fbdisplay/esp32-p4-wifi6-dev-kit_kedei-5in-dsi/board_config.py"
],
[
"board_peripherals.py",
"github:PyDevices/pydevices/board_configs/fbdisplay/esp32-p4-wifi6-dev-kit_kedei-5in-dsi/board_peripherals.py"
],
[
"ft6x36.py",
"github:PyDevices/pydevices/drivers/touch/ft6x36.py"
]
],
"deps": [
[
"pydevices",
"latest"
]
],
"version": "0.1",
"notes": "KeDei 5in DSI (ICN6211) on the DEV-KIT's DSI connector; needs displayif with mipidsi non_burst/continuous_clock. Radio only; codec/SD/camera not yet described."
}
1 change: 1 addition & 0 deletions docs/board-configs.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ An exact match for all four is rare; bus + display controller is usually enough
|-----------|----------|
| `fbdisplay/qualia_tl040hds20` | MicroPython Qualia RGB |
| `fbdisplay/esp32-p4-wifi6-touch-lcd-4b` | Waveshare ESP32-P4-WIFI6-Touch-LCD-4B (MIPI-DSI, touch, ES8311) |
| `fbdisplay/esp32-p4-wifi6-dev-kit_kedei-5in-dsi` | Waveshare ESP32-P4-WIFI6-DEV-KIT with a KeDei 5" Raspberry Pi-style DSI display (ICN6211 bridge, FocalTech touch) |
| `fbdisplay/t-rgb_480` | LilyGO T-RGB 480×480 ST7701 (ESP32-S3; RGB via pydevices/displayif) |
| `fbdisplay/pico2_dvi_sock_640x480` | Pico 2 + Adafruit DVI Sock or PiCowbell HSTX |
| `fbdisplay/pico2w_dvi_sock_640x480` | Pico 2 W + Sock / PiCowbell HSTX |
Expand Down
1 change: 1 addition & 0 deletions docs/device-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Boards whose display is the reason to buy them.
| Product | `board_config` path | Eager UI | Lazy `PERIPHERALS` |
|---|---|---|---|
| Waveshare ESP32-P4-WIFI6-Touch-LCD-4B | `fbdisplay/esp32-p4-wifi6-touch-lcd-4b` | `touch` | `audio_out`, `audio_in`, `sdcard`, `camera`, `radio`, `wlan`, `ble`, `usb_device` |
| Waveshare ESP32-P4-WIFI6-DEV-KIT + KeDei 5" DSI | `fbdisplay/esp32-p4-wifi6-dev-kit_kedei-5in-dsi` | `touch` | `wlan`, `ble` |
| Adafruit Qualia S3 + TL040HDS20 | `fbdisplay/qualia_tl040hds20` | `touch`, `keypad`, `io_expander` | `wlan`, `ble` |
| Waveshare ESP32-S3-Touch-LCD-4.3 | `fbdisplay/esp32-s3-touch-lcd-4_3` | `touch`, `io_expander` | `sdcard`, `can`, `rs485`, `usb_device`, `wlan`, `ble` |
| Waveshare ESP32-S3-Touch-LCD-7 | `fbdisplay/esp32-s3-touch-lcd-7` | `touch`, `io_expander` | `sdcard`, `can`, `rs485`, `usb_device`, `wlan`, `ble` |
Expand Down
1 change: 1 addition & 0 deletions docs/display-boards.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ variant (`C6_WIFI`).
| Product / nickname | `board_config` dir | Resolution | Panel / bus | Touch | Expander / IO |
|---|---|---|---|---|---|
| Waveshare ESP32-P4-WIFI6-Touch-LCD-4B | `esp32-p4-wifi6-touch-lcd-4b` | 720×720 | ST7703 **MIPI DSI** (`mipidsi`) | GT911 | — |
| Waveshare ESP32-P4-WIFI6-DEV-KIT + KeDei 5" DSI | `esp32-p4-wifi6-dev-kit_kedei-5in-dsi` | 800×480 | ICN6211 bridge, **MIPI DSI** (`mipidsi`, `non_burst`, `continuous_clock`) | FocalTech (`ft6x36`) | — |
| Adafruit Qualia S3 + TL040HDS20 | `qualia_tl040hds20` (+ CP under `cp/fbdisplay/qualia_tl040hds20`) | 720×720 | RGB-666→565 **DotClock** | FT6x36 @ `0x48` | PCA9554 @ `0x3f` |
| Waveshare ESP32-S3-Touch-LCD-4.3 | `esp32-s3-touch-lcd-4_3` | 800×480 | ST7262 RGB **DotClock** | GT911 @ `0x5D` | CH422G |
| LILYGO T-RGB 2.1″ round | `t-rgb_480` | 480×480 | ST7701 RGB **DotClock** | CST820 (`cst8xx`) | XL9535 |
Expand Down
2 changes: 1 addition & 1 deletion docs/touch-drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ installers already include the drivers they require.

| File | Chip | Typical boards |
|------|------|----------------|
| `ft6x36.py` | FocalTech FT6x36 | ESP32-S3 dev boards, Wokwi |
| `ft6x36.py` | FocalTech FT6x36 (and the FT5426-family part on a KeDei 5" DSI) | ESP32-S3 dev boards, Wokwi, the P4 DEV-KIT with a KeDei 5" |
| `tt21100.py` | TT21100 | PyPortal, FunHouse |
| `stmpe610.py` | STMPE610 | PiTFT FeatherWing |
| `xpt2046.py` | XPT2046 | Resistive SPI touch |
Expand Down
6 changes: 5 additions & 1 deletion drivers/touch/ft6x36.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ def get_gesture(self) -> int:

def get_positions(self) -> list:
positions = []
num_points = self._i2c.readfrom_mem(self._address, _TD_STATUS_REG, 1)[0] & 0x0F
status = self._i2c.readfrom_mem(self._address, _TD_STATUS_REG, 1)[0]
# Some FocalTech firmware (an FT5426-family part on a KeDei 5" DSI
# panel) reports 0xFF here with nothing touching; read as 15 points it
# was a permanent phantom press. This driver reads at most two.
num_points = 0 if status == 0xFF else status & 0x0F
if num_points > 0:
positions.append(self._get_p1())
if num_points > 1:
Expand Down
Loading