Introduction: Laser Painting With Motion Control and Arduino

Do you like lasers, motion control, neo-pixels, or glow in the dark art? Of course you do, so why not combine them all! Using various supplies you can 'paint' a picture using laser light. Motion control will let you actuate (aka move) the servos which in turn controls where the laser points. The laser will leave a line on the glow in the dark material and then slowly fade away. The art you make is up to you!

Why would you want to do this. Well besides being cool, this project is a good exploration of servos, accelerometers, neo-pixel rings, and control systems. The challenge is combining the various parts and getting them all working together. You will be guided through the steps and technical ideas, and left at the end with your own motion controlled light painter!

Step 1: Components and Tools

To build this project you will need the following components and tools;

Components:

Tools:

  • Soldering station
  • Pliers
  • Micro USB to standard USB
  • Optional: Multimeter to check voltages

Step 2: Design and Simulate

To paint with light, we need a device to read and display user input, a power system, a device to actually move the laser, and a Arduino to manage it all:

  • Motion Controller: An accelerometer will measure tilting gestures. This is how the user chooses where the laser points. A neo-pixel ring will display how the controller is being tilted. A pushbutton turns the laser on and off.
  • Servos: Two servos will move the laser pointer. One will move left and right (x axis). The other servo will be glued sideways onto the other servo, allowing it to move up and down (y axis). This allows us to point the laser anywhere in front of us.
  • Power Systems: A wall wart and regulator will power the project. The wall wart powers the Arduino, which in turn powers the accelerometer (3V3), neo-pixel (5v), and laser (5V). The wall wart also powers the regulator, which in turn powers the servos. Servos, and motors in general, induce a lot of noise. They create spikes of power and cause signal issues. The regulator helps separate the servos from the rest of the system so everything runs smoothly.
  • Microntroller: An Arduino will read in the accelerometer values. It will then move the servos accordingly and light up the corresponding neo-pixel LED.

The simulation below shows the entire system working together. The top two power supplies and four resistors are to fake the circuit into thinking we are tilting the accelerometer. Move the voltage dials to 'fake tilt' the motion controller. The servos will move all the way down or up because the acceleromter-fake is constantly telling the Arduino to make the servos move up or down. You will also see the neo-pixel ring display where the device is 'tilted'.

Step 3: Building the Controller

The controller contains the accelerometer, neo-pixel ring, and pushbutton. This is what the user interacts with. We want the accelerometer to be in the center of the protoboard controller so everything is balanced. The neo-pixel ring can be placed anywhere on the controller. I choose to have it in the center of the controller, encircling the accelerometer. The button can go to the right of left depending on your preference.

Soldering the accelerometer and neo-pixel ring to the protoboard can be challenging because we want the devices to look nice and make solid connections. There are only a few thru holes on the neo-pixel display and we don't want these connection to short with the connections on the accelerometer.

I first soldered the neo-pixel V+ pin to the + rail on the protoboard. You can use a single male header, or a small piece of wire to make this connection. When soldering, make sure to not burn the neo-pixel! Next solder the neo-pixel GND and IN pins to the protoboard.

I then soldered the accelerometer to the protoboard. Make sure the two ends of the accelerometer are not electrically connected. Use the gap in the middle of the protoboard to make an electrical gap between both sides of the accelerometer.

Next we need to solder the button. Solder the four leads from the button to an empty spot on the protoboard.

Now that the three components are soldered to the protoboard, we need to get the connections wired to the arduino. The connection we need to focus on are:

  • Neo-Pixel
    • V+ pin
    • GND
    • IN
  • Accelerometer
    • Vcc
    • GND
    • XOUT
    • YOUT

To get these connections to the Arduino, I first routed the wires to line-up at the bottom right corner of the protoboard. In other words, solder a wire from each connection to an open spot on the protoboard. All the connections should now be next to each other on an open spot on the protoboard. I then soldered these connections to a long set of wires (5ft or so). This set of wires could then be connected to the Arduino (through the breadboard).

Step 4: Servos!

Now we need to setup the servos. For the laser to turn left and right, we have the x axis servo. I hot glued the servo to a piece of solid cardboard (so the rotating part of the servo is perpendicular to the ground). For the laser to go up and down, we have the y axis servo. This servo is hot glued sideways onto the rotating part of the x axis servo. Gluing one servo onto another means that both axis of motion can be achieved at the same time. Once the laser is connected it will be able to pan up and down, and left and right. This allows a spherical 180 degree of motion. In other words it can point the laser at anything in front of it.

Step 5: Code

Now that we have the hardware setup, lets figure out how to control it all!

We start our Arduino sketch by including the libraries that let us control the servos and neo-pixel ring. To get the neo-pixel library working, you will also need to download this libraryand place it in the Libraries folder in your directory. For window machines this can be found at: 'C:\Program Files (x86)\Arduino\libraries'. The servo library was preinstalled in the library folder when you first downloaded the IDE. For more info on servos with Arduino, look here.

#include <Adafruit_NeoPixel.h> //install this library to easily control the neo-pixel ring
#include <Servo.h> //include this library to easily control servos

Next we need to initialize the neo-pixel ring and the servos. This means we are creating an object that is a neo-pixel ring and a servo respectively. Whenever we use 'strip' the computer knows we are dealing with a neo-pixel ring and it will use the Adafruit_NeoPixel library. Using the objects xServo or yServo will use the Servo library. The #define lines simply means that wherever there is the word PIN, it will be replaced with A2, and STRIPSIZE will be replaced with 16. #define is a pre-compiler directive. Learn more about #define here.

#define PIN A2 //neo-pixel control ring
#define STRIPSIZE 16 //tell your code the size of the ring

Adafruit_NeoPixel strip = Adafruit_NeoPixel(STRIPSIZE, PIN, NEO_GRB + NEO_KHZ800); //inialize the ring, call it 'strip'

Servo xServo; //initalize the x servo and call it xServo
Servo yServo; //initalize the y servo and call it yServo

Now we can add the global variables, the things we will use and change throughout the entire program. The first four variables are used in the mapping function later in the code. They can be increased or decreased to calibrate the controller.

 int brightness = 50; //set the brightness to 50 (out of 255);">
//THE ACCELEROMETER IS NOT VERY ACCURATE AND NEEDS CALIBRATION
//ADJUST THESE VALUES TO CALIBRATE YOUR CONTROLLER
//These values tell the mapping function how translate the values
int xLeft = 280; 
int xRight = 365;
int yForward = 295;
int yBack = 377;

int brightness = 50; //set the brightness to 50 (out of 255) 

int xRaw; //x axis from accelerometer before being mapped
int yRaw; //y axis from accelerometer before being mapped
  
int x; //x axis from accelerometer after being mapped
int y; //y axis from accelerometer after being mapped

//start the servos at position 0
int servoX = 0; 
int servoY = 0;

Lets write the setup function now. Remember that the void setup() runs once when powered on. You can choose to initialize the serial monitor or leave it out. It can be incredibly helpful in debugging though! The next three lines of code start the ring, set its brightness, and turn off all the LEDs. It sets the stage for us to use the ring later in the code. The next couple lines of code declare the x and y axis pins (analog 5 and analog 4 respectively) as inputs. The couple lines of code after that use '.attach()' which is used to tell the Arduino which pin to find the servo. '.write()' is what we use to actually move the servos (from 0, to the midpoint 90, to 180).

void setup() {
  Serial.begin(9600);
  
  strip.begin(); //start the neo-pixel ring
  strip.setBrightness(20); //set the brightness to 20 (out of 255) 
  strip.show(); // Initialize all pixels to 'off'

  pinMode(A5,INPUT); //tell the ardino to get x axis measurements from analog pin 5
  pinMode(A4,INPUT); //tell the ardino to get y axis measurements from analog pin 4

  xServo.attach(8); //tell the arduino that the x servo is attached to digital pin 8
  yServo.attach(9); //tell the arduino that the y servo is attached to digital pin 9

  //start both servos at the middle position (0 is left, 90 is middle, 180 is right)
  xServo.write(90);
  yServo.write(90);
}

Next we will look at the void loop() function. This continuously runs while the board is powered. In this program, the loop does nothing but call other functions. Breaking your code up into other functions that do specific tasks can be a great way to organize your code. getAccelValues() will read the controller tilt, map the results to easy-to-use variables, and update the associated global variables. displayTilt() takes the newly updated variables and decides which LEDs need to be lit up to display the controller tilt. It then lights up the corresponding LEDs. writeServos() take the values from getAccelValues and moves the servos accordingly.

void loop() { //the loop does nothing but call the functions that do the important stuff
  getAccelValues(); //start by getting the accelerometer values and mapping them to easy to use numbers
  displayTilt(); //determine how the conrtoller is tilted and light up the corresponding pixel
  writeServos(); //move the servos depending on how the controller is tilted
}

Lets take a deeper look into the getAccelValues() function. It starts by reading in the values from analog pin 5 and analog pin 4. The couples lines after that takes the raw values and maps them to easy to use values from 6 to -6. Using integers from 6 to -6 makes it easy to interface with the neo-pixel ring since there are a discrete number of LEDs (16 in our case). To learn more about the useful mapping function, read this. The servoX and servoY lines of code do the same thing but are meant for controlling the servos. Changing these mapping values will change how quickly the servos move.

void getAccelValues(){
  xRaw = analogRead(A5); //read in the x tilt from the accelerometer
  yRaw = analogRead(A4); //read in the y tilt from the accelerometer
  
  x = map(xRaw,xLeft,xRight,6,-6); //map the raw x value to work well with the ring, it goes from 6 to -6 because of the orientation of the accelerometer on the controller 
  y = map(yRaw,yForward,yBack,6,-6); //map the raw y value to work well with the ring, it goes from 6 to -6 because of the orientation of the accelerometer on the controller

  servoX = map(xRaw,xLeft,xRight,-2,2); //map the raw x value to work well with the servo, the smaller the number the slower and more accurate the servo will be
  servoY = map(yRaw,yForward,yBack,2,-2); //map the raw y value to work well with the servo, the smaller the number the slower and more accurate the servo will be
}

displayTilt() was the tedious part of the code. How does one get the ring to emulate gravity? How will the red LED always be at the lowest point? While it might not be the most efficient method, I decided to connect each LED to a specific x and y axis behavior. Refer to the second picture on this step. Each LED corresponds to a specific amount of tilt of the controller. Based on this I wrote 16 if statements that would control which LED to turn red. An else statement turned all the LEDs green if the controller is held flat. The last two lines pushes the results to the ring. In other words, it lights up the ring. The delay gives the system time to 'do its job'.

void displayTilt(){
  strip.clear(); //start by clearing everything

  /*
   THE FOLLOWING LINES OF CODE CHECK THE X AND Y VALUES, IT MAPS EACH PIXEL ON THE RING TO A RANGE OF X AND Y VALUES
   setPixel(); TAKES THE CORRESPONDING PIXEL AND LIGHTS THAT ONE UP AS RED AND THE REST AS BLUE
  */
  
  //top right
  if(x>0 && y>0 && x==y)setPixel(4); 
  else if(x>0 && y>0 && x<y)setPixel(5); 
  else if(x>0 && y>0 && x>y)setPixel(3); 
  
  //top left
  else if(x<0 && y>0 && -x==y)setPixel(8);
  else if(x<0 && y>0 && -x<y)setPixel(7);
  else if(x<0 && y>0 && -x>y)setPixel(9); 
  
  //bottom right
  else if(x>0 && y<0 && x==-y)setPixel(0);
  else if(x>0 && y<0 && x<-y)setPixel(15);
  else if(x>0 && y<0 && x>-y)setPixel(1); 
  
  //bottom left
  else if(x<0 && y<0 && -x==-y)setPixel(12);
  else if(x<0 && y<0 && -x<-y)setPixel(13); 
  else if(x<0 && y<0 && -x>-y)setPixel(11); 
  
  //top middle
  else if(x==0 && y>0)setPixel(6);
  
  //left middle
  else if(y==0 && x<0)setPixel(10);
  
  //right middle
  else if(y==0 && x>0)setPixel(2);
  
  //bottom middle
  else if(x==0 && y<0)setPixel(14);
  
  //no tilt, light everything up green
  else{
    int i=0;
    for(i=0;i<16;i++){
      strip.setPixelColor(i, 0, brightness, 0);
    } 
    delay(200);
  }
  
  strip.show(); //light up the neo-pixel!
  delay(30); //wait before displaying the next configuration 
}

You may have noticed setPixel(); This is another function I wrote. Its job is to set one of the ring's LEDs to red and the rest blue.

void setPixel(int pixel){
  strip.setPixelColor(pixel, brightness, 0, 0); //set the passed in pixel to be red

  //then fill in the rest of the ring with blue
  int i=0;
  for(i=0;i<16;i++){
    if(i!=pixel)strip.setPixelColor(i, 0, 0, brightness);
  }
}

Lastly we want to take all of the information we have gathered, and actually move the servos. We first read in the current position of each servo (somewhere between 0 and 180). We will move each servo depending on the direction and magnitude of the tilt (which we found in getAccelValues()). In other words, if you tilt the controller really far to the left, the x servo will quickly move the left. Tilt the controller slightly upwards and the servo will slowly pan upwards. We also want to make sure we don't ask the servos to move more than they can. The if statement before each .write() command is used to keep the system from trying to move less than 0 or more than 180.

void writeServos(){
  int xServoCurrent = xServo.read(); //read in the current position of the x servo
  if(((xServoCurrent + servoX) > 5) && ((xServoCurrent + servoX) < 175) && servoX) xServo.write(xServoCurrent + servoX); 
  //^as long as the value won't be out of the range of the servo, move the servo according to the tilt of the controller

  int yServoCurrent = yServo.read(); //read in the current position of the y servo
  if(((yServoCurrent + servoY) > 90) && ((yServoCurrent + servoY) < 175) && servoY) yServo.write(yServoCurrent + servoY);
  //^as long as the value won't be out of the range of the servo, move the servo according to the tilt of the controller
}

That's it! The full sketch is included in this step. Its a lot of information to take in but its best to look at it one function at a time. If you are looking for a challenge, try writing a more efficient program to get the same result! You could vastly improve the program if you found an algorithm to light up the ring!

Step 6: Final Integration and Testing

We now have the controller and the servo setup. Lets put it all together. Start by connecting the wall wart to the breadboard's positive and negative rails using the 2.1mm female barrel jack. Connect the Arduino to the breadboard, with one side going to either rows a-e, and the other side going to either f-j. Tape the laser pointer to the y servo. Then make the following connections:

Motion Controller:

  • Power from motion controller -> 5V on Arduino
  • GND from motion controller -> GND rail
  • IN from motion controller -> A2 on Arduino
  • Button leads from motion controller-> one in 5V rail, the other to the Laser's + (red) wire.
  • Vcc from accelerometer -> 3V3 on Arduino
  • GND from accelerometer -> GND rail
  • XOUT from accelerometer -> A4 on Arduino
  • YOUT from accelerometer -> A3 on Arduino

Arduino:

  • Positive rail -> Vin on Arduino
  • GND rail -> GND on Arduino

Regulator:

  • IN pin on regulator -> Positive Rail
  • GND pin on regulator -> GND rail

Servos:

  • Signal wire on x servo -> D9 on Arduino
  • Signal wire on y servo -> D8 on Arduino
  • GND wire on x and y servo -> GND rail
  • Positive (red) wire on x and y servo -> OUT pin on regulator

Almost done! Lastly, add decoupling capacitors (around 4x 22uF) to the voltage rails on the breadboard.

Step 7: Laser Painting and Future Improvments

You did it, you now have a motion controlled laser painter! You could paint a glow in the dark wall, cover your bike in glow in the dark vinyl and paint it with laser light, or drive your pets and family crazy with the motion-controlled laser pointer! Besides being an endless amount of fun to play with, its a good exploration of accelerometers, neo-pixel rings, servos, and control systems. Try changing various parts of the code and see how it changes the project, but most importantly, remember to enjoy making a cool project!