Tuesday, August 07, 2018

"Spooky Old Tree" - The Owl

Of the three props we created, the Owl, having no moving parts, was the simplest build.

Build

I used my printer's copy function to make a 1:1 scale reproduction of one of our Halloween decorations, which we then spray-glued down to a piece of cardboard.  My wife cut out around it and went over the edges with a black marker.  Once that was done, we used a pushpin to make a couple of holes in each eye for the LED leads, and another set between the feet for the photo-resistor "target".

That completed the graphics work, so all we had to do was wire it up.
The breadboards I'm using come with double-sided tape, so i just peeled of the protective coating and stuck it directly to the cardboard.  Power for the system comes from a USB battery pack.  The system draws very little power, so the pack will run it for hours.  It's just held in place with a couple of pieces of Blue-Tack.

As you can see, we didn't really make any effort at a neat, professional build.  All these projects are relatively temporary and everything is held together with tape, Blue-Tack and hot glue. All connections were simply twisted together and covered with electricians tape.

Circuitry

Here's the schematic for the Owl's circuitry.
There are two basic parts:  the output (the LED eyes) and the input (the photo-resistor).  We'll take a look at the input first, and since it's used for all the props, I'll cover it in some detail.

Input
The input is comprised of an  LDR (Light-Dependent Resistor) wired in series with a fixed 1K resistor to form a voltage divider.  One end of the LDR is connected to a 5V power pin on an Arduino Nano, and the other end is connected to the fixed resistor.  The other end of the fixed resistor goes to ground.  The center tap of the voltage divider is connected to one of the analog input pins on the Nano. (Refer to the schematic.)

Here's the way it works.  

In a simple divider like this, the voltage at the center tap will be the same as the ratio between each resistor and the total resistance in the circuit.  In other words, if we shine enough light on the LDR to lower its resistance to 1K, the total resistance in the circuit would be 2K, and the voltage at the tap would be 2.5V, since the LDR would be dropping half of the 5V source and fixed resistor will be dropping the other half.  If there is less light on the LDR (say, making it 2K) the total resistance would be 3K and the voltage at the tap would be about 1.67V, since the LDR will be dropping 2/3 of the voltage.

The analog input on the Nano converts the voltage to a proportionate value in the range of 0-1023.  So, when the voltage is 2.5V, the value would be about 512.  A 1.67 voltage would be converted to  341, and so on.

So, to make the LDR act as a trigger, we simply need to read the ambient light value and then add some value to create a triggerpoint that is a bit higher.  That way, when the LDR later receives a hit from the flashlight, it's resistance will go down, causing the input at the center tap to rise above the triggerpoint and set off the effect.  For my circuits I've been adding 60 to the ambient light value (i.e. an ambient light value of 40 would have triggerpoint of 100).

Output
The output side of things is even simpler.  Two digital output pins are used, each one connected to the anode of a standard red LED.  The cathode of the LED is connected to a 150 Ohm resistor, which is then connected to ground.  When the pins go high, current flows to the LED and lights it up.  When the pin goes low, the LEDs turn off.  There's not much else to say.


Finally, here's a breadboard diagram in case you want to build a prototype.

Code

Despite being the simplest physical build, the Owl has the most complex code of any of the props, since it can react to a light hit in one of three different ways.  Fortunately, however, the code is still pretty simple and straightforward.

Here it is:

float triggerpoint;
long unsigned caltime;

void Steady(){
  analogWrite(4,255);
  analogWrite(5,255);
  delay(1000);
  analogWrite(4,0);
  analogWrite(5,0);
} 

void Flash(){
  for(int x=0;x<5;x++){
    analogWrite(4,255);
    analogWrite(5,255);
    delay(205);
    analogWrite(4,0);
    analogWrite(5,0);
    delay(250);
  }
}

void Blink(){
  for(int x=0;x<5;x++){
    analogWrite(4,255);
    delay(250);
    analogWrite(4,0);
    analogWrite(5,255);
    delay(250);
    analogWrite(5,0);
  }
}

void Calibrate(){
  triggerpoint=analogRead(A1)+60;
  caltime=millis()+10000;
   
}
void setup(){
  Calibrate();
}

void loop(){
  if(analogRead(A1)>triggerpoint){
    int x=random(3);
    switch(x){
    case 0:
      Steady();
      break;
    case 1:
      Flash();
      break;
    case 2:
      Blink();
      break;
    }
  }

  if(millis()>caltime){
    Calibrate();
  }
}

There's nothing unusual or even very interesting about this code. It defines a couple variables, followed by four function definitions that the main program can call.  Then there's the standard setup function and the main loop, which reads the current light value and, if it is above the triggerpoint, gets a random number and uses the switch structure to determine which effect function to call..  The only thing that might require a bit of explanation is the Calibrate() function.

The hallway where these props are set up has a partially-curtained, west-facing window.  During the morning and late evening, it's rather dim, but during the afternoon, more light can come in.  Since the kids play this game at all hours of the day, there is no single triggerpoint that will work at all times.  To get around that, I created the Calibrate() function.

First, it reads the current ambient light level, adds 60 units to it, and sets that as the triggerpoint.  Then it read the Arduino's built-in millis() function.  This function returns the number of milliseconds since the board was powered on.  Once it has this, Calibrate() adds 10000 to it and saves it as the variable "caltime"  Essentially, caltime is set to 10 seconds into the future. 

Calibrate() is called in the setup() function to set an initial value.  Then, on each pass through the main loop the system checks to see if the real time returned by millis() has caught up with caltime.  If it has, Calibrate() is run again, re-evaluating and setting the triggerpoint and kicking the next update time another 10 seconds into the future.

This means that regardless of changing light conditions, the system will set an appropriate triggerpoint within 10 seconds.  Even if we turn on one of the hallway lights and trigger the effects, 10 seconds later they will go off and be reset to an appropriate working state.

There is an inherent minor flaw in this system, however.  Since the Calibrate() function runs independently of anything else, it will, inevitably, occasionally run when the target is being illuminated by the flashlight, giving a false ambient reading and setting the triggerpoint too high.  I thought of a few ways around this, but after watching the kids play the game, I decided it wasn't necessary.  They don't linger too long on any one effect and any error is corrected within 10 seconds, so they never even notice.

1 comment:

Best Shallow Mount Subwoofer! said...
This comment has been removed by a blog administrator.