Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ firmware/
*.map
tools/pushall_dump.json
tools/ams_dump.json

# Vendor esp32s3 libs folder — 212 MB, copied locally during JC3248W535
# driver work. Not needed for the build; kept outside the repo.
lib/arduino_esp32s3_libs_vendor/
4 changes: 3 additions & 1 deletion include/layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
// gauge positions, text positions, etc.
// To add a new display: create layout_xxx.h and add an #elif here.

#if defined(DISPLAY_240x320)
#if defined(DISPLAY_320x480)
#include "layout_320x480.h" // 320x480 portrait (Guition JC3248W535)
#elif defined(DISPLAY_240x320)
#include "layout_240x320.h" // 240x320 portrait (CYD, Waveshare)
#else
#include "layout_default.h" // ESP32-S3 Mini: ST7789 240x240
Expand Down
134 changes: 134 additions & 0 deletions include/layout_320x480.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#ifndef LAYOUT_320x480_H
#define LAYOUT_320x480_H

// Layout profile: 320x480 portrait (Guition JC3248W535, AXS15231B QSPI IPS).
// Redesigned layout that uses the extra screen real estate — does not simply
// stretch the 240x320 layout. Gauges are larger, AMS strip is always visible,
// and the ETA / bottom status areas are generously sized.

// --- Screen dimensions ---
#define LY_W 320
#define LY_H 480

// --- LED progress bar (top, y=0) ---
#define LY_BAR_W 316
#define LY_BAR_H 7

// --- Header bar ---
#define LY_HDR_Y 10
#define LY_HDR_H 26
#define LY_HDR_NAME_X 8
#define LY_HDR_CY 23 // vertical center of header text
#define LY_HDR_BADGE_RX 10 // badge right margin from SCREEN_W
#define LY_HDR_DOT_CY 13 // multi-printer indicator dot Y

// --- Printing: 2x3 gauge grid (3 columns, 2 rows) ---
// 320px wide split into 3 columns of ~107px — gauges are r=48, spacing tuned
// so left/right edges sit ~5px from the screen edges.
#define LY_GAUGE_R 48 // radius for all gauges (was 32 on 240x)
#define LY_GAUGE_T 9 // progress arc thickness (was 6 on 240x)
#define LY_COL1 56
#define LY_COL2 160
#define LY_COL3 264
#define LY_ROW1 92 // top row center Y (gauge top edge y=44)
#define LY_ROW2 228 // bottom row center Y (gauge top edge y=180)

// --- AMS tray visualization zone (below gauge grid) ---
// Row 2 gauges bottom edge is at y=276 (228+48). Labels extend ~12px below,
// so AMS starts at y=295 with 4px gap under it (ETA begins at y=355).
#define LY_AMS_Y 295
#define LY_AMS_H 56
#define LY_AMS_BAR_H 32
#define LY_AMS_BAR_GAP 3
#define LY_AMS_GROUP_GAP 10
#define LY_AMS_LABEL_OFFY 4
#define LY_AMS_MARGIN 10
#define LY_AMS_BAR_MAX_W 42

// --- Printing: ETA / info zone ---
#define LY_ETA_Y 360
#define LY_ETA_H 46
#define LY_ETA_TEXT_Y 383

// --- Printing: bottom status bar ---
#define LY_BOT_Y 414
#define LY_BOT_H 26
#define LY_BOT_CY 427

// --- Printing: WiFi signal indicator ---
#define LY_WIFI_X 6
#define LY_WIFI_Y 452

// --- Idle screen (with printer) ---
#define LY_IDLE_NAME_Y 45
#define LY_IDLE_STATE_Y 75
#define LY_IDLE_STATE_H 28
#define LY_IDLE_STATE_TY 89
#define LY_IDLE_DOT_Y 125
#define LY_IDLE_GAUGE_R 46
#define LY_IDLE_GAUGE_Y 210
#define LY_IDLE_G_OFFSET 80

// --- Idle screen: AMS zone (below gauges) ---
#define LY_IDLE_AMS_Y 275
#define LY_IDLE_AMS_H 80
#define LY_IDLE_AMS_BAR_H 46

// --- Idle screen (no printer) ---
#define LY_IDLE_NP_TITLE_Y 60
#define LY_IDLE_NP_WIFI_Y 120
#define LY_IDLE_NP_DOT_Y 150
#define LY_IDLE_NP_MSG_Y 200
#define LY_IDLE_NP_OPEN_Y 240
#define LY_IDLE_NP_IP_Y 290

// --- Finished screen (portrait, vertically centered) ---
#define LY_FIN_GAUGE_R 48
#define LY_FIN_GL 96
#define LY_FIN_GR 224
#define LY_FIN_GY 150
#define LY_FIN_TEXT_Y 245
#define LY_FIN_FILE_Y 290
#define LY_FIN_KWH_Y 320
#define LY_FIN_AMS_Y 345
#define LY_FIN_AMS_H 65
#define LY_FIN_AMS_BAR_H 38
#define LY_FIN_BOT_Y 436
#define LY_FIN_BOT_H 28
#define LY_FIN_WIFI_Y 458

// --- AP mode screen ---
#define LY_AP_TITLE_Y 60
#define LY_AP_SSID_LBL_Y 120
#define LY_AP_SSID_Y 160
#define LY_AP_PASS_LBL_Y 210
#define LY_AP_PASS_Y 240
#define LY_AP_OPEN_Y 280
#define LY_AP_IP_Y 315

// --- Simple clock (centered in 480px height) ---
#define LY_CLK_CLEAR_Y 110
#define LY_CLK_CLEAR_H 280
#define LY_CLK_TIME_Y 210
#define LY_CLK_AMPM_Y 265
#define LY_CLK_DATE_Y 310

// --- Pong/Breakout clock (scaled for 320x480) ---
#define LY_ARK_BRICK_ROWS 5
#define LY_ARK_COLS 10
#define LY_ARK_BRICK_W 30 // 10 cols * 30 + 9 gaps * 2 = 318 (fits 320)
#define LY_ARK_BRICK_H 12
#define LY_ARK_BRICK_GAP 2
#define LY_ARK_START_X 1
#define LY_ARK_START_Y 40
#define LY_ARK_PADDLE_Y 460
#define LY_ARK_PADDLE_W 44
#define LY_ARK_TIME_Y 220
#define LY_ARK_DATE_Y 10
#define LY_ARK_DIGIT_W 42
#define LY_ARK_DIGIT_H 64
#define LY_ARK_COLON_W 16
#define LY_ARK_DATE_CLR_X 50
#define LY_ARK_DATE_CLR_W 220

#endif // LAYOUT_320x480_H
11 changes: 11 additions & 0 deletions partitions_16mb.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Partition table for 16 MB flash (Guition JC3248W535 — ESP32-S3-N16R8)
# Dual OTA app slots of 6.25 MB each — large headroom for future features.
# SPIFFS reserved at end but unused (settings stored in NVS).
# NOTE: changing the partition table requires a full USB flash; OTA cannot update it.
#
# Name, Type, SubType, Offset, Size,
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x640000,
app1, app, ota_1, 0x650000, 0x640000,
spiffs, data, spiffs, 0xc90000, 0x360000,
46 changes: 44 additions & 2 deletions platformio.ini
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
; BambuHelper - Bambu Lab Printer Monitor
; Supports ESP32-S3 Super Mini, ESP32-2432S028 (CYD), ESP32-C3 Super Mini
; Supports ESP32-S3 Super Mini, ESP32-2432S028 (CYD), ESP32-C3 Super Mini,
; Waveshare ESP32-S3-Touch-LCD-2 / -1.54, Guition JC3248W535

; --- Shared library dependencies ---
[common]
lib_deps =
lovyan03/LovyanGFX@^1.1.16
lovyan03/LovyanGFX@^1.2.19
knolleary/PubSubClient@^2.8
bblanchon/ArduinoJson@^7.0

Expand Down Expand Up @@ -128,6 +129,47 @@ build_flags =
-D CST816_RST=47
-D CST816_IRQ=48

; =============================================================================
; Guition JC3248W535 - 3.5" 320x480 IPS (AXS15231B QSPI + I2C touch)
; https://www.guition.com/ — ESP32-S3-N16R8, 16MB flash / 8MB PSRAM.
; Display driver wraps Arduino_GFX's Arduino_AXS15231B inside a LovyanGFX
; Panel_Device subclass (src/lgfx_panel_axs15231b_agfx.hpp), since
; mainline LovyanGFX does not ship Panel_AXS15231B.
;
; Touch RST/INT pins are not wired on the shipped Guition boards inspected —
; the touch IC shares reset with the display and is polled (no INT). If your
; board revision exposes these pins, add AXS_TOUCH_RST / AXS_TOUCH_IRQ here.
; =============================================================================
[env:jc3248w535]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
board_build.partitions = partitions_16mb.csv
board_build.arduino.memory_type = qio_opi
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.f_flash = 80000000L
board_build.flash_mode = qio
lib_deps =
${common.lib_deps}
moononournation/GFX Library for Arduino@~1.5.0
build_flags =
-D BOARD_VARIANT=\"jc3248w535\"
-D BOARD_IS_JC3248W535=1
-D ENABLE_OTA_AUTO=1
-D BOARD_HAS_PSRAM=1
-D ARDUINO_USB_CDC_ON_BOOT=1
; --- Display (AXS15231B QSPI, 320x480 portrait native) ---
; Pins are hard-coded inside Panel_AXS15231B_AGFX (src/lgfx_panel_axs15231b_agfx.hpp)
; because Arduino_GFX's databus takes pins at construction, not at begin().
-D DISPLAY_320x480=1
-D BACKLIGHT_PIN=1
; --- Touch (AXS15231B touch controller on I2C @0x3B) ---
-D USE_AXS_TOUCH=1
-D AXS_TOUCH_SDA=4
-D AXS_TOUCH_SCL=8

; =============================================================================
; ESP32-C3 Super Mini + ST7789 240x240
; =============================================================================
Expand Down
102 changes: 102 additions & 0 deletions src/button.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "button.h"
#include "settings.h"
#include "buzzer.h"

#if defined(USE_XPT2046)
#include <SPI.h>
Expand Down Expand Up @@ -27,6 +28,42 @@
value = Wire.read();
return true;
}
#elif defined(USE_AXS_TOUCH)
// AXS15231B integrated touch controller. I2C slave at 0x3B.
// Protocol (per axs15231b-lovyangfx skill): write 8-byte command, read 8
// bytes back. Touch is active when rx[0] == 0 (no gesture) AND rx[1] != 0
// (finger count > 0). Coordinates arrive pre-scaled to panel resolution,
// NOT raw 0-4095. Single-touch only.
#include <Wire.h>
#define AXS_TOUCH_ADDR 0x3B
static const uint8_t AXS_READ_TOUCHPAD_CMD[8] = {
0xB5, 0xAB, 0xA5, 0x5A, 0x00, 0x00, 0x00, 0x08
};
static bool axsTouchBusReady = false;
static bool axsTouchSeen = false;

static bool axsTouchProbe() {
Wire.beginTransmission(AXS_TOUCH_ADDR);
return Wire.endTransmission(true) == 0;
}

// Returns true if a touch is active and fills x/y with panel-scaled
// coordinates (native portrait 320x480 — caller applies rotation).
static bool axsTouchRead(uint16_t& x, uint16_t& y) {
Wire.beginTransmission(AXS_TOUCH_ADDR);
Wire.write(AXS_READ_TOUCHPAD_CMD, sizeof(AXS_READ_TOUCHPAD_CMD));
if (Wire.endTransmission() != 0) return false;
uint8_t rx[8] = {0};
size_t got = Wire.requestFrom((uint8_t)AXS_TOUCH_ADDR, (uint8_t)8);
if (got < 6) return false;
for (size_t i = 0; i < got && i < sizeof(rx); ++i) rx[i] = Wire.read();
// Valid plain-touch: gesture=0 and at least one finger down.
if (rx[0] != 0) return false;
if ((rx[1] & 0x0F) == 0) return false;
x = ((uint16_t)(rx[2] & 0x0F) << 8) | rx[3];
y = ((uint16_t)(rx[4] & 0x0F) << 8) | rx[5];
return true;
}
#elif defined(TOUCH_CS)
#include "display_ui.h" // extern tft for getTouch()
#endif
Expand All @@ -36,8 +73,50 @@ static bool stableState = false;
static unsigned long lastChangeMs = 0;
static const unsigned long DEBOUNCE_MS = 50;

void sanitizeButtonPin() {
// Only the GPIO-backed button types use buttonPin. Touchscreen talks over
// a bus defined elsewhere and has no single pin to conflict.
if (buttonType != BTN_PUSH && buttonType != BTN_TOUCH) return;
if (buttonPin == 0) return;

auto clash = [&](const char* what) {
Serial.printf("Button: pin %u conflicts with %s, disabling\n",
(unsigned)buttonPin, what);
buttonPin = 0;
};

#if defined(BACKLIGHT_PIN) && BACKLIGHT_PIN >= 0
if (buttonPin == BACKLIGHT_PIN) { clash("backlight"); return; }
#endif
#if defined(USE_AXS_TOUCH)
if (buttonPin == AXS_TOUCH_SDA) { clash("AXS touch SDA"); return; }
if (buttonPin == AXS_TOUCH_SCL) { clash("AXS touch SCL"); return; }
#endif
#if defined(USE_CST816)
if (buttonPin == CST816_SDA) { clash("CST816 touch SDA"); return; }
if (buttonPin == CST816_SCL) { clash("CST816 touch SCL"); return; }
#if defined(CST816_IRQ)
if (buttonPin == CST816_IRQ) { clash("CST816 touch IRQ"); return; }
#endif
#if defined(CST816_RST)
if (buttonPin == CST816_RST) { clash("CST816 touch RST"); return; }
#endif
#endif
#if defined(USE_XPT2046)
if (buttonPin == TOUCH_CS) { clash("XPT2046 CS"); return; }
if (buttonPin == TOUCH_IRQ) { clash("XPT2046 IRQ"); return; }
if (buttonPin == TOUCH_MOSI) { clash("XPT2046 MOSI"); return; }
if (buttonPin == TOUCH_MISO) { clash("XPT2046 MISO"); return; }
if (buttonPin == TOUCH_CLK) { clash("XPT2046 CLK"); return; }
#endif
if (buzzerSettings.pin != 0 && buttonPin == buzzerSettings.pin) {
clash("buzzer"); return;
}
}

void initButton() {
if (buttonType == BTN_DISABLED) return;
sanitizeButtonPin();
#if defined(USE_XPT2046)
if (buttonType == BTN_TOUCHSCREEN) {
touchSPI.begin(TOUCH_CLK, TOUCH_MISO, TOUCH_MOSI, TOUCH_CS);
Expand Down Expand Up @@ -75,6 +154,21 @@ void initButton() {
}
return;
}
#elif defined(USE_AXS_TOUCH)
if (buttonType == BTN_TOUCHSCREEN) {
Wire.begin(AXS_TOUCH_SDA, AXS_TOUCH_SCL);
Wire.setClock(400000);
axsTouchBusReady = true;
if (axsTouchProbe()) {
Serial.printf("AXS15231B touch initialized (I2C SDA=%d SCL=%d, addr 0x%02X)\n",
AXS_TOUCH_SDA, AXS_TOUCH_SCL, AXS_TOUCH_ADDR);
axsTouchSeen = true;
} else {
Serial.printf("AXS15231B touch did not answer at init (addr 0x%02X, SDA=%d SCL=%d); will keep retrying at runtime\n",
AXS_TOUCH_ADDR, AXS_TOUCH_SDA, AXS_TOUCH_SCL);
}
return;
}
#endif
if (buttonType == BTN_TOUCHSCREEN) return;
if (buttonPin == 0) return;
Expand Down Expand Up @@ -105,6 +199,14 @@ bool wasButtonPressed() {
cst816Seen = true;
}
raw = (touchNum > 0);
#elif defined(USE_AXS_TOUCH)
if (!axsTouchBusReady) return false;
uint16_t tx = 0, ty = 0;
raw = axsTouchRead(tx, ty);
if (raw && !axsTouchSeen) {
Serial.printf("AXS15231B touch became responsive at runtime (addr 0x%02X)\n", AXS_TOUCH_ADDR);
axsTouchSeen = true;
}
#elif defined(TOUCH_CS)
uint16_t tx, ty;
raw = tft.getTouch(&tx, &ty);
Expand Down
3 changes: 3 additions & 0 deletions src/button.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@

void initButton();
bool wasButtonPressed(); // returns true once per press (edge-detected, debounced)
void sanitizeButtonPin(); // zero buttonPin if it conflicts with a reserved
// subsystem (backlight, touch bus, buzzer). No-op
// for touchscreen type.

#endif // BUTTON_H
Loading