Monday, January 11, 2010

Arduino+PC two axis controlled Laser Gun

If you are a Maker (it seems not politically correct to say Hacker), or a DIY fan, here is a project that I developed in the weekend, playing with Arduino board and Processing.

I built this hack with my kids: Luca and Giulio, and we had lots of fun.


Our Laser Gun works using two servos, connected to an Arduino board. The Arduino is connected to the pc via usb. The thing is controlled via mouse movements on the pc, over a window with a picture taken from the gun position. The laser works normally in low intensity "pointing mode", and when fired is much powerful.
The arduino microcontroller and the pc communicate via serial usb interface. A custom Processing software allows mouse interaction, and enables guidance.

Parts used:
Arduino 2009 board
2 light servos (I used two standard hitec hs55 8gr)
1 laser pointer (savaged from a broken toy gun)
1 breadboard (not strictly necessary)
some adhesive putty (I used UHU Patafix)
some wires


Circuit description:
  • black cable of both servos is connected to ground
  • red cable of both servos is connected to +5Vcc
  • x-servo (the lower one, moving its head on the horizontal left-right plane) control yellow is connected to arduino digital pin 9

  • y-servo (the upper one, moving its head on the vertical top-down plane) control yellow is connected to arduino digital pin10
  • laser pointer negative is connected to ground
  • laser pointer positive is connected to Arduino digital pin 11 (the two laser intensities are managed controlling it -improperly- like a servo, but it works)


Software:
The project requires two pieces of software: one written in Arduino Language (based on Wiring) that is run on the Atmel 328 microcontroller on Arduino 2009 board, and a Processing sketch (Processing programs are called sketches) that is run on the pc.
The two softwares communicate with a standard serial protocol, called Firmata, which is implemented in libraries both on the Arduino side and on the PC Processing side.
I am currently using Microsoft Windows Vista Ultimate 64bit. This is not the best platform for development, and I do not recommend such a setting. A better solution would be Microsoft XP 32 bit.


On the pc, I installed the standard Arduino Development Environment (currently I run arduino-0017), with no additions. This allows to code, edit, compile and download to the Arduino board and test programs written in Wiring.
Arduino Development Environment is based on a version of Processing. Once installed


On the PC I also installed the latest Processing environment (1.0.9 in my case). An add-on library is needed so to support the Firmata serial protocol. This library zip file ( http://www.arduino.cc/playground/uploads/Interfacing/processing-arduino-0017.zip ) file has to be expanded and the three included directories (examples, libraries, src) must be copied to the following folder: \libraries\arduino





Here is the software to be downloaded to the Arduino controller. It is the standard Firmata "servo" template (you can find it among the included examples) with a very simple addition for managing the laser, connected on pin 11.

Note: Unfortunately, it seems that blogger does not like the "<" and ">" characters inside the listings.
the following first two lines are actually #include"<"Firmata.h">" and #include"<"Servo.h">" but you have to remove the " characters.
Additionally, I uploaded the code also on this posterous entry, and on these  two scribd entries: 1 and 2.

#include "<"Firmata.h">"
#include "<"Servo.h">"
Servo servo9;
Servo servo10;
Servo laser11;

void analogWriteCallback(byte pin, int value)
{
    if(pin == 9)
      servo9.write(value);
    if(pin == 10)
      servo10.write(value);
    if(pin == 11)
      laser11.write(value);
     
}


void setup()
{
    Firmata.setFirmwareVersion(0, 2);
    Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);

    servo9.attach(9);
    servo10.attach(10);
    laser11.attach(11);
  
    Firmata.begin(57600);
}

void loop()
{
    while(Firmata.available())
        Firmata.processInput();
}



And here follows the code to be run on the PC Processing environment

I am using a small font, so not to cause unwanted line breaks.
The Processing code will have to be adapted to your environment defining the correct identifier for the USB serial interface (see at the beginning of the setup  procedure), and also to define the proper name of the picture you want to display on screen while operating the laser (see setup procedure). I suggest to use a picture resized to 800x600, taken from the place in which the laser will be put, towards the target.
The picture file has to be put in the Processing sketch directory in which you will save the project. To access that directory just select the Sketch menu from the Processing environment, and then "show sketch folder".


//
// Processing code for interacting with arduino firmata
// to control servos connected to analog 9 and analog 10 pins
// project "lasergun" by Marco Guardigli, mgua@tomware.it 

//

// see
// http://marco.guardigli.it/2010/01/arduinopc-two-axis-controlled-laser-gun.html

// 
// this code is free software, released under the GNU GPL license
// see www.gnu.org for license details
//
// copyleft Marco Guardigli
//    2010 jan 11: first version
//
//

import processing.serial.*;
import cc.arduino.*;       
Arduino arduino;

int maxx;              // windows sizes
int maxy;
int calibrating = 0;   // nonzero during calibration, states are 0,1,2

int calibrateminx=80;  // recalibration window
int calibrateminy=80;
int calibratemaxx=100;
int calibratemaxy=100;
int cx1, cy1, cx2, cy2;  // screen mousex/y coords of the two calibration points

float dzx, dzy;

int maxservox=170;  // maximum servo excursions - to be redefined in recalibration
int minservox=10;
int maxservoy=170;
int minservoy=10;
int initialservox = 90;
int initialservoy = 90;
int servox, servoy;    // current servos positions -in servo range-
int laseroff = 0;      // laser is controlled (improperly) as a servo
int laseron = 170;     // zero is not actually turned off, but is dimmer

PImage room;           //  room background (photo shot from laser initial position)


void setup() {
  println(Arduino.list()); 

// IMPORTANT! This code will not work if you do not write the correct 
// id of the serial interface in the next line (in my case 3)
  arduino = new Arduino(this, Arduino.list()[3], 57600);
  arduino.analogWrite(9,initialservox);
  arduino.analogWrite(10,initialservoy);
  arduino.analogWrite(11,laseroff);  // laser off
// put in the next line the file name of your ambient picture

// taken placing the camera in the place where the lasergun is 
// so to have a correct perspective on the screen

  room = loadImage("myplace.jpg");   // put here the picture of your place
  maxx = room.width;                 // I suggest to resize the picture to 800x600
  maxy = room.height;
  size(maxx,maxy);
  background(120);
  image(room,0,0);  //show background room
}


void draw() {
  switch (calibrating) {
    case 0: {  // no calibration in course: laser follows pointer 
      servox = int(map(mouseX,0,maxx,minservox,maxservox));
      servoy = int(map(mouseY,0,maxy,minservoy,maxservoy));
      arduino.analogWrite(9,servox);
      arduino.analogWrite(10,servoy);
    }
    case 1: {   // need to read first calibration point
      cx1 = mouseX;
      cy1 = mouseY;
    }
    case 2: {   // need to read second calibration point
      cx2 = mouseX;
      cy2 = mouseY;
    }
  }
}


void mousePressed() {
    if (mouseButton == LEFT) {
      if (calibrating == 0) {                // draw shot on screen
        arduino.analogWrite(11,laseron);     // and intensify laser
        stroke(200,200,0);                 
        fill(200,0,0);
        ellipse(mouseX,mouseY,5,5);
        delay(500);
        ellipse(mouseX,mouseY,10,10); 
        arduino.analogWrite(11,laseroff); 
      }
    }
}   
   

void mouseReleased() {
  if (mouseButton == RIGHT) {
    switch (calibrating) {
      case 0: { 
            calibrating = 1;   // stops laser following mouse pointer
            arduino.analogWrite(9,calibrateminx);
            arduino.analogWrite(10,calibrateminy);
            arduino.analogWrite(11,laseron);     // and intensify laser           
            println("cx1/cy1: point mouse to where laser pointer is and RCLICK");
            break;
      }
      case 1: {  // arriving here after rclick release in calibration point 1
            calibrating = 2;
            arduino.analogWrite(9,calibratemaxx);
            arduino.analogWrite(10,calibratemaxy); 
            arduino.analogWrite(11,laseron);     // and intensify laser                       
            print("  calibration point1: "); print(cx1); print(" , ");  println(cy1);
            println("cx2/cy2: point mouse to where laser pointer is and RCLICK");
            break;
      }
      case 2: {  // arriving here after rclick release in calibration point 2
            print("  calibration point2: "); print(cx2); print(" , ");  println(cy2);
            // (cx1,cy1) corresponds to (calibrateminx, calibrateminy)
            // (cx2,cy2) corresponds to (calibratemaxx, calibratemaxy)
            // i will recalculate minservox, minservoy and maxservox, maxservoy
            if (((cx2-cx1) != 0) && ((cy2-cy1) != 0)) {
              stroke(200);
              line (cx1,cy1,cx1,cy2);
              line (cx1,cy2,cx2,cy2);
              line (cx2,cy2,cx2,cy1);
              line (cx2,cy1,cx1,cy1);
 
              dzx = (calibratemaxx - calibrateminx);
// for some reason dzx=(calibratemaxx-calibrateminx)/(cx2-cx1)
// was not calculated well ( !!! )
// so I had to split the calculation int two statements
              dzx = dzx / (cx2 - cx1);              // dzx is now "how much servo per pixel"
              dzy = calibratemaxy - calibrateminy;
              dzy = dzy / (cy2 - cy1);

              float leftx = calibrateminx - ( dzx * cx1 );
              float rightx = calibratemaxx + ( dzx * (maxx-cx2) );
              float upy = calibrateminy - ( dzy * cy1 );
              float downy = calibratemaxy + ( dzy * (maxy-cy2) );

              minservox = int(leftx);
              maxservox = int(rightx);
              minservoy = int(upy);
              maxservoy = int(downy);
            } else {
              println("Invalid calibration points selected.");
            }           
            calibrating = 0;
            arduino.analogWrite(11,laseroff);     // and dim laser           
            break;
          } // end case 2
      default: {
            break;
          } // end case default
      } // end switch
    } // end if mousebutton right
} // end mouseReleased




Features:
Once you have loaded the software on the Arduino board, this will be automatically run at boot. So you will not need to load it again unless you decide to change it.
Operation of the Laser Gun just requires you to connect the USB cable to the pc, and launch the Processing Environment. The Firmata library is designed to "bring outside" of the board all its features, allowing complete control from the PC.

Once initialization has been completed, you will see a window with the picture you loaded in the sketch folder. If you move the mouse, the laser gun should follow your mouse movements.
When you press the left mouse button, the led intensity will grow, to represent "fire". I will probably add some audio features, because the thing is too silent now :-).

You will soon notice that there is a non correspondance between what you point on the screen and the actual position of the laser. This happens because you need to calibrate the system.
Calibrating means that the servo min and max boundaries have to be redefined so to match the space in which you use the LaserGun.

Calibration is performed clicking the right button, and requires you to point with the mouse the place in the screen corresponding to the actual place where you see the laser in your room. When positioned, press again right click, and repeat for a second point that the system will ask. After this procedure has been completed you should have a (reasonable) correspondance between what you aim and what you kill. ( :-)



Safety precautions
Be careful not to point laser in people or animals eyes.
If you use more powerful servos, or a more powerful laser, it is better to power the system via an external power supply, and not thru the USB.






Feel free to improve and enjoy at will




Marco  ( @mgua on twitter )

No comments: