1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-22 23:56:13 +01:00

Support for multiple displays

Refactor display handling so DisplayInterface class passes the relevant commands to the relevant display objects.
This commit is contained in:
Neil McKechnie 2023-02-16 16:41:13 +00:00
parent d0445f157c
commit 8ed3bbd845
13 changed files with 598 additions and 382 deletions

View File

@ -49,6 +49,7 @@
*/ */
#include "DCCEX.h" #include "DCCEX.h"
#include "Display_Implementation.h"
#ifdef CPU_TYPE_ERROR #ifdef CPU_TYPE_ERROR
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
@ -74,11 +75,11 @@ void setup()
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
CONDITIONAL_DISPLAY_START { DISPLAY_START (
// This block is still executed for DIAGS if LCD not in use // This block is still executed for DIAGS if display not in use
LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(0,F("DCC-EX v%S"),F(VERSION));
LCD(1,F("Lic GPLv3")); LCD(1,F("Lic GPLv3"));
} );
// Responsibility 2: Start all the communications before the DCC engine // Responsibility 2: Start all the communications before the DCC engine
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
@ -160,7 +161,8 @@ void loop()
LCN::loop(); LCN::loop();
#endif #endif
Display::loop(); // ignored if LCD not in use // Display refresh
DisplayInterface::loop();
// Handle/update IO devices. // Handle/update IO devices.
IODevice::loop(); IODevice::loop();

View File

@ -47,42 +47,65 @@
#include "Display.h" #include "Display.h"
void Display::clear() { // Constructor - allocates device driver.
clearNative(); Display::Display(DisplayDevice *deviceDriver) {
for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0'; _deviceDriver = deviceDriver;
// Get device dimensions in characters (e.g. 16x2).
numCharacterColumns = _deviceDriver->getNumCols();
numCharacterRows = _deviceDriver->getNumRows();;
for (uint8_t row=0; row<MAX_CHARACTER_ROWS; row++)
rowBuffer[row] = (char *)calloc(1, MAX_CHARACTER_COLS+1);
topRow = -1; // loop2 will fill from row 0
addDisplay(0); // Add this display as display number 0
};
void Display::begin() {
_deviceDriver->begin();
_deviceDriver->clearNative();
}
void Display::_clear() {
_deviceDriver->clearNative();
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
rowBuffer[row][0] = '\0';
topRow = -1; // loop2 will fill from row 0 topRow = -1; // loop2 will fill from row 0
} }
void Display::setRow(byte line) { void Display::_setRow(uint8_t line) {
hotRow = line; hotRow = line;
hotCol = 0; hotCol = 0;
rowBuffer[hotRow][hotCol] = 0; // Clear existing text
} }
size_t Display::write(uint8_t b) { size_t Display::_write(uint8_t b) {
if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1; if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1;
rowBuffer[hotRow][hotCol] = b; rowBuffer[hotRow][hotCol] = b;
hotCol++; hotCol++;
rowBuffer[hotRow][hotCol] = 0; rowBuffer[hotRow][hotCol] = 0;
return 1; return 1;
} }
void Display::loop() { // Refresh screen completely (will block until complete). Used
if (!displayHandler) return; // during start-up.
displayHandler->loop2(false); void Display::_refresh() {
loop2(true);
}
// On normal loop entries, loop will only make one output request on each
// entry, to avoid blocking while waiting for the I2C.
void Display::_displayLoop() {
// If output device is busy, don't do anything on this loop
// This avoids blocking while waiting for the device to complete.
if (!_deviceDriver->isBusy()) loop2(false);
} }
Display *Display::loop2(bool force) { Display *Display::loop2(bool force) {
if (!displayHandler) return NULL;
// If output device is busy, don't do anything on this loop
// This avoids blocking while waiting for the device to complete.
if (isBusy()) return NULL;
unsigned long currentMillis = millis(); unsigned long currentMillis = millis();
if (!force) { if (!force) {
// See if we're in the time between updates // See if we're in the time between updates
if ((currentMillis - lastScrollTime) < LCD_SCROLL_TIME) if ((currentMillis - lastScrollTime) < DISPLAY_SCROLL_TIME)
return NULL; return NULL;
} else { } else {
// force full screen update from the beginning. // force full screen update from the beginning.
@ -104,7 +127,7 @@ Display *Display::loop2(bool force) {
buffer[i] = rowBuffer[rowNext][i]; buffer[i] = rowBuffer[rowNext][i];
} else } else
buffer[0] = '\0'; // Empty line buffer[0] = '\0'; // Empty line
setRowNative(slot); // Set position for display _deviceDriver->setRowNative(slot); // Set position for display
charIndex = 0; charIndex = 0;
bufferPointer = &buffer[0]; bufferPointer = &buffer[0];
@ -113,12 +136,12 @@ Display *Display::loop2(bool force) {
// Write next character, or a space to erase current position. // Write next character, or a space to erase current position.
char ch = *bufferPointer; char ch = *bufferPointer;
if (ch) { if (ch) {
writeNative(ch); _deviceDriver->writeNative(ch);
bufferPointer++; bufferPointer++;
} else } else
writeNative(' '); _deviceDriver->writeNative(' ');
if (++charIndex >= MAX_LCD_COLS) { if (++charIndex >= MAX_CHARACTER_COLS) {
// Screen slot completed, move to next slot on screen // Screen slot completed, move to next slot on screen
slot++; slot++;
bufferPointer = 0; bufferPointer = 0;
@ -128,7 +151,7 @@ Display *Display::loop2(bool force) {
} }
} }
if (slot >= lcdRows) { if (slot >= numCharacterRows) {
// Last slot finished, reset ready for next screen update. // Last slot finished, reset ready for next screen update.
#if SCROLLMODE==2 #if SCROLLMODE==2
if (!done) { if (!done) {
@ -151,7 +174,8 @@ Display *Display::loop2(bool force) {
} }
void Display::moveToNextRow() { void Display::moveToNextRow() {
rowNext = (rowNext + 1) % MAX_LCD_ROWS; rowNext = rowNext + 1;
if (rowNext >= MAX_CHARACTER_ROWS) rowNext = 0;
#if SCROLLMODE == 1 #if SCROLLMODE == 1
// Finished if we've looped back to row 0 // Finished if we've looped back to row 0
if (rowNext == 0) done = true; if (rowNext == 0) done = true;

View File

@ -16,15 +16,18 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
#ifndef LCDDisplay_h #ifndef Display_h
#define LCDDisplay_h #define Display_h
#include <Arduino.h> #include <Arduino.h>
#include "defines.h" #include "defines.h"
#include "DisplayInterface.h" #include "DisplayInterface.h"
// Allow maximum message length to be overridden from config.h // Allow maximum message length to be overridden from config.h
#if !defined(MAX_MSG_SIZE) #if !defined(MAX_MSG_SIZE)
#define MAX_MSG_SIZE 20 // On a screen that's 128 pixels wide, character 22 overlaps end of screen
// However, by making the line longer than the screen, we don't have to
// clear the screen, we just overwrite what was there.
#define MAX_MSG_SIZE 22
#endif #endif
// Set default scroll mode (overridable in config.h) // Set default scroll mode (overridable in config.h)
@ -32,36 +35,17 @@
#define SCROLLMODE 1 #define SCROLLMODE 1
#endif #endif
// This class is created in LCDisplay_Implementation.h // This class is created in Display_Implementation.h
class Display : public DisplayInterface { class Display : public DisplayInterface {
public: public:
Display() {}; Display(DisplayDevice *deviceDriver);
static const int MAX_LCD_ROWS = 8; static const int MAX_CHARACTER_ROWS = 8;
static const int MAX_LCD_COLS = MAX_MSG_SIZE; static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
static const long LCD_SCROLL_TIME = 3000; // 3 seconds static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
// Internally handled functions
static void loop();
Display* loop2(bool force) override;
void setRow(byte line) override;
void clear() override;
size_t write(uint8_t b) override;
protected:
uint8_t lcdRows;
uint8_t lcdCols;
private: private:
void moveToNextRow(); DisplayDevice *_deviceDriver;
void skipBlankRows();
// Relay functions to the live driver in the subclass
virtual void clearNative() = 0;
virtual void setRowNative(byte line) = 0;
virtual size_t writeNative(uint8_t b) = 0;
virtual bool isBusy() = 0;
unsigned long lastScrollTime = 0; unsigned long lastScrollTime = 0;
int8_t hotRow = 0; int8_t hotRow = 0;
@ -71,11 +55,25 @@ protected:
int8_t rowFirst = -1; int8_t rowFirst = -1;
int8_t rowNext = 0; int8_t rowNext = 0;
int8_t charIndex = 0; int8_t charIndex = 0;
char buffer[MAX_LCD_COLS + 1]; char buffer[MAX_CHARACTER_COLS + 1];
char* bufferPointer = 0; char* bufferPointer = 0;
bool done = false; bool done = false;
uint16_t numCharacterRows;
uint16_t numCharacterColumns = MAX_CHARACTER_COLS;
char *rowBuffer[MAX_CHARACTER_ROWS];
public:
void begin() override;
void _clear() override;
void _setRow(uint8_t line) override;
size_t _write(uint8_t b) override;
void _refresh() override;
void _displayLoop() override;
Display *loop2(bool force);
void moveToNextRow();
void skipBlankRows();
char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1];
}; };
#endif #endif

View File

@ -21,4 +21,7 @@
#include "DisplayInterface.h" #include "DisplayInterface.h"
DisplayInterface *DisplayInterface::displayHandler = 0; // Install null display driver initially - will be replaced if required.
DisplayInterface *DisplayInterface::_displayHandler = new DisplayInterface();
uint8_t DisplayInterface::_selectedDisplayNo = 255;

View File

@ -25,23 +25,75 @@
// Definition of base class for displays. The base class does nothing. // Definition of base class for displays. The base class does nothing.
class DisplayInterface : public Print { class DisplayInterface : public Print {
protected:
static DisplayInterface *_displayHandler;
static uint8_t _selectedDisplayNo; // Nothing selected.
DisplayInterface *_nextHandler = NULL;
uint8_t _displayNo = 0;
public: public:
virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; }; // Add display object to list of displays
virtual void setRow(byte line) { (void)line; }; void addDisplay(uint8_t displayNo) {
virtual void clear() { }; _nextHandler = _displayHandler;
virtual size_t write(uint8_t c) { (void)c; return 0; }; _displayHandler = this;
// Additional functions to support multiple displays. _displayNo = displayNo;
// Display number zero is the default one and the original display
// drivers overloaded the above calls only. Newer display drivers
// (e.g. HAL IO_OledDisplay) should override all functions.
virtual void setRow(uint8_t displayNo, byte line) {
if (!displayNo) setRow(line);
} }
virtual void clear(uint8_t displayNo) { static DisplayInterface *getDisplayHandler() {
if (!displayNo) clear(); return _displayHandler;
}
uint8_t getDisplayNo() {
return _displayNo;
} }
static DisplayInterface *displayHandler; // The next functions are to provide compatibility with calls to the LCD function
// which does not specify a display number. These always apply to display '0'.
static void refresh() { refresh(0); };
static void setRow(uint8_t line) { setRow(0, line); };
static void clear() { clear(0); };
// Additional functions to support multiple displays. These perform a
// multicast to all displays that match the selected displayNo.
// Display number zero is the default one.
static void setRow(uint8_t displayNo, uint8_t line) {
_selectedDisplayNo = displayNo;
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) {
if (displayNo == p->_displayNo) p->_setRow(line);
}
}
size_t write (uint8_t c) override {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (_selectedDisplayNo == p->_displayNo) p->_write(c);
return _displayHandler ? 1 : 0;
}
static void clear(uint8_t displayNo) {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (displayNo == p->_displayNo) p->_clear();
}
static void refresh(uint8_t displayNo) {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (displayNo == p->_displayNo) p->_refresh();
}
static void loop() {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
p->_displayLoop();
};
// The following are overridden within the specific device class
virtual void begin() {};
virtual size_t _write(uint8_t c) { (void)c; return 0; };
virtual void _setRow(uint8_t line) {}
virtual void _clear() {}
virtual void _refresh() {}
virtual void _displayLoop() {}
}; };
class DisplayDevice {
public:
virtual bool begin() { return true; }
virtual void clearNative() = 0;
virtual void setRowNative(uint8_t line) = 0;
virtual size_t writeNative(uint8_t c) = 0;
virtual bool isBusy() = 0;
virtual uint16_t getNumRows() = 0;
virtual uint16_t getNumCols() = 0;
};
#endif #endif

View File

@ -27,7 +27,7 @@
#ifndef LCD_Implementation_h #ifndef LCD_Implementation_h
#define LCD_Implementation_h #define LCD_Implementation_h
#include "Display.h" #include "DisplayInterface.h"
#include "SSD1306Ascii.h" #include "SSD1306Ascii.h"
#include "LiquidCrystal_I2C.h" #include "LiquidCrystal_I2C.h"
@ -35,19 +35,26 @@
// Implement the Display shim class as a singleton. // Implement the Display shim class as a singleton.
// The DisplayInterface class implements a display handler with no code (null device); // The DisplayInterface class implements a display handler with no code (null device);
// The Display class sub-classes DisplayInterface to provide the common display code; // The Display class sub-classes DisplayInterface to provide the common display code;
// Then Display class is subclassed to the specific device type classes: // Then Display class talks to the specific device type classes:
// SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers; // SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers;
// LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'.
#if defined(OLED_DRIVER) #if defined(OLED_DRIVER)
#define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) #define DISPLAY_START(xxx) { \
DisplayInterface *t = new Display(new SSD1306AsciiWire(OLED_DRIVER)); \
t->begin(); \
xxx; \
t->refresh(); \
}
#elif defined(LCD_DRIVER) #elif defined(LCD_DRIVER)
#define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) #define DISPLAY_START(xxx) { \
DisplayInterface *t = new Display(new LiquidCrystal_I2C(LCD_DRIVER)); \
t->begin(); \
xxx; \
t->refresh();}
#else #else
// Create null display handler just in case someone calls displayHandler->something without checking if displayHandler is NULL! #define DISPLAY_START(xxx) {}
#define CONDITIONAL_DISPLAY_START { new DisplayInterface(); }
#endif
#endif
#endif // LCD_Implementation_h #endif // LCD_Implementation_h

View File

@ -49,13 +49,15 @@
#include "SSD1306Ascii.h" #include "SSD1306Ascii.h"
#include "version.h" #include "version.h"
class OLEDDisplay : public IODevice, DisplayInterface { typedef SSD1306AsciiWire OLED;
template <class T>
class OLEDDisplay : public IODevice, public DisplayInterface {
private: private:
uint8_t _displayNo = 0;
// Here we define the device-specific variables. // Here we define the device-specific variables.
uint8_t _height; // in pixels uint8_t _height; // in pixels
uint8_t _width; // in pixels uint8_t _width; // in pixels
SSD1306AsciiWire *oled; T *_displayDriver;
uint8_t _rowNo = 0; // Row number being written by caller uint8_t _rowNo = 0; // Row number being written by caller
uint8_t _colNo = 0; // Position in line being written by caller uint8_t _colNo = 0; // Position in line being written by caller
uint8_t _numRows; uint8_t _numRows;
@ -66,26 +68,25 @@ private:
uint8_t _rowNoToScreen = 0; uint8_t _rowNoToScreen = 0;
uint8_t _charPosToScreen = 0; uint8_t _charPosToScreen = 0;
DisplayInterface *_nextDisplay = NULL; DisplayInterface *_nextDisplay = NULL;
uint8_t _selectedDisplayNo = 0;
public: public:
// Static function to handle "OLEDDisplay::create(...)" calls. // Static function to handle "OLEDDisplay::create(...)" calls.
static void create(I2CAddress i2cAddress, int width = 128, int height=64) { static void create(I2CAddress i2cAddress, int width, int height) {
/* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(0, i2cAddress, width, height); /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(0, i2cAddress, width, height);
} }
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
/* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height); /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height);
} }
protected: protected:
// Constructor // Constructor
OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
_displayNo = displayNo; _displayDriver = new T(i2cAddress, width, height);
_I2CAddress = i2cAddress; _I2CAddress = i2cAddress;
_width = width; _width = width;
_height = height; _height = height;
_numCols = (_width+5) / 6; // character block 6 x 8, round up _numCols = _displayDriver->getNumCols();
_numRows = _height / 8; // Round down _numRows = _displayDriver->getNumRows();
_charPosToScreen = _numCols; _charPosToScreen = _numCols;
@ -96,50 +97,27 @@ protected:
// Fill buffer with spaces // Fill buffer with spaces
memset(_buffer, ' ', _numCols*_numRows); memset(_buffer, ' ', _numCols*_numRows);
_displayDriver->clearNative();
// Add device to list of HAL devices (not necessary but allows
// status to be displayed using <D HAL SHOW> and device to be
// reinitialised using <D HAL RESET>).
IODevice::addDevice(this);
// Also add this display to list of display handlers
DisplayInterface::addDisplay(displayNo);
// Is this the main display? // Is this the main display?
if (_displayNo == 0) { if (displayNo == 0) {
// Set first two lines on screen // Set first two lines on screen
setRow(0); this->setRow(displayNo, 0);
print(F("DCC++ EX v")); print(F("DCC-EX v"));
print(F(VERSION)); print(F(VERSION));
setRow(1); setRow(displayNo, 1);
print(F("Lic GPLv3")); print(F("Lic GPLv3"));
} }
// Create OLED driver
oled = new SSD1306AsciiWire();
// Store pointer to this object into CS display hook, so that we
// will intercept any subsequent calls to displayHandler methods.
// Make a note of the existing display reference, to that we can
// pass on anything we're not interested in.
_nextDisplay = DisplayInterface::displayHandler;
DisplayInterface::displayHandler = this;
addDevice(this);
} }
// Device-specific initialisation
void _begin() override {
// Initialise device
if (oled->begin(_I2CAddress, _width, _height)) {
DIAG(F("OLEDDisplay installed on address %s"), _I2CAddress.toString());
// Force all rows to be redrawn
for (uint8_t row=0; row<_numRows; row++)
_rowGeneration[row]++;
// Start with top line (looks better)
_rowNoToScreen = _numRows;
_charPosToScreen = _numCols;
}
}
void _loop(unsigned long) override {
//screenUpdate();
}
void screenUpdate() { void screenUpdate() {
// Loop through the buffer and if a row has changed // Loop through the buffer and if a row has changed
@ -149,8 +127,8 @@ protected:
// First check if the OLED driver is still busy from a previous // First check if the OLED driver is still busy from a previous
// call. If so, don't to anything until the next entry. // call. If so, don't to anything until the next entry.
if (!oled->isBusy()) { if (!_displayDriver->isBusy()) {
// Check if we've just done the end of a row or just started // Check if we've just done the end of a row
if (_charPosToScreen >= _numCols) { if (_charPosToScreen >= _numCols) {
// Move to next line // Move to next line
if (++_rowNoToScreen >= _numRows) if (++_rowNoToScreen >= _numRows)
@ -159,37 +137,65 @@ protected:
if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) {
// Row content has changed, so start outputting it // Row content has changed, so start outputting it
_lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen];
oled->setRowNative(_rowNoToScreen); _displayDriver->setRowNative(_rowNoToScreen);
_charPosToScreen = 0; // Prepare to output first character on next entry _charPosToScreen = 0; // Prepare to output first character on next entry
} else { } else {
// Row not changed, don't bother writing it. // Row not changed, don't bother writing it.
} }
} else { } else {
// output character at current position // output character at current position
oled->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); _displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]);
} }
} }
return; return;
} }
/////////////////////////////////////////////////
// IODevice Class Member Overrides
/////////////////////////////////////////////////
// Device-specific initialisation
void _begin() override {
// Initialise device
if (_displayDriver->begin()) {
_display();
// Force all rows to be redrawn
for (uint8_t row=0; row<_numRows; row++)
_rowGeneration[row]++;
// Start with top line (looks better).
// The numbers will wrap round on the first loop2 entry.
_rowNoToScreen = _numRows;
_charPosToScreen = _numCols;
}
}
void _loop(unsigned long) override {
screenUpdate();
}
// Display information about the device.
void _display() {
DIAG(F("OLEDDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString());
}
///////////////////////////////////////////////// /////////////////////////////////////////////////
// DisplayInterface functions // DisplayInterface functions
// //
///////////////////////////////////////////////// /////////////////////////////////////////////////
DisplayInterface* loop2(bool force) override {
public:
void _displayLoop() override {
screenUpdate(); screenUpdate();
if (_nextDisplay)
return _nextDisplay->loop2(force); // continue to next display
return NULL;
} }
// Position on nominated line number (0 to number of lines -1) // Position on nominated line number (0 to number of lines -1)
// Clear the line in the buffer ready for updating // Clear the line in the buffer ready for updating
// The displayNo referenced here is remembered and any following // The displayNo referenced here is remembered and any following
// calls to write() will be directed to that display. // calls to write() will be directed to that display.
void setRow(uint8_t displayNo, byte line) override { void _setRow(byte line) override {
_selectedDisplayNo = displayNo;
if (displayNo == _displayNo) {
if (line == 255) { if (line == 255) {
// LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") -
// scroll the contents of the buffer and put the new line // scroll the contents of the buffer and put the new line
@ -211,49 +217,23 @@ protected:
// sent to the screen on the next loop entry, by which time // sent to the screen on the next loop entry, by which time
// the line should have been written to the buffer. // the line should have been written to the buffer.
_rowGeneration[_rowNo]++; _rowGeneration[_rowNo]++;
} else if (_nextDisplay)
_nextDisplay->setRow(displayNo, line); // Pass to next display
} }
// Write one character to the screen referenced in the last setRow() call. // Write one character to the screen referenced in the last setRow() call.
size_t write(uint8_t c) override { virtual size_t _write(uint8_t c) override {
if (_selectedDisplayNo == _displayNo) {
// Write character to buffer (if there's space) // Write character to buffer (if there's space)
if (_colNo < _numCols) { if (_colNo < _numCols) {
_buffer[_rowNo*_numCols+_colNo++] = c; _buffer[_rowNo*_numCols+_colNo++] = c;
} }
return 1; return 1;
} else if (_nextDisplay)
return _nextDisplay->write(c);
else
return 0;
} }
// Write blanks to all of the screen (blocks until complete) // Write blanks to all of the screen buffer
void clear (uint8_t displayNo) override { void _clear() {
if (displayNo == _displayNo) {
// Clear buffer // Clear buffer
for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { memset(_buffer, ' ', _numCols*_numRows);
setRow(displayNo, _rowNo); _colNo = 0;
}
_rowNo = 0; _rowNo = 0;
} else if (_nextDisplay)
_nextDisplay->clear(displayNo); // Pass to next display
}
// Overloads of above, for compatibility
void setRow(uint8_t line) override {
setRow(0, line);
}
void clear() override {
clear(0);
}
// Display information about the device.
void _display() {
DIAG(F("OLEDDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString());
} }
}; };

View File

@ -44,24 +44,25 @@
LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols,
uint8_t lcd_rows) { uint8_t lcd_rows) {
_Addr = lcd_Addr; _Addr = lcd_Addr;
lcdRows = lcd_rows; lcdRows = lcd_rows; // Number of character rows (typically 2 or 4).
lcdCols = lcd_cols; lcdCols = lcd_cols; // Number of character columns (typically 16 or 20)
_backlightval = 0; _backlightval = 0;
}
bool LiquidCrystal_I2C::begin() {
I2CManager.begin(); I2CManager.begin();
I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz. I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz.
if (I2CManager.exists(lcd_Addr)) { if (I2CManager.exists(_Addr)) {
DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr); DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcdCols, (int)lcdRows, _Addr.toString());
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
begin();
backlight(); backlight();
displayHandler = this; } else {
} DIAG(F("LCD not found on I2C:%s"), _Addr.toString());
return false;
} }
void LiquidCrystal_I2C::begin() {
if (lcdRows > 1) { if (lcdRows > 1) {
_displayfunction |= LCD_2LINE; _displayfunction |= LCD_2LINE;
} }
@ -99,26 +100,23 @@ void LiquidCrystal_I2C::begin() {
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
display(); display();
// clear it off
clear();
// Initialize to default text direction (for roman languages) // Initialize to default text direction (for roman languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
// set the entry mode // set the entry mode
command(LCD_ENTRYMODESET | _displaymode); command(LCD_ENTRYMODESET | _displaymode);
setRowNative(0); return true;
} }
/********** high level commands, for the user! */ /********** high level commands, for the user! */
void LiquidCrystal_I2C::clearNative() { void LiquidCrystal_I2C::clearNative() {
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes 1.52ms delayMicroseconds(1600); // this command takes 1.52ms
} }
void LiquidCrystal_I2C::setRowNative(byte row) { void LiquidCrystal_I2C::setRowNative(byte row) {
int row_offsets[] = {0x00, 0x40, 0x14, 0x54}; uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
if (row >= lcdRows) { if (row >= lcdRows) {
row = lcdRows - 1; // we count rows starting w/0 row = lcdRows - 1; // we count rows starting w/0
} }
@ -146,6 +144,10 @@ size_t LiquidCrystal_I2C::writeNative(uint8_t value) {
return 1; return 1;
} }
bool LiquidCrystal_I2C::isBusy() {
return rb.isBusy();
}
/*********** mid level commands, for sending data/cmds */ /*********** mid level commands, for sending data/cmds */
inline void LiquidCrystal_I2C::command(uint8_t value) { inline void LiquidCrystal_I2C::command(uint8_t value) {
@ -196,7 +198,7 @@ void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
outputBuffer[len++] = highnib; outputBuffer[len++] = highnib;
outputBuffer[len++] = lownib|En; outputBuffer[len++] = lownib|En;
outputBuffer[len++] = lownib; outputBuffer[len++] = lownib;
I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously
} }
// write 4 data bits to the HD44780 LCD controller. // write 4 data bits to the HD44780 LCD controller.
@ -208,12 +210,12 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) {
uint8_t len = 0; uint8_t len = 0;
outputBuffer[len++] = _data|En; outputBuffer[len++] = _data|En;
outputBuffer[len++] = _data; outputBuffer[len++] = _data;
I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously
} }
// write a byte to the PCF8574 I2C interface. We don't need to set // write a byte to the PCF8574 I2C interface. We don't need to set
// the enable pin for this. // the enable pin for this.
void LiquidCrystal_I2C::expanderWrite(uint8_t value) { void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
outputBuffer[0] = value | _backlightval; outputBuffer[0] = value | _backlightval;
I2CManager.write(_Addr, outputBuffer, 1); // Write command synchronously I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously
} }

View File

@ -62,33 +62,38 @@
#define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit #define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit
#define Rs (1 << BACKPACK_Rs_BIT) // Register select bit #define Rs (1 << BACKPACK_Rs_BIT) // Register select bit
class LiquidCrystal_I2C : public Display { class LiquidCrystal_I2C : public DisplayDevice {
public: public:
LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
void begin(); bool begin() override;
void clearNative() override; void clearNative() override;
void setRowNative(byte line) override; void setRowNative(byte line) override;
size_t writeNative(uint8_t c) override; size_t writeNative(uint8_t c) override;
// I/O is synchronous, so if this is called we're not busy!
bool isBusy() override;
void display(); void display();
void noBacklight(); void noBacklight();
void backlight(); void backlight();
void command(uint8_t); void command(uint8_t);
uint16_t getNumCols() { return lcdCols; }
uint16_t getNumRows() { return lcdRows; }
private: private:
void send(uint8_t, uint8_t); void send(uint8_t, uint8_t);
void write4bits(uint8_t); void write4bits(uint8_t);
void expanderWrite(uint8_t); void expanderWrite(uint8_t);
uint8_t _Addr; uint8_t lcdCols=0, lcdRows=0;
I2CAddress _Addr;
uint8_t _displayfunction; uint8_t _displayfunction;
uint8_t _displaycontrol; uint8_t _displaycontrol;
uint8_t _displaymode; uint8_t _displaymode;
uint8_t _backlightval; uint8_t _backlightval;
uint8_t outputBuffer[4]; uint8_t outputBuffer[4];
// I/O is synchronous, so if this is called we're not busy! I2CRB rb;
bool isBusy() override { return false; }
}; };
#endif #endif

View File

@ -144,39 +144,38 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Constructor // Constructor
SSD1306AsciiWire::SSD1306AsciiWire() { SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) {
I2CManager.begin(); m_i2cAddr = 0;
I2CManager.setClock(400000L); // Set max supported I2C speed m_displayWidth = width;
m_displayHeight = height;
} }
// CS auto-detect and configure constructor // CS auto-detect and configure constructor
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { SSD1306AsciiWire::SSD1306AsciiWire(I2CAddress address, 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.
displayHandler = 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_i2cAddr = address;
m_displayWidth = width; m_displayWidth = width;
m_displayHeight = height; m_displayHeight = height;
}
// Set size in characters in base class bool SSD1306AsciiWire::begin() {
lcdRows = height / 8; I2CManager.begin();
lcdCols = width / 6; 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"));
}
// Set size in characters
m_charsPerColumn = m_displayHeight / fontHeight;
m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up
m_col = 0; m_col = 0;
m_row = 0; m_row = 0;
m_colOffset = 0; m_colOffset = 0;
@ -186,7 +185,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) {
m_colOffset = 2; m_colOffset = 2;
I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init)); I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init));
} else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) { } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) {
// SSD1306 128x64 or 128x32 // SSD1306 or SSD1309 128x64 or 128x32
I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit)); I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit));
if (m_displayHeight == 32) if (m_displayHeight == 32)
I2CManager.write(m_i2cAddr, 5, 0, // Set command mode I2CManager.write(m_i2cAddr, 5, 0, // Set command mode
@ -198,19 +197,18 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) {
} }
// Device found // Device found
DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString()); DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString());
clear();
return true; return true;
} }
/* Clear screen by writing blank pixels. */ /* Clear screen by writing blank pixels. */
void SSD1306AsciiWire::clearNative() { void SSD1306AsciiWire::clearNative() {
const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire 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++) { for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) {
setRowNative(r); // Position at start of row to be erased setRowNative(r); // Position at start of row to be erased
for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { for (uint8_t c = 0; c < m_displayWidth; c += maxBytes) {
uint8_t len = m_displayWidth-c+1; uint8_t len = m_displayWidth-c; // Number of pixel columns remaining
if (len > maxBytes) len = maxBytes; if (len > maxBytes) len = maxBytes;
I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns I2CManager.write_P(m_i2cAddr, blankPixels, len+1); // Write command + 'len' blank columns
} }
} }
} }
@ -263,127 +261,262 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
outputBuffer[0] = 0x40; // set SSD1306 controller to data mode outputBuffer[0] = 0x40; // set SSD1306 controller to data mode
uint8_t bufferPos = 1; uint8_t bufferPos = 1;
// Copy character pixel columns // Copy character pixel columns
for (uint8_t i = 0; i < fontWidth; i++) for (uint8_t i = 0; i < fontWidth; i++) {
if (m_col++ < m_displayWidth)
outputBuffer[bufferPos++] = GETFLASH(base++); 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 // Write the data to I2C display
I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock); I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock);
m_col += fontWidth + letterSpacing;
return 1; return 1;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Font characters, 5x7 pixels, 0x61 characters starting at 0x20. // Font characters, 6x8 pixels, starting at 0x20.
// Lower case characters optionally omitted. // Lower case characters optionally omitted.
const uint8_t FLASH SSD1306AsciiWire::System5x7[] = { const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
// Fixed width; char width table not used !!!! // Fixed width; char width table not used !!!!
// or with lowercase character omitted.
// font data // font data
0x00, 0x00, 0x00, 0x00, 0x00, // (space) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (space) (20)
0x00, 0x00, 0x5F, 0x00, 0x00, // ! 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, // ! (21)
0x00, 0x07, 0x00, 0x07, 0x00, // " 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, // "
0x14, 0x7F, 0x14, 0x7F, 0x14, // # 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, // #
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, // $
0x23, 0x13, 0x08, 0x64, 0x62, // % 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, // %
0x36, 0x49, 0x55, 0x22, 0x50, // & 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, // &
0x00, 0x05, 0x03, 0x00, 0x00, // ' 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, // '
0x00, 0x1C, 0x22, 0x41, 0x00, // ( 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, // (
0x00, 0x41, 0x22, 0x1C, 0x00, // ) 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, // )
0x08, 0x2A, 0x1C, 0x2A, 0x08, // * 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, // *
0x08, 0x08, 0x3E, 0x08, 0x08, // + 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, // +
0x00, 0x50, 0x30, 0x00, 0x00, // , 0x00, 0x50, 0x30, 0x00, 0x00, 0x00, // ,
0x08, 0x08, 0x08, 0x08, 0x08, // - 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // -
0x00, 0x60, 0x60, 0x00, 0x00, // . 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, // .
0x20, 0x10, 0x08, 0x04, 0x02, // / 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, // / (47)
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, // 0 (48)
0x00, 0x42, 0x7F, 0x40, 0x00, // 1 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, // 1
0x42, 0x61, 0x51, 0x49, 0x46, // 2 0x42, 0x61, 0x51, 0x49, 0x46, 0x00, // 2
0x21, 0x41, 0x45, 0x4B, 0x31, // 3 0x21, 0x41, 0x45, 0x4B, 0x31, 0x00, // 3
0x18, 0x14, 0x12, 0x7F, 0x10, // 4 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, // 4
0x27, 0x45, 0x45, 0x45, 0x39, // 5 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, // 5
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, // 6
0x01, 0x71, 0x09, 0x05, 0x03, // 7 0x01, 0x71, 0x09, 0x05, 0x03, 0x00, // 7
0x36, 0x49, 0x49, 0x49, 0x36, // 8 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, // 8
0x06, 0x49, 0x49, 0x29, 0x1E, // 9 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, // 9 (57)
0x00, 0x36, 0x36, 0x00, 0x00, // : 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, // :
0x00, 0x56, 0x36, 0x00, 0x00, // ; 0x00, 0x56, 0x36, 0x00, 0x00, 0x00, // ;
0x00, 0x08, 0x14, 0x22, 0x41, // < 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // <
0x14, 0x14, 0x14, 0x14, 0x14, // = 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, // =
0x41, 0x22, 0x14, 0x08, 0x00, // > 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, // >
0x02, 0x01, 0x51, 0x09, 0x06, // ? 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, // ?
0x32, 0x49, 0x79, 0x41, 0x3E, // @ 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, // @ (64)
0x7E, 0x11, 0x11, 0x11, 0x7E, // A 0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00, // A (65)
0x7F, 0x49, 0x49, 0x49, 0x36, // B 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, // B
0x3E, 0x41, 0x41, 0x41, 0x22, // C 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, // C
0x7F, 0x41, 0x41, 0x22, 0x1C, // D 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, // D
0x7F, 0x49, 0x49, 0x49, 0x41, // E 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, // E
0x7F, 0x09, 0x09, 0x01, 0x01, // F 0x7F, 0x09, 0x09, 0x01, 0x01, 0x00, // F
0x3E, 0x41, 0x41, 0x51, 0x32, // G 0x3E, 0x41, 0x41, 0x51, 0x32, 0x00, // G
0x7F, 0x08, 0x08, 0x08, 0x7F, // H 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, // H
0x00, 0x41, 0x7F, 0x41, 0x00, // I 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, // I
0x20, 0x40, 0x41, 0x3F, 0x01, // J 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, // J
0x7F, 0x08, 0x14, 0x22, 0x41, // K 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, // K
0x7F, 0x40, 0x40, 0x40, 0x40, // L 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, // L
0x7F, 0x02, 0x04, 0x02, 0x7F, // M 0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00, // M
0x7F, 0x04, 0x08, 0x10, 0x7F, // N 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, // N
0x3E, 0x41, 0x41, 0x41, 0x3E, // O 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, // O
0x7F, 0x09, 0x09, 0x09, 0x06, // P 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, // P
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, // Q
0x7F, 0x09, 0x19, 0x29, 0x46, // R 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, // R
0x46, 0x49, 0x49, 0x49, 0x31, // S 0x46, 0x49, 0x49, 0x49, 0x31, 0x00, // S
0x01, 0x01, 0x7F, 0x01, 0x01, // T 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, // T
0x3F, 0x40, 0x40, 0x40, 0x3F, // U 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, // U
0x1F, 0x20, 0x40, 0x20, 0x1F, // V 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, // V
0x7F, 0x20, 0x18, 0x20, 0x7F, // W 0x7F, 0x20, 0x18, 0x20, 0x7F, 0x00, // W
0x63, 0x14, 0x08, 0x14, 0x63, // X 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, // X
0x03, 0x04, 0x78, 0x04, 0x03, // Y 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, // Y
0x61, 0x51, 0x49, 0x45, 0x43, // Z 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, // Z (90)
0x00, 0x00, 0x7F, 0x41, 0x41, // [ 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // [
0x02, 0x04, 0x08, 0x10, 0x20, // "\" 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, // "\"
0x41, 0x41, 0x7F, 0x00, 0x00, // ] 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, // ]
0x04, 0x02, 0x01, 0x02, 0x04, // ^ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, // ^
0x40, 0x40, 0x40, 0x40, 0x40, // _ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, // _
0x00, 0x01, 0x02, 0x04, 0x00, // ` 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, // ' (96)
#ifndef NOLOWERCASE #ifndef NOLOWERCASE
0x20, 0x54, 0x54, 0x54, 0x78, // a 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, // a (97)
0x7F, 0x48, 0x44, 0x44, 0x38, // b 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, // b
0x38, 0x44, 0x44, 0x44, 0x20, // c 0x38, 0x44, 0x44, 0x44, 0x20, 0x00, // c
0x38, 0x44, 0x44, 0x48, 0x7F, // d 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, // d
0x38, 0x54, 0x54, 0x54, 0x18, // e 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, // e
0x08, 0x7E, 0x09, 0x01, 0x02, // f 0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, // f
0x08, 0x14, 0x54, 0x54, 0x3C, // g 0x08, 0x14, 0x54, 0x54, 0x3C, 0x00, // g
0x7F, 0x08, 0x04, 0x04, 0x78, // h 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, // h
0x00, 0x44, 0x7D, 0x40, 0x00, // i 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, // i
0x20, 0x40, 0x44, 0x3D, 0x00, // j 0x20, 0x40, 0x44, 0x3D, 0x00, 0x00, // j
0x00, 0x7F, 0x10, 0x28, 0x44, // k 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // k
0x00, 0x41, 0x7F, 0x40, 0x00, // l 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, // l
0x7C, 0x04, 0x18, 0x04, 0x78, // m 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, // m
0x7C, 0x08, 0x04, 0x04, 0x78, // n 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, // n
0x38, 0x44, 0x44, 0x44, 0x38, // o 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, // o
0x7C, 0x14, 0x14, 0x14, 0x08, // p 0x7C, 0x14, 0x14, 0x14, 0x08, 0x00, // p
0x08, 0x14, 0x14, 0x18, 0x7C, // q 0x08, 0x14, 0x14, 0x18, 0x7C, 0x00, // q
0x7C, 0x08, 0x04, 0x04, 0x08, // r 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, // r
0x48, 0x54, 0x54, 0x54, 0x20, // s 0x48, 0x54, 0x54, 0x54, 0x20, 0x00, // s
0x04, 0x3F, 0x44, 0x40, 0x20, // t 0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, // t
0x3C, 0x40, 0x40, 0x20, 0x7C, // u 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, // u
0x1C, 0x20, 0x40, 0x20, 0x1C, // v 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, // v
0x3C, 0x40, 0x30, 0x40, 0x3C, // w 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, // w
0x44, 0x28, 0x10, 0x28, 0x44, // x 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // x
0x0C, 0x50, 0x50, 0x50, 0x3C, // y 0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00, // y
0x44, 0x64, 0x54, 0x4C, 0x44, // z 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, // z (122)
#endif #endif
0x00, 0x08, 0x36, 0x41, 0x00, // { 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, // { (123)
0x00, 0x00, 0x7F, 0x00, 0x00, // | 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, // |
0x00, 0x41, 0x36, 0x08, 0x00, // } 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, // }
0x08, 0x08, 0x2A, 0x1C, 0x08, // -> 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, // ->
0x08, 0x1C, 0x2A, 0x08, 0x08, // <- 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, // <- (127)
0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol #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
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 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
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
// Extended characters 176-180
0x92, 0x00, 0x49, 0x00, 0x24, 0x00, // Light grey 0xb0
0xcc, 0x55, 0xcc, 0x55, 0xcc, 0x55, // Mid grey 0xb1
0x6a, 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, 0xfe, 0x00, 0xff, 0x00, // Vertical line with double left spur 0xb9
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented Double vertical line with single left spur
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
// Extended characters 185-190
0x28, 0x28, 0xef, 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
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbd
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 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, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
// 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
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xd0
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
// 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
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xe0
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 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
0x00
}; };
const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6;

View File

@ -27,21 +27,24 @@
#include "I2CManager.h" #include "I2CManager.h"
#include "DIAG.h" #include "DIAG.h"
#include "DisplayInterface.h"
// Uncomment to remove lower-case letters to save 108 bytes of flash // Uncomment to remove lower-case letters to save 108 bytes of flash
//#define NOLOWERCASE //#define NOLOWERCASE
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Constructor // Constructor
class SSD1306AsciiWire : public Display { class SSD1306AsciiWire : public DisplayDevice {
public: public:
// Constructors // Constructors
SSD1306AsciiWire(int width, int height); // Auto-detects I2C address SSD1306AsciiWire(int width, int height); // Auto-detects I2C address
SSD1306AsciiWire(); // Requires call to 'begin()' SSD1306AsciiWire(I2CAddress address, int width, int height);
// Initialize the display controller. // Initialize the display controller.
bool begin(I2CAddress address, int width, int height); bool begin();
// Clear the display and set the cursor to (0, 0). // Clear the display and set the cursor to (0, 0).
void clearNative() override; void clearNative() override;
@ -53,6 +56,8 @@ class SSD1306AsciiWire : public Display {
size_t writeNative(uint8_t c) override; size_t writeNative(uint8_t c) override;
bool isBusy() override { return requestBlock.isBusy(); } bool isBusy() override { return requestBlock.isBusy(); }
uint16_t getNumCols() { return m_charsPerRow; }
uint16_t getNumRows() { return m_charsPerColumn; }
private: private:
// Cursor column. // Cursor column.
@ -63,28 +68,31 @@ class SSD1306AsciiWire : public Display {
uint8_t m_displayWidth; uint8_t m_displayWidth;
// Display height. // Display height.
uint8_t m_displayHeight; uint8_t m_displayHeight;
// Display width in characters
uint8_t m_charsPerRow;
// Display height in characters
uint8_t m_charsPerColumn;
// Column offset RAM to SEG. // Column offset RAM to SEG.
uint8_t m_colOffset = 0; uint8_t m_colOffset = 0;
// Current font. // Current font.
const uint8_t* const m_font = System5x7; const uint8_t* const m_font = System6x8;
// Flag to prevent calling begin() twice // Flag to prevent calling begin() twice
uint8_t m_initialised = false; uint8_t m_initialised = false;
// Only fixed size 5x7 fonts in a 6x8 cell are supported. // Only fixed size 6x8 fonts in a 6x8 cell are supported.
static const uint8_t fontWidth = 5; static const uint8_t fontWidth = 6;
static const uint8_t fontHeight = 7; static const uint8_t fontHeight = 8;
static const uint8_t letterSpacing = 1;
static const uint8_t m_fontFirstChar = 0x20; static const uint8_t m_fontFirstChar = 0x20;
static const uint8_t m_fontCharCount = 0x61; static const uint8_t m_fontCharCount;
I2CAddress m_i2cAddr; I2CAddress m_i2cAddr = 0;
I2CRB requestBlock; I2CRB requestBlock;
uint8_t outputBuffer[fontWidth+letterSpacing+1]; uint8_t outputBuffer[fontWidth+1];
static const uint8_t blankPixels[]; static const uint8_t blankPixels[];
static const uint8_t System5x7[]; static const uint8_t System6x8[];
static const uint8_t FLASH Adafruit128xXXinit[]; static const uint8_t FLASH Adafruit128xXXinit[];
static const uint8_t FLASH SH1106_132x64init[]; static const uint8_t FLASH SH1106_132x64init[];
}; };

View File

@ -18,7 +18,7 @@
*/ */
#include "StringFormatter.h" #include "StringFormatter.h"
#include <stdarg.h> #include <stdarg.h>
#include "Display.h" #include "DisplayInterface.h"
bool Diag::ACK=false; bool Diag::ACK=false;
bool Diag::CMD=false; bool Diag::CMD=false;
@ -45,19 +45,17 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
send2(&USB_SERIAL,input,args); send2(&USB_SERIAL,input,args);
send(&USB_SERIAL,F(" *>\n")); send(&USB_SERIAL,F(" *>\n"));
if (!Display::displayHandler) return; DisplayInterface::setRow(row);
Display::displayHandler->setRow(row);
va_start(args, input); va_start(args, input);
send2(Display::displayHandler,input,args); send2(DisplayInterface::getDisplayHandler(),input,args);
} }
void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
va_list args; va_list args;
if (!Display::displayHandler) return; DisplayInterface::setRow(display, row);
Display::displayHandler->setRow(display, row);
va_start(args, input); va_start(args, input);
send2(Display::displayHandler,input,args); send2(DisplayInterface::getDisplayHandler(),input,args);
} }
void StringFormatter::send(Print * stream, const FSH* input...) { void StringFormatter::send(Print * stream, const FSH* input...) {

View File

@ -46,9 +46,13 @@
#if defined(ARDUINO_AVR_UNO) #if defined(ARDUINO_AVR_UNO)
#define ARDUINO_TYPE "UNO" #define ARDUINO_TYPE "UNO"
#undef HAS_ENOUGH_MEMORY #undef HAS_ENOUGH_MEMORY
#define NO_EXTENDED_CHARACTERS
#undef I2C_EXTENDED_ADDRESS
#elif defined(ARDUINO_AVR_NANO) #elif defined(ARDUINO_AVR_NANO)
#define ARDUINO_TYPE "NANO" #define ARDUINO_TYPE "NANO"
#undef HAS_ENOUGH_MEMORY #undef HAS_ENOUGH_MEMORY
#define NO_EXTENDED_CHARACTERS
#undef I2C_EXTENDED_ADDRESS
#elif defined(ARDUINO_AVR_MEGA) #elif defined(ARDUINO_AVR_MEGA)
#define ARDUINO_TYPE "MEGA" #define ARDUINO_TYPE "MEGA"
#elif defined(ARDUINO_AVR_MEGA2560) #elif defined(ARDUINO_AVR_MEGA2560)