Files
MeshCore/variants/m5stack_cardputer_adv/hw/CardputerDisplay.cpp
T
mjgodatandClaude Opus 5.5 5681898ad4 Add M5Stack Cardputer-Adv variant with keyboard/display companion UI
New variant variants/m5stack_cardputer_adv for the Cardputer-Adv
(ESP32-S3, 8 MB) with the Cap LoRa-1262 (SX1262 + GNSS):

- Envs: companion_radio_ble, companion_radio_usb, repeater, room_server.
- Board: enables the Cap's antenna path via the PI4IOE5V6408 on the
  internal I2C bus (Wire1, G8/G9); Wire stays on the Grove port. LoRa and
  microSD share FSPI because the display owns SPI3. TCXO 1.8 V.
- Companion UI adapted from MultiMote/meshcore-cardputer-adv (MIT,
  LICENSE.MultiMote), running on the standard companion so the USB
  terminal CLI stays available. Contacts/identity stay in internal
  SPIFFS; the microSD card is optional (chat history, keyboard layouts).
- Sticky Shift, one-shot Fn with Fn+letter shortcuts, top button (G0)
  sends a flood advert, brightness control.
- Text size setting (Normal/Large/Extra large) with DejaVu Sans fonts
  generated by tools/gen_vlw_font.py; layouts derive from the font size.
- Power save maps to the companion's powersaving pref; the UI vetoes
  light sleep while the screen is on. Low-battery power-off at 3.4 V.
- win_long_cmdline.py: Windows-only -iprefix include shortening so the
  ESP32 compile command stays under the 32,767-character limit.

Shared companion changes are guarded by CARDPUTER_ADV_UI: main.cpp
selects CardputerDataStore/CardputerUITask/CardputerMesh, and MyMesh.h
maps the_mesh to the subclass like the PSRAM storage path does.

Tested on hardware (910.525 MHz mesh): radio RX/TX, USB CLI, BLE, UI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:24:43 -07:00

210 lines
5.1 KiB
C++

#include "CardputerDisplay.h"
#include "font.h"
#include "font_16.h"
#include "font_19.h"
#include "globals.h"
#include <MeshCore.h>
ColorVal UIColor::window_bkg = CardputerDisplay::PaletteColor::P_BLACK;
int32_t emoji_draw_callback(lgfx::LGFXBase *gfx, int32_t x, int32_t y, uint32_t code, int32_t font_height) {
int w = gfx->textWidth("O");
int h = gfx->fontHeight();
gfx->drawRect(x, y + (h - w - w / 2), w, w, TFT_DARKGRAY);
return w;
}
LGFX::LGFX() {
{
auto cfg = bus_instance_.config();
cfg.spi_host = TFT_SPI_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 40000000; // 40 MHz write clock
cfg.spi_3wire = true;
cfg.use_lock = true;
cfg.dma_channel = SPI_DMA_CH_AUTO;
cfg.pin_sclk = PIN_TFT_SCK;
cfg.pin_mosi = PIN_TFT_MOSI;
cfg.pin_miso = -1;
cfg.pin_dc = PIN_TFT_DC;
bus_instance_.config(cfg);
panel_instance_.setBus(&bus_instance_);
}
{
auto cfg = panel_instance_.config();
cfg.pin_cs = PIN_TFT_CS;
cfg.pin_rst = PIN_TFT_RST;
cfg.panel_width = 135;
cfg.panel_height = 240;
cfg.offset_x = 52; // values from M5GFX
cfg.offset_y = 40;
cfg.readable = false;
cfg.invert = true;
// cfg.rgb_order = false;
// cfg.dlen_16bit = false;
// cfg.bus_shared = true;
panel_instance_.config(cfg);
}
{
auto cfg = light_instance_.config();
cfg.pin_bl = PIN_TFT_BL;
cfg.invert = false;
cfg.freq = 256;
cfg.offset = 16;
cfg.pwm_channel = 7;
light_instance_.config(cfg);
panel_instance_.setLight(&light_instance_);
}
setPanel(&panel_instance_);
}
static const uint8_t *const kFonts[CardputerDisplay::TEXT_LEVEL_COUNT] = { DejaVuSans_11, DejaVuSans_16,
DejaVuSans_19 };
static const uint8_t kFontPx[CardputerDisplay::TEXT_LEVEL_COUNT] = { 12, 16, 19 };
static const uint8_t kNormalPx = 12;
const char *CardputerDisplay::textLevelName(uint8_t level) {
static const char *const names[TEXT_LEVEL_COUNT] = { "Normal", "Large", "Extra large" };
return level < TEXT_LEVEL_COUNT ? names[level] : names[TEXT_LARGE];
}
void CardputerDisplay::setTextLevel(uint8_t level) {
if (level >= TEXT_LEVEL_COUNT) level = TEXT_LARGE;
_textLevel = level;
_fontPx = kFontPx[level];
LCD.loadFont(kFonts[level]);
setTextSize(1);
}
void CardputerDisplay::setSmallText() {
_scale = (float)kNormalPx / _fontPx;
LCD.setTextSize(_scale);
}
bool CardputerDisplay::begin() {
LCD.setBaseColor(TFT_BLACK);
bool success = LCD.begin();
if (!success) {
return false;
}
LCD.setRotation(1);
LCD.setColorDepth(16); // lower SPI bandwidth
LCD.setBrightness(128);
LCD.setTextColor(TFT_WHITE, TFT_BLACK);
LCD.setEmojiCallback(emoji_draw_callback);
setTextLevel(_textLevel);
return true;
}
void CardputerDisplay::turnOn() {
_isOn = true;
LCD.wakeup();
}
void CardputerDisplay::turnOff() {
_isOn = false;
LCD.sleep();
}
void CardputerDisplay::clear() {
LCD.clear();
}
void CardputerDisplay::startFrame(ColorVal bkg) {
LCD.startWrite();
LCD.clear(P_BLACK);
}
void CardputerDisplay::endFrame() {
LCD.endWrite();
}
int32_t CardputerDisplay::getFontHeight() const {
return LCD.fontHeight();
}
int16_t CardputerDisplay::getFontLineHeight() const {
return LCD.fontHeight();
}
void CardputerDisplay::drawTextLeftAlignWithScroll(int x, int y, int available_width, const char *text) {
int text_len = strlen(text);
int offset = 0;
// Calculate how many characters from the start to skip so the text fits
while (offset < text_len) {
if (getTextWidth(&text[offset]) <= available_width) {
break;
}
// Skip to next UTF-8 character boundary
unsigned char b = text[offset];
if ((b & 0x80) == 0) {
// Single-byte character (ASCII)
offset += 1;
} else if ((b & 0xE0) == 0xC0) {
// 2-byte character
offset += 2;
} else if ((b & 0xF0) == 0xE0) {
// 3-byte character
offset += 3;
} else if ((b & 0xF8) == 0xF0) {
// 4-byte character
offset += 4;
} else {
offset += 1; // Invalid UTF-8, skip single byte
}
if (offset >= text_len) {
break;
}
}
drawTextLeftAlign(x, y, &text[offset]);
}
// Size 1 is the chosen text level; larger sizes are headings, scaled so a
// heading is about the same height (sz x Normal) at every level.
void CardputerDisplay::setTextSize(int sz) {
_scale = sz <= 1 ? 1.0f : (float)(kNormalPx * sz) / _fontPx;
LCD.setTextSize(_scale);
}
void CardputerDisplay::setColor(ColorVal c) {
_lastColor = c;
LCD.setColor(_lastColor);
LCD.setTextColor(_lastColor);
}
void CardputerDisplay::setCursor(int x, int y) {
LCD.setCursor(x, y);
}
void CardputerDisplay::print(const char *str) {
LCD.print(str);
}
void CardputerDisplay::fillRect(int x, int y, int w, int h) {
LCD.fillRect(x, y, w, h);
}
void CardputerDisplay::drawRect(int x, int y, int w, int h) {
LCD.drawRect(x, y, w, h);
}
void CardputerDisplay::drawXbm(int x, int y, const uint8_t *bits, int w, int h) {
LCD.drawBitmap(x, y, bits, w, h, _lastColor);
}
uint16_t CardputerDisplay::getTextWidth(const char *str) {
return LCD.textWidth(str);
}