Introduction: IoT Workshop: Lab 3 - Controlling Output With Input

About: Director, Strategic Engagements, DX-TED, Microsoft

In this lab you will combine the ideas that you learned in the previous labs (here and here) to make a LED change its brightness based on the level of light available - essentially a nightlight. You will combine analogRead() capability with analogWrite() capability an you will learn about Pulse Width Modulation (PWM).

Step 1: Bill of Materials

What you will need (all the parts from the previous lessons):

*For this lesson series you are using an Arduino Yun. The reason for using this vs. other less expensive Arduino boards is because in future lessons you will make use of the fact that the Arduiono Yuin has on-board Wi-Fi and a Linux distribution. Neither of those capabilities are needed for this lesson, so if you have a different Arduino board (e.g. an Arduino Uno) you can use it. The SparkFun Inventor's Kit (for Arduino Uno) is a good kit with lots of parts (LEDs, resistors, servos, etc.), but it ships with an Arduino Uno instead of the Yun (the Uno doesn't have onboard Wi-Fi or the Linux distribution we will use in later lessons).

Step 2: Wiring the Board

This lessons combines the previous two lessons, so its likely that you already have most of the wiring done from the previous lesson. Wire the Arduino according to the picture at the beginning of this lesson.

Resistors

The 10k Ohm resistor is one part of the voltage divider, working in partnership with the photoresistor.

The 330 Ohm resistor connects to the negative (shorter) lead of the LED.

Wires

This is where building a habit of connecting positive (5V) and negative (GND) pins from the Arduino to the breadboard side-rails starts to pay off. In this lab the pin coming from GND to the negative side-rail supports both the photoresistor circuit and the LED circuit.

  1. Connect 5V to the red/positive side rail.
  2. Connect GND to the blue/negative side rail.
  3. Connect the red/positive side rail to one end of the 10k resistor.
  4. Connect the other end of the 10k Ohm resistor to both one end of the photoresistor and to analog pin 0 (A0).
  5. Connect the other end of the photoresistor to the blue/negative side rail.
  6. Connect digital pin 13 to the positive lead of the LED (the longer lead is the positive lead).
  7. Connect the other lead from the LED to the 330 Ohm resistor.
  8. Connect the other end of the 330 Ohm resistor to the blue/negative side rail.

Step 3: Using Pulse Width Modulation (PWM)

Pulse Width Modulation (PWM) is a technique for simulating analog values on a digital pin. There are several digital pins on Arduino boards that support PWM depending on the board you are using. For example, the ONE or Yun we are using in this workshop supports 8-bit (0-255) PWM on digital pins 3, 5, 6, 9, 10, 11, and 13 using the analogWrite() function.

PWM simulates analog data by creating a square wave (basically a repeating switch between on and off) where the duration of 'on' time is the pulse width. If the square wave has a 50% pulse width (more commonly known as a duty cycle), then the output from that pin is equal amounts on and off. If the duty cycle is 25% then the output from the pin will be on for only one-quarter of the duty cycle (inversely it will be off for three times as long as it is on - 25% on, 75% off).

Because the time windows of a cycle is too fast for the human eye to perceive (about 2 milliseconds on the Arduino pins that support PWM), instead of causing an LED to strobe or flicker, it simply appears to be more or less bright. Using a 25% duty cycle the LED would be on (HIGH output) for half a millisecond and off (LOW output) for 1.5 milliseconds which makes the LED appear to be at 25% brightness. So while we aren't truly sending analog data to a digital LED, we are using PWM to simulate the effect of analog data.

Step 4: Writing the Code

For this lab you will create a new file named lab003.js in the same directory as you did in the previous labs. There are no additional dependencies, so we don't need to make any changes to the package.json file.

In the lab003.js file start by declaring the key objects, including a variable for the LED pin and the analog pin you will use (digital pin 13 for the LED and analog pin A0 for the photoresistor - if you still have your project board wired up from the previous labs then you should be all set). You should also stub out the board.on() callback function for Johnny Five.

var five = require("johnny-five");
var board = new five.Board();
var LEDPIN = 13;
var ANALOGPIN = 0;

board.on("ready", function() {
  // The next code will go here
});

// You will add a couple functions here later in the lab

Inside of the board.on() function you will first initialize the digital pin that you will use for the LED as a pulse width modulation (PWM) pin.

board.on("ready", function() {
  // Set pin 13 to PWM mode
  this.pinMode(LEDPIN, five.Pin.PWM);
 
});

Next you will use the analogRead() function to capture the data coming from the photoresistor. In Lab 2 you simply wrote the data out to the console log. For this lab you will use the data to determine how bright the LED should be. The concept is the same, but the callback function you write this time will have a little more to it (but not much).

First, define the analogRead() function and the callback function that will be invoked when data input is received. The format is this.analogRead( pinNumber, callbackFunction );

  // read the input on analog pin 0:
  this.analogRead(ANALOGPIN, function(val) {
    // The next code will go here
  });

This function tells the application to read the data from the analog pin (0 in this lab) and when input is collected, invoke the callback function in the second argument and pass the input dat in the val argument.

Your goal is to create an application that increases the LED brightness as the ambient room light decreases. If it is light in the room, you don't want the LED to illuminate, and the darker it gets the brighter you want the LED to get. You also want some threshold of ambient light where the LED turns off (i.e. you don't want to have the LED faintly illuminated in a moderately bright room - it just wears the LED out). The way to do this is to map the incoming voltage from the photoresistor to the output voltage for the LED. There is a mismatch though. The photoresistor input is in a 16-bit range of 0-1023 and the PWM supported by our board is an 8-bit range of 0-255. You could simply divide the input value in half but you still need to account for the minimum threshold.

If you were doing this in C using the Arduino structures, values and functions, without using frameworks such as Johnny-Five, you could use the map() function to map the input value into a new data range, such as mapping the analog input value (ranged to 0-1023) to a new range of 0-255. You can do that here - you just have to write the map() function yourself. Add the following code to the bottom of the lab003.js file (after the end of the board.on() function.

// This function maps a value from one range into another range
// Example: map (25, 0, 25, 0, 50) returns 50
// Example: map (20, 0, 100, 0, 10) returns 2
function map(x, in_min, in_max, out_min, out_max) {
  return Math.round((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}

This is the exact formula used by the Arduino. Using this function you can pass in a value and its predefined range, and the desired data range and it will return the appropriate value for that range.

The map() function will return negative values so you need to also recreate the Arduino constrain() function, which will enable you to ensure the value is constrained to a minimum and maximum value. Add the following code immediately after the map() function.

// This function ensures a value is within a defined range
function constrain(x, in_min, in_max) {
  return Math.round(x < in_min ? in_min : x > in_max ? in_max : x);
}

Now the you have written the supporting functions for map() and constrain() you can write the callback function for the analogRead() function you started writing inside the board.on() function.. First you need to use the map() function to map the incoming value from the photoresistor to a brightness value for the LED.

  // read the input on analog pin 0:
  this.analogRead(ANALOGPIN, function(val) {
    // Map the analog value (0-1023) to an 8-bit value (0-255)
    // so it can be used to define the LED output.
    var brightness = map(val, 350, 1023, 0, 255);

  });

When you set the brightness value you mapped the incoming data from a range of 350-1023 to 0-255. In doing so you effectively set the ambient light value of 350 to a brightness of 0 (since 350 maps to 0). If the value coming in from the photoresistor is below 350 then the map() function will return a negative value. To account for the potential for a negative value you can constrain the value to the 0-255 range using the constrain() function you wrote.

  // read the input on analog pin 0:
  this.analogRead(ANALOGPIN, function(val) {
    // Map the analog value (0-1023) to an 8-bit value (0-255)
    // so it can be used to define the LED output.
    var brightness = map(val, 350, 1023, 0, 255);

    // Use the constrain function to ensure the right values
    brightness = constrain(brightness, 0, 255);

  });

This will ensure that the value of brightness is between 0 and 255.

The final step is to set the value of the OUTPUT pin to the value of brightness. In Lab 1 you used digitalWrite() to set the OUTPUT pin to either HIGH or LOW (1 or 0). Since you defined the LED OUTPUT pin as a PWM pin you will use analogWrite() instead, which will tell the board to simulate an analog device using PWM. For debugging purposes you can also add a console.log() call.

  // read the input on analog pin 0:
  this.analogRead(ANALOGPIN, function(val) {
    // Map the analog value (0-1023) to an 8-bit value (0-255)
    // so it can be used to define the LED output.
    var brightness = map(val, 350, 1023, 0, 255);

    // Use the constrain function to ensure the right values
    brightness = constrain(brightness, 0, 255);

    console.log('val: ' + (val * (5.0 / 1024.0)) + '; brightness: ' + brightness);

    // Set the brigthness of the LED
    this.analogWrite(ledPin, brightness);
    
  });

That's all the code - now you are ready to run the application.

Step 5: Run the Application

To run the application, plug the Linino ONE/Arduino Yun into your computer with the USB cable. Open a terminal window (Mac OS X) or Node.js command prompt (Windows) and execute the following commands (replace C:\Development\IoTWorkshop with the path that leads to your Workshop folder):

cd C:\Development\IoTLabs
node lab003.js

You should see some lights on the board blink a little as the app is initialized, and then you should see something like the following in the terminal/console window (the actual values will depend on how much light the photoresistor is receiving):

c:\Development\IoTLabs>node lab003.js
1429678663007 Device(s) COM3
1429678663038 Connected COM3
1429678668085 Repl Initialized
>> val: 1.5478515625; brightness: 0
val: 1.552734375; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.552734375; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0
val: 1.5478515625; brightness: 0

With the application running try changing the amount of light the photoresistor is receiving by slowly covering it with your hand. As the amount of light it detects reduces, the resistance will decrease and more voltage will pass into the INPUT pin. As that happens, thanks to your code, the voltage to the LED over the OUTPUT pin will increase and the LED will glow brighter. You should also notice that there is a minimum amount of light that will cause the LED to turn off all together. That is because of the mapping of the input value of 350 to the output value of 0 (if the room you are in is too dim and the LED is never shutting off, you can increase 350 to something like 500 and see what happens).

Step 6: Conclusion and Next Steps

Congratulations! You made a device that both detects its environment and responds to it. You learned about Pulse Width Modulation and the analogWrite() function for simulating analog behavior on a digital device, and you learned how to recreate the Arduino map() and constrain() functions.

In Lab 4 you will learn how to start sending data from your Arduino to the Cloud.