Introduction: Pixybot Color Tracking Robots

About: I’m a 35 year old maker that has been working with electronics and robotics for over 16 years. I have a strong background in electronics and troubleshooting. I have a strong passion for building robots and wil…

This Pixybot project started shortly after I joined a hacker/maker space in downtown Saint Louis. The space is called Arch Reactor and it has been a great place for me to bounce ideas off of other people with similar interests and knowledge. They have a wide selection of tools available for use, including a TAZ 3D printer and an 80 watt laser cutter.

These robots are built around the Pixy computer vision sensor. This vision sensor is a low cost sensor for under 80 dollars. The sensor tracks color hues at a whopping 50 frames per second. If you’re interested, you can find more information about Pixy at their wiki page. This project sprang up from the idea that Arch Reactor members often go to events to get the community involved in making and repairing. I thought robots would be a nice way to draw individuals to the table, and get them interested in robotics, this would be an interactive display. People are able to play with the robots by moving around the balls so the robots can follow them. This display would feature three robots that could track colored balls on a transportable table. This Instructable is a guideline to help you build your very own Pixybot or a army of Pixybots.

If you build more then one there is a nice feature where they play follow the leader if you train one robot for the color of the battery pack of another robot.

Features include:

  1. Velcro attached battery packs for quick swapping out and charging
  2. Li-ion battery packs rated at 2800mAH at 7.2 volts. They are some good cells that peak at around ~8.4volts
  3. Clean look with translucent white added wire management guide
  4. Arduino controller for an overall cheap design to build 3D printed parts to show off what can be done with the relatively new technology
  5. Soft rubber Lego tires for a much grippier robot. (It’s a word)
  6. Tons of left over prototyping space on the Arduino prototyping shield
  7. Pixy is programmable on the fly and takes only a few seconds to train to a new color
  8. Built-in, easy to grip handle to make picking up the robot easy
  9. Internal sensor to monitor battery pack voltage level
  10. Pause button to allow for reprogramming the Pixy on the fly
  11. Front mounted IR sensors to allow obstacle avoidance

Step 1: Get the Parts

Below is the parts list its not everything as you need a bi-color LED and a few wires to complete the robot.

  1. x2 HS-422 Hitec servo’s
  2. A Pixy (cmu cam 5) (Link)
  3. Arduino Uno
  4. DF robot 2200mAH 7.4v lipo, This can be a 4-6 pack of AA batteries as well.
  5. White Sheet of 1/4inch plastic
  6. White Plastic filament for 3D printed parts
  7. Velcro strips
  8. x2 Sharp IR sensors, model GP2Y0A41SK0F 4cm to 30cm
  9. Arduino Protoshield, You may need to grab a set of headers for the shield. (Link)
  10. A toggle switch
  11. RC Tail wheel
  12. Set of Lego tires 56x30
  13. One Set of battery connectors if building the pack
  14. A pushbutton
  15. 100k ohm resistor
  16. 1meg ohm resistor
  17. 10k resistor
  18. x2 220 ohm resistors

The above will cost you around 150-180 depending on where you source the parts, Clone Arduino's do work but if you can buy the real McCoy to support further development of the Arduino line.

Step 2: Tools and 3D Printing the Chassis

Before beginning construction, try and have all the tools you need,
including access to a 3D printer. I ordered most components online, although you may find a few deals at local stores for the Arduino or plastic.

Tool list:

  1. Hex head Allen keys
  2. Soldering iron
  3. Solder
  4. Wire cutters
  5. Philips screwdriver
  6. Multi meter
  7. Programming cable for the Arduino
  8. Utility knife razor, or hobby knife
  9. Masking tape
  10. 30 minute slow dry epoxy
  11. Assorted drill bits
  12. A drill press or a power hand drill

Pixybot Camera/arduino support

Alternative 3D printed wheel below:

Pixybot wheel

I would use a infill setting of around 30 percent so the screws have material to grab, also beef up the top and bottom thickness on the Arduino support when you slice it. I printed them in PLA but you can use ABS as well. Use a razor or hobby knife at this time to clean up the edges of the 3D printed part.

Step 3: Modify the Servos

In this project we will need to modify the drive servos for continuous rotation as they only have a limited travel. I have linked a great guide on how to do this as its pretty common. (Link) Mostly this involves removing the mechanical stop on a the final gear and soldering in a few resistors. You can also find already modified continuous rotation servo online. (Link)

Step 4: Chassis Construction

I always like to start with a rolling chassis for most of my robots.


First, cut the acrylic base plate to size. It does not have to be exact it just needs to be wide enough for the camera support and long enough to support the battery you are using. Make sure to leave room to mount the tail wheel also.

Drill out the Lego wheels, if those are the type you will be using. Drill out the hub on the wheels with a large enough drill bit that a servo horn screw can fit through the hole. Mix up a small batch of slow dry 2 part epoxy and affix the servo horns to the wheels. Go ahead and set the wheels aside so they can dry overnight.

Proceed to solder the tail wheel assembly together and make sure that it is at the correct height so when you mount it, the base should sit parallel to the surface of the table/bench. Next, drill holes to a fix the camera support to the base plate leaving 3 cm or so of the base in the front. This will allow you to mount the IR sensors in a later step.

Also attach the tail wheel in the same fashion to the back of the chassis plate. I wrapped the servos in masking tape then epoxied them onto the base plate just behind the screws holding the camera support to the bottom of the chassis. Attach wheels once they have dried overnight and use a screw to secure the wheel to the servo horn.

Step 5: Make the Battery or Buy

You will need to build or purchase a battery. I used 7.2volt 2800mAh Li-ion packs for the robots. I purchased the cells though Tenergy. The cells I used look like they are unavailable so I linked some 2600mAh equivalent versions. (Link) I selected the ones with solder able tabs. I soldered the two cells in series and added a polarized quick disconnect to the pack. I then covered the pack in a protective blue heat shrink. Be sure to sure the correct protective circuit for the pack you are building. If you are not comfortable with creating a pack you can buy rather cheap RC car packs that will do the job. Also be sure that no matter what route you take that you have a proper li-ion/Lipo charger for the batteries. Once you have the battery go ahead and charge it up.

Below is a battery pre-made and will work great for the robots. They just have a bit less capacity.

Pre-made Lipo battery

Step 6: Mount the Electronics

I used a few 3mm RC car hex head screws to mount the Arduino and Pixy. You can also use the hardware that came with the Pixy to mount it. Be sure that if you are using 3D printed parts that you pre-drill the mounting holes out. Mount the so that the Pixy is facing forward and also the Arduino’s programming port is easy to get to. Also add two Sharp IR sensors to the front of the robot for obstacle avoidance.

Step 7: Build Out the Shield

I have added quite a few features with only a few components. I would suggest getting a button to act as a pause button. You will need a resistor for connecting the button to a open digital I/O port. For the servos you will need to supply power via the battery positive and negative. Use 3 pin headers on the shield so you have a place to hook up servo's. Then connect the control pin to a PWM port on the Arduino. Now I used a semi bad practice, as the servos are running off the battery and not though a voltage regulator or a DC to DC converter. If you are able try and supply the servos with a separate supply of 5-6 volts. I have linked a few sites below to guide you with adding items to the shield. For the voltage monitor LED I used a bi-color LED.

Add a button for pausing the servos

You will need a push button and a 10k ohm resistor

Add a voltage divider for sensing voltage greater then 5volts

You will need 2 100k ohm resistors or 1 100k ohm and 1 1Meg ohm resistor

Step 8: Finish the Build Out

Once you have finished building the shield, attach it to the Arduino being sure not to bend any pins. At this time connect up servos to headers on the shield and the Pixy will hook-up to the ISP port that has been passed though to the shield. I use Velcro to attach the battery pack to the chassis this made it easy to swap out the battery with another for a quick turn around.

Step 9: Code

You will need the Pixy library from the follow link:

Pixy library

Also try and update to the newest firmware on the Pixy and its controlling software PixyMon

Note:

When loading programs it is best to disconnect the Pixy from the ISP connector then reconnect it once done.

// ArchReactor Pixybot<br>// Written by: Apollo W. Timbers
// 2015
//
// Code revision mark VI
//
// This code is a intigration of the Pixy vision system into a mobile platform to raise awerness of the maker space
// know as Arch Reactor.
// Sections of the code include following tracked objects, IR sensor object detecting, A PID control loop,
// A low battery warning/cuttoff. and pausing the drive servos so you can train the Pixy.
//
// Portions of this code are derived from Adafruit Industries Pixy Pet code.
// Portions of this code are derived from the Pixy CMUcam5 pantilt example code.
//
 
 <p>#include <SPI.h><br>#include <Pixy.h>
#include <Servo.h></p>
#define X_CENTER 160L
#define Y_CENTER 100L
#define RCS_MIN_POS 0L
#define RCS_MAX_POS 1000L
#define RCS_CENTER_POS ((RCS_MAX_POS-RCS_MIN_POS)/2)
 
// number of analog samples to take per reading
#define NUM_SAMPLES 10
 
// constants won't change. They're used here to
// set pin numbers:
int sum = 0; // sum of samples taken
unsigned char sample_count = 0; // current sample number
const int buttonPin = 2; // the number of the pushbutton pin
const int ledPin = 13; // the pin number of the LED pin
int ledgreen = 5; // the pin number of the LED pin
int ledred = 7; // the pin number of the LED pin
// variables will change:
int buttonState = 0; // variable for reading the pushbutton status
Servo leftServo; // Define the Servos
Servo rightServo;
//Servo gripperServo;
int analogInput = A0;
unsigned int raw;
double vcc = 0;
double voltage; // calculated voltage
const int irSenseleft = A1; // Connect sensor to analog pin A0
int distanceleft = 0;
const int irSenseright = A2; // Connect sensor to analog pin A0
int distanceright = 0;
class ServoLoop
 
{
public:
 ServoLoop(int32_t pgain, int32_t dgain);
 
 void update(int32_t error);
 
 int32_t m_pos;
 int32_t m_prevError;
 int32_t m_pgain;
 int32_t m_dgain;
};
 
ServoLoop panLoop(200, 200);
ServoLoop tiltLoop(150, 200);
 
ServoLoop::ServoLoop(int32_t pgain, int32_t dgain)
{
 m_pos = RCS_CENTER_POS;
 m_pgain = pgain;
 m_dgain = dgain;
 m_prevError = 0x80000000L;
}
 
void ServoLoop::update(int32_t error)
{
 long int vel;
 char buf[32];
 if (m_prevError!=0x80000000)
 {
 vel = (error*m_pgain + (error - m_prevError)*m_dgain)>>10;
 //sprintf(buf, "%ld\n", vel);
 //Serial.print(buf);
 m_pos += vel;
 if (m_pos>RCS_MAX_POS)
 m_pos = RCS_MAX_POS;
 else if (m_pos> 3;
 if (size >= 500);
 // Grip();
 // if (millis() - hold > random(3000, 7000))
 // Drop();
 }
 else if (millis() - lastBlockTime > 3000)
 {
 leftServo.writeMicroseconds(1510);
 rightServo.writeMicroseconds(1510);
 ScanForBlocks();
 }
 if(millis()-timer>2999) // if at least 3000mS have passed
 {
 timer=millis(); // reset timer
 readVcc();
 Voltage();
 }
 if(millis()-timerA>49) //Wait 50 ms between each read for IR
 // According to datasheet time between each read
 // is -38ms +/- 10ms. Waiting 50 ms assures each
 // read is from a different sample
 {
 timerA=millis(); // reset timer
 Pausebutton();
 irRead();
 }
 if (voltage >= 6){
 digitalWrite(ledgreen, HIGH);
 }
 else if ((voltage >= 5) && (voltage < 6))
 {
 digitalWrite(ledgreen, LOW);
 digitalWrite(ledred, HIGH);
 }
 else if (voltage < 5)
 {
 leftServo.writeMicroseconds(1510); // Hold motors because of low poer state
 rightServo.writeMicroseconds(1510);
 digitalWrite(ledgreen, LOW);
 digitalWrite(ledred, HIGH); // LED to warn of low battery
 delay (500);
 digitalWrite(ledred, LOW);
 delay (500);
 }
 
}
 
int oldX, oldY, oldSignature;
 
//---------------------------------------
// Track blocks via the Pixy pan/tilt mech
// (based in part on Pixy CMUcam5 pantilt example)
//---------------------------------------
int TrackBlock(int blockCount)
{
 int trackedBlock = 0;
 long maxSize = 0;
 uint32_t hold = 0;
 
 Serial.print("blocks =");
 Serial.println(blockCount);
 
 for (int i = 0; i < blockCount; i++)
 {
 if ((oldSignature == 0) || (pixy.blocks[i].signature == oldSignature))
 {
 long newSize = pixy.blocks[i].height * pixy.blocks[i].width;
 if (newSize > maxSize)
 {
 trackedBlock = i;
 maxSize = newSize;
 }
 }
 }
 
 int32_t panError = X_CENTER - pixy.blocks[trackedBlock].x;
 int32_t tiltError = pixy.blocks[trackedBlock].y - Y_CENTER;
 
 panLoop.update(panError);
 tiltLoop.update(tiltError);
 
 pixy.setServos(panLoop.m_pos, tiltLoop.m_pos);
 
 oldX = pixy.blocks[trackedBlock].x;
 oldY = pixy.blocks[trackedBlock].y;
 oldSignature = pixy.blocks[trackedBlock].signature;
 return trackedBlock;
}
 
void FollowBlock(int trackedBlock)
{
 int32_t followError = pixy.blocks[0].x-X_CENTER; // How far off-center are we looking now?
 
 // Size is the area of the object.
 // We keep a running average of the last 8.
 size += pixy.blocks[trackedBlock].width * pixy.blocks[trackedBlock].height;
 size -= size >> 3;
 
 // Forward speed decreases as we approach the object (size is larger)
 int forwardSpeed = constrain(400 - (size/400), -100, 400); 
 
 // Steering differential is proportional to the error times the forward speed
 int32_t differential = (followError + (followError * forwardSpeed))>>6;
 
 // Adjust the left and right speeds by the steering differential.
 int leftSpeed = constrain(forwardSpeed + differential, -400, 400);
 int rightSpeed = constrain(forwardSpeed - differential, -400, 400);
 
leftSpeed = map(leftSpeed,-400,400,1650,1350); // Map to servo output values
rightSpeed = map(rightSpeed,-400,400,1350,1650); // Map to servo output values 
 
 // Update servos
 leftServo.writeMicroseconds(leftSpeed);
 rightServo.writeMicroseconds(rightSpeed);
}
 
int scanIncrement = (RCS_MAX_POS - RCS_MIN_POS) / 150;
uint32_t lastMove = 0;
//---------------------------------------
// Random search for blocks
//
// This code rotates slowly in a circle
// until a block is detected
//---------------------------------------
void ScanForBlocks()
{
 if (millis() - lastMove > 10)
 {
 lastMove = millis();
 //panLoop.m_pos += scanIncrement;
 //if ((panLoop.m_pos >= RCS_MAX_POS)||(panLoop.m_pos <= RCS_MIN_POS))
 //{
 tiltLoop.m_pos =(RCS_MAX_POS * 0.6);
 //scanIncrement = -scanIncrement;
 //if (scanIncrement < 0)
 
 leftServo.writeMicroseconds(1460);
 rightServo.writeMicroseconds(1460);
 delay (20);
 
 pixy.setServos(panLoop.m_pos, tiltLoop.m_pos);
 }
} 
 
// Take multiple readings, and average them out to reduce false readings
void irRead()
{
 int averagingleft = 0; // Holds value to average readings
 int averagingright = 0; // Holds value to average readings
 
 // Get a sampling of 5 readings from sensor
 //for (int i=0; i<3; i++)
 {
 distanceleft = analogRead(irSenseleft);
 averagingleft = averagingleft + distanceleft;
 distanceright = analogRead(irSenseright);
 averagingright = averagingright + distanceright;
 }
 distanceleft = averagingleft / 5; // Average out readings
 //return(distanceleft); // Return value
 Serial.println(distanceleft, DEC);
 distanceright = averagingright / 5; // Average out readings
 //return(distanceright); // Return value
 Serial.println(distanceright, DEC);
 if (distanceleft > 85)
 {
 leftServo.writeMicroseconds(1510); //allstop
 rightServo.writeMicroseconds(1510);
 delay(200);
 leftServo.writeMicroseconds(1660); //backup
 rightServo.writeMicroseconds(1460);
 delay(500);
 leftServo.writeMicroseconds(1460); //spin
 rightServo.writeMicroseconds(1460);
 delay(300);
 
 }
 else if (distanceright > 85)
 {
 //allstop
 leftServo.writeMicroseconds(1510);
 rightServo.writeMicroseconds(1510);
 delay (200);
 }
}
 
void Voltage()
{
 vcc = readVcc()/1000.0;
 raw = analogRead(analogInput);
 voltage = ((raw / 1023.0) * vcc) * 2;
 Serial.print ("Battery Voltage = ");
 Serial.println(voltage, DEC);
 Serial.print ("VCC Voltage = ");
 Serial.println(vcc, DEC );
}
 
long readVcc() {
 long result;
 // Read 1.1V reference against AVcc
 ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 delay(2); // Wait for Vref to settle
 ADCSRA |= _BV(ADSC); // Convert
 while (bit_is_set(ADCSRA,ADSC));
 result = ADCL;
 result |= ADCH<<8;
 result = 1106400L / result; // Back-calculate AVcc in mV
 return result;
}
 
void Pausebutton()
{
 sample_count = 0;
 sum = 0;
 buttonState = digitalRead(buttonPin);
 
 // check if the pushbutton is pressed.
 // if it is, the buttonState is HIGH:
 if (buttonState == HIGH) {
 // turn LED on:
 leftServo.writeMicroseconds(1510);
 rightServo.writeMicroseconds(1510);
 delay(18000);
 }
}

Step 10: Troubleshooting and Thoughts

Troubleshooting:
Below are some common issues you may find when you have a robot up and running.

Q: My robot is avoiding the ball instead of following it.

A:Try swapping around the left and right drive servos they may be reversed. Also check the PID loop in the program.

Q: The robot is jerky or acting sporadic

A: Try resetting the Arduino, and retraining the Pixy for the color. Attempt using a different color like orange for the robot to track.

Q: My robot seem sluggish and or is losing the tracked object fast

A: Make sure the battery pack is charged and there is ample lighting.

Q: My robot just spins after I first turned it on

A: Verify the connection from the Pixy to the Arduino and If your like me I always forget to take off the protective lens cap.

Q: My robot is oscillating violently left and right as it tracks the ball

A: Try increasing the proportional error in the FollowBlock sub routine. This line of code “int32_t differential = (followError + (followError * forwardSpeed))>>6;”

Things I may do differently on the next version:

  1. Further the design of a usable wheel in the future would drive down cost. Lego wheels work great, but they are expensive and can be hard to find.
  2. Adding a Arduino with a Bluetooth module would be great once the price drops a little further.
  3. I attempted to add battery protection to the Li-ions. I could not get current flow out of the modules purchased from Tenergy.
  4. Adding a full pan tilt module to the robot would increase cost a bit, but I think it would help with human interaction and greatly improve the tracking of the robot as a whole.
  5. Design a way for the robot to squint in full sun light and/or calibrate with an external light sensor. This would help in multiple lighting situations that the robot may encounter.
  6. Further reduction in overall size worked very well for the goal of these robots.
  7. I would like to try attaching a Pixy, with updated firmware, to a full sized 330mm base with reduced motor speed.
Tech Contest

Participated in the
Tech Contest

Robotics Contest

Participated in the
Robotics Contest