Project Started


Created a refined rounded handheld enclosure for the controller, optimized for PETG printing on your Anycubic Kobra 3. It includes a front display bezel, four button caps, USB-C and sensor openings, PCB mounting, battery pocket/shield, and screw-secured rear lid.
Open items
  • Treat this as a controlled prototype print, not a production-qualified enclosure.
  • Print button, USB opening, and screw-pilot coupons first.
  • Display thickness, switch height, battery swelling allowance, connector-plug clearance, and screw torque require physical verification.
Recommended Kobra 3 setup
  • PETG, 0.4 mm nozzle, 0.20 mm layers
  • Front housing exterior face down
  • Rear lid exterior face down
  • Button caps face down
  • Use accessible supports where indicated by the slicer
  • Keep the battery disconnected during the first mechanical assembly test

Waiting for input


Firmware Getting Started
Complete Arduino Application
The complete Arduino sketch for this revision is available here:
It implements:
  • ST7796S 320x480 SPI display initialization and a battery/status screen.
  • MAX17048 battery voltage and percentage readings every five seconds.
  • FT6336U touch detection and activity tracking.
  • SW4 displayed-value reset to zero.
  • LTC2955 software shutdown through GPIO4 / ~KILL.
  • Long-press shutdown and automatic shutdown after five minutes idle.
  • I2C diagnostic scan and USB serial diagnostics.
Required Arduino Setup
  1. Install Arduino IDE 2.x.
  2. Add Espressif ESP32 boards through Boards Manager.
  3. Select ESP32S3 Dev Module.
  4. Set USB CDC On Boot = Enabled, Flash Size = 8 MB, and PSRAM = OPI PSRAM.
  5. Install Adafruit GFX Library from Library Manager.
  6. Open the downloaded .ino file, compile, and upload at 115200 baud.
Critical Battery-Gauge Hardware Correction
The live schematic currently shows U5 MAX17048 pin CELL as no-connect. Battery percentage and voltage cannot operate correctly until U5:CELL is connected to the BAT net. The firmware reports GAUGE ERROR when the MAX17048 does not return valid data. Correct this connection before ordering the next PCB revision.
Hardware GPIO Map

Table


FunctionESP32-S3 GPIO
USB D- / D+GPIO19 / GPIO20 (native USB)
I2C SDA / SCLGPIO8 / GPIO9
SPI MOSI / MISO / SCLKGPIO11 / GPIO13 / GPIO12
microSD CSGPIO10
LCD CS / DC / RESETGPIO14 / GPIO15 / GPIO16
LCD backlight PWMGPIO17
Touch INT / RESETGPIO18 / GPIO21
Soft-power KILLGPIO4
Soft-power interruptGPIO5
Fuel-gauge ALERTGPIO6
Regulator power-goodGPIO7
Zero displayed sensor values buttonGPIO1 (active-low, internal pull-up)
BOOTGPIO0
GPIO3, GPIO45, and GPIO46 are not assigned to peripherals. GPIO0 is reserved for BOOT.
PlatformIO Configuration
Create platformio.ini:

Ini


[env:esp32-s3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
build_flags =
  -D ARDUINO_USB_MODE=1
  -D ARDUINO_USB_CDC_ON_BOOT=1
Starter Firmware
The following uses only Arduino-ESP32 core libraries. It initializes native USB CDC, I2C, SPI, ST7796 control lines, FT6336 reset/interrupt, MAX17048 reads, PWM backlight, and idle-time shutdown. The display initializer is intentionally minimal; integrate a full graphics library after basic electrical bring-up.

Cpp


#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>

static constexpr int PIN_I2C_SDA = 8;
static constexpr int PIN_I2C_SCL = 9;
static constexpr int PIN_SPI_MOSI = 11;
static constexpr int PIN_SPI_MISO = 13;
static constexpr int PIN_SPI_SCLK = 12;
static constexpr int PIN_SD_CS = 10;
static constexpr int PIN_LCD_CS = 14;
static constexpr int PIN_LCD_DC = 15;
static constexpr int PIN_LCD_RST = 16;
static constexpr int PIN_LCD_BL = 17;
static constexpr int PIN_TP_INT = 18;
static constexpr int PIN_TP_RST = 21;
static constexpr int PIN_PWR_KILL = 4;
static constexpr int PIN_PWR_INT = 5;
static constexpr int PIN_FUEL_ALERT = 6;
static constexpr int PIN_PWR_GOOD = 7;
static constexpr int PIN_ZERO_VALUES = 1;

static constexpr uint8_t MAX17048_ADDR = 0x36;
static constexpr uint8_t FT6336_ADDR = 0x38;
static constexpr uint32_t IDLE_TIMEOUT_MS = 5UL * 60UL * 1000UL;

SPIClass displaySPI(FSPI);
volatile bool powerButtonEvent = false;
volatile bool touchEvent = false;
uint32_t lastActivityMs = 0;
float displayedSensorValue1 = 0.0f;
float displayedSensorValue2 = 0.0f;

void resetDisplayedSensorValues() {
  displayedSensorValue1 = 0.0f;
  displayedSensorValue2 = 0.0f;
  // Redraw the value fields here after integrating the graphics library.
  Serial.println("Displayed sensor values reset to zero");
}

void IRAM_ATTR onPowerButton() { powerButtonEvent = true; }
void IRAM_ATTR onTouch() { touchEvent = true; }

void lcdCommand(uint8_t cmd) {
  digitalWrite(PIN_LCD_DC, LOW);
  digitalWrite(PIN_LCD_CS, LOW);
  displaySPI.transfer(cmd);
  digitalWrite(PIN_LCD_CS, HIGH);
}

void lcdData(uint8_t data) {
  digitalWrite(PIN_LCD_DC, HIGH);
  digitalWrite(PIN_LCD_CS, LOW);
  displaySPI.transfer(data);
  digitalWrite(PIN_LCD_CS, HIGH);
}

void initDisplay() {
  pinMode(PIN_LCD_CS, OUTPUT);
  pinMode(PIN_LCD_DC, OUTPUT);
  pinMode(PIN_LCD_RST, OUTPUT);
  pinMode(PIN_SD_CS, OUTPUT);
  digitalWrite(PIN_LCD_CS, HIGH);
  digitalWrite(PIN_SD_CS, HIGH);

  digitalWrite(PIN_LCD_RST, LOW);
  delay(20);
  digitalWrite(PIN_LCD_RST, HIGH);
  delay(120);

  lcdCommand(0x01); // Software reset
  delay(150);
  lcdCommand(0x11); // Sleep out
  delay(120);
  lcdCommand(0x3A); // Pixel format
  lcdData(0x55);    // RGB565
  lcdCommand(0x36); // Memory access control
  lcdData(0x48);
  lcdCommand(0x29); // Display on
}

void initBacklight() {
  ledcAttach(PIN_LCD_BL, 20000, 8);
  ledcWrite(PIN_LCD_BL, 180);
}

void initTouch() {
  pinMode(PIN_TP_RST, OUTPUT);
  pinMode(PIN_TP_INT, INPUT_PULLUP);
  digitalWrite(PIN_TP_RST, LOW);
  delay(10);
  digitalWrite(PIN_TP_RST, HIGH);
  delay(100);
  attachInterrupt(PIN_TP_INT, onTouch, FALLING);
}

bool i2cRead(uint8_t address, uint8_t reg, uint8_t *data, size_t length) {
  Wire.beginTransmission(address);
  Wire.write(reg);
  if (Wire.endTransmission(false) != 0) return false;
  return Wire.requestFrom(address, length) == length &&
         ([&]() { for (size_t i = 0; i < length; ++i) data[i] = Wire.read(); return true; })();
}

float readBatteryPercent() {
  uint8_t raw[2];
  if (!i2cRead(MAX17048_ADDR, 0x04, raw, 2)) return NAN;
  return raw[0] + raw[1] / 256.0f;
}

float readBatteryVoltage() {
  uint8_t raw[2];
  if (!i2cRead(MAX17048_ADDR, 0x02, raw, 2)) return NAN;
  uint16_t value = (uint16_t(raw[0]) << 8) | raw[1];
  return (value >> 4) * 0.00125f;
}

bool readTouch(uint16_t &x, uint16_t &y) {
  uint8_t count;
  if (!i2cRead(FT6336_ADDR, 0x02, &count, 1) || (count & 0x0F) == 0) return false;
  uint8_t p[4];
  if (!i2cRead(FT6336_ADDR, 0x03, p, 4)) return false;
  x = ((p[0] & 0x0F) << 8) | p[1];
  y = ((p[2] & 0x0F) << 8) | p[3];
  return true;
}

[[noreturn]] void shutdownSystem() {
  ledcWrite(PIN_LCD_BL, 0);
  delay(20);
  // LTC2955 KILL is held high in normal operation. Pulling it low releases EN.
  digitalWrite(PIN_PWR_KILL, LOW);
  while (true) delay(1000); // Power should disappear here.
}

void setup() {
  pinMode(PIN_PWR_KILL, OUTPUT);
  digitalWrite(PIN_PWR_KILL, HIGH); // Must occur within LTC2955 startup blanking interval.
  pinMode(PIN_PWR_INT, INPUT_PULLUP);
  pinMode(PIN_FUEL_ALERT, INPUT_PULLUP);
  pinMode(PIN_PWR_GOOD, INPUT);
  pinMode(PIN_ZERO_VALUES, INPUT_PULLUP);
  attachInterrupt(PIN_PWR_INT, onPowerButton, FALLING);

  Serial.begin(115200); // Native USB CDC with PlatformIO build flags above.
  Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL, 400000);
  displaySPI.begin(PIN_SPI_SCLK, PIN_SPI_MISO, PIN_SPI_MOSI, PIN_LCD_CS);

  initBacklight();
  initDisplay();
  initTouch();
  lastActivityMs = millis();

  Serial.println("ESP32-S3 touch controller started");
}

void loop() {
  static bool previousZeroButton = HIGH;
  static uint32_t zeroButtonChangedMs = 0;
  bool zeroButton = digitalRead(PIN_ZERO_VALUES);
  if (zeroButton != previousZeroButton && millis() - zeroButtonChangedMs >= 30) {
    zeroButtonChangedMs = millis();
    previousZeroButton = zeroButton;
    if (zeroButton == LOW) {
      resetDisplayedSensorValues();
      lastActivityMs = millis();
    }
  }

  if (powerButtonEvent) {
    powerButtonEvent = false;
    lastActivityMs = millis();
    Serial.println("Power button event");
  }

  if (touchEvent) {
    touchEvent = false;
    uint16_t x, y;
    if (readTouch(x, y)) {
      lastActivityMs = millis();
      Serial.printf("Touch: %u,%u\n", x, y);
    }
  }

  static uint32_t lastBatteryRead = 0;
  if (millis() - lastBatteryRead >= 5000) {
    lastBatteryRead = millis();
    Serial.printf("Battery: %.2f V, %.1f %%\n", readBatteryVoltage(), readBatteryPercent());
  }

  if (millis() - lastActivityMs >= IDLE_TIMEOUT_MS) shutdownSystem();
  delay(10);
}
Bring-Up Sequence
  1. Power from a current-limited USB supply without the display connected; verify USB VBUS, BAT, SYS_RAW, and 3V3.
  2. Confirm pressing SW1 enables 3V3 and GPIO4 becomes high promptly.
  3. Confirm USB CDC enumeration and flash via native USB while holding BOOT if required.
  4. Scan I2C; expected devices are MAX17048 at 0x36 and FT6336 at 0x38 when the display is attached.
  5. Start the backlight at reduced PWM duty and verify display current before increasing brightness.
  6. Test firmware shutdown and confirm 3V3 falls while USB charging remains active.
Known Integration Item
J2 is electrically mapped to the verified 15-pin Waveshare order. Before PCB manufacture, verify that the selected 15-position 1.25 mm footprint mechanically mates with the exact display cable housing/keying.
  • Complete Arduino Application

  • Required Arduino Setup

  • Critical Battery-Gauge Hardware Correction

  • Hardware GPIO Map

  • PlatformIO Configuration

  • Starter Firmware

  • Bring-Up Sequence

  • Known Integration Item