Unify Heltec V4 OLED, TFT and headless display selection
Detect OLED or the original stock TFT touch assembly in ordinary V4.2/V4.3 images. Add saved live display.type auto/oled/tft/off selection and report the active selection in board output. Keep sensors on primary I2C, preserve shared power, and isolate TFT FSPI from radio HSPI through sleep and changes. Consolidate future picker/build choices only with application capability proof. Retain exact legacy OTA identities and separate R8 hardware images. Document controls and physical qualification limits. Validation: five serialized Full builds with capability/size/RAM checks; focused host tests, existing R8/T-Deck/OLED regressions, build-profile suite, and picker/provider tests. V4.3 OLED live changes and off/auto reboot persistence passed on Mercerwood. Physical TFT qualification remains pending.
This commit is contained in:
1 parent
5b5708283d
commit
b00d346df7
43 files changed
+2123
-76
No files matched your search
@@ -17,6 +17,9 @@
|
||||
#include <helpers/esp32/PowerTestControls.h>
|
||||
#endif
|
||||
#include <helpers/ui/DisplayPowerSettings.h>
|
||||
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED && defined(DISPLAY_CLASS)
|
||||
#include <helpers/ui/HeltecV4DisplaySettings.h>
|
||||
#endif
|
||||
#if defined(OLED_AUTO_DETECT) && OLED_AUTO_DETECT && defined(DISPLAY_CLASS)
|
||||
#include <helpers/ui/OLEDControllerSettings.h>
|
||||
#endif
|
||||
@@ -3553,6 +3556,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
}
|
||||
return;
|
||||
}
|
||||
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED && defined(DISPLAY_CLASS)
|
||||
if (mesh::ui::handleHeltecV4DisplayCommand(command, reply, 160, display)) return;
|
||||
#endif
|
||||
if (mesh::ui::handleDisplayPowerCommand(command, reply, 160)) return;
|
||||
#if defined(OLED_AUTO_DETECT) && OLED_AUTO_DETECT && defined(DISPLAY_CLASS)
|
||||
if (mesh::ui::handleOLEDControllerCommand(command, reply, 160, true)) return;
|
||||
|
||||
@@ -5,7 +5,15 @@
|
||||
// disconnected-pin sentinel. Keep every consumer on the same validity rule.
|
||||
#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) \
|
||||
&& (ENV_PIN_SDA >= 0) && (ENV_PIN_SCL >= 0)
|
||||
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED \
|
||||
&& defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) \
|
||||
&& ENV_PIN_SDA == PIN_BOARD_SDA && ENV_PIN_SCL == PIN_BOARD_SCL
|
||||
// Unified V4 reserves controller 1 for the display/touch probe. Expansion
|
||||
// sensors share the primary board bus, not a second controller on those pins.
|
||||
#define ENV_HAS_SECONDARY_I2C 0
|
||||
#else
|
||||
#define ENV_HAS_SECONDARY_I2C 1
|
||||
#endif
|
||||
#else
|
||||
#define ENV_HAS_SECONDARY_I2C 0
|
||||
#endif
|
||||
@@ -6,6 +6,7 @@
|
||||
#define MESHCORE_NULL_DISPLAY_CLASS_NullDisplayDriver 1
|
||||
#define MESHCORE_SMALL_DISPLAY_CLASS_SH1106Display 1
|
||||
#define MESHCORE_SMALL_DISPLAY_CLASS_SSD1306Display 1
|
||||
#define MESHCORE_SMALL_DISPLAY_CLASS_HeltecV4Display 1
|
||||
#define MESHCORE_SMALL_DISPLAY_CLASS_ST7735Display 1
|
||||
#define MESHCORE_SMALL_DISPLAY_CLASS_U8g2Display 1
|
||||
#define MESHCORE_EINK_DISPLAY_CLASS_GxEPDDisplay 1
|
||||
@@ -14,6 +15,7 @@
|
||||
#define MESHCORE_COLOR_DISPLAY_CLASS_ST7735Display 1
|
||||
#define MESHCORE_COLOR_DISPLAY_CLASS_ST7789Display 1
|
||||
#define MESHCORE_COLOR_DISPLAY_CLASS_ST7789LCDDisplay 1
|
||||
#define MESHCORE_COLOR_DISPLAY_CLASS_HeltecV4Display 1
|
||||
#define MESHCORE_COLOR_DISPLAY_CLASS_NV3001BDisplay 1
|
||||
#define MESHCORE_COLOR_DISPLAY_CLASS_LGFXDisplay 1
|
||||
#define MESHCORE_COLOR_DISPLAY_CLASS_SCIndicatorDisplay 1
|
||||
@@ -63,11 +65,13 @@
|
||||
#undef MESHCORE_COLOR_DISPLAY_CLASS_LGFXDisplay
|
||||
#undef MESHCORE_COLOR_DISPLAY_CLASS_NV3001BDisplay
|
||||
#undef MESHCORE_COLOR_DISPLAY_CLASS_ST7789LCDDisplay
|
||||
#undef MESHCORE_COLOR_DISPLAY_CLASS_HeltecV4Display
|
||||
#undef MESHCORE_COLOR_DISPLAY_CLASS_ST7789Display
|
||||
#undef MESHCORE_COLOR_DISPLAY_CLASS_ST7735Display
|
||||
#undef MESHCORE_SMALL_DISPLAY_CLASS_U8g2Display
|
||||
#undef MESHCORE_SMALL_DISPLAY_CLASS_ST7735Display
|
||||
#undef MESHCORE_SMALL_DISPLAY_CLASS_SSD1306Display
|
||||
#undef MESHCORE_SMALL_DISPLAY_CLASS_HeltecV4Display
|
||||
#undef MESHCORE_SMALL_DISPLAY_CLASS_SH1106Display
|
||||
#undef MESHCORE_EINK_DISPLAY_CLASS_E290Display
|
||||
#undef MESHCORE_EINK_DISPLAY_CLASS_E213Display
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED
|
||||
|
||||
#include "HeltecV4Display.h"
|
||||
#include "HeltecV4DisplayProbe.h"
|
||||
#include "ColorTheme.h"
|
||||
|
||||
// Runtime budget guard: both concrete drivers/private Wire are linked globals,
|
||||
// but count them conservatively with the one retained OLED heap framebuffer.
|
||||
// The separate 4 KiB SDK/bus allowance covers I2C/SPI locks, I2C driver state,
|
||||
// command links and both Wire buffers. TFT has no dynamic pixel canvas.
|
||||
static_assert(SSD1306Display::FRAMEBUFFER_BYTES == 1024,
|
||||
"Update the unified Heltec V4 OLED framebuffer budget");
|
||||
static_assert(sizeof(HeltecV4Display) + SSD1306Display::FRAMEBUFFER_BYTES + 32 <= 2048,
|
||||
"Update the unified Heltec V4 display runtime budget");
|
||||
static_assert(I2C_BUFFER_LENGTH <= 128,
|
||||
"Update the unified Heltec V4 private I2C runtime budget");
|
||||
|
||||
extern const char meshcore_capability_display_heltec_v4_unified[] =
|
||||
"meshcore.capability.display.heltec_v4.unified.v1";
|
||||
extern const char meshcore_capability_display_autodetect_heltec_v4[] =
|
||||
"meshcore.capability.display.autodetect.heltec_v4.v1";
|
||||
|
||||
// Both backends are linked into this image. Only the wrapper owns the shared
|
||||
// palette; monochrome OLED and RGB TFT colors are selected at activation.
|
||||
ColorVal UIColor::window_bkg = 0;
|
||||
ColorVal UIColor::title_bkg = 0;
|
||||
ColorVal UIColor::title_txt = 1;
|
||||
ColorVal UIColor::primary_txt = 1;
|
||||
ColorVal UIColor::secondary_txt = 1;
|
||||
ColorVal UIColor::warning_txt = 1;
|
||||
ColorVal UIColor::popup_bkg = 0;
|
||||
ColorVal UIColor::popup_txt = 1;
|
||||
ColorVal UIColor::corp_blue = 1;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kOledSda = 17;
|
||||
constexpr int kOledScl = 18;
|
||||
constexpr int kTouchSda = 47;
|
||||
constexpr int kTouchScl = 48;
|
||||
constexpr int kTouchReset = 44;
|
||||
constexpr uint8_t kTouchAddress = 0x2E;
|
||||
|
||||
// The original expansion kit routes the LCD's SDO to ground, so its LCD
|
||||
// cannot identify itself through SPI. Read the stock CHSC touch assembly's
|
||||
// documented identity instead. This identifies the assembly, not LCD health.
|
||||
struct TouchIo {
|
||||
TwoWire& wire;
|
||||
|
||||
void reset() {
|
||||
digitalWrite(kTouchReset, LOW);
|
||||
pinMode(kTouchReset, OUTPUT);
|
||||
delay(20);
|
||||
digitalWrite(kTouchReset, HIGH);
|
||||
}
|
||||
void delayMs(unsigned ms) { delay(ms); }
|
||||
bool write(const uint8_t* data, size_t length) {
|
||||
wire.beginTransmission(kTouchAddress);
|
||||
const bool complete = wire.write(data, length) == length;
|
||||
return wire.endTransmission() == 0 && complete;
|
||||
}
|
||||
bool readRegister(uint16_t address, uint8_t* data, size_t length) {
|
||||
wire.beginTransmission(kTouchAddress);
|
||||
const uint8_t reg[] = {uint8_t(address >> 8), uint8_t(address)};
|
||||
const bool complete = wire.write(reg, sizeof(reg)) == sizeof(reg);
|
||||
// The manufacturer's DMA-read sequence uses STOP between the address
|
||||
// write and the read, rather than relying on repeated-start support.
|
||||
if (wire.endTransmission() != 0 || !complete) return false;
|
||||
const size_t received = wire.requestFrom(kTouchAddress, uint8_t(length));
|
||||
const bool ok = received == length && wire.available() >= int(length);
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
data[i] = wire.available() ? uint8_t(wire.read()) : 0xFF;
|
||||
}
|
||||
while (wire.available()) wire.read();
|
||||
return ok;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void HeltecV4Display::claimPower() {
|
||||
if (_power && !_power_claimed) {
|
||||
_power->claim();
|
||||
_power_claimed = true;
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
void HeltecV4Display::releasePower() {
|
||||
if (_power && _power_claimed) {
|
||||
_power->release();
|
||||
_power_claimed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void HeltecV4Display::releasePins() {
|
||||
if (_wire_started) {
|
||||
_display_wire.end();
|
||||
_wire_started = false;
|
||||
}
|
||||
// No inactive driver may back-power a disconnected panel. Wire on GPIO4/3
|
||||
// belongs to RTC/environmental sensors and is never stopped or remapped.
|
||||
const int pins[] = {PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL,
|
||||
PIN_TFT_RST, PIN_TFT_LEDA_CTL, PIN_OLED_RESET,
|
||||
kTouchSda, kTouchScl, kTouchReset};
|
||||
for (int pin : pins) if (pin >= 0) pinMode(pin, INPUT);
|
||||
if (_known_tft) {
|
||||
// The original kit's LCD has an independent supply. Keep its backlight
|
||||
// and reset positively off just like the dedicated TFT driver; floating
|
||||
// either output is not an off command. Cold headless/off never does this.
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
pinMode(PIN_TFT_RST, OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
void HeltecV4Display::deactivate() {
|
||||
if (_active && _active->isOn()) _active->turnOff();
|
||||
if (_type == Type::Tft) _tft.releaseBus();
|
||||
_active = nullptr;
|
||||
_type = Type::None;
|
||||
releasePins();
|
||||
releasePower();
|
||||
}
|
||||
|
||||
bool HeltecV4Display::startOledBus() {
|
||||
if (_wire_started) return true;
|
||||
_wire_started = _display_wire.begin(kOledSda, kOledScl, 100000);
|
||||
if (_wire_started) _display_wire.setTimeOut(20);
|
||||
return _wire_started;
|
||||
}
|
||||
|
||||
bool HeltecV4Display::probeOled() {
|
||||
// Keep the TFT deselected while the same GPIO17/18 pair is briefly used
|
||||
// as OLED I2C. GPIO21 is OLED reset / TFT backlight; release it before ACK.
|
||||
digitalWrite(PIN_TFT_CS, HIGH);
|
||||
pinMode(PIN_TFT_CS, OUTPUT);
|
||||
digitalWrite(PIN_OLED_RESET, LOW);
|
||||
pinMode(PIN_OLED_RESET, OUTPUT);
|
||||
delay(10);
|
||||
digitalWrite(PIN_OLED_RESET, HIGH);
|
||||
delay(10);
|
||||
if (!startOledBus()) return false;
|
||||
_display_wire.beginTransmission(DISPLAY_ADDRESS);
|
||||
return _display_wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
bool HeltecV4Display::probeTouchAssembly() {
|
||||
if (_wire_started) {
|
||||
_display_wire.end();
|
||||
_wire_started = false;
|
||||
}
|
||||
pinMode(kOledSda, INPUT);
|
||||
pinMode(kOledScl, INPUT);
|
||||
// Avoid flashing the backlight while resetting and identifying touch.
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
if (!_display_wire.begin(kTouchSda, kTouchScl, 100000)) return false;
|
||||
_display_wire.setTimeOut(20);
|
||||
TouchIo io{_display_wire};
|
||||
const bool present = mesh::ui::probeHeltecV4Touch(io);
|
||||
_display_wire.end();
|
||||
pinMode(kTouchSda, INPUT);
|
||||
pinMode(kTouchScl, INPUT);
|
||||
if (!present) pinMode(kTouchReset, INPUT);
|
||||
return present;
|
||||
}
|
||||
|
||||
void HeltecV4Display::setPalette() {
|
||||
using namespace mesh::ui::color_theme;
|
||||
const bool color = _type == Type::Tft;
|
||||
UIColor::window_bkg = color ? WINDOW_BACKGROUND : 0;
|
||||
UIColor::title_bkg = color ? TITLE_BACKGROUND : 0;
|
||||
UIColor::title_txt = color ? TEXT : 1;
|
||||
UIColor::primary_txt = color ? TEXT : 1;
|
||||
UIColor::secondary_txt = color ? SECONDARY_TEXT : 1;
|
||||
UIColor::warning_txt = color ? WARNING_TEXT : 1;
|
||||
UIColor::popup_bkg = color ? POPUP_BACKGROUND : 0;
|
||||
UIColor::popup_txt = color ? TEXT : 1;
|
||||
UIColor::corp_blue = color ? ACCENT : 1;
|
||||
}
|
||||
|
||||
void HeltecV4Display::updateDimensions() {
|
||||
setDimensions(_active ? _active->width() : 128,
|
||||
_active ? _active->height() : 64);
|
||||
}
|
||||
|
||||
bool HeltecV4Display::startSelected(Type type) {
|
||||
bool ready = false;
|
||||
if (type == Type::Oled && startOledBus()) {
|
||||
_oled.setRotationDegrees(_rotation);
|
||||
_oled.setFlipped(_flipped);
|
||||
ready = _oled.begin();
|
||||
if (ready) {
|
||||
_active = &_oled;
|
||||
_known_tft = false;
|
||||
}
|
||||
} else if (type == Type::Tft) {
|
||||
_known_tft = true;
|
||||
if (_wire_started) {
|
||||
_display_wire.end();
|
||||
_wire_started = false;
|
||||
}
|
||||
_tft.setRotationDegrees(_rotation);
|
||||
_tft.setFlipped(_flipped);
|
||||
ready = _tft.begin();
|
||||
if (ready) _active = &_tft;
|
||||
}
|
||||
_type = ready ? type : Type::None;
|
||||
if (!ready) {
|
||||
releasePins();
|
||||
releasePower();
|
||||
}
|
||||
setPalette();
|
||||
updateDimensions();
|
||||
return ready;
|
||||
}
|
||||
|
||||
bool HeltecV4Display::begin() {
|
||||
const volatile char* unified = meshcore_capability_display_heltec_v4_unified;
|
||||
(void)*unified;
|
||||
// A boot with display.type=off must not enable any display/probe outputs.
|
||||
if (_started) deactivate();
|
||||
_started = true;
|
||||
++_generation;
|
||||
if (_mode == Mode::Off) {
|
||||
_status = "off";
|
||||
setPalette();
|
||||
updateDimensions();
|
||||
return false;
|
||||
}
|
||||
claimPower();
|
||||
bool oled = false, tft = false;
|
||||
if (_mode == Mode::Auto) {
|
||||
const volatile char* autodetect = meshcore_capability_display_autodetect_heltec_v4;
|
||||
(void)*autodetect;
|
||||
oled = probeOled();
|
||||
if (!oled) tft = probeTouchAssembly();
|
||||
}
|
||||
const Type selected = mesh::ui::selectHeltecV4Display(_mode, oled, tft);
|
||||
if (selected == Type::None) {
|
||||
_known_tft = false; // A rescan found no assembly; leave header pins free.
|
||||
releasePins();
|
||||
releasePower();
|
||||
_status = "none detected";
|
||||
setPalette();
|
||||
updateDimensions();
|
||||
return false;
|
||||
}
|
||||
const bool ready = startSelected(selected);
|
||||
_status = !ready ? "display initialization failed"
|
||||
: selected == Type::Oled ? "OLED"
|
||||
: _mode == Mode::Tft ? "TFT (forced)" : "TFT";
|
||||
return ready;
|
||||
}
|
||||
|
||||
bool HeltecV4Display::setMode(Mode mode) {
|
||||
if (unsigned(mode) > unsigned(Mode::Off)) return false;
|
||||
_mode = mode;
|
||||
if (_started) begin();
|
||||
// A valid off/headless choice is successful even though begin() reports no
|
||||
// active screen. The CLI can report status() independently of setting save.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HeltecV4Display::setRotationDegrees(uint16_t degrees) {
|
||||
if (degrees > 270 || degrees % 90 != 0) return false;
|
||||
_rotation = degrees;
|
||||
// Only touch the selected backend: a dormant OLED may retain an allocated
|
||||
// framebuffer and must not write into a bus now owned by the TFT.
|
||||
if (isOn()) _active->setRotationDegrees(degrees);
|
||||
updateDimensions();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HeltecV4Display::setFlipped(bool flipped) {
|
||||
_flipped = flipped;
|
||||
if (isOn()) _active->setFlipped(flipped);
|
||||
updateDimensions();
|
||||
}
|
||||
|
||||
void HeltecV4Display::turnOn() {
|
||||
if (!_active || _active->isOn()) return;
|
||||
claimPower();
|
||||
// The wrapper owns the rail. Reinitialize after it was cut, even though
|
||||
// each embedded driver's nullptr power owner would otherwise skip reset.
|
||||
const Type type = _type;
|
||||
_active = nullptr;
|
||||
if (!startSelected(type)) _status = "display initialization failed";
|
||||
}
|
||||
|
||||
void HeltecV4Display::turnOff() {
|
||||
if (_active && _active->isOn()) _active->turnOff();
|
||||
if (_type == Type::Tft) _tft.releaseBus();
|
||||
releasePins();
|
||||
releasePower();
|
||||
}
|
||||
|
||||
ColorVal HeltecV4Display::mapColor(ColorVal color) const {
|
||||
if (_type != Type::Oled) return color;
|
||||
// SSD1306 uses literal0/1/2, not RGB565 or just the RGB value's low bit.
|
||||
return mesh::ui::color_theme::isBackground(color) ? 0 : 1;
|
||||
}
|
||||
|
||||
int HeltecV4Display::textLineHeight() { return _active ? _active->textLineHeight() : 8; }
|
||||
int HeltecV4Display::renderWidth() const { return _active ? _active->renderWidth() : width(); }
|
||||
int HeltecV4Display::renderHeight() const { return _active ? _active->renderHeight() : height(); }
|
||||
bool HeltecV4Display::setRenderScale(uint8_t scale, float zoom) {
|
||||
return _active && _active->setRenderScale(scale, zoom);
|
||||
}
|
||||
void HeltecV4Display::clear() { if (isOn()) _active->clear(); }
|
||||
void HeltecV4Display::startFrame(ColorVal bkg) { if (isOn()) _active->startFrame(mapColor(bkg)); }
|
||||
void HeltecV4Display::setTextSize(int size) { if (_active) _active->setTextSize(size); }
|
||||
void HeltecV4Display::setCompactText(bool compact) { if (_active) _active->setCompactText(compact); }
|
||||
void HeltecV4Display::setColor(ColorVal color) { if (_active) _active->setColor(mapColor(color)); }
|
||||
void HeltecV4Display::setCursor(int x, int y) { if (isOn()) _active->setCursor(x, y); }
|
||||
void HeltecV4Display::print(const char* text) { if (isOn()) _active->print(text); }
|
||||
void HeltecV4Display::printWordWrap(const char* text, int max_width) {
|
||||
if (isOn()) _active->printWordWrap(text, max_width);
|
||||
}
|
||||
void HeltecV4Display::fillRect(int x, int y, int w, int h) { if (isOn()) _active->fillRect(x, y, w, h); }
|
||||
void HeltecV4Display::drawRect(int x, int y, int w, int h) { if (isOn()) _active->drawRect(x, y, w, h); }
|
||||
void HeltecV4Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
if (isOn()) _active->drawXbm(x, y, bits, w, h);
|
||||
}
|
||||
uint16_t HeltecV4Display::getTextWidth(const char* text) { return _active ? _active->getTextWidth(text) : 0; }
|
||||
void HeltecV4Display::endFrame() { if (isOn()) _active->endFrame(); }
|
||||
|
||||
#endif // HELTEC_V4_DISPLAY_UNIFIED
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
#include "HeltecV4DisplayPolicy.h"
|
||||
#include "SSD1306Display.h"
|
||||
#include "ST7789LCDDisplay.h"
|
||||
|
||||
// One display interface for the original V4/V4.3 base board and its original
|
||||
// touch expansion kit. R8 uses a different MCU memory/pin configuration.
|
||||
class HeltecV4Display : public DisplayDriver {
|
||||
using Mode = mesh::ui::HeltecV4DisplayMode;
|
||||
using Type = mesh::ui::HeltecV4DisplayType;
|
||||
|
||||
TwoWire _display_wire;
|
||||
SSD1306Display _oled;
|
||||
ST7789LCDDisplay _tft;
|
||||
RefCountedDigitalPin* _power;
|
||||
DisplayDriver* _active = nullptr;
|
||||
Mode _mode = Mode::Auto;
|
||||
Type _type = Type::None;
|
||||
const char* _status = "not initialized";
|
||||
bool _started = false;
|
||||
bool _wire_started = false;
|
||||
bool _power_claimed = false;
|
||||
bool _known_tft = false;
|
||||
bool _flipped = false;
|
||||
uint16_t _rotation = 0;
|
||||
uint32_t _generation = 0;
|
||||
|
||||
void claimPower();
|
||||
void releasePower();
|
||||
void releasePins();
|
||||
void deactivate();
|
||||
void setPalette();
|
||||
void updateDimensions();
|
||||
bool startOledBus();
|
||||
bool probeOled();
|
||||
bool probeTouchAssembly();
|
||||
bool startSelected(Type type);
|
||||
ColorVal mapColor(ColorVal color) const;
|
||||
|
||||
public:
|
||||
explicit HeltecV4Display(RefCountedDigitalPin* power = nullptr)
|
||||
: DisplayDriver(128, 64), _display_wire(1),
|
||||
_oled(nullptr, &_display_wire), _tft(nullptr), _power(power) {}
|
||||
|
||||
bool begin();
|
||||
bool setMode(Mode mode);
|
||||
Mode mode() const { return _mode; }
|
||||
Type selectedType() const { return _type; }
|
||||
const char* status() const { return _status; }
|
||||
uint32_t generation() const { return _generation; }
|
||||
|
||||
bool isOn() override { return _active && _active->isOn(); }
|
||||
bool supportsRotation() const override { return true; }
|
||||
bool setRotationDegrees(uint16_t degrees) override;
|
||||
void setFlipped(bool flipped) override;
|
||||
void turnOn() override;
|
||||
void turnOff() override;
|
||||
int textLineHeight() override;
|
||||
int renderWidth() const override;
|
||||
int renderHeight() const override;
|
||||
bool setRenderScale(uint8_t scale, float zoom) override;
|
||||
void clear() override;
|
||||
void startFrame(ColorVal bkg = UIColor::window_bkg) override;
|
||||
void setTextSize(int size) override;
|
||||
void setCompactText(bool compact) override;
|
||||
void setColor(ColorVal color) override;
|
||||
void setCursor(int x, int y) override;
|
||||
void print(const char* text) override;
|
||||
void printWordWrap(const char* text, int max_width) 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* text) override;
|
||||
void endFrame() override;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh { namespace ui {
|
||||
|
||||
enum class HeltecV4DisplayMode : uint8_t { Auto, Oled, Tft, Off };
|
||||
enum class HeltecV4DisplayType : uint8_t { None, Oled, Tft };
|
||||
|
||||
inline const char* heltecV4DisplayModeName(HeltecV4DisplayMode mode) {
|
||||
switch (mode) {
|
||||
case HeltecV4DisplayMode::Oled: return "oled";
|
||||
case HeltecV4DisplayMode::Tft: return "tft";
|
||||
case HeltecV4DisplayMode::Off: return "off";
|
||||
default: return "auto";
|
||||
}
|
||||
}
|
||||
|
||||
inline const char* heltecV4DisplayTypeName(HeltecV4DisplayType type) {
|
||||
switch (type) {
|
||||
case HeltecV4DisplayType::Oled: return "oled";
|
||||
case HeltecV4DisplayType::Tft: return "tft";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
inline bool parseHeltecV4DisplayMode(const char* text, HeltecV4DisplayMode& mode) {
|
||||
if (!text) return false;
|
||||
if (strcmp(text, "auto") == 0) mode = HeltecV4DisplayMode::Auto;
|
||||
else if (strcmp(text, "oled") == 0) mode = HeltecV4DisplayMode::Oled;
|
||||
else if (strcmp(text, "tft") == 0) mode = HeltecV4DisplayMode::Tft;
|
||||
else if (strcmp(text, "off") == 0) mode = HeltecV4DisplayMode::Off;
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline HeltecV4DisplayType selectHeltecV4Display(HeltecV4DisplayMode mode,
|
||||
bool oled_present,
|
||||
bool tft_present) {
|
||||
if (mode == HeltecV4DisplayMode::Off) return HeltecV4DisplayType::None;
|
||||
if (mode == HeltecV4DisplayMode::Oled) return HeltecV4DisplayType::Oled;
|
||||
if (mode == HeltecV4DisplayMode::Tft) return HeltecV4DisplayType::Tft;
|
||||
if (oled_present) return HeltecV4DisplayType::Oled;
|
||||
return tft_present ? HeltecV4DisplayType::Tft : HeltecV4DisplayType::None;
|
||||
}
|
||||
|
||||
}} // namespace mesh::ui
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh { namespace ui {
|
||||
|
||||
struct HeltecV4TouchIdentity {
|
||||
uint8_t address = 0;
|
||||
uint8_t chip = 0;
|
||||
uint32_t config = 0;
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
};
|
||||
|
||||
inline bool validHeltecV4TouchIdentity(const HeltecV4TouchIdentity& identity) {
|
||||
// The application note gives the chip code only as a five-bit field; it
|
||||
// does not reserve code zero. Require independently read configuration,
|
||||
// address and plausible touch coordinates instead of guessing chip IDs.
|
||||
// Touch resolution is independent of LCD resolution: the manufacturer's
|
||||
// own example is 360x360, even for a rectangular display assembly.
|
||||
return (identity.address == 0x2E || identity.address == 0x5C)
|
||||
&& identity.chip != 0xFF
|
||||
&& identity.config != 0 && identity.config != 0xFFFFFFFFu
|
||||
&& identity.width >= 160 && identity.width <= 4095
|
||||
&& identity.height >= 160 && identity.height <= 4095;
|
||||
}
|
||||
|
||||
inline bool equalHeltecV4TouchIdentity(const HeltecV4TouchIdentity& a,
|
||||
const HeltecV4TouchIdentity& b) {
|
||||
return a.address == b.address && a.chip == b.chip && a.config == b.config
|
||||
&& a.width == b.width && a.height == b.height;
|
||||
}
|
||||
|
||||
template <typename Io>
|
||||
bool readHeltecV4TouchIdentity(Io& io, HeltecV4TouchIdentity& identity) {
|
||||
uint8_t config[4], resolution[4];
|
||||
if (!io.readRegister(0x0001, &identity.address, 1)
|
||||
|| !io.readRegister(0x9E6B, &identity.chip, 1)
|
||||
|| !io.readRegister(0x9E00, config, sizeof(config))
|
||||
|| !io.readRegister(0x9E4C, resolution, sizeof(resolution))) return false;
|
||||
identity.config = uint32_t(config[0]) | uint32_t(config[1]) << 8
|
||||
| uint32_t(config[2]) << 16 | uint32_t(config[3]) << 24;
|
||||
identity.width = uint16_t(resolution[0]) | uint16_t(resolution[1]) << 8;
|
||||
identity.height = uint16_t(resolution[2]) | uint16_t(resolution[3]) << 8;
|
||||
return validHeltecV4TouchIdentity(identity);
|
||||
}
|
||||
|
||||
// CHSC6xxx application note, sections 3.1 and 3.1.1-3.1.5:
|
||||
// https://resource.heltec.cn/download/WiFi_LoRa_32_Expansion_Kit/Touchscreen-Data/
|
||||
// Application%C2%A0Note%C2%A0for%C2%A0CTPM_CHSC6xxx(1).pdf
|
||||
// Only the documented temporary DMA-mode command and identity reads are used.
|
||||
// There are no controller firmware/flash writes. Reset always exits DMA mode,
|
||||
// including after a NACK, short read or rejected identity. Io provides reset(),
|
||||
// delayMs(), write(bytes,length), and readRegister(address,bytes,length).
|
||||
template <typename Io>
|
||||
bool probeHeltecV4Touch(Io& io, HeltecV4TouchIdentity* identity = nullptr) {
|
||||
static const uint8_t enter_dma[] = {0x42, 0xBD, 0x28, 0x35,
|
||||
0xC1, 0x00, 0x35, 0xAE};
|
||||
bool present = false;
|
||||
HeltecV4TouchIdentity first, second;
|
||||
for (uint8_t attempt = 0; attempt < 2 && !present; ++attempt) {
|
||||
io.reset();
|
||||
io.delayMs(30);
|
||||
if (!io.write(enter_dma, sizeof(enter_dma))) continue;
|
||||
io.delayMs(20);
|
||||
present = readHeltecV4TouchIdentity(io, first)
|
||||
&& readHeltecV4TouchIdentity(io, second)
|
||||
&& equalHeltecV4TouchIdentity(first, second);
|
||||
}
|
||||
io.reset();
|
||||
io.delayMs(30);
|
||||
if (present && identity) *identity = first;
|
||||
return present;
|
||||
}
|
||||
|
||||
}} // namespace mesh::ui
|
||||
@@ -0,0 +1,125 @@
|
||||
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED
|
||||
|
||||
#include "HeltecV4DisplaySettings.h"
|
||||
#include <helpers/CheckedSettingsFile.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh { namespace ui {
|
||||
namespace {
|
||||
FILESYSTEM* settings_fs = nullptr;
|
||||
bool saved_mode = false;
|
||||
constexpr char path[] = "/display_type";
|
||||
constexpr char backup[] = "/display_type.bak";
|
||||
constexpr char temporary[] = "/display_type.tmp";
|
||||
|
||||
void encode(HeltecV4DisplayMode mode, uint8_t (&data)[6]) {
|
||||
data[0] = 'D'; data[1] = 'T'; data[2] = 1;
|
||||
data[3] = uint8_t(mode); data[4] = 0; data[5] = 0;
|
||||
for (unsigned i = 0; i < 5; ++i) data[5] ^= data[i];
|
||||
}
|
||||
|
||||
bool read(const char* name, HeltecV4DisplayMode& mode) {
|
||||
uint8_t data[6], expected[6];
|
||||
if (!storage::readFixedSettingsFile(settings_fs, name, data, sizeof(data))
|
||||
|| data[3] > uint8_t(HeltecV4DisplayMode::Off)) return false;
|
||||
const auto candidate = HeltecV4DisplayMode(data[3]);
|
||||
encode(candidate, expected);
|
||||
if (memcmp(data, expected, sizeof(data)) != 0) return false;
|
||||
mode = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool recover() {
|
||||
HeltecV4DisplayMode mode;
|
||||
if (read(path, mode) || !read(backup, mode)) return true;
|
||||
if (settings_fs->exists(path) && !settings_fs->remove(path)) return false;
|
||||
return settings_fs->rename(backup, path) && read(path, mode);
|
||||
}
|
||||
|
||||
bool save(HeltecV4DisplayMode mode) {
|
||||
if (!settings_fs || !recover()) return false;
|
||||
if (settings_fs->exists(temporary) && !settings_fs->remove(temporary)) return false;
|
||||
if (settings_fs->exists(temporary)) return false;
|
||||
File file = settings_fs->open(temporary, "w", true);
|
||||
if (!file) return false;
|
||||
uint8_t data[6];
|
||||
encode(mode, data);
|
||||
const bool written = file.write(data, sizeof(data)) == sizeof(data);
|
||||
file.close();
|
||||
HeltecV4DisplayMode checked;
|
||||
if (!written || !read(temporary, checked) || checked != mode) return false;
|
||||
if (settings_fs->exists(backup) && !settings_fs->remove(backup)) return false;
|
||||
if (settings_fs->exists(path) && !settings_fs->rename(path, backup)) return false;
|
||||
if (!settings_fs->rename(temporary, path)) {
|
||||
// A failed publish must not discard the last complete preference record.
|
||||
if (settings_fs->exists(backup) && !settings_fs->exists(path))
|
||||
settings_fs->rename(backup, path);
|
||||
return false;
|
||||
}
|
||||
if (!read(path, checked) || checked != mode) return false;
|
||||
if (settings_fs->exists(backup)) settings_fs->remove(backup);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* modeName(HeltecV4DisplayMode mode) {
|
||||
switch (mode) {
|
||||
case HeltecV4DisplayMode::Oled: return "oled";
|
||||
case HeltecV4DisplayMode::Tft: return "tft";
|
||||
case HeltecV4DisplayMode::Off: return "off";
|
||||
default: return "auto";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool loadHeltecV4DisplaySettings(FILESYSTEM* fs, HeltecV4Display& display) {
|
||||
settings_fs = fs;
|
||||
saved_mode = false;
|
||||
HeltecV4DisplayMode mode = HeltecV4DisplayMode::Auto;
|
||||
if (fs) {
|
||||
recover();
|
||||
saved_mode = read(path, mode) || read(backup, mode);
|
||||
}
|
||||
display.setMode(mode);
|
||||
return saved_mode;
|
||||
}
|
||||
|
||||
bool handleHeltecV4DisplayCommand(const char* command, char* reply,
|
||||
size_t reply_size, HeltecV4Display& display) {
|
||||
const bool get = strncmp(command, "get ", 4) == 0;
|
||||
if (!get && strncmp(command, "set ", 4) != 0) return false;
|
||||
constexpr char key[] = "display.type";
|
||||
constexpr size_t length = sizeof(key) - 1;
|
||||
if (strncmp(command + 4, key, length) != 0
|
||||
|| (command[4 + length] != 0 && command[4 + length] != ' ')) return false;
|
||||
const char* value = command + 4 + length;
|
||||
if (get) {
|
||||
if (*value) snprintf(reply, reply_size, "Error: unexpected argument");
|
||||
else snprintf(reply, reply_size, "display.type %s (active %s)",
|
||||
modeName(display.mode()), display.status());
|
||||
return true;
|
||||
}
|
||||
while (*value == ' ') ++value;
|
||||
HeltecV4DisplayMode mode;
|
||||
if (!strcmp(value, "auto")) mode = HeltecV4DisplayMode::Auto;
|
||||
else if (!strcmp(value, "oled")) mode = HeltecV4DisplayMode::Oled;
|
||||
else if (!strcmp(value, "tft")) mode = HeltecV4DisplayMode::Tft;
|
||||
else if (!strcmp(value, "off")) mode = HeltecV4DisplayMode::Off;
|
||||
else {
|
||||
snprintf(reply, reply_size, "Error: use set display.type <auto|oled|tft|off>");
|
||||
return true;
|
||||
}
|
||||
if ((!saved_mode || display.mode() != mode) && !save(mode)) {
|
||||
snprintf(reply, reply_size, "Error: display type save failed");
|
||||
return true;
|
||||
}
|
||||
saved_mode = true;
|
||||
display.setMode(mode);
|
||||
snprintf(reply, reply_size, "OK - display.type %s (active %s)",
|
||||
modeName(mode), display.status());
|
||||
return true;
|
||||
}
|
||||
|
||||
}} // namespace mesh::ui
|
||||
|
||||
#endif // HELTEC_V4_DISPLAY_UNIFIED
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include "HeltecV4Display.h"
|
||||
|
||||
namespace mesh { namespace ui {
|
||||
|
||||
// Load before the first display probe, including a saved Off selection.
|
||||
bool loadHeltecV4DisplaySettings(FILESYSTEM* fs, HeltecV4Display& display);
|
||||
bool handleHeltecV4DisplayCommand(const char* command, char* reply,
|
||||
size_t reply_size, HeltecV4Display& display);
|
||||
|
||||
}} // namespace mesh::ui
|
||||
@@ -21,6 +21,7 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) {
|
||||
}
|
||||
|
||||
// Color scheme
|
||||
#if !defined(HELTEC_V4_DISPLAY_UNIFIED) || !HELTEC_V4_DISPLAY_UNIFIED
|
||||
ColorVal UIColor::window_bkg = SSD1306_BLACK;
|
||||
ColorVal UIColor::title_bkg = SSD1306_BLACK;
|
||||
ColorVal UIColor::title_txt = SSD1306_WHITE;
|
||||
@@ -30,6 +31,7 @@ ColorVal UIColor::warning_txt = SSD1306_WHITE;
|
||||
ColorVal UIColor::popup_bkg = SSD1306_BLACK;
|
||||
ColorVal UIColor::popup_txt = SSD1306_WHITE;
|
||||
ColorVal UIColor::corp_blue = SSD1306_WHITE;
|
||||
#endif
|
||||
|
||||
bool SSD1306Display::begin() {
|
||||
if (!_isOn) {
|
||||
@@ -42,7 +44,7 @@ bool SSD1306Display::begin() {
|
||||
, mesh::ui::oledControllerMode()
|
||||
#endif
|
||||
)
|
||||
&& i2c_probe(Wire, DISPLAY_ADDRESS);
|
||||
&& i2c_probe(*_wire, DISPLAY_ADDRESS);
|
||||
if (started) {
|
||||
_panel_ready = true;
|
||||
applyRotation();
|
||||
|
||||
@@ -40,13 +40,14 @@ private:
|
||||
uint8_t _color;
|
||||
int _text_size = 1;
|
||||
RefCountedDigitalPin* _peripher_power;
|
||||
TwoWire* _wire;
|
||||
|
||||
bool i2c_probe(TwoWire& wire, uint8_t addr);
|
||||
void applyRotation();
|
||||
public:
|
||||
SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(PANEL_WIDTH, PANEL_HEIGHT),
|
||||
display(PANEL_WIDTH, PANEL_HEIGHT, &Wire, PIN_OLED_RESET),
|
||||
_peripher_power(peripher_power)
|
||||
SSD1306Display(RefCountedDigitalPin* peripher_power=NULL, TwoWire* wire=&Wire) : DisplayDriver(PANEL_WIDTH, PANEL_HEIGHT),
|
||||
display(PANEL_WIDTH, PANEL_HEIGHT, wire, PIN_OLED_RESET),
|
||||
_peripher_power(peripher_power), _wire(wire)
|
||||
{
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ const char meshcore_capability_display_autodetect_heltec_r8[] =
|
||||
// display.flip remains an additive 180-degree mounting adjustment, independent
|
||||
// of the selected orientation. Other boards keep their compiled orientation.
|
||||
uint8_t ST7789LCDDisplay::effectiveRotation() const {
|
||||
#ifdef MESHCORE_HELTEC_R8_RUNTIME_ROTATION
|
||||
#ifdef MESHCORE_HELTEC_TFT_RUNTIME_ROTATION
|
||||
return (uint8_t)((_base_rotation + (_flipped ? 2 : 0)) & 3);
|
||||
#else
|
||||
return (uint8_t)((DISPLAY_ROTATION + (_flipped ? 2 : 0)) & 3);
|
||||
@@ -183,7 +183,7 @@ uint8_t ST7789LCDDisplay::effectiveRotation() const {
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::applyRotation() {
|
||||
#ifdef HELTEC_V4_R8_TFT
|
||||
#if defined(HELTEC_V4_R8_TFT) || defined(HELTEC_V4_DISPLAY_UNIFIED)
|
||||
const auto rotation = effectiveRotation();
|
||||
// Layout follows saved intent immediately, including before initialization
|
||||
// and while the backlight is off. Defer all panel traffic until awake.
|
||||
@@ -198,9 +198,11 @@ void ST7789LCDDisplay::applyRotation() {
|
||||
}
|
||||
|
||||
bool ST7789LCDDisplay::setRotationDegrees(uint16_t degrees) {
|
||||
#ifdef MESHCORE_HELTEC_TFT_RUNTIME_ROTATION
|
||||
#ifdef MESHCORE_HELTEC_R8_RUNTIME_ROTATION
|
||||
const volatile char* capability = meshcore_capability_display_rotation_heltec_r8;
|
||||
(void)*capability;
|
||||
#endif
|
||||
if (degrees > 270 || degrees % 90 != 0) return false;
|
||||
const uint8_t rotation = (DISPLAY_ROTATION + degrees / 90) & 3;
|
||||
if (_base_rotation != rotation) {
|
||||
@@ -225,6 +227,7 @@ bool ST7789LCDDisplay::i2c_probe(TwoWire& wire, uint8_t addr) {
|
||||
}
|
||||
|
||||
// Color scheme
|
||||
#if !defined(HELTEC_V4_DISPLAY_UNIFIED) || !HELTEC_V4_DISPLAY_UNIFIED
|
||||
ColorVal UIColor::window_bkg = mesh::ui::color_theme::WINDOW_BACKGROUND;
|
||||
ColorVal UIColor::title_bkg = mesh::ui::color_theme::TITLE_BACKGROUND;
|
||||
ColorVal UIColor::title_txt = mesh::ui::color_theme::TEXT;
|
||||
@@ -234,6 +237,7 @@ ColorVal UIColor::warning_txt = mesh::ui::color_theme::WARNING_TEXT;
|
||||
ColorVal UIColor::popup_bkg = mesh::ui::color_theme::POPUP_BACKGROUND;
|
||||
ColorVal UIColor::popup_txt = mesh::ui::color_theme::TEXT;
|
||||
ColorVal UIColor::corp_blue = mesh::ui::color_theme::ACCENT;
|
||||
#endif
|
||||
|
||||
bool ST7789LCDDisplay::begin() {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
@@ -308,14 +312,26 @@ bool ST7789LCDDisplay::begin() {
|
||||
#if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT)
|
||||
displaySPI.begin(PIN_TFT_SCL, PIN_TFT_MISO, PIN_TFT_SDA, PIN_TFT_CS);
|
||||
#endif
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(MESHCORE_HELTEC_R8_TFT_DETECTION) || \
|
||||
(defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED)
|
||||
// Arduino-ESP32 2.x begin() returns void, so inspect its public bus handle
|
||||
// before handing an unavailable SPI peripheral to the panel library.
|
||||
if (!displaySPI.bus()) {
|
||||
displaySPI.end();
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
_present = false; // Latch failure just like an absent LCD.
|
||||
mesh::ui::heltec_r8::backlightOff();
|
||||
mesh::ui::heltec_r8::releaseBus();
|
||||
#else
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
pinMode(PIN_TFT_RST, OUTPUT);
|
||||
pinMode(PIN_TFT_CS, INPUT);
|
||||
pinMode(PIN_TFT_DC, INPUT);
|
||||
pinMode(PIN_TFT_SDA, INPUT);
|
||||
pinMode(PIN_TFT_SCL, INPUT);
|
||||
_panel_ready = false;
|
||||
#endif
|
||||
if (_peripher_power) _peripher_power->release();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ class TDeckCanvas;
|
||||
#define MESHCORE_HELTEC_R8_RUNTIME_ROTATION 1
|
||||
#endif
|
||||
|
||||
#if defined(MESHCORE_HELTEC_R8_RUNTIME_ROTATION) || \
|
||||
(defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED)
|
||||
#define MESHCORE_HELTEC_TFT_RUNTIME_ROTATION 1
|
||||
#endif
|
||||
|
||||
class ST7789LCDDisplay : public DisplayDriver {
|
||||
#if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT)
|
||||
SPIClass displaySPI;
|
||||
@@ -36,8 +41,8 @@ class ST7789LCDDisplay : public DisplayDriver {
|
||||
bool _probe_complete = false;
|
||||
bool _present = false;
|
||||
#endif
|
||||
#ifdef HELTEC_V4_R8_TFT
|
||||
#ifdef MESHCORE_HELTEC_R8_RUNTIME_ROTATION
|
||||
#if defined(HELTEC_V4_R8_TFT) || defined(HELTEC_V4_DISPLAY_UNIFIED)
|
||||
#ifdef MESHCORE_HELTEC_TFT_RUNTIME_ROTATION
|
||||
uint8_t _base_rotation = DISPLAY_ROTATION & 3;
|
||||
#endif
|
||||
bool _rotation_pending = false;
|
||||
@@ -78,7 +83,13 @@ public:
|
||||
}
|
||||
#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT)
|
||||
ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(kWidth, kHeight),
|
||||
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED
|
||||
// The V4 radio uses SPIClass's default HSPI controller. A separate
|
||||
// controller is required so TFT setup/sleep cannot remap or stop it.
|
||||
displaySPI(FSPI),
|
||||
#else
|
||||
displaySPI(HSPI),
|
||||
#endif
|
||||
display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST),
|
||||
_peripher_power(peripher_power)
|
||||
{
|
||||
@@ -106,8 +117,17 @@ public:
|
||||
#endif
|
||||
|
||||
bool isOn() override { return _isOn; }
|
||||
#ifdef MESHCORE_HELTEC_R8_RUNTIME_ROTATION
|
||||
#ifdef MESHCORE_HELTEC_TFT_RUNTIME_ROTATION
|
||||
bool supportsRotation() const override { return true; }
|
||||
#endif
|
||||
#if defined(HELTEC_V4_DISPLAY_UNIFIED) && HELTEC_V4_DISPLAY_UNIFIED
|
||||
// The combined V4 image releases the LCD SPI peripheral before the shared
|
||||
// GPIO17/18 pair is used by the OLED bus again.
|
||||
void releaseBus() {
|
||||
displaySPI.end();
|
||||
_panel_ready = false;
|
||||
_isOn = false;
|
||||
}
|
||||
#endif
|
||||
bool setRotationDegrees(uint16_t degrees) override;
|
||||
void turnOn() override;
|
||||
|
||||
Reference in new issue
Block a user