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:
parent
a9971968c0
commit
8be9d9e0b0
1
DIAG.h
1
DIAG.h
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!!
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
131
IO_OLEDDisplay.h
131
IO_OLEDDisplay.h
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user