Click here to read Part 1 of this article >
This is part 2 of the introduction to CMUcam5 Pixy. Please refer back to Intro to: CMUcam5 Pixy Vision Camera Sensor if you’re not already familiar with PixyMon. In Part 1, I covered the basics of Pixy, explained the hello_world code and created a simple servo-driven application. In this tutorial, I will go a step further and build a ball balancing beam. A servo will set the angle of the beam so the ball stays in the middle, and of course, the ball will be tracked by the Pixy camera sensor.
Hardware
Software
Processing is a very useful, flexible piece of software. It is mostly used for visual arts and visual literacy within technology. It has over 100 libraries and support for various projects. It’s very well documented and there are guide books available covering topics from programming basics to visualization. It supports all operating systems (GNU/Linux, Mac OS X, and Windows). The design of the software is almost identical to the Arduino IDE.
Today, we’ll use Processing to communicate with the Arduino via serial communication.
Figure 1: GUI of Processing
For this project I will make a ball balancing beam. A “tunnel” made of wood will be balancing the ball like a scale (Figure 2). The Beam is 44cm in width and 3cm in height. I made it narrow like a tunnel so the ball that we’ll be tracking can’t fall out.
To move this entire beam I’m using a S06NF servo motor, which will be controlled by the Arduino. We’ll go over the code later in the tutorial. I’ve placed the servo ¼ of the way from the left end of the beam.
Figure 2: S06NF STD Servo Motor / ©RobotShop inc.
The servo will move the beam up and down and the ball will follow this path.
Figure 3: Beam moving up and down
The pixy camera will sit above the beam. I’ll set it up so the visual field of the camera is only limited to the beam. It will only track the ball and nothing else (Figure 14).
First we will need supplies for the construction. I’ll be using a simple XXMM wood (20 cm x 27 cm). I cut the wood using a circular saw, but you can use whatever type of saw you have available that will allow you to make straight, even cuts.
Figure 4: XXMM wood
Remember, only the right tools will allow you to make this beam perfect! I’m using a hammer, a ruler, nails, sandpaper, hot glue, a drill, and a saw.
Figure 5: Tools
First, I’ll make a tunnel where the ball will move left or right. The tunnel will be comprised of 4 wood panels (21cm x 3cm each) for the sides. Two pieces of wood (4cm x 3cm) in height will be attached to each end of the tunnel. The dimensions of the base are 42cm x 3cm x 1cm.
I’m using 15mm clout nails to connect the parts.
Figure 6: Close up of the tunnel
There are many ways to make a tilt point in the middle of the tunnel. I used a very simple method mainly because it is the cheapest and the easiest method. I used a long nail and two small tubes that act like bearings. I marked the center of the tunnel first, then hot glued the bearings to it and inserted the nail.
Figure 7: Nail and tubes for tilt point construction
To make the tilt point, we will also need to make holders for the nail. I used 2 pieces of 8cm x 2cm wood as shown in Figure 8. I also made a small platform that will hold everything together. The dimensions are 12cm x 4.5cm.
Figure 8: Tilt point holders
I used a small piece of wood to mount the servo and to raise it.
Figure 9: Servo mounted on the wood
In this tutorial, I’ve used Arduino UNO but feel free to use any other Arduino that has an SPI connector for connecting to the Pixy Camera.
Once the construction is done, it’s time to move on to connecting the Pixy camera to the Arduino and then to the servo. The schematic is the same as in Intro to: CMUcam5 Pixy Vision Camera Sensor. I’m still using an external 5V supply for the servo.
!WARNING! Don’t forget to connect the GND’s together. If you don’t connect the Power supply, Servo, and Arduino GND together, the servo will freak out!
Figure 10: The wiring diagram
Next, I need to set up the Pixy somewhere above the beam construction so it can detect the ball at any time. Adjust the settings so it only detects the ball. Please refer back to Part 1 to set the settings.
Figure 11: Pixy vision
Now let’s look at some code. To test if the servo works correctly, I modified the middle, far-left and far-right angles, so it fits my construction:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <Servo.h> uint8_t leveled = 110; //middle positon for s1 to keep the board leveled uint8_t far_right = 180; //far left positon for s1 to keep the board leveled uint8_t far_left = 0; //far right positon for s1 to keep the board levele Servo s; void setup(){ s.write(leveled); delay(2000); s.write(far_right); delay(2000); s.write(far_left); delay(2000); } void loop(){ } |
Of course, you can adjust the variables to your preference.
Earlier, I introduced a software called Processing. I’ll be using this to communicate with the Arduino via serial communication.
Simple serial communication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include #include char val; // Data received from the serial port int ledPin = 13; // Set the pin to digital I/O 13 void setup() { pinMode(ledPin, OUTPUT); // Set pin as OUTPUT Serial.begin(9600); // Start serial communication at 9600 bps } void loop() { if (Serial.available()) { // If data is available to read, val = Serial.read(); // read it and store it in val } if (val == '1') { // If 1 was received digitalWrite(ledPin, HIGH); // turn the LED on } else { digitalWrite(ledPin, LOW); // otherwise turn it off } delay(10); // Wait 10 milliseconds for next reading } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import processing.serial.*; Serial myPort; // Create object from Serial class void setup() { size(200,200); //make our canvas 200 x 200 pixels big String portName = Serial.list()[0]; //change the 0 to a 1 or 2 etc. to match your port myPort = new Serial(this, portName, 9600); } void draw() { if (mousePressed == true) { //if we clicked in the window myPort.write('1'); //send a 1 println("1"); } else { //otherwise myPort.write('0'); //send a 0 } } |
It creates a 200x200px window and initilizes the serial port. Void draw() function checks if a mouse is pressed on the window (1 if it was pressed or otherwise it’ll write a 0).
Now, let’s test the code. Click run and try clicking anywhere on the window. Your LED should flash. That means it’s working correctly!
Figure 12: Basic Processing and Arduino code testing
I got values of the servo and processed them in Processing so it could make a small graph like the one shown below:
Figure 13 The graph example
To create a graph, use the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import processing.serial.*; Serial myPort; // The serial port int xPos = 1; // horizontal position of the graph float inByte = 0; void setup () { // set the window size: size(400, 300); // List all the available serial ports // if using Processing 2.1 or later, use Serial.printArray() println(Serial.list()); // I know that the first port in the serial list on my mac // is always my Arduino, so I open Serial.list()[0]. // Open whatever port is the one you're using. myPort = new Serial(this, Serial.list()[0], 9600); // don't generate a serialEvent() unless you get a newline character: myPort.bufferUntil('\n'); // set inital background: background(0); } void draw () { // draw the line: stroke(127, 34, 255); line(xPos, height, xPos, height - inByte); // at the edge of the screen, go back to the beginning: if (xPos >= width) { xPos = 0; background(0); } else { // increment the horizontal position: xPos++; } } void serialEvent (Serial myPort) { // get the ASCII string: String inString = myPort.readStringUntil('\n'); if (inString != null) { // trim off any whitespace: inString = trim(inString); // convert to an int and map to the screen height: inByte = float(inString); println(inByte); inByte = map(inByte, 0, 1023, 0, height); } } |
Arduino code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
#include #include #include #include //37 164 288 uint8_t leveled = 110; //middle positon for s1 to keep the board leveled uint8_t far_right = 180; //far left positon for s1 to keep the board leveled uint8_t far_left = 0; //far right positon for s1 to keep the board levele int current_pos = leveled; int percentage,var,_percen; Servo s; Pixy pixy; void test_board(){ while(Serial.read() != 'b'); Serial.write("Starting test"); s.write(leveled); delay(2000); s.write(far_right); delay(2000); s.write(far_left); delay(2000); Serial.write("Finished test, press any key to continue"); while(Serial.read() != 'c'); s.write(current_pos); Serial.write("Continued"); } void setup() { Serial.begin(9600); s.attach(9); pixy.init(); while (!Serial); //test_board(); s.write(current_pos); } void _servo(unsigned char side,int var){ //by the % we get how "hard" we need to wip :D var = var - 90; if(side == 'L'){ //Serial.write("LEFT"); //90 180 _percen = 90 + var; s.write(_percen); }else{ //Serial.write("RIGHT"); //0 90 _percen = 90 - var; s.write(_percen); } } void loop() { static int i = 0; int j; uint16_t blocks; char buf[32]; // grab blocks! blocks = pixy.getBlocks(); // If there are detect blocks, print them! if (blocks) { i++; // do this (print) every 50 frames because printing every // frame would bog down the Arduino if (i%1 ==0) { //sprintf(buf, "Detected %d:\n", blocks); //Serial.print(buf); for (j=0; j<blocks; j++) { //sprintf(buf, " block %d: ", j); //Serial.print(buf); // pixy.blocks[j].print(); uint32_t x_pos= pixy.blocks[j].x; //Serial.write(x_pos); percentage = (x_pos - 35) / 2.5; if(percentage <= 40 && percentage >= 0){ // Serial.write("LEFT"); var = percentage / 0.4; _servo('L',var); }else if(percentage >= 60 && percentage <= 110){ //Serial.write("RIGHT"); var = (percentage - 60) / 0.5; _servo('R',var); }else{ //Serial.write("MIDDLE"); } } } } } |
I convert the x position from the Pixy to 0-100% and with that I know exactly where the ball is. By knowing where the ball is, I adjust the intensity of the servo swings. If the ball is <=10% then the servo will swing very fast to balance; if it’s around ~40% then the servo will only swing slightly to balance. Balancing is very tricky. We can certainly improve this algorithm to make it more precise.
Here are some suggestions for imporvement:
There are many projects out there that use a similar concept to balance an object. In additional to Pixy, you can use OpenCV with any web camera to detect objects and colors. Next to Processing, there is also Max/MSP version 5. You can utilize distance sensors, pressure sensors, etc. So there are many ways you can improve this project to make it sturdier, steadier, and faster.