Final Project: Intelligent Hummingbird Feeder

Check out this video footage (with sound) 

Still Images Slideshow (no sound)

[with thanks to John Solaperto for help compiling video]


HB Photo 3 Feeder Assembly Photo
HB Photo 14
Example of image captured on webcam
Feeder Assembly showing main feeder (red), electronics box (brown), Ping sensor (2 black circles) and webcam. Example of image captured on webcam




Back to Index


Project Summary

The feeder consists of a 3D printed main feeder for containing feeding liquid. A Ping Ultrasonic sensor detects bird arrival at the feeder and sends serial data to a Raspberry Pi board. The Pi board uses Python code to record the bird visit in a log file with a time stamp. In addition, the Python code triggers a webcam to take a photo of the bird.



Design Goals

Back to Top

Overall Results

This was a very successful project and all design goals were achieved. The feeder works well in lab environment. At time of writing, feeder had just been located outside and so far hummingbirds are proving elusive using Ping Sensor. A switch to using the webcam with motion detect software was more successful.

Below are some snapshots that show how the Ping Sensor system works:

WinSCP Capture
WinSCP screen capture (local file on left, Pi files on right showing 4 successive image captures)


Birdlog capture
Birdlog screen capture showing 4 successive visit logs


Birdshot
One of the 4 image captures with 3D printed hummingbird


Back to Top

Main Feeder Design

The feeder design consists of two components, a feeder base and a feeder cover. Both were designed in Solidworks. Both parts were printed on a Stratasys Prodigy Plus 3D Printer using ABS Plastic. More information on the printing process is contained in the weekly update for 3D scanning and printing.

Design Features include a locking mechanism for top cover, hanging hooks and bird perches.


Feeder Base and Cover
Feeder Base and Cover Photo (diameter 7 inches)

Feeder Design Solidworks Assembly File

Back to Top

Electronics Box

The Electronics Box was designed to contain the electronic components (Pi board, sensor board, sensor, power brick and camera).

Features



Electronics Box
Electronics Box showing holes with Ping sensor


Box Top View
Electronics Box top view showing all components inside

Back to Top

Sensor Board


The sensor board was designed in Eagle. The Arduino base outline was imported from Adafruit library and the design was developed from there. This board plugs directly into an Arduino Uno board. The caps and resistors were through hole parts soldered to surface mount pads (due to availability) which is a relatively easy process. The large copper pad around the outside was turned into a ground plane by soldering a wire to the ground pin as shown in the photo. The board consists of  3 channels intended for 3 separate Capsense signals.

Arduino shield
Sensor Board (Arduino shield)

Back to Top

Raspberry Pi Board

Pi Board
Raspberry Pi Board

The Raspberry Pi board is a computer board based on Broadcom ship technology. For a more detailed background see the Wikipedia summary.
The setup of the Raspberry Pi board is time consuming but is well documented. This was summarized in my Networking and Communications weekly summary.

The Raspberry Pi board has a lot of functionality and this application utilizes only a limited amount of that functionality. The primary need for the Pi in this project was to drive the camera.

Back to Top

Communication

Laptop to Raspberry Pi

The process of setting up the connections to the Raspberry Pi is time consuming (for list of inputs see Networking and Communication Weekly Update). An alternative is to work on the Pi remotely from any location and this can be done using VNC (Virtual Network Computing). This requires a VNC download to the Pi and also to the PC to be used.


Instructions for downloading Tight VNC Package on to Raspberry Pi
http://elinux.org/RPi_VNC_Server

Download Tightvnc to laptop
http://www.tightvnc.com/download.php

File Transfer

An additional challenge is to transfer files from the Pi. One possibility is to use webmail but this proved to be too slow. Another possibility is to add an email service on to the Pi. My solution was to use WinSCP which provides an elegant window to drag and drop files from Pi to laptop.

Controller Board to Raspberry Pi

Communication from the Pi board to the Controller (Arduino) board is via Serial USB.

Back to Top

Ping Ultrasonic Sensor

The Ping Ultrasonic sensor measures distance by triggering an ultrasonic burst and then listening for the return pulse. It measures the time required for the return pulse. Software on the controller board can then be used to convert this time delay into actual distance. It is then easy to create a threshold to trigger some action when an object comes within a specific distance from the sensor. The sensor used in this project is manufactured by Parallex.


Ping sensor
Ping Sensor


Back to Top

Camera/Webcam



Pi camera
MS
        Webcam
Pi camera (left) and Microsoft Webcam
(right) - not to scale

The original intent was to use the Raspberry Pi camera module. However, two devices in a row failed (suspected static damage) and so the design was modified to incorporate a Micorsoft Webcam which was available unused in my home office. The webcam has a USB cable which can be plugged directly into the Pi board.
SimpleCV was downloaded to the Raspberry Pi to interface with the Webcam and Python commands were used to control the camera.


Back to Top

Motion Detect Software

The webcam and Ping sensor design worked well in the lab, however the birds did not appear to like the setup. Possible reasons are the visual appearance of the sensors (look like big black eyes) or perhaps the birds are repelled by the ultrasound pings. In any case, an excellent workaround is to use motion detect software on the Pi. This eliminates the need for a sensor and a controller (Arduino) board. There are a number of good tutorials available including this one. This motion detect software enabled the videos and stills listed above.


Capacitive Sense


The original design intent was to use capacitive sensing to detect the arrival of a bird at the feeder. The capacitive sensing technique is well explained well in the Arduino Capsense library. At a high level, the method is based on measuring the time difference between a state change in a Send pin and a Receive pin. This time will depend on the capacitance used in the system plus any capacitance provided by a human touch or proximity.



Issues Encountered with Capacitive Sensing


Capsense pads
Sample of pad designs

Capsense Code

The code used in this project was based on the Capsense libarary but also added a calibaration routine to determine a baseline threshold.

#include <CapacitiveSensor.h> 


/*
* CapitiveSense Library Demo Sketch
* Paul Badger 2008
* Uses a high value resistor e.g. 10M between send pin and receive pin
* Resistor effects sensitivity, experiment with values, 50K - 50M. Larger resistor values yield larger sensor values.
* Receive pin is the sensor pin - try different amounts of foil/metal on this pin

*/
int sensor_1_baseline = 0;
int sensor_2_baseline = 0;
int sensor_3_baseline = 0;
CapacitiveSensor   cs_4_2 = CapacitiveSensor(4,2);        // 10M resistor between pins 4 & 2, pin 2 is sensor pin, add a wire and or foil if desired
CapacitiveSensor   cs_4_6 = CapacitiveSensor(4,6);        // 10M resistor between pins 4 & 6, pin 6 is sensor pin, add a wire and or foil
CapacitiveSensor   cs_4_8 = CapacitiveSensor(4,8);        // 10M resistor between pins 4 & 8, pin 8 is sensor pin, add a wire and or foil

void setup()                   
{
   cs_4_2.set_CS_AutocaL_Millis(0xFFFFFFFF);     // turn off autocalibrate on channel 1 - just as an example
   cs_4_6.set_CS_AutocaL_Millis(0xFFFFFFFF);     // turn off autocalibrate on channel 1 - just as an example
   cs_4_8.set_CS_AutocaL_Millis(0xFFFFFFFF);     // turn off autocalibrate on channel 1 - just as an example

   Serial.begin(9600);
   calibrate();
}
void loop()                   
{
    long start = millis();
    long total1 =  cs_4_2.capacitiveSensor(30);
    long total2 =  cs_4_6.capacitiveSensor(30);
    long total3 =  cs_4_8.capacitiveSensor(30);

/*
    Serial.print(millis() - start);        // check on performance in milliseconds
    Serial.print("\t");                    // tab character for debug windown spacing
    Serial.println(total1);                  // print sensor output 1
    Serial.print("\t");
    Serial.println(total2);                  // print sensor output 2
    Serial.print("\t");
    Serial.println(total3);                // print sensor output 3
*/

    delay(10);                             // arbitrary delay to limit data to serial port
    if(total1 < sensor_1_baseline) {
        Serial.print("Got a hit on sensor 1.  Value = ");
        Serial.println(total1);
    }
    if (total2 < 197000) { //trial and error value based on serial display readings

        Serial.print("Got a hit on sensor 2.  Value = ");
         Serial.println(total2);
    }

    if (total3 < 200000) { //trial and error value based on serial display readings
        Serial.print("Got a hit on sensor 3.  Value = ");
        Serial.println(total3);
    } 

}

void calibrate() {
  int sensor_1_total=0;
  int sensor_2_total=0;
  int sensor_3_total=0;

  for (int i = 1; i <= 10; i++){

     sensor_1_total = sensor_1_total + cs_4_2.capacitiveSensor(30);
     sensor_2_total = sensor_2_total + cs_4_6.capacitiveSensor(30);
     sensor_3_total = sensor_3_total + cs_4_8.capacitiveSensor(30);
  }

  sensor_1_baseline = int(sensor_1_total / 10);
  sensor_2_baseline = int(sensor_2_total / 10);
  sensor_3_baseline = int(sensor_3_total / 10);

  Serial.print("Sensor 1 baseline = ");
  Serial.println(sensor_1_baseline);
  Serial.print("Sensor 2 baseline = ");
  Serial.println(sensor_2_baseline);
  Serial.print("Sensor 3 baseline = ");
  Serial.println(sensor_3_baseline);

}

Back to Top

Ping Ultrasonic Code (Arduino)

This code is measuring the time for the Ping cycle and converting that to distance based on the component specification. Experimentation found the results to be extremely accurate. A threshold of 3 inches was set to execute a data send to the serial port (this data is then read and used by the Python code on the Raspberry Pi).


const int pingPin = 12; // Signal Pin on the Ultrasonic Range Sensor
void setup() {
Serial.begin(9600); // initialize serial communication:
}
long duration, inches, cm, inMean, cmMean; // Set the variables duration, inches, cm, inMean and cmMean to long
long in[] = {0,0,0,0,0}; // Set variable in for the inches filter to long
long cen[] = {0,0,0,0,0}; // Set variable cen for the centimeter filter to long
int arrayIndex = 0; // Set variable arrayIndex to integer in inches and cm filter

void loop()
{

// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
pinMode(pingPin, OUTPUT); // Define pingPin as an output. ////
digitalWrite(pingPin, LOW); // Write LOW. ////
delayMicroseconds(2); // Wait 2 microseconds. ////
digitalWrite(pingPin, HIGH); // Write HIGH. ////////////////
delayMicroseconds(5); // Wait 5 microseconds ////////////////
digitalWrite(pingPin, LOW); // Write LOW. ////

// The same pin is used to read the echo signal from the PING))) after it bounces off an object.
pinMode(pingPin, INPUT); // Define pingPin as an input.
duration = pulseIn(pingPin, HIGH); // Value of duration is the time in microseconds it takes to receive the signal
// back from the object it sences.

// convert the time into a distance using microsecondsToInches and microsecondsToCentimeters
inches = microsecondsToInches(duration); //// converts the time into inches, see ***Length Conversions***
//// at bottom of program
cm = microsecondsToCentimeters(duration); //// converts time into centimeters, see ***Length Conversions***
//// at bottom of program

if(arrayIndex < 4)
arrayIndex ++;
else
arrayIndex = 0;

in[arrayIndex] = inches;
cen[arrayIndex] = cm;

for(int x; x < 5; x++){ // Averaging Filter used to smooth both the inch and cm values, we read the
inMean += in[x]; // values 5 times, add them and divide by 5 to get a more stable display.
cmMean += cen[x];
}

inches = inMean/5; // Averaged inches value
cm = cmMean/5; // Averaged cm value

inMean = 0;
cmMean = 0;

Serial.print(inches); // Output to the serial monitor the values of inches and cms.
Serial.print("in, ");
Serial.print(cm);
Serial.print("cm");
Serial.println();

if(inches < 3) {
Serial.print("Got a hit on sensor 1"); //Output message to serial if object comes within 3 inches
Serial.println();
delay(10000);
}


//****Length Conversions********

long microsecondsToInches(long microseconds)
{
// According to Parallax's datasheet for the PING))), there are
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
// second). This gives the distance travelled by the ping, outbound
// and return, so we divide by 2 to get the distance of the obstacle.
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
return microseconds / 73.746 / 2.00;
}

long microsecondsToCentimeters(long microseconds)
{
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return microseconds / 29 / 2;
}


Back to Top

Python Code (Raspberry Pi)

The Python code below reads the serial output from the controller board and then executes two commands, one to take a photo and another to log the visit in a log file. The print step commands are used for debug purposes to verify that the program is executing properly.


import serial
from datetime import datetime
from SimpleCV import Camera, Display
from time import sleep

print "Step 1"


log = open("./birdlog.txt", "w")

myCamera = Camera(prop_set={'width': 320, 'height': 240})
port = "/dev/ttyACM0"
serialFromArduino = serial.Serial(port,9600)
serialFromArduino.flushInput()

print "Step 2"

while True:
    now = str(datetime.now())
    input = serialFromArduino.readline()
    print(str(input))
    if "sensor 1" in (str(input)):
        print "Step 3"
        frame = myCamera.getImage()
        frame.save("birdshot" + now + ".jpg")
        log.write(now + "\t" + (str(input)) + "\n")
        log.flush()
        sleep(.9)


Back to Top