5

Good afternoon, I'm trying to use a class to streamline the writing on the LCD. In this class I have an private attribute (it's a pointer, do not know how I call him) called LiquidCrystal_I2C* _lcd, I instantiate it in the constructor, so far so good, but when I call the _lcd-> begin (20, 4) the code below is not executed, but no error is displayed.

Display.h:

class Display{
    public:
        Display();
        ...
    private:
        LiquidCrystal_I2C* _lcd;
        ...
}

Display.cpp:

...
Display::Display() {
    _lcd = new LiquidCrystal_I2C(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
    _lcd->begin (20, 4);
}
...

Program.ino

#include "Display.h"

Display lcd;

void setup() {
    Serial.begin(9600);
}

void loop() {
    Serial.println("test");
    delay(3000);
}

Just I noticed the error when I saw that Serial.println ("test"); was not running. If I comment the _lcd->begin (20, 4); everything works.

I tested writing everything in Program.ino and worked, has nothing to do with connections or something, just do not apply the concept of composition with this class.

Can someone help me? And sorry for the English, i'm from Brazil. Thanks.

  • Do you understand the concept of static access in classes? – Majenko Apr 15 '16 at 19:57
  • If it is as Java, I understand – Natalia Kelim Thiel Apr 15 '16 at 19:58
  • It is pretty much the same, yes. Accessing member variables with Display:: in front of them accesses the static class members, not the object local members. I am quite surprised it compiled actually like that. – Majenko Apr 15 '16 at 20:00
  • I removed the Display::, but still does not work. – Natalia Kelim Thiel Apr 15 '16 at 20:02
  • Is it now: _lcd = new LiquidCrystal_I2C(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); _lcd->begin(20, 4); – Natalia Kelim Thiel Apr 15 '16 at 20:05
4

Most likely cause

You are hitting an insidious problem that even experienced C++ programmers frequently miss: order of initialization of global variables. In your case, Display lcd is initialized (i.e., its constructor is called) before main() and subsequently setup() are executed. However, you almost certainly are #includeing whatever library is declaring the LiquidCrystal_I2C class, probably Wire.h, and so on. In many of these classes, a global variable of that type is also defined to make it easier to access stuff. For example, Serial (as in Serial.println("foo")) is really a global variable of the appropriate type of whatever serial implementation your particular core is using.

The problem you are hitting is that the C++ standard does not guarantee the execution order of the constructors of global variables. The order is an artifact of the compiler, the order in which object files were linked, and probably the phase of the moon :)

What is (almost certainly) happening is that the constructor for your class Display is being called, which initializes the LCD and the i2c bus, and then something else calls either the LCD constructor or the i2c constructor and messes things up.

OK, "how do I fix this?" I hear you ask. Simple: make the global lcd variable a pointer, and allocate the instance in begin(), like so:

#include "Display.h"

Display *lcd;

void setup() {
  Serial.begin(9600);
  lcd = new Display();
  // stuff
}

void loop() {
  // more stuff
  lcd->some_method(some_argument);
  // even more stuff
}

Now you are guaranteed that your code will be the last to touch any initialization code that might affect the display or the bus. Just keep accessing the class Display methods through the pointer.

A less likely cause

It is still possible that your constructors are getting executed in the right order, but your hardware does not like being initialized twice (but test that anyway!), so you may have to figure out how to reset the hardware so you can initialize it again. If I understand your posting correctly, the loop never runs, which means that a second initialization probably either causes a exception (which you would not see unless you had some kind of additional debugging channel; in the case of the ESP8266, for example, that would be the output-only Serial1 port, and even then, you'd have to explicitly turn on debugging. Each core has its own debugging stuff, I forget what the real Arduino does.)

Hope this helps.

/ji

  • It working! Thank You =D And sorry for the delay, I was without Internet... – Natalia Kelim Thiel Apr 27 '16 at 18:13
  • Another satisfied customer :) – JayEye Apr 27 '16 at 18:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.