Lab: Serial Output From p5.js Using the p5.webserial Library

In this lab you’ll learn how to send data from p5.js to a microcontroller using asynchronous serial communication.

Overview

When you use the p5.webserial library for P5.js, it uses the W3C’s WebSerial API to allow your browser to communicate with serial ports on your computer. This lab shows you how to use P5 to control a microcontroller using asynchronous serial communication. WebSerial is currently only available in the Chrome and Chromium browsers and the Microsoft Edge browser, so make sure you’re using one of those to do this lab.

To get the most out of this tutorial, you should know what a microcontroller is and how to program them. You should also understand asynchronous serial communication between microcontrollers and personal computers. You should also understand the basics of P5.js, and should have tried the WebSerial Input to P5.js lab.

Things You’ll Need

For this lab, you’ll need the hardware below,

For this lab, you’ll need the hardware below, and you’ll need the same software setup as the WebSerial Input to P5.js lab: You’ll create a p5.js sketch. You’ll also use the p5.WebSerial library. You can use the p5.js web editor or your favorite text editor for this (the Visual Studio Code editor works well).

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. Microcontroller.  Arduino Nano 33 IoT
LEDs. Shown here are four LEDs. The one on the right is an RGB LED. You can tell this because it has four legs, while the others have only two legs.
Figure 2. LEDs. Shown here are four LEDs. The one on the right is an RGB LED. You can tell this because it has four legs, while the others have only two legs.
Resistors. Shown here are 220-ohm resistors. You can tell this because they have two red and one brown band, followed by a gold band.
Figure 3. Resistors. Shown here are 220-ohm resistors. You can tell this because they have two red and one brown band, followed by a gold band.
An 8 ohm speaker with 2 wires solder to the speakers leads
Figure 4. An 8 ohm speaker (optional).This is a good alternate to the LED if you prefer audible output.

Prepare the breadboard

Connect power and ground on the breadboard to power and ground from the microcontroller. On the Arduino Uno, use the 5V and any of the ground connections. On the Nano, use 3.3V and the ground connections:

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 5. An Arduino Uno on the left connected to a solderless breadboard, right.
Arduino Nano on a breadboard.
Figure 6. Breadboard view of an Arduino Nano mounted on a breadboard.

The +3.3 volts and ground pins of the Arduino Nano are connected by red and black wires(Figure 6), 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.Figure 5. Breadboard view of an Arduino Nano connected to a breadboard. 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.

Made with Fritzing

Add an LED

Connect the LED and resistor to digital I/O pin 11 of the module(Figure 7-8). Alternately, you can replace the 220-ohm LED with a speaker (Figure 9-10). You’ll find code below that uses tones instead of LEDs where appropriate. For more on how to do that, see the Tone Output lab:

Schematic view of an Arduino connected to an LED. Digital pin 5 is connected to a 22-ohm resistor. The other side of the resistor is connected to the anode (long leg) of an LED. The cathode of the LED is connected to ground.
Figure 7. Schematic view of an Arduino connected to an LED.
Breadboard view of an Arduino connected to an LED. 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 blue wire connects Digital to a 22-ohm resistor that straddles the center divide of the breadboard in row 17. The other side of the resistor is connected to the anode (long leg) of an LED. The LED is mounted in rowsd 16 and 17 of the right side of the center section of the board. a black wire connects the cathode's row, row 16, to the ground bus on the right side of the board.
Figure 8. Breadboard view of an Arduino connected to an LED.

Breadboard view of an LED connected to digital pin 5 of an Arduino Nano.
Figure 9. Breadboard view of an LED connected to digital pin 5 of an Arduino Nano.

Figure 9 shows a breadboard view of an LED connected to digital pin 5 of an Arduino Nano. The Nano straddles the center of the breadboard in the first fifteen rows. The Nano’s voltage pin (physical pin 2) connects to the board’s voltage bus, and the Nano’s ground pin (physical pin 14) connects to the board’s ground bus. The LED is in the right center of the board, with its anode in one row and the cathode in the next. A 220-ohm resistor connects the LED’s anode to a wire connecting to digital pin 5. The LED’s cathode is connected to the ground bus.

Breadboard view of an Arduino Nano connected to two force sensing resistors (FSRs) and a speaker. The Nano’s 3.3 Volts (physical pin 2) and ground (physical pin 14) are connected to the voltage and ground buses of the breadboard as usual. The red positive wire of the speaker is connected to digital pin 5 of the Arduino. The black ground wire of the speaker is connected to one leg of a 100 ohm resistor. The other leg of the resistor connects to ground.
Figure 10. Breadboard view of an Arduino Nano connected to a speaker to digital pin 5.

Figure 10 shows a breadboard view of an Arduino Nano connected to a speaker. The Nano’s ground (physical pin 14) is connected to the ground bus of the breadboard as usual. The red positive wire of the speaker is connected to digital pin 5 of the Arduino. The black ground wire of the speaker is connected to one leg of a 100 ohm resistor. The other leg of the resistor connects to ground.

Program the Microcontroller

Program your Arduino to read the analog input as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void setup() {
  Serial.begin(9600);     // initialize serial communications
  pinMode(5, OUTPUT);
}
 
void loop() {
  if (Serial.available() > 0) { // if there's serial data available
    int inByte = Serial.read();   // read it
    Serial.write(inByte);         // send it back out as raw binary data
    analogWrite(5, inByte);       // use it to set the LED brightness
    // if you're using a speaker instead of an LED, uncomment line below  and comment out the previous line:
    //  tone(5, inByte*10);     // play tone on pin 5
  }
}

Only one port at a time can access a serial port.

As you work on this any microcontroller-to-computer application, you will be switching back and forth between the app that programs the microcontroller (in this case, the Arduino IDE) and the app that the microcontroller is communicating with (in this case, p5.js in the browser). You have to keep in mind that only one of these at a time can access a serial port.

That means that when you want to reprogram your Arduino from the Arduino IDE, you should to stop your sketch in the browser window to do so. Then, restart the browser sketch when you’re done reprogramming the Arduino. You don’t need to quit the Arduino IDE each time, because it knows to release the serial port when it’s not programming. However, you do need to close the Serial Monitor in the Arduino IDE when you are using WebSerial in the browser.

The P5.js WebSerial Library

To communicate with your microcontroller serially, you’re going to use the P5.js WebSerial library. If you’re using the p5.js web editor, make a new sketch. Click the Sketch Files tab, and then choose the index.html file. In the head of the document, look for this line:

Right after that line, add this line:

The P5.js Sketch

The sketch you’re going to write will control the microcontroller’s LED from P5.js. Dragging the mouse up and down the canvas will dim or brighten the LED, and typing 0 through 9 will set the LED’s brightness in increments from off (0) through almost full brightness (9). There’s an alternate sketch that will make changing tones if you prefer that instead of a changing LED. The sketch will also receive serial input from the microcontroller just as in the WebSerial Input to P5.js lab, so that you can see that the microcontroller is getting the same values you’re sending.

The setup of your sketch will initialize the P5.webserial library and define your callback functions for serial events. Program the global variables and setup() function as follows:

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
27
28
29
30
31
32
33
// variable to hold an instance of the p5.webserial library:
const serial = new p5.WebSerial();
 
// HTML button object:
let portButton;
let inData;                            // for incoming serial data
let outByte = 0;                       // for outgoing data
 
function setup() {
  createCanvas(400, 300);          // make the canvas
  // check to see if serial is available:
  if (!navigator.serial) {
    alert("WebSerial is not supported in this browser. Try Chrome or MS Edge.");
  }
  // if serial is available, add connect/disconnect listeners:
  navigator.serial.addEventListener("connect", portConnect);
  navigator.serial.addEventListener("disconnect", portDisconnect);
  // check for any ports that are available:
  serial.getPorts();
  // if there's no port chosen, choose one:
  serial.on("noport", makePortButton);
  // open whatever port is available:
  serial.on("portavailable", openPort);
  // handle serial errors:
  serial.on("requesterror", portError);
  // handle any incoming serial data:
  serial.on("data", serialEvent);
  serial.on("close", makePortButton);
}
 
function draw() {
 
}

For now you’re leaving the draw() function empty. You’ll fill it in later. You’ll be adding some functions to read mouse dragging and key pressing as well.

Program the handler functions similarly to those in the WebSerial Input to P5.js lab:

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// if there's no port selected,
// make a port select button appear:
function makePortButton() {
  // create and position a port chooser button:
  portButton = createButton("choose port");
  portButton.position(10, 10);
  // give the port button a mousepressed handler:
  portButton.mousePressed(choosePort);
}
 
// make the port selector window appear:
function choosePort() {
  serial.requestPort();
}
 
// open the selected port, and make the port
// button invisible:
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() {
    console.log("port open");
  }
  // hide the port button once a port is chosen:
  if (portButton) portButton.hide();
}
 
// read any incoming data as a byte:
function serialEvent() {
  // read a byte from the serial port:
  var inByte = serial.read();
  // store it in a global variable:
  inData = inByte;
}
 
// pop up an alert if there's a port error:
function portError(err) {
  alert("Serial port error: " + err);
}
 
// try to connect if a new serial port
// gets added (i.e. plugged in via USB):
function portConnect() {
  console.log("port connected");
  serial.getPorts();
}
 
// if a port is disconnected:
function portDisconnect() {
  serial.close();
  console.log("port disconnected");
}
 
function closePort() {
  serial.close();
}

Program the  draw() function to display the value of any incoming serial bytes. Here it is:

31
32
33
34
35
36
37
function draw() {
  // black background, white text:
  background(0);
  fill(255);
  // display the incoming serial data as a string:
  text("incoming value: " + inData, 30, 30);
}

To read the mouse and keyboard, you’ll need to write functions to respond to the ‘mouseDragged’ and ‘keyPressed’ events. ‘MouseDragged’ will happen whenever you click and drag the mouse on the canvas. When that happens, read the mouseY, and map its position on the canvas to a value from 0 to 255. Convert the result to a number using the int() function. Then send it out the serial port using the serial.write() function:

39
40
41
42
43
44
function mouseDragged() {
  // map the mouseY to a range from 0 to 255:
  outByte = byte(map(mouseY, 0, height, 0, 255));
  // send it out the serial port:
  serial.write(outByte);
}

The serial.write() function is versatile. If you give it a variable or literal that’s a numeric data type, it will send it as its raw binary value. In the code above, note how you’re converting the output of the map() function to a number using the int() function.  If you give it a string, however, it will send out that ASCII string. So be aware of the difference, and make sure you know whether your serial receiving device wants raw binary or ASCII-encoded data.

Program the keyPressed() function similarly to the mouseDragged() function. You want it to read the key strokes, convert them to raw bytes, and send them out the serial port. But you only want to send them if they key hit was 0 through 9. The P5.js variable key returns a numeric value, so you can do math on it and convert it like so:

46
47
48
49
50
51
function keyPressed() {
  if (key >= 0 && key <= 9) { // if the user presses 0 through 9
    outByte = (key * 25); // map the key to a range from 0 to 225
    serial.write(outByte); // send it out the serial port
  }
}

That’s all you want your sketch to do, so try running it now. You should see that the initial incoming serial value is undefined, but when you drag the mouse up and down, or type 0 through 9, it will update when the Arduino program returns what it received. The LED will also change with these actions.

You can see this sketch running on gitHub at this link. You can get the full text of it at this link.

Sending ASCII-Encoded Serial Data

When you send data from p5.js using p5.webserial, the serial.write() function works like it does in Arduino: it sends numbers as binary data. In the programs above, you’re sending binary data from p5.js and reading it as binary in Arduino.

If you want to send ASCII-encoded serial data from P5.js instead, all you have to do is to serial.print() or serial.println() your string. On the Arduino side, you can read single characters one byte at a time simply as well. However, if you want to convert multi-byte number strings to numeric values, you’ll need a new function to read ASCII encoded numeric strings called parseInt().

Program the Microcontroller Again

To start off with, load a sketch from the Arduino examples called PhysicalPixel. You can find it in the File Menu -> Examples -> Communication -> PhysicalPixel. Here’s what it looks like. Change the LED pin number to pin 5 as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const int ledPin = 5; // the pin that the LED is attached to
int incomingByte;     // a variable to read incoming serial data into
 
void setup() {
  Serial.begin(9600);             // initialize serial communication
  pinMode(ledPin, OUTPUT);        // initialize the LED pin as an output
}
 
void loop() {
  if (Serial.available() > 0) { // see if there's incoming serial data
    incomingByte = Serial.read(); // read it
    if (incomingByte == 'H') {    // if it's a capital H (ASCII 72),
      digitalWrite(ledPin, HIGH); // turn on the LED
      // if you're using a speaker instead of an LED, uncomment line below  and comment out the previous line:
      //  tone(5, 440);           // play middle A on pin 5
    }
    if (incomingByte == 'L') {    // if it's an L (ASCII 76)
      digitalWrite(ledPin, LOW);  // turn off the LED
      // if you're using a speaker instead of an LED, uncomment line below  and comment out the previous line:
      // noTone(5);
    }
  }
}

When you run this, open the serial monitor and type H or L, and the LED will go on or off.  Try typing h or l instead. The LED won’t change, because H and h have different ASCII values, as do L and l. But you can see from this that you don’t need to memorize the ASCII chart to check for character values in your code. Put the character you want to read in single quotes, and the Arduino compiler will automatically convert the character to its ASCII value for you. It only works for single characters, though.

Program P5.js To Control the LED

To get P5.js to control this Arduino program serially, you only need to add to the keyPressed() function to read H or L in addition to 0 through 9. Here’s your new mousePressed() function:

1
2
3
4
5
6
7
8
9
10
11
function keyPressed() {
  if (key >= 0 && key <= 9) {
    // if the user presses 0 through 9
    outByte = byte(key * 25); // map the key to a range from 0 to 225
    serial.write(outByte); // send it out the serial port
  }
  if (key === "H" || key === "L") {
    // if the user presses H or L
    serial.write(key); // send it out the serial port
  }
}

Because the key is already a single character, P5.js sends it out as is, and Arduino reads it as a single byte, looking for the ASCII value of H or L. Notice how the values returned to P5.js are 72 and 76, the ASCII values for H and L. For single characters like this, exchanging data is simple.

If you tried to change the LED with the mouse, you didn’t see anything happen unless your output value was 72 or 76. Why is that?

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.

Processing ASCII-Encoded Strings With Arduino

It is also possible to read and interpret ASCII-encoded strings in Arduino. The String.parseInt() function reads an incoming string until it finds a non-numeric character, then converts the numeric string that it read into a long integer. This is a blocking function, meaning that String.parseInt() stops the program and does nothing until it sees a non-numeric character, or until a timeout passes. The timeout is normally one second (or 1000 milliseconds), but you can set it to a lower number of milliseconds using Serial.setTimeout(). Here’s a variation on the original Arduino sketch from above, using Serial.parseInt() this time:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void setup() {
  Serial.begin(9600);    // initialize serial communications
  Serial.setTimeout(10); // set the timeout for parseInt
  pinMode(5, OUTPUT);
}
 
void loop() {
  if (Serial.available() > 0) { // if there's serial data available
    int inByte = Serial.parseInt(); // read it
    if (inByte > 0) {
      Serial.write(inByte);      // send it back out as raw binary data
      analogWrite(5, inByte);    // use it to set the LED brightness
      // if you're using a speaker instead of an LED, uncomment line below  and comment out the previous line:
      //  tone(5, inByte*10);     // play tone on pin 5
    }
  }
}

Upload this to your microcontroller, then open the Serial Monitor and send in some ASCII numeric strings. You’ll see the character that’s represented by the string’s value. For example, 65 will return A, 34 will return “, and so forth.Notice that this version of the sketch has a conditional statement to check if the incoming byte is 0. This is because of a quirk of the parseInt() function. It returns 0 if the timeout is hit, or if the string is legitimately 0. This means you can’t really parse for a string like this: "0\n".

Program p5.js To Send a String

Now that your microcontroller is expecting a string, program P5.js to send one. This means changing the mouseDragged() function. Program it to print a string with a newline at the end using the serial.println() command like so:

1
serial.println(outByte);

Since the newline added at the end by serial.println() is a non-numeric character, the Serial.parseInt() function will see it and parse the string, not waiting for the timeout.

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

When you’re sending data between two computers using asynchronous serial communication, you have to make sure that what the sender is sending is formatted the same as what the receiver is listening for. See Table 1 to review what are suitable data formats for different types/sizes of data and which functions to use on p5.js and Arduino for serial communication.

Number of Bytes
1 Byte
Multi Bytes
Data to Send
A single number < 255
A single character
A single number > 255, multiple values
Send as:
Binary
Ascii
Ascii
p5.js ->
serial.write(integer)
serial.write(string)
serial.write
(valueToSend + ",")
-> Arduino
Serial.read()
Serial.parseInt()

Table 1. Serial Communication: p5.js to Arduino

Think this out in advance before you code, then consider what functions you’ve got on both computers to convert data from strings to raw binary numbers and back. Test with fixed values at first, so you know you’re getting what you think you should. For example, sending an ASCII-encoded numeric string like this:

1023\n

Will always result in these six bytes:

49 48 50 51 10

Likewise, this text string:

Hello\n

will always be:

72 101 108 108 111 10

By sending a string you know both the ASCII and raw binary representations of, you can test your code easier, because what you’re sending won’t change. Once you know the sending and receiving works, then you can send variable strings.

The more you work with serial data, the more you’ll become familiar with the methods for handling it.

For more on serial flow control in P5.js, see the Two-Way Duplex Serial Communication Using p5.WebSerial Lab.

p5.serialport and p5.webserial Compared

Asynchronous serial communication is not part of the core of p5.js, nor is it part of the core for web browsers. To read serial in a p5.js sketch, or in a web page in general, you need some help. One approach is to open a separate application that connects to the serial port and offers a webSocket connection to the browser. This is the approach that the p5.serialport library and the p5.serialcontrol app take. Another approach is to use the browser API called WebSerial. This is the approach that the p5.webserial library takes.

The difference between them is best illustrated by Figures 1 and 2 below. With p5.seriaport, you must open another application on your computer, p5.serialcontrol, to communicate with the serial port. It is this application that handles serial communication, not the p5.js sketch in the browser. This can be more complicated for beginning users.

Diagram of three rectangles connected by arrows. The rectangle on the right represents your p5.js sketch, running in a browser. Your sketch implements the p5.serialport library. Your sketch connects to p5 serial control, the middle rectangle, via a webSocket. The p5 serial control application runs on your laptop. It connects to a serial port on your computer and listens for webSocket connections from your p5.js sketch. It passes whatever comes in the serial port through to the webSocket and vice versa. The third rectangle is your Arduino, connected to p5 serial control via the serial port.
Figure 1. Diagram of the connection from the serial port to p5.js through p5.serialcontrol
Diagram of two rectangles connected by arrows. The rectangle on the right represents your p5.js sketch, running in a browser. Your sketch implements the p5.webserial library. The browser connects to a serial port on your computer. It passes whatever comes in the serial port through to the webSocket and vice versa. The second rectangle is your Arduino, connected via the serial port.
Figure 2. Diagram of the connection from the serial port to p5.js through p5.webserial
p5.serialport/p5.serialcontrol:p5.webserial:
advantages:
* works in all browsers.
* Allows your browser to connect to the serial port of a remote computer, if that computer is running a web server program.
* Supports opening multiple serial ports at the same time
advantages:
* connection is direct to the browser, making it easier for beginners to learn



disadvantages:
* requires a third application (p5.serialcontrol), making it more difficult for beginners to learn
disadvantages:
* only works in Chrome and Edge browsers
* No connection to remote computers
* Not sure if it supports a second serial port.
Table 1. Comparing p5.seriaport to p5.webserial

The APIs of p5.serialport and p5.webserial are similar, so it’s pretty straightforward to convert between them.

The code below is a typical p5.serialport setup. You make an instance of the p5.serialport library with the port name, then you make callback functions for the primary tasks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let serial;          // variable to hold an instance of the serialport library
let portName = '/dev/cu.usbmodem1421'// fill in your serial port name here
  
function setup() {
  serial = new p5.SerialPort();       // make a new instance of the serialport library
  serial.on('list', printList);  // set a callback function for the serialport list event
  serial.on('connected', serverConnected); // callback for connecting to the server
  serial.on('open', portOpen);        // callback for the port opening
  serial.on('data', serialEvent);     // callback for when new data arrives
  serial.on('error', serialError);    // callback for errors
  serial.on('close', portClose);      // callback for the port closing
  
  serial.list();                      // list the serial ports
  serial.open(portName);              // open a serial port
}

The code below is a typical p5.webserial setup. You check t see if WebSerial is available, and then you make an instance of the p5.webserial library, then you look for ports using getPorts():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// variable to hold an instance of the p5.webserial library:
const serial = new p5.WebSerial();
 // port chooser button:
let portButton;
// variable for incoming serial data:
let inData;
  
function setup() {
   // check to see if serial is available:
   if (!navigator.serial) {
    alert("WebSerial is not supported in this browser. Try Chrome or MS Edge.");
  }
  // check for any ports that are available:
  serial.getPorts();
  // setup function continues below:

Unlike with p5.serialport, you don’t pass in the port name. Instead, the user chooses the port with a pop-up menu:

1
2
3
4
5
// if there's no port chosen, choose one:
serial.on("noport", makePortButton);
// open whatever port is available:
serial.on("portavailable", openPort);
// setup function continues below:

Then you make callback functions for the primary tasks. These are similar to p5.serialport’s callbacks:

1
2
3
4
5
6
// handle serial errors:
serial.on("requesterror", portError);
// handle any incoming serial data:
serial.on("data", serialEvent);
serial.on("close", makePortButton)
// setup function continues below:

You also add listeners for when the USB serial port enumerates or de-enumerates. This way, if you unplug your serial device or reset it, your program automatically reopens it when it’s available:

1
2
3
4
  // add serial connect/disconnect listeners from WebSerial API:
  navigator.serial.addEventListener("connect", portConnect);
  navigator.serial.addEventListener("disconnect", portDisconnect);
} // end of setup function

The functions in each API are similar too:

p5.serialport functions (from the library’s guide)p5.webserial functions (from the library’s guide)
read() returns a single byte of data (first in the buffer)read() — reads a single byte from the queue (returns a number)

readChar() returns a single char, e,g, ‘A’, ‘a’
readChar() — reads a single byte from the queue as a character (returns a string)

readBytes() returns all of the data available as an array of bytes
readBytes() — reads all data currently available in the queue as a Uint8Array.
readBytesUntil(charToFind) returns all of the data available until charToFind is encounteredreadBytesUntil(charToFind) — reads bytes until a character is found (returns a Uint8Array)

readString() returns all of the data available as a string
readStringUntil(charToFind) returns all of the data available as a string until charToFind is encounteredreadStringUntil(stringToFind) — reads out a string until a certain substring is found (returns a string)
last() returns the last byte of data from the buffer

lastChar() returns the last byte of data from the buffer as a char
readLine() — read a string until a line ending is found (returns a string)
write() – sends most any JS data type out the port.write() – sends most any JS data type out the port.
print()– sends string data out the serial port
println() – sends string data out the serial port, along with a newline character.
Table 2. Comparison of p5.serialport and p5.webserial APIs

Both p5.serialport and p5.webserial will do the job of getting bytes from a serial device like an Arduino or other microcontroller to p5.js and vice versa. Which you use depends on what you need. p5.webserial is great if you’re connected directly to the browser’s computer via USB. For most serial applications, this is the way to go. Alternately, p5.serialport offers the advantage that you can run p5.serialcontrol app on your browser’s computer or on another computer on the same network. That way, a browser on your phone or tablet can communicate to the serial device via the computer running p5.serialcontrol.

This site contains lab examples for both p5.serialport and p5.webserial for your convenience.

Lab: Bluetooth LE and p5.ble

This exercise introduces you to how to communicate between a Bluetooth LE-equipped microcontroller and p5.js using the p5.ble library.

Introduction

Bluetooth has been a popular method for wireless communication between devices for many years now. It’s a good way to communicate between two devices directly over a distance of 10 meters or less. Version 4.0 of the Bluetooth specification, also known as Bluetooth LE, introduced some changes to Bluetooth, and made it more power-efficient. There are many Bluetooth LE-equipped microcontroller modules on the market, and they all follow the same general patterns of communication. This exercise introduces you to how to communicate between a Bluetooth LE-equipped microcontroller and p5.js using the p5.ble library.

What You’ll Need to Know

To get the most out of this tutorial, you should know what a microcontroller is and how to program them. You should also understand asynchronous serial communication between microcontrollers and personal computers. You should also understand the basics of P5.js. For greater background on Bluetooth LE, see the BLEDocs repository, or the book Make: Bluetooth by Alasdair Allan, Don Coleman, and Sandeep Mistry.

Here are a few additional usedul Bluetooth LE references:

Things You’ll Need

In order to use the ArduinoBLE library, as shown in Figure 1-2, both Arduino  MKR 1010 and the Arduino Nano 33 IoT boards, among others, work for this tutorial. You could also do this on the Nano 33 BLE.

You might need external components for your own Bluetooth LE project, but for this introduction, you won’t need any external components.

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 or Nano 33 BLE or…
Photo of an Arduino MKR 1010 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 2. Arduino MKR 1010 module.

Bluetooth LE Concepts

Bluetooth LE devices can be either central devices, or peripherals. Peripheral devices offer Bluetooth services that central devices can receive. For example, your fitness device is a peripheral device and the mobile phone or laptop that connects to it is a central device.

Peripherals offer services, which consist of characteristics. For example, a light controller might offer a light service, with four characteristics: red, green, blue, and white channels. Characteristics have values, and central devices can connect to a peripheral and read, write, or subscribe to those changing values. Characteristics can be assigned any of these three capabilities.

Bluetooth LE devices, services, and characteristics are described using Universally Unique Identifiers, or UUIDs. UUIDs are 128-bit numbers, and are generally formatted like this: cc3e5f6f-9d50-43fb-86e3-1f69e3916064. You can generate UUIDs using uuidgenerator.net. You can also do it on a MacOS or Posix command line by typing uuidgen.

There are certain short UUIDs defined by the Bluetooth LE specification for well-known services and characteristics, such as battery level, Human Interface Device, and so forth. A list of the more well-known UUIDs can be found on the Bluetooth SIG Assigned Numbers page. When you’re making your own services and characteristics, you should generate long UUIDs.

All good Bluetooth LE libraries follow the device, service, characteristic model.  The general process from the peripheral side is as follows:

  • Set peripheral name
  • Establish advertised services
  • Add characteristics to services
  • Start advertising

From the central side, the process is:

  • Scan for peripherals
  • Connect to a given peripheral
  • Query for services
  • Query for characteristics
  • Read, write, or subscribe to characteristics

Bluetooth LE Central Apps

There are a number of good Bluetooth LE Central apps that let you scan for peripherals and interact with their services and characteristics. When you’re developing Bluetooth LE applications, it’s essential to have one on hand. Here are several:

Figure 3 below shows the initial scan for peripherals using BlueSee on macOS. You can see a variety of devices listed by UUID in the first column (note: this is not your service UUID, it’s an ID that MacOS assigns to the BLE device); the received signal strength (RSSI) in the second column; peripheral’s local name in the third column; and manufacturer name and info in the remaining columns. Most central scanning apps will list at least the device UUID and name, and let you connect to one device at a time.

Screenshot of the BlueSee app on MacOS scanning for peripherals.
Figure 3. BlueSee app scanning for peripherals.

Figure 4 shows the characteristic detail from BlueSee when it’s connected to a particular peripheral’s characteristic. In most apps, like in this one, if a characteristic is writable, you can write to it in either hexadecimal or text.

Screenshot of the BlueSee app's Characteristic detail screen.
Figure 4. BlueSee Characteristic detail screen.

Program the Arduino

Make sure you’re using the Arduino IDE version 1.8.10 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 –> 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.

You’ll need to install the ArduinoBLE library too. Go to the Sketch menu –> Include Library… –> Manage Libraries. A new window will pop up. Search for your the name of the library (ArduinoBLE) and click the Install button. The Library manager will install the library.

Once you’ve installed the library, look in the File –> Examples submenu for the ArduinoBLE submenu. In there, look for the Peripherals submenu and open the sketch labeled LED. This sketch turns your board into a peripheral with one service called LED. That service has one characteristic, called switchCharacteristic, that is readable and writable by connected central devices. When you write the value 1 to this characteristic, the on-board LED turns on. When you write 0 to the characteristic, the LED turns off.

The beginning of the sketch establishes the service and characteristic as global variables:

1
2
3
4
5
6
7
8
#include "ArduinoBLE.h"
 
BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service
 
// BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by central
BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);
 
const int ledPin = LED_BUILTIN; // pin to use for the LED

In the setup(), you’ll follow the steps outlined above: set the name and the services, add characteristics, and advertise:

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
27
28
29
30
31
32
void setup() {
  // initialize serial and wait for serial monitor to be opened:
  Serial.begin(9600);
  while (!Serial);
 
  // set LED pin to output mode:
  pinMode(ledPin, OUTPUT);
 
  // begin initialization:
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");
    while (true);
  }
 
  // set advertised local name and service UUID:
  BLE.setLocalName("LED");
  BLE.setAdvertisedService(ledService);
 
  // add the characteristic to the service
  ledService.addCharacteristic(switchCharacteristic);
 
  // add service:
  BLE.addService(ledService);
 
  // set the initial value for the characteristic:
  switchCharacteristic.writeValue(0);
 
  // start advertising
  BLE.advertise();
 
  Serial.println("BLE LED Peripheral");
}

In the loop(), you wait for a central device to connect, and only take action if it does:

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
27
28
29
30
void loop() {
  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();
 
  // if a central is connected:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's MAC address:
    Serial.println(central.address());
 
    // while the central is still connected to peripheral:
    while (central.connected()) {
      // if the central device wrote to the characteristic,
      // use the value to control the LED:
      if (switchCharacteristic.written()) {
        if (switchCharacteristic.value()) {   // any value other than 0
          Serial.println("LED on");
          digitalWrite(ledPin, HIGH);         // will turn the LED on
        } else {                              // a 0 value
          Serial.println("LED off");
          digitalWrite(ledPin, LOW);          // will turn the LED off
        }
      }
    }
 
    // when the central disconnects, print it out:
    Serial.print("Disconnected from central: ");
    Serial.println(central.address());
  }
}

Upload this to your board, then scan for it with a BLE central scanner like BlueSee or LightBlue. When you find it, connect and try to open the characteristic. Then try writing 1 and 0 to it. You should see the LED going on and off.

The general pattern of this sketch is similar for other Bluetooth LE sketches with this library; check out the other examples and you’ll see. Generally, you wait for a central to connect, then all action takes place after that. If the central is driving the action, then your Arduino sketch waits for characteristics to be written to. If your central app is waiting for action from the Arduino, then you’ll write to characteristics in your Arduino sketch, and the central app will see those changes and take action.

A Central App in p5.ble

The Chrome browser has a web bluetooth extension that enables web pages in Chrome to act as Bluetooth LE central devices. The p5.ble library is based on web-bluetooth, and compatible with p5.js. To get started quickly, disconnect your central app from your board if you’re still connected from the section above, and go to the p5.ble write one characteristic example. Click the connect button in that example, and you’ll get a popup scanner. When you see the LED peripheral, connect to it. Once you’re connected, try writing 0 or 1 to the LED. You should be able to control the LED.  The source code for this example is embedded in the example page. You can borrow from it to write your own custom BLE central p5.js sketch. You’ll need to include the p5.ble library in your index.html page as shown on the quickstart page.

In the write one characteristic sketch, you can see the pattern of activity for a central device described above:

Scan for peripherals is handled when you click the Connect button. The scan is filtered to look only for devices with the desired service UUID.

Connect to a given peripheral is handled by the connectToBle function. It connects to a device and the desired service, then runs the gotCharacteristics function as a callback to query for characteristics.

When you click the write button, the writeToBLE function writes to the characteristic to which it’s connected.

Reading and Writing Sensors in p5.ble

The Read From One Characteristic example shows how to read from a peripheral device that’s outputting a changing sensor value. Similarly, the Start and Stop Notifications example shows how to subscribe to a peripheral’s characteristic so as to get notification when it changes. Try these out along with their associated Arduino sketches to get an understanding of how to get sensor data via Bluetooth LE.

You can set up multiple characteristics in a given service, and often this is the best way to handle things. For example, if you were using the built-in accelerometer on the Nano 33 IoT, you might have three characteristics for x, y, and z acceleration all in a single accelerometer service.

Characteristic Data Types

When you initialize your peripheral’s characteristics in your Arduino sketch, you set the data type for the characteristic. Just as there are byte, bool, int, char, String and float data types, there are BLEByteCharacteristic, BLEBoolCharacteristic, BLEIntCharacteristic, BLECharCharacteristic, BLEStringCharacteristic, and BLEFloatCharacteristic data types in the ArduinoBLE library. You should pick the type appropriate for what you’re using it to do. An analog sensor might want an int, for example. A sensor value that you’ve converted to a floating point value like voltage or acceleration might want a float.

Similarly, you can read the characteristics in p5.ble differently when you know what type they might be.  You’ve got  unit8, uint16 or uint32, int8, int16, int32, float32, float64, and string. When you read, you choose the type like so:

1
myBLE.read(myCharacteristic, 'string', gotValue);

You need to match the type you read with on the central side to the type you sent with on the peripheral side. If you’re not sure what type is correct, set up your Arduino sketch to send a constant value using a type you know. Then read  it in p5.ble using the different types until the value you receive matches the value you’re sending. Make sure to test the limits of your data type. For example, in Arduino, an int can store a 16-bit value from -32,768 to 32,767 on an Uno, or a 32-bit value on a Nano 33 IoT or MKR board, from -2,147,483,648 to 2,147,483,647. A 16-bit int would be int16 in p5.ble, and a 32-bit int would be an int32. If you’re using unsigned ints on the Arduino side, then your ranges are different. A 16-bit unsigned int ranges from 0 to 65,535, and a 32-bit unsigned int goes from 0 to 4,294,967,295.

Further Reading

The ArduinoBLE reference is useful if you want to know all the commands that the library can offer. Likewise, the p5.ble reference is a valuable read as well.

The ArduinoBLE library supports both peripheral and central modes on the Nano33’s and the MKR1010. Here’s a pair of sketches that shows how to connect from a central to a peripheral.

Using a Programming Editor

There are many text-only programming editors on the market, designed to be used with different programming environments. When you program using many different environments, it can be helpful to have one editor that you use for all of them, so your editing interface is consistent.

Organizing Projects

Most programming editors organize files in workspaces. A workspace is basically a folder with all your project’s files in it. So to get started, either open a folder or create a new folder, then make new files in it. For example, if you have an Arduino sketch called mySketch, it consists of a folder called mySketch and a file in it called mySketch.ino. A p5.js project is usually a folder called myProject with a few files in it: sketch.js, index.js, and the p5.js libraries.

Extensions or Plugins

Programming editors usually include a way to add extensions or plugins so that you can automate common tasks, use different programming languages easily, and so forth. For example, Visual Studio Code includes two useful plugins for physical computing, the Arduino plugin, which lets you compile Arduino projects and the Live Share plugin, which lets you share code live across a network.

Visual Studio Code

Microsoft Visual Studio Code is an open source programming editor from Microsoft that’s good for many toolchains at ITP: Arduino, p5.js, node.js, Python, and more. It’s designed for keyboard-only use, through the command palette, which can execute any command in the application using keyboard alone.

There are a few main areas of the VS Code application: the editor area, the file explorer, the consoles.

The explorer (command-shift-e) is where you look for files and folders within your workspace. There’s also a tab in the explorer called Outline where you can see a list of the functions in the open code window.

The editor (command-1, 2, 3, etc) is where you edit code. There can be multiple editors open in the same workspace, if you choose to do so.

The panel (control-`) are where you see the output console, the problems console, the command line terminal, serial terminals, and the debug console.

The command palette (command-shift-P) can execute any command in the application using keyboard alone. You can filter it by starting to type the name of the command you want. The Escape key dismisses the command palette.

Note that everything in VS Code is a file or a directory. Even the application Preferences are stored in a giant JSON file, and when you open preferences from the command palette, you can see this JSON file, or an abstracted HTML-style representation of it.

Keyboard Shortcuts

There are many keyboard shortcuts in VS Code, because it’s designed to be used without a mouse. If you can’t remember them all, there is a guide in the program. Type command-K-S to open it in the editor.

Shortcuts in command palette

  • Backspace, then type file name – will open any file in workspace
  • Colon, then type number – goes to line number in current editor window
  • > then type command. This is the default when you open the command palette. When you type any word, it will filter the command list to find matching commands. Then use up and down arrows to navigate the filtered list.

Getting to Files

Command-shift E shifts focus to the File explorer. The up and down arrows will move through the file names VoiceOver will read out the names. Once you know the file names, use either the file palette (command-P) or the command line terminal (described below) to open the file.

Command-P opens the File palette This is a variation on the command palette that opens files. It operates like the command palette. Type the filename to filter the available files in your current workspace.

Shortcuts in the Editor

To get to any line in the editor, open the command palette (command-shift-P), then backspace and type colon-line number to jump to any line. If you’re already in the editor, control-G-line number will do the same thing.

Working in the Panel

The Panel area contains the command line Terminal, the Output Console, the Problems Console, and the Debug Console.

Type control ` to open the terminal. This gets you to a terminal with your operating system’s command line interface in it. Here you can do all the things you can normally do on the command line: list files (ls), change directories (cd), and so forth. From the terminal,, you can also type “code <filename>” to open any file in the current directory into the editor.

Note that Control-G doesn’t jump you to a line number when you are in the terminal. Type control ` to close the terminal first.

Type control-shift-U to get to the Output console. This is where you’ll read the output of any commands run by a compiler, for example the Arduino compiler.

Extensions

Visual Studio Code lets you extend the application with additional modules downloaded from their online repository. Type control-shift-X to enter the Extensions browser. The Extensions browser works like the command palette: type a word to filter the list then use the up and down arrows to navigate the list.

Two useful plugins for physical computing are the the Arduino plugin by Microsoft and the Live Share plugin. If you have the Arduino IDE installed on your machine, the Arduino Plugin for VS Code  lets you compile and upload for all the boards you have installed.The VS Live Share Plugin allows you to share code live with other users over a network in real time. Live share takes a few steps to get it set up, including a couple of trips to the browser to enable access through your GitHub account. The details are explained in the details page for the extension. Once it’s working, it’s a handy tool for sharing code live.

A Note on Notifications

Some actions or extensions pop up notification alerts over your application. These are called toasts in UX parlance, for reasons only the Secret UX Masters of the Universe understand. You can view all current notifications from the command palette: command-shift-P, then type notifications show. You can scroll through them with arrow keys when they are open. To dismiss them, command-shift-P, then type Notifications clear.

Using the Arduino Command Line Interface

For users who prefer to use their own text editor, there is a command line interface version of the Arduino. “arduino-cli is an all-in-one solution that provides builder, boards/library manager, uploader, discovery and many other tools needed to use any Arduino compatible board and platforms.” It allows you to work with your favorite text editor, and to program in a text-only mode. The gitHub repository for the project includes a pretty good getting started guide to the arduino-cli on the main page, but there are a few extra steps you may want to take to set things up for more convenience. These instructions are written for MacOS, but should work on any POSIX operating system, and might work on Windows with the Linux Subsystem for Windows or with Cygwin, an older collection of Linux command line tools for Windows

Note that as of Sept. 2018, the arduino-cli is still in alpha, meaning that features are still subject to change.

Download the Application

From the gitHub repository, download the latest stable release of arduino-cli. Unzip the resulting download and move the unzipped application to your Applications directory (Linux users may prefer to move it to /usr/bin or /usr/local/bin). Then open the Terminal app (MacOS) or a shell window (Linux) to set things up.

Configure Your Command Line Environment

For those new to command line interfaces, there are a few terms that are helpful to know as well. The command line interface for MacOS is called a shell, specifically the bash shell.There are other shell environments for Linux and Unix (sometimes you’ll hear these collectively referred to as POSIX systems; here’s a longer introduction to command line interfaces in POSIX systems), but the bash shell is one of the most common. The parameters of the shell are called environment variables, and for the bash shell on MacOS, they are stored in a file called .bash_profile. They’re different for each user, so the .bash_profile file is kept in each user’s home directory. The path to this directory is  /Users/username on MacOS, or /home/username on Linux. The shortcut for the home directory is ~. For example, ~Documents/ is the path to your Documents folder. When you’re on the command line, you can always get back to the home directory by typing cd and then pressing enter. You can check what directory you are in by typing pwd (for print working directory)

It’s helpful to add a few modifications to your command line environment to make arduino-cli easier to work with. First, open .bash_profile in your favorite text editor. The dot at the beginning of the filename means this is hidden file, so it won’t show up when you list files. To see it using the command line list command (ls), type ls -a and you’ll see all the hidden files. To open any file in your text editor, type

open -a editorName filename

Replace editorName with the name of your editor, For example, to open the .bash profile in XCode, type open -a editorName .bash_profile.

That open trick is so useful that you’ll want to add a shortcut to your profile. At the end of the file, add the following line:

alias xcode="open -a xcode"

Change the alias name and the editor name as you see fit. Now you can open your editor from the command line. This is useful for other development activities, like working in node.js, p5.js, or Python. Next you need to add the Applications directory to your PATH environment variable so that the shell knows where to find it. Add a new line to the file as follows:

export PATH=${PATH}:/Applications

Arduino-cli does not include the built-in examples that come with the original Arduino graphic IDE. If you’ve installed the original IDE, you have them, but you might want to create an shortcut in your Arduino sketches directory to get to them. Here are the paths to the examples for MacOS, Windows, and Linux:

MacOS: (assuming you installed the Arduino IDE in your applications folder already): /Applications/Arduino.app/Contents/Java/examples

Windows 10: C:\Users\”USERNAME”\AppData\Local\Arduino15\packages\arduino

Linux: ~arduino-1.8.x/examples

To create the shortcut, change directories to your sketch directory and type:

ln -s /Applications/Arduino.app/Contents/Java/examples

and you’ll then have be able to get to them directly from the sketches directory. On Linux or Windows, change the path accordingly.

Finally, if you find the default shell prompt too long, you can add a custom shell prompt. For example, the default MacOS shell prompt is computer-name:directory username$. This appears after you run every process on the command line, before you get to run a new process. To shorten the command prompt to a single $, add this line to your .bash profile:

export PS1="$ "

The $ prompt is common enough that you’ll usually see it at the beginning of any command line instructions in tutorials. If you see it, type what follows it at your command prompt.

To put these changes into effect, save your .bash_profile file, type exit on the command line to close your command line session, then re-open a new shell window. In the new window, you should see that the command prompt is now just a $ symbol. To test your editor shortcut, type

$ touch foo.txt

This will create a new file called foo.txt in your current directory. Then type

$ xcode foo.txt

This should launch XCode (or whatever editor you chose) and open the file foo.txt.

Using arduino-cli

To use arduino-cli on the command line, type its name followed by whatever parameters you want to use. If you type arduino-cli with no parameters, you’ll get a list of the commands available. The application that you downloaded is probably not just called arduino-cli, however. It’s probably something longer, like arduino-cli-0.2.2-alpha.preview-osx. Fortunately, you don’t have to type the whole name. The shell will auto-complete any command you type if it can when you press the tab key. If you type ard then press tab, it should autocomplete the name of the program. In this tutorial, the application name will  be shortened to arduino-cli.

To create a new sketch, type:

$ arduino-cli sketch new SketchName

This will create a new sketch in the default Arduino sketch directory. On MacOS, this directory is ~Documents/Arduino/, so the sketch would be in ~Documents/Arduino/. To get to the directory to work on this new sketch, type:

$ cd ~Documents/Arduino/SketchName

Now that you’ve got your command line environment set up, the getting started with arduino-cli guide will take you through the steps of creating a new sketch, configuring the environment, and uploading it. Below is a summary of the commands you’ll use the most.

New Sketch: arduino-cli sketch new mySketch – creates a new sketch called mySketch in the default Arduino sketch directory

List Available boards: arduino-cli board list – list all boards attached to the computer. This is equivalent to getting the port list in the Arduino graphic IDE.

List All Known Boards: arduino-cli board listall – list all boards in your boards manager. This is equivalent to getting the boards list in the graphic IDE. This produces a long list, so if you wanted to search it for a particular subset, you might use the POSIX grep command like so:

$ arduino-cli board listall | grep mkr

The preceding command would list all known boards with the substring mkr in their name.

Compile: arduino-cli compile -b board:name path/to/sketch – compile the sketch for the given board name. This is equivalent to the Verify command in the graphic IDE.  You don’t need the sketch path if you’re already in the sketch directory. For example, to compile the sketch in the current directory for the Uno:

$ arduino-cli compile  -b arduino:avr:uno

Or for the MKRZero:

$ arduino-cli compile  -b arduino:samd:mkrzero

Attach Board: arduino-cli board attach board:name – attaches a specific board name to a sketch. This is equivalent to choosing a board in the graphic IDE. If you do this when you first set up a new sketch, you won’t need to give a board name every time every time you compile, you can just type

$ arduino-cli compile

Upload: arduino-cli upload -p portname – upload compiled sketch to the portname given. This is equivalent to uploading in the IDE. Note that this command does not re-compile before uploading.  For example:

$ arduino-cli upload -p /dev/cu.usbmodem1411

Find a core: arduino-cli core search coreName – searches for known processor architectures. You can search by processor type, like AVR or M0, or you can search by board name, like Mega or MKR. For example:

$ arduino-cli core search mkr

Install a core: arduino-cli core install coreName – installs a core once you find it. ou must use the full core name, though. For example the core search for MKR above resulted in a core called arduino:samd. To install it, type:

$ arduino-cli core install arduino:samd

Find a library: arduino-cli lib search libraryName – searches the Arduino library manager index for libraries containing the word libraryName. For example, the following will list all MQTT-related libraries in the index:

$arduino-cli lib search mqtt

Install a library: arduino-cli lib install libraryName – install a library that’s listed in the Arduino library manager index. For example:

$ arduino-cli lib install WiFiNINA

or for libraries with multiple word names, use double quotes around the name like so:

$ arduino-cli lib install "Arduino Low Power"

For more information, see the arduino-cli guide. Check back frequently for changing features until it is out of alpha.

Using the Serial Port

On MacOS and Linux, the command line allows you to read input from a serial port just as you would from a file, using the cat command. cat /dev/cu.usbmodemXXXX will open an Arduino’s serial port, if no other application has it open already. control-C will close it.

Lab: Arduino and p5.js using a Raspberry Pi

For some applications, you only need a computer with an operating system in order to connect a serial device like an Arduino or other microcontroller with a browser-based multimedia application like p5.js. This page introduces how to do it using node.js, p5.serialserver, and a Raspberry Pi.

Introduction

For some applications, you only need a computer with an operating system in order to connect a serial device like an Arduino or other microcontroller with a browser-based multimedia application like p5.js. Perhaps you’re planning to run the sketch on a mobile device like an iPhone or Android device, but you need it to get data from sensors on your Arduino, or to be able to control motors or other outputs on the microcontroller. For these applications, an embedded Linux processor like a Raspberry Pi or BeagleBone can do the job well. By running an HTTP server and the p5.serialserver application from the Linux command line, you can make this happen. This page introduces how to do it using node.js, p5.serialserver, and a Raspberry Pi.

To get the most out of this tutorial, you should know what a microcontroller is and how to program microcontrollers. You should also understand asynchronous serial communication between microcontrollers and personal computers. You should also understand the basics of command line interfaces. It’s helpful to know a bit about the Raspberry Pi as well, and p5.js. If you’re looking for a Raspberry Pi setup that works on the networks at ITP, try this one. Finally, this tutorial on serial communication using node.js will give you a decent intro to node.js.

System Diagram

The system for this tutorial is as follows: your microcontroller is running a sketch that communicates using asynchronous serial communication, just like many of  the other serial tutorials on this site. It’s connected to an embedded Linux processor (a Raspberry Pi, in this case), which is running p5.serialserver, a command-line version of the p5.serialcontrol app used in the other p5.js serial tutorials on the site. The Pi is also running a Python-based HTTP server, which will serve your p5.js sketch and HTML page to any browser on the same network. The p5.js sketch uses the p5.serialport library to communicate back to p5.serialserver on the Linux processor in order to read from or write to the microcontroller’s serial port. The diagram below shows the system(Figure 1):

This is a system diagram that depicts the system described in the paragraph above. The browser device is on the left. The Raspberry Pi is in the center, and the Arduino is on the right.
Figure 1. Raspberry Pi serving p5.js files and running p5.serialserver

Node.js

The JavaScript programming language is mainly used to add interactivity to web pages. All modern browsers include a JavaScript interpreter, which allows the browser to run JavaScript code that’s embedded in a web page. Google’s JavaScript engine is called v8, and it’s available under an open source license. Node.js wraps the v8 engine up in an application programming interface that can run on personal computers and servers. On your personal computer, you run it through the command line interface.

Node was originally designed as a tool for writing server programs, but it can do much more. It has a library management system called node package manager or npm that allows you to extend its functionality in many directions. There is also an online registry of node libraries, npmjs.org. You can download libraries from this registry directly using npm. Below you’ll see npm used to add both serial communication functionality and a simple server programming library to node.

Install Linux, Node.js, and p5.serialserver

To get started, you’ll need to set up a Raspberry Pi for command line access. Follow this tutorial on how to set up the Rasberry Pi with th latest Raspbian distribution of Linux, with node.js and a firewall installed.

If you’ve never used a command line interface, check out this tutorial on the Unix/Linux command line interface.  From here on out, you’ll see the command prompt indicated like this:

yourlogin@yourcomputer ~$

Any commands you need to type will follow the $ symbol. The actual command prompt will vary depending on your operating system. On Windows, it’s typically this: >. On most Unix and Linux systems, including OSX, it’s $. Since this tutorial is only for Linux, look for the $.

Post-Install Checklist

Once you’ve installed everything from the previous tutorial, run the following commands on the command line of the Pi to make sure everything you need is installed. If you get the answers below, you’re ready to move on.

To check the version of the Raspbian distribution of Linux that you’re using, type:

$ lsb_release -a

You should get something like this or later:

No LSB modules are available.
Distributor ID: Raspbian
Description: Raspbian GNU/Linux 9.1 (stretch)
Release: 9.1
Codename: stretch

To get the versions of node.js,  npm, and iptables that you’re running, type (and get the replies below, or later):

$ node -v
v6.9.5

$ npm -v
3.10.10

$ sudo iptables --version
iptables v1.6.0

To check that your iptables firewall configuration is correct, type:

$ sudo iptables -S

You should get something like this, though your IP addresses for your router and gateway on lines 9 and 10 might be different:

-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -i wlan0 -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -i wlan0 -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -i wlan0 -p tcp -m tcp --dport 8080 -j ACCEPT
-A INPUT -i wlan0 -p tcp -m tcp --dport 8081 -j ACCEPT
-A INPUT -s 192.168.0.1/32 -i tcp -p tcp -m tcp --dport 22 -j DROP
-A INPUT -s 192.168.0.0/24 -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -j REJECT --reject-with icmp-port-unreachable

Get your Pi’s IP Address

It’s easy enough to run a simple HTTP server on your Pi, and then to use it to serve an HTML page with a p5.js sketch to any browser. First you’ll need to know your Pi’s IP address. You can get it like so:

$ sudo ifconfig wlan0 | grep inet

You’re using the ifconfig command to get the data on the wlan0 network interface. That’s your Pi’s WiFi radio. Then you’re passing the output from ifconfig to the grep program using a pipe (the | character). grep searches through the results for any line beginning with the string ‘inet’. Your result will look something like this:

inet 192.168.0.11 netmask 255.255.255.0 broadcast 192.168.0.255
inet6 2604:2000:c58a:da00:3b21:bed1:e1bb:8c3c prefixlen 64 scopeid 0x0<global>
inet6 fe80::52a3:f22:847f:7beb prefixlen 64 scopeid 0x20<link>

Your numbers will vary, but the one you want will be the four decimal numbers  following the first ‘inet’; 192.168.0.11 in the example above, but yours will be different depending on your network. That’s your IP address. Remember it, you’ll use it in a moment to browse files on your Pi.

Make a Simple Web Server on your Pi

To get your p5.js sketch and HTML page from the Pi, you’ll need to run a web server program. The installed version of the Python programming language includes one already. You need some content to serve. Make a p5.js project. You can download the p5.js example project. You can create all the files yourself, or you can download the files automatically using a command line tool called p5-manager. To install it, type:

$ sudo npm install -g p5-manager

This install might take awhile (45-70 minutes on a Pi Zero W), so take a break while it installs. When it’s installed, you can create a new p5 project anywhere like so:

$ p5 g -b myProject

This will generate (g -b stands for generate bundle) a directory called myProject containing all the files you need for a p5.js project. Change directories into your new project, then update your project’s p5.js files to the latest versions like so:

$ cd myProject
$ p5 update
p5-manager version 0.4.1
The latest p5.js release is version 0.5.16

The sketch.js file in this project doesn’t do anything, so you might want to edit it. You can edit it using the command line editor called nano like so:

$ nano sketch.js

You’ll get a full edit window like the one below, and you can move around the window with the arrow keys. Add the following lines to sketch.js’ draw() function:

1
2
3
4
5
function draw() {
    background('#3399FF');
    fill('#DDFFFF');
    ellipse(width / 2, height / 2, 50, 50);
}

To save the file, type control-X, then Y to confirm. The nano editor will quit and you’ll be back on the command line. Now run Python’s simpleHTTPServer like so:

sudo python -m SimpleHTTPServer 8080

You should get a reply like this:


Serving HTTP on 0.0.0.0 port 8080 ...

Now go to a web browser and enter your IP address from above like so: http://192.168.0.11:8080. You should see a page with a p5.js sketch in it like this(Figure2):

Screenshot of a p5.js sketch running in a browser. a light blue ball on a brilliant blue field fills the canvas of the sketch.
Figure 2. The p5.js serial sketch p5.js sketch running in a browser

Congratulations, your Pi is now a web server! Now you’re ready to add the serial connection.  Type control-C to quit the SimpleHTTPServer.

Add node serialport and p5.serialserver

The next pieces to add are node’s p5.serialserver, which depends on the  node serialport library. The serialport library has to be downloaded and compiled natively for your processor. As the node serialport documentation explains, you’ll need to do it as shown here. You’re enabling unsafe permissions, and building from the source code.The unsafe permissions are needed to give your user account permission to access the /dev directory, in which serial port files live. You can do this all at once, by installing p5.serialserver with the same options, like so:

$ sudo npm install -g p5.serialserver --unsafe-perm --build-from-source

This install will take a long time, so again, take a break (60-90 minutes on a Pi Zero). Once it’s successfully installed, you’ve got all the pieces you need to serve serial-enabled p5.js sketches from your Pi, supplying the serial connection via the Pi’s serial ports.

The Raspberry Pi Serial Ports

There are a couple of ways you can access a serial port on the Pi. The GPIO port for the Pi includes a serial port on pins GPIO14 and GPIO15 (Figure 3.). This port is known as /dev/ttyS0 to the operating system.

The Raspberry Pi's GPIO pin diagram
The Raspberry Pi’s GPIO pin diagram.

Table 1 below details the pin functions

Left SideRight Side
3.3V Power5V Power
GPIO 2 (SDA)5V Power
GPIO 3 (SCL)Ground
GPIO 4 (GPCLK0)GPIO 14 (TX)
GroundGPIO 15 (RX)
GPIO 17GPIO 18 (PWM0)
GPIO 27Ground
GPIO 22GPIO 23
3.3V PowerGPIO 24
GPIO 10 (SPI SDO)Ground
GPIO 9 (SPI SDI)GPIO 25
GPIO 11 (SPI SCLK)GPIO 8 (CE0)
GroundGPIO 7 (CE1)
GPIO 0 (ID_SD)GPIO 1 (ID_SC)
GPIO 5Ground
GPIO 6GPIO 12 (PWM0)
GPIO 13 (PWM1)Ground
GPIO 19 (SPI SDI)GPIO 16
GPIO 26GPIO 20 (SPI SDO)
GroundGPIO 21 (SPI SCLK)

If you’re connected to your Pi through a serial terminal connection, you’re going to have to give that up to talk to your microcontroller. To do that, first log out from the serial terminal and log in via ssh over a network connection. Once you’re logged in over the network, launch raspi-config:

$ sudo raspi-config

Pick option 5, interfacing options, and enable the serial port but disable the serial terminal. Save your choice, exit raspi-config, and restart your Pi:

$ sudo reboot

To get a list of your Pi’s ports, type the following:

$ ls /dev/tty*

You’ll get a long list, most of which are not your ports, and you’ll see the TTYS0 port toward the end. If you have an Arduino or other USB-to-serial device attached, you might see other ports marked TTYUSB0 as well. To determine which are the USB serial devices, run the ls command, then unplug the device, then run it again to see what disappears.

To connect a microcontroller to the GPIO serial port, attach the TX from your controller to the RX of the GPIO port and vice versa. Attach the ground of the GPIO port to the ground of your controller as well. The diagram below(Figure 4) shows the Pi connected to an Uno.

Pi to Uno configuration. Pi is connected to uno through ground pins, as well as, GP 14 (TX) to RX and GP 15(RX) to TX
Figure 4. Raspberry Pi connected serially to an Arduino Uno.

If you’re connecting to a Nano 33, MKRZero, MKR1000, Feather M0 Leonardo, Micro, or any of the boards based on the ARM M0 or ATMega 32U4, connect RX to TX and vice versa, but be aware that the RX and TX pins of those boards are addressed using Serial1 instead of Serial. For example, you’d call Serial1.begin(), Serial1.println(), and so forth.

You can also add serial ports as you would on a laptop, by plugging in a device that is compatible with a USB serial COM driver, like an Arduino. If you’re using a Pi Zero, you’ll need to use a USB-on-the-go adapter to connect to the Zero’s USB port.

The compatibility of your device will depend on the compatibility of the device’sUSB-to-serial chip. The official Arduino models comply with the USB standard serial COM protocol, and require no drivers. Third party and derivative models will vary depending on the hardware. If you want to avoid any driver issues and additional USB cables, use the GPIO serial port.

To test whether you’ve got control over the serial port, put a sketch on your microcontroller that sends out serial messages, like the AnalogReadSerial sketch in the Arduino Basics examples. Connect the controller to your Pi, and then use the cat command to read from the serial port like so:

$ cat /dev/ttyS0

You should see the serial data coming in just as you might in the Arduino Serial Monitor. If you do, you’re ready to build a full serial application. To quit cat, type control-C.

Making a Dynamic p5.serialport Sketch

For background on p5.serialport, see the lab on serial input to p5.js.  To start with, you’ll need a microcontroller sending out serial data. Attach a potentiometer to pin A0 of your ArduinoStart, then with this basic handshaking sketch. Using handshaking (also sometimes called call-and-response) keeps the Pi’s serial buffer from getting full:

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); // initialize serial communications
  while (!Serial.available()) { // until the server responds,
    Serial.println("Hello");    // send a hello message
    delay(500);                 // every half second
  }
}
 
void loop() {
  // if there are incoming bytes:
  if (Serial.available()) {
    // read incoming byte:
    int input = Serial.read();
    // read the input on analog pin 0:
    int sensorValue = analogRead(A0);
    // print out the value you read:
    Serial.println(sensorValue);
    delay(1); // delay in between reads for stability
  }
}
Schematic view of a potentiometer. First leg of the potentiometer is connected to +5 volts. The second leg connected to analog in 0 of the Arduino. The third leg is connected to ground.
Figure 5. Schematic view of a potentiometer connected to analog in 0 of the Arduino
Breadboard view of a potentiometer. First leg of the potentiometer is connected to +5 volts. The second leg connected to analog in 0 of the Arduino. The third leg is connected to ground.
Figure 6. Breadboard view of a potentiometer connected to analog in 0 of an Arduino

On the command line, create a new p5 sketch like so, then change directories to get into the sketch, then update the libraries:

$ p5 g -b serialSketch
$ cd serialSketch
$ p5 update

You’ll need the p5.serialport library to your sketch as well. You can copy it into the libraries directory like so:

$ sudo curl https://raw.githubusercontent.com/vanevery/p5.serialport/master/lib/p5.serialport.js --output libraries/p5.serialport.js

Once you’ve done that, edit the index.html file using nano as you did for the sketch.js above, and add a script include for libraries/p5.serialport.js in the HTML document’s head, before the script include for the sketch.js.

Save the file, then edit the sketch.js as follows:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var serial; // instance of the serialport library
var portName = '/dev/ttyS0'; // fill in your serial port name here
 
var circleSize = 50;
 
function setup() {
  createCanvas(320, 240);
  // initalize serialport library to connect to p5.serialserver on the host:
  serial = new p5.SerialPort(document.location.hostname);
  // set callback functions for list and data events:
  serial.on('list', printList);
  serial.on('data', serialEvent);
  // open the serial port:
  serial.open(portName);
}
 
function draw() {
  background('#3399FF');
  fill('#DDFFFF');
  // draw a circle at the middle of the screen:
  ellipse(width / 2, height / 2, circleSize, circleSize);
}
 
function serialEvent() {
  // read a line of text in from the serial port:
  var data = serial.readLine();
  console.log(data);
  // if you've got a valid line, convert it to a number:
  if (data.length > 0) {
    circleSize = int(data) / 4;
  }
  // send a byte to the microcontroller
  // to prompt it to respond with another reading:
  serial.write("x");
}
 
function printList(portList) {
  // portList is an array of serial port names:
  for (var i = 0; i < portList.length; i++) {
    console.log(i + ' ' + portList[i]);
  }
}

Note that when you’re calling new SerialPort();, you’re including a parameter, document.location.hostname. This is the address from which your sketch was served, in this case, your Pi. You can enter a specific IP address or domain name if you prefer, but document.location.hostname will always return the address of the server. This is the key to making your p5.serialport sketch dynamic, so it’s not locked to localhost.

At this point you have enough of a sketch to test the system. When loaded in a browser, this sketch should look like the one above, and should print the list of serial ports to the JavaScript console.

Run python’s SimpleHTTPServer a little differently this time:

$ python -m SimpleHTTPServer 8080 &

The & makes python return control to the command line so you can do other commands while it’s running. Hit the return key to get a command prompt again, then type:

$ p5serial

You should get this response:

p5.serialserver is running

Do as you did above, and open the sketch in a browser again. This time, open your JavaScript console and you should see the following output:

ws://192.168.0.12:8081
p5.serialport.js:83 opened socket
sketch.js:20 0 /dev/ttyAMA0
sketch.js:20 1 /dev/ttyS0

You should also see the serial data coming in from the microcontroller, and if you turn the potentiometer, you should see the circle size change. If you get this, do a happy dance. You’ve got a working p5.serialserver system working.

To quit p5.serialserver, type control-C. To stop the SimpleHTTPServer, type the following to get a list of the running processes:

$ ps
PID TTY TIME CMD
785 pts/0 00:00:02 bash
8186 pts/0 00:00:00 python
8203 pts/0 00:00:00 ps

This is a list of all running user-started processes and their process numbers. The python SimpleHTTPServer is number 8186 above. To stop it, type:

$ kill 8186

with your own process number, and python will stop. In the future, you can run p5serial this way as well, to get the command line control back while it’s still running.

Once you’ve got this much working, all the same dynamics of serial communication that apply on your desktop also apply on the Pi. Only one program at a time can control the port. Both sides need to agree on electrical connections, timing, and data format. Handshaking can help manage traffic flow. The troubleshooting techniques you used in desktop serial apply here also.

The Pi isn’t perfect as a multimedia host for all projects. It’s not as good on graphics as even the most basic desktop computer, for example. It’s great as a network connector like this, though, and it’s great to serve browser-based content that needs to connect to a serial port.

For more info:

Lab: Serial output from P5.js Using the p5.serialport Library

In this lab you’ll learn how to send data from p5.js to a microcontroller using asynchronous serial communication.

In this lab you’ll learn how to send data from p5.js to a microcontroller using asynchronous serial communication.

Overview

When you use the p5.serialport library for P5.js, it communicates with a webSocket server in the P5.js IDE to give you access to serial devices attached to your computer. This lab shows you how to use P5 to control a microcontroller using asynchronous serial communication.

To get the most out of this tutorial, you should know what a microcontroller is and how to program them. You should also understand asynchronous serial communication between microcontrollers and personal computers. You should also understand the basics of P5.js, and should have tried the Serial Input to P5.js lab.

Things You’ll Need

For this lab, you’ll need the hardware below,

For this lab, you’ll need the hardware below, and you’ll need the same software setup as the Serial Input to P5.js lab: You’ll create a p5.js sketch. You’ll also use the p5.serialport library and the P5.serialcontrol app. You can use the p5.js web editor or your favorite text editor for this (the Visual Studio Code editor or the  Atom text editor work well).

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. Microcontroller.  Arduino Nano 33 IoT
LEDs. Shown here are four LEDs. The one on the right is an RGB LED. You can tell this because it has four legs, while the others have only two legs.
Figure 2. LEDs. Shown here are four LEDs. The one on the right is an RGB LED. You can tell this because it has four legs, while the others have only two legs.
Resistors. Shown here are 220-ohm resistors. You can tell this because they have two red and one brown band, followed by a gold band.
Figure 3. Resistors. Shown here are 220-ohm resistors. You can tell this because they have two red and one brown band, followed by a gold band.
An 8 ohm speaker with 2 wires solder to the speakers leads
Figure 4. An 8 ohm speaker (optional).This is a good alternate to the LED if you prefer audible output.

Prepare the breadboard

Connect power and ground on the breadboard to power and ground from the microcontroller. On the Arduino UNo, use the 5V and any of the ground connections. On the Nano, use 3.3V and the ground connections:

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 5. An Arduino Uno on the left connected to a solderless breadboard, right.
Arduino Nano on a breadboard.
Figure 6. Breadboard view of an Arduino Nano mounted on a breadboard.

The +3.3 volts and ground pins of the Arduino Nano are connected by red and black wires(Figure 6), 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.Figure 5. Breadboard view of an Arduino Nano connected to a breadboard. 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.

Made with Fritzing

Add an LED

Connect the LED and resistor to digital I/O pin 11 of the module(Figure 7-8). Alternately, you can replace the 220-ohm LED with a speaker (Figure 9-10). You’ll find code below that uses tones instead of LEDs where appropriate. For more on how to do that, see the Tone Output lab:

Schematic view of an Arduino connected to an LED. Digital pin 5 is connected to a 22-ohm resistor. The other side of the resistor is connected to the anode (long leg) of an LED. The cathode of the LED is connected to ground.
Figure 7. Schematic view of an Arduino connected to an LED.
Breadboard view of an Arduino connected to an LED. 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 blue wire connects Digital to a 22-ohm resistor that straddles the center divide of the breadboard in row 17. The other side of the resistor is connected to the anode (long leg) of an LED. The LED is mounted in rowsd 16 and 17 of the right side of the center section of the board. a black wire connects the cathode's row, row 16, to the ground bus on the right side of the board.
Figure 8. Breadboard view of an Arduino connected to an LED.

Breadboard view of an LED connected to digital pin 5 of an Arduino Nano.
Figure 9. Breadboard view of an LED connected to digital pin 5 of an Arduino Nano.

Figure 9 shows a breadboard view of an LED connected to digital pin 5 of an Arduino Nano. The Nano straddles the center of the breadboard in the first fifteen rows. The Nano’s voltage pin (physical pin 2) connects to the board’s voltage bus, and the Nano’s ground pin (physical pin 14) connects to the board’s ground bus. The LED is in the right center of the board, with its anode in one row and the cathode in the next. A 220-ohm resistor connects the LED’s anode to a wire connecting to digital pin 5. The LED’s cathode is connected to the ground bus.

Breadboard view of an Arduino Nano connected to two force sensing resistors (FSRs) and a speaker. The Nano’s 3.3 Volts (physical pin 2) and ground (physical pin 14) are connected to the voltage and ground buses of the breadboard as usual. The red positive wire of the speaker is connected to digital pin 5 of the Arduino. The black ground wire of the speaker is connected to one leg of a 100 ohm resistor. The other leg of the resistor connects to ground.
Figure 10. Breadboard view of an Arduino Nano connected to a speaker to digital pin 5.

Figure 10 shows a breadboard view of an Arduino Nano connected to a speaker. The Nano’s ground (physical pin 14) is connected to the ground bus of the breadboard as usual. The red positive wire of the speaker is connected to digital pin 5 of the Arduino. The black ground wire of the speaker is connected to one leg of a 100 ohm resistor. The other leg of the resistor connects to ground.


Program the Microcontroller

Program your Arduino to read the analog input as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
void setup() {
  Serial.begin(9600);           // initialize serial communications
}
 
void loop() {
  if (Serial.available() > 0) { // if there's serial data available
    int inByte = Serial.read();   // read it
    Serial.write(inByte);         // send it back out as raw binary data
    analogWrite(5, inByte);       // use it to set the LED brightness
    // if you're using a speaker instead of an LED, uncomment line below  and comment out the previous line:
    //  tone(5, inByte*10);     // play tone on pin 5
  }
}

The P5.js serialport library

To communicate with your microcontroller serially, you’re going to use the P5.js serialport library and the p5.serialcontrol app. The P5.js serialport library can’t access your serial ports directly when a sketch is running in a browser because the browser doesn’t have direct access to the serial port. But it can communicate with another program on your computer that can exchange data with the serialport. p5.serialcontrol is  the app that connects your sketch, running in a browser, with the serial ports on your computer as shown in Figure 11.

Diagram of three rectangles connected by arrows. The rectangle on the right represents your p5.js sketch, running in a browser. Your sketch implements the p5.serialport library. Your sketch connects to p5 serial control, the middle rectangle, via a webSocket. The p5 serial control application runs on your laptop. It connects to a serial port on your computer and listens for webSocket connections from your p5.js sketch. It passes whatever comes in the serial port through to the webSocket and vice versa. The third rectangle is your Arduino, connected to p5 serial control via the serial port.
Figure 11. Diagram of the connection from the serial port to p5.js through p5.serialcontrol

Once you gain an understanding of serial communication, you can use any program that can connect with your computer’s serial ports to communicate with a microcontroller. Processing, Max/MSP, and OpenFrameworks are three other popular multimedia programming environments that can communicate via the serial ports. You can also do this with Unity, Unreal, or any other programming environment that can access the serial ports.

Install the P5.serialcontrol App

Download the latest version of the P5.serialcontrol application and save it in your Applications folder. When you run it, it will check serial ports on your machine. You don’t need to do anything with the app, just have it open. However, remember this most important fact:

Only one port at a time can access a serial port.

That means that when you want to reprogram your Arduino from the Arduino IDE, you need to quit p5.serialcontrol to do so. Then, reopen p5.serialcontrol when you’re done reprogramming the Arduino. You don’t need to quit the Arduino IDE each time, because it knows to release the serial port when it’s not programming. However, you do need to close the Serial Monitor in the Arduino IDE when you are using p5.serialcontrol.

You’ll need to know the name of your serial port to get started. If you’re not sure how to get this, see the Serial Input to P5.js lab for how to get a list of ports.

The P5.js Sketch

The sketch you’re going to write will control the microcontroller’s LED from P5.js. Dragging the mouse up and down the canvas will dim or brighten the LED, and typing 0 through 9 will set the LED’s brightness in increments from off (0) through almost full brightness (9). There’s an alternate sketch that will make changing tones if you prefer that instead of a changing LED. The sketch will also receive serial input from the microcontroller just as in the Serial Input to P5.js lab, so that you can see that the microcontroller is getting the same values you’re sending.

Program P5.js For Serial Communication

Make a P5.js sketch. If you’re using the p5.js web editor, make a new sketch. Click the Sketch Files tab, and then choose the index.html file. In the head of the document, look for this line:

Right after that line, add this line:

1
<script language="javascript" type="text/javascript" src="https://cdn.jsdelivr.net/npm/p5.serialserver@0.0.28/lib/p5.serialport.js"></script>

The setup of your sketch will initialize the P5.serialport library and define your callback functions for serial events. Program the global variables and setup() function as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var serial;          // variable to hold an instance of the serialport library
var portName = '/dev/cu.usbmodem1421'; // fill in your serial port name here
var inData;                            // for incoming serial data
var outByte = 0;                       // for outgoing data
 
function setup() {
  createCanvas(400, 300);          // make the canvas
  serial = new p5.SerialPort();    // make a new instance of the serialport library
  serial.on('data', serialEvent);  // callback for when new data arrives
  serial.on('error', serialError); // callback for errors
  serial.on('list', printList);       // set a callback function for the serialport list event
  serial.list();                   // list the serial ports
  
  serial.open(portName);           // open a serial port
}

You’re only using the ‘data’ and ‘error’ and ‘list’ callbacks this time, but you can add the other serial callbacks if you want them. You can also add a serialport select menu as you did in the Serial Input to P5.js  lab if you wish.

Program the serialEvent() function and serialError() function similarly to those in the previous lab. They read incoming data (serialEvent()) and report any errors (serialError()), as follows:

1
2
3
4
5
6
7
8
9
10
function serialEvent() {
  // read a byte from the serial port:
  var inByte = serial.read();
  // store it in a global variable:
  inData = inByte;
}
 
function serialError(err) {
  println('Something went wrong with the serial port. ' + err);
}

Program the  draw() function to display the value of any incoming serial bytes. Here it is:

1
2
3
4
5
6
7
function draw() {
  // black background, white text:
  background(0);
  fill(255);
  // display the incoming serial data as a string:
  text("incoming value: " + inData, 30, 50);
}

To read the mouse and keyboard, you’ll need to write functions to respond to the ‘mouseDragged’ and ‘keyPressed’ events. ‘MouseDragged’ will happen whenever you click and drag the mouse on the canvas. When that happens, read the mouseY, and map its position on the canvas to a value from 0 to 255. Convert the result to a number using the int() function. Then send it out the serial port using the serial.write() function:

1
2
3
4
5
6
function mouseDragged() {
  // map the mouseY to a range from 0 to 255:
  outByte = int(map(mouseY, 0, height, 0, 255));
  // send it out the serial port:
  serial.write(outByte);
}

The serial.write() function is versatile. If you give it a variable or literal that’s a numeric data type, it will send it as its raw binary value. In the code above, note how you’re converting the output of the map() function to a number using the int() function.  If you give it a string, however, it will send out that ASCII string. So be aware of the difference, and make sure you know whether your serial receiving device wants raw binary or ASCII-encoded data.

Program the keyPressed() function similarly to the mouseDragged() function. You want it to read the key strokes, convert them to raw bytes, and send them out the serial port. But you only want to send them if they key hit was 0 through 9. The P5.js variable key returns a numeric value, so you can do math on it and convert it like so:

1
2
3
4
5
6
function keyPressed() {
  if (key >= 0 && key <= 9) { // if the user presses 0 through 9
    outByte = byte(key * 25); // map the key to a range from 0 to 225
  }
  serial.write(outByte); // send it out the serial port
}

That’s all you want your sketch to do, so try running it now. You should see that the initial incoming serial value is undefined, but when you drag the mouse up and down, or type 0 through 9, it will update when the Arduino program returns what it received. The LED will also change with these actions.

Sending ASCII-Encoded Serial Data

If you want to send ASCII-encoded serial data from P5.js, all you have to do is to serial.write() your string. Sending strings is the P5.serialport’s default behavior. On the Arduino side, you can read single characters one byte at a time simply as well. However, if you want to convert multi-byte number strings to numeric values, you’ll need a new function to read ASCII encoded numeric strings called parseInt().

Program the Microcontroller Again

To start off with, load a sketch from the Arduino examples called PhysicalPixel. You can find it in the File Menu -> Examples -> Communication -> PhysicalPixel. Here’s what it looks like. Change the LED pin number to pin 5 as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const int ledPin = 5; // the pin that the LED is attached to
int incomingByte;     // a variable to read incoming serial data into
 
void setup() {
  Serial.begin(9600);             // initialize serial communication
  pinMode(ledPin, OUTPUT);        // initialize the LED pin as an output
}
 
void loop() {
  if (Serial.available() > 0) { // see if there's incoming serial data
    incomingByte = Serial.read(); // read it
    if (incomingByte == 'H') {    // if it's a capital H (ASCII 72),
      digitalWrite(ledPin, HIGH); // turn on the LED
      // if you're using a speaker instead of an LED, uncomment line below  and comment out the previous line:
      //  tone(5, 440);           // play middle A on pin 5
    }
    if (incomingByte == 'L') {    // if it's an L (ASCII 76)
      digitalWrite(ledPin, LOW);  // turn off the LED
      // if you're using a speaker instead of an LED, uncomment line below  and comment out the previous line:
      // noTone(5);
    }
  }
}

When you run this, open the serial monitor and type H or L, and the LED will go on or off.  Try typing h or l instead. The LED won’t change, because H and h have different ASCII values, as do L and l. But you can see from this that you don’t need to memorize the ASCII chart to check for character values in your code. Put the character you want to read in single quotes, and the Arduino compiler will automatically convert the character to its ASCII value for you. It only works for single characters, though.

Program P5.js To Control the LED

To get P5.js to control this Arduino program serially, you only need to change the keyPressed() function to read H or L instead of 0 through 9. Here’s your new mousePressed() function:

1
2
3
4
5
function keyPressed() {
  if (key ==='H' || key ==='L') { // if the user presses H or L
    serial.write(key);              // send it out the serial port
  }
}

Because the key is already a single character, P5.js sends it out as is, and Arduino reads it as a single byte, looking for the ASCII value of H or L. Notice how the values returned to P5.js are 72 and 76, the ASCII values for H and L. For single characters like this, exchanging data is simple.

If you tried to change the LED with the mouse, you didn’t see anything happen unless your output value was 72 or 76. Why is that?

Processing ASCII-Encoded Strings With Arduino

It is also possible to read and interpret ASCII-encoded strings in Arduino. The String.parseInt() function reads an incoming string until it finds a non-numeric character, then converts the numeric string that it read into a long integer. This is a blocking function, meaning that String.parseInt() stops the program and does nothing until it sees a non-numeric character, or until a timeout passes. The timeout is normally one second (or 1000 milliseconds), but you can set it to a lower number of milliseconds using Serial.setTimeout(). Here’s a variation on the original Arduino sketch from above, using Serial.parseInt() this time:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void setup() {
  Serial.begin(9600);    // initialize serial communications
  Serial.setTimeout(10); // set the timeout for parseInt
}
 
void loop() {
  if (Serial.available() > 0) { // if there's serial data available
    int inByte = Serial.parseInt(); // read it
    if (inByte >= 0) {
      Serial.write(inByte);      // send it back out as raw binary data
      analogWrite(5, inByte);    // use it to set the LED brightness
      // if you're using a speaker instead of an LED, uncomment line below  and comment out the previous line:
      //  tone(5, inByte*10);     // play tone on pin 5
    }
  }
}

Upload this to your microcontroller, then open the Serial Monitor and send in some ASCII numeric strings. You’ll see the character that’s represented by the string’s value. For example, 65 will return A, 34 will return “, and so forth.Notice that this version of the sketch has a conditional statement to check if the incoming byte is 0. This is because of a quirk of the parseInt() function. It returns 0 if the timeout is hit, or if the string is legitimately 0. This means you can’t really parse for a string like this: "0\n".

Program P5.js To Send a String With a Newline Character

Now that your microcontroller is expecting a string, program P5.js to send one. This means changing the mouseDragged() function. You still need to convert it to an integer using the int() function (you could also use round()), but then you need to convert it back to a String and add a delimiter. A quick way to do this is by adding the delimiter in the serial.write() command like so:

1
serial.write(outByte + '\n');

When the command encounters the two different elements, the number and the string (‘\n’), it will convert the number into a string in in order to concatenate the two. In addition, the newline on the end will is useful on the Arduino side. Since it’s a non-numeric character, the Serial.parseInt() function will see it and parse the string, not waiting for the timeout.

The full code for all the examples in this lab can be found in this gitHub repository.

Conclusion

When you’re sending data between two computers using asynchronous serial communication, you have to make sure that what the sender is sending is formatted the same as what the receiver is listening for. See Table 1 to review what are suitable data formats for different types/sizes of data and which functions to use on p5.js and Arduino for serial communication.

Number of Bytes
1 Byte
Multi Bytes
Data to Send
A single number < 255
A single character
A single number > 255, multiple values
Send as:
Binary
Ascii
Ascii
p5.js ->
serial.write(integer)
serial.write(string)
serial.write
(valueToSend + ",")
-> Arduino
Serial.read()
Serial.parseInt()

Table 1. Serial Communication: p5.js to Arduino

Think this out in advance before you code, then consider what functions you’ve got on both computers to convert data from strings to raw binary numbers and back. Test with fixed values at first, so you know you’re getting what you think you should. For example, sending an ASCII-encoded numeric string like this:

1023\n

Will always result in these six bytes:

49 48 50 51 10

Likewise, this text string:

Hello\n

will always be:

72 101 108 108 111 10

By sending a string you know both the ASCII and raw binary representations of, you can test your code easier, because what you’re sending won’t change. Once you know the sending and receiving works, then you can send variable strings.

The more you work with serial data, the more you’ll become familiar with the methods for handling it.

For more on serial flow control in P5.js, see the Two-Way Duplex Serial Communication Using P5.js Lab.

Lab: Two-Way (Duplex) Serial Communication Using An Arduino and the p5.serialport Library

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

Introduction

In the Introduction to Asynchronous Serial Communication lab, you learned about various methods for managing the communications between computers via asynchronous serial communication. These included formatting your data as ASCII-encoded strings or raw serial bytes and managing the flow of data using handshaking. In the P5.js Serial Input Lab, you sent data from one sensor to a personal computer. In this lab, you’ll send data from multiple sensors to a program in P5.js. You’ll use the data from the sensors to create a pointing-and-selecting device (i.e. a mouse).

What You Should Know

To get the most out of this tutorial, you should know what a microcontroller is and how to program them. You should also understand asynchronous serial communication between microcontrollers and personal computers. You should also understand the basics of P5.js. It would also help to go through the following labs first:

These videos might help in understanding this lab as well:

Things You’ll Need

For this lab, you’ll need the hardware below, and you’ll need the same software setup as the Serial Input to P5.js lab: You’ll create a p5.js sketch. You’ll also use the p5.serialport library and the P5.serialcontrol app. You can use the p5.js web editor or your favorite text editor for this (the Visual Studio Code editor or the  Atom text editor work well).

Figures 1-5 below are 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. Microcontroller. Shown here is an 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 four breadboard-mounted pushbuttons
Figure 4. A pushbutton
Photo of two potentiometers
Figure 5. two potentiometers. You can use any two analog sensors in place of these if you prefer.

Connect the Sensors

For this exercise, you’re going to need two analog inputs to your microcontroller, and one digital input. It doesn’t matter what they are, so use something that’s easy for you to set up. The photos and schematic in this lab show potentiometers and a pushbutton. You don’t have to use these, though. Any three sensor inputs will do the job. If you’re looking for options, consider:

Photo of a breadboard-mountable joystick, This component is mounted on a printed circuit board that's about 4cm on each side. The joystick itself is about 6cm tall, controllable by a thumb. There are five pins on one side of the PCB for mounting on the breadboard.
Figure 6. A joystick, which consists of two potentiometers and a pushbutton
Photo of a rotary encoder
Figure 7. Rotary encoders, which include a built-in pushbutton
Photo of the IMU sensor on teh Nano 33 IoT. It's the small rectangular chip above and to the left of the main processor.
Figure 8. The built-in accelerometer on the Arduino Nano 33 IoT, which measures acceleration on three axes

As long as you have three sensors that will output changing readings, you can follow this lab.

Connect the two analog sensors to analog pins 0 and 1 like you did in the analog input to Arduino lab. Connect a pushbutton to digital pin 2 like you did in the digital input and output with Arduino lab.

Schematic view of an Arduino attached to two potentiometers and a pushbutton. The potentiometers' center pins are connected to the Arduino's A0 and A1 inputs, respectively.
Figure 9. Schematic view of an Arduino attached to two potentiometers and a pushbutton. The potentiometers’ center pins are connected to the Arduino’s A0 and A1 inputs, respectively. Their left pins are connected to the voltage bus, and the right pins are connected to the ground bus, 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 Uno attached to two potentiometers and a pushbutton.
Figure 10. Breadboard view of an Arduino Uno attached to two potentiometers and a pushbutton. The potentiometers’ center pins are connected to the Arduino’s A0 and A1 inputs, respectively. Their left pins are connected to the voltage bus, and the right pins are connected to the ground bus, 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 attached to two potentiometers and a pushbutton
Figure 11. Breadboard view of an Arduino Nano attached to two potentiometers and a pushbutton. The potentiometers’ center pins are connected to the Arduino’s A0 and A1 inputs, respectively. Their left pins are connected to the voltage bus, and the right pins are connected to the ground bus, 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.

(Diagrams made with Fritzing, a circuit design program)


Sending Multiple Serial Data using Punctuation

You’re going to program the microcontroller to read the pushbutton and two analog sensors just like you did in the Intro to Serial Communications Lab. When you have to send multiple data items, you need a way to separate them. If you’re sending them as ASCII-encoded strings, it’s simple: you can just put non-numeric punctuation bytes between them (like a comma or a space) and a unique termination punctuation at the end (like a newline and/or carriage return).

This program will send the two analog sensor values and then the pushbutton. All three will be ASCII-encoded numeric strings, separated by commas. The whole line of sensor values will be terminated by carriage return (\r, ASCII 13) and newline (\n, ASCII 10).

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
27
const int buttonPin = 2;      // digital input
 
void setup() {
  // configure the serial connection:
  Serial.begin(9600);
  // configure the digital input:
  pinMode(buttonPin, INPUT);
}
 
void loop() {
  // read the first analog sensor:
  int sensorValue = analogRead(A0);
  // print the results:
  Serial.print(sensorValue);
  Serial.print(",");
 
  // read the second analog sensor:
  sensorValue = analogRead(A1);
  // print the results:
  Serial.print(sensorValue);
  Serial.print(",");
 
  // read the button:
  sensorValue = digitalRead(buttonPin);
  // print the results:
  Serial.println(sensorValue);
}

When you run this and output it to the Serial Monitor, you should see something like this:

348,363,1
344,362,1
345,363,1
344,375,0
365,374,0
358,369,0
355,369,0
352,373,0
356,373,0

Turn the potentiometers (or tweak the analog sensors) and push the button. Now you’ve got a data format: three sensors, comma-separated, terminated by carriage return and newline. This means that you already have an algorithm for how you’re going to program P5.js to read the serial input:

  • Read the incoming serial data into a string until a carriage return and newline appear
  • split the string into substrings on the commas
  • convert the substrings into numbers
  • assign the numbers to variables to change your programNow that you’ve got a plan, put it into action.

Receive the data in P5.js

Now write a P5.js sketch that reads the data as formatted by the Arduino program above. Download the latest version of the P5.serialcontrol application if you haven’t already and save it in your Applications folder. When you run it, it will check serial ports on your machine. You don’t need to do anything with the app, just have it open.

You’ll need to know the name of your serial port to get started. If you’re not sure how to get this, see the Serial Input to P5.js lab for how to get a list of ports.

The P5.js Sketch

The sketch you’re going to write will control the microcontroller’s LED from P5.js. Dragging the mouse up and down the canvas will dim or brighten the LED, and typing 0 through 9 will set the LED’s brightness in increments from off (0) through almost full brightness (9). There’s an alternate sketch that will make changing tones if you prefer that instead of a changing LED. The sketch will also receive serial input from the microcontroller just as in the Serial Input to P5.js lab, so that you can see that the microcontroller is getting the same values you’re sending.

As you saw in the Serial Input to P5.js lab,  you’ll need to copy the p5.seriaport.js lbrary into your sketch’s libraries folder. You’ll need to run p5.serialcontrol, or run the p5.serialserver from the command line as you did in the Serial Input to P5.js lab as well.

Make a P5.js sketch. If you’re using the p5.js web editor, make a new sketch. Click the Sketch Files tab, and then choose the index.html file. In the head of the document, look for this line:

Right after that line, add this line:

1
<script language="javascript" type="text/javascript" src="https://cdn.jsdelivr.net/npm/p5.serialserver@0.0.28/lib/p5.serialport.js"></script>

The setup of your sketch will initialize the P5.serialport library and define your callback functions for serial events. , as you did in other sketches

Then in the setup(), create a canvas, make an instance of the serialport library, and declare your callback functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var serial;          // variable to hold an instance of the serialport library
 
function setup() {
  createCanvas(800, 600);          // make canvas
  smooth();                        // antialias drawing lines
  serial = new p5.SerialPort();    // make a new instance of the serialport library
  serial.on('list', printList);    // set a callback function for the serialport list event
  serial.on('connected', serverConnected); // callback for connecting to the server
  serial.on('open', portOpen);     // callback for the port opening
  serial.on('data', serialEvent);  // callback for when new data arrives
  serial.on('error', serialError); // callback for errors
  serial.on('close', portClose);   // callback for the port closing
 
  serial.list();                   // list the serial ports
}

This time, you’ll add a serialport select menu as you did in the Serial Input to P5.js  lab.First, declare a global variable to hold the port menu at the top of your sketch like so:

1
var portSelector; // a select menu for the port list

Then change the printList function like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// get the list of ports:
function printList(portList) {
  // make a select menu and position it:
  portSelector = createSelect();
  portSelector.position(10,10);
   
  // portList is an array of serial port names
  for (var i = 0; i < portList.length; i++) {
    // Display the list the console:
    // console.log(i + " " + portList[i]);
    // add item to the select menu:
    portSelector.option(portList[i]);
  }
  // set a handler for when a port is selected from the menu:
  portSelector.changed(mySelectEvent);
}

When the select menu’s value has changed, you can assume a serial port has been selected, so write a handler to open it like so:

1
2
3
4
5
6
7
8
9
function mySelectEvent() {
  let item = portSelector.value();
   // if there's a port open, close it:
  if (serial.serialport != null) {
    serial.close();
  }
  // open the new port:
  serial.open(item);
}

From now on, when you run this sketch, you’ll need to select the serial port to open the port.

The rest of the serial event handlers are all the same as you saw in the P5.js Serial Input Lab, except for the serialEvent(). Here are all but the serialEvent():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function serverConnected() {
  console.log('connected to server.');
}
 
function portOpen() {
  console.log('the serial port opened.')
}
 
function serialError(err) {
  console.log('Something went wrong with the serial port. ' + err);
}
 
function portClose() {
 console.log('The serial port closed.');
}

Program the serialEvent() function to read the incoming serial data as a string until it encounters a carriage return and newline (‘\r\n’). Then check to see that the resulting string has a length greater than 0 bytes. If it does, use the split() function to split it in to an array of strings. If the resulting array is at least three elements long, you have your three sensor readings. The first reading is the first analog sensor, and can be mapped to the horizontal movement using the locH variable. The second is the second analog sensor and can be mapped to the locV variable. The third is the button. When it’s 0, set the circleColor variable equal to 255 and when it’s 1, set the variable to 0. Here’s how:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.length > 0 ) {
    var sensors = split(inString, ',');            // split the string on the commas
    if (sensors.length > 2) {                      // if there are three elements
      locH = map(sensors[0], 0, 1023, 0, width);   // element 0 is the locH
      locV = map(sensors[1], 0, 1023, 0, height); // element 1 is the locV
      circleColor = 255 - (sensors[2] * 255);      // element 2 is the button
    }
  }
}

Note the mappings of sensor[0] and sensor[1]. You should use the input mappings for your accelerometer instead of 0 and 1023. If your analog values are greater than the width of the sketch or the height, the circle will be offscreen, which is why you have to map your sensor range to the screen size.

Program the draw() function to draw a circle that’s dependent on three global variables, locH, locV, and circleColor. Add these three globals to the top of the program:

1
2
3
var locH = 0;
var locV = 0;        // location of the circle
var circleColor = 255; // color of the circle

Finally, here is the draw function:

1
2
3
4
5
function draw() {
  background(0);               // black background
  fill(circleColor);           // fill depends on the button
  ellipse(locH, locV, 50, 50); // draw the circle
}

If you run this, you should see the circle moving onscreen whenever you tilt the accelerometer. When you press the pushbutton, the circle will disappear. Okay, it’s not exactly a  mouse, but you are controlling an animation from a device that you built.

Flow Control: Call and Response (Handshaking)

You’ve seen now that by coming up with a serial format (called a protocol), you can write the algorithm for receiving it even before you see any data. You can send multiple pieces of data this way, as long as you format it consistently.

Sometimes you can run into a problem when the sender sends faster than the receiver can read. When this happens, the receiver program slows down as the serial buffer fills up. You can manage this by implementing some form of flow control. The simplest way do to this is using a call-and-response method, where the sending program only sends when it’s told to do so, and the receiving program has to request new data every time it finishes reading what it’s got.

You can add handshaking to the code above fairly simply. Modify the Arduino code as follows. First, add a a new block of code in the setup() This block sends out a message until it gets a byte of data from the remote computer:

1
2
3
4
5
6
7
void setup() {
  Serial.begin(9600);
  while (Serial.available() <= 0) {
    Serial.println("hello"); // send a starting message
    delay(300);              // wait 1/3 second
  }
}

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(buttonPin);
    // print the results:
    Serial.println(sensorValue);
  }
}

The rest of the 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. When you write a program to receive this format, it just has to behave the same way you did:

  • Open the serial port
  • Wait for a Hello
  • Send a byte to request data
  • Begin loop:
  • Wait for one set of data
  • Send a byte to request new data
  • end loop

Next, modify the P5.js sketch.  Most of the changes are in the serialEvent() function. The initial “hello” messages will trigger this function, so when you get a “hello” or any other string, you need to send a byte back so that the Arduino has a byte available to read. Here’s the new serialEvent():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.length > 0) {
    if (inString !== 'hello') { // if you get hello, ignore it
      var sensors = split(inString, ','); // split the string on the commas
      if (sensors.length > 2) { // if there are three elements
        locH = map(sensors[0], 0, 1023, 0, width); // element 0 is the locH
        locV = map(sensors[1], 0, 1023, 0, height); // element 1 is the locV
        circleColor = 255 - (sensors[2] * 255); // element 2 is the button
      }
    }
    serial.write('x'); // send a byte requesting more serial data
  }
}

You also need to add a line to the openPort() function like so:

1
2
3
4
5
function portOpen() {
  console.log('the serial port opened.')
  // send a byte to prompt the microcontroller to send:
  serial.write('x');
}

The reason for this is that if your Arduino has already broken out of the loop (let’s say you opened the Serial monitor to check), then it is waiting for a byte from p5.js to send the next block of code. By sending a byte when you know the port has just been opened in p5.js, you force the Arduino to send you new data.

That’s it. Your sketch should still run just as it did before, though the serial communication is managed better now, because Arduino’s only sending when P5.js is ready to receive.

The full code for all the examples in this lab can be found in this gitHub repository.

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.

Build an Application of Your Own

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 P5.js, Node.js, 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. Present a working software and hardware model of your idea.

Lab: Sensor Change Detection

In this lab you’ll learn some methods for determining when a sensor’s reading changes significantly.

Introduction

Microcontrollers can sense what’s going on in the physical world using digital and analog sensors, but a single sensor reading doesn’t tell you much. In order to tell when something significant happens, you need to know when that reading changes. For example, when a digital input changes from LOW to HIGH or the reverse, you can tell that a person closed or opened  a switch. When a force-sensing resistor reaches a peak reading, you know that something has hit the sensor. In this lab, you’ll learn how to program your microcontroller to look for three common changes in sensor readings that give you information about events in the physical world: state change detection on digital sensors, and threshold crossing and peak detection on analog sensors. You’ll use these three techniques all the time when you’re designing to read users’ actions.

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

Understanding How Your Sensor Changes

Before you start trying to detect specific sensor change events, you should know what your sensor’s changes look like over time. You might want to start by viewing the change on an oscilloscope, or by using the Serial Plotter in the Arduino IDE Tools menu (command-shift-L), or a graphing program like the one shown in the WebSerial input to p5.js Lab or Serial Output From Arduino to Processing lab to understand how your sensors change. Figure 7 shows a typical sensor change graph.

Graphing a sensor in Processing. The drawing shows a graph in which the line is alternately rising and falling on a relatively smooth curve.
Figure 7. Graphing a sensor in Processing

Sensor changes are described in terms of the change in the sensor’s property, often a voltage output, over time. The most important cases to consider for sensor change are the rising and falling edges of a digital or binary sensor, and the rising and falling edges and the peak of an analog sensor. The graphs in Figures 8 and 9 of sensor voltage over time illustrate these conditions:

Graph of a digital sensor's rising and falling edge. The graph shows a flat line near zero, followed by a vertical rise to the top of the vertical axis. This change is labeled "Rising". Then there is another flat line for a short distance, then a vertical drop back to near zero. This change is labeled "Falling".
Figure 8. Digital sensors change from high voltage to low and vice versa. The change from low voltage to high is called the rising edge, and the change from high voltage to low is called the falling edge
Graph of an analog sensor. Graph depicts the sensor rising, peaking, and falling over time. It also depicts a threshold value below the rising, peak, and falling points.
Figure 9. The three general states of an analog sensor are when it’s rising (current state > previous state), when it’s falling (current state < previous state), and when it’s at a peak.

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 10 and 11.

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 10. Breadboard view of an Arduino Uno on the left connected to a solderless breadboard, right.

Figure 10 shows 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 11. Breadboard view of Arduino Nano mounted on a breadboard.

Figure 11 shows 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

Add a pushbutton

Connect a pushbutton to digital input 2 on the Arduino. Figures 12 and 13 show the schematic and breadboard views of this for an Arduino Uno, and Figure 14 shows the breadboard view for an Arduino 33 IoT.

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 12. 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 13. 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 14. 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.


Program the Microcontroller to Read the Pushbutton’s State Change

In the Digital Lab you learned how to read a pushbutton using the digitalRead() command. To tell when a pushbutton is pushed, you need to determine when the button’s state changes from off to on. With the button wired as you have it here, the button’s state will change from 0 to 1. In order to know that, you need to know not only what the current state of the button is, but you also need to remember the state of the button the previous time you read it. This is called state change detection. To do this, set up a global variable to store the button’s previous state. Initialize the button in your program’s setup() function using the pinMode() command. Then, in the loop() function, write a block of code that reads the button and compares its state to the previous state variable. To do this, you need to read the button, check the current button state against the last state, then save the current state of the button in a variable for the next time through the loop like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int lastButtonState = LOW;    // state of the button last time you checked
 
void setup() {
  // make pin 2 an input:
  pinMode(2, INPUT);
}
 
void loop() {
  // read the pushbutton:
  int buttonState = digitalRead(2);
 
  // check if the current button state is different than the last state:
  if (buttonState != lastButtonState) {
     // do stuff if it is different here
  }
 
  // save button state for next comparison:
  lastButtonState = buttonState;
}

If buttonState is not equal to lastButtonState, then the button has changed. Then you want to check if the current state is HIGH. If it is, then you know the button has changed from LOW to HIGH. That means your user pressed it. Print out a message to that effect.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int lastButtonState = LOW;   // state of the button last time you checked
 
void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // make pin 2 an input:
  pinMode(2, INPUT);
}
 
void loop() {
  // read the pushbutton:
  int buttonState = digitalRead(2);
 
  // if it's changed and it's high, toggle the mouse state:
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      Serial.println("Button was just pressed.");
    }
  }
  // save button state for next comparison:
  lastButtonState = buttonState;
}

Your code should only print out a message when the button changes state. For every button press, you should get one line of code. You can use this technique any time you need to tell when a digital input changes state.

Count Button Presses

One of the many things you can do with state change detection is to count the number of button presses. Each time the button changes from off to on, you know it’s been pressed once. By adding another global variable and incrementing it when you detect the button press, you can count the number of button presses.  Add a global variable at the top of your program like so:

1
2
int lastButtonState = LOW;   // state of the button last time you checked
int buttonPresses = 0;       // count of button presses

Then in the if statement that detects the button press, add one to the button press:

1
2
3
4
5
6
if (buttonState == HIGH) {
     buttonPresses++;
     Serial.print("Button has been pressed ");
     Serial.print(buttonPresses);
     Serial.println(" times.");
   }

The key to detecting state change is the use of a variable to save the current state for comparison the next time through the loop. This is a pattern you’ll see below as well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int lastSensorState = LOW;   // sensor's previous state
// other globals and the setup go here
 
void loop() {
  // read the sensor:
  int sensorState = digitalRead(2);
 
  // if it's changed:
  if (sensorState != lastSensorState) {
    // take action or run a more detailed check
  }
  // save sensor state for next comparison:
  lastSensorState = sensorState;
}

Long Press, Short Press

Sometimes you want to take a different action on a short button press than you do on a long button press. To do this, you need to know now only when the button changes, but also how long it stays in a pressed state after it changes. Here’s how you might do that.

Start with some global variables for the button pin number, and the length of a long press or a short press, in milliseconds. You also need a variable to track how long the button has been pressed, and as in the code above, you need a variable to track the last button state. Add another variable called pressTime, which will keep track of the last time the button went from LOW to HIGH:

1
2
3
4
5
6
7
8
9
10
11
// the input pin:
int buttonPin = 2;
 
// the length of the presses in ms:
int longPress = 750;
int shortPress = 250;
// variable for how long the user actually presses:
long pressTime = 0;
 
// previous state of the button:
int lastButtonState = LOW;

In the setup(), set the button pin mode and initialize serial as you did before:

1
2
3
4
5
void setup() {
  // initialize serial and I/O pin:
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);
}

In the loop, look for the button to change state, and when it does, note the press time in the pressTime variable. When the button is released (goes from HIGH to LOW), calculate how ling it was pressed, and print it:

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
void loop() {
  // read the button:
  int buttonState = digitalRead(buttonPin);
 
  // if the button has changed:
  if (buttonState != lastButtonState) {
    // if the button is pressed, start a timer:
    if (buttonState == HIGH) {
      pressTime = millis();
    }
    // if it's released, stop the timer:
    if (buttonState == LOW) {
      long holdTime = millis() - pressTime;
      // take action for long press, short press, or tap:
      if (holdTime > longPress) {
        Serial.println("long press");
      } else if (holdTime > shortPress) {
        Serial.println("short press");
      } else {
        Serial.println("Tap");
      }
    }
  }
  // save button state for next time:
  lastButtonState = buttonState;
}

You can see from this that you’ve actually got three states now, long press (> 750ms), short press (250-750ms), and tap (>250ms). With this, you can make one button do three things.

Analog Sensor Threshold Detection

When you’re using analog sensors, binary state change detection like you saw above is not usually effective, because your sensors can have multiple states. Remember, an analog sensor on an Arduino can have up to 1024 possible states. The simplest form of analog state change detection is to look for the sensor to rise above a given threshold in order to take action. However, if you want the action be triggered only once when your sensor passes the threshold, you need to keep track of both its current state and previous state.

Change the Breadboard

To build this example, you’ll need an analog sensor attached to your microcontroller, as shown in the Analog Input lab. Figures 15-17 show how to connect it.

Schematic of Arduino connected to an FSR on pin 2. One leg of the FSR connects to +5V. The other leg simultaneously connects to the Arduino's digital pin 2 and one end of a 10-kilohm resistor. The other end of the resistor connects to ground.
Figure 15. Schematic of Arduino connected to an FSR on pin 2
Breadboard view of Arduino connected to an FSR on pin 2. One leg of the FSR connects to +5V. The other leg simultaneously connects to the Arduino's digital pin 2 and one end of a 10-kilohm resistor. The other end of the resistor connects to ground.
Figure 16. Breadboard view of Arduino connected to an FSR on pin 2. The FSR is connected to two rows in the left center section of the breadboard. One of its pins is wired to voltage. The other is connected to ground through a 10-kilohm resistor. The row connecting the two resistors is wired to analog input 0.
Breadboard view of Arduino Nano connected to an FSR on pin 2.
Figure 17. Breadboard view of Arduino Nano connected to an FSR on pin 2. The FSR is connected to two rows in the left center section of the breadboard, below the Nano. One of its pins is wired to voltage. The other is connected to ground through a 10-kilohm resistor. The row connecting the two resistors is wired to analog input 0.

Program the Microcontroller to Read a Sensor Threshold Crossing

This example is very similar to the one above:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int lastSensorState = LOW;   // sensor's previous state
int threshold = 512;   // an arbitrary threshold value
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  // read the sensor:
  int sensorState = analogRead(A0);
 
  // if it's above the threshold:
  if (sensorState >= threshold) {
    // check that the previous value was below the threshold:
     if (lastSensorState < threshold) {
        // the sensor just crossed the threshold
        Serial.println("Sensor crossed the threshold");
     }
  }
  // save button state for next comparison:
  lastSensorState = sensorState;
}

This program will give you an alert only when the sensor value crosses the threshold when it’s rising. You won’t get any reading when it crosses the threshold when it’s falling, and you’ll only get one message when it crosses the threshold. It is possible to sense a threshold crossing when the sensor is falling, by reversing the greater than and less than signs in the example above. The threshold you set depends on your application. For example, if you’re using a light sensor to detect when it’s dark enough to turn on artificial lighting, you’d use the example above, and turn on the light when the threshold crossing happens. But you might also need to check for the falling threshold crossing to turn off the light.

Detecting a Peak

There are times when you need to know when an analog sensor reaches its highest value in a given time period. This is called a peak. To detect a peak, you first set an initial peak value at zero.  Pick a threshold below which you don’t care about peak values. Any time the sensor value rises above the peak value, you set the peak value equal to the sensor value. When the sensor value starts to fall, the peak will remain with the highest value:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int peakValue = 0;
int threshold = 50;   //set your own value based on your sensors
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  //read sensor on pin A0:
  int sensorValue = analogRead(A0);
  // check if it's higher than the current peak:
  if (sensorValue > peakValue) {
    peakValue = sensorValue;
  }
}

You only really know you have a peak when you’ve passed it, however. When the current sensor value is less than the last reading you saved as the peak value, you know that last value was a peak. When the sensor value falls past below threshold after you have a peak, but your peak value is above the threshold, then you know you’ve got a significant peak value. after you use that peak value, you need to reset the variable to 0 to detect other peaks :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int peakValue = 0;
int threshold = 50;   //set your own value based on your sensors
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  //read sensor on pin A0:
  int sensorValue = analogRead(A0);
  // check if it's higher than the current peak:
  if (sensorValue > peakValue) {
    peakValue = sensorValue;
  }
  if (sensorValue <= threshold) {
    if (peakValue > threshold) {
      // you have a peak value:
      Serial.println(peakValue);
      // reset the peak variable:
      peakValue = 0;
    }
  }
}

Dealing with Noise

Quite often, you get noise from sensor readings that can interfere with peak readings. Instead of a simple curve, you get a jagged rising edge filled with many local peaks, as shown in Figure 18:

Graph of a sensor rising above a constant threshold, peaking, falling below that peak but still above the threshold, then rising to its highest point and falling below the threshold
Figure 18. Graph of local peaks

You can smooth out the noise and ignore some of these local peaks by adding in a noise variable and checking to see if the sensor’s change is different than the previous reading and the noise combined, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int peakValue = 0;
int threshold = 50;   //set your own value based on your sensors
int noise = 5;        //set a noise value based on your particular sensor
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  //read sensor on pin A0:
  int sensorValue = analogRead(A0);
  // check if it's higher than the current peak:
  if (sensorValue > peakValue) {
    peakValue = sensorValue;
  }
  if (sensorValue <= threshold - noise ) {
    if (peakValue > threshold + noise) {
      // you have a peak value:
      Serial.println(peakValue);
      // reset the peak value:
      peakValue = 0;
    }
  }
}

Most sensor change cases can be described using a combination of state change detection, threshold crossing, and peak detection. When you start to write sensor change detection routines, make sure you understand these three basic techniques, and make sure you have a good idea what your sensor’s readings look like over time. With that combination, you should be able to detect most simple sensor change events.

Lab: I2C Communication With An Infrared Temperature Sensor

In this lab, you’ll see synchronous serial communication in action using the Inter-integrated Circuit (I2C) protocol. You’ll communicate with an infrared temperature sensor chip from a microcontroller in order to read the temperature of an object in front of the sensor.

Introduction

In this lab, you’ll see synchronous serial communication in action using the Inter-integrated Circuit (I2C) protocol. You’ll communicate with an infrared temperature sensor chip from a microcontroller in order to read the temperature of an object in front of the sensor.

Related videos: Intro to Synchronous Serial, I2C

What You’ll Need to Know

To get the most out of this Lab, you should be familiar with the basics of programming an Arduino microcontroller. If you’re not, review the Digital Input and Output Lab, and perhaps the Getting Started with Arduino guide. You should also understand asynchronous serial communication and how it differs from synchronous serial communication.

Things You’ll Need

Figure 1-3 are the parts that  you need for this lab.

Three 22AWG solid core hookup wires. Each is about 6cm long. The top one is black; the middle one is red; the bottom one is blue. All three have stripped ends, approximately 4 to 5mm on each end.
Figure 1. 22AWG solid core hookup wires.
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 2. Arduino Nano 33 IoT
A TMP007 Temperature sensor module. There are 7 pins along the bottom, labeled VCC, GND, SDA, SCL, Alert, AD1, and AD0 (left to right)
Figure 3. A TMP007 Temperature sensor module.
A photo of 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 4. A short solderless breadboard.

Connect the temperature sensor

The temperature sensor used in this lab,  a Texas Instruments TMP007, is an integrated circuit (IC) that can read the temperature of an object placed in front of it. Connect the sensor’s power and ground connections and the connections for clock and serial data as shown in Figure 5-7. For the Arduino UNO board and Nano boards, the I2C pins are fixed A4(SDA) and A5(SCL):

Schematic view of an Arduino attached to a TMP007 temperature sensor. The TMP007 temperature sensor has 7 pins, and when the sensor is positioned with the pins on the bottom and pointing away from you, the pins are labeled VCC, Ground, SDA, SCL, Alert, AD1, and AD0. The sensor's VCC and ground pins (pins 1 and 2) are connected to the Aruino's 5V and GND pins, respectively. The SCL pin (pin 4) is connected to the Arduino's A4 input and the Alert pin (pin 5) is connected to the A5 input.
Figure 4. Schematic view of an Arduino attached to a TMP007 temperature sensor.
Schematic view of an Arduino attached to a TMP007 temperature sensor. The TMP007 temperature sensor has 7 pins, and when the sensor is positioned with the pins on the bottom and pointing away from you, the pins are labeled VCC, Ground, SDA, SCL, Alert, AD1, and AD0. The sensor's VCC and ground pins (pins 1 and 2) are connected to the Aruino's 5V and GND pins, respectively. The SCL pin (pin 4) is connected to the Arduino's A4 input and the Alert pin (pin 5) is connected to the A5 input.
Figure 5. Schematic view of an Arduino attached to a TMP007 temperature sensor.
An Arduino Nano attached to a TMP007 temperature sensor. The TMP007 temperature sensor has 7 pins, and when the sensor is positioned with the pins on the left hand side of the board the pins are labeled VCC, Ground, SDA, SCL, Alert, AD1, and AD0. The sensor's VCC and ground pins (pins 1 and 2) are connected to the Arduino's 3.3V (pin 2) and GND (pin 14) pins, respectively. The sensor's SDA pin (pin 3) is connected to the Arduino's A4 input (pin 8) and the sensor's SCL pin (pin 4) is connected to the Arduino's A5 input (pin 9).
Figure 6. An Arduino Nano attached to a TMP007 temperature sensor. The TMP007 temperature sensor has 7 pins, and when the sensor is positioned with the pins on the left hand side of the board the pins are labeled VCC, Ground, SDA, SCL, Alert, AD1, and AD0. The sensor’s VCC and ground pins (pins 1 and 2) are connected to the Arduino’s 3.3V (pin 2) and GND (pin 14) pins, respectively. The sensor’s SDA pin (pin 3) is connected to the Arduino’s A4 input (pin 8) and the sensor’s SCL pin (pin 4) is connected to the Arduino’s A5 input (pin 9).

The circuit is now complete, and you’re ready to write a program to control it. One of the advantages of the I2C synchronous serial protocol (as opposed to the SPI protocol) is that you only ever need two wires for communication to one or multiple devices.

How the Temperature Sensor Works

I2C devices exchange bits of data whenever the shared clock signal changes.  Controller and peripheral devices both send bits of data when the clock changes from low to high (called the rising edge of the clock). Unlike with SPI, they cannot send data at the same time.

The TMP007 has a series of memory registers that control its function. You can write to or read from these registers using I2C communication from your microcontroller. Some of these registers are writable by the controller so that you can configure the sensor. For example, you can set its I2C address, set the sensitivity of the sensor, and so forth. Some registers are configuration registers, and by writing to them, you configure the chip. For example, you can set lower and upper limits of temperature sensitivity. Other memory registers are read-only. For example, when the sensor has read the temperature of an object, it will store the result in a register that you can read from the controller. The details of the chip’s registers can be found in the TMP007 datasheet, in the “Register Maps” section, page 26.

I2C devices exchange data in 7-bit chunks, using an eighth bit to signal if you’re reading or writing by the controller or for acknowledgement of data received. To get the temperature from the TMP007, your controller device sends the sensor’s address (a 7-bit number) followed by a single bit indicating whether you want to read data or write data (1 for read, 0 for write). Then you send the memory register that you want to read from or write to. For example, as shown in Figure 7, the object temperature is stored in memory register 01 of the TMP007. To get the temperature, you send:

I2C data. I2C communication. The first 3 bytes are messages from the controller to the peripheral. The first byte is a one followed by 7 zeros. The first 7 bits in the byte are for the peripheral at address 0x40 and the last bit indicates this is a write command. The second byte is 6 zeros, a one, and a zero. The first 7 bits in the byte indicates this is to read register 0x01 and the last bit is an acknowledgement from the device. The third byte is a one, 6 zeros, and a one. The first 7 bytes again are for the peripheral at address 0x40 and the last bit indicates this is a read command. The final 2 bytes are messages from the peripheral to the controller. The first byte is for 6 zero bits, a one, and a zero. The first 7 bits are the data in register 0x01 and the last bit is an acknowledgement from the device. The second byte is 6 zero bits and 2 ones. The first 7 bits are again data in the register 0x01 and the last bit is a signal from the device that the message is complete.
Figure 7. I2C data

To use I2C communication on an Arduino microcontroller, you use the Wire library, which is built into the Arduino IDE. You can find Arduino-compatible libraries for many devices that use the Wire library, but never expose it directly in their APIs. Adafruit’s TMP007 library is typical of this style of library. The library’s readObjTempC() command sends a write command to start a temperature reading of the object in front of the sensor, then sends a read command to read the appropriate temperature register, then combines the results using a formula explained in the datasheet on page 25, then returns the result to you as degrees celsius. Similarly, the readDieTempC() sends a write command to read the chip temperature, then a read command to read the appropriate temperature register, then does the math and gives you the result.

Install the External Libraries

The TMP007 library relies on the Adafruit Sensor library in addition to the Wire library, so you’ll need both in order to make this work. You can use the library manager to find these libraries or download the TMP007 library here and the Sensor library here. Make sure you’re using Arduino version 1.5.5 or later.

Once you’ve downloaded the libraries, change the name of the resulting .zip file to remove the words -master or _Library-master from the end of the file name. Keep the .zip extension though. Open the Arduino IDE (version 1.6.8 or later) and choose the Sketch menu, then choose Import Library… and finally click the Add Library… option. A dialog box will pop up asking you to find the library you want to add. Navigate to the .zip file for the Sensor library and choose it. The library will be added and you’ll see the words “Library added to your libraries. Check the ‘Import Library’ menu”.

Program the Microcontroller

At the beginning of your code, include the appropriate libraries. In the setup(), initialize the sensor with begin(). If the sensor responds, then begin() will return true, and if not, it will return false. This is how to check that the sensor is properly wired to your microcontroller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "Wire.h"
#include "Adafruit_TMP007.h"
 
Adafruit_TMP007 tmp007; // instance of the sensor library
 
void setup() {
  Serial.begin(9600);
  boolean sensorInitialized = tmp007.begin(); // initialize the sensor
 
  while (!sensorInitialized) {
    // Do nothing until sensor responds
    Serial.println("Sensor is not responding");
  }
}

The TMP007 can only read every four seconds. In the main loop, set up an if statement to check when the millis() passes another four seconds using the modulo operator:

1
2
3
4
void loop() {
  if (millis() % 4000 < 2) { // if 4 seconds have passed
  }
}

Inside that if statement, read the object temperature and the chip temperature and print them both out:

1
2
3
4
5
6
7
8
9
10
11
12
13
void loop() {
  if (millis() % 4000 < 2) { // if 4 seconds have passed
    float objectTemp = tmp007.readObjTempC(); // read object temperature
    float chipTemp = tmp007.readDieTempC(); // read chip temperature
 
    // print the results:
    Serial.print("Chip temperature: ");
    Serial.print(chipTemp);
    Serial.print(" deg. C. \t Object temperature: ");
    Serial.print(objectTemp);
    Serial.println(" deg. C");
  }
}

As you can see, you’re never actually calling commands from the Wire library directly, but the commands in the TMP007 library are relying on the Wire library. This is a common way to use the Wire library to handle I2C communication.

Conclusion

I2C is a common protocol among many ICs, and it’s handy because you can combine many devices on the same bus. You need to make sure the device addresses are unique. Each device will have its own way to change the address.  For the TMP007, see Table 2 in the data sheet; you have to set the address pins (AD1 and AD0 on the breakout board appropriately to change the address.

I2C can also be used to combine several Arduinos on a bus, with one as the controller and the others as peripherals. If you build your own Arduino-compatible circuit on a breadboard or use boards like the Adafruit Trinket or Trinket Pro, this can be an inexpensive way to combine several controllers in a more complex project. There are examples this in the Wire library documentation on the Arduino site.