Desktop Pal with "stereo" ultrasonic sensors and PIR detectorImportant! You must also download and install several Arduino libraries, required by the sketches in this installment. These libraries are provided in Lesson 3, so if you’ve been following along in this series, you should already have them. This lesson does not require any additional libraries other than those provided for in Lesson 3.
Remember: When adding new libraries, you must exit the Arduino IDE if it is open. Once you have copied he library files to the Arduino sketchbook libraries folder, you may restart the Arduino IDE.
Tip: In my prototype Desktop Pal, I used an enclosure box measuring 8” by 8”. This provides a five inch separation of the two ultrasonic sensors (measured at their centers). That’s enough for a good stereoscopic spread. If your enclosure is smaller, you may want to think about moving it into a larger one, or else putting the two ultrasonic sensors on “side booms” to help spread them out.
Wiring diagram for the left ultrasonic sensorWarning: Be absolutely certain not to cross the connections to the sensor. Be especially sure you don’t mix the G and V pins on the 3-pin header, or else you could damage the sensor, and possibly your Itead Leonardo board! The G (for ground) and V (for V+) are the power connections, and mixing them up can lead to bad things.
Tip: The PIR sensor I’ve selected for the Desktop Pal project has its own blue LED, which glows whenever the sensor is activated (ON). You can use this LED to verify operation of the sensor.
PIR sensors are said to detect the heat given off by humans and other warm-bodied critters. That’s somewhat true, but not entirely accurate; understanding how they work can aid in predicting when and why the sensor may be activated. Inside the PIR module is a set of two infrared-sensitive detectors. These detectors are made to respond to infrared radiation emitted by or reflected from objects, living or not. While certain kinds of infrared light may be associated with “heat” (the sun, an infrared heat lamp), PIR sensors don’t actually measure this heat. They are sensitive to radiation in the infrared end of the electromagnetic spectrum. By comparing changes between the two sensors, the module can determine likely motion. If you stand perfectly still in front a PIR sensor, it will no longer “see” you no matter how hot you are, because you’ve not moving. Once you flex a muscle, that new movement is detected, and you’ve been found. Aiding the detection range is a (usually) white plastic lens that collects the infrared radiation from the desired field of view. Most PIR sensors are designed to have a detection “cone” of 70 to 120 degrees, sometimes even more. (The sensor selected for Desktop Pal has a sensing angle of 100 degrees.) While PIR sensors are very good at ignoring common light sources, they can be triggered by sudden glints of sunlight or incandescent light, curtains fluttering in the wind, even air currents from a heat register in the wall, floor, or ceiling. For best results, aim your Desktop Pal so its PIR sensor is away from anything that might wrongly influence its motion detection.
Passive infrared detector module attached to side of the Desktop Pal
Wiring diagram for the passive infrared (PIR) detector moduleTip: Because the buzzer is “active LOW,” it will produce sound when the Pin 13 LED is off. This is the usual state of the LED when not programmed otherwise, and when no sketch is being uploaded. Because of this, the buzzer will always sound unless you add code to turn it off. More about this in a moment.
Note that some documentation for the Electronic Brick Buzzer reverses the function of the LOW and HIGH signals. In this module, the sound is indeed emitted when the pin goes LOW.
Wiring diagram for the electronic brick buzzer module
Electronic Brick Buzzer taped to the inside front of the Desktop Pal enclosureTip: Be sure to keep ready access to the servo extension on Pin 13 of the Itead Leonardo, so you can disconnect the buzzer when you need to, like when developing sketches.
Note: The code below is not commented in the interest of brevity. The actual .ino sketch file contains embedded comments for you to review.
void setup() {
pinMode (13, OUTPUT);
digitalWrite(13, HIGH);
}
void loop() {
digitalWrite(13, LOW);
delay(200);
digitalWrite(13, HIGH);
delay(2000);
}
The sketch begins by using the setup() function to make pin 13 an OUTPUT, and setting its value to HIGH. This stops the buzzer from making any output.
In the loop() function, pin 13 is toggled between LOW and HIGH. Tone is emitted for 200 milliseconds, followed by 2 seconds of silence.
Once tested, if you’d like to unset the tones, comment out the four lines in the loop() function, and re-upload the sketch. The buzzer will now only sound during sketch uploading, and once or twice briefly when the Itead Leonardo first powers up.
Tip: In addition to simple on/off tone, the buzzer can recreate simple musical notes by using pulse width modulation (PWM). I’ll cover this feature in an upcoming lesson.
Tip: If all you see is a series of “0 cm” readings, or other readings that don’t change regardless of the distance between the sensor and your hand, the sensor may not be connected properly. Recheck your wiring.
This sketch is a modified version of the PalUltrasonicTest sketch you tried in Lesson 2, so I won’t go into additional detail here, except to note the lineUltrasonic ultrasonic(10,11);which sets up the sensor to use pins 10 and 11 for the trigger and echo connections to this sensor. For the right sensor, the sketch used pins 5 and 6.
Ultrasonic ultrasonicR(5,6); Ultrasonic ultrasonicL(10,11);The sketch begins by creating two objects, one for each ultrasonic sensor. For ease of use, I’ve named the right sensor ultrasonicR, and the left sensor ultrasonicL. Clever, no?!
void loop() {
int sonicValR = getUltrasonic(ultrasonicR);
delay (40);
int sonicValL = getUltrasonic(ultrasonicL);
Serial.print(sonicValL);
Serial.print(",");
Serial.println(sonicValR);
delay(1000);
}
The loop function repeatedly grabs values from both sensors, and then prints them in the Serial Monitor window. Take note of a few things in this code:
int getUltrasonic(Ultrasonic sensor) {
int total = 0;
int sonicVal = 0;
int counter = 0;
while (counter < 3) {
delay (38);
sonicVal = sensor.Ranging(CM);
if (sonicVal < 500) {
total = total + sonicVal;
counter++;
}
}
return (total / 3);
}
The sketch packages the actual reading of the ultrasonic sensors in a user-defined function, getUltrasonic. The function first “passes in” a reference to the sensor object to use, either the right sensor, or the left sensor. By passing this value to the function, the one function can be used with any number of ultrasonic sensors connected to the Arduino.
This reference is in the form of:
( DataType parameter )where DataType defines the type of data being passed into the function, and parameter is a variable that’s used in the function to represents the ultrasonic sensor to read. The specific sensor to read is defined when the function is called from within the loop(). Example:
getUltrasonic(ultrasonicR)This line passes the ultrasonicR object to the getUltrasonic function – this is the sensor that’s read that time around. Then,
getUltrasonic(ultrasonicL)passes the ultrasonicL object to the function, and it’s the left sensor that gets now read. The main guts of the getUltrasonic function is a while loop that collects a total of three separate readings from the sensor. If the reading is less than 500, it’s used to calculate an average of three separate reads. The averaging – and tossing out any value over 500 – is meant to ignore any possible noise or glitches in the values retrieved from the sensor.
Important: For this test, I am assuming you’ve already read through Lesson 3 of this series, where you installed the Metro timer library described in that installment. This sketch will not work if the Metro library is missing.
#include <Metro.h> Metro dozeMetro = Metro(1000);These lines add a reference to the Metro timer library, and create a new object (named dozeMetro) which has a one second interval. This interval causes the dozeMetro object to elapse once every second; it provides a nice way to keep track of time events without a lot of extra coding on our part. Recall from Lesson 2 that the Desktop Pal’s side switch is used with an Arduino interrupt, to make sure that any triggers of the switch are not missed because the code is busy doing something else. The same concept is used with the PIR sensor:
volatile int dozer = 0; volatile boolean doze = false;
attachInterrupt(digitalPinToInterrupt(3), triggerPIR, RISING);The first two lines create global variables that are used later in an Arduino interrupt service routine (otherwise known as an ISR). The ISR is just a user-defined function that is registered ahead of time with the Arduino, so the board knows what to do when the interrupt occurs. In this code, when an interrupt occurs on pin 3, the processor must put everything else on hold, and immediately branch off to a function named triggerPIR. The RISING in the line of code specifies the pin change that causes the interrupt to occur. Valid choices here are HIGH and LOW, for when the pin goes to either HIGH or LOW, CHANGING for when there is any change in the pin, and RISING or FALLING, which trigger on the LOW/HIGH or HIGH/LOW transition, respectively.
Tip: The volatile keyword used when defining the dozer and doze variables forewarn the Arduino compiler that these values may be subject to change from outside the main execution of the processor (in this case, the ISR).
Using volatile instructs the compiler that these variables must always use the Arduino’s RAM memory, rather than one of its hardware registers (a kind of temporary holding tank). The values of the variables are thus guaranteed to be accurate at any time, regardless of how they are changed in the sketch.
The interrupt service routine for the PIR sensor is simple:void triggerPIR() {
dozer = 0;
doze = false;
}
When the interrupt occurs, the code resets both the dozer timer counter, and the doze flag variable.
Finally, inside the loop() function is a while loop that repeats as long as the doze flag is set to false, and only when the dozeMetro interval has transpired:
while (!doze && dozeMetro.check() == 1) {
if (dozer++ >= 10) {
doze = true;
}
if (digitalRead(3) == 1) {
dozer = 0;
doze = false;
}
Serial.print (dozer);
Serial.print (":");
Serial.println (doze);
delay(5);
}
The code
dozeMetro.check() == 1executes the innards of the while loop only once every second, but without having to use a delay statement (the delay statement that’s there is just for testing purposes). When not dozing, the dozer counter is incremented by 1s, once a second. If dozer reaches 10, Pal dozes off until the PIR sensor turns it back off again. The line:
if (digitalRead(3) == 1)resets the dozer and doze variables as long as the PIR sensor continues to register motion.