Println() or endl for LCD (Swapping Serial and LCD)

I've been trying to set up a project in such in a way that I can comment/uncomment one line and switch between displaying on a 16x2 LCD or displaying on the Serial monitor.

Rather than littering my code with #ifdef SERIAL ... #else and writing two sets of instructions for everything, I've been using the Arduino Streaming library: GitHub - janelia-arduino/Streaming: Streaming C++-style Output with Operator << and I'm having some success!

This for example works great:

#define LCD_ROWS 2
#define LCD_COLS 16
#define LCD_ADDR 0x27
#include "Streaming.h"
#include "LiquidCrystal_I2C.h"
LiquidCrystal_I2C lcd(0x27, 16, 2);

// #define SERIAL_DISPLAY_OVERRIDE

#ifdef SERIAL_DISPLAY_OVERRIDE
  #define DISPLAY Serial
#else
  #define DISPLAY lcd
#endif

#define BAUDRATE 115200

class Foo {
  public:
    const char * name;
    int value;
    void display(Print& p) {
       p << name << " " << value; 
    }
    Foo(const char * name, int value)
      : name(name), value(value) { }
};

void setup() {
  Serial.begin(BAUDRATE);
  lcd.init();
  lcd.backlight();
  Foo foo("Test", 69);
  foo.display(DISPLAY);
}

void loop() { }

Simply comment or uncomment #define SERIAL_DISPLAY_OVERRIDE

My issue comes when I want to do the same thing but instead of printing on the same line, I want the name and value printed on two lines. The streaming lib supports endl but the LiquidCrystal lib does not.

Are there any thoughts on how I might achieve this?

I have a kind of hacky attempt here that seems to work is there a way to actually get << endl << to work?

#define LCD_ROWS 2
#define LCD_COLS 16
#define LCD_ADDR 0x27
#include "Streaming.h"
#include "LiquidCrystal_I2C.h"
LiquidCrystal_I2C lcd(0x27, 16, 2);

#define SERIAL_DISPLAY_OVERRIDE

#ifdef SERIAL_DISPLAY_OVERRIDE
  #define DISPLAY Serial
#else
  #define DISPLAY lcd
#endif

#define BAUDRATE 115200

Print& endLine() {
#ifdef SERIAL_DISPLAY_OVERRIDE
  DISPLAY << endl;
#else
  DISPLAY.setCursor(0,1);
#endif
  return DISPLAY;
}

class Foo {
  public:
    const char * name;
    int value;
    void display(Print& p) {
      #ifndef SERIAL_DISPLAY_OVERRIDE
        lcd.clear();
        lcd.setCursor(0,0);
      #endif
      
      p << name; endLine() << value; 
    }
    Foo(const char * name, int value)
      : name(name), value(value) { }
};

void setup() {
  Serial.begin(BAUDRATE);
  lcd.init();
  lcd.backlight();
  Foo foo("Test", 69);
  foo.display(DISPLAY);
}

void loop() { }

Because I can't use: p << name << endl << value;

What if your display is 20*4 ?
You could walk through the string and print a single character at a time and advance a row when needed.
Do you also want to scroll it up, like a terminal ?

Something like this could account for a 2 or 4 line LCD, which is nice, but doesn't solve applying '\n' or '\r' or println() to LCD just yet.

#define LCD_ROWS 2
#define LCD_COLS 16
#define LCD_ADDR 0x27
#include "Streaming.h"
#include "LiquidCrystal_I2C.h"
LiquidCrystal_I2C lcd(0x27, LCD_COLS, LCD_ROWS);

// #define SERIAL_DISPLAY_OVERRIDE

#ifdef SERIAL_DISPLAY_OVERRIDE
  #define DISPLAY Serial
#else
  #define DISPLAY lcd
#endif

#define BAUDRATE 115200

Print& endLine() {
#ifdef SERIAL_DISPLAY_OVERRIDE
  DISPLAY << endl;
#else
  static byte line = 0;
  ++line %= LCD_ROWS;
  DISPLAY.setCursor(0, line);
#endif
  return DISPLAY;
}

class Foo {
  public:
    const char * name;
    int value;
    void display(Print& p) {
      #ifndef SERIAL_DISPLAY_OVERRIDE
        lcd.clear();
        lcd.setCursor(0,0);
      #endif
      
      p << name; endLine() << value;
    }
    Foo(const char * name, int value)
      : name(name), value(value) { }
};

void setup() {
  Serial.begin(BAUDRATE);
  lcd.init();
  lcd.backlight();
  Foo foo("Test", 69);
  foo.display(DISPLAY);
}

void loop() { }

Okay, getting mildly warmer... I've irradicated the need for the endLine() function by simply adding:

void println() {
  setCursor(0, 1);
}

In the LiquidCrystal .h file (as it had no implementation anyway)

For my use case... If I'm using endl then the LCD cursor would 'have' to already be at row 0 because it's only a two line LCD. So that's no issue.

Then for the main code (with the addition above) we've shortened it to:

#define LCD_ROWS 2
#define LCD_COLS 16
#define LCD_ADDR 0x27
#include "Streaming.h"
#include "LiquidCrystal_I2C.h"
LiquidCrystal_I2C lcd(0x27, LCD_COLS, LCD_ROWS);


// #define SERIAL_DISPLAY_OVERRIDE

#ifdef SERIAL_DISPLAY_OVERRIDE
  #define DISPLAY Serial
#else
  #define DISPLAY lcd
#endif

#define BAUDRATE 115200

class Foo {
  public:
    const char * name;
    int value;
    void display(Print& p) {
      p << name; DISPLAY.println(); p << value; 
    }
    Foo(const char * name, int value)
      : name(name), value(value) { }
};

void setup() {
  Serial.begin(BAUDRATE);
  lcd.init();
  lcd.backlight();
  Foo foo("Test", 69);
  foo.display(DISPLAY);
  lcd.println();
}

void loop() { }

Which actually is probably usable now! Shame I can't get '\n' or 'endl' working.

Is there a way to do a similar thing like is in Streaming.h i:e

enum _EndLineCode { endl };

inline Print &operator <<(Print &obj, _EndLineCode)
{ obj.println(); return obj; }

But add that into the LCD library instead and replace obj.println() with a setCursor()?

Not sure that this is what you want, but with the hd44780 library for LCDs (available via the IDE library manager) you can enable line wrap.

I'd say it could be a halfway house! The trick then, will be to get the wrapping to always land at the start of the next line.

It does kind of suprise me that they havent added println to any of the libraries. They must be keeping track of the cursor pos. So do the print then reset the cursor cols and bump the cursor down a line (wrapping if above the configured cols)

not shure if it fits for you, but you could adopt the write method of the LCD to handle the LF as real "line feed" and wrap around if you are at the end of your available rows.

// https://forum.arduino.cc/t/println-or-endl-for-lcd/945695/5

#include "Streaming.h"

#include <Wire.h>
#include <NoiascaLiquidCrystal.h>      // download library from https://werner.rothschopf.net/202009_arduino_liquid_crystal_intro.htm/
#include <NoiascaHW/lcd_PCF8574.h>     // include the proper IO interface
const byte cols = 16;                  // columns/characters per row
const byte rows = 2;                   // how many rows
const byte addr = 0x3F;                // set the LCD address to 0x3F or 0x27

//LiquidCrystal_PCF8574 lcd(addr, cols, rows);  // create lcd object - with support of special characters

class MyLCD : public LiquidCrystal_PCF8574 {
  public:
    size_t write(uint8_t value) {
      if (value == 13)
      {
        currentRow++;                             // next line
        if (currentRow>=rows) currentRow = 0;
        setCursor(0, currentRow);
      }
      else if (value != 10)                       // ignore CR
      {
        switch (funcPtr (special, value))         // rest handle with converter
        {
          case NOPRINT :       // don't print
            break;
          case ADDE :          // print an additional e after character
            send(value, rsDR);
            send('e', rsDR);   // add a small e after the printed character
            break;
          default :            // includes 1: just print
            send(value, rsDR);
        }
      }
      return 1;
    }
  public:
    MyLCD (byte addr, byte cols, byte rows) : LiquidCrystal_PCF8574(addr, cols, rows) {}
};

MyLCD lcd(addr, cols, rows);

void lcdSetup()
{
  Wire.begin();                        // start I2C library
  lcd.begin();                         // initialize the LCD
  lcd.backlight();                     // turn on backlight
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("LCD Streaming"));
  lcdSetup();
  lcd << "Test0"  << endl << "Test1" << endl << "Test2" << endl << "Test3";
}

void loop() {
}

inherited from my NoiascaLiquidCrystal lib, as it already keeps track of the current line (otherwise you would need your own home, clear and setCursor in your derived class).

Thanks both, lots to consider and unpack there! I'm certain from the answers posted there could be an elegant way to do this.

My 'throw poop at the wall until it sticks' approach has somehow found a dirty solution...

If I include the Streaming lib AFTER I declare the lcd object, I can access it inside the Streaming.h file (eek!). So if I then add a new _EndLineCode to the enum I can do this:

//inside Streaming.h
enum _EndLineCode { endl, endl_LCD};

inline Print &operator <<(Print &obj, _EndLineCode e) { 
  if (e)
    lcd.setCursor(0,1);
  else
    obj.println();
  return obj; }

This allows my macro mess to finish the job with:

#ifdef SERIAL_DISPLAY_OVERRIDE
  (...)
#else
  (...)
  #define endl endl_LCD
#endif

like so:

#define LCD_ROWS 2
#define LCD_COLS 16
#define LCD_ADDR 0x27

#include "LiquidCrystal_I2C.h"
LiquidCrystal_I2C lcd(0x27, LCD_COLS, LCD_ROWS);

#include "Streaming.h" 

// #define SERIAL_DISPLAY_OVERRIDE

#ifdef SERIAL_DISPLAY_OVERRIDE
  #define DISPLAY Serial
  #define LCD_INIT() void
  #define LCD_BACKLIGHT() void
  #define LCD_CLEAR() void
  #define LCD_SETCURSOR(x, y) (void)(x, y)
#else
  #define DISPLAY lcd
  #define LCD_INIT() lcd.init()
  #define LCD_BACKLIGHT() lcd.backlight();
  #define LCD_CLEAR() lcd.clear()
  #define LCD_SETCURSOR(x, y) lcd.setCursor(x, y)
  #define endl endl_LCD
#endif



#define BAUDRATE 115200

class Foo {
  public:
    const char * name;
    int value;
    void display(Print& p) {
      LCD_CLEAR();
      LCD_SETCURSOR(0, 0);
      p << name << endl << value;
    }
    Foo(const char * name, int value)
      : name(name), value(value) { }
};

void setup() {
  Serial.begin(BAUDRATE);
  LCD_INIT();
  LCD_BACKLIGHT();
  Foo foo("Test", 69);
  foo.display(DISPLAY);
}
 
void loop() { }

This now works for both with the SERIAL_DISPLAY_OVERRIDE on/off and 'deletes' any LCD code from the sketch when the override is enabled.

I don't like it, but it seems to work so far :smiley: any thoughts?

at least I would make two version of a function

#ifdef SERIAL_DISPLAY_OVERRIDE                    // Serial
void generalBegin() 
{  
 Serial.begin(115200);
}
#else
void generalBegin() 
{  
  lcd.init();
  lcd.clear(); // mostly not needed as most of the libs call it after / during init anyway.
  lcd.backlight();
}

and just call the generalBegin() in setup() to get rid of some macros

That does clean it up a bit more. Thanks!

don't want to be a bean counter but...

#define LCD_ADDR 0x27 
LiquidCrystal_I2C lcd(0x27, LCD_COLS, LCD_ROWS);

doesn't look soo nice neither, does it?

Count those beans kind Sir! This was only a demo test sketch for battle testing. Can you think of any better ideas of making this do the same thing? Some of it feels kind of backwards to me. Are there 'proper' ways of achieving the same result?

... if you define a macro for the I2C address you should use it

LiquidCrystal_I2C lcd(LCD_ADDR , LCD_COLS, LCD_ROWS);

but in general, in 2022 i would use const or const expresssions:

#include "LiquidCrystal_I2C.h"
constexpr byte lcd_rows = 2;
constexpr byte lcd_cols = 16;
constexpr byte lcd_addr = 0x27;
LiquidCrystal_I2C lcd(lcd_addr, lcd_cols, lcd_rows);

no need for a precompiler #define

Ah no, I had adding the define into the lcd constructor no worries, just a 'forget' by me.... I meant for the overall approach to getting endl to work?

I would dislike

  • to have the need to change a library
  • to have the "toggle LCD line" as add on in the streaming function instead of the the LCD class

but if it works for you keep it.
Your code - your freedom :wink:

True, but minimal edits on my part. Thanks for your help!

I found a better solution to the original problem I mentioned (not the one in the title) where I was trying to solve swapping between serial and LCD with a single #define. Although it is more code. That is to use the Visitor design pattern.

Several different classes in my code all need printing or displaying to an LCD. And so if you define the accept(IVisitor* visitor) in each class that needs printing then you can create a display visitor that can creep into the class and pull back anything it requires to be printed/displayed.

That then allows one class to isolate and handle all the printing. Which meant I could create two classes, a SerialVisitor and an LCD Visitor (both inheriting from a Visitor Interface) and swap between them as neeed (even during runtime if that was required, but it would need a very small tweak).

So that meant I could use the #define to do:

#define SERIAL_NOT_LCD

#ifdef SERIAL_NOT_LCD
  IVisitor* v = new SerialVisitor;
#else
  IVisitor* v = new LCDVisitor;
#endif

As an 'example code' this is a little messy because I was using it to 'sandbox' the idea... But it does work!

// #define SERIAL_NOT_LCD  // Uncomment to ENABLE serial and DISABLE LCD

#include "LiquidCrystal_I2C.h"
LiquidCrystal_I2C lcd(0x27, 16, 2);

class Foo;
class Bar;

class IVisitor {
  public:
    virtual void init() {}
    virtual void visit(Foo& f) = 0;
    virtual void visit(Bar& b) = 0;
};

class Foo {
  public:
    int i = 69;
    void accept(IVisitor* v) {
      v->visit(*this);
    }
};

class Bar {
  public:
    String s = "It Works!";
    void accept(IVisitor* v) {
      v->visit(*this);
    }
};

class SerialVisitor : public IVisitor
{
  public:
    void init() override {
      Serial.begin(115200);
    }
    void visit(Foo& f) override {
      Serial.println("From Foo:");
      Serial.println(f.i);
      Serial.println();
    }

    void visit(Bar& b) override {
      Serial.println("From Bar:");
      Serial.println(b.s);
      Serial.println();
    }
};


class LCDVisitor : public IVisitor {
  public:
    void init() override {
      lcd.init();
      lcd.backlight();
    }

    void visit(Foo& f) override {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("From Foo:");
      lcd.setCursor(0, 1);
      lcd.print(f.i);
    }

    void visit(Bar& b) override {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("From Bar:");
      lcd.setCursor(0, 1);
      lcd.print(b.s);
    }
};

Foo f;
Bar b;

#ifdef SERIAL_NOT_LCD
  IVisitor* v = new SerialVisitor;
#else
  IVisitor* v = new LCDVisitor;
#endif

void setup() {
  v->init();
  f.accept(v);
  delay(1500);
  b.accept(v);
}

void loop() {
  // put your main code here, to run repeatedly:
}

It's also called 'Double Dispatch' apparently, it's kind of like having a switch case that acts based on the object type, but achieved using function overloads.

It also groups all the display/printing logic into one place which is kind of nice. And allows for near limitless extension for the future too. Want to create an 'OLEDVisitor' or 'PrintToWebPageVisitor' ? No problem...Just write it without changing (Breaking!) any existing code.

Visitor, in general, seems to basically be a (guarded) backdoor into whatever class you need it in, so you can also use it to create other visitors that can do things completely unrelated to printing.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.