Creating Serial Objects Within a library

Hello all,

I’m writing a library to interface with a sensor that communicates over serial and I want to create a Serial object inside the library to use. However when I try to do so, the Arduino I’m running on just completely locks up. No errors during upload or anything. Just as soon as the program gets to the SensorSerial.begin() line, it halts. This also happens if I move that line out of the constructor and just into the setup function. I’ve verified this is the case by sending test strings out on the USB serial connection and by blinking the onboard LED. I’m running this on an MKR1000. Any help in resolving this would be greatly appreciated.

This is what I have right now, I’ve trimmed out a bunch of stuff that’s not relevant.

Sketch

#include <sensor.h>


sensor xs;

void setup()
{
}

void loop()
{
  doOtherStuff();
}

Header

class sensor
{
public:
    sensor();
    HardwareSerial &SensorSerial = SERIAL_PORT_HARDWARE;
};

Source

sensor::sensor()
{
  
  SensorSerial.begin(112500);

}

You should not access hardware in a constructor. Constructors are 'called' before all hardware might be initialised. That's why there are begin methods in most classes.

Is there a reason why you don't just pass a reference to the Serial object in the a begin method? It would be more flexible in my opinion.

And what's the odd baudrate?

Lastly, give a simple example of what doOtherStuff() actually does.

Per @sterretje's suggestion. Using a Stream reference gives you flexibility to do the I/O with any class that inherits from Stream rather than restricting yourself to HardwareSerial.

Obviously, with a sensor, you're more likely to do input rather than println(). But, this example is just to show how to access the object via the reference.

class sensor {
  public:
    sensor(Stream &p) : port(p) {
    }

    void sayHello() {
      port.println("Hello World");
    }

  private:
    Stream &port;
};

sensor xs(Serial1);

void setup() {
  Serial1.begin(115200);
  xs.sayHello();
}

void loop() {
}

@sterretje could you elaborate on what you mean about the constructors being called before hardware is initialized? Wouldn't any object created in the sketch part of Arduino program be created after all of the other base Arduino classes are initialized? And no there isn't really a reason I'm not passing an existing Serial object. I think I understand what you mean by that but I'm not sure how that would be implemented. The 112500 baud is just the default for the sensor I'm using and the doOtherStuff() function is just simple logic based on the sensor feedback, setting valve positions and things.

@gfvalvo I'm still struggling to understand how the whole Stream system works. I understand that Stream is the base class that other things inherit from but there's such a web of derived classes in there it's hard to follow. From what I can tell, when calling the regular Serial.whatever() functions, Serial is actually an object of class Uart? But none of the references I've seen talk about creating Uart objects. It also seemed like many of the Serial object functions don't come from the Stream or Print classes so I'm not sure how to create a Stream object and have it do all the same things as Serial. I guess my question is, what do you mean when you say I would be restricting myself if I used HardwareSerial vs Stream? Also there is two way communication with the sensor so I do need to send to it and receive from it.

btm710:
@gfvalvo I’m still struggling to understand how the whole Stream system works. I understand that Stream is the base class that other things inherit from but there’s such a web of derived classes in there it’s hard to follow.

There is no “web”. Print is the base class:

class Print {

Stream inherits from Print:

class Stream: public Print {

Both Print and Stream are abstract classes … no objects of these types can be instantiated because they contain purely virtual functions.

Numerous concrete classes inherit from Stream:

class HardwareSerial: public Stream
class usb_serial_class : public Stream
class SoftwareSerial : public Stream

Depending on what “Arduino” board you’re using, “Serial” could be an object of HardwareSerial class, usb_serial_class class, or some other class. That’s why using a Stream reference in your class provides the most flexible solution.

so I’m not sure how to create a Stream object and have it do all the same things as Serial.

You don’t want to CREATE a Stream object. Rather (as stated above) you want to pass in a reference to an existing Stream object.

Also there is two way communication with the sensor so I do need to send to it and receive from it.

Since I gave you and example of how to send to the sensor, it shouldn’t be too difficult for you to figure out how to receive from it.

I was able to get it to run by creating a reference to Serial1, same as was suggested above, thank you. I had attempted to call the begin() member function of the referenced Stream object but then realized that even though I was referencing Serial1, I can't call begin() because its not a function in Stream but rather of the derived class that Serial1 comes from.

I think I understand what was said about Stream being more flexible because I don't always know what class the "Serial" objects belong to but is there a way to reference the actual class Stream belongs to? That way I can call begin() from inside my library. Ideally I could do something like:

class sensor {
  public:
    sensor(Stream &p) : port(p) {
      port.begin(112500);
    }

    void sayHello() {
      port.println("Hello World");
    }

   //Create some reference to Serial1 using the Serial1 class rather than stream
   GETSERIAL1CLASS &port; //Not sure how this would look

  private:
   
};

sensor xs(Serial1);

void setup() {
  xs.sayHello();
}

void loop() {
}

Could I have the sensor class inherit from whatever class Serial1 belongs to? I know sterretje said accessing hardware in the constructor was a bad idea but I don't understand why that is.

Also, any thoughts on why my original code would have caused the Arduino to lock up?

Call begin() (if required) for the Stream-derived object outside of your class in the main code that uses the class. That's where it's known if begin() is required. You class doesn't need to know or care about begin().

OK so what if I want to have an xs.begin() function that calls Serial1.begin() but also does other things to set up the sensor?

No. One more time: DO NOT CALL Serial1.begin() FROM INSIDE THE CLASS.

Do all configuration required for the Stream instance you’re using outside of the class in the main setup() code. The class should neither know nor care about the required configuration (because it varies for different classes inheriting from Stream). All the class needs to know or care about is doing I/O with the Stream.

Your code consists of a few parts. Some of them you see and some of them you usually don't see. What you see when you write your program is your sketch and other files in the sketch directory.

The parts that you usually don't see are main() and crt0.

As you might know, a C/CPP program has a function called main(). It's hidden in the Arduino world but you can find it in (on a Windows system) C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\main.cpp.

The main() function calls the init() function which is responsible for the initialisation of the hardware; after that setup() and loop() are called.

However, main() is called from (wiki crt0) which does all the memory related initialisation stuff; see e.g. Startup code for some more details; you can ignore the fact that it mentions ARM.

From both articles, the constructor is 'called' if you declare it as a global variable (global variables are those that are not declared inside a function).

And after that hardware initialisation is done in main(). If you look at init() in C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\wiring.c, you will see that at the end it affects the serial port by manipulating UCSR0B.

Note:
crt0 is usually included as an object file during the link process, not as a source file that is first compiled.

I’m not much of a C++ programmer, hence I might not quite understand @gfvalvo’s comment

No. One more time: DO NOT CALL Serial1.begin() FROM INSIDE THE CLASS.

I can understand that it does not fit in the code that he presented in reply #2.

And I can understand where @btm710 comes from; keep all sensor related stuff in a class.

But it can fit if one does not derive; I found Pass reference to Serial object into a class? using a simple google search for arduino create class that can take a serial object.

Based on that, I modified @gfvalvo’s code to below; it should be able to handle all serial ports as well as SoftwareSerial.

#include <SoftwareSerial.h>

class sensor {
  public:
    sensor(HardwareSerial &p)
    {
      hwStream = &p;
    }
    sensor(SoftwareSerial &p)
    {
      swStream = &p;
    }

    void begin(uint32_t baudRate)
    {
      stream = !hwStream ? (Stream*)swStream : hwStream;
      if (hwStream)
      {
        hwStream->begin(baudRate);
      }
      else
      {
        swStream->begin(baudRate);
      }
    }

    void sayHello()
    {
      stream->println("Hello World");
    }

  private:
    HardwareSerial* hwStream;
    SoftwareSerial* swStream;
    Stream* stream;
};

sensor xs(Serial);

void setup()
{
  xs.begin(57600);
  xs.sayHello();
}

void loop()
{
}

Tested with Serial as demonstrated, should equally work for other serial ports (including software).

Comments are welcome if this is a proper approach or if I violate every C++ rule under the sun :wink:

OK I think I now understand why one would not want to call something like Serial.begin() in a class constructor:

  • The physical Arduino hardware is set up/initialized in the init() function called in the main() function of the top level "hidden" C++ program
  • Global constructors are executed as part of the crt0 routine
  • The main() function is also called from crt0, but after the global contructors

So with the order of operations here it is possible/likely/guaranteed(?) that the Arduino hardware would not be ready to handle a command like Serial.begin() at the time that, in my case, the sensor class constructor is called.

This leads me to ask about how header files are handled. Arduino.h is included as the first line of the main.cpp file which intuitively feels like it would be executed before the actual main() function inside the main.cpp file. When those articles talk about crt0 calling "main", does that refer to the main() function or the main.cpp file? When does the program actually enter and execute anything in Arduino.h?

So get now why I don't want to call Serial1.begin() in the sensor class constructor, however what's wrong with calling Serial1.begin() inside of a sensor.begin() function? There are several steps to set up this sensor so it would be convenient if I could do something like this:

class sensor {
  public:
    sensor(GETCLASS &p) : port(p) { }


    void begin(){
      port.begin(112500);
      port.write(SETUPINSTRUCTIONS);
      port.readBytes(buffer, length);
      
    }
    void sayHello() {
      port.println("Hello World");
    }

   //Create some reference to Serial1 using the Serial1 class rather than stream
   GETSERIAL1CLASS &port; //Not sure how this would look

  private:
   
};

sensor xs(Serial1);

void setup() {
  xs.begin();
  xs.sayHello();
}

void loop() {
}

I realize having an explicit Serial1.begin() in the setup() function would only be one more step but I'm trying to make this library as stupid simple as possible for the end user. Also this has now become academic question about how one might create an object of a variable class type i.e. how to initialize port if you don't know what class its going to be referencing.

UPDATE:
@sterretje you posted your reply as I was writing this, perfect timing!

I must say I'm embarrassed I didn't find that thread you linked since its so close to what I'm trying to do here. Though in my defense I did look for this kind of topic using the Arduino forum search tool which in hindsight may not have been a great idea...

Anyway, I was not aware you could overload class constructors, this is good to know. If I'm reading this correctly, your method would support any input to the sensor class constructor that is or is derived from the HardwareSerial or SoftwareSerial? And then once you're done using functions only found in those classes you can switch back to using Stream so you don't have to write overloaded functions for everything? I have yet to attempt implementing this but seems like it would work for me. The only minor thing is I think this would not work for other classes not derived from HWS or SWS like for I2C or Serial_. But again I'm not sure I would need to do this anyway so not really an issue.

I think that there are only a few sensors that support serial and I2C; but I might be mistaken.

If you want to write one class for both, I'm not sure if it can be done; trial and error :wink:

You started this thread with

I'm writing a library to interface with a sensor that communicates over serial

So I think it's unimportant for your project :wink:

There is actually a model of this sensor that uses I2C instead of serial but uses the same communication protocol but yeah I'm not using that one so not a problem. But like I said earlier, I'm now generally interested in how one might actually do that so if I figure out how I'll post it here for reference.

Thanks for your help!

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.