diff --git a/DIAG.h b/DIAG.h index c942e59..adb7e07 100644 --- a/DIAG.h +++ b/DIAG.h @@ -24,4 +24,5 @@ #include "StringFormatter.h" #define DIAG StringFormatter::diag #define LCD StringFormatter::lcd +#define LCD2 StringFormatter::lcd #endif diff --git a/DisplayInterface.h b/DisplayInterface.h index 64fc1de..d9afdc4 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -28,8 +28,18 @@ class DisplayInterface : public Print { public: virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; }; virtual void setRow(byte line) { (void)line; }; - virtual void clear() {}; + virtual void clear() { }; 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; }; diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index e925d96..11ad4a9 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1209,6 +1209,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { case thrunge_parse: case thrunge_broadcast: case thrunge_lcd: + default: // thrunge_lcd+1, ... if (!buffer) buffer=new StringBuffer(); buffer->flush(); stream=buffer; @@ -1244,7 +1245,9 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { case thrunge_lcd: LCD(id,F("%s"),buffer->getString()); break; - - default: break; + default: // thrunge_lcd+1, ... + if (mode > thrunge_lcd) + LCD2(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display + break; } } diff --git a/EXRAIL2.h b/EXRAIL2.h index 12bca3b..2c4ad96 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -73,11 +73,15 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_IFLOCO }; +// Ensure thrunge_lcd is put last as there may be more than one display, +// sequentially numbered from thrunge_lcd. enum thrunger: byte { thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, thrunge_serial1, thrunge_serial2, thrunge_serial3, thrunge_serial4, thrunge_serial5, thrunge_serial6, - thrunge_lcd, thrunge_lcn}; + thrunge_lcn, + thrunge_lcd, // Must be last!! + }; diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index e54e48b..b81c86f 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -80,6 +80,7 @@ #undef KILLALL #undef LATCH #undef LCD +#undef LCD2 #undef LCN #undef MOVETT #undef ONACTIVATE @@ -198,7 +199,8 @@ #define JOIN #define KILLALL #define LATCH(sensor_id) -#define LCD(row,msg) +#define LCD(row,msg) +#define LCD2(display,row,msg) #define LCN(msg) #define MOVETT(id,steps,activity) #define ONACTIVATE(addr,subaddr) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 3d3fece..599664b 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -45,7 +45,7 @@ // 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. -// 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. // CAUTION: The macros below are multiple passed over myAutomation.h @@ -142,6 +142,15 @@ const int StringMacroTracker1=__COUNTER__; tmode=thrunge_lcd; \ lcdid=id;\ 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) { @@ -298,6 +307,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define KILLALL OPCODE_KILLALL,0,0, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) +#define LCD2(display,id,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 ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), @@ -368,6 +378,8 @@ const HIGHFLASH byte RMFT2::RouteCode[] = { // Restore normal code LCD & SERIAL macro #undef LCD #define LCD StringFormatter::lcd +#undef LCD2 +#define LCD2 StringFormatter::lcd #undef SERIAL #define SERIAL 0x0 #endif diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 859f82d..7cdb34e 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -51,6 +51,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { private: + uint8_t _displayNo = 0; // Here we define the device-specific variables. uint8_t _height; // in pixels uint8_t _width; // in pixels @@ -64,16 +65,22 @@ private: uint8_t *_lastRowGeneration = NULL; uint8_t _rowNoToScreen = 0; uint8_t _charPosToScreen = 0; + DisplayInterface *_nextDisplay = NULL; + uint8_t _selectedDisplayNo = 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); + /* 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: // Constructor - OLEDDisplay(I2CAddress i2cAddress, int width, int height) { + OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { + _displayNo = displayNo; _I2CAddress = i2cAddress; _width = width; _height = height; @@ -89,9 +96,26 @@ protected: // Fill buffer with spaces 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 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); } @@ -99,15 +123,9 @@ protected: void _begin() override { // 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 %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 for (uint8_t row=0; row<_numRows; row++) @@ -120,7 +138,10 @@ protected: } void _loop(unsigned long) override { + screenUpdate(); + } + void screenUpdate() { // 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, @@ -156,54 +177,82 @@ protected: // ///////////////////////////////////////////////// DisplayInterface* loop2(bool force) override { - (void)force; // suppress compiler warning + //screenUpdate(); + if (_nextDisplay) + return _nextDisplay->loop2(force); // continue to next display 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 == 255) { - // LCD(255, "xxx") - scroll the contents of the buffer - // and put the new line at the bottom of the screen - for (int row=1; row<_numRows; row++) { - 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. + // The displayNo referenced here is remembered and any following + // calls to write() will be directed to that display. + void setRow(uint8_t displayNo, byte line) override { + _selectedDisplayNo = displayNo; + if (displayNo == _displayNo) { + if (line == 255) { + // LCD(255,"xxx") or LCD2(displayNo,255, "xxx") - + // scroll the contents of the buffer and put the new line + // at the bottom of the screen + for (int row=1; row<_numRows; row++) { + 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; - // 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]++; + _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]++; + + } 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) - void clear () override { - // Clear buffer - for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { - setRow(_rowNo); - } - _rowNo = 0; + void clear (uint8_t displayNo) override { + if (displayNo == _displayNo) { + // Clear buffer + for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { + setRow(displayNo, _rowNo); + } + _rowNo = 0; + } else if (_nextDisplay) + _nextDisplay->clear(displayNo); // Pass to next display } - - // 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; + + // 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 Configured addr %s"), _I2CAddress.toString()); + DIAG(F("OLEDDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); } }; diff --git a/StringFormatter.cpp b/StringFormatter.cpp index c1f20c4..382c484 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -51,6 +51,16 @@ void StringFormatter::lcd(byte row, const FSH* input...) { 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...) { va_list args; va_start(args, input); diff --git a/StringFormatter.h b/StringFormatter.h index 4787715..80543b9 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -46,6 +46,7 @@ class StringFormatter // DIAG support static void diag( 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 printEscape( char c);