Project tutorial

UltrasonicEyes © GPL3+

UltrasonicEyes is a fun and quirky project you can place somewhere and watch as it looks at things moving around in front of it. Freaky!

  • 13,947 views
  • 37 comments
  • 105 respects

Components and supplies

Apps and online services

About this project

I wanted to make a quirky project using some 8x8 matrix LEDs and some ultrasonic sensors... something different from what other people usually make with ultrasonics - and I wanted it to be fun and playful.

So I created what I call UltrasonicEyes - a fun project that you sit somewhere near where people move around and it will look around at where people are, and blink and well, just weird you out in a fun and creepy way!

What will we need?

Ultrasonic sensor modules are designed to detect obstacles and to determine how far away the obstacle is, so they generally have a detection distance of up to 3-4 meters, which is a good distance for this project to be placed inside a living room or office area.

I am using 2x HC-SR04 modules I picked up from e-Bay. You can find them super-cheap.

The modules are pretty straightforward to use, and only require 3 or 4 wires, to connect to an Arduino micro-controller.

The 8x8 led Matrix modules I chose to use allow chaining, so only one of them needs to be connected to the Arduino, and the second module connects to the first.

The modules use SPI, so we only require 5 wires to go to the Arduino to control eye images on both displays.​ We need two of them of course!

NOTE: The code provided uses Hardware SPI, so if you are using a different Arduino board than the Nano, please check which of the pins are Hardware SPI pins for MOSI and SCK and wire them up accordingly!

You'll also need an Arduino compatible micro-controller of some type. I am using a Nano (compatible) because it's small enough to fit in the case, has USB for power/programming the firmware and it has a stack of GPIO's to connect everything too.

I have soldered everything onto a proto-board that is the same size as a half breadboard, but I'd recommend you just build everything onto a half sized breadboard first, as that way the project requires no soldering and can be easily pulled apart or changed.

The last few things you'll need are an LDR (Photo resistor) for detecting light, a 330ohm resistor and a bunch of breadboard wires, both male to female, and female to female.

NOTE: You can use any color wires in this project... there is no requirement to use the same colors that I specify, but, it's always good practice to use red and black for POWER and GND, and use other colours for other wiring as it makes it super easy to identify which wires have power running through them and which wires are being used for data & signals.

Putting it all together

Lets start by plugging the Nano onto the breadboard right at the end so the USB is hanging off the edge, but still keeping all pins plugged into the board.

POWER and GND connections

Now connect a black wire from the GND connection on the Nano to the GND rail on the breadboard. Now do the same with a red wire, connecting the 3V (or 5V if that is all you have) to the POWER rail on the breadboard.

While we are working on the GND and POWER, lets connect a black wire between the two GND rails on each side of the breadboard. Do the same with a red wire and the two POWER rails.

*Note: Some matrix LED panels might require 5V instead of the 3.3V depending on the brand. If you find you are unreliable results, try using the 5V pin from the Arduino.

Lets wire up the Ultrasonic sensors

Connect a black wire between the GND pins on each of the Ultrasonic sensors to the GND rail on the breadboard. Do the same with a red wire and the VCC (POWER) pins on the sensors and the POWER rail on the breadboard.

Now lets hook up the following white and blue wires:

  • White wire from the TRIG pin on sensor 1 to Digital pin 2 on the Arduino
  • Blue wire from the ECHO pin on sensor 1 to Digital pin 3 on the Arduino
  • White wire from the TRIG pin on sensor 2 to Digital pin 4 on the Arduino
  • Blue wire from the ECHO pin on sensor 2 to Digital pin 5 on the Arduino

Great job! That's the Ultrasonic sensors taken care of!

Connecting the two 8x8 LED matrix displays

Connect a black wire between the incoming GND pin on one of the LED matrix displays to the GND rail on the breadboard. Do the same with a red wire and the incoming VCC (POWER) pin on the display and the POWER rail on the breadboard.

Connect a black wire between the outgoing GND pin of display one and the incoming GND pin on display two. Do the same with a red wire and the outgoing VCC pin on display one and the incoming VCC pin on display 2.

While we are connecting wires between the 2 displays, lets finish that part off...

  • Connect a yellow wire between the outgoing SCK (Clock) pin on display one and the incoming SCK pin on display two.
  • Connect a blue wire between the outgoing MOSI (Data) pin on display one and the incoming MOSI pin on display two.
  • Connect a white wire between the outgoing CS (Select) pin on display one and the incoming CS pin on display two.

Great! Now lets connect the rest of display one to the breadboard...

  • Connect a yellow wire between the incoming SCK pin on display one and digital pin 13 on the Arduino.
  • Connect a blue wire between the incoming MOSI pin on display one and digital pin 11 on the Arduino.
  • Connect a white wire between the incoming CS pin on display one and digital pin 10 on the Arduino.

REMEMBER: The code provided uses Hardware SPI, so if you are using a different Arduino board than the Nano, please check which of the pins are Hardware SPI pins for MOSI and SCK and wire them up accordingly!

Well done. Now on to the final wiring steps...

Connecting the LDR and Resistor to detect ambient light

Before we connect these wires, why are we even doing this step? Well, I am glad you asked! The LDR connected to the Arduino will allow us to detect wether it is light or dark around UltrasonicEyes and we are going to use that information to brighten or dim the LED displays accordingly.

We don't want the displays super bright at nighttime as in darker light, we can still see the displays quite well when the brightness is around 30%, but in daylight, or in a bright room, we need to punch the brightness higher to make the displays more visible.

Ok, lets get this last step complete so we can move on to putting it in the 3D case!

Connect the LDR across 2 rows of pins on the breadboard, just like in the wiring diagram above. Leave room to place the resistor and a wire that will go to the Arduino.

Connect the 330ohm resistor between one row of pins on one side of the LDR and the GND rail of the breadboard.

Connect a red wire between the row of pins on the other side of the LDR to the POWER rail of the breadboard.

Finally, lets connect a brown wire between the row of pins that the 330ohm resistor is connected to and Analogue pin 5 (A5) on the Arduino. It needs to be an analogue pin because we need to read a value between 0 and 255 from the LDR (light strength) instead of just 0 and 1, like we would get from a digital pin.

Lets power it up and upload the code

Ok, that's it, we are all wired up. Time to plug the USB cable in between the Arduino and your computer, and upload the UltrasonicEyes sketch provided below to see it all working.

Once it's powered up and the code has been uploaded, walk around in front of your sensors, or move your hands in front of them and see what happens!

Want to make it more permanent?

Looking to make your UltrasonicEyes more permanent? Check out my video that covers taking the breadboard version and soldering it to a proto-board here...

Moving UltrasonicEyes from Breadboard to Proto-board

And then print the 2 parts of the case on any 3D printer and assemble like I did in the video!

I'm also looking at expanding UltrasonicEyes to have a capacitive touch button (or regular button) to cycle through different eye shapes... you can watch my experiments here...

Adding a capacitive touch button to cycle through eye images

You can check out the rest of my projects & videos at... unexpectedmaker.com

Follow me on twitter, Facebook, Instagram & tindie

That's it!

Code

Ultrasonic EyesC/C++
#include <MD_MAX72xx.h> // We use this to control the 8x8 LED matrix Displays - You'll need to install this library from the library manager if you donb't already have it.
#include <NewPing.h> // We use NewPing to control the Ultrasonic Sensors - You'll need to install this library from the library manager if you donb't already have it.

// define pins attached to LED matrix display 1
//#define  CLK_PIN   13  // We are using hardware SPI - make sure you have connected the CLK pin to the hardware CLK pin on your device
//#define DATA_PIN  11  // We are using hardware SPI - make sure you have connected the MOSI pin to the hardware MOSI pin on your device
#define CS_PIN    10  // Chip Select pin
#define  MAX_DEVICES 2 // number of displays - we need 2, one for each eye

#define LIGHT A5 // We use Analogue pin 5 to read the light value from the LDR

// We are using hardware SPI which automatically 
MD_MAX72XX mx = MD_MAX72XX(CS_PIN, MAX_DEVICES); // Initialise the 2 Matrix displays

#define  t1  2  // Trigger pin on Ultrasonic Sensor 1
#define  e1  3  // Echo pin on Ultrasonic Sensor 1
#define  t2  4  // Trigger pin on Ultrasonic Sensor 2
#define  e2  5  // Echo pin on Ultrasonic Sensor 2
#define maxDist 400 // the max distance for the ultrasonic pulse

NewPing eyeR(t2, e2, maxDist ); // Initialise Ultrasonic sensor 2
NewPing eyeL(t1, e1, maxDist ); // Initialise Ultrasonic sensor 1

// We track the current state of the system with this integer variable, this way after a blink, we can out the eyes back to looking at the last direction they were in
// Possible states are:
// 0: Looking forward
// 1: Looking Right
// 2: Looking Left
int currentState = -1; 

// We store the time and distance for the pings for each Ultrasonic sensor in these variables
long duration1, duration2;
int distance1, distance2;

// We want the blink to be randomly inserted into the cycle
float nextBlink = millis() + 1000;

// We store the current light intensity in this variable
float lightAmount = 0;

uint8_t eye_forward[COL_SIZE] =
{
  0b00111100,
  0b01000010,
  0b01011010,
  0b10101101,
  0b10111101,
  0b10011001,
  0b01000010,
  0b00111100
};

uint8_t eye_right[COL_SIZE] =
{
  0b00111100,
  0b01000010,
  0b01110010,
  0b11011001,
  0b11111001,
  0b10110001,
  0b01000010,
  0b00111100
};

uint8_t eye_left[COL_SIZE] =
{
  0b00111100,
  0b01000010,
  0b01001110,
  0b10010111,
  0b10011111,
  0b10001101,
  0b01000010,
  0b00111100
};

uint8_t eye_blink[COL_SIZE] =
{
  0b00000000,
  0b00111100,
  0b01111110,
  0b11111111,
  0b10111101,
  0b11000011,
  0b01111110,
  0b00111100
};

void setup()
{
  // Initialise the Matrix Display library
  mx.begin();

  // Set the pin modes for Ultrasonic sensor 1
  pinMode( t1, OUTPUT );
  pinMode( e1, INPUT );

  // Set the pin modes for Ultrasonic sensor 2
  pinMode( t2, OUTPUT );
  pinMode( e2, INPUT );

  // Set each trigger pin on the Ultrasonic sensors to start at LOW 
  digitalWrite( t1, LOW );
  digitalWrite( t2, LOW );

// Set the pin mode for the LDR to be an INPUT
  pinMode( LIGHT, INPUT );

  // Start with the eyes looking forward
  ShowEye_Forward();
  currentState = 0;
}

void loop()
{
  // read in the current light level
  lightAmount = analogRead( LIGHT );
  // make sure the light value is within the range of the Max Intensity of the displays
  lightAmount = ( lightAmount / 255 ) * MAX_INTENSITY;
  // set the intensity
  mx.control(MD_MAX72XX::INTENSITY, lightAmount );

  // Ping the Left eye with a recursion of 5
  distance1 = eyeL.ping_median( 5 );
  // Delay 500ms before we ping the right eye, so we don't get conflicting results
  delay(500);
  // Ping the Right eye with recursion of 5 
  distance2 = eyeR.ping_median( 5 );

  // Check to see if it's time to try to insert a blink
  if ( nextBlink < millis() )
  {
    // Set the next blink time to be a random time between now + 2 seconds and 10 seconds
    nextBlink = millis() + random(2000, 10000);

    // Now we need to decide if this blink is a single blink, or a double blink
    // Pick a random number between 1 and 13 and if it is less than or equal to 6, it's going to be a double blink
    if ( random(1,13) <= 6 )
      ShowEye_Blink_Dbl();
    else
      ShowEye_Blink();

    // Pause for 250ms to ensure the blink can happen before we go back to looking around
    delay(250);

    // Exit this itterartion of loop90 early.
    return;
  }
  
  // We need to work out if the difference between the distances of each ultrasonic sensors detected item is less than 15
  // or if both distances are 0, meaning nothing was detected
  // If this condition is met, then make the eyes look forward 
  float difference = ( distance2 - distance1 );
  if ( abs( difference ) < 250 || (distance1 == 0 && distance2 == 0 ) )
  {
    ShowEye_Forward();
    currentState = 0;
  }
  // Now if distance1 is greater than distance2 and distance1 is also greater than 0, then we want to look right  
  else if ( distance2 < distance1 && distance1 > 0)
  {
    ShowEye_Right();
    currentState = 1;
  }
  // Now if distance2 is greater than distance1 and distance2 is also greater than 0, then we want to look left  
  else if ( distance1 < distance2 && distance2 > 0 )
  {
    ShowEye_Left();
    currentState = 2;
  }

  // delay the loop for 250ms to ensure the eyes have time to display correctly
  delay(250);
}



/***************************************/
/************ MAX7219 Stuff ************/
/***************************************/

void ShowEye_Right()
{
  // Clear the displays
  mx.clear();
  // Set the current display brightness
  mx.control(MD_MAX72XX::INTENSITY, lightAmount );

  // Loop through each row of the displays
  for (uint8_t row=0; row<ROW_SIZE; row++)
  {
    // Set the colums of the display 1
    mx.setColumn(row, eye_right[row]);
    // Set the colums of the display 2
    mx.setColumn(row+8, eye_right[row]);
  }
}

void ShowEye_Left()
{
// Clear the displays
  mx.clear();
  // Set the current display brightness
  mx.control(MD_MAX72XX::INTENSITY, lightAmount );

  // Loop through each row of the displays
  for (uint8_t row=0; row<ROW_SIZE; row++)
  {
     // Set the colums of the display 1
    mx.setColumn(row, eye_left[row]);
     // Set the colums of the display 2
    mx.setColumn(row+8, eye_left[row]);
  }
}


void ShowEye_Forward()
{
// Clear the displays
  mx.clear();
  // Set the current display brightness
  mx.control(MD_MAX72XX::INTENSITY, lightAmount );

  // Loop through each row of the displays
  for (uint8_t row=0; row<ROW_SIZE; row++)
  {
     // Set the colums of the display 1
    mx.setColumn(row, eye_forward[row]);
     // Set the colums of the display 2
    mx.setColumn(row+8, eye_forward[row]);
  }
}

void ShowEye_Blink()
{
// Clear the displays
  mx.clear();
  // Set the current display brightness
  mx.control(MD_MAX72XX::INTENSITY, lightAmount );

  // Loop through each row of the displays
  for (uint8_t row=0; row<ROW_SIZE; row++)
  {
     // Set the colums of the display 1
    mx.setColumn(row, eye_blink[row]);
     // Set the colums of the display 2
    mx.setColumn(row+8, eye_blink[row]);
  }

  // Hold the blink for 150ms
  delay(150);

  // Put the state of the eyes back to what they were
  if ( currentState == 0 )
    ShowEye_Forward();
  else if ( currentState == 1 )
    ShowEye_Right();
  else if ( currentState == 2 )
    ShowEye_Left();

}

void ShowEye_Blink_Dbl()
{
// Clear the displays
  mx.clear();
  // Set the current display brightness
  mx.control(MD_MAX72XX::INTENSITY, lightAmount );

  // Loop through each row of the displays
  for (uint8_t row=0; row<ROW_SIZE; row++)
  {
     // Set the colums of the display 1
    mx.setColumn(row, eye_blink[row]);
     // Set the colums of the display 2
    mx.setColumn(row+8, eye_blink[row]);
  }

  // Wait 75ms
  delay(75);

  // Put the state of the eyes back to what they were - briefly
  if ( currentState == 0 )
    ShowEye_Forward();
  else if ( currentState == 1 )
    ShowEye_Right();
  else if ( currentState == 2 )
    ShowEye_Left();

  // Wait 75ms
  delay(75);

  // Blink the eyes
  ShowEye_Blink();
}

Custom parts and enclosures

3D printed case - Back
Print with any 3D printer, no specific settings required.
3D printed case - Front
Print with any 3D printer, no specific settings required.

Schematics

Circuit Layout
Ultrasoniceyes fritzing iqk06vmj34
Fritzing Circuit
ultrasoniceyes_final_Th84xpUyB2.fzz

Comments

Similar projects you might like

Arduino Atari Adaptor

Project tutorial by Dante Roumega

  • 7,375 views
  • 6 comments
  • 23 respects

Arduino Pendulum Physics Lab

Project tutorial by Paul Kassebaum

  • 831 views
  • 0 comments
  • 2 respects

Generating Audio with an Arduino and a Resistor Ladder DAC

Project showcase by 3 developers

  • 2,670 views
  • 5 comments
  • 7 respects

Automated Plant Watering System

Project in progress by Ammar Shahid

  • 263 views
  • 0 comments
  • 2 respects

Humidity Measuring Molecule

Project showcase by KatjaNiggl

  • 1,402 views
  • 4 comments
  • 15 respects

Electroplating with Copper

Project tutorial by Ryan Gill

  • 6,847 views
  • 8 comments
  • 15 respects
Add projectSign up / Login