Chapter 4. Finishing the Breadboard

Now that our sensor platform both measures the current environmental conditions and looks out for motion, let’s add our final sensor and finish building our prototype.

The Sensor

The next sensor we’ll add is one to detect changes in ambient noise levels.

Electret Microphone

We’re going to use the electret microphone breakout board from Adafruit Industries (see Figure 4-1). The board comes with a capsule microphone and supporting hardware including, an op-amp and a potentiometer to allow you to adjust the gain.

Electret microphones are small, omnidirectional microphones commonly used in cell phones and laptops. They can have an extremely wide frequency response; the one we’re using has a 20Hz to 20kHz frequency response. They are small, very cheap to produce, and quite sensitive. Unfortunately, they also have some drawbacks, including a very uneven frequency response, high noise floor, and high levels of distortion. Despite that, they’re a good choice if fidelity isn’t an overriding issue for your application.

Since the electret microphone only produces a few milli-volts peak-to-peak, it needs to be coupled with an op-amp before it can be used. The Adafruit breakout board uses a Maxim MAX4466 for this purpose.

The Adafruit Electret Microphone Amplifier back with potentiometer (left) and front with the transducer (right)

Figure 4-1. The Adafruit Electret Microphone Amplifier back with potentiometer (left) and front with the transducer (right)

Wiring the Breadboard

Starting where we left off at the end of the last chapter, let’s go ahead and add the microphone to our current setup (see Figure 4-2).

Looking at the silkscreen on the Adafruit board, you’ll see that there are three connectors (see Figure 4-2 again). Looking at the front, the output (signal) is on the left, labeled OUT; the middle connector is the ground, labeled GND; and the power is on the right, labeled VCC. You’ll also see from the silkscreen that we can drive the board with anything from 2.4V up to 5.5V. That’s good, as our Arduino microcontroller board runs at 5V. We can plug the microphone board directly into the Arduino.

Connect a jumper wire from the +ve (+5V) rail to the VCC connector of the breakout board, and another from the −ve (GND) rail to the GND connector. Finally, connect another jumper wire from the OUT connector on the microphone breakout board to the A0 pin on the Arduino.

Note

The A0 pin on the Arduino is on the lefthand side of the board when you’re looking at it with the USB and power jacks facing away from you. While you can use this pin as a normal digital input/output pin, this is a special pin that can also be used for analog input.

The wiring diagram for incorporating the electret microphone

Figure 4-2. The wiring diagram for incorporating the electret microphone

You may notice in Figure 4-3 that we’ve actually wired the breadboard slightly differently from the wiring diagram in Figure 4-2. This is because our microphone breakout board came with three header pins, and instead of connecting the wires to the microphone board, we soldered the headers to it and then plugged that into the breadboard. This really simplified things.

Note

See Chapter 5 for more information about how to solder if you haven’t done any soldering before. If you don’t want to solder the header pins to the microphone board quite yet, you can probably get away with threading some jumper wires through the holes and firmly wrapping them around the board. You’ll need to make sure that they make a good connection to the pad, and that they don’t touch and short each other out.

If you’ve followed the instructions, you should have something that looks a lot like Figure 4-3.

The Arduino and breadboarded DHT-22, PIR, and microphone

Figure 4-3. The Arduino and breadboarded DHT-22, PIR, and microphone

Modifying the Software

Calibrating sound level readings so that you get a value measured in decibels is actually a really hard to do accurately, so we’re not even going to try here. In any case, we’re really only interested in knowing how the noise level around the sensor platform changes over time, rather than the absolute value of the sound pressure on the microphone.

We can determine this far more easily than measuring an absolute (calibrated) value by keeping a running average of the sound pressure on the microphone and reporting how the current reading has changed with respect to our running average.

Go ahead and make the changes highlighted in bold below to our code:

#include <DHT.h>
#define DHTTYPE DHT22
#define SILENT_VALUE 380 // starting neutral mic value (self-correcting)1

int dhtPin = 2;
DHT dht(dhtPin, DHTTYPE);

int pirPin = 3;
int pirState = LOW;   // we start, assuming no motion detected
int pirVal = 0;
int motionState = 0;

int micPin = 0;
int micVal = 0;

void setup() {2
    pinMode(dhtPin, INPUT);     // declare DHT sensor pin as input
    pinMode(pirPin, INPUT);     // declare PIR sensor pin as input

    Serial.begin(9600);         // open the serial connection to your PC
    dht.begin();
}

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(t) || isnan(h)) {
    Serial.println("Error: Failed to read from DHT");
  } else {
    Serial.print( "T = " ),
    Serial.print( t );
    Serial.print( "C, H = " );
    Serial.print( h );
    Serial.println( "%" );
  }

  pirVal = digitalRead(pirPin);  // read input value
  if(pirVal == HIGH){            // check if the input is HIGH
    if(pirState == LOW){
      // we have just turned on
      motionState = 1;
      Serial.println( "Motion started" );

      // We only want to print on the output change, not state
      pirState = HIGH;
    }
  }else{
    if(pirState == HIGH){
      // we have just turned of
      motionState = −1;
      Serial.println( "Motion ended" );

      // We only want to print on the output change, not state
      pirState = LOW;
    }
  }

  micVal = getSound();
  Serial.print( "MIC = " );
  Serial.println( micVal );

  motionState = 0; // reset motion state
  delay(2000);

}

int getSound() {
  static int average = SILENT_VALUE;3
  static int avgEnvelope = 0;4
  int avgSmoothing = 10;5
  int envSmoothing = 2;
  int numSamples = 1000;
  int envelope = 0;6
  for ( int i = 0; i< numSamples; i++ ) {
    int sound = analogRead(micPin);7
    int sampleEnvelope = abs(sound - average);
    envelope = (sampleEnvelope+envelope)/2;
    avgEnvelope = (envSmoothing * avgEnvelope + sampleEnvelope) / (envSmoothing + 1);
    average = (avgSmoothing * average + sound) / (avgSmoothing + 1);
  }
  return envelope;
}
1

The analogRead() command converts the input voltage range, 0 to 5 volts, to a digital value between 0 and 1023. Here we’re setting the “silent” value to be 380, or around 1.85V. This “background noise” level will self correct over time.

2

Since the microphone is producing an analog signal, we don’t have to initialize the A0 pin for either input or output. This isn’t necessary when we’re using the pin in analog mode, because in this mode they can be only be used for input. Without additional hardware, the Arduino cannot produce an analog signal, although it can fake it using Pulse Width Modulation (PWM) on some pins. These pins are marked with a ~ symbol on the silkscreen.

3

The running average is where the current neutral position for the microphone is stored.

4

The average envelope level is the running average for the sound pressure level.

5

Larger values of aveSmoothing give more smoothing for the average, while larger values for envSmoothing give more smoothing for the envelope.

6

The envelope is the mean sound taken over many samples.

7

Instead of digitalRead( ) we instead use analogRead( ) to get the state of the A0 pin to which our microphone connected. As mentioned above, this will return a digital value between 0 and 1023 representative of the analog voltage level present on the pin.

Once you’ve entered the code, plug your Arduino into your computer, then go ahead and compile and upload the sketch to your board.

Note

See the sections Connecting to the Board and Uploading the Sketch in Chapter 1 if you’re having trouble uploading the sketch to your Arduino board.

When you see the “Done uploading.” message in the status bar, click on the Serial Console button in the development environment to open the serial console.

Running the Software

Opening the Serial Console will reset the Arduino, at which point you should see something a lot like Figure 4-4. Every couple of seconds, a new reading of the temperature, humidity, and (sound) envelope values will be printed to the console. As before, interspaced with these will be the notifications from the PIR sensor of the beginning and end of any movement.

The Arduino should now also report noise levels in the serial console

Figure 4-4. The Arduino should now also report noise levels in the serial console

Note

Remember that the envelope value being reported is not a measurement of the absolute volume of the noise (sound pressure) on the microphone, but instead is a measurement of the change in this noise.

Adding Some LEDs

While we have haven’t made use of them much so far, as our Arduino boards have been connected directly to our laptop and we’ve been able to use the serial connection to see what’s going on, LEDs are used almost ubiquitously as a debugging tool when building hardware. Later in the book (see Chapter 7), we’re going to unhook our Arduino from our laptop and put it on the network, and we won’t be able to see the serial output from the board directly any more. So now is a good time to add some LEDs to our project to help us with debugging later on.

We’re going to use three LEDs (see Figure 4-5): one to show that our board has booted and is turned on, another to show us that it’s running our loop( ) correctly, and a final one to show us if there has been any movement detected by the PIR sensor.

Note

I’m going to use a green LED for power, and two red LEDs for the loop and motion indicator lights. You don’t have to do that; any LED will do.

Insert three LEDs into your breadboard as shown in Figure 4-5, and use a jumper wire to connect the GND pin of each of the LEDs to the −ve (GND) rail of the breadboard.

Warning

Remember that LEDs are directional components and must not be inserted backwards. Look carefully at the two legs of the LED; one should be shorter than the other. The shorter leg corresponds to the ground, while the longer leg is the positive.

Then, in a similar manner to the way we wired the pull-up resistor we used for the DHT-22 sensor, connect a 220Ω resistor to the positive pin of each of the three LEDs, bridging the gap between the two sides of the breadboard. Then use a jumper wire to connect the three LEDs to PINs 13, 9, and 8 on the Arduino board, respectively going left-to-right (see Figure 4-5 again if needed).

The wiring diagram with added LEDs

Figure 4-5. The wiring diagram with added LEDs

Note

A resistor is needed inline between the LED and the Arduino to limit the current flowing across the resistor; if you drive an LED with too high a current, you will destroy it. If you think back to Chapter 1, you’ll remember that we plugged an LED directly into PIN 13 of the Arduino board. While it’s generally okay to do that, it’s not good practice, and you should avoid it now that you know better.

If you’ve followed the instructions and the wired the breadboard as laid out in Figure 4-5, you should have something that looks a lot like Figure 4-6.

The Arduino and breadboarded sensors with some added LEDs

Figure 4-6. The Arduino and breadboarded sensors with some added LEDs

Now we’re changed the hardware, it’s time to modify our software.

Modifying the Software

The changes to our Arduino sketch are pretty self-explanatory. We’ll set up the three LED pins as OUTPUT in the setup() function and then toggle them HIGH and LOW depending on what we want to indicate.

Make the following changes shown in bold to your code:

#include <DHT.h>
#define DHTTYPE DHT22
#define SILENT_VALUE 380  // starting neutral microphone value (self-correcting)

int dhtPin = 2;
DHT dht(dhtPin, DHTTYPE);

int pirPin = 3;
int pirState = LOW;   // we start, assuming no motion detected
int pirVal = 0;
int motionState = 0;

int micPin = 0;
int micVal = 0;

int powPin = 13;
int ledPin = 8;
int motPin = 9;

void setup() {
    pinMode(dhtPin, INPUT);     // declare DHT sensor pin as input
    pinMode(pirPin, INPUT);     // declare PIR sensor pin as input

    pinMode(powPin, OUTPUT);    // Power LED
    pinMode(ledPin, OUTPUT);    // Rx/Tx LED
    pinMode(motPin, OUTPUT);    // Motion Detected LED

    Serial.begin(9600);         // open the serial connection to your PC
    dht.begin();
    digitalWrite(powPin, HIGH);

}

void loop() {
  digitalWrite(ledPin, HIGH);

  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(t) || isnan(h)) {
    Serial.println("Error: Failed to read from DHT");
  } else {
    Serial.print( "T = " ),
    Serial.print( t );
    Serial.print( "C, H = " );
    Serial.print( h );
    Serial.println( "%" );
  }

  pirVal = digitalRead(pirPin);  // read input value
  if(pirVal == HIGH){            // check if the input is HIGH
    if(pirState == LOW){
      // we have just turned on
      motionState = 1;
      digitalWrite(motPin, HIGH);  // turn LED ON
      Serial.println( "Motion started" );


      // We only want to print on the output change, not state
      pirState = HIGH;
    }
  }else{
    if(pirState == HIGH){
      // we have just turned off
      motionState = −1;
      digitalWrite(motPin, LOW);  // turn LED OFF
      Serial.println( "Motion ended" );

      // We only want to print on the output change, not state
      pirState = LOW;
    }
  }

  micVal = getSound();
  Serial.print( "MIC = " );
  Serial.println( micVal );

  motionState = 0; // reset motion state
  digitalWrite(ledPin, LOW);
  delay(2000);

}

int getSound() {
  static int average = SILENT_VALUE;
  static int avgEnvelope = 0;
  int avgSmoothing = 10;
  int envSmoothing = 2;
  int numSamples=1000;
  int envelope=0;
  for (int i=0; i<numSamples; i++) {
    int sound=analogRead(micPin);
    int sampleEnvelope = abs(sound - average);
    envelope = (sampleEnvelope+envelope)/2;
    avgEnvelope = (envSmoothing * avgEnvelope + sampleEnvelope) / 
                  (envSmoothing + 1);
    average = (avgSmoothing * average + sound) / (avgSmoothing + 1);
  }
  return envelope;
}

Once you’ve entered the code, plug your Arduino into your computer, then go ahead and compile and upload the sketch to your board.

Note

See the sections Connecting to the Board and Uploading the Sketch in Chapter 1 if you’re having trouble uploading the sketch to your Arduino board.

When you see the “Done uploading.” message in the status bar, click on the Serial Console button in the development environment to open the serial console.

Running the Software

If you open up the serial console in the Arduino development environment, everything should pretty much be exactly the same as before. However, this time there should be some blinking lights to accompany your data (see Figure 4-7).

The LEDs show the current state of the sketch, and are helpful for monitoring our board when we unplug it from our laptop

Figure 4-7. The LEDs show the current state of the sketch, and are helpful for monitoring our board when we unplug it from our laptop

The leftmost LED should turn on at the end of the setup() function and stay on: this is the LED we’re using to indicate that the power to the board is on and our sketch has started. The rightmost LED should be pulled high (and thus turn on) at the start of the loop() function, and then be pulled low (and thus turn off) after the loop completes. This means that the LED will slowly blink on and off, showing us that data is being taken. Finally, the middle LED should be pulled high (and turn on) when the PIR sensor detects the start of motion, and then low again (and turn off) at the end of motion.

Making the Output Machine-Readable

Right now the output of our sketch to the serial port was designed to be read by a human. However, in the next chapter we’re going use some Python code to collect our data and save it to a CSV file on disk so we can examine it afterwards, and do some visualization. For that, we’re going to have to make some changes to the output of our sketch.

We only really need to modify serial output in the loop( )—the rest of our code can stay the same:

void loop() {
  digitalWrite(ledPin, HIGH);

  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(t) || isnan(h)) {
    // Don't output temperature and humidity readings
  } else {
    Serial.print( t );
    Serial.print( ", " );
    Serial.print( h );
    Serial.print( ", " );
  }

  pirVal = digitalRead(pirPin);  // read input value
  if(pirVal == HIGH){            // check if the input is HIGH
    if(pirState == LOW){
      // we have just turned on
      motionState = 1;
      digitalWrite(motPin, HIGH);  // turn LED ON

      // We only want to print on the output change, not state
      pirState = HIGH;
    }
  }else{
    if(pirState == HIGH){
      // we have just turned off
      motionState = −1;
      digitalWrite(motPin, LOW);  // turn LED OFF

      // We only want to print on the output change, not state
      pirState = LOW;
    }
  }

  micVal = getSound();
  Serial.print( micVal );
  Serial.print( ", " );

  Serial.print( pirVal );
  Serial.print( ", " );
  Serial.println( motionState );

  motionState = 0; // reset motion state
  digitalWrite(ledPin, LOW);
  delay(2000);

}

After making these changes, re-upload the sketch to your hardware. If you reopen the serial console, the output from the sketch should be changed from this:

T = 22.60C, H = 42.50%
MIC = 14
T = 22.50C, H = 42.60%
MIC = 25
Motion Started
T = 22.50C, H = 42.60%
MIC = 25
T = 22.60C, H = 42.60%
MIC = 30
T = 22.70C, H = 42.70%
MIC = 114
Motion Ended
T = 22.60C, H = 42.70%
MIC = 20

to something more like this:

22.60, 42.50, 14, 0, 0
22.50, 42.60, 25, 1, 11
22.60, 42.60, 30, 1, 0
22.70, 42.70, 114, 1, −1
22.60, 42.70, 20, 0, 0
1

Here we’re using the previously unused motionState variable to show the start and end of a PIR motion event. A 1 denotes the start of motion, and a −1 denotes the end of motion.

…as we can see in Figure 4-8.

The Arduino should now also report noise levels in the serial console

Figure 4-8. The Arduino should now also report noise levels in the serial console

This will be a lot easier to read into Python—or any other sort of code—than the more human-friendly but hard-to-parse output we had before.

Communicating with Python

We will use Python as a quick tool to talk to our Arduino. If you haven’t used Python before, there are many tutorials and books that will help get you started. Our intention here is to give you the code required to connect to the Arduino and start receiving data.

First, make sure you have the pySerial module installed on your computer. Although the Arduino is connected via a USB port, it uses the computer’s serial interface protocols for communication.

To talk to the Arduino, we need to find the port that the Arduino is connected to. On a typical Macintosh, the Arduino will show up as a USB modem device:

%python -m serial.tools.list_ports
/dev/tty.Bluetooth-Modem
/dev/tty.Bluetooth-PDA-Sync
/dev/tty.usbmodem621
3 ports found
%

Your ports may not look exactly like this, but should be similar. Once you have found a USB modem device that looks promising, use the ser command to open the serial port in order to talk to the Arduino. Make sure you have first imported the serial library into Python.

>>> import serial
>>> ser = serial.Serial('/dev/tty.usbmodem621')

If you get any errors, try another one of the serial ports. If that doesn’t work, make sure no other devices are currently using the serial port in question. Unplug and then reconnect your Arduino to make sure the port is not in use. Once you have successfully opened the serial port, the following quick test will confirm a working connection:

>>> x = ser.read(30)
>>> print x

If you are running the demo code from the sensor mote project, you should see a printout that looks like this:

22.60, 42.50, 14, 0, 0

After you have tested your connection using the Python command line, feel free to close the serial port:

>>> ser.close()

We can take our Python code a step further. The following Python script will collect time-stamped data from your sensor mote and save it to a .csv file:

import serial
import time

ser = serial.Serial('/dev/tty.usbmodem621', 9600, timeout=1)
time.sleep(1)

logfile = open('test.csv', 'a')

count = 0
while (count < 5):
    count = count + 1
    line = ser.readline()
    now = time.strftime("%d/%m/%Y %H:%M:%S", time.localtime())
    a =  "%s, %s, %s" % (now, line, "\n")
    print a
    logfile.write(a)
    logfile.flush()
logfile.close()
ser.close()

Once you have confirmed that you can collect data and send it to a log file, you have completed the steps required to build a sensor mote by hand and connect it to your computer.

Note

If you are interested in being able to move your data from the Arduino to a TCP connection, this can be done simply by downloading and implementing a Python TCP to Serial bridge example.

Summary

In this chapter we’ve added our final sensor to the breadboard, finalized our software and hardware design, and ran some simple tests with Python. In the next chapter we’re going to move our project off the breadboard.

Get Distributed Network Data now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.