This repository is a source starter for the Waveshare ESP32-S3 Touch AMOLED 1.8 board.
The firmware uses ESP-IDF 5.5.5 and the Waveshare board support package. It includes these features:
- A 368 by 448 LVGL display with touch input.
- Warm-reset recovery for the AMOLED panel.
- A captive portal for WiFi setup.
- A WiFi-only USB provisioning fallback.
- An AXP2101 power monitor.
- A USB screenshot channel.
- A demo screen that shows WiFi state and free heap.
- Host tools for safe flash backup, restore, and credential removal.
The demo cycles display brightness between 85 percent and 40 percent when you tap the screen.
You need these items:
- A Waveshare ESP32-S3 Touch AMOLED 1.8 board.
- A USB data cable.
- A macOS, Linux, or Windows host computer.
- Git and mise.
- A WiFi network.
CI builds the firmware on Linux. The hardware procedure is tested on macOS.
- Clone this repository.
- Enter the repository directory.
- Connect the board with a USB data cable.
- Run
mise install. - Run
mise run flashwithout--force. - Scan the QR code on the screen.
- Join the
app-setup-XXXXnetwork that the screen shows. - Select your WiFi network in the captive portal.
- Enter the WiFi password.
- Submit the form.
The first flash stores a credential-free factory backup in backups/.
CAUTION: Do not use mise run flash -- --force for the first flash. This command takes no factory backup.
The board saves WiFi values only after it gets an IP address. Then the board restarts and opens the demo screen.
Read INSTALL.md for the full installation and recovery procedures.
The setup screen shows a temporary network name, password, and QR code. The network password changes after each restart.
The captive portal supports these network types:
- Open networks.
- WPA2-Personal networks.
- WPA3-Personal networks.
The portal marks WEP and enterprise networks as unsupported. It does not send the WiFi password to a log or response.
If the phone does not open the portal, go to http://192.168.4.1/. Keep the phone connected when it reports no internet.
If you cannot use the captive portal, write the WiFi values through USB:
- Keep the board connected through USB.
- Run
mise run provision. - Enter the WiFi network name.
- Enter the WiFi password.
The command writes only wifi_ssid and wifi_pass to the NVS partition. It does not read credential values from the board.
provision depends on backup. Thus, the command cannot overwrite the last chance to save a credential-free factory image.
Close the serial monitor before a capture. Then run:
mise run screenshot -- screen.pngThe command saves a 368 by 448 PNG file. It refuses to replace an existing file.
The placeholder product name is app. Choose a product name before you add product code.
The name must contain one through three lowercase ASCII letters (a through z). This limit keeps the setup network name in its buffer.
If you use a longer name, increase APP_PORTAL_AP_NAME_CAPACITY in firmware/main/provisioning/portal.h by one byte for each additional letter. Then run mise run build.
ESP-IDF requires the exact app_main entry-point name. Never rename this function or its references.
The source file can have a new name. If you rename the file, update firmware/main/CMakeLists.txt.
The app value in the second field of the factory row in firmware/partitions.csv is an ESP-IDF partition type. Never rename it.
The product placeholder appears in these forms:
- Standalone lowercase
appproduct text and literal values. app_for public firmware functions and file names.APP_for capacity macros and environment variables.Appfor product text that starts with an uppercase letter.project(app)for the ESP-IDF project.app-setup-for setup network names.app-provision-for temporary directory names..app-install-versionfor the toolchain marker file."app"for the NVS namespace and other exact literal values.
Run these commands to list the placeholder locations and file names:
git grep -n -w 'app'
git grep -n 'app_'
git grep -n 'APP_'
git grep -n -w 'App'
git grep -n 'project(app)'
git grep -n 'app-setup-'
git grep -n 'app-provision-'
git grep -n '.app-install-version'
git grep -n '"app"'
git ls-files | grep -E 'app_|APP_|app-setup-|app-provision-|\.app-install-version'The first search includes the reserved partition type. The app_ searches include the exact app_main name.
Rename all other placeholder forms. Rename one form at a time.
Keep the firmware and host names equal. Update references when you rename a file.
Renamed Python imports can have a different sort order. Let Ruff sort the imports before Ruff formats the files.
After the rename, run these commands:
ruff check --fix .
mise run format
mise run build
mise run test-host
mise run format-check
mise run lint
mise run licenses
mise run secrets
python - <<'PY'
from pathlib import Path
import os
import re
import subprocess
entry_point = re.compile(r"(?<![A-Za-z0-9_])app_main(?![A-Za-z0-9_])")
placeholder = re.compile(
r"(?<![A-Za-z0-9_])(?:app|App)(?![A-Za-z0-9_])|app_|APP_"
)
definition = re.compile(r"^void[ \t]+app_main\(void\)[ \t]*$")
result = subprocess.run(
["git", "ls-files", "-z"],
check=True,
stdout=subprocess.PIPE,
)
paths = [os.fsdecode(value) for value in result.stdout.split(b"\0") if value]
factory_rows = []
definitions = []
violations = []
for path in paths:
masked_path = entry_point.sub("", path)
if placeholder.search(masked_path):
violations.append(path)
if path == "README.md":
continue
data = Path(path).read_bytes()
if b"\0" in data:
continue
for line_number, line in enumerate(data.decode("utf-8").splitlines(), 1):
masked_line = entry_point.sub("", line)
if definition.fullmatch(line):
definitions.append(f"{path}:{line_number}")
if path == "firmware/partitions.csv" and not line.lstrip().startswith("#"):
fields = [field.strip() for field in line.split(",")]
if fields and fields[0] == "factory":
factory_rows.append((line_number, tuple(fields)))
if len(fields) > 1 and fields[1] == "app":
fields[1] = "ESP_IDF_PARTITION_TYPE"
masked_line = ",".join(fields)
if placeholder.search(masked_line):
violations.append(f"{path}:{line_number}:{line}")
if len(factory_rows) != 1 or len(factory_rows[0][1]) < 2:
raise SystemExit("firmware/partitions.csv must contain one factory row")
if factory_rows[0][1][1] != "app":
raise SystemExit("The factory partition type must stay app")
if len(definitions) != 1:
raise SystemExit("The firmware must contain one exact app_main definition")
if violations:
print("Rename these remaining placeholder forms:")
print(*violations, sep="\n")
raise SystemExit(1)
PYThe final search permits only the reserved app partition type and the exact app_main name. It validates both exceptions.
References in tests and documents can keep the exact app_main name. These references name the required ESP-IDF entry point.
The NVS namespace has two matching definitions:
APP_NVS_NAMESPACEinfirmware/main/credentials.c.NVS_NAMESPACEintools/provision.py.
The device identity check in tools/deprovision.py also contains the ESP-IDF project name.
- INSTALL.md gives installation, update, credential-removal, and factory-recovery procedures.
- docs/TROUBLESHOOTING.md gives recovery steps for common board and tool problems.
- CLAUDE.md records the board rules, module map, safety gates, and project conventions.
Project code uses the Apache License 2.0. Read LICENSE.