Bruce Firmware – Build Guide for CachyOS / Arch Linux
Custom Board: ESP32_S3-16N8R_ili9341_xpt2046
Hardware:
- ESP32-S3 QFN56 rev v0.2 · 16 MB Flash · 8 MB OPI PSRAM
- Display: ILI9341 · Touch: XPT2046
- Crystal: 40 MHz · Baud: 921600
1 · System Dependencies
sudo pacman -S --needed git python python-pip uv base-devel
Note:
uvis available in the AUR if not already installed:BASHyay -S uv
2 · Python Virtual Environment (uv)
PlatformIO wird als globales Tool installiert – kein projektgebundenes .venv nötig.
# Install PlatformIO as a global uv tool
uv tool install platformio --with littlefs-python --with fatfs-ng --with pyyaml
# Verify install and add to PATH (only once, if not already in ~/.local/bin)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
# Confirm
pio --version
Alternativ, falls ein projektgebundenes venv bevorzugt wird:
# Project-scoped venv
uv venv
uv pip install platformio
source .venv/bin/activate
pio --version
3 · Clone Bruce Firmware
git clone --recursive https://github.com/pr3y/Bruce.git
cd Bruce
--recursiveist wichtig – Bruce hat Git-Submodules.
4 · Custom Board Files erstellen
4.1 · Verzeichnisstruktur anlegen
mkdir -p boards/ESP32_S3-16N8R_ili9341_xpt2046
4.2 · pins_arduino.h
cat > boards/ESP32_S3-16N8R_ili9341_xpt2046/pins_arduino.h << 'EOF'
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include "soc/soc_caps.h"
#include <stdint.h>
#ifndef DEVICE_NAME
#define DEVICE_NAME "ESP32-S3 16N8R ILI9341"
#endif
static const uint8_t TX = 43;
static const uint8_t RX = 44;
static const uint8_t SDA = 47;
static const uint8_t SCL = 48;
static const uint8_t SS = 8;
static const uint8_t MOSI = 35;
static const uint8_t MISO = 37;
static const uint8_t SCK = 36;
#define SERIAL_RX 44
#define SERIAL_TX 43
#define USB_as_HID 1
#define FP 1
#define FM 2
#define FG 3
// ── Display: ILI9341 via TFT_eSPI ───────────────────────────────────────────
#define HAS_SCREEN 1
#define ROTATION 2
#define MINBRIGHT (uint8_t)1
#define USER_SETUP_LOADED 1
#define ILI9341_DRIVER 1
#define TFT_RGB_ORDER TFT_BGR
#define TFT_BACKLIGHT_ON 1
#define TFT_CS 15
#define TFT_DC 16
#define TFT_RST 9
#define TFT_MOSI 18
#define TFT_SCLK 17
#define TFT_MISO 13
#define TFT_BL 21
#define SMOOTH_FONT 1
#define SPI_FREQUENCY 10000000
#define SPI_READ_FREQUENCY 8000000
#define SPI_TOUCH_FREQUENCY 2500000
// ── Touch: XPT2046 via CYD28_TouchR (bit-banged SPI) ────────────────────────
#define HAS_TOUCH 1
#define CYD28_TouchR_CS 4
#define CYD28_TouchR_IRQ 5
#define CYD28_TouchR_MOSI 11
#define CYD28_TouchR_MISO 10
#define CYD28_TouchR_CLK 12
#define TOUCH_CS CYD28_TouchR_CS
// ── Main SPI bus (external modules) ─────────────────────────────────────────
// NOTE: CC1101_SS_PIN and SDCARD_CS are intentionally omitted.
// Defining them as -1 causes ESP-HAL to map to GPIO 255 (0xFF),
// which is not a valid pin → "Invalid pin: 255" boot errors.
#define SPI_SCK_PIN 36
#define SPI_MOSI_PIN 35
#define SPI_MISO_PIN 37
#define SPI_SS_PIN 8
// ── Grove I2C ────────────────────────────────────────────────────────────────
#define GROVE_SDA 47
#define GROVE_SCL 48
#endif
EOF
4.3 · interface.cpp
cat > boards/ESP32_S3-16N8R_ili9341_xpt2046/interface.cpp << 'EOF'
#include "core/powerSave.h"
#include "core/utils.h"
#include <CYD28_TouchscreenR.h>
#include <interface.h>
CYD28_TouchR touch(320, 240);
void _setup_gpio() {
bruceConfig.colorInverted = 0;
bruceConfigPins.rotation = ROTATION;
pinMode(TFT_BL, OUTPUT);
ledcAttach(TFT_BL, 5000, 8);
ledcWrite(TFT_BL, 255);
}
void _post_setup_gpio() {
if (!touch.begin()) {
Serial.println("Touch IC not Started");
log_i("Touch IC not Started");
} else {
Serial.println("Touch IC Started");
}
}
int getBattery() { return 0; }
void _setBrightness(uint8_t brightval) {
int dutyCycle;
if (brightval == 100) dutyCycle = 255;
else if (brightval == 75) dutyCycle = 130;
else if (brightval == 50) dutyCycle = 70;
else if (brightval == 25) dutyCycle = 20;
else if (brightval == 0) dutyCycle = 0;
else dutyCycle = ((brightval * 255) / 100);
ledcWrite(TFT_BL, dutyCycle);
}
void InputHandler(void) {
static unsigned long tm = millis();
if (millis() - tm > 200 || LongPress) {
if (touch.touched()) {
auto t = touch.getPointScaled();
t = touch.getPointScaled();
tm = millis();
if (bruceConfigPins.rotation == 3) {
t.y = (tftHeight + 20) - t.y;
t.x = tftWidth - t.x;
}
if (bruceConfigPins.rotation == 0) {
int tmp = t.x;
t.x = tftWidth - t.y;
t.y = tmp;
}
if (bruceConfigPins.rotation == 2) {
int tmp = t.x;
t.x = t.y;
t.y = (tftHeight + 20) - tmp;
}
if (!wakeUpScreen()) AnyKeyPress = true;
else return;
touchPoint.x = t.x;
touchPoint.y = t.y;
touchPoint.pressed = true;
touchHeatMap(touchPoint);
} else {
touchPoint.pressed = false;
}
}
}
void powerOff() {
esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, LOW);
esp_deep_sleep_start();
}
void checkReboot() {}
EOF
4.4 · PlatformIO Environment (.ini)
Die Environment-Konfiguration wird direkt in die Haupt-platformio.ini eingetragen.
Öffne platformio.ini und füge am Ende den folgenden Block hinzu:
[env:ESP32_S3-16N8R_ili9341_xpt2046]
board = esp32-s3-devkitc-1
board_build.partitions = custom_16Mb.csv
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
board_build.f_flash = 80000000L
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
build_src_filter = ${env.build_src_filter} +<../boards/ESP32_S3-16N8R_ili9341_xpt2046>
build_flags =
${env.build_flags}
-Iboards/ESP32_S3-16N8R_ili9341_xpt2046
-Os
-DCORE_DEBUG_LEVEL=1
-DBOARD_HAS_PSRAM
-DUSE_HSPI_PORT=1
-DDEVICE_NAME='"ESP32-S3 16N8R ILI9341"'
upload_speed = 921600
monitor_speed = 115200
lib_deps =
${env.lib_deps}
Warum
esp32-s3-devkitc-1statt Custom-JSON?
Der Standard-ESP32-S3-Devkit ist im PlatformIO-Board-Registry vorhanden und passt für
den QFN56 ES32-S3. Die eigentliche Hardware-Konfiguration erfolgt über diebuild_flags
undboard_build.*-Optionen. Ein eigenes Board-JSON wäre nur nötig bei abweichenden
Upload-Protokollen oder ungewöhnlichen MCU-Varianten.
5 · Partition Table prüfen
Bruce liefert custom_16Mb.csv bereits mit. Prüfen ob vorhanden:
ls partitions/custom_16Mb.csv
# oder im Root-Verzeichnis:
ls custom_16Mb.csv
Falls nicht vorhanden, anlegen:
cat > custom_16Mb.csv << 'EOF'
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x650000,
app1, app, ota_1, 0x660000, 0x650000,
spiffs, data, spiffs, 0xCB0000, 0x340000,
coredump, data, coredump, 0xFF0000, 0x10000,
EOF
6 · PlatformIO initialisieren
Beim ersten Start werden Toolchain, ESP32-Arduino-Core und alle Libraries
automatisch heruntergeladen (~500 MB, nur einmalig):
# Activate venv if you used the project-scoped approach
# source .venv/bin/activate
pio pkg update
7 · Build
pio run -e ESP32_S3-16N8R_ili9341_xpt2046
Ausgabe bei Erfolg:
RAM: [= ] x.x% (used xxxxx bytes from 327680 bytes)
Flash: [=== ] xx.x% (used xxxxxxx bytes from 16777216 bytes)
================================ [SUCCESS] ================================
Paralleles Kompilieren beschleunigen (nutzt alle CPU-Kerne):
pio run -e ESP32_S3-16N8R_ili9341_xpt2046 -j $(nproc)
8 · Flash
8.1 · Port ermitteln
# ESP32-S3 via USB-JTAG/Serial (Espressif 0x1001) erscheint als:
ls /dev/ttyACM* # USB-JTAG bridge (häufigster Fall)
ls /dev/ttyUSB* # Falls separater USB-Serial-Chip verbaut
8.2 · User zur dialout-Gruppe hinzufügen (einmalig)
sudo usermod -aG uucp $USER # Arch verwendet 'uucp', nicht 'dialout'
# Logout/Login erforderlich, oder:
newgrp uucp
8.3 · Flash-Befehl
pio run -e ESP32_S3-16N8R_ili9341_xpt2046 --target upload
Mit explizitem Port (falls automatische Erkennung fehlschlägt):
pio run -e ESP32_S3-16N8R_ili9341_xpt2046 --target upload --upload-port /dev/ttyACM0
8.4 · Serial Monitor
pio device monitor -e ESP32_S3-16N8R_ili9341_xpt2046 -b 115200
Beenden: Ctrl+C
9 · ESP32-S3 Boot Mode (falls Upload fehlschlägt)
Der ESP32-S3 DevKit unterstützt automatisches Reset via DTR/RTS.
Falls das nicht funktioniert, manuell in den Download-Modus:
BOOT-Taste gedrückt haltenRST-Taste kurz drückenBOOTloslassen- Upload starten
10 · Troubleshooting
| Symptom | Ursache | Fix |
|---|---|---|
Invalid pin: 255 |
-1 in pin defines |
CC1101_SS_PIN / SDCARD_CS auskommentiert lassen |
TG1WDT_SYS_RST Boot-Loop |
GPIO 255 Exception | Obiges Fix, neu flashen |
SPIWP:0xee · mode:DIO statt QIO |
Flash-Mode-Mismatch | board_build.flash_mode = qio in ini prüfen |
Upload schlägt fehl mit Permission denied |
Fehlende Gruppe | sudo usermod -aG uucp $USER + neu einloggen |
esptool.py: command not found |
PlatformIO noch nicht initialisiert | pio pkg update ausführen |
| PSRAM nicht erkannt | qio_opi nicht gesetzt |
board_build.arduino.memory_type = qio_opi prüfen |
Zusammenfassung der Dateipfade
Bruce/
├── platformio.ini ← env-Block hier einfügen
├── custom_16Mb.csv ← Partition table
└── boards/
└── ESP32_S3-16N8R_ili9341_xpt2046/
├── pins_arduino.h ← Pin-Definitionen
└── interface.cpp ← GPIO / Touch / Brightness