[SOLVED/changed] instead special version of serial.print() using macros

Hi everybody,

I'm wokring on a project
logging onewire temperature-data to a SD-card avoiding tinkering around with loose parts through using ready-to-use components which are a teensy 4.1 and a wemos D1 mini.

Where I want to use a second serial interface on a teensy 4.1 and a ESP8266 WeMos D1 Mini to communicate with each other. For debugging purposes I additionally connect a USB CP2102 to listen to what the Teensy or the Wemos sends.

This means I have up to three serial monitors opened at the same time. To make it easier to distinguis which serial monitor is what I would like to write functions that can be used the exact same way as Serial.print() / Serial.println().
but that add info which serial interface is sending. Like "TeenSy3" "WeMosSoft"

Exact same way means I can put floats, integers and booleans, array of chars etc. into the brackets.

In standard-functions the variable-type of the parameters have to be defined.
So it only works for one variable-type.
print() and prrintln() can do them all. So there must be something different.

If I would have to write a heritaged class in a new library or something like that for realising this special version of print()/println() I stay away from it.

I want to emphasise: I don't expect what I write as follows:
except somebody would have fun to write, test and debug the whole thing until it is ready to use.

I decide either it is 20 lines of easy to understand code without knowing anything about classes in libraries
or I will take the route to use a construction assign everything to a global Debug_PString and then using two functions that do the printing to each serial-interface by calling this one function Myprint() / Myprintln()

If somebody posts a sketchy code that has to be adapted it would take approx. 50 hours of time to learn it from scratch how to adpat it. Then I decide to take the 2,5 hours to write the standard-functions. Even if this is more laborious, much less "elegant" and whatever.

It is a very conscious decision of mine to refuse to study and expand my programming-knowledge at this point.

So the first question is: Would I have to write a new class in a library to do so or will it be eaiser?

additional question: would have somebody have fun writing, testing, debugging it until ready to use?

best regards Stefan

StefanL38:
In standard-functions the variable-type of the parameters have to be defined.
So it only works for one variable-type.
print() and prrintln() can do them all. So there must be something different.

Yes and no. You're right that C++ functions have defined parameter types. But, the language allows function overloading. So, there are actually several print() and println() functions that support the many different data types that you'd want to print. Take a look in Print.cpp:

  size_t print(const __FlashStringHelper *);
    size_t print(const String &);
    size_t print(const char[]);
    size_t print(char);
    size_t print(unsigned char, int = DEC);
    size_t print(int, int = DEC);
    size_t print(unsigned int, int = DEC);
    size_t print(long, int = DEC);
    size_t print(unsigned long, int = DEC);
    size_t print(double, int = 2);
    size_t print(const Printable&);

    size_t println(const __FlashStringHelper *);
    size_t println(const String &s);
    size_t println(const char[]);
    size_t println(char);
    size_t println(unsigned char, int = DEC);
    size_t println(int, int = DEC);
    size_t println(unsigned int, int = DEC);
    size_t println(long, int = DEC);
    size_t println(unsigned long, int = DEC);
    size_t println(double, int = 2);
    size_t println(const Printable&);
    size_t println(void);

Regardless, within the limits that you've set (and your undefined specification of "easy to understand"), I don't see a ready way to do what you're asking.

If you move to Sloeber (Arduino eclipse plugin) different serial outputs appear in different colors.
You could also use a macro to wrap the standard Serial.print to simply create a header for each message to indicate its source. There are examples of debug macros which use variadic arguments which would be useful if you pass varying numbers of arguments.

I probably misunderstand your problem but: If you have multiple hardware serial ports and need more specificity that "Serial", "Serial1" and Serial2" etc, can you just alias the names like:

HardwareSerial *TeenSy3 = (HardwareSerial *)&Serial;
HardwareSerial *WeMosSoft = (HardwareSerial *)&Serial2;
HardwareSerial *cp2102 = (HardwareSerial *)&Serial3;

.
.
.
void setup( void )
{
    TeenSy3->begin( 9600 );
    WeMosSoft->begin( 38400 );
    cp2102->begin( 115200 );
    .
    .
    .
    
}//setup

void loop( void )
{
    .
    .
    .
    TeenSy3->println( "Hello World." );
    WeMosSoft->println( "Hello World." );
    cp2102->println( "Hello World." );
    .
    .
    .
    
}//loop

to make it easier to identify which serial messages are going where?

6v6gt:
If you move to Sloeber (Arduino eclipse plugin) different serial outputs appear in different colors.

Or, open a PuTTY window for each COM port and put a yellow Post-It on the screen over each PuTTY window to identify the source:

All you need to do is create a trivial class that inherits from Stream or Print, then implement the handful of trivial Print low-level virtual functions to do character-level i/o to the device of your choice. You can then have the write function watch for line-endings and output the port ID message either on just the first line, or every line, or whatever you want. Print gives you ALL the print functions you're used to using without you having to write any code. Your constructor can take an argument of type &Stream or Stream* to tell it what device to write to.

class DebugPrint : public Print {
public:
  Print* stream;
  bool nl = true;
  virtual size_t write(uint8_t b) {
    if (nl) {
      LOG_DEBUG_PRINT_PREFIX();
      nl = false;
    }
    if (b == '\n') {
      nl = true;
    }
    LOG_OUTPUT.write(b);
    return stream->write(b);
  }
} debugPrint;

other examples

class hierarchy of Print and Stream

StefanL38:
It is a very conscious decision of mine to refuse to study and expand my programming-knowledge at this point.

So the first question is: Would I have to write a new class in a library to do so or will it be eaiser?

My problem is that you have not provided an example of the sort of output you want.

I use the following few macros to present debug info without needing a whole lot of typing. Maybe you could adapt the idea to your requirement

#define debugTxt(abc) Serial.println("<  "#abc" >");
#define debugVar(abc) Serial.print("<  "#abc" = "); Serial.print(abc); Serial.println('>')
#define debugTxtVar(abc, def) Serial.print("<  "#abc" = "); Serial.print(def); Serial.println('>')

For example I use the 3rd one like this

debugTxtVar("this is text", myVar);

and it prints
This is text myVar = 236

Apologies if I am so far off the mark that I am on Mars rather than Earth

...R

PS ... I probably got this idea from elsewhere on the Forum, but I can't remember

Hi everybody,

thank you very much for answering and your suggestions. Robin2 is right

My problem is that you have not provided an example of the sort of output you want.

So I will add the explanation and example here
usually if you just deal with one µC

serial.print("My Debugmessage");

will do

No I have two µC let's name them "µC_A" and "µC_B"
Both µClers are connected to my computer via USB-cable. The standard "Serial" for flashing.

For µC_A talking to µC_B they use a different serial interface. Let's name them "mySerial" and "Serial3"

one way to monitor the serial data that is exchanged between "µC_A" and "µC_B" would be
inside the Wemos-Code
whereever there is a function-call mySerial".print(anything)
to add a two lines of code
Serial.print("Wemos->Teensy:");
Serial.print(anything);

Inside the Teensy-code
whereever there is a function-call Serial3.print(anything)
to add a two lines of code
Serial.print("Teensy->Wemos:");
Serial.print(anything);

So I can easily identify and distinguish the messages send between "µC_A" and "µC_B"
from other things.

Now with the specialised print-version I want to write a function "myPrint()" where I just write one line of code

myPrint(anything);

and what this single line of code is doing is
Wemos-Code:
mySerial".print(anything)
Serial.print("Wemos->Teensy:");
Serial.print(anything);

Teensy-code

Serial3".print(anything)
Serial.print("Teensy->Wemos:");
Serial.print(anything);

I think what Robin suggested is a different approach than creating a new function myPrint. It uses the macro-capabilities of the compiler so I have to write a short line and the compiler replaces the short line
with the pattern defined in the macro-expression.

Very cool way of doing this. This leads me to the idea:
is there anywhere a "how to effectively debug-Tutorial" ?

best regards Stefan

RayLivingston:
All you need to do is create a trivial class that inherits from Stream or Print, then implement the handful of trivial Print low-level virtual functions to do character-level i/o to the device of your choice. You can then have the write function watch for line-endings and output the port ID message either on just the first line, or every line, or whatever you want. Print gives you ALL the print functions you're used to using without you having to write any code. Your constructor can take an argument of type &Stream or Stream* to tell it what device to write to.

Hi Ray,
thank you for providing this text. I apologise right now for what I write next:
This is an excellent example of how to piss off beginners.
the main recipe is "write a short text overloaded with special tecnical terms"
For a beginner there are arising the following questions:

  • what is a class?
  • what means "inherit"
  • what means "virtual"-function compared to "non-virtual"-function?
  • what is an constructor?
  • what does "Stream" mean in this case
    Now imagine you would have to explain all these questions through tutorials.
    Loads of work. If you have fun with it explaining it in easy to understand words I'm happy having initiated an occasion to have fun for you. If you don't have fun writing it - just drop it.
    And I'm sure that it took you months of programming and learning to come to this point
    of expertise to write it like that. You can tap on your shoulder to have so much knowledge about programming C++. Wow!
    That's why I wrote at the starting either post ready to use code including examples - but only if you have fun writing it!" or present an easy to understand description of another solution.
    What Robin2 answered was still pretty compact but I estimate with playing for half an hour with it I will have analysed the details.
    best regards Stefan

Here is an example of the macro solution I mentioned already in post #2.

#define DEBUG
// from https://forum.arduino.cc/index.php?topic=64555.0
#ifdef DEBUG
#define DEBUG_PRINT(...) Serial.print(__VA_ARGS__)
#define DEBUG_PRINTLN(...) Serial.println(__VA_ARGS__)
#else
#define DEBUG_PRINT(...)
#define DEBUG_PRINTLN(...)
#endif

The problem with developing your own version of Serial.print at he C++ level is (a) the exotic data types it handles and (b) the variable number of arguments. You increase the complexity as soon as you do something like this:
Serial.print( 68, HEX ).
A preprocessor macro can handle all that by a simple textual substitution.

Hi 6v6gt,

I made the decision to use the macro-approach. Your arguments second that.
Would you mind providing an example and/or some further explanation of the "VA_ARGS"-macro?

is this a predefined macro like "FILE" ?

And as additional question:
I have written some explanation about a "debug"-macro.
What would be a good suited place to post this little "how to use macros for serial-debaug-output"

best regards Stefan

if you decide to revisit the Stream wrapper idea, I use it in this file

search for "debugPrint"

Juraj:
if you decide to revisit the Stream wrapper idea, I use it in this file
WiFiEspAT/EspAtDrv.cpp at master · JAndrassy/WiFiEspAT · GitHub
search for "debugPrint"

Given that OP is looking for a beginner-friendly solution, it's probably worth mentioning that both LOG_DEBUG_PRINT_PREFIX and LOG_OUTPUT are defined in a separate file.

HI everybody, so I have a first working version using macros to send serial debug-output to two serial ports
I stumbled over some details which I have described in the comments at the beginning.

I took Robin2's example as a start and renamed several things to be more selfexplaining.

here the complete code that I used for testing. This code has some additional useful functions

#include <SoftwareSerial.h>
// Teensy-IO14(Tx3) <-> WeMos-D8(RX)
// Teensy_IO15(Rx3) <-> WeMos-D0(Tx)
                      //RX, TX        WeMos_Rx     WeMos_TX      
SoftwareSerial mySerial(15, 16); //   GPIO15=D8    GPIO16=D0      

void PrintFileNameDateTime()
{
  Serial.println("Code running comes from file ");
  Serial.println(__FILE__);
  Serial.print("  compiled ");
  Serial.print(__DATE__);
  Serial.print(" ");
  Serial.println(__TIME__);  
}


boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}

unsigned long MyTestTimer = 0;                   // variables MUST be of type unsigned long
const byte    OnBoard_LED = 2;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);
  
  if ( TimePeriodIsOver(MyBlinkTimer,BlinkPeriod) ) {
    digitalWrite(IO_Pin,!digitalRead(IO_Pin) ); 
  }
}



void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  mySerial.begin(115200);
  Serial.println("mySerial.begin(115200); done");
  mySerial.println("Hello, world?");  
}


#define debugTxtVar(myParameterText, variableName) \
        Serial.print("pre and fixed-text "#myParameterText" "#variableName"= "); \
        Serial.print(variableName); \
        Serial.println(" suffixed text")

// this backslash "\" in the #define is the multiline-feature        
// for macros. Dividing the macro in multiple lines makes ist better readable
// do NOT add a backslash in the last line
// if you would like to have an empty line for formatting
// this line must have at least a backslash "\" for indicating multiline

#define Print(PrintToBoth) \
        Serial.print("WemosUSB:<"); \
        Serial.print(PrintToBoth); \
        Serial.print(">"); \
        \        
        mySerial.print("Wemos_myS:<"); \
        mySerial.print(PrintToBoth); \
        mySerial.print(">"); \
        

#define Println(PrintToBoth) \
        Serial.print("WemosUSB:<"); \
        Serial.print(PrintToBoth); \
        Serial.println(">"); \
        mySerial.print("Wemos_myS:<"); \
        mySerial.print(PrintToBoth); \
        mySerial.println(">"); \

        

void loop() {
  BlinkHeartBeatLED(OnBoard_LED,500);

  if (mySerial.available() ) {
    Serial.write(mySerial.read());  
  }
    
  if ( TimePeriodIsOver(MyTestTimer,1000) ) {
    Print("Debug test ");
    Println( millis() );
  }  
}

and here the most stripped down version with just the macros setup and loop (and this unsatisfying delay() |-(( :frowning: :o

#include <SoftwareSerial.h>
SoftwareSerial mySerial(15, 16); //   GPIO15=D8    GPIO16=D0      

void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  mySerial.begin(115200);
  Serial.println("mySerial.begin(115200); done");
  mySerial.println("Hello, world?");  
}

#define debugTxtVar(myParameterText, variableName) \
        Serial.print("pre and fixed-text "#myParameterText" "#variableName"= "); \
        Serial.print(variableName); \
        Serial.println(" suffixed text")

// this backslash "\" in the #define is the multiline-feature        
// for macros. Dividing the macro in multiple lines makes ist better readable
// do NOT add a backslash in the last line
// if you would like to have an empty line for formatting
// this line must have at least a backslash "\" for indicating multiline

#define Print(PrintToBoth) \
        Serial.print("WemosUSB:<"); \
        Serial.print(PrintToBoth); \
        Serial.print(">"); \
        \        
        mySerial.print("Wemos_myS:<"); \
        mySerial.print(PrintToBoth); \
        mySerial.print(">"); \
        

#define Println(PrintToBoth) \
        Serial.print("WemosUSB:<"); \
        Serial.print(PrintToBoth); \
        Serial.println(">"); \
        mySerial.print("Wemos_myS:<"); \
        mySerial.print(PrintToBoth); \
        mySerial.println(">"); \

        
void loop() {
    Print("Debug test ");
    Println( millis() );
    delay(2000);
}

best regards Stefan