Interpreting Serial Data

Introduction

Serial data is passed byte by byte from one device to another, but it’s up to you to decide how each device (computer or microcontroller) should interpret those bytes, when the beginning of a message is, when the end is, and what to do with the bytes in between.  Serial communication protocols define the structure of communication between devices. These notes explain how serial data is interpreted by computers.

To get the most out of these notes, you should know what a microcontroller is and have an understanding of the basics of microcontroller programming. You should also understand the basics of serial communication. You should have some understanding of programming a personal computer as well, ideally in a language that can access the serial ports of the computer, like p5.js, the Processing programming environment, the node.js programming environment, Python, or Java.

This is a useful page to return to when you have a specific advanced serial communication problem to solve.

ASCII and Binary

Related video: ASCII Bytes Explained

Imagine that you’re sending the value of one sensor from a microcontroller to a personal computer. If the sensor’s value is always less than 255, you know it can fit in a single byte. This kind of message is easy. Just send the value over and over, and the receiver can read the latest byte to have your whole message. In Arduino, you can do this using the Serial.write() command, as shown in the Arduino sketch below:

Binary Serial Arduino Program

1
2
3
4
5
6
7
8
9
10
11
12
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  // read the sensor:
  int sensorValue = analogRead(A0);
  // divide by 4 to reduce the range to 0-255:
  sensorValue = sensorValue / 4;
  // send it:
  Serial.write(sensorValue);
}

Imagine a typical stream of bytes sent by the program above:

23 23 23 23 24 24 25 25 26 28 27 27 27

Every value you send can range from 0 to 255, the full range that can fit in a byte.  A protocol like this is often called a binary protocol, because there’s no other meaning to each byte’s value other than the value of the number itself.

Now imagine you want to send two sensor readings.  You might think “No problem; just add another analogRead() command and another Serial.write() command.” But the resulting stream of data would look the same. Each byte would range from 0 to 255, and you’d have no way to know which byte represents which sensor. You need some punctuation bytes.

If you’re sending more than one value (and you usually are), then the receiving computer has to know when the message starts and when it ends, and it needs to know how to put the bytes together into a message.

Computers use numbers to represent alphanumeric characters (letters and numbers and punctuation) in bytes. The first standard code for doing this was called the ASCII code which stands for American Standard Code for Information Interchange. ASCII assigns each number or letter a specific byte value from 0 to 127.  For example, capital A is ASCII value 65. Capital B is 66. A space is ASCII value 32. The numeral 0 is ASCII 48. ASCII includes only the characters for the English alphabet, though, so a newer protocol, the Unicode protocol, includes the ASCII character set and includes codes for other alphabets as well. The simplest subset of Unicode, UTF-8, is compatible with ASCII.

The ASCII table and Unicode set can be found in many computer manuals’ indexes, and all over the place online. These codes are used for number-to-character translation in every computer operating system and programming language.

Because ASCII and Unicode assign each alphanumeric character a unique value, including punctuation symbols, you can now differentiate the values in your serial stream by ASCII-encoding them.  Change the Serial.write() in the program above to a Serial.print() command, and add one more line:

ASCII-encoded Serial Arduino Program

1
2
3
4
5
6
7
8
9
10
11
12
13
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  // read the sensor:
  int sensorValue = analogRead(A0);
  // divide by 4 to reduce the range to 0-255:
  sensorValue = sensorValue / 4;
  // send it:
  Serial.print(sensorValue);
  Serial.print(",");
}

Now you’ve got a string of bytes representing numeric characters, AND a byte representing a comma.  What would the values of the bytes be?

50 51 44 50 51 44 50 51 44 50 51 44 50 52 44 50 52 44 50 53 44 50 53 44 50 54 44 50 56 44 50 55 44 50 55 44 50 55 44

Why is it so much longer, and what are all those 44s doing in there so regularly?  If you look at the ASCII table, you’ll see why. The sensor values are ASCII-encoded now.  That means that the value 23 is represented by the character “2” followed by the character “3”. In ASCII, “2” has the value 50 and “3” has the value 51. And “,” has the value 44.  So the numbers above, read as ASCII, translate to:

"23,23,23,23,24,24,25,25,26,28,27,27,27"

It takes more bytes to send data this way, but you have a more human-readable protocol, because most receiving programs will default to displaying byte values using the ASCII or unicode characters assigned to them.

Serial Terminal Programs

Related video: Serial 4 – Devices and Bytes

Most serial terminal programs assume that when you’re receiving serial data, it should be interpreted as ASCII characters, This is why you’ll see random characters when you open the Serial Monitor in Arduino after uploading the binary serial program above: the Arduino’s using a binary protocol, but the Serial Monitor thinks it’s an ASCII protocol.

The freeware program CoolTerm is a useful Serial Terminal application, because it can show you both ASCII and raw binary values. Download it, then open it. Click the Options icon, then choose your serial port from the Serial Port menu:

Screenshot of the CoolTerm options menu, showing the port name that's the same as your Arduino, and a rate of 9600 bps
Figure 1. CoolTerm options menu

Click OK, then click Connect (Figure 1). You should see the same random characters you were seeing in the Arduino IDE’s Serial Monitor as shown in Figure 2. (make sure you have the Serial Monitor closed before you connect in CoolTerm because Serial ports can only be controlled by one program at a time). But if you click on the View Hex icon, you’ll see a very different view:

CoolTerm "view hex" view
Figure 2. The CoolTerm serial terminal application showing the hexadecimal view.

Now you’re looking at the ASCII characters for each byte in the right hand column, and the raw binary values in the center column (in hexadecimal notation) as shown in Figure 3. This is really handy when you’re trying to interpret a binary protocol.

Click the Disconnect icon in CoolTerm  (because  Serial ports can only be controlled by one program at a time) and upload the ASCII-encoded Serial Arduino program above. Once the program’s uploaded, connect in CoolTerm again and look at how the hex view has changed:

The CoolTerm serial terminal application showing the hexadecimal view. The screen is filled with hexadecimal values in the center, along with the ASCII characters corresponding to those values running down the side. This time you see the numeric values of the ASCIII characters.
Figure 3. The CoolTerm serial terminal application showing the hexadecimal view. This time you see the numeric values of the ASCIII characters.

With this version of the program, the ASCII view is more readable. In the hex view, you can tell the commas from the regular data, because every third byte is 0x2C, or 44 in decimal, or “,” in ASCII.

Related video: Serial 7 – Reading Strings

Make the following final improvement to the program above:

ASCII-Encoded Serial Arduino Sketch with Line Break

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  // read the sensor:
  int sensorValue = analogRead(A0);
  // divide by 4 to reduce the range to 0-255:
  sensorValue = sensorValue / 4;
  // send it:
  Serial.print(sensorValue);
  Serial.print(",");
 
  // read another sensor:
  int sensorValue2 = analogRead(A1);
  // divide by 4 to reduce the range to 0-255:
  sensorValue2 = sensorValue2 / 4;
  // send it:
  Serial.println(sensorValue2);
}

When you view the output of this, you’ll see you get a string of two numbers, with a linefeed after each string. In hex view, these will be represented as 0x0A (linefeed) and 0x0D (carriage return). This is a simple multi-value protocol. You’ve got a comma separating the values, and a linefeed and carriage return separating each set of values.  This protocol is both easy to read, and easy for most personal computer programming environments to interpret. For more on that, see the Serial Duplex Lab using P5.js and p5.webserial.

Managing the Serial Buffer

Before you read this section, try the Serial Duplex Lab using P5.js and p5.webserial. This section will make more sense when you’ve seen the programs in that lab in operation.

The reason this form of  serial communication is called  “asynchronous” is that the data between the two devices involved is not synchronized. The sender can send when the receiver is not listening, and vice versa. Both sides typically maintain a serial buffer, which is a place in memory to store incoming serial data before it is used, as mentioned in the serial basics notes. On computers with an operating system, this serial buffer is maintained even when your program isn’t running. So when you stop your program and re-start it, there may be data in the buffer from a previous run of the program. This can cause errors. Most programming environments automatically flush this buffer every time you restart the program, but if the one you are using does not, then look for a function that flushes the buffer. It’s often called flush() and it’s good to run it right after you open the serial port. In Processing and p5.webserial it’s called clear().

In Processing:

// in your setup() after you create a new serial object called myPort: myPort.clear();

In p5.js with p5.webserial:

1
2
3
4
5
6
7
8
9
10
function openPort() {
  // wait for the serial.open promise to return,
  // then call the initiateSerial function
  serial.open().then(initiateSerial);
 
  // once the port opens, let the user know:
  function initiateSerial() {
    serial.clear();
  }
}

Make Sure There’s Any Data To Read

The first thing you can do wrong is to assume there’s data there to read when there isn’t. Imagine the following situation:

Your microcontroller is continually sending a string of three sensors values to a program on your personal computer, ASCII-encoded, comma-separated, and terminated by a newline, like so:

234,23,142\n

The controller is not waiting for a response from your desktop program, it’s just continually sending as fast as it can. The desktop program reading this data is waiting until it sees a newline character, then reading the whole buffer. Then it splits the incoming string into an array and converts the values to integers. You want to read only when a newline character comes in.

In p5.js using the p5.webserial library it might look like this:

1
2
3
4
5
function serialEvent(){
  let inputString = serial.readStringUntil("\r\n");
  // split the string into an array:
  let sensorReadings = split(inputString, ",");
}

In Processing, it might look like this:

// in the setup(), configure the serial object to generate serial events // only when a newline arrives, like so: myPort.bufferUntil(‘\n’); // then your serialEvent function looks like this: void serialEvent(Serial myPort){ String inputString = myPort.readString(); // split the string into an array: String[] sensorReadings = split(inputString, “,”); }

The receiving program may not be reading as frequently as the microcontroller is sending, and the personal computer’s serial buffer contains the unread bytes. You stop the desktop program, and there’s still a byte in the buffer, like this:

\n

The next time you start your program, there’s a newline in the buffer even if the Arduino’s not running, so a serial data event is generated and your serialEvent() function is called. But there’s no string preceding the newline character, so when you try to split the string into an array, you get an error .  The serial communication between the devices was working properly, but because it’s asynchronous, it’s your job to check that the data in the buffer is what you think it is. That’s your job as programmer. You might address this problem by checking that there’s a valid string to read first:

in p5.js with p5.webserial:

1
2
3
4
5
6
let inputString = myPort.readStringUntil("\r\n");
 if (inputString != null) {
   // when you know you've got a good string, take action:
   // split the string into an array:
   let sensorReadings = split(inputString, ",");
 }

in Processing:

String inputString = myPort.readString(); if (inputString != null) { // when you know you’ve got a good string, take action on it: // split the string into an array: String[] sensorReadings = split(inputString, “,”); }

Similar problems can happen in all serial environments.

Clear Any Old Data Before Reading

Although this solution works if you’re reading the serial buffer as a string, it doesn’t solve every problem. Another way to avoid this particular problem is to make sure that you’ve cleared out the serial buffer at the beginning of your program before you start reading new data. Once you’ve configured your serial object and opened the port, clear the buffer by calling serial.clear().

Make Sure All The Data Has Been Received

You can still get errors even if you’ve cleared the buffer and made sure there’s data to read if the data there to read doesn’t match your expectations. In the example above, your data sentence includes three ASCII-encoded numbers and a newline.  But what if the microcontroller doesn’t send that?  Maybe there’s an error in its program, or maybe there’s still data in the serial buffer (because you didn’t clear the buffer).  You should check to make sure that everything you expect is present before you operate on it. For example, imagine you’re splitting the input data into an array of three strings, then copying those strings into global variables like so:

in Processing:

// global variables: int xPosition, yPosition, zPositon; // then your serialEvent function looks like this: void serialEvent(Serial myPort){ String inputString = myPort.readString(); // split the string into an array: String[] sensorReadings = split(inputString, “,”); // copy the first element into xPosition: xPosition = int(sensorReadings[0]); // copy the second element into yPosition: yPosition = int(sensorReadings[1]); // copy the third element into zPosition: zPosition = int(sensorReadings[2]); }

In p5.js with p5.webserial:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// global variables:
let xPosition, yPosition, zPositon;
 
// then your serialEvent function looks like this:
function serialEvent(){
  let inputString = serial.readStringUntil("\r\n");
  // split the string into an array:
  let sensorReadings = split(inputString, ",");
  // copy the first element into xPosition:
  xPosition = Number(sensorReadings[0]);
  // copy the second element  into yPosition: 
  yPosition = Number(sensorReadings[1]);
  // copy the third element into zPosition:
  zPosition = Number(sensorReadings[2]); 
}

This works great until you don’t have three elements in the array. If there wasn’t a full sentence of data, the array might have only one or two elements, and your error is back. To solve this, make sure the length of the array is as long as the number of elements you’re trying to read from it like so:

in Processing:

// global variables: int xPosition, yPosition, zPositon; // then your serialEvent function looks like this: void serialEvent(Serial myPort){ String inputString = myPort.readString(); // split the string into an array: String[] sensorReadings = split(inputString, “,”); if (sensorReadings.length > 2) { // copy the first element into xPosition: xPosition = int(sensorReadings[0]); // copy the second element into yPosition: yPosition = int(sensorReadings[1]); // copy the third element into zPosition: zPosition = int(sensorReadings[2]); } }

In p5.js with p5.webserial:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// global variables:
let xPosition, yPosition, zPositon;
 
// then your serialEvent function looks like this:
function serialEvent(){
  let inputString = serial.readStringUntil("\r\n");
  // split the string into an array:
  let sensorReadings = split(inputString, ",");
  if (sensorReadings.length > 2) { 
    // copy the first element into xPosition:
    xPosition = Number(sensorReadings[0]);
    // copy the second element  into yPosition: 
    yPosition = Number(sensorReadings[1]);
    // copy the third element into zPosition:
    zPosition = Number(sensorReadings[2]);
  
}

If all of the data isn’t there for any reason, this if statement will prevent an error by skipping the part where you look for data that’s not there.  When you combine it with the check for a null string above, and the clearing of the serial buffer, you make your serial reading much more stable.  The final result might look like this:

in Processing:

// global variables: int xPosition, yPosition, zPositon; // then your serialEvent function looks like this: void serialEvent(Serial myPort){ String inputString = myPort.readString(); if (!inputString) return; // split the string into an array: String[] sensorReadings = split(inputString, “,”); if (sensorReadings.length > 2) { // copy the first element into xPosition: xPosition = int(sensorReadings[0]); // copy the second element into yPosition: yPosition = int(sensorReadings[1]); // copy the third element into zPosition: zPosition = int(sensorReadings[2]); } }

In p5.js with p5.webserial:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// global variables:
let xPosition, yPosition, zPositon;
 
// then your serialEvent function looks like this:
function serialEvent(){
  let inputString = serial.readStringUntil("\r\n");
  if (!inputString) return;
  // split the string into an array:
  let sensorReadings = split(inputString, ",");
  if (sensorReadings.length > 2) { 
    // copy the first element into xPosition:
    xPosition = Number(sensorReadings[0]);
    // copy the second element  into yPosition: 
    yPosition = Number(sensorReadings[1]);
    // copy the third element into zPosition:
    zPosition = Number(sensorReadings[2]);
  
}

When combined with a handshaking methodology as seen in the Serial Duplex Lab using P5.js and p5.webserial, you also ensure that the serial buffer is only filled when you’re ready for new data. All of these practices are good serial communication practices and will make your projects more stable.

Parsing Text in Arduino

There are some tools in the Serial library of Arduino that make it simpler to parse text strings. For example if you know you are getting a string of numbers separated by commas, you can use Serial.parseInt() like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void setup() {
  // initialize serial
  Serial.begin(9600);
}
 
void loop() {
  if (Serial.available()) {
    int x = Serial.parseInt();
    int y = Serial.parseInt();
    int z = Serial.parseInt();
    Serial.print("x = ");
    Serial.print(x);
    Serial.print(", y = ");
    Serial.print(y);
    Serial.print(", z = ");
    Serial.println(z);
  }
}

Try sending comma-separated strings of three numbers to this from the Serial Monitor. You should get an output like this:

x = 34, y = 45, z = 56

Sometimes you will get 0 values if a non-numeric character arrives (like a newline or carriage return). You can catch these by checking for the right number of bytes. For example, 34, 56, 78 followed by carriage return and newline is 10 bytes. So if you know you can expect at least 10 bytes, you could use if (Serial.available() > 10). There are a number of other useful finding and parsing functions in the Serial library, including:

  • Serial.find()
  • Serial.findUntil()
  • Serial.parseInt()
  • Serial.parseFloat()
  • Serial.readBytes()
  • Serial.readBytesUntil()
  • Serial.readString()
  • Serial.readStringUntil()

For more on these, see the full Arduino Serial reference.

Data Protocols

What you’ve seen in these notes has been a common data protocol called Comma Separated Values (CSV), in which the values of different data items, whether numeric or otherwise, are encoded as text and separated by commas. Here are a few examples:

23,35,423,24,554,23,51,443,63,74,0,43
cat, dog, chicken, eel, pig, donkey, monkey, ocelot
23.5, 96.8, 2.343, 0.65, -17.34

CSV is probably the simplest of data formats, because Unicode is ubiquitous, and almost all programming APIs have a function for splitting strings on a delimiter like a comma. However, there might be more complex data protocols.

URL Encoding

You may have seen something like this at the end of a web URL:

?lat=40.8028523&long=-73.9562655&name=coco%20nail%20salon

This is called URL encoding, and it’s a common way of formatting data so that it can be included with the hypertext transport protocol, HTTP. You’ll notice that it always starts with a ?. Web locations can’t include a ?, so it delimits the beginning of a query string. Items are separated by & symbols, and each item’s key (or name) comes first, followed by an = sign, then the item’s value. This is more complex than CSV, but it’s possible to come up with an algorithm to read it, and functions to do so are common in web-native programming APIs.

JavaScript Object Notation

A common format used in JavaScript is JavaScript Object Notation, or JSON. A JSON string looks like this:

1
2
3
4
{ "name": "Sandy",
  "age": 99,
  "employed": true
}

JSON data is always enclosed in braces {}, and each key-value data pair is separated by a colon (:). pairs are separated by commas. Data items can be of any data type, because JavaScript itself is an untyped language.

Since p5.js is a JavaScript API, let’s look at how it’d be handy if we sent data serially to it. Here’s a sample Arduino program to send JSON to p5.js:

1
2
3
4
5
6
7
8
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  Serial.println("{\"x\": 34, \"y\": 45, \"z\": 200}");
  delay(100);
}

Now here’s a serialEvent() function you can use in p5.js with p5.webserial to receive the data. Take this sketch and replace the serialEvent function it with the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function serialEvent() {
  // read a string from the serial port
  // until you get carriage return and newline:
  var inString = serial.readStringUntil("\r\n");
    //check to see that there's actually a string there:
    if (inString) {
      // parse the string as JSON:
      var sensors = JSON.parse(inString);
      locH = sensors.x;
      locV = sensors.y;
      circleColor = sensors.z;
      console.log(sensors);
  }
}

What’s happening here is that p5.js is expecting a JSON-formatted string, and parsing it (JSON.parse()), and then you get to use the values in it just like a regular JSON object. It’s simpler than parsing all the pieces out with split(). Of course, it’s a pain to have to format a complex JSON-formatted string in Arduino, but there is a library that’ll help, called Arduino_JSON. You can install it from the Library manager (search for the name with the underscore, Arduino_JSON), and install it. There are several examples that come with it, but here’s a simple one for putting sensor values into a JSON string:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <Arduino_JSON.h>
// make a new JSONVar object called device:
JSONVar device;
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  // make a data item called x in the JSONVar object:
  device["x"] = analogRead(A0);
  delay(1);
  // make a data item called y in the JSONVar object:
  device["y"] = analogRead(A1);
  delay(1);
  // make a data item called z in the JSONVar object:
  device["z"] = analogRead(A2);
  // print out the JSONVar object:
  Serial.println(device);
}

Add three analog sensors to pins A0 through A2 (potentiometers will do the job). Then try this with the same sketch. p5.js will read the data from the sensors and use them to set the position and color of the ball.

To see the sketch running on GitHub at this link. You can see the source files for copying into the p5.js editor at this link.

Conclusion

There are lots of different data formats out there, for different applications. Learning how they are structured and how to format them will simplify communication from device to device. Most protocols are either binary, as you saw at the beginning of these notes, or ASCII-encoded, like the ones in the latter half of these notes. Of the ASCII-encoded protocols, CSV is the simplest and most common, but all of them can be learned and interpreted with a little work. When you are using a well-known protocol, it’s always worth checking to see if there’s a library to format or interpret it in whatever programming environment you are using.

Labs: Motors and Transistors

The following labs are about controlling DC motors and other high-current loads with transistor and H-Bridges.

Lab: Two-way (Duplex) Serial Communication using an Arduino and Processing

In this tutorial you’ll learn how to send data using asynchronous serial between an Arduino and Processing in both directions.

Introduction

In the  serial output from Arduino to Processing lab, you sent data from one sensor to a personal computer. In this lab, you’ll send data from multiple sensors to a program on a personal computer. You’ll use the data from the sensors to create a pointing-and-selecting device (i.e. a mouse).

These videos will help in understanding this lab:

What You’ll Need to Know

To get the most out of this Lab, you should be familiar with how to program an Arduino, and with the basics of serial communication. If you’re not, review the links below:

Things You’ll Need

From Figure 1-5 are basically what you need for this lab.

Photo of an Arduino Nano 33 IoT module. The USB connector is at the top of the image, and the physical pins are numbered in a U-shape from top left to bottom left, then from bottom right to top right.
Figure 1: Arduino Nano 33 IoT
A short solderless breadboard with two rows of holes along each side. There are no components mounted on the board. The board is oriented sideways so that the long rows of holes are on the top and bottom of the image.
Figure 2: A short solderless breadboard.
Three short pieces of hookup wire: one is clad in red insulation, one in blue, and one in black. All three have exposed ends approximately 5mm long.
Figure 3: 22AWG hookup wire
An ADXL335 accelerometer module. There are six pins along the bottom, labeled GND, Z, Y, X, 3V, and ST (left to right)
Figure 4: An ADXL335 accelerometer module.
A pushbutton with two wires soldered one to each of the button's contacts.
Figure 5: A pushbutton.

Connect the sensors

Connect two analog sensors to analog pins 0 and 1 like you did in the analog lab. Connect a switch to digital pin 2 like you did in the digital lab.

The photos and schematic in this lab show an accelerometer (Figure 6) and a pushbutton (Figure 7). You don’t have to use these, though. Use whatever sensors are appropriate to your final application. While you’re figuring what sensors to use, use the most convenient sensors you’ve got in hand; perhaps two potentiometers for the analog sensors and a pushbutton?

Schematic view of an Arduino attached to an AXDL335 accelerometer and a pushbutton. The accelerometer's X and Y pins are connected to the Arduino's A0 and A1 inputs, respectively. The pushbutton is connected from the Arduino's voltage output to pin D2. a 10-kilohm connects the junction of the switch and pin D2 to ground.
Figure 6: Schematic view of an Arduino attached to an AXDL335 accelerometer and a pushbutton. The accelerometer’s X and Y pins are connected to the Arduino’s A0 and A1 inputs, respectively. The pushbutton is connected from the Arduino’s voltage output to pin D2. a 10-kilohm connects the junction of the switch and pin D2 to ground.
Breadboard view of an Arduino attached to an AXDL335 accelerometer and a pushbutton. The accelerometer's X and Y pins are connected to the Arduino's A0 and A1 inputs, respectively. The pushbutton is connected from the Arduino's voltage output to pin D2. a 10-kilohm connects the junction of the switch and pin D2 to ground.
Figure 7: Breadboard view of an Arduino attached to an AXDL335 accelerometer and a pushbutton. The accelerometer’s X and Y pins are connected to the Arduino’s A0 and A1 inputs, respectively. The pushbutton is connected from the Arduino’s voltage output to pin D2. a 10-kilohm connects the junction of the switch and pin D2 to ground.

Breadboard view of an Arduino Nano connected to a pushbutton and an ADXL3xx accelerometer.
Figure 8. Breadboard view of an Arduino Nano connected to a pushbutton and an ADXL3xx accelerometer.

Figure 8: Breadboard view of an Arduino Nano connected to a pushbutton and an ADXL3xx accelerometer. The Nano is connected as usual, straddling the first fifteen rows of the breadboard with the USB connector facing up. Voltage (physical pin 2) is connected to the breadboard’s voltage bus, and ground (physical pin 14) is connected to the breadboard’s ground bus. The pushbutton straddles the middle of the board below the Nano. One pins of the pushbutton is connected to the voltage bus of the board. The other is connected to ground through a 10-kilohm resistor. The junction between the pushbutton and the resistor is connected to digital pin 2 of the Nano (physical pin 20). The accelerometer is connected to six rows in the left center section of the board below the pushbutton. Its Vcc pin is connected to the voltage bus, and its ground pin is connected to the ground bus. The X axis pin is connected to the Nano’s analog in 0 (physical pin 4) and the Y axis pin is connected to the Nano’s analog in 1 (physical pin 5).


(Diagram made with Fritzing)

Note: Not all accelerometers are the same. However, Analog Electronics makes a very popular series of accelerometers, the ADXL3xx series, that have three analog outputs for X, Y, and Z acceleration. This schematic should work interchangeably for most of them.

Read and send the serial data

To begin with, just send the value from one sensor, the first analog sensor (the first axis of the accelerometer in the photos) and divide the output to give a maximum value of 255:

1
2
3
4
5
6
7
8
9
10
void setup() {
  // start serial port at 9600 bps:
  Serial.begin(9600);
}
 
void loop() {
  // read analog input, divide by 4 to make the range 0-255:
  int analogValue = analogRead(A0)/4;
  Serial.println(analogValue);
}

When you open the serial monitor, you should see a number between 0 and 255 scrolling down the window. That’s because Serial.println() formats the value it prints as an ASCII-encoded decimal number, with a linefeed at a carriage return at the end. Also, the serial monitor assumes it should show you the ASCII character corresponding to each byte it receives.

Try changing the Serial.println() to a Serial.write(). Now you get a range of garbage characters. What’s going on?

The write() command doesn’t format the bytes as ASCII. It sends out the raw binary value of the byte. The Serial Monitor receives that binary value and assumes it should show you the ASCII character corresponding to that value again. The garbage characters are characters corresponding to the ASCII values the Monitor is receiving.

But you already knew that from the serial output from Arduino To Processing lab, didn’t you?

For example, imagine that analogValue = 32:

  • Serial.println(analogValue) results in “32” with a linefeed and carriage return
  • Serial.write(analogValue) results in ” “, the space character, which has the ASCII value 32.

How many bytes does Serial.println(analogValue) send when analogValue = 32?

Serial.println(analogValue) actually sends FOUR bytes! It sent a byte to represent the 3, a byte to represent the 2, a byte to tell the Monitor to move the cursor down a line (newline), and a byte to move the cursor all the way to the left (carriage return). The raw binary values of those four bytes are 51 (ASCII for “3”), 50 (ASCII for “2”), 10 (ASCII for “newline”), and 13 (ASCII for “carriage return”). Check the ASCII table and you’ll see for yourself.

Send the data in many formats

Try this program and view the results in the Serial Monitor (video link):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void setup() {
  // start serial port at 9600 bps:
  Serial.begin(9600);
}
 
void loop() {
  // read analog input, divide by 4 to make the range 0-255:
  int analogValue = analogRead(A0) / 4;
  Serial.println(analogValue);
 
  // print different formats:
  Serial.write(analogValue);  // Print the raw binary value
  Serial.print('\t');             // print a tab
  // print ASCII-encoded values:
  Serial.print(analogValue, BIN); // print ASCII-encoded binary value
  Serial.print('\t');             // print a tab
  Serial.print(analogValue);      // print decimal value
  Serial.print('\t');             // print a tab
  Serial.print(analogValue, HEX); // print hexadecimal value
  Serial.print('\t');             // print a tab
  Serial.print(analogValue, OCT); // print octal value
  Serial.println();               // print linefeed and carriage return
}

You should get output like this:

â 11100010 226 E2 342
á 11100001 225 E1 341
á 11100001 225 E1 341
á 11100001 225 E1 341
à 11100000 224 E0 340
à 11100000 224 E0 340
ß 11011111 223 DF 337
ß 11011111 223 DF 337
ß 11011111 223 DF 337

This sketch is printing the raw binary value, then the ASCII-encoded binary value, then the ASCII-encoded decimal, hexadecimal, and octal values. You may never need all of these different formats, but you’ll likely need at least the decimal and the raw binary versions at some point.

Send the values for all three sensors

If you prefer to read serial input in a command line interface, that will work here as well.

In the first serial lab, you sent one byte representing one sensor’s value, over and over. When you’re sending multiple sensor values, it gets a little more complicated. You need to a way to know which value represents which sensor. For example, imagine if you used the following loop to send your sensor values:

1
2
3
4
5
6
7
void loop() {
  for (int thisSensor = 0; thisSensor < 3; thisSensor++) {
    int sensorValue = analogRead(thisSensor);
    Serial.print(sensorValue);
    Serial.print(",");
  }
}

You’ll get a string like this:

452,345,416,234,534,417,325,452,231

How can you tell which value corresponds to which sensor? You don’t know which sensor is which. You could assume that if you start listening when the microcontroller starts sending that the first reading corresponds to the first sensor, but you can’t know that for sure. There are two ways to get your sensor values in order. You can use punctuation or you can use a call-and-response or handshaking method. Use whichever makes the most sense to you. They’re explained below.

Punctuation Method

One way to send the data such that it can be interpreted clearly is to punctuate each set of data uniquely. Just as a sentence ends with a period, you can end your data with a carriage return and a newline. Change the for loop above so that a carriage return and newline are printed at the end of each string of three values.

1
2
3
4
5
6
7
8
9
10
11
12
13
void loop() {
  for (int thisSensor = 0; thisSensor < 3; thisSensor++) {
    int sensorValue = analogRead(thisSensor);
    Serial.print(sensorValue);
    // if you're on the last sensor value, end with a println()
    // otherwise, print a comma
    if (thisSensor == 2) {
      Serial.println();
    } else {
      Serial.print(",");
    }
  }
}

From this loop, you’d get output like this:

452,345,416
234,534,417
325,452,231

This is much better. Whenever you get a newline, you know that the next value is the first sensor.

Now write a program that reads the two analog sensors on your board and the one digital switch, and prints them out in this format:

analog1, analog2, switch
analog1, analog2, switch
analog1, analog2, switch

Start by setting up a constant for the switch pin’s number. Then in the setup, initialize serial communications at 9600bps, and declare the switch pin as an input.

1
2
3
4
5
6
7
8
const int switchPin = 2;      // digital input
 
void setup() {
  // configure the serial connection:
  Serial.begin(9600);
  // configure the digital input:
  pinMode(switchPin, INPUT);
}

In the main loop, use a local variable called sensorValue to read each input. Read the two analog inputs first, and print them with a comma after each one. Then read the digital input, and print it with a carriage return and linefeed at the end.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void loop() {
  // read the sensor:
  int sensorValue = analogRead(A0);
  // print the results:
  Serial.print(sensorValue);
  Serial.print(",");
 
  // read the sensor:
  sensorValue = analogRead(A1);
  // print the results:
  Serial.print(sensorValue);
  Serial.print(",");
 
  // read the sensor:
  sensorValue = digitalRead(switchPin);
  // print the results:
  Serial.println(sensorValue);
}

Once you’ve got a data format, all you have to do is read it in the receiving program.

Receive the data in Processing

Now write a Processing sketch that reads the data as formatted by the Arduino program above.

First, set up the beginning of your program as you did in the first serial lab, to import the serial library and make a global variable to hold an instance of it. Then in the setup(), print out a list of serial ports, and put the one you want in a string. Then instantiate the serial library in the global variable you made.

Warning: you might get an error message when trying to use the Processing Serial Library for the first time. If this happens: inn Processing 2.0 and beyond, there is an item in the Tools menu, “Fix the Serial Library” that fixes the serial library. Test it by deleting /var/lock/ and running the Processing serial sketch again, it should now work!

Processing Code:

import processing.serial.*; // import the Processing serial library Serial myPort; // The serial port void setup() { // List all the available serial ports println(Serial.list()); // I know that the first port in the serial list on my computer // is always my Arduino module, so I open Serial.list()[0]. // Change the 0 to the appropriate number of the serial port // that your microcontroller is attached to. String portName = Serial.list()[0]; myPort = new Serial(this, portName, 9600); }

Add one extra line at the end of the setup() method. This line tells the sketch not to call serialEvent() unless an ASCII newline byte (value 10) comes in the serial port. It will save any other bytes it gets in the serial buffer, so you can read them all at once when the newline arrives:

Processing Code:

// read incoming bytes to a buffer // until you get a linefeed (ASCII 10): myPort.bufferUntil(‘\n’);

The draw() method doesn’t do anything, just like the first serial lab:

Processing Code:

void draw() { // twiddle your thumbs }

Now your serialEvent() method only occurs when you get a newline, so you can assume that the entire string that came before the newline is in the serial buffer. Read the string, and check to see if it’s null. If it isn’t, print it out. This way, you’ll be sure you’ve got a full string before you do anything with it.

Processing Code:

void serialEvent(Serial myPort) { // read the serial buffer: String myString = myPort.readStringUntil(‘\n’); if (myString != null) { println(myString); } }

The Serial.println() in Arduino attaches a newline and a carriage return. Newlines, carriage returns, spaces, and tabs are often called whitespace and there’s a command that removed whitespace from a string, called trim(). Replace the println() with the following two lines. The first trims the whitespace characters off. The second splits the string into three separate strings at the commas, the converts those strings to integers (related video: convert Strings to Floats):

myString = trim(myString); // split the string at the commas // and convert the sections into integers: int sensors[] = int(split(myString, ‘,’));

Next, print out those three integers using a for() loop, like so:

for (int sensorNum = 0; sensorNum < sensors.length; sensorNum++) { print(“Sensor ” + sensorNum + “: ” + sensors[sensorNum] + “\t”); } // add a linefeed at the end: println();

Run the sketch now and you should get output like this:

Sensor 0: 510 Sensor 1: 499 Sensor 2: 0
Sensor 0: 510 Sensor 1: 498 Sensor 2: 0
Sensor 0: 510 Sensor 1: 498 Sensor 2: 0
Sensor 0: 510 Sensor 1: 497 Sensor 2: 0
Sensor 0: 509 Sensor 1: 498 Sensor 2: 0
Sensor 0: 510 Sensor 1: 497 Sensor 2: 0

Now you’ve got all the sensor values as integers, and you can do whatever you want with them. Use them to move a circle on the screen. To do that, you’ll need a few global variables at the beginning of the sketch:

float fgcolor = 0; // Fill color defaults to black float xpos, ypos; // Starting position of the ball

Add a line at the beginning of the setup() to set the window size:

size(800, 600);

Put the following lines in the draw method. These will draw the circle using the variables you just declared:

background(#243780); // blue background fill(fgcolor); // Draw the shape ellipse(xpos, ypos, 20, 20);

In order to see anything happen, you need to assign the sensor values to these variables. Do this at the end of the serialEvent():

// make sure you’ve got all three values: if (sensors.length > 1) { xpos = sensors[0]; ypos = sensors[1]; // the pushbutton will send 0 or 1. // This converts them to 0 or 255: fgcolor = sensors[2] * 255; }

If you run this, you should see the ball moving onscreen whenever you press the switch and move the analog sensors. If your analog values are greater than 800 or 600, the ball will be offscreen, so you may have to map your sensor range to the screen size. For example, the accelerometer sensor ranges are approximately 430 to 580. Since the screen is 0 to 800 (horizontal) and 0 to 600 (vertical), map the ranges of the sensors to fill the screen.

xpos = map(sensors[0], 430,580,0,width); ypos = map(sensors[1], 430,580,0,height);

Call and Response (Handshaking) Method

Another way to keep multiple bytes of serial data in order is to send one set of values at a time, rather than sending repeatedly. If you use this method, the receiving program has to request new data every time it finishes reading what it’s got.

You can convert the punctuation method shown above to a call-and-response method fairly simply. First, modify the Arduino code. Add a new method at the end of the sketch called establishContact(). This method sends out a message on startup until it gets a byte of data from Processing. Later you’ll modify the Processing sketch to look for this message and send a response:

1
2
3
4
5
6
void establishContact() {
  while (Serial.available() >= 0) {
    Serial.println("hello");   // send a starting message
    delay(300);
  }
}

To call this, add a line at the end of the setup() method:

1
establishContact();

Now, modify the loop() by adding an if() statement to look for incoming serial data and read it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void loop() {
  if (Serial.available() > 0) {
    // read the incoming byte:
    int inByte = Serial.read();
    // read the sensor:
    sensorValue = analogRead(A0);
    // print the results:
    Serial.print(sensorValue);
    Serial.print(",");
 
    // read the sensor:
    sensorValue = analogRead(A1);
    // print the results:
    Serial.print(sensorValue);
    Serial.print(",");
 
    // read the sensor:
    sensorValue = digitalRead(switchPin);
    // print the results:
    Serial.println(sensorValue);
  }
}

The rest of the Arduino sketch remains the same. When you run this and open the serial monitor, you’ll see:

hello
hello
hello
hello

Type any character in the output box and click Send. You’ll get a string of sensor values at the end of your hellos:

510,497,0

Type another character and click Send. It doesn’t matter what character you send, but the loop will always wait for an incoming byte before sending a new set of sensor values.

Next modify the Processing program. Add a new global variable called firstContact before the setup():

boolean firstContact = false; // Whether you’ve heard from the microcontroller

Then modify the serialEvent(). Until firstContact is true, the serialEvent() will look for the “hello” message from the microcontroller. When it gets the initial message, it will send out a byte to request data. From then on, it will read in the string, parse it, and send a byte to request more data when it’s done.

void serialEvent(Serial myPort) { // read the serial buffer: String myString = myPort.readStringUntil(‘\n’); // if you got any bytes other than the linefeed: if (myString != null) { myString = trim(myString); // if you haven’t heard from the microncontroller yet, listen: if (firstContact == false) { if (myString.equals(“hello”)) { myPort.clear(); // clear the serial port buffer firstContact = true; // you’ve had first contact from the microcontroller myPort.write(‘A’); // ask for more } } // if you have heard from the microcontroller, proceed: else { // split the string at the commas // and convert the sections into integers: int sensors[] = int(split(myString, ‘,’)); // print out the values you got: for (int sensorNum = 0; sensorNum > sensors.length; sensorNum++) { print(“Sensor ” + sensorNum + “: ” + sensors[sensorNum] + “\t”); } // add a linefeed after all the sensor values are printed: println(); if (sensors.length > 1) { xpos = map(sensors[0], 430, 580, 0, width); ypos = map(sensors[1], 430, 580, 0, height); fgcolor = sensors[2] * 255; } } // when you’ve parsed the data you have, ask for more: myPort.write(“A”); } }

If you did everything right, the ball should move in response to the analog sensors, and appear or disappear when you press the button.

Advantages of Raw Binary vs. ASCII

All the examples shown here sent the sensor values as ASCII-encoded strings. As mentioned above, that means you sent three bytes to send a three-digit value. If that same value was less than 255, you could send it in one raw binary byte. So ASCII is definitely less efficient. However, it’s more readable for debugging purposes, and if the receiving program is well-suited to convert strings to numbers, then ASCII is a good way to go. If the receiver’s not so good at converting strings to numbers (for example, it’s more challenging to read a multiple byte string in Arduino than in Processing) then you may want to send your data as binary values.

Advantages of Punctuation or Call-and-Response

The punctuation method for sending multiple serial values may seem simpler, but it has its limitations. You can’t easily use it to send binary values, because you need to have a byte with a unique value for the punctuation. In the example above, you’re using the value 10 (ASCII newline) as punctuation, so if you were sending your sensor values as raw bytes, you’d be in trouble when the sensor’s value is 10. The receiver would interpret the 10 as punctuation, not as a sensor value. In contrast, call-and-response can be used whether you’re sending data as raw binary values or as ASCII-encoded values.

Sometimes the receiver reads serial data slower than the sender sends it. For example, if you have a program that does a lot of graphic work, it may only read serial data every few milliseconds. The serial buffer will get full in that case, you’ll notice a lag in response time. This is when it’s good to switch to a call-and-response method.

Get creative

You just duplicated the basic functionality of a mouse; that is, a device with two analog sensors that affect X and Y, and a digital sensor (mouse button). What applications can you think of that could use a better physical interface for a mouse? A video editor that scrubs forward and back when you tilt a wand? An action game that reacts to how hard you hit a punching bag? An instructional presentation that speeds up if you shift in your chair too much? A music program driven by a custom musical instrument that you design

Create a prototype in Arduino and Processing, or whatever programming environment you choose. Come up with a physical interface that makes it clear what actions map to what movements and actions. Figure out which actions can and should be possible at the same time.

Lab: Servo Motor Control with an Arduino

In this tutorial, you’ll learn how to control a servomotor’s position from a microcontroller using the value returned from an analog sensor.

In this tutorial, you’ll learn how to control a servomotor’s position from a microcontroller using the value returned from an analog sensor.

Introduction

Servos are the easiest way to start making motion with a microcontroller. Servos can turn through a range of 180 degrees and you can use them to create all sorts of periodic or reciprocating motions. Check out some of the mechanisms at Rob Ive’s site for ideas on how to make levers, cams, and other simple machines for making motion. The resources section of this site has links to other sites on construction, mechanics, and kinetics as well.

What You’ll Need to Know

To get the most out of this lab, you should be familiar with the following concepts. You can check how to do so in the links below:

Things You’ll Need

Prepare the breadboard

Connect power and ground on the breadboard to power and ground from the microcontroller. On the Arduino module, use the 5V or 3.3V (depending on your model) and any of the ground connections, as shown in Figures 9 and 10.

An Arduino Uno on the left connected to a solderless breadboard, right. The Uno's 5V output hole is connected to the red column of holes on the far left side of the breadboard. The Uno's ground hole is connected to the blue column on the left of the board. The red and blue columns on the left of the breadboard are connected to the red and blue columns on the right side of the breadboard with red and black wires, respectively. These columns on the side of a breadboard are commonly called the buses. The red line is the voltage bus, and the black or blue line is the ground bus.
Figure 9 Breadboard view of an Arduino Uno on the left connected to a solderless breadboard, right.

Figure 9. An Arduino Uno on the left connected to a solderless breadboard, right. The Uno’s 5V output hole is connected to the red column of holes on the far left side of the breadboard. The Uno’s ground hole is connected to the blue column on the left of the board. The red and blue columns on the left of the breadboard are connected to the red and blue columns on the right side of the breadboard with red and black wires, respectively. These columns on the side of a breadboard are commonly called the buses. The red line is the voltage bus, and the black or blue line is the ground bus.


Arduino Nano on a breadboard.
Figure 10. An Arduino Nano mounted on a solderless breadboard. The Nano is mounted at the top of the breadboard, straddling the center divide, with its USB connector facing up. The top pins of the Nano are in row 1 of the breadboard.

The Nano, like all Dual-Inline Package (DIP) modules, has its physical pins numbered in a U shape, from top left to bottom left, to bottom right to top right. The Nano’s 3.3V pin (physical pin 2) is connected to the left side red column of the breadboard. The Nano’s GND pin (physical pin 14) is connected to the left side black column. These columns on the side of a breadboard are commonly called the buses. The red line is the voltage bus, and the black or blue line is the ground bus. The blue columns (ground buses) are connected together at the bottom of the breadboard with a black wire. The red columns (voltage buses) are connected together at the bottom of the breadboard with a red wire.

Images made with Fritzing

Connect an Analog Input Sensor and a Servo

Connect an analog input to analog pin 0 as you did in the Analog Input Lab covered previously. A force-sensing resistor is shown in Figure 11-14 below, but you can also use a potentiometer, phototransistor, or any analog input you prefer. Then connect an RC servomotor to digital pin 9. The yellow wire of the servo goes to the pin, and the red and black wires go to +5V and ground, respectively.

Most RC servomotors are rated for 4-6 volt power input. When you’re using a 3.3V microcontroller like the Nano 33 IoT, you can use the Vin pin to power the motor if you’re running off USB power, or off a 5V source connected to the Vin.

Related video:Intro to Servo Motors

Safety Warning! Not all servos have the same wiring colors. For example, the Hextronik servos that come with Adafruit’s ARDX kit use red for +5V, brown for ground, and mustard yellow for control. Check the specifications on your particular servomotor to be sure.

Schematic view of an Arduino Uno connected to a voltage divider input circuit on analog in pin 0 and a servomotor on digital pin 9. On the left, a fixed 10-kilohm resistor is attached to analog in pin 0 and to ground on the Arduino. A variable resistor is attached to analog in pin 0 and to +5 volts. On the right, a servomotor's control wire is attached to digital pin D3. The motor's voltage input is attached to +5 volts, and its ground is attached to ground on the Arduino. A 10-microfarad capacitor is mounted across the +5V and ground buses close to where the motor voltage and ground wires are connected.
Figure 11. Schematic view of a servomotor and an analog input attached to an Arduino Uno.
Breadboard view of a servomotor and an analog input attached to an Arduino Uno. The +5 volts and ground pins of the Arduino are connected by red and black wires, respectively, to the left side rows of the breadboard. +5 volts is connected to the left outer side row (the voltage bus) and ground is connected to the left inner side row (the ground bus). The side rows on the left are connected to the side rows on the right using red and black wires, respectively, creating a voltage bus and a ground bus on both sides of the board. A force-sensing resistor, or FSR, is mounted in rows 18 and 19 of the left center section of the breadboard. a 10-kilohm resistor connects one leg of the FSR to the left side ground bus. A blue wire connects the row that connects these two to analog in 0 on the Arduino. A red wire connects the other pin to the left side voltage bus. A servomotor's voltage and ground connections are connected to the voltage and ground buses on the left side of the breadboard. the servomotor's control wire is connected to pin D9 of the Arduino. A 10-microfarad capacitor is mounted across the +5V and ground buses close to where the motor voltage and ground wires are connected.
Figure 12. Breadboard view of a servomotor and an analog input attached to an Arduino Uno.
Schematic view of an Arduino Nano 33 IoT connected to a voltage divider input circuit on analog in pin 0 and a servomotor on digital pin 9. On the left, a fixed 10-kilohm resistor is attached to analog in pin 0 and to ground on the Arduino. A variable resistor is attached to analog in pin 0 and to Vin pin (+5 volts). On the right, a servomotor's control wire is attached to digital pin D3. The motor's voltage input is attached to Vin, and its ground is attached to ground on the Arduino. A 10-microfarad capacitor is mounted across the 3.3V and ground buses.
Figure 13. Schematic view of a servomotor and an analog input attached to an Arduino Nano 33 IoT.
Breadboard view of an Arduino Nano 33 IoT connected to a voltage divider input circuit on analog in pin 0 and a servomotor on digital pin 9. A fixed 10-kilohm resistor is attached to analog in pin 0 and to ground on the Arduino. A variable resistor is attached to analog in pin 0 and to Vin pin (+5 volts). A servomotor's control wire is attached to digital pin D3. The motor's voltage input is attached to Vin, and its ground is attached to ground on the Arduino. A 10-microfarad capacitor is mounted across the 3.3V and ground buses.
Figure 14. Breadboard view of a servomotor and an analog input attached to an Arduino Nano 33 IoT.

When you attach the servo, you’ll need a row of three male headers to attach it to a breadboard. You may find that the pins don’t stay in the servo’s connector holes. Put the pins in the servo’s connector, then push them down on a table gently. They will slide up inside their plastic sheaths, and fit better in your servo’s connector.

Different RC servomotors will have different current requirements. The Tower SG5010 model servo sold by Adafruit draws more current than the HiTec HS311 and HS318 sold by ServoCity, for example. The Tower Pro servo draws 100-300 mA with no load attached, while the HiTec servos draw 160-180mA. The decoupling capacitor in the circuit will smooth out any voltage dips that occur when the servo turns on, but you will need an external 5V supply if you are using more than one servomotor.

Related video: Connect the Servo

Figures 15-17 show steps of this in action.

Photo of a servomotor connector with three header pins next to it. The header pins appear too short to connect properly to the servomotor connector.
Figure 15. Attaching header pins to a servomotor connector. If your header pins are too short, as shown here, you can lengthen them.
Photo of a hand holding a servomotor connector with header pins pushed partway into the holes. The pins are being braced against a tabletop.
Figure 16. Push the short ends of the header pins into the servomotor connector’s holes and then brace the long ends against a tabletop while you push down on the connector. Do this gently and the header pins will move in their plastic mount.
Photo of a servomotor connector with three header pins next to it. The header pins are now longer on top and shorter on bottom than they were in the first picture.
Figure 17. Now your header pins will be longer on top and shorter on bottom, and will stay firmly in the servomotor connector.

Program the Microcontroller

First, find out the range of your sensor by using analogRead() to read the sensor and printing out the results.

1
2
3
4
5
6
7
8
9
void setup() {
  Serial.begin(9600);       // initialize serial communications
}
 
void loop()
{
  int analogValue = analogRead(A0); // read the analog input
  Serial.println(analogValue);      // print it
}

Now, map the result of the analog reading to a range from 0 to 179, which is the range of the sensor in degrees. Store the mapped value in a local variable called servoAngle.

1
2
3
4
5
6
7
8
9
10
11
12
13
void setup() {
  Serial.begin(9600);       // initialize serial communications
}
 
void loop()
{
  int analogValue = analogRead(A0); // read the analog input
  Serial.println(analogValue);      // print it
 
  // if your sensor's range is less than 0 to 1023, you'll need to
  // modify the map() function to use the values you discovered:
  int servoAngle = map(analogValue, 0, 1023, 0, 179);
}

Finally, add the servo library at the beginning of your code, then make a variable to hold an instance of the library, and a variable for the servo’s output pin. In the setup(), initialize your servo using servo.attach(). Then in your main loop, use servoAngle to set the servo’s position.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "Servo.h"      // include the servo library
 
Servo servoMotor;       // creates an instance of the servo object to control a servo
int servoPin = 9;       // Control pin for servo motor
// time when the servo was last updated, in ms
long lastMoveTime = 0
 
void setup() {
  Serial.begin(9600);       // initialize serial communications
  servoMotor.attach(servoPin);  // attaches the servo on pin 9 to the servo object
}
 
void loop() {
  int analogValue = analogRead(A0); // read the analog input
  Serial.println(analogValue);      // print it
 
  // if your sensor's range is less than 0 to 1023, you'll need to
  // modify the map() function to use the values you discovered:
  int servoAngle = map(analogValue, 0, 1023, 0, 179);
 
  // move the servo using the angle from the sensor every 20 ms:
  if (millis() - lastMoveTime > 20) {
    servoMotor.write(servoAngle);
    lastMoveTime = millis();
  }
}

Related video: Code for the Servo & Turn the Servo

Get Creative

Servo motors give you the power to do all kinds of things.

They can be used to push a remote control button, in a pinch, as shown in Figure 18.

Photo of a remote control mounted in a wooden cradle. A servomotor mounted on the side of the cradle is positioned such that when it moves, its horn presses down on the power button of the remote control.
Figure 18. A servomotor can press remote control buttons The remote control is mounted in a wooden frame, and the servo is mounted on the side of the frame. The servo horn moves down to press the power button.

You can play music with found objects like in this Project by Nick Yulman. You can build a frisking machine like in this project by Sam Lavigne and Fletcher Bach. If you’ve got 800 or so of them and a lot of time, you can build a wooden mirror like this Project by Daniel Rozin.

Lab: Digital Input and Output with an Arduino

In this lab, you’ll connect a digital input circuit and a digital output circuit to a microcontroller. Though this is written for the Arduino microcontroller module, the principles apply to any microcontroller.

Introduction

In this lab, you’ll connect a digital input circuit and a digital output circuit to a microcontroller. Though this is written for the Arduino microcontroller module, the principles apply to any microcontroller.

Digital input and output are the most fundamental physical connections for any microcontroller. The pins to which you connect the circuits shown here are called General Purpose Input-Output, or GPIO, pins. Even if a given project doesn’t use digital in and out, you’ll often use LEDs and pushbuttons or switches during the development for testing whether everything’s working.

What You’ll Need to Know

To get the most out of this lab, you should be familiar with the following concepts and you should install the Arduino IDE on your computer. You can check how to do so in the links below:

Things You’ll Need

Figures 1-8 show the parts you’ll need for this exercise. Click on any image for a larger view.

Photo of an Arduino Nano 33 IoT module. The USB connector is at the top of the image, and the physical pins are numbered in a U-shape from top left to bottom left, then from bottom right to top right.
Figure 1. Arduino Nano 33 IoT
Photo of flexible jumper wires
Figure 2. Jumper wires.  You can also use pre-cut solid-core jumper wires.
Photo of a solderless breadboard
Figure 3. A solderless breadboard
Photo of an 8 ohm speaker
Figure 4. An 8 ohm speaker (optional).This is a good alternate to the LED if you prefer audible output.

Photo of a handful of LEDs
Figure 5. LEDs. The long leg goes to voltage and the short leg goes to ground
Photo of a handful of 220-ohm resistors.
Figure 6. 220-ohm resistors. These ones are 4-band resistors. They are colored red, red, brown and gold, which signifies 2, 2 (red, red), times 10 (brown), with a 5% tolerance (gold).
Photo of a handful of 10-kilohm resistors
Figure 7. 10-kilohm resistors. These ones are 5-band resistors. They are colored brown, black, black, red, brown, which signifies 1 (brown), 0, 0 (black, black), times 100 (red), with a 1% tolerance (brown). Four-band 10-kilohm resistors are colored brown, black, orange (1, 0, times 1000), gold (5% tolerance).
Photo of four breadboard-mounted pushbuttons
Figure 8. A pushbutton. Any switch will do the job as well.

The video Video: Digital Output covers the material in this lab.

Prepare the breadboard

If you’re using a brand new breadboard, you might want to check out these videos before you get started, to prep your board and care for your microcontroller.

Connect power and ground on the breadboard to power and ground from the microcontroller. On the Arduino Uno, use the 5V or 3.3V (depending on your model) and any of the ground connections. Figures 9 and 10 show how to do this for an Arduino Uno and for an Arduino Nano 33 IoT.

An Arduino Uno on the left connected to a solderless breadboard, right. The Uno's 5V output hole is connected to the red column of holes on the far left side of the breadboard. The Uno's ground hole is connected to the blue column on the left of the board. The red and blue columns on the left of the breadboard are connected to the red and blue columns on the right side of the breadboard with red and black wires, respectively. These columns on the side of a breadboard are commonly called the buses. The red line is the voltage bus, and the black or blue line is the ground bus.
Figure 9. Breadboard view of an Arduino Uno on the left connected to a solderless breadboard, right.

As shown in Figure 9, the Uno’s 5V output hole is connected to the red column of holes on the far left side of the breadboard. The Uno’s ground hole is connected to the blue column on the left of the board. The red and blue columns on the left of the breadboard are connected to the red and blue columns on the right side of the breadboard with red and black wires, respectively. These columns on the side of a breadboard are commonly called the buses. The red line is the voltage bus, and the black or blue line is the ground bus.


Arduino Nano on a breadboard.
Figure 10 Breadboard view of an Arduino Nano mounted on a solderless breadboard.

As shown in Figure 10, the Nano is mounted at the top of the breadboard, straddling the center divide, with its USB connector facing up. The top pins of the Nano are in row 1 of the breadboard.

The Nano, like all Dual-Inline Package (DIP) modules, has its physical pins numbered in a U shape, from top left to bottom left, to bottom right to top right. The Nano’s 3.3V pin (physical pin 2) is connected to the left side red column of the breadboard. The Nano’s GND pin (physical pin 14) is connected to the left side black column. These columns on the side of a breadboard are commonly called the buses. The red line is the voltage bus, and the black or blue line is the ground bus. The blue columns (ground buses) are connected together at the bottom of the breadboard with a black wire. The red columns (voltage buses) are connected together at the bottom of the breadboard with a red wire.


Images made with Fritzing

Related video: Overview of the Arduino microcontroller

Related video: Connect Power and Ground using wires

Add a Digital Input (a Pushbutton)

Connect a pushbutton to digital input 2 on the Arduino. Figures 11 and 12 show the schematic and breadboard views of this for an Arduino Uno, and Figure 13 shows the breadboard view for an Arduino 33 IoT. The pushbutton shown below is a store-bought momentary pushbutton, but you can use any pushbutton. Try making your own with a couple of pieces of metal as shown in the Switches lab.

Related video: Connect a pushbutton to a digital pin

What Are The Input and Output Pins?

If you’re not sure what pins are the inputs and outputs of your board, check the Microcontroller Pin Functions page for more information. The reference page on the  standard breadboard layouts for the Uno, Nano series, and MKR series might be useful as well.

Schematic view of an Arduino connected to a pushbutton. One side of the pushbutton is connected to digital pin 2 of the Arduino. A 10-kilohm resistor is connected from digital pin 2 to ground as well. The other side of the pushbutton is attached to +5V.
Figure 11. Schematic view of an Arduino connected to a pushbutton.
Breadboard view of an Arduino connected to a pushbutton. The +5 volts and ground pins of the Arduino are connected by red and black wires, respectively, to the left side rows of the breadboard. +5 volts is connected to the left outer side row (the voltage bus) and ground is connected to the left inner side row (the ground bus). The side rows on the left are connected to the side rows on the right using red and black wires, respectively, creating a voltage bus and a ground bus on both sides of the board. The pushbutton is mounted across the middle divide of the solderless breadboard. A 10-kilohm resistor connects from the same row as pushbutton's bottom left pin to the ground bus on the breadboard. There is a wire connecting to digital pin 2 from the same row that connects the resistor and the pushbutton. The top left pin of the pushbutton is connected to +5V.
Figure 12. Breadboard view of an Arduino connected to a pushbutton.

Breadboard view of an Arduino Nano connected to a pushbutton. The +5 volts and ground pins of the Arduino are connected by red and black wires, respectively, to the left side rows of the breadboard. +5 volts is connected to the left outer side row (the voltage bus) and ground is connected to the left inner side row (the ground bus). The side rows on the left are connected to the side rows on the right using red and black wires, respectively, creating a voltage bus and a ground bus on both sides of the board. The pushbutton is mounted across the middle divide of the solderless breadboard. A 10-kilohm resistor connects from the same row as pushbutton's bottom left pin to the ground bus on the breadboard. There is a wire connecting to digital pin 2 from the same row that connects the resistor and the pushbutton. The top left pin of the pushbutton is connected to +3.3V.
Figure 13. Breadboard view of an Arduino Nano connected to a pushbutton.

Figure 13 shows the breadboard view of an Arduino Nano connected to a pushbutton. The +3.3 volts and ground pins of the Arduino are connected by red and black wires, respectively, to the left side rows of the breadboard. +3.3 volts is connected to the left outer side row (the voltage bus) and ground is connected to the left inner side row (the ground bus). The side rows on the left are connected to the side rows on the right using red and black wires, respectively, creating a voltage bus and a ground bus on both sides of the board. The pushbutton is mounted across the middle divide of the solderless breadboard. A 10-kilohm resistor connects from the same row as pushbutton’s bottom left pin to the ground bus on the breadboard. There is a wire connecting to digital pin 2 from the same row that connects the resistor and the pushbutton. The top left pin of the pushbutton is connected to +3.3V.


Note on The Pulldown Resistor

What happens if you don’t include the resistor connecting the pushbutton to ground? The resistor connecting the pushbutton is a pulldown resistor. It provides the digital input pin with a connection to ground. Without it, the input will behave unreliably.

If you don’t have a 10-kilohm resistor for the pushbutton, you can use any reasonably high value. 4.7K, 22K, and even 1 Megohm resistors have all been tested with this circuit and they work fine.  See the digital input and output notes for more about the digital input circuit.

If you’re not sure about the resistor color codes, use a multimeter to measure the resistance of your resistors in ohms, and check this resistor color code calculator.

Add Digital Outputs (LEDs)

Connect a 220-ohm resistor and an LED in series to digital pin 3 and another to digital pin 4 of the Arduino. Figures 14,  15, and 16 below show the schematic view as well as the breadboard view for both the Uno and the Nano. If you prefer an audible tone over a blinking LED, you can replace the LEDs with speakers or buzzers. The 220-ohm resistor will work with LED, speaker, or buzzer.

Arduino connected to pushbutton and two LEDs, Schematic view. The pushbutton is connected as described in the image above. Digital pins 3 and 4 are connected to 22-ohm resistors. The other sides of the resistors are connected to the anodes (long legs) of two LEDs. The cathodes of the LEDs are both connected to ground.
Figure 14. Arduino connected to pushbutton and two LEDs, Schematic view.
Arduino Nano connected to pushbutton and two LEDs, Breadboard view. The pushbutton is connected as described in the image above. Digital pins 3 and 4 are connected to 22-ohm resistors. The resistors are mounted across the center divide of the breadboard, each in its own row. The other sides of the resistors are connected to the anodes (long legs) of two LEDs. The cathodes of the LEDs are both connected to ground.
Figure 15. Arduino Uno connected to pushbutton and two LEDs, Breadboard view.

Arduino Nano connected to pushbutton and two LEDs, Breadboard view. The pushbutton is connected as described in the image above. Digital pins 3 and 4 are connected to 22-ohm resistors. The resistors are mounted across the center divide of the breadboard, each in its own row. The other sides of the resistors are connected to the anodes (long legs) of two LEDs. The cathodes of the LEDs are both connected to ground.
Figure 16. Arduino Nano connected to pushbutton and two LEDs, Breadboard view.

Note on LED Resistor Values

For the resistor on the LED, the higher the resistor value, the dimmer your LED will be. So 220-ohm resistors give you a nice bright LED, 1-kilohm will make it dimmer, and 10K or higher will likely make it too dim to see. Similarly, higher resistor values attenuate the sound on a speaker, so a resistor value above 220-ohm will make the sound from your speaker or buzzer quieter.

Related video: Resistors for an LED

Program the Arduino

Make sure you’re using the Arduino IDE version 1.8.19 or later. If you’ve never used the type of Arduino module that you’re using here (for example, a Nano 33 IoT), you may need to install the board definitions. Go to the Tools Menu –> Board submenu –> Board Manager. A new window will pop up. Search for your board’s name (for example, Nano 33 IoT), and the Boards manager will filter for the correct board. Click install and it will install the board definition.

Connect the microcontroller to your computer via USB. When you plug the Arduino into your computer, you’ll find a new serial port in the Tools–>Port menu (for details on installing the software, and USB-to-serial drivers for older Arduino models on Windows, see the Arduino Getting Started Guide). In the MacOS, the name look like this: /dev/tty.usbmodem-XXXX (Board Type) where XXXX are part of the board’s unique serial number and Board Type is the board type (for example, Arduino Uno, Nano 33 IoT, MKRZero, etc.)  In Windows it will be called COM and a number. Figure 17 shows the tools men and its Port submenu.

Screenshot of the Arduino Tools menu showing the Ports submenu
Figure 17. The Arduino Tools menu showing the Ports submenu

Now it’s time to write a program that reads the digital input on pin 2. When the pushbutton is pressed, turn the yellow LED on and the red one off. When the pushbutton is released, turn the red LED on and the yellow LED off.

In your setup() method, you need to set the three pins you’re using as inputs or outputs, appropriately.

1
2
3
4
5
void setup() {
  pinMode(2, INPUT);    // set the pushbutton pin to be an input
  pinMode(3, OUTPUT);   // set the yellow LED pin to be an output
  pinMode(4, OUTPUT);   // set the red LED pin to be an output
}

In the main loop, first you need an if-then-else statement to read the pushbutton. If you’re replacing the LED with a buzzer, the code below will work as is. If you’re using a speaker, there’s an alternative main loop just below the first one:

1
2
3
4
5
6
7
8
9
10
11
12
13
void loop() {
   // read the pushbutton input:
   if (digitalRead(2) == HIGH) {
     // if the pushbutton is closed:
     digitalWrite(3, HIGH);    // turn on the yellow LED
     digitalWrite(4, LOW);     // turn off the red LED
   }
   else {
     // if the switch is open:
     digitalWrite(3, LOW);     // turn off the yellow LED
     digitalWrite(4, HIGH);    // turn on the red LED
   }
 }

Here’s an alternate loop function for an audible output on two speakers. If you want to use only one speaker, try alternating the tone frequency from 440Hz (middle A) to 392Hz (middle G):

1
2
3
4
5
6
7
8
9
10
11
12
13
void loop() {
   // read the pushbutton input:
   if (digitalRead(2) == HIGH) {
     // if the pushbutton is closed:
     tone(3, 440);    // turn on the first speaker to 440 Hz
     noTone(4);     // turn off the second speaker
   }
   else {
     // if the switch is open:
     noTone(3);     // turn off the first speaker
     tone(4, 440);    // turn on the second speaker to 440 Hz
   }
 }

Once you’re done with that, you’re ready to compile your sketch and upload it. Click the Verify button to compile your code. Then click the Upload button to upload the program to the module. After a few seconds, the following message will appear in the message pane to tell you the program was uploaded successfully. Related video: Upload the code to the Arduino

Binary sketch size: 5522 bytes (of a 7168 byte maximum)

Press the pushbutton and watch the LEDs change until you get bored. That’s all there is to basic digital input and output!

The Uno vs Newer Boards

If you’ve used an Uno r3 board before (the “classic Uno”), and are migrating to the Nano or a newer board, you may notice that the serial connection behaves differently. When you reset the MKR, Nano, Uno R4, or Leonardo boards,  or upload code to them, the serial port seems to disappear and re-appear. Here’s why:

There is a difference between the Uno R3 and most of the newer boards like the MKR boards, the Nano 33 IoT and BLE, the Leonardo and the Uno R4: the Uno R3 has a USB-to-serial chip on the board which is separate from the microcontroller that you’re programming. The Uno R3’s processor, an ATMega328, cannot communicate natively via USB, so it needs the separate processor. That USB-to-serial chip is not reset when you upload a new sketch, so the port appears to be there all the time, even when your Uno R3 is being reset.

The newer boards can communicate natively using USB. They don’t need a separate USB-to-serial chip. Because of this, they can be programmed to operate as a mouse, as a keyboard, or as a USB MIDI device.  Since they are USB-native, their USB connection gets reset when you upload new code or reset the processor. That’s normal behavior for them; it’s as if you turned off the device, then turned it back on. Once it’s reset, it will let your computer’s operating system know that it’s ready for action, and your serial port will reappear. This takes a few seconds. It means you can’t reset the board, and then open the serial port in the next second. You have to wait those few seconds until the Arduino board has made itself visible to the computer’s operating system again.

If you have problems with the UBS-native boards’ serial connection, tap the reset button once, then wait a few seconds, then see if the port shows up again once the board has reset itself. You can also double-tap the reset on the MKR and Nano boards to cause the processor to reset and go into a sleep mode. In this mode, the USB connection will reset itself, but your sketch won’t start running. The built-in LED will glow softly. Then upload a blank sketch. From there, you can start as if your board was brand new.

Related video: Digital Output

Applications

Many projects can be made with just digital input and output. For example, a combination lock is just a series of pushbuttons that have been pushed in a particular sequence. Consider the cymbal-playing monkey in Figures 18-20:

A mechanical toy monkey that plays cymbals. The cymbals are covered with aluminum foil. The foil is connected to wires, and the wires are connected to an Arduino and breaboard. The two wires from the cymbals act as a switch when they are hit together.
Figure 18. A mechanical toy monkey that plays cymbals. The cymbals are covered with aluminum foil. The foil is connected to wires, and the wires are connected to an Arduino and breadboard. The two wires from the cymbals act as a switch when they are hit together.

The monkey’s cymbals can be turned into a switch by lining them with tin foil and screwing wires to them:

Detail of the cymbal monkey's cymbal. It is covered with tin foil, as described above.
Figure 19. Detail of the cymbal monkey’s cymbal. It is covered with aluminum foil, as described above.
Detail of the other cymbal. This one is also covered with aluminum foil.
Figure 20. Detail of the other cymbal. This one is also covered with aluminum foil.

Those wires can be run to a breadboard and used as a switch. Then the microcontroller could be programmed to listen for pattern of cymbal crashes, and if it sees that pattern, to open a lock by turning on a digital output.

Consider the project ideas from Project 1 for more applications you can do with simple input and output.

Variables

Adapted from Variables

Introduction

This tutorial explains how computer programs organize information in computer memory using variables. All computer programming languages use variables to manage memory, so it’s useful to understand this no matter what programming language or computer you’re using. Although the following was written with microcontrollers and physical computing applications in mind, it applies to programming in general.

The programming language examples below use a syntax based on the programming language C. That same syntax is used by many other languages, including Arduino (which is written in C), Java, Processing (which is written in Java), JavaScript, and others.

What Is Computer Memory, Anyway?

A computer’s memory is basically a matrix of switches, laid out in a regular grid, not unlike the switches you see on the back of a lot of electronic gear as shown in Figure 1:

Five DIP Switches
Figure 1. DIP Switches

Each switch represents the smallest unit of memory, a bit.  If the switch is on, the bit’s value is 1. If it’s off, the value is 0. Each bit has an address in the grid. We can envision a grid that represents that memory like this:

bit0bit1bit2bit3bit4bit5bit6bit7
bit8bit9bit10bit11bit12bit13bit14bit15
Table 1. All data that’s stored in computer memory is stored in these arrays of bits.

So if a bit can be only 0 or 1, how do we get values greater than 1?

When you count normally, you count in groups of ten. This is because you have ten fingers. So to represent two groups of ten, you write “20”, meaning “2 tens and 0 ones”. This counting system is called base ten, or decimal notation. Each digit place in base ten represents a power of ten: 100 is 102, 1000 is 103, etc.

Now, imagine you had only two fingers. You might count in groups of two. This is called base two, or binary notation. So two, for which you write “2” in base ten, would be “10” in base two, meaning one group of two and 0 ones. Each digit place in base two represents a power of two: 100 is 22, or 4 in base ten, 1000 is 23, or 8 in base ten, and so forth.

Any number you represent in decimal notation can be converted into binary notation by simply regrouping it in groups of two. Once you’ve got the number in binary form, you can store it in computer memory, letting each binary digit fill a bit of memory.  So the number 238 in decimal notation would be 11101110 in binary notation. The bits in memory used to store 238 would look like this:

27 (128)26 (64)25 (32)24 (16)23 (8)22 (4)21 (2)20 (1)
11101110
Table 2. The bits of the decimal value 238. 238 = 27 (128) + 26 (64) +25 (32) +23 (8) + 22 (4) + 21 (2)

Arranging Memory into Variable Space

Programming languages organize computer memory by breaking the grid of bits up into smaller chunks and labeling them with names.  Those names are called variables, and they refer to a location in the computer’s memory. When you ask for the value of a variable, you’re asking what the states of the switches in that location in memory are.

If you think of your program as a set of instructions, then variables are the words that you use to describe what those instructions act upon.

For example:

” When the user has pushed the button two times… “

For this you need a variable called buttonPushed, and you need to check when it’s equal to 2:

1
if (buttonPushed == 2)

When you want to store a piece of something like the number of times a button’s been pushed in the computer’s memory, you give it a name and a data type, which  states how much memory you intend to use. You usually give it an initial value as well. This is called declaring the variable, and it looks like this:

1
2
3
4
int sensorValue = 234;
byte buttonPushed = 15;
long timeSinceStart = 10324;
boolean isOpen = false;

Data Types

Every variable has a data type. The data type of a variable determines how much of the computer’s memory the variable will occupy.  Different programming languages have different data types. The examples above use data types from the C programming language that Arduino uses.

The first one, int sensorValue, is an integer data type. Ints in C take up 16 bits (32 bits in the 32-bit boards like the Nano 33 IoT), so they can contain 216 different values (or 232). Ints can only contain integers, but they can be positive or negative, so the variable sensorValue above could range from -32,768 to 32,767. That’s a range from -215 to 215, with one bit used to store the plus or minus sign.

The second, buttonPushed, is a byte data type. Bytes take up 8 bits, and can therefore store 28 or 256 different values, from 0 to 255. Bytes in Arduino are unsigned, meaning that they can only be positive numbers.

The third, timeSinceStart is a long integer type, for storing very large values. In this instance, it might be storing the number of milliseconds since your program started, which can get big very quickly. Long ints are signed, and can range from -2,147,483,648 to 2,147,483,647. That’s 232 possible values.

The fourth, isOpen, is a boolean variable.  Booleans can only true or false, and ideally take up just one bit in memory (though most programming languages use a whole byte for convenience).

00000000
11101010
00001111
00000000
00000000
00101000
01010100
0
Table 3. A number of variables stored in a processor’s memory as 1s and 0s.

When you declare a variable, the microcontroller picks the next available address and sets aside as many bits are needed for the data type you declare. If you declare a byte, for example, it sets aside 8 bits. An integer gets 16 bits. A string gets one byte (eight bits) for every character of the string, and a byte to end the string.

What About Fractional Numbers?

The variable types above are all for whole numbers, or integers. But how do you store a number like 3.1415 or 2.7828 or other fractional numbers?  These are called floating-point numbers in the programming world, and they’re a special type of variable called a float.  In Arduino, floats are actually 32-bit numbers, stored in 4 bytes of memory. A few of those bits are used to store the decimal point position.

Depending on the processor you are using, the data types might be different sizes. Table 4 shows the different sizes for the data types on the Uno, an 8-bit processor, and the Nano 22 IoT, a 32-bit processor.

Data TypeUnoNano 33 IoT
byte1 byte1 byte
int2 bytes4 bytes
float4 bytes4 bytes
char1 byte1 byte
long4 bytes4 bytes
short2 bytes2 bytes
double4 bytes8 bytes
bool1 byte1 byte
Table 4. The basic Arduino data types and their relative sizes on the Uno and Nano 33 IoT

Here’s a snippet of code to find out a given data type’s size on an Arduino:

1
Serial.print(sizeof(int));

Replace int above with byte, bool, double, float, long, or the data type whose size you want to know.

Choosing the Right Data Type

How do you know what data type to choose when declaring variables? It depends on two factors: what you’re going to use the variables for, and what functions you plan to use on them. First, consider how likely the numbers you might store are likely to be. For example, if you’re counting button pushes, you’re unlikely to get more than a few hundred in a few minutes, so an int or a byte might be fine. But for a number that might get large, like counting the number of milliseconds since some past event, the number could get very large, so you might need a long int.

Different built-in functions of a programming language will require different data types as parameters, so when you can, use data types that match the functions you plan to use.  For example, if you were using a variable to store the results of Arduino’s millis() function, you should use a long int, because millis() returns that data type.

Doing Math With Variables

When you add, subtract, multiply, or divide with variables in a computer program, the results you get depend on the variable types you used. For example,  if you ran the function below:

1
2
3
int voltage = 5;
int divider = 2;
int newVoltage = voltage / divider;

You might think that newVoltage = 2.5, right? Wrong. Because you used ints, the fractional part is gone, so the result would be 2.  Here’s another:

1
2
byte buttonPushes = 254;
buttonPushes = buttonPushes + 4;

After this, you’d expect that buttonPushes = 258, right? Wrong again!  Because you used a byte, you can’t store a value larger than 255, so when the result is larger than that, the number rolls over to the lowest possible value again. The result would be 2.

Wait, what?

Look at it this way. The highest value you can store in a byte is 255. Therefore, if you try to store 256, it rolls over to 0. 257 rolls over to 1. And 258 rolls over to 2. So 254 + 4 in a byte variable yields 2.  If you used an int instead of a byte, then you’d get the result you expect (258) because an int can hold values larger than 255.

Numeric Notation Systems

There are three notation systems used most commonly in programming languages to represent numbers: binary (base two), decimal (base ten), and hexadecimal (base sixteen). In hexadecimal notation, the letters A through F represent the decimal numbers 10 through 15. Furthermore, there is a system of notation called ASCII, which stands for American Standard Code for Information Interchange, which represents most alphanumeric characters from the romanized alphabet as number values. More on ASCII can be found in the pages on serial communication. For more, see this online table representing the decimal numbers 0 to 255 in decimal, binary, hexadecimal, and ASCII. While you can work mostly in decimal notation, there are times when it’s more convenient to represent numbers in ms other than base 10.

Table 5 shows a few number values in the different bases, and the different notation forms:

Decimal valueHexadecimalBinary
 30x030b11
120x0C0b1100
450x2D0b101101
2340xEA0b11101010
10000x3E80b1111101000
Table 5. A few number values in base 10, and their equivalent values in binary (base 2)

Because the values are all bits in the computer’s memory,  you can use all of these notation systems interchangeably. Here are a few examples:

1
2
3
4
5
if (colorValue == 0xFF); // check to see if the color value is 255
 
// add 5 to 0x90. Result will be 0x95:
int channelNumber = 5;
int midiCommand = 0x90 + channelNumber;

Variable scope

Variables are local to a particular function in your code if they are declared in that function. Local variables can’t be used by functions outside the one that declares them, and the memory space allotted to them is released  when the function ends. Variables are global when they are declared at the beginning of a program, outside all functions. Global variables are accessible to all functions in a the program, and their value is maintained for the duration of the program. Usually you use global variables for values that will need to be kept in memory for future use by other functions, and local variables when you know the value won’t be used outside that function. In general, it’s better to default to local variables when you can, to manage memory more efficiently. Here’s a typical example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int oldButtonPush = 0// global variable
 
void setup() {
   Serial.begin(9600);
}
 
void loop() {
   int buttonPush = digitalRead(3); // local variable
   if (buttonPush != lastButtonPush) {
      // the button changed. Do something here
      // then store the current button push state
      // in the global variable for the next time
      // through the loop:
   oldButtonPush = buttonPush; 
   }
}

In this example, the variable buttonPush is local to the loop function. You couldn’t read it in the setup, or any other function. The variable oldButtonPush, on the other hand, is global, and can be read by any function. In the example above, the local variable is used to read the latest state of a digital input, and then later, the value is put into the global variable so that you can get a new reading and compare it to the old one.

Constants

In addition to variables, every programming language also includes constants, which are simply variables that don’t change. They’re a useful way to label and change numbers that get used repeatedly within your program. For example, imagine you’re writing a program that runs a servo motor. Servo motors have a minimum and maximum pulse width that doesn’t change, although each servo’s minimum and maximum might be somewhat different. Rather than change every occurrence of the minimum and maximum numbers in the program, we make them constants, so we only have to change the number in one place.

You don’t have to use constants in your programs, but they’re handy to know about, and you will encounter them in other people’s programs.

In C and therefore in Arduino, there are two ways you can declare constants. You can use the const keyword, like so:

1
2
const int LEDpin = 3;
const int sensorMax = 253;

Or you can use  define:

1
2
#define LEDPin 3
#define sensorMax 253

Defines are always preceded by a #, and are don’t have a semicolon at the end of the line. Defines always come at the beginning of the program. They actually work a bit like aliases. What happens is that you define a number as a name, and before compiling, the compiler checks for all occurrences of that name in the program and replaces it with the number. This way, defines don’t take up any memory, but you get all the convenience of a named constant. There are several defines in the libraries of the Arduino core libraries, so it’s preferable to use const instead of #define for constants.

For more on variables in Arduino, see the variable reference page.

Analog Input

Introduction

This is an introduction to basic analog input on a microcontroller. In order to get the most out of it, you should know something about the following concepts.  You can check how to do so in the links below:

These video links will help in understanding analog input:

Analog Input

While a digital input to a microcontroller can tell you about discrete changes in the physical world, such as whether the cat is on the mat, or the cat is off the mat, there are times when this is not enough. Sometimes you want to know how fat the cat on the mat is. In order to know this, you’d need to be able to measure the force the cat exerts on the mat as a variable quantity. When you want to measure variably changing conditions like this, you need analog inputs. An analog input to a microcontroller is an input that can read a variable voltage, typically from 0 volts to the maximum voltage that powers the microcontroller itself.

Many transducers are available to convert various changing conditions to changing electrical quantities. There are photocells that convert the amount of light falling on them to a varying resistance; flex sensors that change resistance as they are bent; Force-sensitive resistors (FSRs) that change resistance based on a changing force applied to the surface of the sensor; thermistors that change resistance in response to changing heat; and many more.

Related video: Resistors, variable resistors, and photocells

In order to read these changing resistances, you put them in a circuit and pass a current through them, so that you can see the changing voltage that results. There are a few variations on this circuit. The simplest is called a voltage divider. Because the two resistors are in series voltage at the input to the microcontroller is proportional to the ratio of the resistors. If they are equal, then the input voltage is half the total voltage. So in the circuit in Figure 1, if the variable resistor changes (for example, if it’s a flex sensor being bent), then the voltage at the input changes.  The fixed resistor’s value is generally chosen to complement the variable resistor’s range. For example, if you have a variable resistor that’s 10-20 kilohms, you might choose a 10 kilohm fixed resistor.

analog in schematic
Figure 1. voltage divider with a variable resistor and a fixed resistor

In Figure 2, you use a potentiometer,  which is a variable resistor with three connections. The center of the potentiometer, called the wiper,  is connected to the microcontroller. The other two sides are attached to power and ground. The wiper can move from one end of the resistor to the other. In effect, it divides the resistor into two resistors and measures the resistance at the point where they meet, just like a voltage divider.

Related videos:

potentiometer schematic
Figure 2. potentiometer schematic

Since a microcontroller’s inputs can read only two values (typically 0 volts or the controller’s supply voltage), an analog input pin needs an extra component to read this changing, or analog voltage, and convert it to a digital form. An analog-to-digital converter (ADC) is a device that does this. It reads a changing input voltage and converts it to a binary value, which a microcontroller can then store in memory.Many microcontrollers have ADCs built in to them. Arduino boards have an ADC attached to the analog input pins.

The ADC in the Arduino can read the input voltage at a resolution of 10 bits. That’s a range of 1024 points. If the input voltage range (for example, on the Uno) is 0 to 5 volts, that means that the smallest change it can read is 5/1024, or 0.0048 Volts. For a 3.3V board like the Nano 33 IoT, it’s 0.0029 volts. When you take a reading with the ADC using the analogRead() command, the microcontroller stores the result in memory. It takes an int type variable to store this, because a byte is not big enough to store the 10 bits of an ADC reading. A byte can hold only 8 bits, or a range from 0 to 255.

The command in Arduino is the analogRead() command, and it looks like this:

1
sensorReading = analogRead(pin);
  • Pin is the analog input pin you are using;
  • sensorReading is an integer variable containing the result from the ADC.

The number produced in sensorReading is will be between 0 and 1023. Its maximum may be less, depending on the circuit you use. A potentiometer will give the full range, but a voltage divider for a variable resistor like a force sensing resistor or flex sensor, where one of the resistors is fixed, will not.

The analog inputs on an Arduino (and in fact, on most microcontrollers), are all connected to the same ADC circuit, so when the microcontroller has to switch the ADC’s input from one pin to another when you try to read two pins one after another. If you read them too fast, you can get unstable readings. You can also get more reliable readings by introducing a small delay after you take an analog reading. This allows the ADC time to stabilize before you take your next reading.

Here’s an example of how to read three analog inputs with minimal delay and maximum stability:

1
2
3
4
5
6
sensorOne = analogRead(A0);
delay(1);
sensorTwo = analogRead(A1);
delay(1);
sensorOne = analogRead(A2);
delay(1);

Analog and digital inputs are the two simplest ways that a microcontroller reads changing sensor voltage inputs. Once you’ve understood these two, you’re ready to use a variety of sensors.

Videos: Digital and Analog Input and Output

ITP Videos by Jeff Feddersen on Vimeo.

Digital Input and Output

The Fixed Resistor is Necessary!

Ohm Part 2 from ITP_NYU on Vimeo.

Digital input with internal pull-up resistors

Analog Input

Analog Output

Pseudo-analog output:

Tone Output

Servo Control using Pulse Width Modulation

Analog Output: Motor Control

Multiple Inputs or Outputs

Multiplexers

Jeff explains how a multiplexer allows you to connect multiple analog or digital input circuits to a single microcontroller input.

Shift Registers

Jeff and Tom explain how to use shift registers to control multiple digital outputs.

Binary Coded Decimal (BCD) controller

Jeff shows you how to control multiple LEDs on a 7-segment numerical LED display using a Binary Coded Decimal (BCD) controller

Videos: Serial Communication

ITP Videos by Jeff Feddersen on Vimeo.

Asynchronous Serial

Introduction to Asynchronous Serial

Serial Communication Under the Hood

Reading Serial Input on an Arduino

Devices and Bytes: ASCII vs Binary

Serial to p5.js in binary:

Serial to p5.js in ASCII:

Serial from p5.js to Arduino:

Serial out to p5.js multi-part ASCII:

Screens: Communicating from a mobile device to a microcontroller using p5.js serialControl:

Serial to Processing:

Synchronous Serial (I2C and SPI)

Programming an ATTiny from an Arduino Uno via SPI