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

Support for multiple displays (OLED etc).

New EXRAIL command LCD2(display,row,"text").  Display 0 is the usual one, other displays can be configured through HAL.
This commit is contained in:
Neil McKechnie 2023-02-11 15:47:50 +00:00
parent a9971968c0
commit 8be9d9e0b0
9 changed files with 140 additions and 48 deletions

1
DIAG.h
View File

@ -24,4 +24,5 @@
#include "StringFormatter.h" #include "StringFormatter.h"
#define DIAG StringFormatter::diag #define DIAG StringFormatter::diag
#define LCD StringFormatter::lcd #define LCD StringFormatter::lcd
#define LCD2 StringFormatter::lcd
#endif #endif

View File

@ -28,8 +28,18 @@ class DisplayInterface : public Print {
public: public:
virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; }; virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; };
virtual void setRow(byte line) { (void)line; }; virtual void setRow(byte line) { (void)line; };
virtual void clear() {}; virtual void clear() { };
virtual size_t write(uint8_t c) { (void)c; return 0; }; virtual size_t write(uint8_t c) { (void)c; return 0; };
// Additional functions to support multiple displays.
// 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) {
if (!displayNo) clear();
}
static DisplayInterface *lcdDisplay; static DisplayInterface *lcdDisplay;
}; };

View File

@ -1209,6 +1209,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
case thrunge_parse: case thrunge_parse:
case thrunge_broadcast: case thrunge_broadcast:
case thrunge_lcd: case thrunge_lcd:
default: // thrunge_lcd+1, ...
if (!buffer) buffer=new StringBuffer(); if (!buffer) buffer=new StringBuffer();
buffer->flush(); buffer->flush();
stream=buffer; stream=buffer;
@ -1244,7 +1245,9 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
case thrunge_lcd: case thrunge_lcd:
LCD(id,F("%s"),buffer->getString()); LCD(id,F("%s"),buffer->getString());
break; break;
default: // thrunge_lcd+1, ...
default: break; if (mode > thrunge_lcd)
LCD2(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display
break;
} }
} }

View File

@ -73,11 +73,15 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFLOCO OPCODE_IFLOCO
}; };
// Ensure thrunge_lcd is put last as there may be more than one display,
// sequentially numbered from thrunge_lcd.
enum thrunger: byte { enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3, thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6, thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcd, thrunge_lcn}; thrunge_lcn,
thrunge_lcd, // Must be last!!
};

View File

@ -80,6 +80,7 @@
#undef KILLALL #undef KILLALL
#undef LATCH #undef LATCH
#undef LCD #undef LCD
#undef LCD2
#undef LCN #undef LCN
#undef MOVETT #undef MOVETT
#undef ONACTIVATE #undef ONACTIVATE
@ -199,6 +200,7 @@
#define KILLALL #define KILLALL
#define LATCH(sensor_id) #define LATCH(sensor_id)
#define LCD(row,msg) #define LCD(row,msg)
#define LCD2(display,row,msg)
#define LCN(msg) #define LCN(msg)
#define MOVETT(id,steps,activity) #define MOVETT(id,steps,activity)
#define ONACTIVATE(addr,subaddr) #define ONACTIVATE(addr,subaddr)

View File

@ -45,7 +45,7 @@
// Descriptive texts for routes and animations are created in a sepaerate function which // Descriptive texts for routes and animations are created in a sepaerate function which
// can be called to emit a list of routes/automatuions in a form suitable for Withrottle. // can be called to emit a list of routes/automatuions in a form suitable for Withrottle.
// PRINT(msg) and LCD(row,msg) is implemented in a separate pass to create // PRINT(msg), LCD(row,msg) and LCD2(display,row,msg) are implemented in a separate pass to create
// a getMessageText(id) function. // a getMessageText(id) function.
// CAUTION: The macros below are multiple passed over myAutomation.h // CAUTION: The macros below are multiple passed over myAutomation.h
@ -143,6 +143,15 @@ const int StringMacroTracker1=__COUNTER__;
lcdid=id;\ lcdid=id;\
break;\ break;\
} }
#undef LCD2
#define LCD2(display,id,msg) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=(thrunger)(thrunge_lcd+display); \
lcdid=id;\
break;\
}
void RMFT2::printMessage(uint16_t id) { void RMFT2::printMessage(uint16_t id) {
thrunger tmode; thrunger tmode;
@ -298,6 +307,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define KILLALL OPCODE_KILLALL,0,0, #define KILLALL OPCODE_KILLALL,0,0,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCD(id,msg) PRINT(msg) #define LCD(id,msg) PRINT(msg)
#define LCD2(display,id,msg)
#define LCN(msg) PRINT(msg) #define LCN(msg) PRINT(msg)
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
@ -368,6 +378,8 @@ const HIGHFLASH byte RMFT2::RouteCode[] = {
// Restore normal code LCD & SERIAL macro // Restore normal code LCD & SERIAL macro
#undef LCD #undef LCD
#define LCD StringFormatter::lcd #define LCD StringFormatter::lcd
#undef LCD2
#define LCD2 StringFormatter::lcd
#undef SERIAL #undef SERIAL
#define SERIAL 0x0 #define SERIAL 0x0
#endif #endif

View File

@ -51,6 +51,7 @@
class OLEDDisplay : public IODevice, DisplayInterface { class OLEDDisplay : public IODevice, 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
@ -64,16 +65,22 @@ private:
uint8_t *_lastRowGeneration = NULL; uint8_t *_lastRowGeneration = NULL;
uint8_t _rowNoToScreen = 0; uint8_t _rowNoToScreen = 0;
uint8_t _charPosToScreen = 0; uint8_t _charPosToScreen = 0;
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 = 128, int height=64) {
/* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(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) {
/* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height);
} }
protected: protected:
// Constructor // Constructor
OLEDDisplay(I2CAddress i2cAddress, int width, int height) { OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
_displayNo = displayNo;
_I2CAddress = i2cAddress; _I2CAddress = i2cAddress;
_width = width; _width = width;
_height = height; _height = height;
@ -89,9 +96,26 @@ protected:
// Fill buffer with spaces // Fill buffer with spaces
memset(_buffer, ' ', _numCols*_numRows); memset(_buffer, ' ', _numCols*_numRows);
// Is this the main display?
if (_displayNo == 0) {
// Set first two lines on screen
setRow(0);
print(F("DCC++ EX v"));
print(F(VERSION));
setRow(1);
print(F("Lic GPLv3"));
}
// Create OLED driver // Create OLED driver
oled = new SSD1306AsciiWire(); oled = new SSD1306AsciiWire();
// Store pointer to this object into CS display hook, so that we
// will intercept any subsequent calls to lcdDisplay methods.
// Make a note of the existing display reference, to that we can
// pass on anything we're not interested in.
_nextDisplay = DisplayInterface::lcdDisplay;
DisplayInterface::lcdDisplay = this;
addDevice(this); addDevice(this);
} }
@ -99,15 +123,9 @@ protected:
void _begin() override { void _begin() override {
// Initialise device // Initialise device
if (oled->begin(_I2CAddress, _width, _height)) { 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 %s"), _I2CAddress.toString()); DIAG(F("OLEDDisplay installed on address %s"), _I2CAddress.toString());
// Set first two lines on screen
LCD(0,F("DCC++ EX v%S"),F(VERSION));
LCD(1,F("Lic GPLv3"));
// Force all rows to be redrawn // Force all rows to be redrawn
for (uint8_t row=0; row<_numRows; row++) for (uint8_t row=0; row<_numRows; row++)
@ -120,7 +138,10 @@ protected:
} }
void _loop(unsigned long) override { void _loop(unsigned long) override {
screenUpdate();
}
void screenUpdate() {
// Loop through the buffer and if a row has changed // Loop through the buffer and if a row has changed
// (rowGeneration[row] is changed) then start writing the // (rowGeneration[row] is changed) then start writing the
// characters from the buffer, one character per entry, // characters from the buffer, one character per entry,
@ -156,54 +177,82 @@ protected:
// //
///////////////////////////////////////////////// /////////////////////////////////////////////////
DisplayInterface* loop2(bool force) override { DisplayInterface* loop2(bool force) override {
(void)force; // suppress compiler warning //screenUpdate();
if (_nextDisplay)
return _nextDisplay->loop2(force); // continue to next display
return NULL; 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
void setRow(byte line) override { // The displayNo referenced here is remembered and any following
if (line == 255) { // calls to write() will be directed to that display.
// LCD(255, "xxx") - scroll the contents of the buffer void setRow(uint8_t displayNo, byte line) override {
// and put the new line at the bottom of the screen _selectedDisplayNo = displayNo;
for (int row=1; row<_numRows; row++) { if (displayNo == _displayNo) {
strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); if (line == 255) {
_rowGeneration[row-1]++; // LCD(255,"xxx") or LCD2(displayNo,255, "xxx") -
} // scroll the contents of the buffer and put the new line
line = _numRows-1; // at the bottom of the screen
} else if (line >= _numRows) for (int row=1; row<_numRows; row++) {
line = _numRows - 1; // Overwrite bottom line. strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols);
_rowGeneration[row-1]++;
}
line = _numRows-1;
} else if (line >= _numRows)
line = _numRows - 1; // Overwrite bottom line.
_rowNo = line; _rowNo = line;
// Fill line with blanks // Fill line with blanks
for (_colNo = 0; _colNo < _numCols; _colNo++) for (_colNo = 0; _colNo < _numCols; _colNo++)
_buffer[_rowNo*_numCols+_colNo] = ' '; _buffer[_rowNo*_numCols+_colNo] = ' ';
_colNo = 0; _colNo = 0;
// Mark that the buffer has been touched. It will be // Mark that the buffer has been touched. It will be
// sent to the screen on the next loop entry. // sent to the screen on the next loop entry.
_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.
size_t write(uint8_t c) override {
if (_selectedDisplayNo == _displayNo) {
// Write character to buffer (if there's space)
if (_colNo < _numCols) {
_buffer[_rowNo*_numCols+_colNo++] = c;
}
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 (blocks until complete)
void clear () override { void clear (uint8_t displayNo) override {
// Clear buffer if (displayNo == _displayNo) {
for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { // Clear buffer
setRow(_rowNo); for (_rowNo = 0; _rowNo < _numRows; _rowNo++) {
} setRow(displayNo, _rowNo);
_rowNo = 0; }
_rowNo = 0;
} else if (_nextDisplay)
_nextDisplay->clear(displayNo); // Pass to next display
} }
// Write one character // Overloads of above, for compatibility
size_t write(uint8_t c) override { void setRow(uint8_t line) override {
// Write character to buffer (if space) setRow(0, line);
if (_colNo < _numCols) }
_buffer[_rowNo*_numCols+_colNo++] = c; void clear() override {
return 1; clear(0);
} }
// Display information about the device. // Display information about the device.
void _display() { void _display() {
DIAG(F("OLEDDisplay Configured addr %s"), _I2CAddress.toString()); DIAG(F("OLEDDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString());
} }
}; };

View File

@ -51,6 +51,16 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
send2(LCDDisplay::lcdDisplay,input,args); send2(LCDDisplay::lcdDisplay,input,args);
} }
void StringFormatter::lcd(uint8_t display, byte row, const FSH* input...) {
va_list args;
if (!LCDDisplay::lcdDisplay) return;
LCDDisplay::lcdDisplay->setRow(display, row);
va_start(args, input);
send2(LCDDisplay::lcdDisplay,input,args);
va_end(args);
}
void StringFormatter::send(Print * stream, const FSH* input...) { void StringFormatter::send(Print * stream, const FSH* input...) {
va_list args; va_list args;
va_start(args, input); va_start(args, input);

View File

@ -46,6 +46,7 @@ class StringFormatter
// DIAG support // DIAG support
static void diag( const FSH* input...); static void diag( const FSH* input...);
static void lcd(byte row, const FSH* input...); static void lcd(byte row, const FSH* input...);
static void lcd(uint8_t display, byte row, const FSH* input...);
static void printEscapes(char * input); static void printEscapes(char * input);
static void printEscape( char c); static void printEscape( char c);