Introduction: Tracking a Light Source With an Actuator

About: geek nerd ubergeek

...or maybe it should be called "Using an old solar cell from a completely corroded garden light as a light sensor". :)

In this instructable, I'll build on my previous and show you how you can make a actuator find the greatest light source. Really though, this really just a matter of finding the highest analog input and could be used to find... the loudest source of sound, the highest temperature, direction of electromagnetic field... ? I can't wait to see what other clever applications people will come up with!

Step 1: Setup Arduino and Actuator With Limit Detection

There's an Instructable for that!

You'll want to make sure you test it all out.... Don't worry, I'll wait....

Step 2: Buy More Stuff!

In the previous example, I assumed everyone had jumper wires and a quick prototyping board. If not, pick yourself up a couple prototyping boards and batch of jumper wires with pins presoldered. Note about jumper wires - I try to always use red for vcc and black for ground - this means you'll use more of those two colors. If you don't have an Arduino yet (you clearly didn't do the last step did you), or are in the market for another, here is a really good deal from Adafruit: Includes a proto board, wires and, if you don't have a an old solar cell to salvage, includes a photocell and 10K resistor.

For this Instructable, in addition to the materials in the previous Instructable, you will need:

  • 2 pieces of wire about the throw distance of your actuator plus a foot.

And either:

OR, my choice since I'm working on a project that will eventually use a bunch of these:

  • An old solar cell from a dysfunctional garden lamp

Step 3: Choose Your Light Sensor

The first picture above shows the wiring of the photocell sensor with 10K resistor to ground. The photocell is passive, meaning that it doesn't supply it's own current. It simply changes resistance such that in full light it provides no resistance and you should read 1024 off of A4. My testing in the sun with this setup showed that the photocell could maybe use some more resistance. It shot up to 1024 long before the cell was directly facing the sun, but it will work find with the limited amount of light produced by a desklamp.

The second picture above shows a solar cell connected ground-to-ground and positive to A4. Since the solar cell generates it's own current which the Arduino ADC can handle, you can plug it directly in. The one I'm showing here is a 2.2V cell. Measure the output in direct sun with a multimeter (VDC). Any cell under 5V will work here.

Use your two long pieces of wire and attach the photocell or solar cell to the end of the actuator. Be sure to insulate the leads of the photocell or the back of the solar cell. I covered the back of the solar cell with electrical tape.

Step 4: Test Your Light Sensor

Take a quick look over your connections and then plug your Arduino into your computer using the USB cable. Upload the sketch below to your Arduino and open the serial console (Tools / Serial Monitor in the Arduino IDE).

With the sketch below running, you should see a measurable difference when passing a light over your sensor and the reading should increase as the light source gets closer to the sensor.

analogRead - reads an analog pin N times per second over N period and writes the min and max to the console */ #define ANALOG_PIN A4 #define SAMPLE_INTERVAL 10 // every 10ms #define SAMPLE_PERIOD 1000 // 1 sec // the setup routine runs once when you press reset: void setup() { // initialize serial communication at 9600 bits per second: Serial.begin(9600); pinMode(ANALOG_PIN, INPUT); } // the loop routine runs over and over again forever: void loop() { int min = 0xff; int max = 0; for (int i = 0; i < SAMPLE_PERIOD / SAMPLE_INTERVAL; i ++) { int sensorValue = analogRead(ANALOG_PIN); if (min == 0xff || sensorValue < min) min = sensorValue; if (sensorValue > max) max = sensorValue; delay(SAMPLE_INTERVAL); } char out[255]; sprintf(out, "min=%d max=%d", min, max); Serial.println(out); }

Step 5: Follow That Desklamp!

Ok, so maybe there is a better application than tracking a desklamp... :)

If you upload the code that follows to the Arduino, you should be able reproduce the results in the video.

This script moves an actuator until maximum analog input is acheived. While it's example application is to track a light source with a solar cell that is attached to the end of the actuator, it could be used to track maximum input of anything attached to the LIGHT_SENSOR_PIN. maybe like sound source tracking too? :) */ #define ACTUATOR_AMP_SENSOR_PIN A5 #define ACTUATOR_PWM_PIN 11 #define ACTUATOR_DIR_PIN 12 #define LIGHT_SENSOR_PIN A4 // MAX speed is 255 - this is the value that gets written // to the ACTUATOR_PWM_PIN. Note do not change or limit // detection will no work #define ACTUATOR_SPEED 255 // when the amp sensor analog value is between these, it is // considered to be at rest (no current being drawn). #define ZEROAMP_RANGE_LOW 505 #define ZEROAMP_RANGE_HIGH 520 // directions for the actuator (value gets set to ACTUATOR_DIR_PIN) #define RETRACT 0 #define EXTEND 1 // don't move if sampled light input value is within // STILL_TOLERANCE of g_lastInputValue #define STILL_TOLERANCE 2 // move actuator until drop from max is less than #define SEEK_TOLERANCE 2 // minimum number of milliseconds delay between sampling the ACS712 // recommended in manufacturer sample source #define SAMPLING_DELAY 1 int g_currentDirection = EXTEND; int g_lastInputValue = 0; // limited to 255 char strings void serialPrintf(char const *format, ...) { char buffer[255]; va_list args; va_start(args, format); vsprintf(buffer, format, args); va_end(args); Serial.println(buffer); } boolean isActuatorMoving() { int val = analogRead(ACTUATOR_AMP_SENSOR_PIN); // serialPrintf("Amp sensor reading: %d", val); return val < ZEROAMP_RANGE_LOW || val > ZEROAMP_RANGE_HIGH; } void stopActuator() { analogWrite(ACTUATOR_PWM_PIN, 0); } boolean almostEqual(int a, int b, int tolerance) { return a >= b - tolerance && a <= b + tolerance; } // mSeconds = optionally move in direction for n milliseconds and then stop // returns false if mSeconds != 0 and at end of travel boolean moveActuator(int direction, int mSeconds=0) { serialPrintf("moving actuator direction=%d", direction); analogWrite(ACTUATOR_PWM_PIN, ACTUATOR_SPEED); digitalWrite(ACTUATOR_DIR_PIN, direction); if(mSeconds > 0){ delay(mSeconds); boolean stillMoving = isActuatorMoving(); stopActuator(); return stillMoving; } return true; } // returns the number of milliseconds taken unsigned long moveActuatorToEnd(int direction) { long startMs = millis(); moveActuator(direction); while (isActuatorMoving()){ delay(SAMPLING_DELAY); // slight delay between sampling the ACS712 module is recommended } stopActuator(); long endMs = millis(); return endMs - startMs; } void changeDirection(int direction = -1) { if (direction == -1) direction = g_currentDirection + 1; // mask to single bit - 0 or 1, so 0 + 1 becomes 1 and 1 + 1 becomes 0 g_currentDirection = direction & 1; } // finds the max input by moving through the full range of motion void calibrate() { moveActuatorToEnd(EXTEND); // start fully extended long tMaxInput = 0; // time elapsed while retracting that max input was achieved int maxInput = 0; // what was the input long startMs = millis(); moveActuator(RETRACT); while (isActuatorMoving()) { int inputValue = analogRead(LIGHT_SENSOR_PIN); if (inputValue > maxInput) { maxInput = inputValue; tMaxInput = millis() - startMs; delay(SAMPLING_DELAY); } } long endMs = millis(); // move back to point in time when at max moveActuator(EXTEND, endMs - startMs - tMaxInput); } // returns false if at end of travel boolean seekHighInput(int pin, int direction, int inputValue) { int nextInputValue = inputValue; int maxInputValue = inputValue; boolean stillMoving; boolean atMax; serialPrintf("seeking to high input in direction %d. inputValue=%d", direction, inputValue); moveActuator(direction); do { delay(SAMPLING_DELAY); stillMoving = isActuatorMoving(); if (nextInputValue > maxInputValue) maxInputValue = nextInputValue; nextInputValue = analogRead(pin); serialPrintf("stillMoving=%d nextInputValue=%d", stillMoving, nextInputValue); atMax = almostEqual(nextInputValue, maxInputValue, SEEK_TOLERANCE); } while (stillMoving && atMax); stopActuator(); return stillMoving; } void seekToLight() { boolean stillMovable; int inputValue = analogRead(LIGHT_SENSOR_PIN); int initialValue = inputValue; if (almostEqual(inputValue, g_lastInputValue, STILL_TOLERANCE)) { return; } stillMovable = seekHighInput(LIGHT_SENSOR_PIN, g_currentDirection, inputValue); inputValue = analogRead(LIGHT_SENSOR_PIN); boolean lessThanAtStart = inputValue < initialValue - STILL_TOLERANCE; if (!stillMovable || lessThanAtStart ) { changeDirection(); } // if we are receiving less input than at start of this call, changeDirection // and go back to max if (lessThanAtStart) { seekHighInput(LIGHT_SENSOR_PIN, g_currentDirection, inputValue); } g_lastInputValue = inputValue; } // the setup routine runs once when you press reset: void setup() { // initialize serial communication at 9600 bits per second: Serial.begin(9600); pinMode(ACTUATOR_PWM_PIN, OUTPUT); pinMode(ACTUATOR_DIR_PIN, OUTPUT); pinMode(ACTUATOR_AMP_SENSOR_PIN, INPUT); pinMode(LIGHT_SENSOR_PIN, INPUT); // we really don't need to calibrate anything, the seek algorithm should handle // variance in the ranges of inputs to the light sensor. // // the scan of the range of movement for highest input source that calibrate() // performs may come in handy in high ambient light environments where it's hard // to distinguish a singular source. The seek algorithm may need to be iterated // on to better support high ambient light environments // //calibrate(); serialPrintf("setup complete"); } // the loop routine runs over and over again forever: void loop() { seekToLight(); }

Step 6: The Big Picture

It may seem like it's a huge leap to go from following a desklamp to tilting a 50lb, 5'x2.5' petal, but it should actually be easier. If you've played with the desklamp tracking in a well lit room, you may have noticed that it can have a hard time finding a singular source of light when the ambient light is bright.

In the case of the solar sunflower, we will most likely "track" to the position in which the array is producing the most current as measured by..., that's right, another ACS712 current sensor!

If you are having trouble with copying and pasting the code samples, you can find all of them, plus all of the code for the bigger project on GitHub

The codes for this Instructable are in the arduino/scripts analogRead and trackSun folders.

As always, hope you find this helpful,


Yo,, the editing experience here could use some work. Pasting code is painful. Call me. I've done some pretty clever things with tinyMce and I can help you. :)