Using #IFNDEF for serial

Hi, is it possible to use this:

#ifndef Serial
#define Serial.begin(115200);
#endif

to define a default serial communication port in a library?
I have some functions in a small library that I am creating that can optionally use serial for debug/monitoring, where I have a bool that specifies if the functions of the objects should or should not use the serial. The init() function looks like this right now:

void initWith(PinName Bpin, bool useSer, Stream &ser);

through which I can pass the Serial to the object to be used.
But, since the serial is optional, I need to define a default value, which should be just Serial (generic virtual serial over USB on STM32 devices). But if the specific project doesn't have Serial defined, the .init() for the objects doesn't compile, because the Serial is not defined.

So, my question is, can I define a generic Serial like this? Or is there another way?

Thanks!

These #defines found in many variants pins_arduino.h file may be useful.

// These serial port names are intended to allow libraries and architecture-neutral
// sketches to automatically default to the correct port name for a particular type
// of use.  For example, a GPS module would normally connect to SERIAL_PORT_HARDWARE_OPEN,
// the first hardware serial port whose RX/TX pins are not dedicated to another use.
//
// SERIAL_PORT_MONITOR        Port which normally prints to the Arduino Serial Monitor
//
// SERIAL_PORT_USBVIRTUAL     Port which is USB virtual serial
//
// SERIAL_PORT_LINUXBRIDGE    Port which connects to a Linux system via Bridge library
//
// SERIAL_PORT_HARDWARE       Hardware serial port, physical RX & TX pins.
//
// SERIAL_PORT_HARDWARE_OPEN  Hardware serial ports which are open for use.  Their RX & TX
//                            pins are NOT connected to anything by default.
#define SERIAL_PORT_MONITOR   Serial
#define SERIAL_PORT_HARDWARE  Serial

I would think the default would be NULL which means you get no debug output. If, on the other hand, you pass in a Stream object, you do get debug output.

You cannot use it, as it has bad syntax in #define.

Anyway you probabelly do not want to define new macro, but do something like this

This woud not work too, becase Serial.begin calls method begin of object(class) Serial -but this will compile only when Serial is not defined (#ifndef) so the Serial would not exist.

This one may be, what you need

and then, inside initWith (or the full library) have something like that

void initWith(PinName Bpin, bool useSer, Stream &ser) {
  myBpin = Bpin;
  myuseSer = useSer;
  mySer = ser;
  if (myuseSer) {
    ser.println("Yay! We can use debugging now!");
  } else {
    // no debugging without Serial here ...
  };

@t4st3r's code is passing by reference:

therefore, you can't use NULL as a reference must be bound to a valid object. So, you must pass a Stream object. If a "null" object is an option, then you must use a pointer.

One nice thing about references is that they can't be null, and you usually don't want to pass a copy of an object.

But in this case, the API would be clearer taking a pointer that defaults to null (using nullptr since we're using C++, not plain C)

void initWith(PinName Bpin, Stream *debugStream = nullptr);

Leave out the second arg and you "get nothing". In the library's implementation, instead of checking a bool flag, you consistently check the pointer itself if (debugStream), and use -> instead of the . operator.

If instead you really want to use a reference, then you could have a special instance. In this case, Stream is an abstract class, but easy enough to add the stubs. First, in my_lib.h

#include <Arduino.h>
extern Stream* const debug_stream_disabled;

#define PinName int  // made this up just so it will compile

void initWith(PinName Bpin, Stream &debugStream = *debug_stream_disabled);

That extern says, "someone else has defined a special pointer". It has to be a pointer because Stream is abstract; you can't instances of those. Note also the placement of the const: the variable itself cannot be changed (as opposed to a "non-const pointer to const-Stream")

The resulting function signature is reasonably clear, as when displayed with the IDE's code assistance. Then the implantation in my_lib.cpp

#include "my_lib.h"

namespace {
  class : public Stream {
  public:
    int available() {return 0;}
    int read() {return 0;}
    int peek() {return 0;}
    size_t write(uint8_t) {return 0;}
  } doNothingStream;
}

Stream* const debug_stream_disabled = &doNothingStream;

void initWith(PinName Bpin, Stream &debugStream) {
  if (&debugStream != debug_stream_disabled) {
    debugStream.print("initialized with: ");
    debugStream.println(Bpin);
  } else {
    Serial.println("initialized without debug output");  // just to show the logic works
  }
}

The anonymous namespace holds an anonymous concrete subclass with a single instance, which is not directly accessible from the outside. It's used to populate that "magic" pointer, to fulfill the extern

In actual usage, including the init function, you can compare the address of the reference with the magic pointer. This is only strictly necessary with the read functions; otherwise you might wait forever. For writes, you could just call the do-nothing write implementation. (If you're only going to print to it, it would be simpler to use a Print instead of a Stream)

To test this

#include "my_lib.h"

void setup() {
  Serial.begin(115200);
  initWith(123, Serial);
  initWith(456);
  initWith(789, Serial);
}

void loop() {}

As it stands it is not optional (no default value) and can’t be null (It’s a reference).

May be pass a pointer to the stream and then your code can check if it’s nullptr and the debug bool is set.

I’m often using this for debug

#define DEBUG 1    // SET TO 0 OUT TO REMOVE TRACES

#if DEBUG
#define D_SerialBegin(...) Serial.begin(__VA_ARGS__);
#define D_print(...)       Serial.print(__VA_ARGS__)
#define D_write(...)       Serial.write(__VA_ARGS__)
#define D_println(...)     Serial.println(__VA_ARGS__)
#define D_printf(...)      Serial.printf(__VA_ARGS__)
#else
#define D_SerialBegin(...)
#define D_print(...)
#define D_write(...)
#define D_println(...)
#define D_printf(...)
#endif

instead of Serial.print() or Serial.write() you use D_print() and D_write()

if you set the #DEBUG macro to 0 then all traces (and opening of the Serial port if you used the macro) will go away.

(Remove the printf entry if on a small AVR)

very true and easily done v. passing a reference

not sure why you limit the discussion to Serial.begin(). I've passed a ptr to a HardwareSerial which was one of several possible uarts (Arduino Mega) or a bluetooth interface.

i've also passed a separate ptr for debug which can be used in a sub-function for debugging but only used if not NUL

Well, yes. I ended up using a Stream reference stored in the object, with the init function like this:

void initWith(PinName Bpin, int mode = 0, int activeOn = 0, bool useSer = false, Stream &ser = Serial);

which works, but only if the Serial is initialized in the sketch. If it isn't, then I (understandably) get a compilation error because the "Serial" isn't initialized. So I should use a pointer instead? Default would be NULL? Thanks!

To add to this: I've tried changing the pass-by-reference to passing the serial by pointer, but I get an error "cannot convert 'HardwareSerial' to 'Stream*'" when testing the function on an STM32. Is this an issue related to the HardwareSerial not being derived from Stream? How would I go around fixing this? Thanks.

Since you didn't post an complete, updated code, there's no way to know.

Passing a pointer to a Stream object is more flexible. For example, it will work with Arduino boards whose main "Serial" interface is actually native USB. Also, if two-way debug isn't require, just pass a pointer to a Print object instead.

Hello, the full library files are available at my github repo (GitHub - t4st3r/simpleLIB: Simple library for Arduino IDE, a collection of random functions that I wrote for various projects.), with some examples (currently non-functional because of this issue). I think these are a bit too big to be uploaded here.

So where in there is the code you attempted using a pointer?

Code here:

//Fully configured buttonPressed instance
#include <simpleLIB.h>

BasicButton button(1); //create a BasicButton object, assigned number 1

void setup() {
  Serial.begin(115200);
  button.initWith(PA_0, 0, 0, true, Serial); //initialize button with pin PA_0, mode 0, active on 0, serial monitoring enabled on "Serial"
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
}

void loop() {
  if (button.buttonPressed() == true) { //if the button is pressed, turn on the builtin LED for 1 second
    digitalWrite(LED_BUILTIN, LOW);
    delay(1000);
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

it is the buttonPressed_full_configuration example in the library.
Error log:

In file included from C:\Users\taste\AppData\Local\Arduino15\packages\STMicroelectronics\hardware\stm32\2.9.0\cores\arduino/wiring.h:48,
                 from C:\Users\taste\AppData\Local\Arduino15\packages\STMicroelectronics\hardware\stm32\2.9.0\cores\arduino/Arduino.h:36,
                 from C:\Users\taste\AppData\Local\arduino\sketches\64FC5F0DF186647F08D9C644B1F5624A\sketch\buttonPressed_full_configuration.ino.cpp:1:
C:\Users\taste\OneDrive\Dokumenty\Arduino\libraries\simpleLIB\examples\BasicButton\buttonPressed_full_configuration\buttonPressed_full_configuration.ino: In function 'void setup()':
C:\Users\taste\AppData\Local\Arduino15\packages\STMicroelectronics\hardware\stm32\2.9.0\cores\arduino/WSerial.h:17:22: error: cannot convert 'USBSerial' to 'Stream*'
   17 |       #define Serial SerialUSB
      |                      ^~~~~~~~~
      |                      |
      |                      USBSerial
C:\Users\taste\OneDrive\Dokumenty\Arduino\libraries\simpleLIB\examples\BasicButton\buttonPressed_full_configuration\buttonPressed_full_configuration.ino:8:37: note: in expansion of macro 'Serial'
    8 |   button.initWith(PA_0, 0, 0, true, Serial); //initialize button with pin PA_0, mode 0, active on 0, serial monitoring enabled on "Serial"
      |                                     ^~~~~~
In file included from C:\Users\taste\OneDrive\Dokumenty\Arduino\libraries\simpleLIB\examples\BasicButton\buttonPressed_full_configuration\buttonPressed_full_configuration.ino:2:
c:\Users\taste\OneDrive\Dokumenty\Arduino\libraries\simpleLIB/simpleLIB.h:38:92: note:   initializing argument 5 of 'void BasicButton::initWith(PinName, int, int, bool, Stream*)'
   38 |   void initWith(PinName Bpin, int mode = 0, int activeOn = 0, bool useSer = false, Stream *ser = NULL); //used instead of begin()
      |                                                                                            ^
exit status 1

Compilation error: exit status 1

If you're using a pointer, then you must pass the object's address:
button.initWith(PA_0, 0, 0, true, &Serial);

That may not be the only error. But try it and post the results.

Okay, that was the problem. The sketch now works as intended.
When the initWith is called like this:

button.initWith(PA_0, 0, 0, true, &Serial);

there are serial monitoring messages (and no errors), and when called like this:

button.initWith(PA_0, 0, 0);

there are also no errors and there is no serial monitoring.

Thanks!

Also, could you point me (hehe) to some resources on how to use pointers properly? I still don't feel like I understand the whole thing. Thanks again!