Using the serial monitor for debugging / analysing what is going on inside your code

If you want to write programs for an arduino or other microcontroller that can be programmed with the Arduino-IDE you need to have a connection from your computer to the microcontroller-board. Mostly a USB-cable is used for this.

You upload your program and the microcontroller starts to execute the uploaded code.

If the upload has finished you can use this exact same connection to analyse what is going on inside your code.

For this analysing you add commands that "print" to the serial-monitor.
Almost any microcontroller has this serial interface. If you use the arduino-IDE this serial interface is used for flashing and can be used for communicating in both directions

microcontroller ==> computer
Computer ==> microcontroller or in short

microcontroller <=serial-interface=> computer

To send from computer to microcontroller and to receive into the computer
There is the tool Serial monitor

The window of the serial monito has five important options
grafik

If you click on this checkbox the content shown in the serial monitor-window is always scrolled down automatically to make the last lines readable. Older lines move up out of sight.

grafik
Show timestamp enables to add a timestamp on the leftside. This is handsom if you are interested in how much time is between to receivings

The baudrate-Option

grafik

The baudrate in the serial monitor must match the baudrate in your code
You can adjust the baudrate to any value.

The most common baudrate used today is 115200 baud


Though a lot of example-codes use 9600 baud.

The lower the number the longer it takes to send/receive a byte.
9600 baud has a substantial slow-down-effect on code that should run fast.

So adjust it to 115200 baud.
Inside your code this is done with the function-call

Serial.begin(115200);

where the number 115200 is the baudrate.

Thats how the serial monitor can be adjusted
Next post is about how to get words send from your microcontroller to the serial monitor

The most minimalistic sketch to get some words onto the serial monitor is

void setup() {
  // activate serial interface with a baudrate of 115200
  Serial.begin(115200); 
  Serial.println( F("Hello World") );
}

void loop() {
}

As setup runs only once per power-on / reset
a single line with the coded words "Hello World" apears in the serial monitor

With each pressing of the reset-button the program starts new sending this single line with Hello World

Next step is to use

Serial.print()

inside function loop.

preliminary remark

The function of delay() is easy to understand.
However, there is a serious disadvantage of delay().

You get used to a programming style that greatly hinders multifunctionality.
I will explain the bad properties of delay().
These bad qualities are the almost total blocking of code execution.

So here is the bad example

int myCounter = 0;

void setup() {
  // activate serial interface with a baudrate of 115200
  Serial.begin(115200); 
  Serial.println( F("setup finished") );
}

void loop() {
  myCounter++; // increase value if variable myCounter by 1
  Serial.print( F("myCounter has value ") );
  Serial.println(myCounter);
  delay(1000); // block code-execution for 1000 milliseconds = 1 second
}

This is the output seen in the serial monitor

There is only one line
grafik
Showing that setup is really executed only once
and there are multiple lines
grafik

showing how function loop is "looping"
From the timestamps on the left you can see that each new line is printed approximated one second after the line before

The variable counter is increased once per iteration and as the code-execution is blocked by the delay(1000); the variable myCounter is increased only once per second.

In the next post I introduce you to the non-blocking timing that enables much more functionality like

  • flashing any kind of LED pattern and still react on a button-press at any time
  • driving a stepper-motor and react on a limit-switch at any time
  • executing some code at high speed while other code is only executed once every 3 seconds or once every 7 days

Use the F() macro always for debug - make sure your house doesnt collapse from the weight of scaffolding.

Next I introduce you to my personally preferred way of using non-blocking timing.
It deviates from the standard-example in the Arduino-IDE-examples for the following reasons:

  • The standard blink without delay example requires to have mutliple variables in multiple lines of code.

  • Badly chosen variable-names that make it harder to understand what is going on

my version has a function that has a self-explaining name and reduces the code to write to
two lines

  1. defining a Timer-variable
  2. using the function
unsigned long myCounter = 0;
unsigned long myDelayTimer = 0;

// helperfunction for easy to use non-blocking timing
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
}

void setup() {
  // activate serial interface with a baudrate of 115200
  Serial.begin(115200);
  Serial.println( F("setup finished") );
}

void loop() {
  myCounter++; // incriment variable myCounter with EVERY iteration of loop()

  if (  TimePeriodIsOver (myDelayTimer, 1000) ) {
    // only if 1000 milliseconds have passed by print to the serial monitor
    Serial.print( F("myCounter has value ") );
    Serial.println(myCounter);
  }
}

The serial output shows this

As you can see the numbers are big and increase very fast.
This is caused by the non-blocking timing
the line of code

  myCounter++; // increment variable myCounter with EVERY iteration of loop()

is outside the timing-function
void loop() is looping very fast as you can see from the very fast increasing numbers of variable myCounter

But the printing to the serial monitor is executed only once per second as you can see from the time-stamps on the right.

The function "TimePeriodIsOver" has a self-explaining name.
As the name of the function says

Only if that amount of time specified in the second parameter of the function has passed by
execute the code inside the if-condition.

In the example-code this is the number 1000
if ( TimePeriodIsOver (myDelayTimer, 1000) ) {

representing 1000 milliseconds which is 1 second.

You should start playing with this number 1000 = changing it to different values to see the effect it has

Now I want to expand the code some more to have a fast incrementing variable and a slow incrementing variable

unsigned long myOncePerSecondCounter = 0;
unsigned long myFastCounter = 0;

unsigned long myDelayTimer = 0;

// helperfunction for easy to use non-blocking timing
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
}

void setup() {
  // activate serial interface with a baudrate of 115200
  Serial.begin(115200);
  Serial.println( F("setup finished") );
}

void loop() {
  myFastCounter++; // increment variable myFastCounter with EVERY iteration of loop()

  if (  TimePeriodIsOver (myDelayTimer, 1000) ) {
    // only if 1000 milliseconds have passed by print to the serial monitor
    myOncePerSecondCounter++;
    Serial.print( F("myFastCounter has value ") );
    Serial.println(myFastCounter);

    Serial.print( F("myOncePerSecondCounter has value ") );
    Serial.println(myOncePerSecondCounter);
  }
}

The serial output looks like this

So this shows you can have things that get executed very fast in combination with things that get executed only once every "X" seconds, "X" minutes, "X" hours, "X" days.

Yes for sure X days. Variables of type unsigned long can be as big as 4.294.967.295
which are
2^32 - 1
Ans = 4.294967295e9
Ans/1000 (milli-sweconds per second)
Ans = 4.294967295e6
Ans/3600 (seconds per hour)
Ans = 1193.046470833
Ans/24 (hours per day
resulting in
Ans = 49.710269618 days

your function has a bug.

myDelayTimer is a global variable thus initialized to 0.

if you were to perform the test

if (  TimePeriodIsOver (myDelayTimer, 1000) ) {

long in the execution of the sketch, say 10 seconds after the sketch started running, then in your function the test

  if ( currentMillis - expireTime >= TimePeriod ) {

is true right away and you don't wait...

the same applies if you only call this from time to time (say you want a 1s delay but it happens every 10 seconds because the code is busy doing something else)

in order for you function to work, you need to initialise ONCE (the tricky part) myDelayTimer to millis() before calling your function...

see the issue?

here the issue is not really visible because the setup is super short and you jump right in

Why did I expand on non-blocking timing in a tutorial about using serial-debug-output?

Well the examples show how to add serial debug-output
by using the functions

  Serial.begin();
  Serial.println(  );

and give a heads up on a second aspect non-blocking timing.

Here follows a link to an even more comfortable version for serial debug-output that makes use of something that is called "macro"
There is some explanation in this tutorial what a macro is.

Yes I can see it.

Some additional information

  1. Serial print can affect the timing in your code; the lower the baud rate, the bigger the chance for that.
  2. If one tries to debug crashing code, it is advisable to use Serial.flush() after a Serial print statement so the data is displayed before the code crashes.