Moving code to a library but can't get the imported LCD library to work [solved]

I'm trying to clean up my sketch by putting related code chunks into libraries, but I'm having trouble with the display code; specifically, I can't seem to get the hd44780 library to work from a cpp file rather than a sketch.

This works fine in a sketch:

#include <hd44780.h>
#include <hd44780ioClass/hd44780_pinIO.h>


const int rs = 8, en = 9, db4 = 4, db5 = 5, db6 = 6, db7 = 7;
hd44780_pinIO lcd(rs, en, db4, db5, db6, db7);


const int LCD_COLS = 20;
const int LCD_ROWS = 4;


void setup() {
  lcd.begin(LCD_COLS, LCD_ROWS);
  lcd.setCursor(0, 0);          // col 1, row 1
  lcd.print("***              ***");
}

void loop() {
  

}

Where do I put each part of that in c++ class files?

I've tried every which way that I could think of, but here's one:

sketch:

#include "UIv2.h"

UI ui();

void setup() {

}

void loop() {
  

}

UIv2.h:

#ifndef UIV2_H_
#define UIV2_H_

#include <hd44780.h>
#include <hd44780ioClass/hd44780_pinIO.h>

class UI {
public:

	UI();
	virtual ~UI();

};

#endif /* UIV2_H_ */

UIv2.cpp

#include "UIv2.h"

const int rs = 8, en = 9, db4 = 4, db5 = 5, db6 = 6, db7 = 7;
hd44780_pinIO lcd(rs, en, db4, db5, db6, db7);

const int LCD_COLS = 20;
const int LCD_ROWS = 4;

UI::UI() {
	lcd.begin(LCD_COLS, LCD_ROWS);
	lcd.setCursor(0, 0);          // col 1, row 1
	lcd.print("***              ***");
}



UI::~UI() {
	// TODO Auto-generated destructor stub
}

I swear I had this working a few days ago, but I botched it somehow and now I can't figure out what I had done. Thank you for any help!

The cpp file should NOT declare any variables. The header file should. If you REALLY need to have your library create the LCD object, and I would argue that you should NOT do that, you need to create the instance at the same time as you create your instance:

UI::UI() : lcd(rs, en, db4, db5, db6, db7)
{
}

The arguments to the lcd constructor should be passed to your constructor.

I think part of the problem might be:

UI ui();

I think you wanted to create an instance of the UI class named 'ui'. What I think you did is declare a function named 'ui' which returns an object of class UI and takes no arguments. I think what you want is:

UI ui;

The constructor of your global UI instance will run before the platform is initialized, so it's not recommended to use lcd methods. I'd recommend adding a "begin" method where you do your initialization, instead of doing it in the constructor.

Pieter

PaulS, johnwasser, and PieterP, thank you for your replies.

PaulS, you say I should not have my library create the LCD object. I'm listening. Create it in the sketch and pass it by pointer? I was considering that, but then won't it be local to setup() and thus vanish at the end of setup? Creating it in loop doesn't sound right, either. I feel like I'm not understanding what you meant.

johnwasser, I had a V8 moment reading your reply. Facepalm. Yes, you're absolutely right. That might have been the problem all along. I come over from the Microchip-C crowd and my old habits bit me in the butt.

PieterP, that sounds like a good solution.

I'll work on it this eve and hopefully post my fixed and working code. Thank you all for your guidance.

I can't believe I did this. Long story short, the files that I was saving in Eclipse were not the files that the Arduino IDE were using in its builds. So of course, absolutely nothing I did worked, because none of it was ever used by the compiler.

Sigh.

If only using Eclipse for Arduino development was easier. I messed around for DAYS trying to switch over to Eclipse only, but could never get it to work. In the major mess from that, I ended up with two sets of files.

I'm going back to the simpler Geany where I could get things done.

Tomorrow I will face it anew.

Incorporating my new knowledge, I have moved the LCD set up code to the sketch and I am trying to pass the object by reference. This seems to work fine for the receiving function, but if I want to hold on to that for future use by the class, I don't know how to store it as a reference. What I mean is, if I tell the class here's a declaration for later, it tries to construct the object instead of waiting to receive the object later:

.h

class UI {
public:
 UI();
 void go(hd44780_pinIO & _lcd);
 virtual ~UI();

private:
 hd44780_pinIO lcd;
//protected:
};

.cpp

UI::UI() {
}

void UI::go(hd44780_pinIO & _lcd) {
 lcd = _lcd;
 lcd.setCursor(0, 0);          // col 1, row 1
 lcd.print("***              ***");
}

At compile I get what I would expect:

/Users/ncd/Documents/Arduino/libraries/UIv2/UIv2.cpp: In constructor 'UI::UI()':
/Users/ncd/Documents/Arduino/libraries/UIv2/UIv2.cpp:9:8: error: no matching function for call to 'hd44780_pinIO::hd44780_pinIO()'
 UI::UI() {
        ^
In file included from /Users/ncd/Documents/Arduino/libraries/UIv2/UIv2.h:9:0,
                 from /Users/ncd/Documents/Arduino/libraries/UIv2/UIv2.cpp:3:
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:113:1: note: candidate: hd44780_pinIO::hd44780_pinIO(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t)
 hd44780_pinIO(uint8_t rs,  uint8_t rw, uint8_t en,
 ^
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:113:1: note:   candidate expects 9 arguments, 0 provided
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:98:1: note: candidate: hd44780_pinIO::hd44780_pinIO(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t)
 hd44780_pinIO(uint8_t rs,  uint8_t rw, uint8_t en,
 ^
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:98:1: note:   candidate expects 7 arguments, 0 provided
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:81:1: note: candidate: hd44780_pinIO::hd44780_pinIO(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t)
 hd44780_pinIO(uint8_t rs,  uint8_t en,
 ^
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:81:1: note:   candidate expects 8 arguments, 0 provided
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:66:1: note: candidate: hd44780_pinIO::hd44780_pinIO(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t)
 hd44780_pinIO(uint8_t rs,  uint8_t en,
 ^
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:66:1: note:   candidate expects 6 arguments, 0 provided
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:58:7: note: candidate: constexpr hd44780_pinIO::hd44780_pinIO(const hd44780_pinIO&)
 class hd44780_pinIO : public hd44780
       ^
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:58:7: note:   candidate expects 1 argument, 0 provided
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:58:7: note: candidate: constexpr hd44780_pinIO::hd44780_pinIO(hd44780_pinIO&&)
/Users/ncd/Documents/Arduino/libraries/hd44780/hd44780ioClass/hd44780_pinIO.h:58:7: note:   candidate expects 1 argument, 0 provided
Using library UIv2 in folder: /Users/ncd/Documents/Arduino/libraries/UIv2 (legacy)
Using library hd44780 at version 1.0.1 in folder: /Users/ncd/Documents/Arduino/libraries/hd44780 
exit status 1
Error compiling for board Arduino Nano.

... and maybe it's my imagination, but it seems slower going through the class rather than in the original sketch.

You can pass it in by reference, but save and use it as a pointer. Note, I don't have that library installed so I can't compile this. But, you should get the idea. Also, don't forget the #include guards in your .h file.

class UI {
  public:
    UI(hd44780_pinIO & _lcd) : lcd(& _lcd) {};
    void go();
    virtual ~UI();

  private:
    hd44780_pinIO *lcd;
};

void UI::go() {
 lcd->setCursor(0, 0);          // col 1, row 1
 lcd->print("***              ***");
}

void setup() {
  // put your setup code here, to run once:

}

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

}

gfvalvo:
You can pass it in by reference, but save and use it as a pointer.

Why would you save it as a pointer? Just save the reference.

PieterP:
Why would you save it as a pointer? Just save the reference.

True, I was originally thinking that the "passing in" might happen in a begin() method rather than the class's constructor. In that case, you can pass in a reference, but you can't assign it to a class data member that's a reference. I think that can only be done in an Initializer List. That's why it would need to be a pointer. Correct?

gfvalvo:
True, I was originally thinking that the "passing in" might happen in a begin() method rather than the class's constructor. In that case, you can pass in a reference, but you can't assign it to a class data member that's a reference. I think that can only be done in an Initializer List. That's why it would need to be a pointer. Correct?

Correct!

I think passing it in in the begin method could give rise to lifetime problems:

UI ui;

void setup() {
    hd44780_pinIO lcd;
    ui.begin(lcd);
}

void loop() {
    ui.go();  // Oops! lcd no longer exists
}

If you pass it in the constructor, the user can still make the same mistake, but it's much harder to do so.

OK... This compiles without errors. I don't have that LCD so I can't test it.

MainSketch.ino:

#include <UIv2.h>


UI ui;


void setup()
{
  ui.begin();
}


void loop()
{
}

UIv2.h:

#ifndef UIV2_H_
#define UIV2_H_

#include <hd44780.h>
#include <hd44780ioClass/hd44780_pinIO.h>

class UI
{
  public:
    void begin();  // Complete the initialization

  private:
    // Class private member. Shared by all instances.
    static hd44780_pinIO lcd;
};

#endif /* UIV2_H_ */

UIv2.cpp:

#include "UIv2.h"

static const int rs = 8, en = 9, db4 = 4, db5 = 5, db6 = 6, db7 = 7;

// Constructing the class member object
hd44780_pinIO UI::lcd(rs, en, db4, db5, db6, db7);

const int LCD_COLS = 20;
const int LCD_ROWS = 4;

void UI::begin() 
{
  UI::lcd.begin(LCD_COLS, LCD_ROWS);
  UI::lcd.setCursor(0, 0);          // col 1, row 1
  UI::lcd.print("***              ***");
}

@johnwasser, that was brilliant. It works, exactly as I wanted, and it's so clean! Thank you so much for your help. I need to study it (and c++ more) to understand better why everything is the way it is. Do you recommend any particular online or paper resource for c++, particularly for transitioning from c? If not, I'm sure I can find something; I just thought you might already be familiar. Thanks again.

johnwasser:
OK... This compiles without errors. I don't have that LCD so I can't test it.

Don't know why you want the private hd44780_pinIO object to be static to the class. Theoretically, there's no reason you couldn't have multiple UI objects each controlling their own hd44780_pinIO object.

I've come around to @PieterP's point of view. Pass a reference in to the constructor and use it to set a private internal reference member via the initializer list.

gfvalvo:
I've come around to @PieterP's point of view.

Well, mostly. If you really want the UI object to contain a hd44780_pinIO object, you could do this without losing the flexibility of letting the calling code decide how the hd44780_pinIO is connected:

#include <hd44780.h>
#include <hd44780ioClass/hd44780_pinIO.h>

class UI
{
  public:
    UI(uint8_t rs, uint8_t en, uint8_t db4, uint8_t db5, uint8_t db6, uint8_t db7) : lcd(rs, en, db4, db5, db6, db7) {}
    void begin() {
      const uint8_t LCD_COLS = 20;
      const uint8_t LCD_ROWS = 4;

      lcd.begin(LCD_COLS, LCD_ROWS);
      lcd.setCursor(0, 0);
      lcd.print("***              ***");
    }

  private:
    hd44780_pinIO lcd;
};

UI ui(8, 9, 4, 5, 6, 7);

void setup() {
  ui.begin();
}

void loop() {}

Or, you might decide that the UI class is a type of hd44780_pinIO class:

#include <hd44780.h>
#include <hd44780ioClass/hd44780_pinIO.h>

class UI : public hd44780_pinIO
{
  public:
    UI(uint8_t rs, uint8_t en, uint8_t db4, uint8_t db5, uint8_t db6, uint8_t db7) : hd44780_pinIO(rs, en, db4, db5, db6, db7){}
    void begin() {
      const uint8_t LCD_COLS = 20;
      const uint8_t LCD_ROWS = 4;

      hd44780_pinIO::begin(LCD_COLS, LCD_ROWS);
      hd44780_pinIO::setCursor(0, 0);
      hd44780_pinIO::print("***              ***");
    }
};

UI ui(8, 9, 4, 5, 6, 7);

void setup() {
  ui.begin();
}

void loop() {}

Lots of ways this cat can be skinned.