Files
MeshCore/src/helpers/ui/SSD1306Display.cpp
T
mikecarper b00d346df7 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.
2026-10-26 15:19:17 -07:00

179 lines
4.9 KiB
C++

#include "SSD1306Display.h"
#if defined(OLED_AUTO_DETECT) && OLED_AUTO_DETECT
#include "OLEDControllerSettings.h"
#endif
#if defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM)
// Adafruit_SSD1306::begin allocates WIDTH * ((HEIGHT + 7) / 8) once and
// reuses that framebuffer on later begin calls. The driver itself is embedded
// in the linked global display, not allocated on the heap. Even including
// that object again plus allocator overhead fits the 2 KiB runtime bound.
static_assert(SSD1306Display::FRAMEBUFFER_BYTES == 1024,
"Update the SSD1306 OLED framebuffer budget");
static_assert(sizeof(SSD1306Display) + SSD1306Display::FRAMEBUFFER_BYTES + 32 <= 2048,
"Update the SSD1306 OLED runtime budget");
#endif
bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) {
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
return (error == 0);
}
// 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;
ColorVal UIColor::primary_txt = SSD1306_WHITE;
ColorVal UIColor::secondary_txt = SSD1306_WHITE;
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) {
if (_peripher_power) _peripher_power->claim();
_isOn = true;
}
const bool started = display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS,
true, false
#if defined(OLED_AUTO_DETECT) && OLED_AUTO_DETECT
, mesh::ui::oledControllerMode()
#endif
)
&& i2c_probe(*_wire, DISPLAY_ADDRESS);
if (started) {
_panel_ready = true;
applyRotation();
} else {
_panel_ready = false;
if (_peripher_power) {
#if PIN_OLED_RESET >= 0
digitalWrite(PIN_OLED_RESET, LOW);
#endif
// begin() owns one claim even when it was already on, or turnOn()
// reached it after a previous failure. Other peripheral owners remain.
_peripher_power->release();
}
_isOn = false;
}
return started;
}
void SSD1306Display::applyRotation() {
display.setRotation((_base_rotation + (_flipped ? 2 : 0)) & 3);
setDimensions(display.width(), display.height());
display.clearDisplay();
display.display();
}
bool SSD1306Display::setRotationDegrees(uint16_t degrees) {
uint8_t rotation;
switch (degrees) {
case 0:
#ifdef DISPLAY_ROTATION
rotation = DISPLAY_ROTATION & 3;
#else
rotation = 0;
#endif
break;
case 90: rotation = 1; break;
case 180: rotation = 2; break;
case 270: rotation = 3; break;
default: return false;
}
_base_rotation = rotation;
if (_panel_ready) applyRotation();
return true;
}
void SSD1306Display::setFlipped(bool flipped) {
if (_flipped == flipped) return;
_flipped = flipped;
if (_panel_ready) applyRotation();
}
void SSD1306Display::turnOn() {
if (!_isOn) {
if (_peripher_power || !_panel_ready) {
if (!begin()) return; // begin owns and rolls back its claim on failure
} else {
_isOn = true; // A continuously powered, initialized panel needs no reset.
}
}
display.ssd1306_command(SSD1306_DISPLAYON);
}
void SSD1306Display::turnOff() {
display.ssd1306_command(SSD1306_DISPLAYOFF);
if (_isOn) {
if (_peripher_power) {
#if PIN_OLED_RESET >= 0
digitalWrite(PIN_OLED_RESET, LOW);
#endif
_peripher_power->release();
_panel_ready = false;
}
_isOn = false;
}
}
void SSD1306Display::clear() {
display.clearDisplay();
display.display();
}
void SSD1306Display::startFrame(ColorVal bkg) {
display.clearDisplay(); // TODO: apply 'bkg'
_color = SSD1306_WHITE;
display.setTextColor(_color);
setTextSize(1);
display.setTextWrap(false); // Layout/ellipsis owns wrapping and row spacing.
display.cp437(true); // Use full 256 char 'Code Page 437' font
}
void SSD1306Display::setTextSize(int sz) {
_text_size = sz > 0 ? sz : 1;
display.setTextSize(_text_size);
}
void SSD1306Display::setColor(ColorVal c) {
_color = c;
display.setTextColor(_color);
}
void SSD1306Display::setCursor(int x, int y) {
display.setCursor(x, y);
}
void SSD1306Display::print(const char* str) {
display.print(str);
}
void SSD1306Display::fillRect(int x, int y, int w, int h) {
display.fillRect(x, y, w, h, _color);
}
void SSD1306Display::drawRect(int x, int y, int w, int h) {
display.drawRect(x, y, w, h, _color);
}
void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display.drawBitmap(x, y, bits, w, h, _color);
}
uint16_t SSD1306Display::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
}
void SSD1306Display::endFrame() {
display.display();
}