LCD behaves differently when used as a class member.

Okay, so this bug is very strange. I am using the LiquidCrystal.h library to use a display. If I use the lcd.setCursor function in my normal code it properly places the cursor on the LCD. However, if I call lcd.setCursor from within a class with a reference to the lcd object, the column it gets sent to is correct but the row is zero instead of one.

I have added two sample sketches which reproduce the behavior and are about as simple as I can make them. As best I can tell the code should literally be doing the same thing since both are nominally acting on the same object.

It's super strange. Does this happen to anyone else? I'm not even sure how such a bug could arise.

LCD_Test.ino (204 Bytes)

LCD_Test_Class.ino (402 Bytes)

But when I pass the lcd as a pointer it works fine. So this is probably me misunderstanding some detail about references.

Where is the bug? When the display geometry is initialized within the class, everything is fine! I have tested using 20x4 PLCD and MEGA.

#include <LiquidCrystal.h>
LiquidCrystal lcd(28, 26, 25, 24, 23, 22);

class mouse {
              private:
                LiquidCrystal _lcd;
              public:
                mouse (LiquidCrystal &lcd) : _lcd(lcd){};
                void writeTest(void) 
                {
                  _lcd.begin(20, 4);
                  _lcd.setCursor(7, 3);  //8th character position of Line-4
                  _lcd.print("Test");
                };
            };

mouse myMouse(lcd);

void setup() 
{
 //lcd.begin(40, 2);  //40 charcters and 2-line
// lcd.begin(20, 4);    //20 character 4 line LCD (mine)
// lcd.setCursor(0, 3);
  lcd.clear();
  lcd.display();
  myMouse.writeTest();
}

void loop() 
{

}

Please, post your new codes.

I would appreciate if someone clarifies the syntax/semantic rules of C++ that require that the geometry of the LCD should be initialized within the class! What about the other functions -- lcd.clear(); and lcd.display();? Will they work outside the class?

I'll post them each in their own code box, the attachment feature is a bit ackward. My display is a LCM-S04004DSF and uses an Arduino Due. Each block is a complete program and should be duplicable if you adjust the pin numbers.

This version works correctly, and displays the text on the second line of the display.

#include <LiquidCrystal.h>

LiquidCrystal lcd(28, 26, 25, 24, 23, 22);

void setup() {
  lcd.begin(40, 2);
  lcd.clear();
  lcd.display();

  lcd.setCursor(0, 1);
  lcd.print("Test");
}

void loop() {

}

This version works incorrectly, and displays the text on the first line of the display because for some reason _lcd.setCursor(0, 1) did not actually move the cursor. But it's clearly doing something because the text still appears, so one function works but the other doesn't.

#include <LiquidCrystal.h>

class mouse {
  private:
    LiquidCrystal _lcd;
  public:
    mouse(LiquidCrystal &lcd) : _lcd(lcd) {};
    void writeTest(void) {
      _lcd.setCursor(0, 1);
      _lcd.print("Test");
    };
};

LiquidCrystal lcd(28, 26, 25, 24, 23, 22);
mouse myMouse(lcd);

void setup() {
  lcd.begin(40, 2);
  lcd.clear();
  lcd.display();
  myMouse.writeTest();
}

void loop() {

}

But this version works fine again. All I've done is pass it using a pointer instead of a reference, swapping _lcd.setCursor for _lcd->setCursor, so my understanding is that these are completely identical. Yet for some reason they are not.

#include <LiquidCrystal.h>

class mouse {
  private:
    LiquidCrystal* _lcd;
  public:
    mouse(LiquidCrystal* lcd) : _lcd(lcd) {};
    void writeTest(void) {
      _lcd->setCursor(0, 1);
      _lcd->print("Test");
    };
};

LiquidCrystal lcd(28, 26, 25, 24, 23, 22);
mouse myMouse(&lcd);

void setup() {
  lcd.begin(40, 2);
  lcd.clear();
  lcd.display();
  myMouse.writeTest();
}

void loop() {

}

Your display is 40x4. This requires two controllers. Often this is handled by a separate class.

The regular displays e.g. 8x2, 16x1, 16x2, 20x2, 20x4, 40x2 only have a single controller.

I only have 16x2 and 20x4. Access by pointer or class member should behave the same.

If you have paid £45 for a 40x4 display, it seems a bit wasteful to only use 40x2 i.e. half of the rows.

David.

I didn't include the second controller in the example code; I would think it should be similar on any two row device, but I don't know how it works (or why it fails).

So if I'm definitely seeing this repeatably and controllably, but others can't reproduce it, it seems like it must be something about my hardware. Perhaps the Due version of the library has some small difference? But what possible difference could cause correct and nominally identical code to behave differently?

So I think it must be something about how I'm using the references and pointers that I'm missing. But if that were the case then you all should be able to replicate it.

I suppose I have a solution that works, so for anyone else with this problem -- use pointers. Feel free to tell us why though =)

Oh I can definitely repeat it.
You need to make your _lcd a reference to the original object vs a copy of it. :wink:
i.e.

 private:
    LiquidCrystal & _lcd;

You don't want to make a copy of a LiquidCrystal object, particularly before you call begin()
The LiquidCrystal constructor defaults to a 16x1 geometry. When you call begin() you are setting the geometry and usually to something different than 16x1.
Different geometries use different address for the column 0 on each line and the begin() function sets up a table for all the column 0 addresses. Also of particular importance in this case is _numlines which gets set in begin().
So if you make a copy of a LiquidCrystal object before calling begin(), you might be using incorrect column 0 addresses and will be using _numlines = 1.

Your second example is making a copy of the lcd object.
The mouse() lcd parameter is by reference but then _lcd(lcd) initializer is equilivant to _lcd = lcd so the contents of the lcd object is assigned to _lcd so you have now made a copy of it into a new LiquidCrystal object.
So now lcd and _lcd are not the same object and since this is happening before begin() is called with the correct geometry, the two objects will be using different _numlines. setCursor() enforces that the row is not >= _numlines so if you make a copy of the lcd object before you called begin() with the correct number of rows/lines, you won't be able to use anything but line/row 0.

--- bill

Fascinating! Thanks Bill, that made all three do what I expected and was a very clear explanation. Sounds like I was just using bad logic for how to use it.