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?
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With