Digital Electronics

PHYS 432 - Serial Console

Updated Wednesday May 05, 2021

Introduction

This task introduces the serial port (or console) which allows you to send text strings from your Arduino Nano back to your computer over USB where you can read them. This technique can be extremely useful if a sketch isn't working the way you think it should, as trying to debug a microprocessor just on the basis of a few LEDs is very frustrating. The same method can also be used to send text to your Arduino, but we won't explore that capability here.

Wiring

There is no particular wiring needed for this task. If you still have your LED and button wired up from Task 1, it is fine to leave it in place. We won't be using any of the Arduino pins, so there is no harm having something attached to them.

Sketch

Put the following sketch into the Arduino IDE or else download it from task2.ino. There are a number of new concepts introduced here, but the basic idea is to simply write a message every second showing how many loops have executed.


/*
 * Task2
 * 
 * Write messages over the serial port
 * 
 */

// Declare outside loop() so it can be used in both code blocks
int n;

void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);

  // Write something out to show we are alive
  Serial.println("Starting...");
  
  // Initialize our variables
  n = 0;
}

void loop() {
  
  // Increment count, equivalent to n = n+1;
  n++; 

  Serial.print("The loop has iterated "+ String(n));
  if (n==1) {
    Serial.println(" time");
  }
  else {
    Serial.println(" times");
  }
  
  // Wait 1 second
  delay(1000);
}

How it Works

Before the setup() block, there is a variable declaration. If you want to use a variable in both the setup() and loop() block, it needs to be declared outside these blocks. This is known as the scope of the variable, and if it is declared inside a block, it only is known inside that block's scope. By declaring it outside, the variable has global scope, and the same variable can be accessed from both blocks. I could have avoided the initialization to zero in the setup() block by simply adding an initializer to the declaration: int n=0;. Note that if n was declared within the loop() block, it would be reinitialized on each iteration of the loop, and wouldn't retain the value from the previous loop. The static variable qualifier could be used to fix this, but for non-experts this is more confusing than helpful.

Our serial connection from the Arduino Nano back to the computer is initialized with the Serial.begin(9600); statement. This uses a special chip (on the back of the board) to translate the RS-232 serial communication protocol to run over a USB bus. The number is the baud rate used for the connection. If you have never heard the term "baud rate" before, be thankful that you are young. Baud is essentially a measure of bits sent per second (including any overhead or control bits). My first network connection as a grad student ran at 2400 baud over a phone line, or 0.002 Mb/s... Unless you are sending lots of data, just leave this number alone. Serial supports a wide range of speeds up to 115200 baud, but the faster you go, the more you risk losing characters. Serial is actually a class, and it has global scope, so you don't need to declare any variables here. All calls to Serial will access the same serial connection back to your computer.

We can write text to the serial port using Serial.println(). This command automatically terminates whatever text you send with a special "newline" character, that makes the output legible by showing up on the screen in its own line. Later, we use the print function which does not append the newline to print part of a line. Anything within double quotes is treated as a string (or character array) and written one byte at a time to the serial port.

In the loop, we increment the value stored in n and then write this value to the serial port. Here we use some nice features of C++ to first convert the integer n into its representation in a C++ style String object. We can then add this object to a C-style string indicated by the double quotes, and the compiler will automatically make the type conversion and concatenate the two pieces together. We could equally well write the static part first, then write the integer with a second call to Serial.print() but this addition of String objects can be rather handy. More information on the String object data type can be found here.

To avoid grammatical errors, we print a different ending to the line if n is one or more than one. This is the sign of a considerate programmer! The if/else statement is standard C syntax, but looks a bit goofy if you are used to python. This is the price for not needing to care about indenting. The parentheses enclose the conditional statement, and the curly brackets enclose the code blocks to execute if the conditional statement is satisfied. The indentation is there to make the code look pretty, but isn't needed for any functionality. This entire thing could be put on one line with all spaces removed and it would still work. Note that here we only use Serial.println() when we want to end the line. Multiple print statements will just keep sending characters that will appear on the same line. We finish this off with a 1 second delay so that we aren't flooded with text.

Compiling and Uploading

Follow the same procedure described on the Arduino IDE page to compile and upload your sketch. You may be surprised to see that while we are now using close to 10% of both the program memory space and the dynamic memory. Stings are expensive, and the construct we have used above makes the compiler create a string variable in dynamic memory and fill it by copying the static string values (within the double quotes) from the program memory. If you use lots of static strings, you will quickly fill up your precious dynamic memory.

There are a couple of ways around this. The easiest is to wrap all static (double quote constant) strings with F(). This construct directs the compiler to leave the string in the program (Flash) memory and access it directly from there. This is by far the most convenient solution, as you just need to convert "my string" to F("my string") to make this work. The second method is to use the PROGMEM directive to specify that this value is to be read from program memory. This PROGMEM directive actually works with any data type, but the syntax is a bit complicated, so the F() directive was invented as a simpler shortcut to deal with strings. Describing how PROGMEM works is beyond the scope of this tutorial, but you can read more about this at the PROGMEM reference page if you are interested. By wrapping my static strings with the F() directive, I was able to reduce the dynamic memory usage and the program memory usage by a few hundred bytes (copying to dynamic memory involves additional instructions). The tradeoff is that the nice addition operator no longer works, so I need to break this string up into two separate print commands.

Serial Console

If you successfully uploaded this sketch, you may be wondering where the text is going. It doesn't show up anywhere automatically, but there is a serial console to interact with the serial stream under "Tools : Serial Monitor". This will open a window that will show all the data written to the serial port by the Arduino. Note that in the lower right there is a menu to select the baud rate. If you change this number in your sketch, you will also need to change it here to match. Now that the serial monitor is open, restart your Arduino (you can either push the reset button on the board, or just upload the sketch again) and make sure that you see the full sequence of text written. This serial console can also be used to send data back to the Arduino over the same serial port. The sketch must explicitly read this data from the serial port, and describing how to do this is the topic of many Arduino tutorials, so I won't reproduce that here.

If you can see the text sent by the Arduino on your computer, congratulations! The serial port can be extremely useful to help debug code that isn't quite working the way you want. Don't be afraid to stick Serial.println() statements into your code to check that things are working properly.

Going Further

While not necessary, there are a lot of nice examples of using the serial port to transfer data to or from your Arduino. The example found under "File : Examples : Basic : DigitalReadSerial" shows an example of reading a button state and writing the value to the serial port. Try to adapt this to the code from Task 1 and see if you can read your button. If you remove the delay, you can probably sample the button fast enough to see the button bouncing, although the characters will go by very fast on the screen. A more interesting example is shown in "File : Examples : Communication : Dimmer". This allows you to set the brightness of an LED by typing a number into the Serial Monitor on your computer and transmit this number to the Arduino over the serial port. This number is then used to control the duty factor of a PWM-enabled pin with an LED attached to control how bright the LED appears. Look at the link shown in the comments for more details about how to construct this circuit and understand how it works.

When you are done playing around, save your sketch somewhere you can find it, and move on to Task 3.