Go Down

Topic: Deferring SoftwareSerial object creation (Read 1 time) previous topic - next topic

galileotech

I'm trying to *conditionally* setup a Serial connection using "SoftwareSerial" so I wanted to defer creating the object until I know I need it (this differs from all of the examples). So I converted this working example:
Code: [Select]
#include <SoftwareSerial.h>

SoftwareSerial st_serial = SoftwareSerial(2, 3);

void setup()  {
 pinMode(2, INPUT);
 pinMode(3, OUTPUT);
 st_serial.begin(9600);
 delay(500);
}


void loop() {
 st_serial.print(120, BYTE);
 delay(3000);
 st_serial.print(0, BYTE);
 while(1);
}


To the deferrable version:

Code: [Select]
SoftwareSerial *st_serial = NULL;

void setup()  {
 pinMode(2, INPUT);
 pinMode(3, OUTPUT);
 st_serial = new SoftwareSerial(2, 3);
 st_serial->begin(9600);
 delay(500);
}


void loop() {
 st_serial->print(120, BYTE);
 delay(3000);
 st_serial->print(0, BYTE);
 while(1);


This fails to compile due to "undefined reference to `operator new(unsigned int)'". Although I new to C++ (from C), this appears to be textbook use of C++ and is in many C++ tutorials.  I have also avoided the use of "new" since I can't find it mentioned with avr-gcc and instead used both of the following to attempt to get a pointer to a new object.

Code: [Select]
st_serial = &SoftwareSerial(2, 3);
st_serial = &(SoftwareSerial(2, 3));


Both which compile successfully, but never works (i.e activate the device I communicating with as the first example above does).

So, one, is avg-gcc differ in its implementation of C++ since "new Object" seems standard and doesn't work?, or am I just another newbie.  And, two, how do I get the second example creating obj in setup) to work?

Thanks,
/me




galileotech

Appears there was a thread that references a "fix" for the "new" syntax, that was related to my question.  So there are some differences in std implementation of C++.

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1216134904

I fixed the second example (and used "new") by adding the following code at the top:

Code: [Select]
#include <stdlib.h>
/*
__extension__ typedef int __guard __attribute__((mode (__DI__)));

int __cxa_guard_acquire(__guard *g) {return !*(char *)(g);};
void __cxa_guard_release (__guard *g) {*(char *)g = 1;};
void __cxa_guard_abort (__guard *) {};

*/

void * operator new(size_t size);
void operator delete(void * ptr);

void * operator new(size_t size)
{
 return malloc(size);
}

void operator delete(void * ptr)
{
 free(ptr);
}


The commented out code above was a part of the recommend code siplet but it wouldn't compile (error: '__guard' was not declared in this scope In function 'int __cxa_guard_acquire(__guard*)':) and I did not need the functionality it gave.

Thanks for checking this one out.

CosineKitty

Just curious... why do you need to "defer" creating the serial connection?  Maybe if we understood what the problem was, there would be another way to do what you want.

- Don

galileotech

The primary purpose of this exercise was to learn AVR C++ (knowing C) and I did that in droves.   However, I needed to defer the creation of the serial because I did not know if I would need it.   A contrived example would be where this would be applicable would be if you were building a program on a uP that, via a jumper say, you could select say serial or I2C interface to another device.  You would not want to alway create a Serial (SoftwareSerial) object (calling the constructor and telling it what pin you wanted it to use) when your jumper said to use the I2C object instead.

But the bigger picture is you generally want to avoid global variables unless you have a compelling reasons to do so.  Especially if your trying the C++ OO route IMO.  Of course, this is coming from someone who programs on large CPUs with a OS.  The "style" of programming on uP may be very different and more pragmatic considering the more limited resources - I'm a newbie in this arena  :-X.  Hmm, maybe that would be a good thread for later...

Galileo

CosineKitty

Hi Galileo,

Yes, I too am a software engineer by day, working on much larger OOP-based systems.  However, in the microcontroller world, there is a lot to be said for predictability.  You have very little memory, very little debugging support, and there is not much recourse for failures like out-of-memory conditions.  I have found myself taking a somewhat balanced approach: I use C++ classes, but I avoid dynamic memory allocation.  I also have found that C++ constructors do not seem to get called for global instances of the class.  This just bit me as a matter of fact!  I had to replace the constructor with a goofy Initialize() method that gets called by the setup() function.

So I take advantage of OOP concepts like making things private inside a class, providing controlled public interfaces, but still use global variables for instances so I know exactly how much memory I am using.  If I need a dynamic buffer, I declare my own global array and use indexes into the array to implement a stack or a queue.  You can even create your own freelist to allocate for yourself, but you risk running out of resources at a critical time.  Of course, you can hide all of this inside a class, but I would still recommend making the instance of that class be a global variable.

In the case of the serial port, unless you write your own software serial driver (which has been done, though the timing constraints make it hard to do anything else useful at the same time), you don't have much choice about what pins are used for the serial port.  At least, this is the case on the ATMEGA8.  Maybe things are more flexible on the higher-end processors?

As another example of how my coding style has changed in the Arduino environment, I notice I no longer automatically use multi-byte data types like 'int', if it is at all possible to use 'char' instead.  I have found this saves a lot of program memory in addition to the obvious reduction in data memory.  This alone has made the difference in fitting a complex algorithm in the 7K I had available.

- Don

mikalhart

Galileo--

Code: [Select]
st_serial = &SoftwareSerial(2, 3);
st_serial = &(SoftwareSerial(2, 3));


This kind of code fails because the syntax SoftwareSerial(2, 3) causes a temporary instance to be created.  A pointer to that temporary object is assigned to st_serial and then the object is immediately destroyed.  The second line repeats the same process, leaving the pointer pointing to a destroyed object.  Yikes!

If you need "deferred construction" you might try something like this:

Code: [Select]
void PrintMessage()
{
 SoftwareSerial ss(2, 3);
 ss.print(120, BYTE);
 ss.print(0, BYTE);
} // ss is destroyed here

void loop()
{
...
if (...)
 PrintMessage();
...
}

This will cause an instance of the SoftwareSerial object to be created if and only if the PrintMessage function is called.  Further, it is neatly destroyed at the exit to the function.  The downside to this is that you incur the performance penalty of contructing and destructing the SoftwareSerial object each time the function is called.  And it temporarily consumes a little bit of stack space in the process.  

CosineKitty--

C++ constructors are called for global instances of classes.  For a demonstration of this, run the following little program and see that it prints x is 1:

Code: [Select]
int x = 0;

class C
{
 public:
 C() { x = 1; }
};

C c;

void setup()
{
 Serial.begin(9600);
 Serial.print("x is ");
 Serial.println(x);
}

void loop()
{
}

The problem with global objects is that C++ makes no guarantee on the order things like object constructors are called during the program's initialization phase.  If you do create global objects, you must take care to avoid writing code in the constructor that might depend on other initializations that haven't run yet.  For example, I learned through trial and error that delay() doesn't work from within a global constructor.  Presumably the interrupt vector hasn't been wired yet.

Regards,

mh

CosineKitty

Hi Mikal,

I tried your experiment and you are correct: the constructor is called for a global class instance.

I now know why my constructor did not work as expected.  It was because it does some digital output, but it does it before setup() was called, so the pins I was writing to have not yet been defined as output pins!  Silly me.   :)

Thanks for clearing up the confusion...

Don

mikalhart

Don, I made that very same mistake not 2 weeks ago. :)

Have fun.

Mikal

Go Up