mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-12-23 12:51:24 +01:00
390 lines
15 KiB
C++
390 lines
15 KiB
C++
/* 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
|
|
//------------------------------------------------------------------------------
|
|
|
|
// 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
|
|
|
|
};
|