/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
 * Modifications (C) 2021 Neil McKechnie
 *
 *  This file is part of CommandStation-EX
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino SSD1306Ascii Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
#include "SSD1306Ascii.h"
#include "I2CManager.h"
#include "FSH.h"

//==============================================================================
// SSD1306/SSD1106 I2C command bytes
//------------------------------------------------------------------------------
/** Set Lower Column Start Address for Page Addressing Mode. */
static const uint8_t SSD1306_SETLOWCOLUMN = 0x00;
/** Set Higher Column Start Address for Page Addressing Mode. */
static const uint8_t SSD1306_SETHIGHCOLUMN = 0x10;
/** Set Memory Addressing Mode. */
static const uint8_t SSD1306_MEMORYMODE = 0x20;
/** Set display RAM display start line register from 0 - 63. */
static const uint8_t SSD1306_SETSTARTLINE = 0x40;
/** Set Display Contrast to one of 256 steps. */
static const uint8_t SSD1306_SETCONTRAST = 0x81;
/** Enable or disable charge pump.  Follow with 0X14 enable, 0X10 disable. */
static const uint8_t SSD1306_CHARGEPUMP = 0x8D;
/** Set Segment Re-map between data column and the segment driver. */
static const uint8_t SSD1306_SEGREMAP = 0xA0;
/** Resume display from GRAM content. */
static const uint8_t SSD1306_DISPLAYALLON_RESUME = 0xA4;
/** Force display on regardless of GRAM content. */
static const uint8_t SSD1306_DISPLAYALLON = 0xA5;
/** Set Normal Display. */
static const uint8_t SSD1306_NORMALDISPLAY = 0xA6;
/** Set Inverse Display. */
static const uint8_t SSD1306_INVERTDISPLAY = 0xA7;
/** Set Multiplex Ratio from 16 to 63. */
static const uint8_t SSD1306_SETMULTIPLEX = 0xA8;
/** Set Display off. */
static const uint8_t SSD1306_DISPLAYOFF = 0xAE;
/** Set Display on. */
static const uint8_t SSD1306_DISPLAYON = 0xAF;
/**Set GDDRAM Page Start Address. */
static const uint8_t SSD1306_SETSTARTPAGE = 0xB0;
/** Set COM output scan direction normal. */
static const uint8_t SSD1306_COMSCANINC = 0xC0;
/** Set COM output scan direction reversed. */
static const uint8_t SSD1306_COMSCANDEC = 0xC8;
/** Set Display Offset. */
static const uint8_t SSD1306_SETDISPLAYOFFSET = 0xD3;
/** Sets COM signals pin configuration to match the OLED panel layout. */
static const uint8_t SSD1306_SETCOMPINS = 0xDA;
/** This command adjusts the VCOMH regulator output. */
static const uint8_t SSD1306_SETVCOMDETECT = 0xDB;
/** Set Display Clock Divide Ratio/ Oscillator Frequency. */
static const uint8_t SSD1306_SETDISPLAYCLOCKDIV = 0xD5;
/** Set Pre-charge Period */
static const uint8_t SSD1306_SETPRECHARGE = 0xD9;
/** Deactivate scroll */
static const uint8_t SSD1306_DEACTIVATE_SCROLL = 0x2E;
/** No Operation Command. */
static const uint8_t SSD1306_NOP = 0xE3;
//------------------------------------------------------------------------------
/** Set Pump voltage value: (30H~33H) 6.4, 7.4, 8.0 (POR), 9.0. */
static const uint8_t SH1106_SET_PUMP_VOLTAGE = 0x30;
/** First byte of set charge pump mode */
static const uint8_t SH1106_SET_PUMP_MODE = 0xAD;
/** Second byte charge pump on. */
static const uint8_t SH1106_PUMP_ON = 0x8B;
/** Second byte charge pump off. */
static const uint8_t SH1106_PUMP_OFF = 0x8A;
//------------------------------------------------------------------------------

// Sequence of blank pixels, to optimise clearing screen.
// Send a maximum of 30 pixels per transmission.
const uint8_t FLASH SSD1306AsciiWire::blankPixels[30] = 
  {0x40,        // First byte specifies data mode
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};  


//==============================================================================
// this section is based on https://github.com/adafruit/Adafruit_SSD1306

/** Initialization commands for a 128x32 or 128x64 SSD1306 oled display. */
const uint8_t FLASH SSD1306AsciiWire::Adafruit128xXXinit[] = {
    // Init sequence for Adafruit 128x32/64 OLED module
    0x00,                              // Set to command mode
    SSD1306_DISPLAYOFF,
    SSD1306_SETDISPLAYCLOCKDIV, 0x80,  // the suggested ratio 0x80 
    SSD1306_SETMULTIPLEX, 0x3F,        // ratio 64 (initially)
    SSD1306_SETDISPLAYOFFSET, 0x0,     // no offset
    SSD1306_SETSTARTLINE | 0x0,        // line #0
    SSD1306_CHARGEPUMP, 0x14,          // internal vcc
    SSD1306_MEMORYMODE, 0x02,          // page mode
    SSD1306_SEGREMAP | 0x1,            // column 127 mapped to SEG0
    SSD1306_COMSCANDEC,                // column scan direction reversed
    SSD1306_SETCOMPINS, 0X12,          // set COM pins
    SSD1306_SETCONTRAST, 0x7F,         // contrast level 127
    SSD1306_SETPRECHARGE, 0xF1,        // pre-charge period (1, 15)
    SSD1306_SETVCOMDETECT, 0x40,       // vcomh regulator level
    SSD1306_DISPLAYALLON_RESUME,
    SSD1306_NORMALDISPLAY,
    SSD1306_DISPLAYON
};

//------------------------------------------------------------------------------
// This section is based on https://github.com/stanleyhuangyc/MultiLCD

/** Initialization commands for a 128x64 SH1106 oled display. */
const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
  0x00,                                  // Set to command mode
  SSD1306_DISPLAYOFF,
  SSD1306_SETDISPLAYCLOCKDIV, 0X80,      // set osc division
  SSD1306_SETMULTIPLEX, 0x3F,            // ratio 64
  SSD1306_SETDISPLAYOFFSET, 0X00,        // set display offset
  SSD1306_SETSTARTPAGE | 0X0,            // set page address
  SSD1306_SETSTARTLINE | 0x0,            // set start line
  SH1106_SET_PUMP_MODE, SH1106_PUMP_ON,  // set charge pump enable
  SSD1306_SEGREMAP | 0X1,                // set segment remap
  SSD1306_COMSCANDEC,                    // Com scan direction
  SSD1306_SETCOMPINS, 0X12,              // set COM pins
  SSD1306_SETCONTRAST, 0x80,             // 128
  SSD1306_SETPRECHARGE, 0X1F,            // set pre-charge period
  SSD1306_SETVCOMDETECT,  0x40,          // set vcomh
  SH1106_SET_PUMP_VOLTAGE | 0X2,         // 8.0 volts
  SSD1306_NORMALDISPLAY,                 // normal / reverse
  SSD1306_DISPLAYON
};

//==============================================================================
// SSD1306AsciiWire Method Definitions
//------------------------------------------------------------------------------
 
// Auto-detect address
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) 
  : SSD1306AsciiWire(0, width, height) { }

// Constructor with explicit address
SSD1306AsciiWire::SSD1306AsciiWire(I2CAddress address, int width, int height) {
  m_i2cAddr = address;
  m_displayWidth = width;
  m_displayHeight = height;
  // Set size in characters
  m_charsPerColumn = m_displayHeight / fontHeight;
  m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up
}

bool SSD1306AsciiWire::begin() {
  I2CManager.begin();
  I2CManager.setClock(400000L);  // Set max supported I2C speede

  if (m_i2cAddr == 0) {
    // Probe for I2C device on 0x3c and 0x3d.
    for (uint8_t address = 0x3c; address <= 0x3d; address++) {
      if (I2CManager.exists(address)) {
        m_i2cAddr = address;
        break;
      }
    }
    if (m_i2cAddr == 0)
      DIAG(F("OLED display not found"));
  }

  m_col = 0;
  m_row = 0;
  m_colOffset = 0;

  if (m_displayWidth==132 && m_displayHeight==64) {
    // SH1106 display.  This uses 128x64 centered within a 132x64 OLED.
    m_colOffset = 2;
    I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init));
  } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) {
    // SSD1306 or SSD1309 128x64 or 128x32
    I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit));
    if (m_displayHeight == 32) 
      I2CManager.write(m_i2cAddr, 5, 0, // Set command mode
        SSD1306_SETMULTIPLEX, 0x1F,     // ratio 32
        SSD1306_SETCOMPINS, 0x02);      // sequential COM pins, disable remap
  } else {
    DIAG(F("OLED configuration option not recognised"));
    return false;
  }
  // Device found
  DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString());
  return true;
}

/* Clear screen by writing blank pixels. */
void SSD1306AsciiWire::clearNative() {
  const int maxBytes = sizeof(blankPixels) - 1;  // max number of pixel columns (bytes) per transmission
  for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) {
    setRowNative(r);   // Position at start of row to be erased
    for (uint8_t c = 0; c < m_displayWidth; c += maxBytes) {
      uint8_t len = m_displayWidth-c;  // Number of pixel columns remaining
      if (len > maxBytes) len = maxBytes;
      I2CManager.write_P(m_i2cAddr, blankPixels, len+1);  // Write command + 'len' blank columns
    }
  }
}

//------------------------------------------------------------------------------

// Set cursor position (by text line)
void SSD1306AsciiWire::setRowNative(uint8_t line) {
  // Calculate pixel position from line number
  uint8_t row = line*8;
  if (row < m_displayHeight) {
    m_row = row;
    m_col = m_colOffset;
    // Before using buffer, wait for last request to complete
    requestBlock.wait();
    // Build output buffer for I2C
    uint8_t len = 0;
    outputBuffer[len++] = 0x00;  // Set to command mode
    outputBuffer[len++] = SSD1306_SETLOWCOLUMN | (m_col & 0XF);
    outputBuffer[len++] = SSD1306_SETHIGHCOLUMN | (m_col >> 4);
    outputBuffer[len++] = SSD1306_SETSTARTPAGE | (m_row/8);
    I2CManager.write(m_i2cAddr, outputBuffer, len, &requestBlock);
  }
}
//------------------------------------------------------------------------------

// Write a character to the OLED
size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
  const uint8_t* base = m_font;

#if defined(NOLOWERCASE)
  // Adjust if lowercase is missing
  if (ch >= 'a') {
    if (ch <= 'z')
      ch = ch - 'a' + 'A';  // Capitalise
    else
      ch -= 26; // Allow for missing lowercase letters
  }
#endif
  if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
    return 0;
  // Check if character would be partly or wholly off the display
  if (m_col + fontWidth > m_displayWidth)
    return 0;

  ch -= m_fontFirstChar;
  base += fontWidth * ch;
  // Before using buffer, wait for last request to complete
  requestBlock.wait();
  // Build output buffer for I2C
  outputBuffer[0] = 0x40;     // set SSD1306 controller to data mode
  uint8_t bufferPos = 1;
  // Copy character pixel columns
  for (uint8_t i = 0; i < fontWidth; i++) {
    if (m_col++ < m_displayWidth) 
      outputBuffer[bufferPos++] = GETFLASH(base++);
  }

  // Write the data to I2C display
  I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock);
  return 1;
}


//------------------------------------------------------------------------------

// Font characters, 6x8 pixels, starting at 0x20.
// Lower case characters optionally omitted.
const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {

    // Fixed width; char width table not used !!!!

    // font data
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // (space) (20)
    0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,  // ! (21)
    0x00, 0x07, 0x00, 0x07, 0x00, 0x00,  // "
    0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00,  // #
    0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00,  // $
    0x23, 0x13, 0x08, 0x64, 0x62, 0x00,  // %
    0x36, 0x49, 0x55, 0x22, 0x50, 0x00,  // &
    0x00, 0x05, 0x03, 0x00, 0x00, 0x00,  // '
    0x00, 0x1C, 0x22, 0x41, 0x00, 0x00,  // (
    0x00, 0x41, 0x22, 0x1C, 0x00, 0x00,  // )
    0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00,  // *
    0x08, 0x08, 0x3E, 0x08, 0x08, 0x00,  // +
    0x00, 0x50, 0x30, 0x00, 0x00, 0x00,  // ,
    0x08, 0x08, 0x08, 0x08, 0x08, 0x00,  // -
    0x00, 0x60, 0x60, 0x00, 0x00, 0x00,  // .
    0x20, 0x10, 0x08, 0x04, 0x02, 0x00,  // / (47)
    0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00,  // 0 (48)
    0x00, 0x42, 0x7F, 0x40, 0x00, 0x00,  // 1
    0x42, 0x61, 0x51, 0x49, 0x46, 0x00,  // 2
    0x21, 0x41, 0x45, 0x4B, 0x31, 0x00,  // 3
    0x18, 0x14, 0x12, 0x7F, 0x10, 0x00,  // 4
    0x27, 0x45, 0x45, 0x45, 0x39, 0x00,  // 5
    0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00,  // 6
    0x01, 0x71, 0x09, 0x05, 0x03, 0x00,  // 7
    0x36, 0x49, 0x49, 0x49, 0x36, 0x00,  // 8
    0x06, 0x49, 0x49, 0x29, 0x1E, 0x00,  // 9 (57)
    0x00, 0x36, 0x36, 0x00, 0x00, 0x00,  // :
    0x00, 0x56, 0x36, 0x00, 0x00, 0x00,  // ;
    0x00, 0x08, 0x14, 0x22, 0x41, 0x00,  // <
    0x14, 0x14, 0x14, 0x14, 0x14, 0x00,  // =
    0x41, 0x22, 0x14, 0x08, 0x00, 0x00,  // >
    0x02, 0x01, 0x51, 0x09, 0x06, 0x00,  // ?
    0x32, 0x49, 0x79, 0x41, 0x3E, 0x00,  // @ (64)
    0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00,  // A (65)
    0x7F, 0x49, 0x49, 0x49, 0x36, 0x00,  // B
    0x3E, 0x41, 0x41, 0x41, 0x22, 0x00,  // C
    0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00,  // D
    0x7F, 0x49, 0x49, 0x49, 0x41, 0x00,  // E
    0x7F, 0x09, 0x09, 0x01, 0x01, 0x00,  // F
    0x3E, 0x41, 0x41, 0x51, 0x32, 0x00,  // G
    0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00,  // H
    0x00, 0x41, 0x7F, 0x41, 0x00, 0x00,  // I
    0x20, 0x40, 0x41, 0x3F, 0x01, 0x00,  // J
    0x7F, 0x08, 0x14, 0x22, 0x41, 0x00,  // K
    0x7F, 0x40, 0x40, 0x40, 0x40, 0x00,  // L
    0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00,  // M
    0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00,  // N
    0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00,  // O
    0x7F, 0x09, 0x09, 0x09, 0x06, 0x00,  // P
    0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00,  // Q
    0x7F, 0x09, 0x19, 0x29, 0x46, 0x00,  // R
    0x46, 0x49, 0x49, 0x49, 0x31, 0x00,  // S
    0x01, 0x01, 0x7F, 0x01, 0x01, 0x00,  // T
    0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00,  // U
    0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00,  // V
    0x7F, 0x20, 0x18, 0x20, 0x7F, 0x00,  // W
    0x63, 0x14, 0x08, 0x14, 0x63, 0x00,  // X
    0x03, 0x04, 0x78, 0x04, 0x03, 0x00,  // Y
    0x61, 0x51, 0x49, 0x45, 0x43, 0x00,  // Z (90)
    0x00, 0x00, 0x7F, 0x41, 0x41, 0x00,  // [
    0x02, 0x04, 0x08, 0x10, 0x20, 0x00,  // "\"
    0x41, 0x41, 0x7F, 0x00, 0x00, 0x00,  // ]
    0x04, 0x02, 0x01, 0x02, 0x04, 0x00,  // ^
    0x40, 0x40, 0x40, 0x40, 0x40, 0x00,  // _
    0x00, 0x01, 0x02, 0x04, 0x00, 0x00,  // ' (96)
#ifndef NOLOWERCASE
    0x20, 0x54, 0x54, 0x54, 0x78, 0x00,  // a (97)
    0x7F, 0x48, 0x44, 0x44, 0x38, 0x00,  // b
    0x38, 0x44, 0x44, 0x44, 0x20, 0x00,  // c
    0x38, 0x44, 0x44, 0x48, 0x7F, 0x00,  // d
    0x38, 0x54, 0x54, 0x54, 0x18, 0x00,  // e
    0x08, 0x7E, 0x09, 0x01, 0x02, 0x00,  // f
    0x08, 0x14, 0x54, 0x54, 0x3C, 0x00,  // g
    0x7F, 0x08, 0x04, 0x04, 0x78, 0x00,  // h
    0x00, 0x44, 0x7D, 0x40, 0x00, 0x00,  // i
    0x20, 0x40, 0x44, 0x3D, 0x00, 0x00,  // j
    0x00, 0x7F, 0x10, 0x28, 0x44, 0x00,  // k
    0x00, 0x41, 0x7F, 0x40, 0x00, 0x00,  // l
    0x7C, 0x04, 0x18, 0x04, 0x78, 0x00,  // m
    0x7C, 0x08, 0x04, 0x04, 0x78, 0x00,  // n
    0x38, 0x44, 0x44, 0x44, 0x38, 0x00,  // o
    0x7C, 0x14, 0x14, 0x14, 0x08, 0x00,  // p
    0x08, 0x14, 0x14, 0x18, 0x7C, 0x00,  // q
    0x7C, 0x08, 0x04, 0x04, 0x08, 0x00,  // r
    0x48, 0x54, 0x54, 0x54, 0x20, 0x00,  // s
    0x04, 0x3F, 0x44, 0x40, 0x20, 0x00,  // t
    0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00,  // u
    0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00,  // v
    0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00,  // w
    0x44, 0x28, 0x10, 0x28, 0x44, 0x00,  // x
    0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00,  // y
    0x44, 0x64, 0x54, 0x4C, 0x44, 0x00,  // z (122)
#endif
    0x00, 0x08, 0x36, 0x41, 0x00, 0x00,  // { (123)
    0x00, 0x00, 0x7F, 0x00, 0x00, 0x00,  // |
    0x00, 0x41, 0x36, 0x08, 0x00, 0x00,  // }
    0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00,  // ->
    0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00,  // <- (127)
#ifndef NO_EXTENDED_CHARACTERS
// Extended characters - based on "DOS Western Europe" characters
//   International characters not yet implemented.
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented 0x80
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented 0x90
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x38, 0x44, 0xc6, 0x44, 0x20, 0x00,  // cent 0x9b
    0x44, 0x6e, 0x59, 0x49, 0x62, 0x00,  // £ 0x9c
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented 0xa0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x10, 0x28, 0x54, 0x28, 0x44, 0x00,  // <<
    0x44, 0x28, 0x54, 0x28, 0x10, 0x00,  // >>
    // Extended characters 176-180
    0x92, 0x00, 0x49, 0x00, 0x24, 0x00,  // Light grey 0xb0
    0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x55,  // Mid grey 0xb1
    0x6d, 0xff, 0xb6, 0xff, 0xdb, 0xff,  // Dark grey 0xb2
    0x00, 0x00, 0x00, 0xff, 0x00, 0x00,  // Vertical line 0xb3
    0x08, 0x08, 0x08, 0xff, 0x00, 0x00,  // Vertical line with left spur 0xb4

    0x14, 0x14, 0x14, 0xff, 0x00, 0x00,  // Vertical line with double left spur 0xb5
    0x08, 0x08, 0xff, 0x00, 0xff, 0x00,  // Double vertical line with single left spur
    0x08, 0x08, 0xf8, 0x08, 0xf8, 0x00,  // Top right corner, single horiz, double vert
    0x14, 0x14, 0x14, 0xfc, 0x00, 0x00,  // Top right corner, double horiz, single vert

    // Extended characters 185-190
    0x14, 0x14, 0xf7, 0x00, 0xff, 0x00,  // Double vertical line with double left spur 0xb9
    0x00, 0x00, 0xff, 0x00, 0xff, 0x00,  // Double vertical line 0xba
    0x14, 0x14, 0xf4, 0x04, 0xfc, 0x00,  // Double top right corner 0xbb
    0x14, 0x14, 0x17, 0x10, 0x1f, 0x00,  // Double bottom right corner 0xbc
    0x08, 0x08, 0x0f, 0x08, 0x0f, 0x00,  // Bottom right corner, single horiz, double vert 0xbd
    0x14, 0x14, 0x14, 0x1f, 0x00, 0x00,  // Bottom right corner, double horiz, single vert 0xbe

    // Extended characters 191-199
    0x08, 0x08, 0x08, 0xf8, 0x00, 0x00,  // Top right corner 0xbf
    0x00, 0x00, 0x00, 0x0f, 0x08, 0x08,  // Bottom left corner 0xc0
    0x08, 0x08, 0x08, 0x0f, 0x08, 0x08,  // Horizontal line with upward spur 0xc1
    0x08, 0x08, 0x08, 0xf8, 0x08, 0x08,  // Horizontal line with downward spur 0xc2
    0x00, 0x00, 0x00, 0xff, 0x08, 0x08,  // Vertical line with right spur 0xc3
    0x08, 0x08, 0x08, 0x08, 0x08, 0x08,  // Horizontal line 0xc4
    0x08, 0x08, 0x08, 0xff, 0x08, 0x08,  // Cross 0xc5
    0x00, 0x00, 0x00, 0xff, 0x14, 0x14,  // Vertical line double right spur 0xc6
    0x00, 0x00, 0xff, 0x00, 0xff, 0x08,  // Double vertical line single right spur 0xc7

    // Extended characters 200-206
    0x00, 0x00, 0x1f, 0x10, 0x17, 0x14,  // Double bottom left corner 0xc8
    0x00, 0x00, 0xfc, 0x04, 0xf4, 0x14,  // Double top left corner 0xc9
    0x14, 0x14, 0x17, 0x10, 0x17, 0x14,  // Double horizontal with double upward spur 0xca
    0x14, 0x14, 0xf4, 0x04, 0xf4, 0x14,  // Double horizontal with double downward spur 0xcb
    0x00, 0x00, 0xff, 0x00, 0xf7, 0x14,  // Double vertical line with double right spur 0xcc
    0x14, 0x14, 0x14, 0x14, 0x14, 0x14,  // Double horizontal line 0xcd
    0x14, 0x14, 0xf7, 0x00, 0xf7, 0x14,  // Double cross 0xce

    0x14, 0x14, 0x14, 0x17, 0x14, 0x14,  // Double horizontal line single upward spur 0xcf
    0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08,  // Horiz single line with double upward spur 0xd0
    0x14, 0x14, 0x14, 0xf4, 0x14, 0x14,  // Horiz double line with single downward spur 0xd1
    0x08, 0x08, 0xf8, 0x08, 0xf8, 0x08,  // Horiz single line with double downward spur 0xd2
    0x00, 0x00, 0x0f, 0x08, 0x0f, 0x08,  // Bottom left corner, double vert single horiz 0xd3
    0x00, 0x00, 0x00, 0x1f, 0x14, 0x14,  // Bottom left corner, single vert double horiz 0xd4
    0x00, 0x00, 0x00, 0xfc, 0x14, 0x14,  // Top left corner, single vert double horiz 0xd5
    0x00, 0x00, 0xf8, 0x08, 0xf8, 0x08,  // Top left corner, double vert single horiz 0xd6
    0x08, 0x08, 0xff, 0x00, 0xff, 0x08,  // Cross, double vert single horiz 0xd7
    0x14, 0x14, 0x14, 0xf7, 0x14, 0x14,  // Cross, single vert double horiz 0xd8

    // Extended characters 217-223
    0x08, 0x08, 0x08, 0x0f, 0x00, 0x00,  // Bottom right corner 0xd9
    0x00, 0x00, 0x00, 0xf8, 0x08, 0x08,  // Top left corner 0xda
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // Solid block 0xdb
    0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,  // Bottom half block 0xdc
    0xff, 0xff, 0xff, 0x00, 0x00, 0x00,  // Left half block 0xdd
    0x00, 0x00, 0x00, 0xff, 0xff, 0xff,  // Right half block 0xde
    0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,  // Top half block 0xdf

    0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00,  // Bottom Left block 0xe0
    0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0,  // Bottom Right block
    0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00,  // Top left block 
    0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f,  // Top right block 0xe3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented 0xf0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // Not implemented
    // Extended character 248
    0x00, 0x06, 0x09, 0x09, 0x06, 0x00   // degree symbol 0xf8
#endif
};

const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6;