diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 5b43017..47551b2 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -1,3 +1,21 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ #include #include "CommandDistributor.h" #include "WiThrottle.h" diff --git a/CommandDistributor.h b/CommandDistributor.h index e27b60e..93e4d7c 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -1,3 +1,21 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ #ifndef CommandDistributor_h #define CommandDistributor_h #include "DCCEXParser.h" diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 462bd79..9749fea 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -12,18 +12,6 @@ #include "config.h" #include "DCCEX.h" -//////////////////////////////////////////////////////////////// -// -// Enables an I2C 2x24 or 4x24 LCD Screen -#if ENABLE_LCD -bool lcdEnabled = false; -#if defined(LIB_TYPE_PCF8574) -LiquidCrystal_PCF8574 lcdDisplay(LCD_ADDRESS); -#elif defined(LIB_TYPE_I2C) -LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_LINES); -#endif -#endif - // Create a serial command parser for the USB connection, // This supports JMRI or manual diagnostics and commands // to be issued from the USB serial console. @@ -31,45 +19,22 @@ DCCEXParser serialParser; void setup() { -//////////////////////////////////////////// -// -// More display stuff. Need to put this in a .h file and make -// it a class -#if ENABLE_LCD - Wire.begin(); - // Check that we can find the LCD by its address before attempting to use it. - Wire.beginTransmission(LCD_ADDRESS); - if (Wire.endTransmission() == 0) - { - lcdEnabled = true; - lcdDisplay.begin(LCD_COLUMNS, LCD_LINES); - lcdDisplay.setBacklight(255); - lcdDisplay.clear(); - lcdDisplay.setCursor(0, 0); - lcdDisplay.print("DCC++ EX v"); - lcdDisplay.print(VERSION); - lcdDisplay.setCursor(0, 1); -#if COMM_INTERFACE >= 1 - lcdDisplay.print("IP: PENDING"); -#else - lcdDisplay.print("SERIAL: READY"); -#endif -#if LCD_LINES > 2 - lcdDisplay.setCursor(0, 3); - lcdDisplay.print("TRACK POWER: OFF"); -#endif - } -#endif - // The main sketch has responsibilities during setup() // Responsibility 1: Start the usb connection for diagnostics // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); + DIAG(F("DCC++ EX v%S"),F(VERSION)); + + CONDITIONAL_LCD_START { + // This block is ignored if LCD not in use + LCD(0,F("DCC++ EX v%S"),F(VERSION)); + LCD(1,F("Starting")); + } // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi -#ifdef WIFI_ON +#if WIFI_ON WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT); #endif // WIFI_ON @@ -83,7 +48,8 @@ void setup() // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - DCC::begin(MOTOR_SHIELD_TYPE); + DCC::begin(MOTOR_SHIELD_TYPE); + LCD(1,F("Ready")); } void loop() @@ -101,7 +67,9 @@ void loop() #if WIFI_ON WifiInterface::loop(); #endif - + + LCDDisplay::loop(); // ignored if LCD not in use + // Optionally report any decrease in memory (will automatically trigger on first call) #if ENABLE_FREE_MEM_WARNING static int ramLowWatermark = 32767; // replaced on first loop @@ -110,7 +78,7 @@ void loop() if (freeNow < ramLowWatermark) { ramLowWatermark = freeNow; - DIAG(F("\nFree RAM=%d\n"), ramLowWatermark); + LCD(2,F("Free RAM=%5db"), ramLowWatermark); } #endif } diff --git a/DCC.h b/DCC.h index 580d8b7..4bc0197 100644 --- a/DCC.h +++ b/DCC.h @@ -162,16 +162,5 @@ private: #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif -#if ENABLE_LCD -#include -#if defined(LIB_TYPE_PCF8574) -#include -extern LiquidCrystal_PCF8574 lcdDisplay; -#elif defined(LIB_TYPE_I2C) -#include -extern LiquidCrystal_I2C lcdDisplay; -#endif -extern bool lcdEnabled; -#endif #endif diff --git a/DCCEX.h b/DCCEX.h index 091bd47..fb4baef 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -11,7 +11,8 @@ #include "version.h" #include "WifiInterface.h" #include "EthernetInterface.h" - +#include "LCD_Implementation.h" +#include "freeMemory.h" #include #endif diff --git a/DIAG.h b/DIAG.h index e0d4383..6d16f78 100644 --- a/DIAG.h +++ b/DIAG.h @@ -20,4 +20,5 @@ #define DIAG_h #include "StringFormatter.h" #define DIAG StringFormatter::diag +#define LCD StringFormatter::lcd #endif diff --git a/LCDDisplay.cpp b/LCDDisplay.cpp new file mode 100644 index 0000000..e031f10 --- /dev/null +++ b/LCDDisplay.cpp @@ -0,0 +1,79 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +// CAUTION: the device dependent parts of this class are created in the .ini using LCD_Implementation.h +#include "LCDDisplay.h" + + void LCDDisplay::clear() { + clearNative(); + for (byte row=0;row=MAX_LCD_ROWS || hotCol>=MAX_LCD_COLS) return -1; + rowBuffer[hotRow][hotCol]=b; + hotCol++; + rowBuffer[hotRow][hotCol]=0; + return 1; + } + + void LCDDisplay::loop() { + if (!lcdDisplay) return; + lcdDisplay->loop2(false); + } + + LCDDisplay* LCDDisplay::loop2(bool force) { + if ((!force) && (millis() - lastScrollTime)< LCD_SCROLL_TIME) return NULL; + lastScrollTime=millis(); + clearNative(); + int rowFirst=nextFilledRow(); + if (rowFirst<0)return NULL; // No filled rows + setRowNative(0); + writeNative(rowBuffer[rowFirst]); + for (int slot=1;slot. + */ +#ifndef LCDDisplay_h +#define LCDDisplay_h +#include + +// This class is created in LCDisplay_Implementation.h + +class LCDDisplay : public Print { + + public: + static const int MAX_LCD_ROWS=8; + static const int MAX_LCD_COLS=16; + static const long LCD_SCROLL_TIME=3000; // 3 seconds + + static LCDDisplay* lcdDisplay; + LCDDisplay(); + void interfake(int p1, int p2, int p3); + + // Internally handled functions + static void loop(); + LCDDisplay* loop2(bool force); + void setRow(byte line); + void clear(); + + virtual size_t write(uint8_t b); + using Print::write; + + private: + int nextFilledRow(); + + // Relay functions to the live driver + void clearNative(); + void displayNative(); + void setRowNative(byte line); + void writeNative(char * b); + + unsigned long lastScrollTime=0; + int hotRow=0; + int hotCol=0; + int topRow=0; + int lcdRows; + void renderRow(byte row); + char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS+1]; +}; + +#endif diff --git a/LCD_Implementation.h b/LCD_Implementation.h new file mode 100644 index 0000000..1da344e --- /dev/null +++ b/LCD_Implementation.h @@ -0,0 +1,55 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +//////////////////////////////////////////////////////////////////////////////////// +// This implementation is designed to be #included ONLY ONCE in the .ino +// +// It will create a driver implemntation and a shim class implementation. +// This means that other classes can reference the shim without knowing +// which libraray is involved. +//////////////////////////////////////////////////////////////////////////////////// + +#include "config.h" +#include +#include "LCDDisplay.h" + +LCDDisplay * LCDDisplay::lcdDisplay=0; + +// Implement the LCDDisplay shim class as a singleton. +// Notice that the LCDDisplay class declaration (LCDDisplay.h) is independent of the library +// but the implementation is compiled here with dependencies on LCDDriver which is +// specific to the library in use. +// Thats the workaround to the drivers not all implementing a common interface. + +#if defined(OLED_DRIVER) + #include "LCD_OLED.h" + #define CONDITIONAL_LCD_START for (LCDDisplay * dummy=new LCDDisplay();dummy!=NULL; dummy=dummy->loop2(true)) + + +#elif defined(LCD_DRIVER) + #include "LCD_LCD.h" + #define CONDITIONAL_LCD_START for (LCDDisplay * dummy=new LCDDisplay();dummy!=NULL; dummy=dummy->loop2(true)) + +#else + #include "LCD_NONE.h" + #define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */ +#endif + + + diff --git a/LCD_LCD.h b/LCD_LCD.h new file mode 100644 index 0000000..6f88ddc --- /dev/null +++ b/LCD_LCD.h @@ -0,0 +1,37 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + #include + LiquidCrystal_I2C LCDDriver(LCD_DRIVER); // set the LCD address, cols, rows + // DEVICE SPECIFIC LCDDisplay Implementation for LCD_DRIVER + LCDDisplay::LCDDisplay() { + lcdDisplay=this; + LCDDriver.init(); + LCDDriver.backlight(); + interfake(LCD_DRIVER); + clear(); + } + void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; lcdRows=p3; } + void LCDDisplay::clearNative() {LCDDriver.clear();} + void LCDDisplay::setRowNative(byte row) { + LCDDriver.setCursor(0, row); + LCDDriver.print(F(" ")); + LCDDriver.setCursor(0, row); + } + void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); } + void LCDDisplay::displayNative() { LCDDriver.display(); } diff --git a/LCD_NONE.h b/LCD_NONE.h new file mode 100644 index 0000000..65e05e4 --- /dev/null +++ b/LCD_NONE.h @@ -0,0 +1,27 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +// dummy LCD shim to keep linker happy + LCDDisplay::LCDDisplay() {} + void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; (void)p3;} + void LCDDisplay::setRowNative(byte row) { (void)row;} + void LCDDisplay::clearNative() {} + void LCDDisplay::writeNative(char * b){ (void)b;} // + void LCDDisplay::displayNative(){} + diff --git a/LCD_OLED.h b/LCD_OLED.h new file mode 100644 index 0000000..efa2bc7 --- /dev/null +++ b/LCD_OLED.h @@ -0,0 +1,57 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +// OLED Implementation of LCDDisplay class +// Note: this file is optionally included by LCD_Implenentation.h +// It is NOT a .cpp file to prevent it being compiled and demanding libraraies even when not needed. + +#include +Adafruit_SSD1306 LCDDriver(OLED_DRIVER); + +// DEVICE SPECIFIC LCDDisplay Implementation for OLED + +LCDDisplay::LCDDisplay() { + if(LCDDriver.begin(SSD1306_SWITCHCAPVCC, 0x3C) || LCDDriver.begin(SSD1306_SWITCHCAPVCC, 0x3D)) { + DIAG(F("\nOLED display found")); + delay(2000); // painful Adafruit splash pants! + lcdDisplay=this; + LCDDriver.setTextSize(1); // Normal 1:1 pixel scale + LCDDriver.setTextColor(SSD1306_WHITE); // Draw white text + interfake(OLED_DRIVER,0); + clear(); + return; + } + DIAG(F("\nOLED display not found\n")); + } + + void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; lcdRows=p2/8; (void)p3;} + + void LCDDisplay::clearNative() {LCDDriver.clearDisplay();} + + void LCDDisplay::setRowNative(byte row) { + // Positions text write to start of row 1..n and clears previous text + int y=8*row; + LCDDriver.fillRect(0, y, LCDDriver.width(), 8, SSD1306_BLACK); + LCDDriver.setCursor(0, y); + } + + void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); } + + void LCDDisplay::displayNative() { LCDDriver.display(); } + diff --git a/StringFormatter.cpp b/StringFormatter.cpp index e99cfc3..261794a 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -31,6 +31,8 @@ #define __FlashStringHelper char #endif +#include "LCDDisplay.h" + bool Diag::ACK=false; bool Diag::CMD=false; bool Diag::WIFI=false; @@ -44,6 +46,14 @@ void StringFormatter::diag( const __FlashStringHelper* input...) { send2(diagSerial,input,args); } +void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) { + if (!LCDDisplay::lcdDisplay) return; + LCDDisplay::lcdDisplay->setRow(row); + va_list args; + va_start(args, input); + send2(LCDDisplay::lcdDisplay,input,args); +} + void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) { va_list args; va_start(args, input); @@ -56,7 +66,6 @@ void StringFormatter::send(Print & stream, const __FlashStringHelper* input...) send2(&stream,input,args); } - void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va_list args) { // thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output @@ -66,6 +75,13 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va char c=pgm_read_byte_near(flash+i); if (c=='\0') return; if(c!='%') { stream->print(c); continue; } + + bool formatContinues=false; + byte formatWidth=0; + bool formatLeft=false; + do { + + formatContinues=false; i++; c=pgm_read_byte_near(flash+i); switch(c) { @@ -73,14 +89,34 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va case 'c': stream->print((char) va_arg(args, int)); break; case 's': stream->print(va_arg(args, char*)); break; case 'e': printEscapes(stream,va_arg(args, char*)); break; + case 'E': printEscapes(stream,(const __FlashStringHelper*)va_arg(args, char*)); break; case 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); break; - case 'd': stream->print(va_arg(args, int), DEC); break; - case 'l': stream->print(va_arg(args, long), DEC); break; + case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; + case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; case 'b': stream->print(va_arg(args, int), BIN); break; case 'o': stream->print(va_arg(args, int), OCT); break; case 'x': stream->print(va_arg(args, int), HEX); break; case 'f': stream->print(va_arg(args, double), 2); break; + //format width prefix + case '-': + formatLeft=true; + formatContinues=true; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + formatWidth=formatWidth * 10 + (c-'0'); + formatContinues=true; + break; } + } while(formatContinues); } va_end(args); } @@ -94,6 +130,17 @@ void StringFormatter::printEscapes(Print * stream,char * input) { } } +void StringFormatter::printEscapes(Print * stream, const __FlashStringHelper * input) { + + if (!stream) return; + char* flash=(char*)input; + for(int i=0; ; ++i) { + char c=pgm_read_byte_near(flash+i); + printEscape(stream,c); + if (c=='\0') return; + } +} + void StringFormatter::printEscape( char c) { printEscape(diagSerial,c); } @@ -109,4 +156,27 @@ void StringFormatter::printEscape(Print * stream, char c) { default: stream->print(c); } } + + +void StringFormatter::printPadded(Print* stream, long value, byte width, bool formatLeft) { + if (width==0) { + stream->print(value, DEC); + return; + } + + int digits=(value <= 0)? 1: 0; // zero and negative need extra digot + long v=value; + while (v) { + v /= 10; + digits++; + } + + if (formatLeft) stream->print(value, DEC); + while(digitsprint(' '); + digits++; + } + if (!formatLeft) stream->print(value, DEC); + } + diff --git a/StringFormatter.h b/StringFormatter.h index 62d2492..5cbccf1 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -27,6 +27,7 @@ #define __FlashStringHelper char #endif +#include "LCDDisplay.h" class Diag { public: static bool ACK; @@ -42,16 +43,19 @@ class StringFormatter static void send(Print & serial, const __FlashStringHelper* input...); static void printEscapes(Print * serial,char * input); + static void printEscapes(Print * serial,const __FlashStringHelper* input); static void printEscape(Print * serial, char c); // DIAG support static Print * diagSerial; static void diag( const __FlashStringHelper* input...); + static void lcd(byte row, const __FlashStringHelper* input...); static void printEscapes(char * input); static void printEscape( char c); private: static void send2(Print * serial, const __FlashStringHelper* input,va_list args); + static void printPadded(Print* stream, long value, byte width, bool formatLeft); }; #endif diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 044b00b..5c536b5 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -103,7 +103,7 @@ void WiThrottle::parse(Print & stream, byte * cmdx) { // we have to take a copy of the cmd buffer as the reply will get built into the cmdx byte local[150]; - for (byte i=0;i