Experiment Guide for the Johnny-Five Inventor's Kit

Pages
Contributors: rwaldron, reconbot, D___Run___, lyzadanger, Shawn Hymel
Favorited Favorite 8

Experiment 3: Reading a Potentiometer

Introduction

So far the experiments have been focused on output: writing code with Johnny-Five to control the state of one or more LEDs. In this experiment, you'll learn how to read analog input data from a potentiometer. You'll learn about how development boards (like the Tessel 2) sample and process analog input and how to use data from analog sources to make things happen. Using data from the potentiometer, you'll control a bar graph in your terminal, alter the brightness of an LED and control the activity of multiple LEDs.

Preflight Check

Whoa there, Turbo! If this is your first experiment with the Johnny-Five Inventor's Kit (J5IK) and the Tessel 2, there are a few things you gotta do first:
  1. Set up your computer
  2. Configure your Tessel 2
Note: These steps only have to be done once, but they are required. Internet connection may be necessary!

Suggested Reading

The following tutorials provide in-depth background on some of the hardware concepts in this experiment:

Parts Needed

You will need the following parts for this experiment:

  • 1x Tessel 2
  • 1x Breadboard
  • 1x Potentiometer
  • 1x Standard LED (any color)
  • 1x 100Ω Resistor
  • 7x Jumper Wires
Using a Tessel 2 without the kit? No worries! You can still have fun and follow along with this experiment. We suggest using the parts below:
Breadboard - Self-Adhesive (White)

Breadboard - Self-Adhesive (White)

PRT-12002
$5.50
48
Trimpot 10K Ohm with Knob

Trimpot 10K Ohm with Knob

COM-09806
$1.05
6
Jumper Wires - Connected 6" (M/M, 20 pack)

Jumper Wires - Connected 6" (M/M, 20 pack)

PRT-12795
$2.10
2
LED - Assorted (20 pack)

LED - Assorted (20 pack)

COM-12062
$3.95
8
Resistor 100 Ohm 1/4 Watt PTH - 20 pack (Thick Leads)

Resistor 100 Ohm 1/4 Watt PTH - 20 pack (Thick Leads)

PRT-14493
$1.25

Tessel 2

DEV-13841
4 Retired

Introducing Potentiometers

A potentiometer is a resistance-based analog sensor that changes its internal resistance based on the rotation of its knob. The potentiometer has an internal voltage divider, creating a varying voltage on the center pin. The voltage on the center pin changes as the knob is turned. You can use an analog input pin to read this changing voltage.

alt text

Hardware Hookup

Its now the fun part! It's time to start building your circuit. Let's take a look at what goes into building this circuit.

Polarized Components Pay special attention to the component’s markings indicating how to place it on the breadboard. Polarized components can only be connected to a circuit in one direction.

Build the Potentiometer Circuit

To hook up the potentiometer, attach the two outside pins to a supply voltage (3.3V in this circuit) and ground. It doesn’t matter which is connected where, as long as one is connected to power, and the other to ground. The center pin is then connected to an analog input pin so the Tessel 2 can measure changes in voltage as the knob is turned.

Note: The potentiometer included in the kit has three marks on it that will help you figure out which breadboard rows the pins are plugged into.

alt text

Having a hard time seeing the circuit? Click on the wiring diagram for a closer look.

  1. Connect the potentiometer to the breadboard. Connect the potentiometer's power pins (supply and ground) to the power rail with jumper wires and connect the middle pin to the Tessel 2's Port A, Pin 7.
  2. Connect the LED. Use a jumper wire to connect the Tessel 2's Port B, Pin 5 through the 100Ω resistor and to the LED's anode (positive, longer) leg. Connect the LED's cathode to ground on the power rail. Double-check the legs on the LED to make sure you haven't plugged it in backward!
  3. Use jumper wires to connect the Tessel 2's 3.3V and GND pins to the breadboard's power rails.

Reading Analog Sensors With Johnny-Five

Open your favorite code editor, create a file called sensor-basic.js and save it in the j5ik/ directory. Type — or copy and paste — the following JavaScript code into your sensor-basic.js file:

language:javascript
var five = require("johnny-five");
var Tessel = require("tessel-io");
var board = new five.Board({
  io: new Tessel()
});

board.on("ready", () => {
  var sensor = new five.Sensor("a7");

  sensor.on("change", () => console.log(sensor.value));
});

Type — or copy and paste — the following into your terminal:

t2 run sensor-basic.js

This isn't particularly interesting, as all it does is output the value of the sensor (integers between 0 and 1023) until the program is interrupted. yawn

alt text

You should see something similar to this. Your sensor values (0-1023) being logged in the console.

Exploring the Hardware

Analog Input

For software to be able to work with data coming from an analog sensor, it first needs to be converted from an analog signal (infinite possible values) to a digital signal (discrete set of values). The microcontroller on the Tessel 2 does this analog-to-digital conversion (ADC) for you, sampling the incoming analog voltages and converting them to a range of values between 0 (0V) and 1023 (3.3V). Values in between are quantized to the nearest integer.

Asynchronous I/O

In Experiment 1: Blink an LED, we looked at the asynchronous nature of Node.js runtime, comparing how the Arduino Programming Language's delay function is blocking, while Johnny-Five's execution model follows the Node.js convention: non-blocking. Listening for Board ready events is an example of a common asynchronous pattern. Non-blocking I/O is extremely important to the JavaScript-for-hardware story, and the Tessel 2 has an exemplary implementation.

Tessel 2's support for asynchronous, non-blocking I/O is in its hardware. In addition to the Mediatek chip (that's where your code executes), it has a second processor (Atmel® SAMD21) that is responsible for I/O.

The MediaTek processor and the Atmel coprocessor exchange messages representing I/O state. Code that executes on the MediaTek chip (the Linux user space) has asynchronous access to the state captured on SAMD21. For example, if a digital input pin goes HIGH, the SAMD21 sends a message to the MediaTek chip and any code running can elect to consume that message. When code running on the MediaTek chip wants to output values to the SAMD21 to be written to an output pin, it simply sends the message, then keeps going.

alt text

The MediaTek chip (green) and the Atmel SAMD21 (blue) allow for Asynchronous I/O on your Tessel 2.

For the purpose of our experiment, it's helpful to understand that when program code running on the MediaTek chip wants to know the value of an analog input, it sends a request for that information, registers a handler (a function) and continues on. Sometime "later" (likely within a millisecond or two), and always in a different execution turn, the SAMD21 responds with the present value of the input. The registered handler is then invoked with response value. This may happen once or repeatedly, but never causes the program to stop and wait.

Exploring the Code

Once the board has emitted the ready event, hardware inputs are ready for interaction:

language:javascript
var sensor = new five.Sensor("a7");

The first thing to do is create an instance of the Sensor class, indicating that this sensor is attached to Port A, Pin 7.

language:javascript
sensor.on("change", () => console.log(sensor.value));

Then, a handler function is registered for change events, meaning that anytime the reading changes, the function will be called.

Every time this function is called and the sensor value is displayed, it occurs in a different execution turn than the previous invocation, staying true to the asynchronous, non-blocking model. The handler function for change events simply logs sensor.value. sensor.value is a 10-bit number (0-1023), representing the last successful ADC-processed read from the Sensor object's associated analog input pin.

Note: The minimum version of Node.js that runs on Tessel 2 supports many ES2015 features, including Arrow Functions, so we're using those here to simplify the program.

Variation: Sensor Input Graphing

For this experiment, you will be "bar chart graphing" light intensity values in your terminal using the Barcli module:

barcli

barcli [bahrk-lee]

Barcli is a simple tool for displaying real-time data in the console. Multiple instances of Barcli can be stacked to show multiple axes, sensors or other data sources in an easy-to-read horizontal bar graph.

In your terminal, make sure you're inside the working directory (j5ik), then type — or copy and paste — the following command:

npm install barcli

We'll make our sensor data a little more interesting (than just numbers scrolling by). Let's visualize it as a graph displayed directly in the terminal. Open your favorite code editor, create a file called sensor-graph.js and save it in the j5ik/ directory.

Type — or copy and paste — the following JavaScript code into your sensor-graph.js file:

language:javascript
var Barcli = require("barcli");
var five = require("johnny-five");
var Tessel = require("tessel-io");
var board = new five.Board({
  io: new Tessel(),
  repl: false,
  debug: false,
});

board.on("ready", function() {
  var range = [0, 100];
  var graph = new Barcli({
    label: "My Data",
    range: range,
  });
  var sensor = new five.Sensor({
    pin: "a7",
    threshold: 5 // See notes below for detailed explanation
  });

  sensor.on("change", () => {
    graph.update(sensor.scaleTo(range));
  });
});

Type — or copy and paste — the following into your terminal:

t2 run sensor-graph.js

Once the program starts up, the terminal should display something like this:

alt text

Exploring the Code

Notice that the Board constructor call is being passed an object with two properties that we haven't seen before: repl: false and debug: false. These settings tell Johnny-Five to shut off both the "on-by-default" REPL (read–eval–print loop—an interactive prompt) and connection debugging output.

Also added is a the Barcli module:

language:javascript
var Barcli = require("barcli");

... which means it can be put to use in your program.

To create a graph, we'll need an instance of a Barcli object. First, though, we need to define a range of values that are valid for the graph:

language:javascript
var range = [0, 100];
var graph = new Barcli({
  label: "My Data",
  range: range,
});

The graph object is now able to represent values from 0 to 100. However, recall that values coming from the potentiometer will range from 0 to 1023. We've got to scale those, too, so that they can be graphed!

The way we're instantiating the Sensor object looks a bit different from the previous example. Instead of passing the constructor a String identifying the pin, like so:

language:javascript
var sensor = new five.Sensor("a7");

... we're using the options object form here, which allows us to pass a whole object full of extra information to the constructor:

language:javascript
var sensor = new five.Sensor({
  pin: "a7",
  threshold: 5
});

Let's talk about that threshold property. It defines how much a sensor's value needs to change before a change event is emitted. The default value of threshold is 1. As you saw in the first example, any change to the sensor's value -- remember, values range from 0 to 1023 and are whole numbers (integers) -- will cause a change event. For this variation, that's too much sensitivity.

Specifically, we want to define a threshold that will limit the change events to those that are relevant to our range, which is 0-100. Since we'll be scaling the sensor's value from 0-1023 to 0-100, we only care about changes of approximately every 10 steps in 1023 (obviously that's fudging a little, and you're encouraged to be more precise in your actual programs). The threshold value is exactly half of the approximate step:

  • > (value + 5)
  • < (value - 5)

Finally, the program replaces the call to console.log(...) with a call to graph.update(...) and passes the result of calling the sensor object's scaleTo(...) method with the same range as graph is using:

language:javascript
sensor.on("change", () => {
  graph.update(sensor.scaleTo(range));
});

Unpacking the line:

language:javascript
graph.update(sensor.scaleTo(range));

When invocations are nested like this, they proceed from the inside out. First, the sensor object's 10-bit value is scaled to 0-100 (range) and then that resulting value is passed to the graph object's update method.

Variation: Using Sensor Input to Create Output

For the final variation of this experiment, you'll process the sensor readings to control the brightness of the LED in your circuit. Open your favorite code editor, create a file called sensor-input-to-output.js and save it in the j5ik/ directory. Type — or copy and paste — the following JavaScript code into your sensor-input-to-output.js file:

language:javascript
var five = require("johnny-five");
var Tessel = require("tessel-io");
var board = new five.Board({
  io: new Tessel()
});

board.on("ready", function() {
  var sensor = new five.Sensor({
    pin: "a7",
    threshold: 2
  });
  var led = new five.Led("b5");

  sensor.on("change", () => {
    led.brightness(sensor.scaleTo(0, 255));
  });
});

Type — or copy and paste — the following into your terminal:

t2 run sensor-input-to-output.js

Once the program starts up, LED should display something like this:

Exploring the Code

Again, we're instantiating a Sensor object to represent the potentiometer, and defining a threshold. This time we'll set it to 2, because the rounded result of 1023 (the top of the 10-bit sensor range) divided by 255 (the top of the 8-bit brightness range) is 4, and threshold should be set to half of the full step size:

language:javascript
var sensor = new five.Sensor({
  pin: "a7",
  threshold: 2
});

Next, a new instance of the Led class is created, with Pin 5 on Port B:

language:javascript
var led = new five.Led("b5");

+The change event remains the same, but the operation within the handler is updated to call the led object's brightness(...) method, passing the sensor's value scaled from its 10-bit input range (0-1023) to the 8-bit output range (0-255) of the led:

 language:javascript
 sensor.on("change", () => {
   led.brightness(sensor.scaleTo(0, 255));
 });

Alternatively, the conversion could have been written as a bit-shifting operation, where the 10-bit value is shifted 2 bits to the right, since brightness(...) expects an 8-bit value (0-255). This bit-shifting operation is the most efficient way to scale the 10-bit analog input value to the 8-bit PWM output value:

language:javascript
0b1111111111 === 1023;
0b0011111111 === 255;
0b11111111 === 255;
(0b1111111111 >> 2) === 255;

And could be applied in our code like so:

language:javascript
sensor.on("change", () => {
  led.brightness(sensor.value >> 2);
});

This snippet means "shift the sensor.value 2 bits to the right" — the two right-most bits are discarded. Voila! An 8-bit number from a 10-bit number.

Building Further

  • Experiment with bit shifting in your Node.js console.

Reading Further