Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move object creation to setup() function of arduino

Tags:

c

arduino

I've created a class that handles sending data to a led matrix (max7219).

This is what I was doing to create an instance of the LedControl class.

LedControl lc=LedControl(11, 13, 12);// data, clock, latch;

void setup() 
{
   ...
}

Now I'm trying to add timer-interrupts to my class. But I found that I can't set the appropriate registries in the initializer (LedControl::LedControl()). If I move this code to setup, it all works perfectly. My guess is that the Arduino bootloader, which uses timer1 for PWM overwrites these registries, just before calling setup() but after my object has been initialized.

So my idea was to just move the object creation to the setup function like so

// FAIL
LedControl lc;

void setup() 
{
   lc=LedControl(11, 13, 12);// data, clock, latch;
   ...
}

But then I get the error no matching function for call to 'LedControl::LedControl()'

I tried using pointers (LedControl *lc; lc=&LedControl(11, 13, 12);), but as far as I could tell, this would mean I would have to write (*lc).someFunction() everywhere instead of lc.someFunction(). Even less elegant than moving the registry setting code to setup().

So my question is. How do I create an object in the setup() function, but still have a global variable pointing to it?

like image 743
Gerben Avatar asked Sep 14 '13 20:09

Gerben


1 Answers

Your first error "no matching.. " is because you have no default constructor. You can make that approach function.

Add a no parameter, AKA default constructor, to the class, like this:

class LedControl {
  LedControl();
  LedControl(uint8_t pin1, uint8_t pin2, uint8_t pin3);

private:
  uint8_t pin1;
  uint8_t pin2;
  uint8_t pin3;
};

LedControl::LedControl() : pin1(0), pin2(0), pin3(0) {
  // this constructor leaves the class unusable
}

LedControl::Ledcontrol(uint8_t p1, uint8_t p2, uint8_t p3) 
  : pin1(p1), pin2(p2), pin3(p3)
{
  // this object is ready to use    
}

With this class, your approach will work, but is not the optimal method. This line does too much work:

void setup() {
  lc = LedControl(11, 13, 12);// data, clock, latch;
}

This line of code involves the compiler creating some code for you:

  • First another instance of the class is constructed on the stack using the parameters 11,13,12.
  • Then it applies the = operator copying the data from the stack object to your global object.
  • When setup() exits, the stack object is flushed.

Because the temporary object is on the stack, your program didn't use too much memory, but your code size is larger because it involves extra operations to construct the temporary then copy from the temporary to the permanent.

Note that the compiler is creating an = operator for you, to fill the function of the line

  lc = LedControl(11, 13, 12);

This may or may not work depending on what is in your constructor. The compiler can only presume that you needed a simple = operator. All the basic assignment operator will do is copy all the data members from the instance on the right side of the = to the instance on the left side. The compiler constructed = will not contain any code.

If your constructor does anything significant (other than save the parameters) then the compiler constructed(guessed) assignment operator may not work as you expected. For your case the constructor probably sets the pin modes, something like this:

LedControl::LedControl(uint8_t p1, uint8_t p2, uint8_t p3) 
  : pin1(p1), pin2(p2), pin3(p3)
{
  pinMode(pin1, INPUT);
  pinMode(pin2, OUTPUT);
  pinMode(pin3, OUTPUT);
  return;
}

This happens to function, but only by chance. The pinMode() calls are made when the temporary object is constructed and called from that object, not from the global lc. Because pinMode()'s are global this case will achieve the correct goal, but maybe not in the way expected. For more complex operations such as registering interrupt handlers, you will need to create your own assignment operator:

LedControl& operator= (const LedControl & other);

In that method, you could ensure that the state of the global lc is what you need. An easier/safer approach is to not deal with that at all.

A simple and efficient approach, you may have seen in other libraries is to add a method to the class that assigns the pins:

class LedControl {
  void attach(uint8_t pin1, uint8_t pin2, uint8_t pin3);
};

void LedControl::attach(uint8_t pin1, uint8_t pin2, uint8_t pin3) {
  this.pin1 = pin1;
  this.pin2 = pin2;
  this.pin3 = pin3;
  // do other setup type operations
  return;
}

Now your program, constructs the blank object, and assigns pins during setup():

LedControl lc; // not ready to use until attach pins

void setup() {
  lc.attach(11, 13, 12);// data, clock, latch;
  ...
}

This involves no temporary object construction, and no assignment operator. With respect to design, some people might fairly comment that the user might forget to call attach() and leave the global lc object unusable. For a desktop application, you might add some code to prevent that failure case. For an embedded application, that is a risk you accept which is balanced by the gains in code size or memory savings.

like image 170
jdr5ca Avatar answered Sep 30 '22 11:09

jdr5ca