mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
244 lines
7.2 KiB
C++
244 lines
7.2 KiB
C++
#include "SH1106Display.h"
|
|
#include <string.h>
|
|
#include <Adafruit_GrayOLED.h>
|
|
#include "Adafruit_SH110X.h"
|
|
#include "MiscFixedRenderer.h"
|
|
|
|
bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr)
|
|
{
|
|
wire.beginTransmission(addr);
|
|
uint8_t error = wire.endTransmission();
|
|
return (error == 0);
|
|
}
|
|
|
|
// Color scheme
|
|
ColorVal UIColor::window_bkg = SH110X_BLACK;
|
|
ColorVal UIColor::title_bkg = SH110X_BLACK;
|
|
ColorVal UIColor::title_txt = SH110X_WHITE;
|
|
ColorVal UIColor::primary_txt = SH110X_WHITE;
|
|
ColorVal UIColor::secondary_txt = SH110X_WHITE;
|
|
ColorVal UIColor::warning_txt = SH110X_WHITE;
|
|
ColorVal UIColor::popup_bkg = SH110X_BLACK;
|
|
ColorVal UIColor::popup_txt = SH110X_WHITE;
|
|
ColorVal UIColor::corp_blue = SH110X_WHITE;
|
|
|
|
bool SH1106Display::begin()
|
|
{
|
|
// Wire must already be initialised by board.begin() before this is called.
|
|
// Boards with non-standard SH1106 addresses should define DISPLAY_ADDRESS
|
|
// in their variant/platformio configuration. The SA0 strap selects 0x3C or
|
|
// 0x3D and differs between revisions of the same board (e.g. T-Beam
|
|
// Supreme), so fall back to the other address of the pair.
|
|
uint8_t addr = 0;
|
|
if (i2c_probe(Wire, DISPLAY_ADDRESS)) {
|
|
addr = DISPLAY_ADDRESS;
|
|
} else if (i2c_probe(Wire, DISPLAY_ADDRESS ^ 1)) {
|
|
addr = DISPLAY_ADDRESS ^ 1;
|
|
}
|
|
// Run the Adafruit init even when no panel answered: it is what allocates
|
|
// the frame buffer and the I2C device. Skipping it leaves i2c_dev and
|
|
// spi_dev NULL, and UITask::begin() calls turnOn() regardless of our
|
|
// return value, which then dereferences the null spi_dev.
|
|
bool ok = display.begin(addr ? addr : DISPLAY_ADDRESS, true);
|
|
#ifdef DISPLAY_ROTATION
|
|
display.setRotation(DISPLAY_ROTATION);
|
|
#endif
|
|
return addr != 0 && ok;
|
|
}
|
|
|
|
void SH1106Display::turnOn()
|
|
{
|
|
display.oled_command(SH110X_DISPLAYON);
|
|
uint8_t pre[] = { 0xD9, _precharge };
|
|
display.oled_commandList(pre, 2);
|
|
display.setContrast(_contrast);
|
|
_isOn = true;
|
|
_force_redraw = true; // panel was off — guarantee the next endFrame() flushes
|
|
}
|
|
|
|
void SH1106Display::turnOff()
|
|
{
|
|
display.oled_command(SH110X_DISPLAYOFF);
|
|
_isOn = false;
|
|
}
|
|
|
|
void SH1106Display::clear()
|
|
{
|
|
display.clearDisplay();
|
|
display.display();
|
|
_force_redraw = true; // next endFrame() must flush even if its CRC matches a pre-clear frame
|
|
}
|
|
|
|
void SH1106Display::startFrame(ColorVal bkg)
|
|
{
|
|
display.clearDisplay(); // TODO: apply 'bkg'
|
|
_color = SH110X_WHITE;
|
|
display.setTextColor(_color);
|
|
display.setTextSize(1);
|
|
_text_sz = 1;
|
|
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
|
}
|
|
|
|
// The GFX buffer still holds the last frame sent until the next startFrame().
|
|
bool SH1106Display::slideBegin(int y0)
|
|
{
|
|
_slide_y0 = y0 < 0 ? 0 : (y0 > height() ? height() : y0);
|
|
memcpy(_slide_old, display.getBuffer(), sizeof(_slide_old));
|
|
return true;
|
|
}
|
|
|
|
// Buffer layout: one byte per column per 8-row page, bit n = row page*8+n.
|
|
void SH1106Display::slideCompose(int dx)
|
|
{
|
|
uint8_t* buf = display.getBuffer();
|
|
const int W = width();
|
|
int off = dx < 0 ? -dx : dx;
|
|
if (off > W) off = W;
|
|
uint8_t fresh[128];
|
|
for (int p = _slide_y0 / 8; p < height() / 8; p++) {
|
|
const uint8_t keep = (p == _slide_y0 / 8) ? (uint8_t)~(0xFF << (_slide_y0 & 7)) : 0;
|
|
uint8_t* cur = buf + p * W;
|
|
const uint8_t* old = _slide_old + p * W;
|
|
memcpy(fresh, cur, W);
|
|
for (int x = 0; x < W; x++) {
|
|
uint8_t v;
|
|
if (dx >= 0) v = x < W - off ? old[x + off] : fresh[x - (W - off)];
|
|
else v = x >= off ? old[x - off] : fresh[x + (W - off)];
|
|
cur[x] = (cur[x] & keep) | (v & ~keep);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SH1106Display::setTextSize(int sz)
|
|
{
|
|
_text_sz = sz;
|
|
_vw_dirty = true;
|
|
display.setTextSize(sz);
|
|
}
|
|
|
|
void SH1106Display::setColor(ColorVal c)
|
|
{
|
|
_color = c;
|
|
display.setTextColor(_color);
|
|
}
|
|
|
|
void SH1106Display::setCursor(int x, int y)
|
|
{
|
|
display.setCursor(x, y);
|
|
}
|
|
|
|
uint8_t SH1106Display::glyphXAdvance(uint32_t cp) {
|
|
return miscFixedXAdvance(cp, _text_sz);
|
|
}
|
|
|
|
void SH1106Display::translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) {
|
|
if (_single_font) {
|
|
size_t n = strlen(src);
|
|
if (n >= dest_size) n = dest_size - 1;
|
|
memcpy(dest, src, n);
|
|
dest[n] = '\0';
|
|
} else {
|
|
DisplayDriver::translateUTF8ToBlocks(dest, src, dest_size);
|
|
}
|
|
}
|
|
|
|
void SH1106Display::print(const char *str)
|
|
{
|
|
if (!_single_font) { display.print(str); return; }
|
|
miscFixedPrint(display, str, _text_sz, _color);
|
|
}
|
|
|
|
void SH1106Display::fillRect(int x, int y, int w, int h)
|
|
{
|
|
display.fillRect(x, y, w, h, _color);
|
|
}
|
|
|
|
void SH1106Display::drawRect(int x, int y, int w, int h)
|
|
{
|
|
display.drawRect(x, y, w, h, _color);
|
|
}
|
|
|
|
void SH1106Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h)
|
|
{
|
|
display.drawBitmap(x, y, bits, w, h, SH110X_WHITE);
|
|
}
|
|
|
|
uint16_t SH1106Display::getTextWidth(const char *str)
|
|
{
|
|
if (_single_font) return miscFixedTextWidth(str, _text_sz);
|
|
int16_t x1, y1;
|
|
uint16_t w, h;
|
|
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
|
|
return w;
|
|
}
|
|
|
|
void SH1106Display::setBrightness(uint8_t level)
|
|
{
|
|
// Contrast alone has limited effect on some OLED panels; combining with
|
|
// pre-charge period (0xD9) gives a wider perceptible dimming range.
|
|
// Pre-charge 0x11 = phase1=1,phase2=1 (minimum drive); 0x1F = default.
|
|
static const uint8_t contrast_values[] = { 0, 25, 60, 150, 255 };
|
|
static const uint8_t precharge_values[] = { 0x11, 0x15, 0x1F, 0x1F, 0x1F };
|
|
uint8_t idx = level < 5 ? level : 4;
|
|
_contrast = contrast_values[idx];
|
|
_precharge = precharge_values[idx];
|
|
uint8_t pre[] = { 0xD9, _precharge };
|
|
display.oled_commandList(pre, 2);
|
|
display.setContrast(_contrast);
|
|
}
|
|
|
|
#ifdef UI_PERF_L1
|
|
uint32_t g_disp_flushes = 0;
|
|
#endif
|
|
|
|
void SH1106Paged::sendBlock(uint8_t page, uint8_t col, uint8_t len) {
|
|
uint8_t c = col + _page_start_offset;
|
|
uint8_t cmd[] = { 0x00, (uint8_t)(SH110X_SETPAGEADDR + page), (uint8_t)(0x10 + (c >> 4)), (uint8_t)(c & 0xF) };
|
|
uint8_t dc_byte = 0x40;
|
|
const uint8_t* ptr = buffer + (uint16_t)page * WIDTH + col;
|
|
uint16_t maxbuff = i2c_dev->maxBufferSize() - 1;
|
|
i2c_dev->setSpeed(i2c_preclk);
|
|
i2c_dev->write(cmd, 4);
|
|
while (len) {
|
|
uint8_t n = len < maxbuff ? len : (uint8_t)maxbuff;
|
|
i2c_dev->write(ptr, n, true, &dc_byte, 1);
|
|
ptr += n; len -= n;
|
|
}
|
|
i2c_dev->setSpeed(i2c_postclk);
|
|
}
|
|
|
|
void SH1106Display::endFrame()
|
|
{
|
|
const uint8_t* buf = display.getBuffer();
|
|
const uint8_t per_page = 128 / BLOCK_W;
|
|
bool any = false;
|
|
bool dirty[BLOCKS];
|
|
for (uint8_t i = 0; i < BLOCKS; i++) {
|
|
const uint8_t* q = buf + (uint16_t)i * BLOCK_W; // blocks run along each page in buffer order
|
|
uint32_t h = 2166136261u;
|
|
for (uint8_t j = 0; j < BLOCK_W; j++) { h ^= q[j]; h *= 16777619u; }
|
|
dirty[i] = _force_redraw || h != _block_hash[i];
|
|
_block_hash[i] = h;
|
|
any |= dirty[i];
|
|
}
|
|
if (!any) return;
|
|
#ifdef UI_PERF_L1
|
|
g_disp_flushes++;
|
|
#endif
|
|
if (_force_redraw || !display.canSendBlocks()) {
|
|
_force_redraw = false;
|
|
display.display();
|
|
return;
|
|
}
|
|
for (uint8_t page = 0; page < 64 / 8; page++) {
|
|
// runs of neighbouring dirty blocks go out as one transfer
|
|
for (uint8_t b = 0; b < per_page; ) {
|
|
if (!dirty[page * per_page + b]) { b++; continue; }
|
|
uint8_t e = b;
|
|
while (e + 1 < per_page && dirty[page * per_page + e + 1]) e++;
|
|
display.sendBlock(page, b * BLOCK_W, (e - b + 1) * BLOCK_W);
|
|
b = e + 1;
|
|
}
|
|
}
|
|
}
|