mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
LilyGo T5 S3 Epaper Pro No GPS version implementation stage 1 - H752-B; set backlight double click boot to 153 full brightness on, triple click to 40 brightness, double click off
This commit is contained in:
38
boards/t5s3-epaper-pro.json
Normal file
38
boards/t5s3-epaper-pro.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi",
|
||||
"partitions": "default_16MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "LilyGo T5S3 E-Paper Pro",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://lilygo.cc/products/t5-e-paper-s3-pro",
|
||||
"vendor": "LILYGO"
|
||||
}
|
||||
@@ -4,6 +4,10 @@
|
||||
#include <Mesh.h>
|
||||
#include "RadioPresets.h" // Shared radio presets (serial CLI + settings screen)
|
||||
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
#include "target.h" // for board.setBacklight() CLI command
|
||||
#endif
|
||||
|
||||
#ifdef HAS_4G_MODEM
|
||||
#include "ModemManager.h" // Serial CLI modem commands
|
||||
#endif
|
||||
@@ -2662,6 +2666,30 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
Serial.println(" Error: use 0 (default), 4800, 9600, 19200, 38400, 57600, or 115200");
|
||||
}
|
||||
|
||||
// Backlight control (T5S3 E-Paper Pro only)
|
||||
} else if (memcmp(config, "backlight ", 10) == 0) {
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
const char* val = &config[10];
|
||||
if (strcmp(val, "on") == 0) {
|
||||
board.setBacklight(true);
|
||||
Serial.println(" > backlight ON");
|
||||
} else if (strcmp(val, "off") == 0) {
|
||||
board.setBacklight(false);
|
||||
Serial.println(" > backlight OFF");
|
||||
} else {
|
||||
int brightness = atoi(val);
|
||||
if (brightness >= 0 && brightness <= 255) {
|
||||
board.setBacklightBrightness((uint8_t)brightness);
|
||||
board.setBacklight(brightness > 0);
|
||||
Serial.printf(" > backlight brightness = %d\n", brightness);
|
||||
} else {
|
||||
Serial.println(" Error: use 'on', 'off', or 0-255");
|
||||
}
|
||||
}
|
||||
#else
|
||||
Serial.println(" Error: backlight not available on this device");
|
||||
#endif
|
||||
|
||||
} else {
|
||||
Serial.printf(" Error: unknown setting '%s' (try 'help')\n", config);
|
||||
}
|
||||
@@ -2707,6 +2735,11 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
Serial.println(" erase Format filesystem");
|
||||
Serial.println(" reboot Restart device");
|
||||
Serial.println(" ls / cat / rm File operations");
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
Serial.println("");
|
||||
Serial.println(" Display:");
|
||||
Serial.println(" set backlight on/off/0-255 Control front-light");
|
||||
#endif
|
||||
|
||||
// =====================================================================
|
||||
// Existing system commands (unchanged)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#define FIRMWARE_VER_CODE 10
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "8 March 2026"
|
||||
#define FIRMWARE_BUILD_DATE "11 March 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
|
||||
@@ -49,10 +49,6 @@
|
||||
static bool webReaderNeedsRefresh = false;
|
||||
static bool webReaderTextEntry = false; // True when URL/password entry active
|
||||
#endif
|
||||
// AGC reset - periodically re-assert RX boosted gain to prevent sensitivity drift
|
||||
#define AGC_RESET_INTERVAL_MS 500
|
||||
static unsigned long lastAGCReset = 0;
|
||||
|
||||
// Emoji picker state
|
||||
#include "EmojiPicker.h"
|
||||
static bool emojiPickerMode = false;
|
||||
@@ -90,8 +86,6 @@
|
||||
TouchInput touchInput(&Wire);
|
||||
#endif
|
||||
|
||||
CPUPowerManager cpuPower;
|
||||
|
||||
void initKeyboard();
|
||||
void handleKeyboardInput();
|
||||
void drawComposeScreen();
|
||||
@@ -343,6 +337,11 @@
|
||||
}
|
||||
#endif
|
||||
|
||||
// Board-agnostic: CPU frequency scaling and AGC reset
|
||||
CPUPowerManager cpuPower;
|
||||
#define AGC_RESET_INTERVAL_MS 500
|
||||
static unsigned long lastAGCReset = 0;
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
static uint32_t _atoi(const char* sp) {
|
||||
uint32_t n = 0;
|
||||
@@ -436,7 +435,9 @@ static uint32_t _atoi(const char* sp) {
|
||||
/* GLOBAL OBJECTS */
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include "UITask.h"
|
||||
#if HAS_GPS
|
||||
#include "MapScreen.h" // After BLE — PNGdec headers conflict with BLE if included earlier
|
||||
#endif
|
||||
UITask ui_task(&board, &serial_interface);
|
||||
#endif
|
||||
|
||||
@@ -749,8 +750,8 @@ void setup() {
|
||||
initKeyboard();
|
||||
#endif
|
||||
|
||||
// Initialize touch input (CST328)
|
||||
#ifdef HAS_TOUCHSCREEN
|
||||
// Initialize touch input (CST328 on T-Deck Pro)
|
||||
#if defined(LilyGo_TDeck_Pro) && defined(HAS_TOUCHSCREEN)
|
||||
if (touchInput.begin(CST328_PIN_INT)) {
|
||||
MESH_DEBUG_PRINTLN("setup() - Touch input initialized");
|
||||
} else {
|
||||
@@ -880,6 +881,7 @@ void loop() {
|
||||
sensors.loop();
|
||||
|
||||
// Map screen: periodically update own GPS position and contact markers
|
||||
#if HAS_GPS
|
||||
if (ui_task.isOnMapScreen()) {
|
||||
static unsigned long lastMapUpdate = 0;
|
||||
if (millis() - lastMapUpdate > 30000) { // Every 30 seconds
|
||||
@@ -887,9 +889,7 @@ void loop() {
|
||||
MapScreen* ms = (MapScreen*)ui_task.getMapScreen();
|
||||
if (ms) {
|
||||
// Update own GPS position when GPS is enabled
|
||||
#if HAS_GPS
|
||||
ms->updateGPSPosition(sensors.node_lat, sensors.node_lon);
|
||||
#endif
|
||||
|
||||
// Always refresh contact markers (new contacts arrive via radio)
|
||||
ms->clearMarkers();
|
||||
@@ -905,12 +905,13 @@ void loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// CPU frequency auto-timeout back to idle
|
||||
cpuPower.loop();
|
||||
|
||||
// Audiobook: service audio decode regardless of which screen is active
|
||||
#ifndef HAS_4G_MODEM
|
||||
#if defined(LilyGo_TDeck_Pro) && !defined(HAS_4G_MODEM)
|
||||
{
|
||||
AudiobookPlayerScreen* abPlayer =
|
||||
(AudiobookPlayerScreen*)ui_task.getAudiobookScreen();
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include "NotesScreen.h"
|
||||
#include "RepeaterAdminScreen.h"
|
||||
#include "DiscoveryScreen.h"
|
||||
#if HAS_GPS
|
||||
#include "MapScreen.h"
|
||||
#endif
|
||||
#include "target.h"
|
||||
#if defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
|
||||
#include <WiFi.h>
|
||||
@@ -340,7 +342,11 @@ public:
|
||||
y += 10;
|
||||
display.drawTextCentered(display.width() / 2, y, "[N] Notes [S] Settings ");
|
||||
y += 10;
|
||||
#if HAS_GPS
|
||||
display.drawTextCentered(display.width() / 2, y, "[E] Reader [G] Maps ");
|
||||
#else
|
||||
display.drawTextCentered(display.width() / 2, y, "[E] Reader ");
|
||||
#endif
|
||||
y += 10;
|
||||
#if defined(HAS_4G_MODEM) && defined(MECK_WEB_READER)
|
||||
display.drawTextCentered(display.width() / 2, y, "[T] Phone [B] Browser ");
|
||||
@@ -359,7 +365,11 @@ public:
|
||||
|
||||
// Nav hint
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
display.drawTextCentered(display.width() / 2, y, "Tap screen to cycle home views");
|
||||
#else
|
||||
display.drawTextCentered(display.width() / 2, y, "Press A/D to cycle home views");
|
||||
#endif
|
||||
display.setTextSize(1); // restore
|
||||
} else if (_page == HomePage::RECENT) {
|
||||
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
|
||||
@@ -952,7 +962,11 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
#ifdef HAS_4G_MODEM
|
||||
sms_screen = new SMSScreen(this);
|
||||
#endif
|
||||
#if HAS_GPS
|
||||
map_screen = new MapScreen(this);
|
||||
#else
|
||||
map_screen = nullptr;
|
||||
#endif
|
||||
setCurrScreen(splash);
|
||||
}
|
||||
|
||||
@@ -1362,6 +1376,16 @@ char UITask::handleLongPress(char c) {
|
||||
|
||||
char UITask::handleDoubleClick(char c) {
|
||||
MESH_DEBUG_PRINTLN("UITask: double click triggered");
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
// Double-click boot button → full brightness backlight toggle
|
||||
if (board.isBacklightOn()) {
|
||||
board.setBacklight(false);
|
||||
} else {
|
||||
board.setBacklightBrightness(153);
|
||||
board.setBacklight(true);
|
||||
}
|
||||
c = 0; // consume event — don't pass through as navigation
|
||||
#endif
|
||||
checkDisplayOn(c);
|
||||
return c;
|
||||
}
|
||||
@@ -1369,7 +1393,17 @@ char UITask::handleDoubleClick(char c) {
|
||||
char UITask::handleTripleClick(char c) {
|
||||
MESH_DEBUG_PRINTLN("UITask: triple click triggered");
|
||||
checkDisplayOn(c);
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
// Triple-click → half brightness backlight (comfortable reading)
|
||||
if (board.isBacklightOn()) {
|
||||
board.setBacklight(false); // If already on, turn off
|
||||
} else {
|
||||
board.setBacklightBrightness(80);
|
||||
board.setBacklight(true);
|
||||
}
|
||||
#else
|
||||
toggleBuzzer();
|
||||
#endif
|
||||
c = 0;
|
||||
return c;
|
||||
}
|
||||
@@ -1643,6 +1677,7 @@ void UITask::gotoWebReader() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_GPS
|
||||
void UITask::gotoMapScreen() {
|
||||
MapScreen* map = (MapScreen*)map_screen;
|
||||
if (_display != NULL) {
|
||||
@@ -1655,6 +1690,7 @@ void UITask::gotoMapScreen() {
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
_next_refresh = 100;
|
||||
}
|
||||
#endif
|
||||
|
||||
void UITask::onAdminLoginResult(bool success, uint8_t permissions, uint32_t server_time) {
|
||||
if (repeater_admin && isOnRepeaterAdmin()) {
|
||||
|
||||
@@ -121,7 +121,9 @@ public:
|
||||
void gotoAudiobookPlayer(); // Navigate to audiobook player
|
||||
void gotoRepeaterAdmin(int contactIdx); // Navigate to repeater admin
|
||||
void gotoDiscoveryScreen(); // Navigate to node discovery scan
|
||||
#if HAS_GPS
|
||||
void gotoMapScreen(); // Navigate to map tile screen
|
||||
#endif
|
||||
#ifdef MECK_WEB_READER
|
||||
void gotoWebReader(); // Navigate to web reader (browser)
|
||||
#endif
|
||||
|
||||
286
src/helpers/ui/FastEPDDisplay.cpp
Normal file
286
src/helpers/ui/FastEPDDisplay.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#include "FastEPDDisplay.h"
|
||||
#include "FastEPD.h"
|
||||
#include <string.h>
|
||||
|
||||
// Fallback if FastEPD doesn't define these constants
|
||||
#ifndef BBEP_SUCCESS
|
||||
#define BBEP_SUCCESS 0
|
||||
#endif
|
||||
#ifndef CLEAR_FAST
|
||||
#define CLEAR_FAST 0
|
||||
#endif
|
||||
#ifndef CLEAR_SLOW
|
||||
#define CLEAR_SLOW 1
|
||||
#endif
|
||||
#ifndef BB_MODE_1BPP
|
||||
#define BB_MODE_1BPP 0
|
||||
#endif
|
||||
|
||||
// FastEPD constants (defined in FastEPD.h)
|
||||
// BB_PANEL_LILYGO_T5PRO_V2 — board ID for V2 hardware
|
||||
// BB_MODE_1BPP — 1-bit per pixel mode
|
||||
// CLEAR_FAST, CLEAR_SLOW — full refresh modes
|
||||
|
||||
// Periodic slow (deep) refresh to clear ghosting
|
||||
#define FULL_SLOW_PERIOD 20 // every 20 fast-refreshes, do a slow refresh
|
||||
|
||||
FastEPDDisplay::~FastEPDDisplay() {
|
||||
delete _canvas;
|
||||
delete _epd;
|
||||
}
|
||||
|
||||
bool FastEPDDisplay::begin() {
|
||||
if (_init) return true;
|
||||
|
||||
Serial.println("[FastEPD] Initializing T5S3 E-Paper Pro V2...");
|
||||
|
||||
// Create FastEPD instance and init hardware
|
||||
_epd = new FASTEPD;
|
||||
// Meshtastic-proven init for V2 hardware (pinned FastEPD fork commit)
|
||||
Serial.println("[FastEPD] Using BB_PANEL_LILYGO_T5PRO_V2");
|
||||
int rc = _epd->initPanel(BB_PANEL_LILYGO_T5PRO_V2, 28000000);
|
||||
if (rc != BBEP_SUCCESS) {
|
||||
Serial.printf("[FastEPD] initPanel FAILED: %d\n", rc);
|
||||
delete _epd;
|
||||
_epd = nullptr;
|
||||
return false;
|
||||
}
|
||||
Serial.printf("[FastEPD] Panel initialized (rc=%d)\n", rc);
|
||||
|
||||
// Enable display via PCA9535 GPIO (required for V2 hardware)
|
||||
// Pin 0 on PCA9535 = EP_OE (output enable for source driver)
|
||||
_epd->ioPinMode(0, OUTPUT);
|
||||
_epd->ioWrite(0, HIGH);
|
||||
Serial.println("[FastEPD] PCA9535 EP_OE set HIGH");
|
||||
|
||||
// Set 1-bit per pixel mode
|
||||
_epd->setMode(BB_MODE_1BPP);
|
||||
Serial.println("[FastEPD] Mode set to 1BPP");
|
||||
|
||||
// Create Adafruit_GFX canvas for drawing (960×540, 1-bit)
|
||||
// ~64KB, should auto-allocate in PSRAM on ESP32-S3 with PSRAM enabled
|
||||
_canvas = new GFXcanvas1(EPD_WIDTH, EPD_HEIGHT);
|
||||
if (!_canvas || !_canvas->getBuffer()) {
|
||||
Serial.println("[FastEPD] Canvas allocation FAILED!");
|
||||
return false;
|
||||
}
|
||||
Serial.printf("[FastEPD] Canvas allocated: %dx%d (%d bytes)\n",
|
||||
EPD_WIDTH, EPD_HEIGHT, (EPD_WIDTH * EPD_HEIGHT) / 8);
|
||||
|
||||
// Initial clear — white screen
|
||||
Serial.println("[FastEPD] Calling clearWhite()...");
|
||||
_epd->clearWhite();
|
||||
Serial.println("[FastEPD] Calling fullUpdate(true) for initial clear...");
|
||||
_epd->fullUpdate(true); // blocking initial clear
|
||||
_epd->backupPlane(); // Save clean state for subsequent diffs
|
||||
Serial.println("[FastEPD] Initial clear complete");
|
||||
|
||||
// Set canvas defaults
|
||||
_canvas->fillScreen(1); // White background (bit=1 → white in FastEPD)
|
||||
_canvas->setTextColor(0); // Black text (bit=0 → black in FastEPD)
|
||||
_canvas->setFont(&FreeSans24pt7b);
|
||||
_canvas->setTextWrap(false);
|
||||
|
||||
_curr_color = GxEPD_BLACK;
|
||||
_init = true;
|
||||
_isOn = true;
|
||||
|
||||
Serial.println("[FastEPD] Display ready (960x540, 1BPP)");
|
||||
return true;
|
||||
}
|
||||
|
||||
void FastEPDDisplay::turnOn() {
|
||||
if (!_init) begin();
|
||||
_isOn = true;
|
||||
}
|
||||
|
||||
void FastEPDDisplay::turnOff() {
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
void FastEPDDisplay::clear() {
|
||||
if (!_canvas) return;
|
||||
_canvas->fillScreen(1); // White
|
||||
_canvas->setTextColor(0);
|
||||
_frameCRC.reset();
|
||||
}
|
||||
|
||||
void FastEPDDisplay::startFrame(Color bkg) {
|
||||
if (!_canvas) return;
|
||||
_canvas->fillScreen(1); // White background
|
||||
_canvas->setTextColor(0); // Black text
|
||||
_curr_color = GxEPD_BLACK;
|
||||
_frameCRC.reset();
|
||||
}
|
||||
|
||||
void FastEPDDisplay::setTextSize(int sz) {
|
||||
if (!_canvas) return;
|
||||
_frameCRC.update<int>(sz);
|
||||
|
||||
// Font mapping for 960×540 display at ~234 DPI
|
||||
// The T-Deck Pro at 240×320 (~140 DPI) uses FreeSans9pt for body text.
|
||||
// At 234 DPI we need roughly 2.5× larger fonts for equivalent physical size.
|
||||
// Built-in 5×7 font scaled 4× = 20×28px — readable for status bar items.
|
||||
switch(sz) {
|
||||
case 0: // Tiny — node name, clock, battery %, menu shortcuts
|
||||
_canvas->setFont(NULL);
|
||||
_canvas->setTextSize(4); // 5×7 × 4 = 20×28 physical pixels
|
||||
break;
|
||||
case 1: // Small/normal — body text, contact list items
|
||||
_canvas->setFont(&FreeSans24pt7b);
|
||||
_canvas->setTextSize(1);
|
||||
break;
|
||||
case 2: // Medium bold — MSG count, headings, labels
|
||||
_canvas->setFont(&FreeSansBold24pt7b);
|
||||
_canvas->setTextSize(1);
|
||||
break;
|
||||
case 3: // Large — splash screen title, onboarding
|
||||
_canvas->setFont(&FreeSansBold24pt7b);
|
||||
_canvas->setTextSize(1);
|
||||
break;
|
||||
default:
|
||||
_canvas->setFont(&FreeSans24pt7b);
|
||||
_canvas->setTextSize(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FastEPDDisplay::setColor(Color c) {
|
||||
if (!_canvas) return;
|
||||
_frameCRC.update<Color>(c);
|
||||
|
||||
// Colours are inverted for e-paper:
|
||||
// DARK = background colour = WHITE on e-paper
|
||||
// LIGHT = foreground colour = BLACK on e-paper
|
||||
if (c == DARK) {
|
||||
_canvas->setTextColor(1); // White (background)
|
||||
_curr_color = GxEPD_WHITE;
|
||||
} else {
|
||||
_canvas->setTextColor(0); // Black (foreground)
|
||||
_curr_color = GxEPD_BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
void FastEPDDisplay::setCursor(int x, int y) {
|
||||
if (!_canvas) return;
|
||||
_frameCRC.update<int>(x);
|
||||
_frameCRC.update<int>(y);
|
||||
|
||||
// Scale virtual coordinates to physical, with baseline offset
|
||||
// The +5 pushes text baseline down so ascenders don't overlap elements above
|
||||
// (Same convention as GxEPDDisplay for T-Deck Pro)
|
||||
_canvas->setCursor(
|
||||
(int)((x + offset_x) * scale_x),
|
||||
(int)((y + offset_y + 5) * scale_y)
|
||||
);
|
||||
}
|
||||
|
||||
void FastEPDDisplay::print(const char* str) {
|
||||
if (!_canvas || !str) return;
|
||||
_frameCRC.update<char>(str, strlen(str));
|
||||
_canvas->print(str);
|
||||
}
|
||||
|
||||
void FastEPDDisplay::fillRect(int x, int y, int w, int h) {
|
||||
if (!_canvas) return;
|
||||
_frameCRC.update<int>(x);
|
||||
_frameCRC.update<int>(y);
|
||||
_frameCRC.update<int>(w);
|
||||
_frameCRC.update<int>(h);
|
||||
|
||||
// Canvas uses 1-bit color: convert GxEPD color
|
||||
uint16_t canvasColor = (_curr_color == GxEPD_BLACK) ? 0 : 1;
|
||||
_canvas->fillRect(
|
||||
(int)((x + offset_x) * scale_x),
|
||||
(int)((y + offset_y) * scale_y),
|
||||
(int)(w * scale_x),
|
||||
(int)(h * scale_y),
|
||||
canvasColor
|
||||
);
|
||||
}
|
||||
|
||||
void FastEPDDisplay::drawRect(int x, int y, int w, int h) {
|
||||
if (!_canvas) return;
|
||||
_frameCRC.update<int>(x);
|
||||
_frameCRC.update<int>(y);
|
||||
_frameCRC.update<int>(w);
|
||||
_frameCRC.update<int>(h);
|
||||
|
||||
uint16_t canvasColor = (_curr_color == GxEPD_BLACK) ? 0 : 1;
|
||||
_canvas->drawRect(
|
||||
(int)((x + offset_x) * scale_x),
|
||||
(int)((y + offset_y) * scale_y),
|
||||
(int)(w * scale_x),
|
||||
(int)(h * scale_y),
|
||||
canvasColor
|
||||
);
|
||||
}
|
||||
|
||||
void FastEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
if (!_canvas || !bits) return;
|
||||
_frameCRC.update<int>(x);
|
||||
_frameCRC.update<int>(y);
|
||||
_frameCRC.update<int>(w);
|
||||
_frameCRC.update<int>(h);
|
||||
_frameCRC.update<uint8_t>(bits, (w * h + 7) / 8);
|
||||
|
||||
uint16_t canvasColor = (_curr_color == GxEPD_BLACK) ? 0 : 1;
|
||||
uint16_t startX = (int)((x + offset_x) * scale_x);
|
||||
uint16_t startY = (int)((y + offset_y) * scale_y);
|
||||
uint16_t widthInBytes = (w + 7) / 8;
|
||||
|
||||
for (uint16_t by = 0; by < h; by++) {
|
||||
int y1 = startY + (int)(by * scale_y);
|
||||
int y2 = startY + (int)((by + 1) * scale_y);
|
||||
int block_h = y2 - y1;
|
||||
|
||||
for (uint16_t bx = 0; bx < w; bx++) {
|
||||
int x1 = startX + (int)(bx * scale_x);
|
||||
int x2 = startX + (int)((bx + 1) * scale_x);
|
||||
int block_w = x2 - x1;
|
||||
|
||||
uint16_t byteOffset = (by * widthInBytes) + (bx / 8);
|
||||
uint8_t bitMask = 0x80 >> (bx & 7);
|
||||
bool bitSet = pgm_read_byte(bits + byteOffset) & bitMask;
|
||||
|
||||
if (bitSet) {
|
||||
_canvas->fillRect(x1, y1, block_w, block_h, canvasColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t FastEPDDisplay::getTextWidth(const char* str) {
|
||||
if (!_canvas || !str) return 0;
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
_canvas->getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
|
||||
return (uint16_t)ceil((w + 1) / scale_x);
|
||||
}
|
||||
|
||||
void FastEPDDisplay::endFrame() {
|
||||
if (!_epd || !_canvas) return;
|
||||
|
||||
uint32_t crc = _frameCRC.finalize();
|
||||
if (crc == _lastCRC) {
|
||||
return; // Frame unchanged, skip display update
|
||||
}
|
||||
_lastCRC = crc;
|
||||
|
||||
// Copy GFXcanvas1 buffer to FastEPD's current buffer — direct copy.
|
||||
// Both use same polarity: bit 1 = white, bit 0 = black.
|
||||
// (Meshtastic inverts because OLEDDisplay uses opposite convention — not us.)
|
||||
uint8_t* src = _canvas->getBuffer();
|
||||
uint8_t* dst = _epd->currentBuffer();
|
||||
size_t bufSize = ((uint32_t)EPD_WIDTH * EPD_HEIGHT) / 8;
|
||||
|
||||
if (!src || !dst) return;
|
||||
|
||||
memcpy(dst, src, bufSize);
|
||||
|
||||
// fullUpdate(true) is the only refresh mode that gives clean transitions
|
||||
// on the ED047TC1 panel. CLEAR_FAST causes ghosting/mashing on this hardware.
|
||||
// The brief white flash between frames is inherent to this panel's waveform.
|
||||
_epd->fullUpdate(true);
|
||||
_epd->backupPlane();
|
||||
}
|
||||
120
src/helpers/ui/FastEPDDisplay.h
Normal file
120
src/helpers/ui/FastEPDDisplay.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
// =============================================================================
|
||||
// FastEPDDisplay — Parallel e-ink display driver for T5 S3 E-Paper Pro
|
||||
//
|
||||
// Architecture:
|
||||
// - FastEPD handles hardware init, power management, and display refresh
|
||||
// - Adafruit_GFX GFXcanvas1 handles all drawing/text rendering
|
||||
// - On endFrame(), canvas buffer is copied to FastEPD and display is updated
|
||||
//
|
||||
// This avoids depending on FastEPD's drawing API — only uses its well-tested
|
||||
// hardware interface (initPanel, fullUpdate, partialUpdate, currentBuffer).
|
||||
// =============================================================================
|
||||
|
||||
#include <Adafruit_GFX.h>
|
||||
#include "variant.h" // EPD_WIDTH, EPD_HEIGHT (only compiled for T5S3 builds)
|
||||
#include <Fonts/FreeSans9pt7b.h>
|
||||
#include <Fonts/FreeSans12pt7b.h>
|
||||
#include <Fonts/FreeSans18pt7b.h>
|
||||
#include <Fonts/FreeSans24pt7b.h>
|
||||
#include <Fonts/FreeSansBold12pt7b.h>
|
||||
#include <Fonts/FreeSansBold18pt7b.h>
|
||||
#include <Fonts/FreeSansBold24pt7b.h>
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
|
||||
// GxEPD2 color constant compatibility — MapScreen uses these directly
|
||||
#ifndef GxEPD_BLACK
|
||||
#define GxEPD_BLACK 0x0000
|
||||
#endif
|
||||
#ifndef GxEPD_WHITE
|
||||
#define GxEPD_WHITE 0xFFFF
|
||||
#endif
|
||||
|
||||
// Forward declare FastEPD class (actual include in .cpp)
|
||||
class FASTEPD;
|
||||
|
||||
// Inline CRC32 for frame change detection
|
||||
// (Copied from GxEPDDisplay.h — avoids CRC32/PNGdec name collision)
|
||||
class FrameCRC32 {
|
||||
uint32_t _crc = 0xFFFFFFFF;
|
||||
public:
|
||||
void reset() { _crc = 0xFFFFFFFF; }
|
||||
template<typename T> void update(T val) {
|
||||
const uint8_t* p = (const uint8_t*)&val;
|
||||
for (size_t i = 0; i < sizeof(T); i++) {
|
||||
_crc ^= p[i];
|
||||
for (int b = 0; b < 8; b++)
|
||||
_crc = (_crc >> 1) ^ (0xEDB88320 & -(int32_t)(_crc & 1));
|
||||
}
|
||||
}
|
||||
template<typename T> void update(const T* data, size_t len) {
|
||||
const uint8_t* p = (const uint8_t*)data;
|
||||
for (size_t i = 0; i < len * sizeof(T); i++) {
|
||||
_crc ^= p[i];
|
||||
for (int b = 0; b < 8; b++)
|
||||
_crc = (_crc >> 1) ^ (0xEDB88320 & -(int32_t)(_crc & 1));
|
||||
}
|
||||
}
|
||||
uint32_t finalize() { return _crc ^ 0xFFFFFFFF; }
|
||||
};
|
||||
|
||||
|
||||
class FastEPDDisplay : public DisplayDriver {
|
||||
FASTEPD* _epd;
|
||||
GFXcanvas1* _canvas; // Adafruit_GFX 1-bit drawing surface (960×540)
|
||||
bool _init = false;
|
||||
bool _isOn = false;
|
||||
uint16_t _curr_color; // GxEPD_BLACK or GxEPD_WHITE for canvas drawing
|
||||
FrameCRC32 _frameCRC;
|
||||
uint32_t _lastCRC = 0;
|
||||
int _fullRefreshCount = 0; // Track for periodic slow refresh
|
||||
uint32_t _lastUpdateMs = 0; // Rate limiting — minimum interval between refreshes
|
||||
|
||||
// Virtual 128×128 → physical 960×540 mapping
|
||||
// Non-square scaling (1.78:1 aspect stretch) — acceptable for initial bringup
|
||||
static constexpr float scale_x = 7.5f; // 960 / 128
|
||||
static constexpr float scale_y = 4.21875f; // 540 / 128
|
||||
static constexpr float offset_x = 0.0f;
|
||||
static constexpr float offset_y = 0.0f;
|
||||
|
||||
public:
|
||||
FastEPDDisplay() : DisplayDriver(128, 128), _epd(nullptr), _canvas(nullptr) {}
|
||||
~FastEPDDisplay();
|
||||
|
||||
bool begin();
|
||||
|
||||
bool isOn() override { return _isOn; }
|
||||
void turnOn() override;
|
||||
void turnOff() override;
|
||||
void clear() override;
|
||||
void startFrame(Color bkg = DARK) override;
|
||||
void setTextSize(int sz) override;
|
||||
void setColor(Color c) override;
|
||||
void setCursor(int x, int y) override;
|
||||
void print(const char* str) override;
|
||||
void fillRect(int x, int y, int w, int h) override;
|
||||
void drawRect(int x, int y, int w, int h) override;
|
||||
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
|
||||
uint16_t getTextWidth(const char* str) override;
|
||||
void endFrame() override;
|
||||
|
||||
// --- Raw pixel access for MapScreen (bypasses scaling) ---
|
||||
void drawPixelRaw(int16_t x, int16_t y, uint16_t color) {
|
||||
if (_canvas) _canvas->drawPixel(x, y, color ? 1 : 0);
|
||||
}
|
||||
int16_t rawWidth() { return EPD_WIDTH; }
|
||||
int16_t rawHeight() { return EPD_HEIGHT; }
|
||||
|
||||
void drawTextRaw(int16_t x, int16_t y, const char* text, uint16_t color) {
|
||||
if (!_canvas) return;
|
||||
_canvas->setFont(NULL);
|
||||
_canvas->setTextSize(4); // 4× built-in 5×7 = 20×28, readable on 960×540
|
||||
_canvas->setTextColor(color ? 1 : 0);
|
||||
_canvas->setCursor(x, y);
|
||||
_canvas->print(text);
|
||||
}
|
||||
|
||||
void invalidateFrameCRC() { _lastCRC = 0; }
|
||||
};
|
||||
@@ -1,5 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
// T5S3 E-Paper Pro uses parallel e-ink (FastEPD), not SPI (GxEPD2)
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
#include "FastEPDDisplay.h"
|
||||
using GxEPDDisplay = FastEPDDisplay;
|
||||
#else
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
|
||||
@@ -105,3 +111,5 @@ public:
|
||||
// (needed because drawPixelRaw bypasses CRC tracking)
|
||||
void invalidateFrameCRC() { last_display_crc_value = 0; }
|
||||
};
|
||||
|
||||
#endif // !LilyGo_T5S3_EPaper_Pro
|
||||
70
variants/lilygo_t5s3_epaper_pro/CPUPowerManager.h
Normal file
70
variants/lilygo_t5s3_epaper_pro/CPUPowerManager.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// CPU Frequency Scaling for ESP32-S3
|
||||
//
|
||||
// Typical current draw (CPU only, rough):
|
||||
// 240 MHz ~70-80 mA
|
||||
// 160 MHz ~50-60 mA
|
||||
// 80 MHz ~30-40 mA
|
||||
//
|
||||
// SPI peripherals and UART use their own clock dividers from the APB clock,
|
||||
// so LoRa, e-ink, and GPS serial all work fine at 80MHz.
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#ifndef CPU_FREQ_IDLE
|
||||
#define CPU_FREQ_IDLE 80 // MHz — normal mesh listening
|
||||
#endif
|
||||
|
||||
#ifndef CPU_FREQ_BOOST
|
||||
#define CPU_FREQ_BOOST 240 // MHz — heavy processing
|
||||
#endif
|
||||
|
||||
#ifndef CPU_BOOST_TIMEOUT_MS
|
||||
#define CPU_BOOST_TIMEOUT_MS 10000 // 10 seconds
|
||||
#endif
|
||||
|
||||
class CPUPowerManager {
|
||||
public:
|
||||
CPUPowerManager() : _boosted(false), _boost_started(0) {}
|
||||
|
||||
void begin() {
|
||||
setCpuFrequencyMhz(CPU_FREQ_IDLE);
|
||||
_boosted = false;
|
||||
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (_boosted && (millis() - _boost_started >= CPU_BOOST_TIMEOUT_MS)) {
|
||||
setIdle();
|
||||
}
|
||||
}
|
||||
|
||||
void setBoost() {
|
||||
if (!_boosted) {
|
||||
setCpuFrequencyMhz(CPU_FREQ_BOOST);
|
||||
_boosted = true;
|
||||
MESH_DEBUG_PRINTLN("CPU power: boosted to %d MHz", CPU_FREQ_BOOST);
|
||||
}
|
||||
_boost_started = millis();
|
||||
}
|
||||
|
||||
void setIdle() {
|
||||
if (_boosted) {
|
||||
setCpuFrequencyMhz(CPU_FREQ_IDLE);
|
||||
_boosted = false;
|
||||
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
bool isBoosted() const { return _boosted; }
|
||||
uint32_t getFrequencyMHz() const { return getCpuFrequencyMhz(); }
|
||||
|
||||
private:
|
||||
bool _boosted;
|
||||
unsigned long _boost_started;
|
||||
};
|
||||
|
||||
#endif // ESP32
|
||||
305
variants/lilygo_t5s3_epaper_pro/T5S3Board.cpp
Normal file
305
variants/lilygo_t5s3_epaper_pro/T5S3Board.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#include <Arduino.h>
|
||||
#include "variant.h"
|
||||
#include "T5S3Board.h"
|
||||
#include <Mesh.h> // For MESH_DEBUG_PRINTLN
|
||||
|
||||
void T5S3Board::begin() {
|
||||
MESH_DEBUG_PRINTLN("T5S3Board::begin() - starting");
|
||||
|
||||
// Initialize I2C with T5S3 V2 pins
|
||||
// Note: No explicit peripheral power enable needed on T5S3
|
||||
// (unlike T-Deck Pro's PIN_PERF_POWERON)
|
||||
Wire.begin(I2C_SDA, I2C_SCL);
|
||||
Wire.setClock(100000); // 100kHz for reliable fuel gauge communication
|
||||
MESH_DEBUG_PRINTLN("T5S3Board::begin() - I2C initialized (SDA=%d, SCL=%d)", I2C_SDA, I2C_SCL);
|
||||
|
||||
// Call parent class begin (handles CPU freq, etc.)
|
||||
// Note: ESP32Board::begin() also calls Wire.begin() but with our
|
||||
// PIN_BOARD_SDA/SCL defines it will use the same pins — harmless.
|
||||
ESP32Board::begin();
|
||||
|
||||
// Configure backlight (off by default — save power)
|
||||
#ifdef BOARD_BL_EN
|
||||
pinMode(BOARD_BL_EN, OUTPUT);
|
||||
digitalWrite(BOARD_BL_EN, LOW);
|
||||
MESH_DEBUG_PRINTLN("T5S3Board::begin() - backlight pin configured (GPIO%d)", BOARD_BL_EN);
|
||||
#endif
|
||||
|
||||
// Configure user button
|
||||
pinMode(PIN_USER_BTN, INPUT);
|
||||
|
||||
// Configure LoRa SPI MISO pullup
|
||||
pinMode(P_LORA_MISO, INPUT_PULLUP);
|
||||
|
||||
// Handle wake from deep sleep
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
uint64_t wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||
if (wakeup_source & (1ULL << P_LORA_DIO_1)) {
|
||||
startup_reason = BD_STARTUP_RX_PACKET;
|
||||
}
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||
}
|
||||
|
||||
// Test BQ27220 communication and configure design capacity
|
||||
#if HAS_BQ27220
|
||||
uint16_t voltage = getBattMilliVolts();
|
||||
MESH_DEBUG_PRINTLN("T5S3Board::begin() - Battery voltage: %d mV", voltage);
|
||||
configureFuelGauge();
|
||||
#endif
|
||||
|
||||
// Early low-voltage protection
|
||||
#if HAS_BQ27220 && defined(AUTO_SHUTDOWN_MILLIVOLTS)
|
||||
{
|
||||
uint16_t bootMv = getBattMilliVolts();
|
||||
if (bootMv > 0 && bootMv < AUTO_SHUTDOWN_MILLIVOLTS) {
|
||||
Serial.printf("CRITICAL: Boot voltage %dmV < %dmV — sleeping immediately\n",
|
||||
bootMv, AUTO_SHUTDOWN_MILLIVOLTS);
|
||||
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
|
||||
esp_sleep_enable_ext1_wakeup(1ULL << PIN_USER_BTN, ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
MESH_DEBUG_PRINTLN("T5S3Board::begin() - complete");
|
||||
}
|
||||
|
||||
// ---- BQ27220 register helpers (static, file-local) ----
|
||||
|
||||
#if HAS_BQ27220
|
||||
static uint16_t bq27220_read16(uint8_t reg) {
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(reg);
|
||||
if (Wire.endTransmission(false) != 0) return 0;
|
||||
if (Wire.requestFrom((uint8_t)BQ27220_I2C_ADDR, (uint8_t)2) != 2) return 0;
|
||||
uint16_t val = Wire.read();
|
||||
val |= (Wire.read() << 8);
|
||||
return val;
|
||||
}
|
||||
|
||||
static uint8_t bq27220_read8(uint8_t reg) {
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(reg);
|
||||
if (Wire.endTransmission(false) != 0) return 0;
|
||||
if (Wire.requestFrom((uint8_t)BQ27220_I2C_ADDR, (uint8_t)1) != 1) return 0;
|
||||
return Wire.read();
|
||||
}
|
||||
|
||||
static bool bq27220_writeControl(uint16_t subcmd) {
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(0x00);
|
||||
Wire.write(subcmd & 0xFF);
|
||||
Wire.write((subcmd >> 8) & 0xFF);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---- BQ27220 public interface ----
|
||||
|
||||
uint16_t T5S3Board::getBattMilliVolts() {
|
||||
#if HAS_BQ27220
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(BQ27220_REG_VOLTAGE);
|
||||
if (Wire.endTransmission(false) != 0) return 0;
|
||||
uint8_t count = Wire.requestFrom((uint8_t)BQ27220_I2C_ADDR, (uint8_t)2);
|
||||
if (count != 2) return 0;
|
||||
uint16_t voltage = Wire.read();
|
||||
voltage |= (Wire.read() << 8);
|
||||
return voltage;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t T5S3Board::getBatteryPercent() {
|
||||
#if HAS_BQ27220
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(BQ27220_REG_SOC);
|
||||
if (Wire.endTransmission(false) != 0) return 0;
|
||||
uint8_t count = Wire.requestFrom((uint8_t)BQ27220_I2C_ADDR, (uint8_t)2);
|
||||
if (count != 2) return 0;
|
||||
uint16_t soc = Wire.read();
|
||||
soc |= (Wire.read() << 8);
|
||||
return (uint8_t)min(soc, (uint16_t)100);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int16_t T5S3Board::getAvgCurrent() {
|
||||
#if HAS_BQ27220
|
||||
return (int16_t)bq27220_read16(BQ27220_REG_AVG_CURRENT);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int16_t T5S3Board::getAvgPower() {
|
||||
#if HAS_BQ27220
|
||||
return (int16_t)bq27220_read16(BQ27220_REG_AVG_POWER);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t T5S3Board::getTimeToEmpty() {
|
||||
#if HAS_BQ27220
|
||||
return bq27220_read16(BQ27220_REG_TIME_TO_EMPTY);
|
||||
#else
|
||||
return 0xFFFF;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t T5S3Board::getRemainingCapacity() {
|
||||
#if HAS_BQ27220
|
||||
return bq27220_read16(BQ27220_REG_REMAIN_CAP);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t T5S3Board::getFullChargeCapacity() {
|
||||
#if HAS_BQ27220
|
||||
uint16_t fcc = bq27220_read16(BQ27220_REG_FULL_CAP);
|
||||
if (fcc > BQ27220_DESIGN_CAPACITY_MAH) fcc = BQ27220_DESIGN_CAPACITY_MAH;
|
||||
return fcc;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t T5S3Board::getDesignCapacity() {
|
||||
#if HAS_BQ27220
|
||||
return bq27220_read16(BQ27220_REG_DESIGN_CAP);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int16_t T5S3Board::getBattTemperature() {
|
||||
#if HAS_BQ27220
|
||||
uint16_t raw = bq27220_read16(BQ27220_REG_TEMPERATURE);
|
||||
return (int16_t)(raw - 2731); // 0.1°K to 0.1°C
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---- BQ27220 Design Capacity configuration ----
|
||||
// Identical procedure to TDeckBoard — sets 1500 mAh for T5S3's larger cell.
|
||||
// The BQ27220 ships with 3000 mAh default. This writes once on first boot
|
||||
// and persists in battery-backed RAM.
|
||||
|
||||
bool T5S3Board::configureFuelGauge(uint16_t designCapacity_mAh) {
|
||||
#if HAS_BQ27220
|
||||
uint16_t currentDC = bq27220_read16(BQ27220_REG_DESIGN_CAP);
|
||||
Serial.printf("BQ27220: Design Capacity = %d mAh (target %d)\n", currentDC, designCapacity_mAh);
|
||||
|
||||
if (currentDC == designCapacity_mAh) {
|
||||
uint16_t fcc = bq27220_read16(BQ27220_REG_FULL_CAP);
|
||||
Serial.printf("BQ27220: Design Capacity correct, FCC=%d mAh\n", fcc);
|
||||
if (fcc < designCapacity_mAh * 3 / 2) {
|
||||
return true; // FCC is sane, nothing to do
|
||||
}
|
||||
// FCC is stale from factory — fall through to reconfigure
|
||||
Serial.printf("BQ27220: FCC %d >> DC %d, reconfiguring\n", fcc, designCapacity_mAh);
|
||||
}
|
||||
|
||||
// Unseal
|
||||
bq27220_writeControl(0x0414); delay(2);
|
||||
bq27220_writeControl(0x3672); delay(2);
|
||||
// Full Access
|
||||
bq27220_writeControl(0xFFFF); delay(2);
|
||||
bq27220_writeControl(0xFFFF); delay(2);
|
||||
|
||||
// Enter CFG_UPDATE
|
||||
bq27220_writeControl(0x0090);
|
||||
bool cfgReady = false;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
delay(20);
|
||||
uint16_t opStatus = bq27220_read16(BQ27220_REG_OP_STATUS);
|
||||
if (opStatus & 0x0400) { cfgReady = true; break; }
|
||||
}
|
||||
if (!cfgReady) {
|
||||
Serial.println("BQ27220: Timeout waiting for CFGUPDATE");
|
||||
bq27220_writeControl(0x0092);
|
||||
bq27220_writeControl(0x0030);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write Design Capacity at 0x929F
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(0x3E); Wire.write(0x9F); Wire.write(0x92);
|
||||
Wire.endTransmission();
|
||||
delay(10);
|
||||
|
||||
uint8_t oldMSB = bq27220_read8(0x40);
|
||||
uint8_t oldLSB = bq27220_read8(0x41);
|
||||
uint8_t oldChk = bq27220_read8(0x60);
|
||||
uint8_t dataLen = bq27220_read8(0x61);
|
||||
|
||||
uint8_t newMSB = (designCapacity_mAh >> 8) & 0xFF;
|
||||
uint8_t newLSB = designCapacity_mAh & 0xFF;
|
||||
uint8_t temp = (255 - oldChk - oldMSB - oldLSB);
|
||||
uint8_t newChk = 255 - ((temp + newMSB + newLSB) & 0xFF);
|
||||
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(0x3E); Wire.write(0x9F); Wire.write(0x92);
|
||||
Wire.write(newMSB); Wire.write(newLSB);
|
||||
Wire.endTransmission();
|
||||
delay(5);
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(0x60); Wire.write(newChk); Wire.write(dataLen);
|
||||
Wire.endTransmission();
|
||||
delay(10);
|
||||
|
||||
// Write Design Energy at 0x92A1
|
||||
{
|
||||
uint16_t designEnergy = (uint16_t)((uint32_t)designCapacity_mAh * 37 / 10);
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(0x3E); Wire.write(0xA1); Wire.write(0x92);
|
||||
Wire.endTransmission();
|
||||
delay(10);
|
||||
uint8_t deOldMSB = bq27220_read8(0x40);
|
||||
uint8_t deOldLSB = bq27220_read8(0x41);
|
||||
uint8_t deOldChk = bq27220_read8(0x60);
|
||||
uint8_t deLen = bq27220_read8(0x61);
|
||||
uint8_t deNewMSB = (designEnergy >> 8) & 0xFF;
|
||||
uint8_t deNewLSB = designEnergy & 0xFF;
|
||||
uint8_t deTemp = (255 - deOldChk - deOldMSB - deOldLSB);
|
||||
uint8_t deNewChk = 255 - ((deTemp + deNewMSB + deNewLSB) & 0xFF);
|
||||
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(0x3E); Wire.write(0xA1); Wire.write(0x92);
|
||||
Wire.write(deNewMSB); Wire.write(deNewLSB);
|
||||
Wire.endTransmission();
|
||||
delay(5);
|
||||
Wire.beginTransmission(BQ27220_I2C_ADDR);
|
||||
Wire.write(0x60); Wire.write(deNewChk); Wire.write(deLen);
|
||||
Wire.endTransmission();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// Exit CFG_UPDATE with reinit
|
||||
bq27220_writeControl(0x0091);
|
||||
delay(200);
|
||||
|
||||
// Seal
|
||||
bq27220_writeControl(0x0030);
|
||||
delay(5);
|
||||
|
||||
// Force RESET to reinitialize FCC
|
||||
bq27220_writeControl(0x0041);
|
||||
delay(1000);
|
||||
|
||||
uint16_t verifyDC = bq27220_read16(BQ27220_REG_DESIGN_CAP);
|
||||
uint16_t newFCC = bq27220_read16(BQ27220_REG_FULL_CAP);
|
||||
Serial.printf("BQ27220: Post-config DC=%d FCC=%d mAh\n", verifyDC, newFCC);
|
||||
|
||||
return verifyDC == designCapacity_mAh;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
97
variants/lilygo_t5s3_epaper_pro/T5S3Board.h
Normal file
97
variants/lilygo_t5s3_epaper_pro/T5S3Board.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include "variant.h"
|
||||
#include <Wire.h>
|
||||
#include <Arduino.h>
|
||||
#include "helpers/ESP32Board.h"
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
// BQ27220 Fuel Gauge Registers (shared with TDeckBoard)
|
||||
#define BQ27220_REG_TEMPERATURE 0x06
|
||||
#define BQ27220_REG_VOLTAGE 0x08
|
||||
#define BQ27220_REG_CURRENT 0x0C
|
||||
#define BQ27220_REG_SOC 0x2C
|
||||
#define BQ27220_REG_REMAIN_CAP 0x10
|
||||
#define BQ27220_REG_FULL_CAP 0x12
|
||||
#define BQ27220_REG_AVG_CURRENT 0x14
|
||||
#define BQ27220_REG_TIME_TO_EMPTY 0x16
|
||||
#define BQ27220_REG_AVG_POWER 0x24
|
||||
#define BQ27220_REG_DESIGN_CAP 0x3C
|
||||
#define BQ27220_REG_OP_STATUS 0x3A
|
||||
|
||||
class T5S3Board : public ESP32Board {
|
||||
public:
|
||||
void begin();
|
||||
|
||||
void powerOff() override {
|
||||
btStop();
|
||||
// Turn off backlight before sleeping
|
||||
#ifdef BOARD_BL_EN
|
||||
digitalWrite(BOARD_BL_EN, LOW);
|
||||
#endif
|
||||
}
|
||||
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Hold LoRa DIO1 and NSS during deep sleep
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
||||
|
||||
if (pin_wake_btn < 0) {
|
||||
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||
} else {
|
||||
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1) | (1ULL << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||
}
|
||||
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000ULL);
|
||||
}
|
||||
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
// BQ27220 fuel gauge interface (identical register protocol to TDeckBoard)
|
||||
uint16_t getBattMilliVolts() override;
|
||||
uint8_t getBatteryPercent();
|
||||
int16_t getAvgCurrent();
|
||||
int16_t getAvgPower();
|
||||
uint16_t getTimeToEmpty();
|
||||
uint16_t getRemainingCapacity();
|
||||
uint16_t getFullChargeCapacity();
|
||||
uint16_t getDesignCapacity();
|
||||
int16_t getBattTemperature();
|
||||
bool configureFuelGauge(uint16_t designCapacity_mAh = BQ27220_DESIGN_CAPACITY_MAH);
|
||||
|
||||
// Backlight control (GPIO11 — functional warm-tone front-light, PWM capable)
|
||||
// Brightness 0-255 (0=off, 153=comfortable reading, 255=max)
|
||||
bool _backlightOn = false;
|
||||
uint8_t _backlightBrightness = 153; // Same default as Meshtastic
|
||||
|
||||
void setBacklight(bool on) {
|
||||
#ifdef BOARD_BL_EN
|
||||
_backlightOn = on;
|
||||
analogWrite(BOARD_BL_EN, on ? _backlightBrightness : 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void setBacklightBrightness(uint8_t brightness) {
|
||||
#ifdef BOARD_BL_EN
|
||||
_backlightBrightness = brightness;
|
||||
if (_backlightOn) {
|
||||
analogWrite(BOARD_BL_EN, brightness);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isBacklightOn() const { return _backlightOn; }
|
||||
|
||||
void toggleBacklight() {
|
||||
setBacklight(!_backlightOn);
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const {
|
||||
return "LilyGo T5S3 E-Paper Pro";
|
||||
}
|
||||
};
|
||||
19
variants/lilygo_t5s3_epaper_pro/pins_arduino.h
Normal file
19
variants/lilygo_t5s3_epaper_pro/pins_arduino.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef Pins_Arduino_h
|
||||
#define Pins_Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define USB_VID 0x303a
|
||||
#define USB_PID 0x1001
|
||||
|
||||
// Default Wire will be mapped to RTC, Touch, PCA9535, BQ25896, BQ27220, TPS65185
|
||||
static const uint8_t SDA = 39;
|
||||
static const uint8_t SCL = 40;
|
||||
|
||||
// Default SPI will be mapped to LoRa + SD card
|
||||
static const uint8_t SS = 46; // LoRa CS
|
||||
static const uint8_t MOSI = 13;
|
||||
static const uint8_t MISO = 21;
|
||||
static const uint8_t SCK = 14;
|
||||
|
||||
#endif /* Pins_Arduino_h */
|
||||
144
variants/lilygo_t5s3_epaper_pro/platformio.ini
Normal file
144
variants/lilygo_t5s3_epaper_pro/platformio.ini
Normal file
@@ -0,0 +1,144 @@
|
||||
; ===========================================================================
|
||||
; LilyGo T5 S3 E-Paper Pro (H752-B / V2 hardware)
|
||||
; 4.7" parallel e-ink (960x540), GT911 touch, SX1262 LoRa, no keyboard
|
||||
; ===========================================================================
|
||||
;
|
||||
; Place t5s3-epaper-pro.json in boards/ directory.
|
||||
; Place variant files in variants/LilyGo_T5S3_EPaper_Pro/
|
||||
; Place FastEPDDisplay.h/.cpp in src/helpers/ui/
|
||||
;
|
||||
|
||||
[LilyGo_T5S3_EPaper_Pro]
|
||||
extends = esp32_base
|
||||
board = t5s3-epaper-pro
|
||||
board_build.flash_mode = qio
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.arduino.memory_type = qio_opi
|
||||
board_upload.flash_size = 16MB
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/LilyGo_T5S3_EPaper_Pro
|
||||
-D LilyGo_T5S3_EPaper_Pro
|
||||
-D T5_S3_EPAPER_PRO_V2
|
||||
-D BOARD_HAS_PSRAM=1
|
||||
-D CORE_DEBUG_LEVEL=1
|
||||
-D FORMAT_SPIFFS_IF_FAILED=1
|
||||
-D FORMAT_LITTLEFS_IF_FAILED=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_DIO2_AS_RF_SWITCH
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=2.4f
|
||||
-D P_LORA_DIO_1=10
|
||||
-D P_LORA_NSS=46
|
||||
-D P_LORA_RESET=1
|
||||
-D P_LORA_BUSY=47
|
||||
-D P_LORA_SCLK=14
|
||||
-D P_LORA_MISO=21
|
||||
-D P_LORA_MOSI=13
|
||||
-D ENV_INCLUDE_AHTX0=0
|
||||
-D ENV_INCLUDE_BME280=0
|
||||
-D ENV_INCLUDE_BMP280=0
|
||||
-D ENV_INCLUDE_SHTC3=0
|
||||
-D ENV_INCLUDE_SHT4X=0
|
||||
-D ENV_INCLUDE_LPS22HB=0
|
||||
-D ENV_INCLUDE_INA3221=0
|
||||
-D ENV_INCLUDE_INA219=0
|
||||
-D ENV_INCLUDE_INA226=0
|
||||
-D ENV_INCLUDE_INA260=0
|
||||
-D ENV_INCLUDE_MLX90614=0
|
||||
-D ENV_INCLUDE_VL53L0X=0
|
||||
-D ENV_INCLUDE_BME680=0
|
||||
-D ENV_INCLUDE_BMP085=0
|
||||
-D HAS_BQ27220=1
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=2800
|
||||
-D PIN_USER_BTN=0
|
||||
-D SDCARD_USE_SPI1
|
||||
-D ARDUINO_LOOP_STACK_SIZE=32768
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/LilyGo_T5S3_EPaper_Pro>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; T5S3 standalone — touch UI (stub), verify display rendering
|
||||
; Uses FastEPD for parallel e-ink, Adafruit GFX for drawing
|
||||
; ---------------------------------------------------------------------------
|
||||
[env:meck_t5s3_headless]
|
||||
extends = LilyGo_T5S3_EPaper_Pro
|
||||
build_flags =
|
||||
${LilyGo_T5S3_EPaper_Pro.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=1500
|
||||
-D MAX_GROUP_CHANNELS=20
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D DISPLAY_CLASS=FastEPDDisplay
|
||||
-D USE_EINK
|
||||
build_src_filter = ${LilyGo_T5S3_EPaper_Pro.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/ui/FastEPDDisplay.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_T5S3_EPaper_Pro.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
adafruit/Adafruit GFX Library@^1.11.0
|
||||
https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Phase 3+ variants (uncomment when touch input is implemented)
|
||||
; ---------------------------------------------------------------------------
|
||||
|
||||
; T5S3 BLE companion — touch UI, BLE phone bridging
|
||||
;[env:meck_t5s3_ble]
|
||||
;extends = LilyGo_T5S3_EPaper_Pro
|
||||
;build_flags =
|
||||
; ${LilyGo_T5S3_EPaper_Pro.build_flags}
|
||||
; -I examples/companion_radio/ui-new
|
||||
; -D MAX_CONTACTS=500
|
||||
; -D MAX_GROUP_CHANNELS=20
|
||||
; -D BLE_PIN_CODE=123456
|
||||
; -D OFFLINE_QUEUE_SIZE=256
|
||||
; -D DISPLAY_CLASS=FastEPDDisplay
|
||||
; -D USE_EINK
|
||||
; -D MECK_WEB_READER=1
|
||||
;build_src_filter = ${LilyGo_T5S3_EPaper_Pro.build_src_filter}
|
||||
; +<helpers/esp32/*.cpp>
|
||||
; +<helpers/ui/MomentaryButton.cpp>
|
||||
; +<helpers/ui/FastEPDDisplay.cpp>
|
||||
; +<../examples/companion_radio/*.cpp>
|
||||
; +<../examples/companion_radio/ui-new/*.cpp>
|
||||
;lib_deps =
|
||||
; ${LilyGo_T5S3_EPaper_Pro.lib_deps}
|
||||
; densaugeo/base64 @ ~1.4.0
|
||||
; bitbank2/PNGdec@^1.0.1
|
||||
; adafruit/Adafruit GFX Library@^1.11.0
|
||||
; https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip
|
||||
|
||||
; T5S3 standalone — touch UI, no BLE/WiFi, maximum battery life
|
||||
;[env:meck_t5s3_standalone]
|
||||
;extends = LilyGo_T5S3_EPaper_Pro
|
||||
;build_flags =
|
||||
; ${LilyGo_T5S3_EPaper_Pro.build_flags}
|
||||
; -I examples/companion_radio/ui-new
|
||||
; -D MAX_CONTACTS=1500
|
||||
; -D MAX_GROUP_CHANNELS=20
|
||||
; -D OFFLINE_QUEUE_SIZE=256
|
||||
; -D DISPLAY_CLASS=FastEPDDisplay
|
||||
; -D USE_EINK
|
||||
;build_src_filter = ${LilyGo_T5S3_EPaper_Pro.build_src_filter}
|
||||
; +<helpers/esp32/*.cpp>
|
||||
; +<helpers/ui/MomentaryButton.cpp>
|
||||
; +<helpers/ui/FastEPDDisplay.cpp>
|
||||
; +<../examples/companion_radio/*.cpp>
|
||||
; +<../examples/companion_radio/ui-new/*.cpp>
|
||||
;lib_deps =
|
||||
; ${LilyGo_T5S3_EPaper_Pro.lib_deps}
|
||||
; densaugeo/base64 @ ~1.4.0
|
||||
; bitbank2/PNGdec@^1.0.1
|
||||
; adafruit/Adafruit GFX Library@^1.11.0
|
||||
; https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip
|
||||
95
variants/lilygo_t5s3_epaper_pro/target.cpp
Normal file
95
variants/lilygo_t5s3_epaper_pro/target.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include <Arduino.h>
|
||||
#include "variant.h"
|
||||
#include "target.h"
|
||||
|
||||
T5S3Board board;
|
||||
|
||||
// LoRa radio on separate SPI bus
|
||||
// T5S3 V2 SPI pins: SCLK=14, MISO=21, MOSI=13 (shared with SD card)
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass loraSpi(HSPI);
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, loraSpi);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
// No GPS on H752-B
|
||||
#if HAS_GPS
|
||||
GPSStreamCounter gpsStream(Serial2);
|
||||
MicroNMEALocationProvider gps(gpsStream, &rtc_clock);
|
||||
EnvironmentSensorManager sensors(gps);
|
||||
#else
|
||||
SensorManager sensors;
|
||||
#endif
|
||||
|
||||
// Phase 2: Display
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
MESH_DEBUG_PRINTLN("radio_init() - starting");
|
||||
|
||||
// NOTE: board.begin() is called by main.cpp setup() before radio_init()
|
||||
// I2C is already initialized there with correct pins
|
||||
|
||||
fallback_clock.begin();
|
||||
MESH_DEBUG_PRINTLN("radio_init() - fallback_clock started");
|
||||
|
||||
// Use existing Wire for RTC discovery
|
||||
// AutoDiscoverRTCClock will find PCF85063 at 0x51 if present
|
||||
rtc_clock.begin(Wire);
|
||||
MESH_DEBUG_PRINTLN("radio_init() - rtc_clock started");
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
MESH_DEBUG_PRINTLN("radio_init() - initializing LoRa SPI (SCLK=%d, MISO=%d, MOSI=%d, NSS=%d)...",
|
||||
P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, P_LORA_NSS);
|
||||
loraSpi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, P_LORA_NSS);
|
||||
MESH_DEBUG_PRINTLN("radio_init() - SPI initialized, calling radio.std_init()...");
|
||||
bool result = radio.std_init(&loraSpi);
|
||||
if (result) {
|
||||
radio.setPreambleLength(32);
|
||||
MESH_DEBUG_PRINTLN("radio_init() - preamble set to 32 symbols");
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("radio_init() - radio.std_init() returned: %s", result ? "SUCCESS" : "FAILED");
|
||||
return result;
|
||||
#else
|
||||
MESH_DEBUG_PRINTLN("radio_init() - calling radio.std_init() without custom SPI...");
|
||||
bool result = radio.std_init();
|
||||
if (result) {
|
||||
radio.setPreambleLength(32);
|
||||
MESH_DEBUG_PRINTLN("radio_init() - preamble set to 32 symbols");
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng);
|
||||
}
|
||||
|
||||
void radio_reset_agc() {
|
||||
radio.setRxBoostedGainMode(true);
|
||||
}
|
||||
50
variants/lilygo_t5s3_epaper_pro/target.h
Normal file
50
variants/lilygo_t5s3_epaper_pro/target.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
// Include variant.h first to ensure all board-specific defines are available
|
||||
#include "variant.h"
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <T5S3Board.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
|
||||
// Display support — FastEPDDisplay for parallel e-ink (not GxEPD2)
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/FastEPDDisplay.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
|
||||
// No GPS on H752-B (non-GPS variant)
|
||||
// If porting to H752-01/H752-02 with GPS, enable this:
|
||||
#if HAS_GPS
|
||||
#include "helpers/sensors/EnvironmentSensorManager.h"
|
||||
#include "helpers/sensors/MicroNMEALocationProvider.h"
|
||||
#include "GPSStreamCounter.h"
|
||||
#else
|
||||
#include <helpers/SensorManager.h>
|
||||
#endif
|
||||
|
||||
extern T5S3Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
|
||||
#if HAS_GPS
|
||||
extern GPSStreamCounter gpsStream;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
#else
|
||||
extern SensorManager sensors;
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
void radio_reset_agc();
|
||||
188
variants/lilygo_t5s3_epaper_pro/variant.h
Normal file
188
variants/lilygo_t5s3_epaper_pro/variant.h
Normal file
@@ -0,0 +1,188 @@
|
||||
#pragma once
|
||||
|
||||
// =============================================================================
|
||||
// LilyGo T5 S3 E-Paper Pro V2 (H752-01/H752-B) - Pin Definitions for Meck
|
||||
//
|
||||
// 4.7" parallel e-ink (ED047TC1, 960x540, 16-grey) — NO SPI display
|
||||
// GT911 capacitive touch (no physical keyboard)
|
||||
// SX1262 LoRa, BQ27220+BQ25896 battery, PCF85063 RTC, PCA9535 IO expander
|
||||
// =============================================================================
|
||||
|
||||
// Board identifier
|
||||
#define LilyGo_T5S3_EPaper_Pro 1
|
||||
#define T5_S3_EPAPER_PRO_V2 1
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// I2C Bus — shared by GT911, PCF85063, PCA9535, BQ25896, BQ27220, TPS65185
|
||||
// -----------------------------------------------------------------------------
|
||||
#define I2C_SDA 39
|
||||
#define I2C_SCL 40
|
||||
|
||||
// Aliases for ESP32Board base class compatibility
|
||||
#define PIN_BOARD_SDA I2C_SDA
|
||||
#define PIN_BOARD_SCL I2C_SCL
|
||||
|
||||
// I2C Device Addresses
|
||||
#define I2C_ADDR_GT911 0x5D // Touch controller
|
||||
#define I2C_ADDR_PCF85063 0x51 // RTC
|
||||
#define I2C_ADDR_PCA9535 0x20 // IO expander (e-ink power control)
|
||||
#define I2C_ADDR_BQ27220 0x55 // Fuel gauge
|
||||
#define I2C_ADDR_BQ25896 0x6B // Battery charger
|
||||
#define I2C_ADDR_TPS65185 0x68 // E-ink power driver
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SPI Bus — shared by LoRa and SD card
|
||||
// Different from T-Deck Pro! (T-Deck: 33/47/36, T5S3: 13/21/14)
|
||||
// -----------------------------------------------------------------------------
|
||||
#define BOARD_SPI_SCLK 14
|
||||
#define BOARD_SPI_MISO 21
|
||||
#define BOARD_SPI_MOSI 13
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LoRa Radio (SX1262)
|
||||
// SPI bus shared with SD card, different chip selects
|
||||
// -----------------------------------------------------------------------------
|
||||
#define P_LORA_NSS 46
|
||||
#define P_LORA_DIO_1 10 // IRQ
|
||||
#define P_LORA_RESET 1
|
||||
#define P_LORA_BUSY 47
|
||||
#define P_LORA_SCLK BOARD_SPI_SCLK
|
||||
#define P_LORA_MISO BOARD_SPI_MISO
|
||||
#define P_LORA_MOSI BOARD_SPI_MOSI
|
||||
// Note: No P_LORA_EN on T5S3 — LoRa is always powered
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// E-Ink Display (ED047TC1 — 8-bit parallel, NOT SPI)
|
||||
// Driven by epdiy/FastEPD library via TPS65185 + PCA9535
|
||||
// GxEPD2 is NOT used on this board.
|
||||
// -----------------------------------------------------------------------------
|
||||
// Parallel data bus (directly wired to ESP32-S3 GPIOs)
|
||||
#define EP_D0 5
|
||||
#define EP_D1 6
|
||||
#define EP_D2 7
|
||||
#define EP_D3 15
|
||||
#define EP_D4 16
|
||||
#define EP_D5 17
|
||||
#define EP_D6 18
|
||||
#define EP_D7 8
|
||||
|
||||
// Control signals
|
||||
#define EP_CKV 48 // Clock vertical
|
||||
#define EP_STH 41 // Start horizontal
|
||||
#define EP_LEH 42 // Latch enable horizontal
|
||||
#define EP_STV 45 // Start vertical
|
||||
#define EP_CKH 4 // Clock horizontal (edge)
|
||||
|
||||
// E-ink power is managed by TPS65185 through PCA9535 IO expander:
|
||||
// PCA9535 IO10 -> EP_OE (output enable, source driver)
|
||||
// PCA9535 IO11 -> EP_MODE (output mode, gate driver)
|
||||
// PCA9535 IO13 -> TPS_PWRUP
|
||||
// PCA9535 IO14 -> VCOM_CTRL
|
||||
// PCA9535 IO15 -> TPS_WAKEUP
|
||||
// PCA9535 IO16 -> TPS_PWR_GOOD (input)
|
||||
// PCA9535 IO17 -> TPS_INT (input)
|
||||
|
||||
// Display dimensions — native resolution of ED047TC1
|
||||
#define EPD_WIDTH 960
|
||||
#define EPD_HEIGHT 540
|
||||
|
||||
// Backlight (warm-tone front-light — functional on V2!)
|
||||
#define BOARD_BL_EN 11
|
||||
|
||||
// We do NOT define DISPLAY_CLASS or EINK_DISPLAY_MODEL here.
|
||||
// The parallel display uses FastEPD, not GxEPD2.
|
||||
// DISPLAY_CLASS will be defined in platformio.ini as FastEPDDisplay
|
||||
// for builds that include display support.
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Touch Controller (GT911)
|
||||
// No physical keyboard on this board — touch-only input
|
||||
// -----------------------------------------------------------------------------
|
||||
#define HAS_TOUCHSCREEN 1
|
||||
#define GT911_PIN_INT 3
|
||||
#define GT911_PIN_RST 9
|
||||
#define GT911_PIN_SDA I2C_SDA
|
||||
#define GT911_PIN_SCL I2C_SCL
|
||||
|
||||
// No keyboard
|
||||
// #define HAS_PHYSICAL_KEYBOARD 0
|
||||
|
||||
// Compatibility: main.cpp references CST328 touch (T-Deck Pro).
|
||||
// Map to GT911 equivalents so shared code compiles.
|
||||
// The actual touch init for T5S3 will use GT911 in Phase 2.
|
||||
#define CST328_PIN_INT GT911_PIN_INT
|
||||
#define CST328_PIN_RST GT911_PIN_RST
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SD Card — shares SPI bus with LoRa
|
||||
// -----------------------------------------------------------------------------
|
||||
#define HAS_SDCARD
|
||||
#define SDCARD_USE_SPI1
|
||||
#define SDCARD_CS 12
|
||||
#define SPI_CS SDCARD_CS
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// GPS — Not present on H752-B (non-GPS variant)
|
||||
// If a GPS model is used (H752-01/H752-02), define HAS_GPS=1
|
||||
// and uncomment the GPS pins below.
|
||||
// -----------------------------------------------------------------------------
|
||||
// #define HAS_GPS 1
|
||||
// #define GPS_BAUDRATE 38400
|
||||
// #define GPS_RX_PIN 44
|
||||
// #define GPS_TX_PIN 43
|
||||
|
||||
// Fallback for code that references GPS_BAUDRATE without HAS_GPS guard
|
||||
// (e.g. MyMesh.cpp CLI rescue command)
|
||||
#ifndef GPS_BAUDRATE
|
||||
#define GPS_BAUDRATE 9600
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// RTC — PCF85063 (proper hardware RTC, battery-backed!)
|
||||
// This is a significant upgrade over T-Deck Pro which has no RTC.
|
||||
// -----------------------------------------------------------------------------
|
||||
#define HAS_PCF85063_RTC 1
|
||||
#define PCF85063_I2C_ADDR 0x51
|
||||
#define PCF85063_INT_PIN 2
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// PCA9535 IO Expander
|
||||
// Controls e-ink power sequencing and has a user button
|
||||
// -----------------------------------------------------------------------------
|
||||
#define HAS_PCA9535 1
|
||||
#define PCA9535_I2C_ADDR 0x20
|
||||
#define PCA9535_INT_PIN 38
|
||||
|
||||
// PCA9535 pin assignments (directly from LilyGo schematic):
|
||||
// Port 0 (IO0x): IO00-IO07 — mostly unused/reserved
|
||||
// Port 1 (IO1x):
|
||||
#define PCA9535_EP_OE 0 // IO10 — EP output enable (source driver)
|
||||
#define PCA9535_EP_MODE 1 // IO11 — EP mode (gate driver)
|
||||
#define PCA9535_BUTTON 2 // IO12 — User button via IO expander
|
||||
#define PCA9535_TPS_PWRUP 3 // IO13 — TPS65185 power up
|
||||
#define PCA9535_VCOM_CTRL 4 // IO14 — VCOM control
|
||||
#define PCA9535_TPS_WAKEUP 5 // IO15 — TPS65185 wakeup
|
||||
#define PCA9535_TPS_PWRGOOD 6 // IO16 — TPS65185 power good (input)
|
||||
#define PCA9535_TPS_INT 7 // IO17 — TPS65185 interrupt (input)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Buttons & Controls
|
||||
// -----------------------------------------------------------------------------
|
||||
#define BUTTON_PIN 0 // Boot button (GPIO0)
|
||||
#define PIN_USER_BTN 0
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Power Management
|
||||
// -----------------------------------------------------------------------------
|
||||
#define HAS_BQ27220 1
|
||||
#define BQ27220_I2C_ADDR 0x55
|
||||
|
||||
// T5S3 E-Paper Pro battery (1500 mAh — larger than T-Deck Pro's 1400 mAh)
|
||||
#ifndef BQ27220_DESIGN_CAPACITY_MAH
|
||||
#define BQ27220_DESIGN_CAPACITY_MAH 1500
|
||||
#endif
|
||||
|
||||
#define AUTO_SHUTDOWN_MILLIVOLTS 2800
|
||||
|
||||
// No explicit peripheral power pin on T5S3 (unlike T-Deck Pro's PIN_PERF_POWERON)
|
||||
// Peripherals are always powered when the board is on.
|
||||
Reference in New Issue
Block a user