Tweety Bot

TWEETY BOT

Built 11/2015

Tweety Bot started out an a much more ambitious project.

I would hope so I guess since all he actually does is sit around and Tweet. Anyhow, the original goal was to build a birdy bot that could sit on your arm or shoulder and use a gryo/accel sensor to stay upright.

I worked up some micro servos that would allow at least two different axis of movement and made up a duck looking head for the body. That one was called JABB (Just Another Bird Bot). After messing with JABB for a while and realizing how ugly the body was going to be with a full ProMini hanging onto it and liking the idea of a little bird like robot, I thought about simplifying things. VERY simple in fact.

Tweety's brain is a very simple ATiny85 like I used in other bots like Buford, Buzz and PuP. This allowed me to get the whole CPU, US sensor, and a light sensor to fit inside it's little head. After messing with JABB I thought it would be fun to have a simply little tweeting bird bot that was not mobile.

In TweetyBot, a single servo allows his head to pan left and right. A small light sensor is on top of his head to know how bright things are and a yellow LED was stuffed inside his beak to activate along with his tweeting sounds so his beak lights up when he makes sounds. Very simply but kind of entertaining as well.

When TweetyBot first starts up he does a right to left pan looking for the closet object. He then moves to that spot and just starts randomly tweeting and moving his head around. The light sensor is used to "go to sleep" when it's dark or when his little cover is put over him. Kind of like a real bird as well. Tweety HeadThere are some "features", which to most would be "bugs", in the code where the head doesn't do a perfect pan on startup which actually adds a little more realisim to his actions.

CONSTRUCTION

Construction is very straight forward. A hacked up 3d printable owl body was used from one of the many on Thingiverse.com. I didn't want or need the head so I chopped it off of the digital model before printing and hollowed out the back to make room for the neck servo. The servo mounts in the hollow area and the head fits on the servo output.

TweetyBot's head is from Roxanna77's design and it just looked like a perfect bird head to me. You can find it here:  http://www.thingiverse.com/thing:1054142

TweetyBot runs off 5v or so and can be powered by most any supply including a battery pack, solar chargeable pack, or just a USB port if you want.

I wish I would have made him a little more heavy at the base or added some material as it is a little top heavy and unless you have him glued to a base he moves around and eventually finds the edge of the desk. So I guess he IS a little mobile after all.

 

WHAT DOES HE DO?

Like most of my creations he really doesn't do much. Just randomly moves his head and tweets sometimes. I did have to dial down the randomness some as he does get annoying if he is making a racket too often.

He does get annoyed if you get in his face and starts moving his head back and forth to get away from you. 

He also quiets down when it gets dark enough or when you put his goodnight cloth (black cloth) over him as he thinks it dark and time to shut up. 
Once you take the cloth off he restarts his routine and spins his head around and starts tweeting again.

THE BAD STUFF

TweetyBot BackThe bad stuff mostly is my screw up in design. He really only needs three connections to/from his head. The + power, the ground and then the control going back to the neck servo. I thought it'd be simple to just add three pins sticking out his head and hook up to that feeding the plus and ground both to the inside of the head and back to the servo. It does work but it also can fail easily as the head moves back and forth eventually breaking wires. 

TWEETYBOT'S FUTURE

TweetyBot will get his wires cleaned up and mounted to a better base. Maybe a little wooden platform or something. I've already added a USB cable to power him so he can sit somewhere and be powered off a computer or power pack and do his thing. He seems like a reasonbly happy bird.

TweetyBot Resources

If you have some weird desire to build your own here are the modified models:
TweetyBotBody.stl - Modified from an Owl version from ThingiVerse with hollow back
TweetyBotBeak.stl - Print with zero infill to have a hollow beak for LED to shine through

 

Example Code

I'm sure there are still some bugs and problems here but it's working for what I want it to do. I think there are some pieces not even used and the chirping parts are from some sample code online elsewhere.

//** TweetyBot - ATTiny85 Version of JABB
//** 
//** v1.00 - 11/1/2015 - Stephen W Nolen / aka Protowrxs

// ATTiny85 Pin Uses
//             -----
// 1 Rest (5) |o  | 8 Vcc
// 2 Sonar(3) |   | 7 (2) LDR light  D2/A1
// 3 Speak(4) |   | 6 (1) Output to 2N2222 driving pager vib motor
// 4 Gnd      |   | 5 (0) Head Servo D0
//       -----

#include <SoftwareServo.h> 
SoftwareServo headServo; 

int speakerPin = A3;
int headServopin = 0;
int motorPin = 1;    // Yes, looks the same as the ADC but that is apparently how it works

int headPosition;
int headCenter = 90;
int headLeft = 130;
int headRight = 50;

// vars used to determine random moves of the head and direction
int headOffset;
int headDirection;

// US sensor used for location and annoying him
byte TRIGGER_PIN = 3;
byte ECHO_PIN = 3;
int uScm;
int closePosition;
int uScmClose;

boolean activeMode;    // if we are active or not. Used by light sensor to shutup in the dark or be more active in the light

int lightPin = 1;      // LDR Input pin on A1 (ATTiny85 pin 7)
int lightLevel;         // LDR Light level as read - gets quite at dark, active in light by changing the random high/low values?

// Timers used for various random activity
long generalTimer1;
long generalTimer2;
long generalTimer3;
long nextHeadMove;

//** H)ead, T)weet, L)owC)hirp and H)ighC)hirp random low/highs to allow light controls
//** eventually we want to increment / decrements these based on the ambient light levels to be more active during bright light
int Hlow = 5000;
int Hhigh = 60000;
int Tlow = 10000;
int Thigh = 20000;
int LClow = 10000;
int LChigh = 20000;
int HClow = 10000;
int HChigh = 20000;

long myDelay;

int currentPhase;      // we step through phases for chirps - not sure why, might be best to randomize?

void setup()
{
  // This is from an AdaFruit example here: 
  //https://learn.adafruit.com/trinket-gemma-servo-control/code
  // Set up the interrupt that will refresh the servo for us automagically
  OCR0A = 0xAF;            // any number is OK
  TIMSK |= _BV(OCIE0A);    // Turn on the compare interrupt (below!)

  //Setup speaker output
  pinMode(speakerPin,OUTPUT);
  pinMode(motorPin, OUTPUT);
  
  //Psudeo randomness
  randomSeed(analogRead(0));

  //Setup this for ADC input but I think ADread does it already
  pinMode(lightPin,INPUT);  //LDR light sensor

  //This may not be needed either
  pinMode(headServopin, OUTPUT);  //This should be done in servo code though

  //** Attach the head servo
  headServo.attach(headServopin);
//  headServo.setMaximumPulse(2200);    // do not think we need this as it's working as is

  headServo.write(headCenter);

  // Tweet, find closest objecgt and then chirp on start up
  // Also does similar when the light returns from being dark
  tweet(random(2,4),random(1,5));      // startup tune
  findCloseObject();
  highChirp(random(1,5), random(1,10));
  
  peck(250,200);
  
  generalTimer1 = millis();
  generalTimer2 = millis();
  generalTimer3 = millis();
  
  activeMode = true;

}

void loop()
{
  uScm = ReadSonar();

  if (uScm < 10)
  { 
    headServo.write(headLeft);
    headPosition = headLeft;
    tweet(random(1,3),random(1,5));  
  }
  
  if (uScm < 5) 
  {
    headServo.write(headRight);
    headPosition = headRight;
    highChirp(random(1,5), random(1,5)); 
  }
 
  lightLevel = analogRead(lightPin);
  
  if (activeMode)
  {
    // if it gets dark then shut up after last chirp
    if (lightLevel < 150)
    { 
      activeMode = false;
      lowChirp(5,5);
    }

    // if really bright just keep on chirping
    if (lightLevel > 900)
    {
      highChirp(5,5);
    }

    //If random timer is up then jump to the next phase of randomness
    if (millis() > myDelay)
     switch (currentPhase)
      {
        case 0: 
          highChirp(random(2,6),random(1,6));
          headPosition = random(headRight,headLeft);
          headServo.write(headPosition);
          myDelay = millis() + random(HClow,HChigh);        
          currentPhase++;
        break;
        case 1:
          lowChirp(random(2,6),random(1,10));
          headPosition = random(headRight,headLeft);
          headServo.write(headPosition);
          myDelay = millis() + random(LClow,LChigh);        
          currentPhase++;
        break;
        case 2: 
          tweet(random(2,4),random(1,5));
          headPosition = random(headRight,headLeft);
          headServo.write(headPosition);
          myDelay = millis() + random(Tlow,Thigh);        
          currentPhase++;
        break;
        case 3: 
          headPosition = random(headRight,headLeft);
          headServo.write(headPosition);
          peck(random(100,400),random(75,500));
          myDelay = millis() + random(Tlow,Thigh);        
          currentPhase = 0;
        break;
     }  // End Case
  } // end mydelay timer
  
// Else we are inactive so see if we have enough light to wake up or not
else
  {
    if (lightLevel > 150)
    {
      activeMode = true;
      tweet(5,5);
      findCloseObject();
      delay(2000); //just sit there a few and look at that thing
      tweet(5,5);
    }

  }   // End Active if/then
  delay(10);  

} //End main loop

void buzzMotor(int chirpsNumber)
{
  
  int i;
  int x;
  
  for (int veces=0; veces<=chirpsNumber;veces++)
  {
    for (i=0; i<200; i++)
    {
      digitalWrite(motorPin, HIGH);
      delayMicroseconds(i);
      digitalWrite(motorPin, LOW);
      delayMicroseconds(i);
    }
    
    for (i=90; i>200; i--)
    {
      digitalWrite(motorPin, HIGH);
      delayMicroseconds(i);
      digitalWrite(motorPin, LOW);
      delayMicroseconds(i);
    }
  }
}


// Low chirp sound
void lowChirp(int intensity, int chirpsNumber)
{
  int i;
  int x;
  
  for (int veces=0; veces<=chirpsNumber;veces++)
  {
    for (i=0; i<200; i++)
    {
      digitalWrite(speakerPin, HIGH);
      delayMicroseconds(i);
      digitalWrite(speakerPin, LOW);
      delayMicroseconds(i);
    }
    
    for (i=90; i>200; i--)
    {
      digitalWrite(speakerPin, HIGH);
      delayMicroseconds(i);
      digitalWrite(speakerPin, LOW);
      delayMicroseconds(i);
    }
  }
}

//High chirp sound
void highChirp(int intensity, int chirpsNumber)
{
  int i;
  int x;
  
  for (int veces=0; veces<=chirpsNumber;veces++)
  {
    for (i=100; i>0; i--)
    {
      for (x=0; x<intensity; x++)
      {
        digitalWrite(speakerPin, HIGH);
        delayMicroseconds(i);
        digitalWrite(speakerPin, LOW);
        delayMicroseconds(i);
      }
  }
  }
}

// Tweet sound
void tweet(int intensity, int chirpsNumber)
{
  int i;
  int x;
  
  for (int veces=0; veces<=chirpsNumber;veces++)
  {
    for (i=80; i>0; i--)
    {
      for (int x=0; x<intensity; x++)
      {
        digitalWrite(speakerPin, HIGH);
        delayMicroseconds(i);
        digitalWrite(speakerPin, LOW);
        delayMicroseconds(i);
      }
  }
  }
}

// ** returns the ET in millis for timer variable passed
long getET(long mytimer){
  long result;
  result = (millis() - mytimer);
  return result;
}

// just move randomly a little bit - not used at the moment
void RandomHeadMove()
{
    headOffset = random(0,2);
    headDirection = random(1,10);
    if (headDirection <= 5)
    {
      headPosition = headPosition - headOffset;
    }
    else
    {
      headPosition = headPosition + headOffset;
    }
    headServo.write(headPosition);
    generalTimer1 = millis();
    SoftwareServo::refresh();
}

long ReadSonar()
{
  long duration, distance;

  pinMode(TRIGGER_PIN, OUTPUT);
  digitalWrite(TRIGGER_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIGGER_PIN, LOW);

  pinMode(ECHO_PIN, INPUT);

  duration = pulseIn(ECHO_PIN, HIGH);
  distance = duration / 29 / 2;
  if (distance == 0)
  {
    distance = 9999;
  }
  return distance;

}

// This is from an AdaFruit example here: 
//https://learn.adafruit.com/trinket-gemma-servo-control/code
// We'll take advantage of the built in millis() timer that goes off
// to keep track of time, and refresh the servo every 20 milliseconds
// The SIGNAL(TIMER0_COMPA_vect) function is the interrupt that will be
// Called by the microcontroller every 2 milliseconds
volatile uint8_t counter = 0;
SIGNAL(TIMER0_COMPA_vect) {
  // this gets called every 2 milliseconds
  counter += 2;
  // every 20 milliseconds, refresh the servos!
  if (counter >= 20) {
    counter = 0;
    SoftwareServo::refresh();
  }
}

void findCloseObject()
{
   int pos;
   uScmClose = 9999;   //Start with way out there, otherwise it's zero and that doesn't work silly
   for (pos = 0; pos <= 180; pos += 1) 
   { // goes from 0 degrees to 180 degrees
     headServo.write(pos);
     delay(10);                //wait for slow servo stuff to move
     uScm = ReadSonar();
     if (uScm < uScmClose)
     {
       closePosition = pos;   //Save the position for now
       uScmClose = uScm;      //Save the closest value to check
     }
   }
   headServo.write(closePosition);
}

void peck(int peckSpeed, int peckTime)
{
  analogWrite(motorPin, peckSpeed);
  delay(peckTime);
  analogWrite(motorPin,0);
}

 

Just Tweetying

Comments

Submitted by jinx on Sun, 2016-11-13 19:07

love it, one for my boys  me thinks, spooky as well  my owl was cooling when I saw your posting  proto, using this http://www.thingiverse.com/thing:1874045 as a test for the cube, had the idea of shoving a pololu motor "spinning head and an IR ,,, but yours is way better "gonna make.

Submitted by Protowrxs on Sun, 2016-11-13 20:38

Protowrxs's picture

Thanks Funny you were printing that.  I just figured I should post it since it's been sitting for a while.

I like that owl model better with the head option, should be easier than what I did. Will be cool to see what you come up with!

Stephen

Submitted by Roxanna77 on Sun, 2016-11-13 20:43

Roxanna77's picture

Thanks for posting Proto! That thing is darn cute, gotta love that head too! ;-)

 

~Roxy~
"Propeller Girl"
I tinker, therefore I am..... 

Submitted by nschreiber0813 on Mon, 2016-11-14 06:46

nschreiber0813's picture

Dear: Protowrx

Thank you for this project. I really badly want that exact robot for my house and I don't know why lol.

From: Noah

Submitted by Protowrxs on Mon, 2016-11-14 21:31

Protowrxs's picture

Not sure if anyone has the interest but the modified body and the cut up beak are here:

TweetyBotBody.stl - Modified from an Owl version from ThingiVerse with hollow back
TweetyBotBeak.stl - Print with zero infill to have a hollow beak for LED to shine through

Use the head from Roxanna but you may need to open the "eyes" up for an SR04.

Submitted by Roxanna77 on Mon, 2016-11-14 21:54

Roxanna77's picture

If anyone wants it I can draw up one with bigger eye holes, Let me know the dimensions( Diameter and edge to edge) and I can whip it up in a few minutes!

 

~Roxy~
"Propeller Girl"
I tinker, therefore I am.....

Submitted by Protowrxs on Mon, 2016-11-14 21:58

Protowrxs's picture

Cool - I just merged in a blank SR04 as a hole with TinkerCAD back then I think.

It's not too different but enough that it won't fit if I remember.

Stephen

Submitted by Roxanna77 on Mon, 2016-11-14 22:05

Roxanna77's picture

I bought a bunch from different suppliers and the diameter and distance apart was different often... at $1.25 apiece its hard to complain though.

~Roxy~
"Propeller Girl"
I tinker, therefore I am.....

Submitted by viswesh on Thu, 2016-11-17 10:29

viswesh's picture

Great work as always sir

Submitted by Protowrxs on Fri, 2016-11-18 18:49

Protowrxs's picture

Finally took this thing to work and have had a few people commenting on how fun it appears.

The light level there isn't quite what I programmed it for so sometimes it does a wake up for no reason but it also keeps it generally quite with random chirps and moves which is what I was looking for at the office.

Fun and easy stuff

Stephen