Pass reference to Serial object into a class?

Hello,

This is my first post in the Arduino forum. I've done C before over the years, but am quite new to C++ and finding some of the syntax a little tricky, though I'm enjoying the experience overall.

I am working on application that sends data across radio links, and it is working fine and seems to be very reliable.

The next step is to turn this code into a library. I followed the How to make an Arduino Library tutorial and it all worked perfectly, so I'm very enthusiastic!

I have all of the interfaces/methods planned out (the application is already written and working as a sketch, so this is mainly a "porting" exercise). However, one of the this facilities I would like to implement is a facility such that the object instance knows which serial port to use. The serial port to use will be defined at the application level, and the object instance will be told which serial port it should be use.

Here's some example code which illustrates what I'm trying to do; unfortunately the code doesn't compile. The compiler gives the following error:

error: 'Serial' does not name a type
Serial _serialRef;
^

This error refers to the line immediately after the private: keyword, below.

class Test
{
  private:
    Serial _serialRef;
  public:
    Test
    {
     
    }

    setSerial(Serial serialObject)
    {
      _serialRef=serialObject;
    }
    
    sendText(char *someText)
    {
      _serialRef.println(someText);
    }
};

Application-level code will set the serial port to be used, using something like the following:

Serial.begin();
Test testObj;
testObj.setSerial(&Serial);
testObj.sendText("Hello World!");

Note: I've not put the above code into a library. At the moment, I added a simple test class to my sketch, to try and get the syntax correct.

I'm clearly missing something (probably) trivial. Any help/pointers (ha! Pointers! :slight_smile: ) would be much appreciated.

Regards

Mark

1 Like

Use the Stream class. All serial classes inherit from it.

I recently did something like this but wanted the option to use either HW serial or SW serial…

free example… I hope it helps you.

#include <SoftwareSerial.h>

#define MESSAGE_LENGTH 64

class SerialMessenger {
  public:
    SerialMessenger( HardwareSerial& device) {hwStream = &device;}
    SerialMessenger( SoftwareSerial& device) {swStream = &device;}
    void begin(uint32_t baudRate);
    char* checkForNewMessage(const char endMarker, bool errors);
    
  private:
    HardwareSerial* hwStream;
    SoftwareSerial* swStream;
    Stream* stream;
    char incomingMessage[MESSAGE_LENGTH];
    size_t idx = 0;
};

char* SerialMessenger::checkForNewMessage(const char endMarker, bool errors = false)
{
  stream = !hwStream? (Stream*)swStream : hwStream;
  if (stream->available())
  {
    if (stream->peek() == '\r')
    {
      (void)stream->read();
      return nullptr;
    }
    incomingMessage[idx] = stream->read();
    if (incomingMessage[idx] == endMarker)
    {
      incomingMessage[idx] = '\0';
      idx = 0;
      return incomingMessage;
    }
    else
    {
      idx++;
      if (idx > MESSAGE_LENGTH - 1)
      {
        if (errors)
        {
          stream->print(F("{\"error\":\"message too long\"}\n"));
        }
        while (stream->read() != '\n')
        {
          delay(50);
        }
        idx = 0;
        incomingMessage[idx] = '\0';
      }
    }
  }
  return nullptr;
}

void SerialMessenger::begin(uint32_t baudRate)
{
  if (hwStream)
  {
    hwStream->begin(baudRate);
  }
  else
  {
    swStream->begin(baudRate);
  }
}

SoftwareSerial softSerial(2, 3);

SerialMessenger vera(Serial);
SerialMessenger electron(softSerial);

void setup()
{
  vera.begin(115200);
  electron.begin(57600);
}


void loop()
{
  if(const char* veraMessage = vera.checkForNewMessage('\n'))
  {
    char newMessage[MESSAGE_LENGTH];
    strcpy(newMessage, veraMessage);
    Serial.println(newMessage);
  }
  if(const char* electronMessage = electron.checkForNewMessage('\n'))
  {
    char newMessage[MESSAGE_LENGTH];
    strcpy(newMessage, electronMessage);
    Serial.println(newMessage);
  }
}

The "Serial" device can be a member of any one of several different classes (HardwareSerial, USBSerial, etc.), depending on which port, and which board. So, trying to pass a reference to the physical device is an exercise in frustration and incompatibility. However, ALL inherit from a number of generic base classes. In particular, Print and Stream. So, pass a pointer or reference to the Serial port cast to a Print or Stream device instead:

Stream *myPort = (Stream *)Serial;

or

Stream &myPort = (Stream &)Serial;

Regards,
Ray L.

Thank you all for your instant replies! Amazing!

Well, I stumbled around a bit, and I came up with this, which compiles and does work (tested with the serial monitor):

class Test
{
  private:
    Stream *_streamRef;
  
  public:
    setSerial(Stream *streamObject)
    {
      _streamRef=streamObject;
    }
    
    sendText(char *someText)
    {
      _streamRef->println(someText);
    }
};

Test test;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  test.setSerial(&Serial); // tell the class which serial object to use
}

void loop() {
  // put your main code here, to run repeatedly:
  test.sendText("Hello, world!");
  delay(1000);
}

As far as I can see, the object instance wouldn't care if it was a hardware serial port or a software serial port, or a port which streams over I2C etc. It'll just use it. Anyone see any issues with this code?

Thanks again,

Mark

markwills:
As far as I can see, the object instance wouldn't care if it was a hardware serial port or a software serial port, or a port which streams over I2C etc. It'll just use it. Anyone see any issues with this code?

nice job.

since you are using the Serial object to call begin here:

Serial.begin(9600);

you can do it this way... versus my example where I create a class member function to call begin(), choosing to wrap the entire Serial object in the class...

and most importantly, what you did works!

Definitely think you’re better off with a pointer. Why waste the RAM making an extra, local copy of the object?

gfvalvo:
Definitely think you’re better off with a pointer. Why waste the RAM making an extra, local copy of the object?

Am I making an extra local copy of the object? The object expects a pointer to a stream object, so (unless I'm mistaken - I'm on day 2 of c++ coding!) no additional objects are created.

Mark

BulldogLowell:
nice job.

since you are using the Serial object to call begin here:

Serial.begin(9600);

you can do it this way... versus my example where I create a class member function to call begin(), choosing to wrap the entire Serial object in the class...

and most importantly, what you did works!

Yes, I'm going for simplicity and baby steps! Your example was impressive, but I did have to go for a little lie down while breaking it down :slight_smile:

Mark

markwills:
Yes, I'm going for simplicity and baby steps! Your example was impressive, but I did have to go for a little lie down while breaking it down :slight_smile:

and ironically all it does is echo to Serial what comes in on either serial connection (the full code parses and moves messages between several devices connected via Serial). I just saved the basic class for moments like this. :slight_smile:

markwills:
Am I making an extra local copy of the object? The object expects a pointer to a stream object, so (unless I'm mistaken - I'm on day 2 of c++ coding!) no additional objects are created.

Sorry, I was just affirming the goodness of your new technique (Reply #4) verses your attempted use of a reference in your original post. In that post, it looked to me like “_serialRef” was a local copy.

gfvalvo:
Sorry, I was just affirming the goodness of your new technique (Reply #4) verses your attempted use of a reference in your original post. In that post, it looked to me like “_serialRef” was a local copy.

_serialRef is a REFERENCE to the original Serial object, NOT a copy of the object. It is, in fact, simply a pointer to the original object, wrapped in the syntactic candy of a c++ reference. Objects are very rarely copied, and in cases like the Serial object, CANNOT be copied, as the two copies would then be fighting each other over access to the single hardware device.
Regards,
Ray L.

RayLivingston:
_serialRef is a REFERENCE to the original Serial object, NOT a copy of the object. It is, in fact, simply a pointer to the original object, wrapped in the syntactic candy of a c++ reference. Objects are very rarely copied, and in cases like the Serial object, CANNOT be copied, as the two copies would then be fighting each other over access to the single hardware device.
Regards,
Ray L.

The original post had:

class Test
{
  private:
    Serial _serialRef;
  .
  .
  .
};

How do you tell '_serialRef' is a reference and not an instance of the 'Serial' class?

gfvalvo:
The original post had:

class Test

{
 private:
   Serial _serialRef;
 .
 .
 .
};




How do you tell '_serialRef' is a reference and not an instance of the 'Serial' class?

Aside from the fact that it could not possibly work as written? The OPs original code did not compile, and contained several errors, so it can't be taken literally. What he was trying to do could ONLY work if _serialRef is either a reference or a pointer. Style-wise, a reference is the better way to go. You cannot pass an object directly, and making a copy of the object, even if possible, is pointless, as the copy would not work properly, and would in this case certainly also cause the original object to stop work properly.
Regards,
Ray L.

BulldogLowell:
I recently did something like this but wanted the option to use either HW serial or SW serial…

free example… I hope it helps you.

@BulldogLowell

I’m trying to learn from your example. If you don’t mind, a question: why did you explicitly differentiate between Hardware and Software serial types in your class? Why didn’t you just treat everything as a ‘Stream *’?

Meaning (in very abbreviated form), something like this:

#include <SoftwareSerial.h>

class SerialPrinter {
  public:
    SerialPrinter(Stream *device) {streamPtr = device;}
    void printIt(const char *);
  private:
    Stream *streamPtr;
};

void SerialPrinter::printIt(const char *msg) {
  streamPtr->println(msg);
}

SoftwareSerial mySoftSerial(2, 24);

SerialPrinter hard(&Serial);
SerialPrinter soft(&mySoftSerial);

void setup() {
  mySoftSerial.begin(9600);
  Serial.begin(115200);
  delay(1000);
}

void loop() {
  hard.printIt("Hello World: Hard");
  delay(1000);
  soft.printIt("Hello World: soft");
  delay(1000);
}

As I said, I’m still trying to learn. Appreciate your time.

gfvalvo:
@BulldogLowell

I’m trying to learn from your example. If you don’t mind, a question: why did you explicitly differentiate between Hardware and Software serial types in your class? Why didn’t you just treat everything as a ‘Stream *’?

Meaning (in very abbreviated form), something like this:

#include <SoftwareSerial.h>

class SerialPrinter {
 public:
   SerialPrinter(Stream *device) {streamPtr = device;}
   void printIt(const char *);
 private:
   Stream *streamPtr;
};

void SerialPrinter::printIt(const char *msg) {
 streamPtr->println(msg);
}

SoftwareSerial mySoftSerial(2, 24);

SerialPrinter hard(&Serial);
SerialPrinter soft(&mySoftSerial);

void setup() {
 mySoftSerial.begin(9600);
 Serial.begin(115200);
 delay(1000);
}

void loop() {
 hard.printIt(“Hello World: Hard”);
 delay(1000);
 soft.printIt(“Hello World: soft”);
 delay(1000);
}



As I said, I'm still trying to learn. Appreciate your time.

Where do you see him doing that? This is the entirety of his class definition:

class SerialPrinter {[color=#222222][/color]
  public:[color=#222222][/color]
    SerialPrinter(Stream *device) {streamPtr = device;}[color=#222222][/color]
    void printIt(const char *);[color=#222222][/color]
  private:[color=#222222][/color]
    Stream *streamPtr;[color=#222222][/color]
};[color=#222222][/color]
[color=#222222][/color]
void SerialPrinter::printIt(const char *msg) {[color=#222222][/color]
  streamPtr->println(msg);[color=#222222][/color]
}

No mention of SoftwareSerial or HardwareSerial anywhere in there. Only Stream.
Regards,
Ray L.

RayLivingston:
Where do you see him doing that?

In the </> Code Block of Reply #2.

RayLivingston:
This is the entirety of his class definition:

That's the simplified class definition that I proposed, instead of what he wrote in Reply #2.

Suggest you reread my question more carefully in the context his Reply #2.

gfvalvo:
That's the simplified class definition that I proposed, instead of what he wrote in Reply #2.

Suggest you reread my question more carefully in the context his Reply #2.

Yes, both classes inherit from Stream, but if I'm making a complete wrapper, then methods like begin() need to be differentiated based on the respective object, since begin() is a member of both HardwareSerial and SoftwareSerial, but not Stream.

You can see the class defines pointers to both HWS and SWS and I'm copying them to a Stream pointer... but not for begin(). (there is an implicit cast happening there, the compiler gets that they both inherit from Stream).

I hope that clarifies that for you.

markwills:
As far as I can see, the object instance wouldn’t care if it was a hardware serial port or a software serial port, or a port which streams over I2C etc.

This is called ‘polymorphism’ and is the third of the three “big important things” (I forget the word they use) about object-oriented languages, the list being:

  • Encapsulation
  • Inheritance
  • Polymorphism

It’s one of the main mechanisms used to code up stuff in an objecty-orientedy way. For instance, rather than have switch statements to control behavior (if it’s one of these, do that, otherwise if it’s one of those, do this other thing) you put the behaviour inside classes and pass around references to objects. This becomes handy when you have to do several things that are all different depending on what case you are dealing with.

For instance:

Say you have a robot arm. The arm has to pick up an item off the belt, it has to dunk the item in water, shake it until it’s dry, put it in a rack, and then go to its home position. And all of these things are different depending on which item is on the belt. Even the question of how you tell whether the item is dry yet.

One way to do it is to have a switch/case statement for each of these operations. You have a state machine, with a switch/case for each stage in the process, and then in each branch there’s another switch case for the type of item.

The problem is that what you have to do for each item is scattered across five or six places. Adding a new type of item is a bit of a pain.

What you can do instead is have an abstract class named ItemRinser. Like Stream, if has a bunch of methods which are not implemented. You then have subclasses - one for each item type - and all the operations for that type of item are bundled up there. The main loop identifies what item it is dealing with, sets a reference to an appropriate class instance, and then asks that thing to please move the arm appropriately.

It all comes down to the same stuff (mostly) once it’s compiled down, but the code is easier to write and maintain.

markwills:
I'm clearly missing something (probably) trivial. Any help/pointers (ha! Pointers! :slight_smile: ) would be much appreciated.

Regards

Mark

Check out my library: GitHub - krupski/Stdinout: Standard input/output/error support for Arduino

It allows "connecting" a character device to the standard input/output/error streams.

For example, the line "[b]STDIO.open (Serial);[/b]" connects the serial port to stdin and stdout so that you can then do this:

[b]
fprintf (stdout, "Hello there\n");[/b]

...and have it go to the serial port.

If you look at the code, you should readily see how to pass a character device object (such as "Serial, "LCD", etc...) to some code and then make use of it.

Hope this helps.