LASER chaser: a little Arduino project
Other projects

Long ago, when we walked ten miles to school each day and made our own water, you needed lots of equipment to do any home microprocessor projects. That included things like an EPROM burner and a PCB etching tank. Now those were projects. The world has changed since we carved circuit boards out of stone and refined our own copper ore, so I thought I'd see what all this Arduino talk was all about.
 

What

Oh My Lord if we don't stop making every Computer Science student build line-following robots the world will be sick with them. (Sick with robots I mean.) This robot chases a LASER light. Here's a video:

• From reset until 6 seconds it's calibrating, meaning it's recording the highest and lowest values picked up by any of its 4 sensors. Those are reference values it will use later.

• Then it chases the light. You will notice that the light has to be right in front of it. It can't see far away, and I'll go more into that problem later on.

• At about 14 seconds it resets itself. That's a battery problem.

• From 22 seconds on it's chasing smoke, or rather it's moved far enough out of its calibration range that it's chasing reflected light from the floor.

Here are some photos. It's looking pretty barbaric, but this project is about the Arduino, not about making a glam carriage. Right click (or whatever) to see them full size.







 
 
 

A description of the problem

Problems:
  • Differentially detect the red LASER light and move towards it.
  • Don't get bogged down by little things like hitting a wall.
  • Don't go into the bright light.

    Not problems:
  • Survive any other environment besides a normal house floor.
  • Make someone impressed with energy conservation.
  • Avoid cliffs.
  • Actually have a criterion for success.

    Some things will be needed:
  • chassis and motors needed
  • light sensors needed
  • collision sensors needed
  • power is needed
  • software is needed

    motors needed

    It needs to be able to move. I chose to use servos at random (they were in a box nearby already). Here's a photo to show how they are placed in the chassis. The "chassis" is an ex-box for screws.

    A fragment of the datasheet,
    of a now discontinued servo.
    And the wheels.


    light sensors needed

    In order to chase the LASER light it needs a way to detect the red LASER light differentially with respect to bright room-normal ambient light. I gave it two types of sensors, photocells with a red filter and photocells with a blue filter.

    The 650nm bandpass filters are 6mm x 6mm square. I glued them onto the photoresistors. I don't have specifications on the blue filters because I just cut them from the front panel of an old DVD player.

    The blue "eyes" filter out the red and respond otherwise to the ambient light (unless the ambient light is red). When the blue eyes detect brightness, the robot backs away. It's like the blue pill ... it's trying to go back to the way things were ... living in the dark unknown of artificial purpose.

    The red "eyes" are like the red pill ... and one could, if one were so inclined, make this robot follow the red light down into a rabbit hole.


    collision sensors needed

    It needs to know when it has hit something. For collision detectors I bought SPST switches with levers from Radio Shack.


    power is needed

    A 9v batter was placed inside the chassis. Five minutes of chasing with these servos drain the battery to the point where the Arduino board starts resetting itself. Five minutes was sufficient for my purpose. I mean, really, how long can you tease a robot until self-reflection causes you to realize that you and the robot have swapped places of controller and the controlled?


    software is needed

    That comes right after the construction.
     
     
     

    Circuit Construction

    Here is a circuit diagram:
    SWL : Left side bumper/switch
    SWR : Right side bumper/switch

    RLA : Resistor, Left Ambient
    RRA : Resistor, Right Ambient

    RLE : Resistor, Left Eye
    RRE : Resistor, Right Eye

    A2,A3,A4,A5 : Arduino analog pins 2 - 5
    D2,D3 : Arduino digital pins 2 and 3

     
     
     

    The code: gathering sensor data

    I gathered some data from the system. On the right is the Arduino code I used to get the data. On the left is the data as captured, and below that is a summary. What it comes down to is that the sensors have a very narrow range. Barely 2x in the best case. You can move around the room and cast a shadow or remove a shadow and the robot responds to it. So some fiddling with the thresholds was needed.



    With a DVM I measured the photoresistor resistance in three conditions and got the following:
    96KΩ with the room very dark.
    700Ω with the room very bright.
    1700Ω with the room normal, with the LASER light reflecting off a manila colored folder 1" away.

    From the circuit diagram, you can see it's a voltage divider with the photoresistors and a 10K fixed resistor.
    So that's 5v x 10K/(10K + RRxE) and that should provide a range of 0.47v to 4.27v:
    5v x 10K/(10K + 96K) = 0.47 v
    5v x 10K/(10K + 1.7K) = 4.27v
    So it's almost a factor of 10 from dark to bright.


    const int RightAmbPin = A4;
    const int Left_AmbPin = A3;
    const int RightEyePin = A5;
    const int Left_EyePin = A2;
    int RightAmbVal = 0;
    int Left_AmbVal = 0;
    int RightEyeVal = 0;
    int Left_EyeVal = 0;
    
    void setup() { 
      Serial.begin(9600);
      Serial.print("Left_AmbVal");
      Serial.print("   ");
      Serial.print("Left_EyeVal");
      Serial.print(" ::  ");
      Serial.print("RightAmbVal");
      Serial.print("   ");
      Serial.println("RightEyeVal");
    } 
    void loop() { 
      RightEyeVal  = analogRead(RightEyePin);
      Left_EyeVal  = analogRead(Left_EyePin);
      RightAmbVal  = analogRead(RightAmbPin);
      Left_AmbVal  = analogRead(Left_AmbPin);
      Serial.print(Left_AmbVal);
      Serial.print("   ");
      Serial.print(Left_EyeVal);
      Serial.print(" ::  ");
      Serial.print(RightAmbVal);
      Serial.print("   ");
      delay (500) ;
    }
    
    analogtestSerial.ino
    But that's not what I measure in the real system. Because I'm not going to try to do this in darkness, or to film in the dark, it's the difference between normal lighting and the normal lighting with the LASER light that matters. And from the plot above, that's only about 1.4x:
    Right:Dark ―10x➝ Normal room light ―1.35x➝ LASER light close
    Left:  Dark ―4x➝ Normal room light ―1.4x➝ LASER light close

     
     
     

    The code: robot control

    In designing a control algorithm I considered how I think the robot should behave. This is not a thermostat or a factory or an autopilot. Essentially, the robot is meant to mimic spastic LASER light chasing behaviour of a cat. The robot should jump towards the LASER light. Sometimes it should move in proportion to the intensity of the light in an attentive or creeping manner, as the intensity is an analog of proximity. Perhaps, at times, there should be some momentum to its attention. I did some empirical testing and ended up with three main turns of code. These are:
    1. BB: Bang-bang control
    2. P: Proportional control
    3. DPPMD: Decaying Proportional Poor-Man's Derivative control

    1. The Bang-bang control is simply that if the light is above the threshold, go at it. If not, stop.

    2. The Proportional control is simply a y=mx + b, where the slope is based on the servo drive value range (0-90 in a given direction of rotation) divided by the range of input values scaled by the low and high values from calibration.
    That's (90 - 0)/(detected_peak_value - measured_high_reference_value). The input value is massaged a bit on the way in and in some cases it's the average value from an array of N previous input values.

    3. The Decaying Proportional Poor-Man's Derivative, which I just made up of course, works by keeping a rolling array of the previous input values. The proportional drive is based on the average value in the array, so it decays when the input value decays. But the Poor-Man's Derivative comes in when the input is sensed to be above the current average value. Then, all array elements are immediately replaced with the new, higher, input value. This way it responds quickly to increases of light intensity.

    In all these cases there is one other mechanism at work, and that is that if there is no significant above-threshold light detected on one side, but there is on the other, then reverse drive the wheel on the intense side. That gives it more of a jumpy/startled style behaviour.
    The relevant code sections are shown below for all three control mechanisms (only the right side control shown):
    void loop() 
    { 
      calibrating to find reference and peak values.
      ...
      calibration done
      ...
      // get the state of the outside world:
      RightEyeVal  = analogRead(RightEyePin);
      ...
         if (firsttimethrough == 1) 
         {
           // Proportional is a y = mx +b kind of thing:
           mR = float(90/(float(RightEyePeak) - float(RE_Ref))) ;
           bR = mR*RE_Ref ;
         }
    
    
    #ifdef USE_DERIVATIVE
        reloadRight = (RightEyeVal > RightEyeComputedVal) ? 1 : 0 ; 
    #else
        reloadRight = 0 ; 
    #endif
        RightEyeComputedVal = 0 ;
    
    
    #ifdef USE_ROLLINGAVERAGE
        for (i=0 ; i < (ArrayLength-1); i++) 
        {
          RightEyeComputedVal = RightEyeComputedVal + RightEyeArray[i] ;
          RightEyeArray[i]   = (reloadRight) ? RightEyeVal : RightEyeArray[i+1] ; 
        }
        RightEyeArray[ArrayLength-1] = RightEyeVal ;
        RightEyeComputedVal = float ((RightEyeComputedVal + RightEyeVal)/ArrayLength) ;
    #else   
        RightEyeComputedVal = RightEyeVal;
    #endif
    
    
          Left_Speed = int( (RightEyeComputedVal*mR) - bR) ;
    
          
    #ifdef USE_BANG_BANG
          Rthresh = RE_Ref + 10 ;
          Lthresh = LE_Ref + 10 ;
          // Bang-bang control. Just go when the light is a wee bit above threshold.
          if ((RightEyeComputedVal > Rthresh) 
               && (Left_EyeComputedVal > Lthresh)){ Left_Speed =  90 ; }
          else if (RightEyeComputedVal > Rthresh) { Left_Speed =  90 ; }
          else if (Left_EyeComputedVal > Lthresh) { Left_Speed = -90 ; }
          else                                    { Left_Speed =   0 ; }
    #endif
    
    
          // Finally convert the values to L and R servo sense.
          // Lspeed above 90 is forward motion.
          Left_Speed = 90 + Left_Speed ;
          Left_Servo.write(Left_Speed) ;
    ...
    }
    


    All the code is here, ifdef'd to produce any of the three behaviours: laserchaser6.ino.

    notes on the calculations

    There is a potential problem with the Arduino integer range of +/- 32K when using the arrays RightEyeArray and Left_EyeArray. The incoming values are 1K maximum, so an array length of 33 may cause rollover as the item are summed up. With a minimum loop delay of 20msec, an array size of 33 means averaging over 0.66 seconds only, so it might be easy to exceed the 32K range. Just something to consider.

    As much as I'd like it to not be so, arbitrary decisions had to be made about an operating range. Based on a few runs of data collection, I decided on a range for the reference values, both min and peak. Unlike the video at the top of this page, the robot now has a two phase calibration process. It spins around for a second to sense the min and max values of light without the LASER light, then waits for another second "expecting" to sense min and max value of the LASER light. The values I chose for operating corners are:
    const int LE_RefDesignMin  =  100;
    const int LE_RefDesignMax  =  250;
    const int LE_PeakDesignMin =  300;
    const int LE_PeakDesignMax = 1000;
    
    const int RE_RefDesignMin  =  100;
    const int RE_RefDesignMax  =  320;
    const int RE_PeakDesignMin =  370;
    const int RE_PeakDesignMax = 1000;
    

    Given the limits set for Ref min and max, and Peak min and max, this gives a operational ranges as shown below. The thick lines are typical operating curves. To make certain the robot's calculations do not go awry the code forces all input values below the xE_RefDesignMin to xE_RefDesignMin, all values above xE_PeakDesignMax to xE_PeakDesignMax, all output values over 90 to 90, and all output values below zero to zero.
    I ran test cases with a set of input values at points near the operating corners to verify the operation of the algorithm. Those were:
    Left_RangeTest[] = (0, 99, 100, 101, 200, 319, 320, 321, 369, 370, 371, 600, 999, 1000, 1001, 1023) ;
    RightRangeTest[] = (0, 99, 100, 101, 200, 249, 250, 251, 299, 300, 301, 600, 999, 1000, 1001, 1023) ;
    and the calculated values agree with calculations, with one exception: the output is below the line while the array is first filling up to an average value.



    There are two sad cases for proportional control. The first is when the xE_REF is at xE_RefDesignMin when the xE_Peak is at xE_PeakDesignMax. It's proportional, but the slope is so low that it behaves lethargically unless the LASER light in right near it.

    The second is the reverse case, when the xE_REF is at xE_RefDesignMax when the xE_Peak is at xE_PeakDesignMin. This makes the steepest possible slope and there it behaves mostly like the bang-bang control. The slope is so steep that might it be possible that an input value of 1023 drives the output to exceed INT_MAX. I checked that, and an input range of 0 to 1023 gives a calculated output range (before clipping to 0 to 90) of -144 to 4000. That's well within the +/- 32K range.
     
     
     

    Results

    Daft thing.

    Bang-bang control.
    Decaying proportional control, also showing its back-away behaviour when encountering a bright light.
    Decaying Proportional and Poor-Man's Derivative control.

     
     
     

    Discussion and possibilities

    Again, this project was about the Arduino, not about following the LASER light. There is well established prior art in this domain, and devices for chasing LASER lights are readily available. I already had one such device (image below):

    Even though I'm not planning to take this further, there are some things worth noting about the sensors and control. Given the small range of values from the sensors, I'd opt for a better sensor. If only there was a cheap red LASER light movement detector available ... how about the sensor from an optical mouse? It looks like it would work if one were up to the optics challenge. The video below show a test where I moved the LASER spot back and forth beneath a mouse. If you look at the cursor on the laptop screen (near bottom center) you can see the cursor move due to the LASER light moving.


    The three parts of normal control systems are Proportional, Integral, and Derivative. In order to determine what control system is required we need to consider the desired behaviour.

    If we want to avoid a situation where the robot creeps towards whatever light is available, even if it's only a dim light, then a strong integral control component will have to be avoided.

    Proportional control is good when one wants proportional behaviour. But if we want the robot to act like a sit-and-wait predator, then the quick response of bang-bang or a modified derivative control will be required. If you've ever seen a cat do this you'll know that their reactions are all out of proportion, so maybe proportional is not needed.

    Proportional control is of use when the set-point is stable(ish). The problem here is that even though the LASER light may look to us as though it's "on the right side" when it is moving around on the right side of the robot, the robot's sensed value can be changing quite a lot. Averaging, as I did with the array, increases the effective area of the sensor.

    The main bit of fussiness I found with this whole thing was having to calibrate and then NOT change the lighting in the room. The robot can't autocalibrate since it has no detectors for either ThereIsNoLASERlightPresent nor NowThereIsAmaximumLASERvalueToRecord. Without keeping the room light constant, I've found that sometimes the robot would back up when a shadow fell on it, or it start spinning or chasing after the floor, like when the room gets brighter but not bright enough for the ambient light sensors to stop it.

    The problem is one of determining if the "eyes" were picking up something it should follow or not. What this robot needs is a camera. With a camera one could isolate the red LASER differentially with respect to a larger background, and probably with a far greater dynamic range.
     
     
     

    Parts list

    Arduino Duemilanove A gift
    Adafruit motor shield (RD-Ada-02) Robotshop
    2x S35 GWS STD
    continuous rotation servo motor
    (RB-GWS-23) Robotshop
    Lynxmotion servo wheel
    2.63" x 0.35"
    (RB-Lyn-27) Robotshop
    4x Photocell GL5528 Can't recall
    2x SPST switch Radio Shack
    650nm filter An OPTOLONG product, probably from Alibaba.com
    blue light filter front panel of an old DVD player
    battery connector the box on the left on the floor
    wires and resistors and
    perfboard and stuff
    the other box on the floor, to the left of the first box