mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-12-23 12:51:24 +01:00
First stab at HAL-installable OLED Display Driver.
This commit is contained in:
parent
0c88c74706
commit
7b79680de2
178
IO_OLEDDisplay.h
Normal file
178
IO_OLEDDisplay.h
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* It 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 CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This driver provides a more immediate interface into the OLED display
|
||||
* than the one installed through the config.h file. When an LCD(...) call
|
||||
* is made, the text is output immediately to the specified display line,
|
||||
* without waiting for the next 2.5 second refresh. However, no scrolling
|
||||
* takes place, so if the line specified is off the screen then the text
|
||||
* will instead be shown on the bottom line of the screen.
|
||||
*
|
||||
* To install, use the following command in myHal.cpp:
|
||||
|
||||
* OLEDDisplay::create(address, width, height);
|
||||
*
|
||||
* where address is the I2C address (0x3c or 0x3d),
|
||||
* width is the width in pixels of the display, and
|
||||
* height is the height in pixels of the display.
|
||||
*
|
||||
* Valid width and height are 128x32 (SSD1306 controller),
|
||||
* 128x64 (SSD1306) and 132x64 (SH1106). The driver uses
|
||||
* a 5x7 character set in a 6x8 pixel cell.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef IO_OLEDDISPLAY_H
|
||||
#define IO_OLEDDISPLAY_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "DisplayInterface.h"
|
||||
#include "SSD1306Ascii.h"
|
||||
#include "version.h"
|
||||
|
||||
class OLEDDisplay : public IODevice, DisplayInterface {
|
||||
private:
|
||||
// Here we define the device-specific variables.
|
||||
uint8_t _height; // in pixels
|
||||
uint8_t _width; // in pixels
|
||||
SSD1306AsciiWire *oled;
|
||||
uint8_t _rowNo = 0; // Row number being written by caller
|
||||
uint8_t _colNo = 0; // Position in line being written by caller
|
||||
uint8_t _numRows;
|
||||
uint8_t _numCols;
|
||||
char *_buffer = NULL;
|
||||
uint8_t *_rowGeneration = NULL;
|
||||
uint8_t *_lastRowGeneration = NULL;
|
||||
uint8_t _rowNoToScreen = 0;
|
||||
uint8_t _charPosToScreen = 0;
|
||||
|
||||
public:
|
||||
// Static function to handle "OLEDDisplay::create(...)" calls.
|
||||
static void create(I2CAddress i2cAddress, int width = 128, int height=64) {
|
||||
/* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(i2cAddress, width, height);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor
|
||||
OLEDDisplay(I2CAddress i2cAddress, int width, int height) {
|
||||
_I2CAddress = i2cAddress;
|
||||
_width = width;
|
||||
_height = height;
|
||||
_numCols = _width / 6; // character block 6 x 8
|
||||
_numRows = _height / 8;
|
||||
|
||||
// Allocate arrays
|
||||
_buffer = (char *)calloc(_numRows*_numCols, sizeof(char));
|
||||
_rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
|
||||
_lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
|
||||
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
// Create OLED driver
|
||||
oled = new SSD1306AsciiWire();
|
||||
// Initialise device
|
||||
if (oled->begin(_I2CAddress, _width, _height)) {
|
||||
// Store pointer to this object into CS display hook, so that we
|
||||
// will intercept any subsequent calls to lcdDisplay methods.
|
||||
DisplayInterface::lcdDisplay = this;
|
||||
|
||||
DIAG(F("OLEDDisplay installed on address x%x"), (int)_I2CAddress);
|
||||
|
||||
// First clear the entire screen
|
||||
oled->clearNative();
|
||||
|
||||
// Set first two lines on screen
|
||||
LCD(0,F("DCC++ EX v%S"),F(VERSION));
|
||||
LCD(1,F("Lic GPLv3"));
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// DisplayInterface functions
|
||||
//
|
||||
// TODO: Limit to one call to setRowNative or writeNative
|
||||
// per entry so that the function doesn't block waiting
|
||||
// for I2C to complete.
|
||||
/////////////////////////////////////////////////
|
||||
DisplayInterface* loop2(bool force) override {
|
||||
|
||||
// Loop through the buffer and if a row has changed
|
||||
// (rowGeneration[row] is changed) then start writing the
|
||||
// characters from the buffer, one character per entry,
|
||||
// to the screen until that row has been refreshed.
|
||||
// TODO: Currently this is done all in one go! Split
|
||||
// it up so that at most one call is made to either
|
||||
// setRowNative or writeNative per loop entry - then
|
||||
// we shan't have to wait for I2C.
|
||||
for (uint8_t row = 0; row < _numRows; row++) {
|
||||
if (_rowGeneration[row] != _lastRowGeneration[row]) {
|
||||
// Row has been modified, write to screen
|
||||
oled->setRowNative(row);
|
||||
for (uint8_t _col = 0; _col < _numCols; _col++) {
|
||||
oled->writeNative(_buffer[(uint16_t)row*_numCols+_col]);
|
||||
}
|
||||
_lastRowGeneration[row] = _rowGeneration[row];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Position on nominated line number (0 to number of lines -1)
|
||||
// Clear the line in the buffer ready for updating
|
||||
void setRow(byte line) override {
|
||||
if (line >= _numRows) line = _numRows-1;
|
||||
_rowNo = line;
|
||||
// Fill line with blanks
|
||||
for (_colNo = 0; _colNo < _numCols; _colNo++)
|
||||
_buffer[_rowNo*_numCols+_colNo] = ' ';
|
||||
_colNo = 0;
|
||||
// Mark that the buffer has been touched. It will be
|
||||
// sent to the screen on the next loop entry.
|
||||
_rowGeneration[_rowNo]++;
|
||||
}
|
||||
|
||||
// Write blanks to all of the screen (blocks until complete)
|
||||
void clear () override {
|
||||
// Clear buffer
|
||||
for (_rowNo = 0; _rowNo < _numRows; _rowNo++) {
|
||||
setRow(_rowNo);
|
||||
}
|
||||
_rowNo = 0;
|
||||
}
|
||||
|
||||
// Write one character
|
||||
size_t write(uint8_t c) override {
|
||||
// Write character to buffer (if space)
|
||||
if (_colNo < _numCols)
|
||||
_buffer[_rowNo*_numCols+_colNo++] = c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Display information about the device.
|
||||
void _display() {
|
||||
DIAG(F("OLEDDisplay Configured addr x%x"), (int)_I2CAddress);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_OLEDDISPLAY_H
|
@ -144,7 +144,11 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Constructor
|
||||
SSD1306AsciiWire::SSD1306AsciiWire() {}
|
||||
SSD1306AsciiWire::SSD1306AsciiWire() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(400000L); // Set max supported I2C speed
|
||||
|
||||
}
|
||||
|
||||
// CS auto-detect and configure constructor
|
||||
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) {
|
||||
@ -193,7 +197,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) {
|
||||
return false;
|
||||
}
|
||||
// Device found
|
||||
DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr);
|
||||
DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr);
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
@ -204,7 +208,8 @@ void SSD1306AsciiWire::clearNative() {
|
||||
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 = min(m_displayWidth-c, maxBytes-1) + 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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user