/* 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 * . */ #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 //------------------------------------------------------------------------------ // Constructor SSD1306AsciiWire::SSD1306AsciiWire() { I2CManager.begin(); I2CManager.setClock(400000L); // Set max supported I2C speed } // CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { I2CManager.begin(); I2CManager.setClock(400000L); // Set max supported I2C speed // Probe for I2C device on 0x3c and 0x3d. for (uint8_t address = 0x3c; address <= 0x3d; address++) { if (I2CManager.exists(address)) { begin(address, width, height); // Set singleton Address so CS is able to call it. lcdDisplay = this; return; } } DIAG(F("OLED display not found")); } bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { if (m_initialised) return true; m_i2cAddr = address; m_displayWidth = width; m_displayHeight = height; // Set size in characters in base class lcdRows = height / 8; lcdCols = width / 6; 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 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:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); clear(); return true; } /* Clear screen by writing blank pixels. */ void SSD1306AsciiWire::clearNative() { const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire 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 - 1; c += maxBytes-1) { uint8_t len = m_displayWidth-c+1; if (len > maxBytes) len = maxBytes; I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of 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 (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; #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 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++) outputBuffer[bufferPos++] = GETFLASH(base++); // Add blank pixels between letters for (uint8_t i = 0; i < letterSpacing; i++) outputBuffer[bufferPos++] = 0; // Write the data to I2C display I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock); m_col += fontWidth + letterSpacing; return 1; } //------------------------------------------------------------------------------ // Font characters, 5x7 pixels, 0x61 characters starting at 0x20. // Lower case characters optionally omitted. const uint8_t FLASH SSD1306AsciiWire::System5x7[] = { // Fixed width; char width table not used !!!! // or with lowercase character omitted. // font data 0x00, 0x00, 0x00, 0x00, 0x00, // (space) 0x00, 0x00, 0x5F, 0x00, 0x00, // ! 0x00, 0x07, 0x00, 0x07, 0x00, // " 0x14, 0x7F, 0x14, 0x7F, 0x14, // # 0x24, 0x2A, 0x7F, 0x2A, 0x12, // $ 0x23, 0x13, 0x08, 0x64, 0x62, // % 0x36, 0x49, 0x55, 0x22, 0x50, // & 0x00, 0x05, 0x03, 0x00, 0x00, // ' 0x00, 0x1C, 0x22, 0x41, 0x00, // ( 0x00, 0x41, 0x22, 0x1C, 0x00, // ) 0x08, 0x2A, 0x1C, 0x2A, 0x08, // * 0x08, 0x08, 0x3E, 0x08, 0x08, // + 0x00, 0x50, 0x30, 0x00, 0x00, // , 0x08, 0x08, 0x08, 0x08, 0x08, // - 0x00, 0x60, 0x60, 0x00, 0x00, // . 0x20, 0x10, 0x08, 0x04, 0x02, // / 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0 0x00, 0x42, 0x7F, 0x40, 0x00, // 1 0x42, 0x61, 0x51, 0x49, 0x46, // 2 0x21, 0x41, 0x45, 0x4B, 0x31, // 3 0x18, 0x14, 0x12, 0x7F, 0x10, // 4 0x27, 0x45, 0x45, 0x45, 0x39, // 5 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6 0x01, 0x71, 0x09, 0x05, 0x03, // 7 0x36, 0x49, 0x49, 0x49, 0x36, // 8 0x06, 0x49, 0x49, 0x29, 0x1E, // 9 0x00, 0x36, 0x36, 0x00, 0x00, // : 0x00, 0x56, 0x36, 0x00, 0x00, // ; 0x00, 0x08, 0x14, 0x22, 0x41, // < 0x14, 0x14, 0x14, 0x14, 0x14, // = 0x41, 0x22, 0x14, 0x08, 0x00, // > 0x02, 0x01, 0x51, 0x09, 0x06, // ? 0x32, 0x49, 0x79, 0x41, 0x3E, // @ 0x7E, 0x11, 0x11, 0x11, 0x7E, // A 0x7F, 0x49, 0x49, 0x49, 0x36, // B 0x3E, 0x41, 0x41, 0x41, 0x22, // C 0x7F, 0x41, 0x41, 0x22, 0x1C, // D 0x7F, 0x49, 0x49, 0x49, 0x41, // E 0x7F, 0x09, 0x09, 0x01, 0x01, // F 0x3E, 0x41, 0x41, 0x51, 0x32, // G 0x7F, 0x08, 0x08, 0x08, 0x7F, // H 0x00, 0x41, 0x7F, 0x41, 0x00, // I 0x20, 0x40, 0x41, 0x3F, 0x01, // J 0x7F, 0x08, 0x14, 0x22, 0x41, // K 0x7F, 0x40, 0x40, 0x40, 0x40, // L 0x7F, 0x02, 0x04, 0x02, 0x7F, // M 0x7F, 0x04, 0x08, 0x10, 0x7F, // N 0x3E, 0x41, 0x41, 0x41, 0x3E, // O 0x7F, 0x09, 0x09, 0x09, 0x06, // P 0x3E, 0x41, 0x51, 0x21, 0x5E, // Q 0x7F, 0x09, 0x19, 0x29, 0x46, // R 0x46, 0x49, 0x49, 0x49, 0x31, // S 0x01, 0x01, 0x7F, 0x01, 0x01, // T 0x3F, 0x40, 0x40, 0x40, 0x3F, // U 0x1F, 0x20, 0x40, 0x20, 0x1F, // V 0x7F, 0x20, 0x18, 0x20, 0x7F, // W 0x63, 0x14, 0x08, 0x14, 0x63, // X 0x03, 0x04, 0x78, 0x04, 0x03, // Y 0x61, 0x51, 0x49, 0x45, 0x43, // Z 0x00, 0x00, 0x7F, 0x41, 0x41, // [ 0x02, 0x04, 0x08, 0x10, 0x20, // "\" 0x41, 0x41, 0x7F, 0x00, 0x00, // ] 0x04, 0x02, 0x01, 0x02, 0x04, // ^ 0x40, 0x40, 0x40, 0x40, 0x40, // _ 0x00, 0x01, 0x02, 0x04, 0x00, // ` #ifndef NOLOWERCASE 0x20, 0x54, 0x54, 0x54, 0x78, // a 0x7F, 0x48, 0x44, 0x44, 0x38, // b 0x38, 0x44, 0x44, 0x44, 0x20, // c 0x38, 0x44, 0x44, 0x48, 0x7F, // d 0x38, 0x54, 0x54, 0x54, 0x18, // e 0x08, 0x7E, 0x09, 0x01, 0x02, // f 0x08, 0x14, 0x54, 0x54, 0x3C, // g 0x7F, 0x08, 0x04, 0x04, 0x78, // h 0x00, 0x44, 0x7D, 0x40, 0x00, // i 0x20, 0x40, 0x44, 0x3D, 0x00, // j 0x00, 0x7F, 0x10, 0x28, 0x44, // k 0x00, 0x41, 0x7F, 0x40, 0x00, // l 0x7C, 0x04, 0x18, 0x04, 0x78, // m 0x7C, 0x08, 0x04, 0x04, 0x78, // n 0x38, 0x44, 0x44, 0x44, 0x38, // o 0x7C, 0x14, 0x14, 0x14, 0x08, // p 0x08, 0x14, 0x14, 0x18, 0x7C, // q 0x7C, 0x08, 0x04, 0x04, 0x08, // r 0x48, 0x54, 0x54, 0x54, 0x20, // s 0x04, 0x3F, 0x44, 0x40, 0x20, // t 0x3C, 0x40, 0x40, 0x20, 0x7C, // u 0x1C, 0x20, 0x40, 0x20, 0x1C, // v 0x3C, 0x40, 0x30, 0x40, 0x3C, // w 0x44, 0x28, 0x10, 0x28, 0x44, // x 0x0C, 0x50, 0x50, 0x50, 0x3C, // y 0x44, 0x64, 0x54, 0x4C, 0x44, // z #endif 0x00, 0x08, 0x36, 0x41, 0x00, // { 0x00, 0x00, 0x7F, 0x00, 0x00, // | 0x00, 0x41, 0x36, 0x08, 0x00, // } 0x08, 0x08, 0x2A, 0x1C, 0x08, // -> 0x08, 0x1C, 0x2A, 0x08, 0x08, // <- 0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol };