Lesson 3: Debugging with Serial
Table of Contents
- Using Serial.print for debugging
- Modify your blink code to use Serial.print
- Use the built-in LED
- Visualizing data with the Serial Plotter
- Exercises
- Lesson Summary
- Next Lesson
Figure. A video showing how to use Serial.println() to debug code. For this, we modified the simple blink program to add serial prints (source code).
Debugging code is always hard. Debugging code+circuits is even harder! πͺπ½
Now that weβre on our third Arduino lesson (woohoo!), itβs time to introduce some Arduino debugging strategies.
For hardware debugging, multimeters and oscilloscopes are useful. If you donβt have access to these tools, we recommend building your circuits in a simulation tool like Tinkercad Circuits and using their virtual instruments (e.g., multimeters). If youβre physically building something and itβs not working, you could try to replicate it in Tinkercad or some other simulation tool.
For code debugging, itβs common to use βprintlineβ statements (yes, I know! π€£)βsee video above. While the Arduino IDE 2 introduced debugging support (breakpoints, code stepping), it requires specific boards and hardware. For most of us, Serial.print remains the go-to technique. You can also always use the debugger in Tinkercad Circuits to step through code in simulation.
Using Serial.print for debugging
Using βprint outβ statements to βconsoleβ is perhaps the oldest software debugging technique. It is the standard technique for Arduino as well although can be tedious at times.
Unlike JavaScript, Java, C# or other code that runs in your web browser or natively on your laptop, your Arduino code is running on the Arduinoβs microcontroller (e.g., the Uno uses the ATmega328P; Leonardo uses the ATmega32u4). Thus, when we βprint to consoleβ, we actually need to get the data from the Arduinoβs microcontroller on to your development computer. For this, Arduino uses the serial protocol. More specifically, the function Serial.print() and Serial.println().

These two functions print data to the serial port as human-readable ASCII text (the println version simply inserts a carriage return \r followed by a newline character \n). To send data without converting it to ASCII text, you need to use Serial.write().
In later lessons, weβll see how to use serial communication for more than just debugging purposes but to actually bidirectionally communicate with the computer (see L1: Intro to Serial and these ITP examples). For our introductory lessons, however, weβll just use it to print out information about how our program is performing.
Arduino Uno: Once you turn on serial via
Serial.begin(), you can no longer use digital Pins 0 or 1 for I/O because on the Uno, these pins are shared with the USB-serial communication chip (digital pin 0 is RX and pin 1 is TX). See Arduino documentation.Arduino Leonardo: This restriction does not apply. The Leonardo handles USB communication natively through its ATmega32u4 chip, so
Serialgoes directly over USB without using Pins 0 or 1. On the Leonardo, Pins 0 and 1 are a separate hardware serial port accessible viaSerial1.This is one reason many of our βstarterβ examples use Pin 3 rather than Pins 0 or 1, avoiding this Uno limitation. Pin 3 also has the added benefit of being configurable for analog output, which weβll get to in the next lesson.
Build a simple βHello World!β Serial.print program
Letβs build a simple βHello World!β program that uses Serial.print functionality to receive ASCII data over the serial port. We donβt even need external hardware for this: just our Arduino Leonardo and a USB cable.
Step 1: Initialize the serial port
To use the serial port, we must first initialize it with Serial.begin(BAUD_RATE). The baud rate is the transmission speed in bits per second (bps) and is typically set to 9600 unless greater speeds are needed. Because the Serial library uses asynchronous communication, both the transmitter and receiver need to agree on the speed of communication (the baud rate). So, you will also have to set the baud rate in the βSerial Monitorβ window (see Step 3 below).
Typically, we initialize the serial port in setup(), as it only needs to run one time.
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {}Step 2: Use Serial.print and Serial.println to write data
Hereβs a complete program that writes βHello world!β once every 500 ms.
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {
Serial.println("Hello world!");
delay(500);
}Step 3: Open βSerial Monitorβ in the Arduino IDE
Finally, to view the incoming serial data, open up the Serial Monitor in the Arduino IDE.

And you should see something like this:

The full code is on GitHub here.
Printing out variables
You will obviously want to print out more than just strings. So, how do you print out variables?
The simple answer is to use multiple Serial.print and Serial.println statements. To print variables, put the variable as the sole parameter (see below). A more complicated answer is available in our Inside Arduino guide. You can also see the example code on the Serial.print API page.
Below, weβve written a simple program to print out the current time (in milliseconds) since the Arduino was turned on and our program began to run:
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {
// Get the current time since the Arduino started our program (in ms)
unsigned long currentTimestampMs = millis();
Serial.print("Time since Arduino started: ");
Serial.print(currentTimestampMs);
Serial.println(" ms");
delay(500);
}
This code is also on GitHub here.
Modify your blink code to use Serial.print
Now, letβs return to our blink code and modify it to use Serial.print to print out when the LED is on and off.
const int LED_OUTPUT_PIN = 3;
void setup() {
pinMode(LED_OUTPUT_PIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
// Turn on the LED
digitalWrite(LED_OUTPUT_PIN, HIGH); // turn LED on (5V)
Serial.println("Pin 3 is HIGH (5V)"); // print status
delay(500); // delay is in milliseconds
// Turn off the LED
digitalWrite(LED_OUTPUT_PIN, LOW); // turn LED off (0V)
Serial.println("Pin 3 is LOW (0V)"); // print status
delay(500); // delay is in milliseconds
}Hereβs a video of my code running with the Serial Monitor in the background.
Figure. A video of the blink program with serial prints showing in the Serial Monitor (source code).
Use the built-in LED
In addition to Serial.print, we can also use the Arduinoβs built-in LED for some quick debugging (e.g., turn on the built-in LED to indicate some program state without hooking up an external LED circuit).
On the Arduino Uno and Leonardo, the built-in LED is on Pin 13. So, if you write digitalWrite(13, HIGH); in your code, the built-in LED will turn on. Because not all Arduino boards have the built-in LED at Pin 13, you should use the constant LED_BUILTIN rather than a literal pin number.

In fact, the official Arduino Blink example uses the built-in LED and the constant LED_BUILTIN to demonstrate blinking. This is also the program that ships with your Arduino and runs when you first power it up.
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}You can access this example directly in the Arduino IDE:

Visualizing data with the Serial Plotter
Weβve now learned how to use Serial Monitor and the built-in LED (LED_BUILTIN) to help with debugging.
A third approach, which also uses serial, is called Serial Plotterβa built-in tool that graphs numeric values over time. This is useful for visualizing how values change and will become even more powerful once we start working with analog input (sensors) in later lessons. But we can start using it right now with the code weβve already written.
Plotting the LED state
Letβs make a new program that steadily increases a state value called _triangleValue. Once it passes a certain threshold TURN_ON_THRESHOLD, we turn on the LED at LED_BUILTIN.
const int LED_OUTPUT_PIN = LED_BUILTIN;
const int MAX_THRESHOLD = 255;
const int MIN_THRESHOLD = 0;
const int TURN_ON_THRESHOLD = 128;
int _triangleValue = 0;
int _triangleStep = 5;
void setup() {
pinMode(LED_OUTPUT_PIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
// If tri value great than threshold, turn on led
int ledState = (_triangleValue > TURN_ON_THRESHOLD) ? HIGH : LOW;
digitalWrite(LED_OUTPUT_PIN, ledState);
// Print triangle value
Serial.println(_triangleValue);
// Update triangle wave
_triangleValue += _triangleStep;
if (_triangleValue >= MAX_THRESHOLD ||
_triangleValue <= MIN_THRESHOLD) {
_triangleStep = -_triangleStep;
}
delay(30);
}To open the Serial Plotter, go to Tools -> Serial Plotter or click on the βgraph iconβ in the Arduino IDE toolbar. Hereβs a video of this code running with the Serial Plotter showing a triangle wave.
Figure. I first open Serial Monitor to see the raw values before opening the Serial Plotter to see a real-time graph.
Plotting multiple lines with labels
The Arduino IDE 2 Serial Plotter supports multiple named data lines with auto-generated legends and distinct colors. To use this feature, format your output as comma-separated Label:value pairs, ending with a newline. For example:
Label1:value1,Label2:value2\n
Letβs modify our previous program but graph the TURN_ON_THRESHOLD.
const int LED_OUTPUT_PIN = LED_BUILTIN;
const int MAX_THRESHOLD = 255;
const int MIN_THRESHOLD = 0;
const int TURN_ON_THRESHOLD = 128;
int _triangleValue = 0;
int _triangleStep = 5;
void setup() {
pinMode(LED_OUTPUT_PIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
// If tri value great than threshold, turn on led
int ledState = (_triangleValue > TURN_ON_THRESHOLD) ? HIGH : LOW;
digitalWrite(LED_OUTPUT_PIN, ledState);
// Print labeled values for Serial Plotter
Serial.print("Turn-on Threshold:");
Serial.print(TURN_ON_THRESHOLD);
Serial.print(",");
Serial.print("Triangle:");
Serial.println(_triangleValue);
// Update triangle wave
_triangleValue += _triangleStep;
if (_triangleValue >= MAX_THRESHOLD ||
_triangleValue <= MIN_THRESHOLD) {
_triangleStep = -_triangleStep;
}
delay(30);
}In the Serial Plotter, youβll see two colored lines with a legend: Turn-on Threshold as a solid line at the TURN_ON_THRESHOLD value and Triangle as a triangular ramp of _triangleValue. The IDE automatically assigns colors and generates the legend from your labels. You can click on the checkboxes to toggle which values are graphed.
Figure. Adding in a threshold value to Serial Plotter. Notice how each graphed value now has a color and label. Note: you can toggle the checkboxes on/off to select which values are graphed.
A peek ahead: plotting sensor data
This section previews analog input, which we cover in the Intro to Input lessons. If you have a potentiometer or other analog sensor handy, you can try this nowβotherwise, come back to it later!
Once you learn analogRead in a future lesson, the Serial Plotter becomes especially powerful for visualizing real sensor data. For example, you could plot a sensor reading alongside a threshold value and use the built-in LED to indicate when the threshold is crossed:
const int SENSOR_PIN = A0;
const int THRESHOLD = 800;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
int sensorValue = analogRead(SENSOR_PIN);
// Turn on the built-in LED when sensor goes above threshold
if (sensorValue > THRESHOLD) {
digitalWrite(LED_BUILTIN, HIGH);
} else {
digitalWrite(LED_BUILTIN, LOW);
}
// Format output so the IDE auto-generates a legend and distinct lines
Serial.print("Raw_A0:");
Serial.print(sensorValue);
Serial.print(",");
Serial.print("Threshold:");
Serial.println(THRESHOLD);
delay(50);
}The Serial Plotter shows the sensor value fluctuating in real time with a flat threshold line for comparison, and the built-in LED lights up whenever the value goes above the thresholdβgiving you both visual feedback on the board and a graph on screen. This combination of Serial Plotter + indicator LED is a technique youβll use frequently when calibrating sensors and tuning interactive projects.
Figure. Graphing the analog input value. Learn more about analog input in our []βIntro to Inputβ lessons](/physcomp/arduino/intro-input.html).
Exercises
Want to go further? Here are some things to try:
- Blink counter. Modify the blink program to count and print the number of times the LED has blinked (e.g., βBlink count: 1β, βBlink count: 2β, β¦).
- How fast is loop()? Remove all
delay()calls from a simple program that just printsmillis(). How fast doesloop()execute? What do you observe in the Serial Monitor? - Experiment with baud rates. Try changing the baud rate in
Serial.begin()to115200instead of9600. What happens if the baud rate in your code doesnβt match the baud rate selected in the Serial Monitor?
Lesson Summary
Debugging is an essential skill for physical computing. In this lesson, you learned:
- How to initialize serial communication using
Serial.begin(). - How to send text and variable data from the Arduino to your computer using
Serial.print()andSerial.println(). - How to use the Serial Monitor to view text-based debugging information in real-time.
- How to use the Serial Plotter to visualize numerical data and thresholds graphically.
- How to use the Arduinoβs
LED_BUILTINas a quick visual debugging tool without needing to wire external circuits.
Next Lesson
Now that we know a bit about debugging with Serial.print() and the Serial Plotter, itβs time to learn about βanalog outputβ on the Arduino. Weβll be using Serial.print() throughout the rest of our tutorials.
Previous: Blinking an LED with Arduino Next: Fading an LED with Arduino