Solar tracker on an Arduino Uno
Four light sensors steer a pan and tilt servo pair, an INA219 measures the panel, and the sketch alternates tracked and fixed windows, printing only measured averages. The whole build, an Arduino Uno and 18 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Advanced Runs in your browser. Free, and no account needed.
The parts retail for about $43. On Mokxi they're free.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Solar tracker: aims a panel at the light, and compares tracked with fixed.
//
// It runs an honest experiment: twenty seconds tracking, twenty seconds
// parked facing south at 45 degrees, again and again, and adds up the energy
// the INA219 measures in each mode. It prints measured numbers only.
//
// Four light sensors sit in a cross around the panel: top left, top right,
// bottom left and bottom right. When the left pair sees more light than the
// right pair, the panel is pointing too far right, so the pan servo turns it
// left, and the same goes for top and bottom with the tilt servo. When every
// sensor sees the same light, the panel is facing the sun.
//
// In Mokxi, the light is yours to set: drag each sensor's slider and watch
// the servos chase it. The light does not move as the servos turn (the
// simulator does not model where the sun is), so a tracker here keeps
// turning until it reaches its limit or you even the light out.
//
// Wiring (Arduino Uno):
// light sensors (LDR over 10 k to ground) TL A0, TR A1, BL A2, BR A3
// pan servo D9, tilt servo D10
// INA219 on I2C (A4 SDA, A5 SCL): panel + to VIN+, VIN- to the 36 ohm load
// lamps: tracking D7, fixed D6; restart the comparison with the button on D2
#include <Servo.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
const uint8_t TL = A0, TR = A1, BL = A2, BR = A3;
const int DEADBAND = 20; // ignore differences smaller than this
const unsigned long STEP_MS = 40;
Servo pan;
Servo tilt;
int panAngle = 90;
int tiltAngle = 45;
unsigned long stepAt = 0;
unsigned long printAt = 0;
Adafruit_INA219 ina;
float volts = 0, milliamps = 0, milliwatts = 0;
// The comparison: twenty seconds tracking, twenty seconds parked, again and
// again. Only measured numbers are printed: the energy the INA219 saw in each
// mode, and the time spent in it.
const uint8_t TRACK_LED = 7;
const uint8_t FIXED_LED = 6;
const uint8_t RESET_BUTTON = 2;
const unsigned long WINDOW_MS = 20000;
const int PARK_PAN = 90, PARK_TILT = 45; // facing south at 45 degrees
bool tracking = true;
unsigned long windowAt = 0;
unsigned long sampleAt = 0;
float energy[2]; // milliwatt-hours: [0] tracked, [1] fixed
float seconds[2];
float windowEnergy = 0;
bool buttonDown = false;
void stepTracker() {
int tl = analogRead(TL), tr = analogRead(TR), bl = analogRead(BL), br = analogRead(BR);
int left = (tl + bl) / 2, right = (tr + br) / 2;
int top = (tl + tr) / 2, bottom = (bl + br) / 2;
// One degree a step is slow enough to be smooth and quick enough to keep
// up with the real sun, which moves a quarter of a degree a minute.
if (left - right > DEADBAND) panAngle--;
if (right - left > DEADBAND) panAngle++;
if (top - bottom > DEADBAND) tiltAngle++;
if (bottom - top > DEADBAND) tiltAngle--;
panAngle = constrain(panAngle, 0, 180);
tiltAngle = constrain(tiltAngle, 10, 170);
}
void readPanel() {
volts = ina.getBusVoltage_V() + ina.getShuntVoltage_mV() / 1000.0;
milliamps = ina.getCurrent_mA();
milliwatts = ina.getPower_mW();
}
void report() {
Serial.print(tracking ? "TRACK" : "FIXED");
Serial.print(" ");
Serial.print("pan ");
Serial.print(panAngle);
Serial.print(" tilt ");
Serial.print(tiltAngle);
Serial.print(" panel ");
Serial.print(volts, 2);
Serial.print(" V ");
Serial.print(milliamps, 1);
Serial.print(" mA ");
Serial.print(milliwatts, 0);
Serial.print(" mW");
Serial.println();
}
void printAverage(uint8_t mode) {
Serial.print(mode == 0 ? "tracked " : "fixed ");
if (seconds[mode] < 1) {
Serial.print("not measured yet");
return;
}
Serial.print("average ");
Serial.print(energy[mode] * 3600.0 / seconds[mode], 1);
Serial.print(" mW over ");
Serial.print(seconds[mode], 0);
Serial.print(" s");
}
void endWindow() {
Serial.print("== ");
Serial.print(tracking ? "tracked" : "fixed");
Serial.print(" window: ");
Serial.print(windowEnergy, 3);
Serial.println(" mWh measured");
Serial.print("== so far: ");
printAverage(0);
Serial.print(", ");
printAverage(1);
Serial.println();
windowEnergy = 0;
tracking = !tracking;
windowAt = millis();
digitalWrite(TRACK_LED, tracking ? HIGH : LOW);
digitalWrite(FIXED_LED, tracking ? LOW : HIGH);
Serial.println(tracking ? "-- tracking for 20 s" : "-- parked at pan 90, tilt 45 for 20 s");
}
void sample() {
// Energy is power times time: add up each tenth of a second's worth.
unsigned long now = millis();
float hours = (now - sampleAt) / 3600000.0;
sampleAt = now;
uint8_t mode = tracking ? 0 : 1;
energy[mode] += milliwatts * hours;
seconds[mode] += hours * 3600.0;
windowEnergy += milliwatts * hours;
}
void readButton() {
bool down = digitalRead(RESET_BUTTON) == LOW;
if (down && !buttonDown) {
energy[0] = energy[1] = seconds[0] = seconds[1] = 0;
windowEnergy = 0;
tracking = true;
windowAt = millis();
digitalWrite(TRACK_LED, HIGH);
digitalWrite(FIXED_LED, LOW);
Serial.println("-- comparison restarted: tracking for 20 s");
}
buttonDown = down;
}
void setup() {
Serial.begin(115200);
pan.attach(9);
tilt.attach(10);
pan.write(panAngle);
tilt.write(tiltAngle);
ina.begin();
pinMode(TRACK_LED, OUTPUT);
pinMode(FIXED_LED, OUTPUT);
pinMode(RESET_BUTTON, INPUT_PULLUP);
digitalWrite(TRACK_LED, HIGH);
sampleAt = windowAt = millis();
Serial.println("Solar tracker ready: tracking for 20 s, then parked for 20 s.");
}
void loop() {
unsigned long now = millis();
readButton();
if (now - windowAt >= WINDOW_MS) endWindow();
if (now - stepAt >= STEP_MS) {
stepAt = now;
if (tracking) {
stepTracker();
} else {
panAngle = PARK_PAN;
tiltAngle = PARK_TILT;
}
pan.write(panAngle);
tilt.write(tiltAngle);
readPanel();
sample();
}
if (now - printAt >= 1000) {
printAt = now;
report();
}
}
Parts list
20 parts, plus the jumper wires. They retail for about $43; on Mokxi they're free.
- 1 × Arduino Uno R3
- 1 × Full-size breadboard
- 4 × Light sensor
- 4 × Resistor, 10k Ω
- 2 × Hobby servo
- 1 × Current monitor, INA219
- 1 × Solar panel, 6 V 1 W
- 1 × Resistor, 36 Ω
- 1 × LED, green
- 2 × Resistor, 220 Ω
- 1 × LED, yellow
- 1 × Pushbutton
How it is wired
17 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Arduino Uno R3 pin SCL; Current monitor, INA219 pin SCL
- Arduino Uno R3 pin SDA; Current monitor, INA219 pin SDA
- Arduino Uno R3 pin 10; Hobby servo (2) pin PWM
- Arduino Uno R3 pin 9; Hobby servo (1) pin PWM
- Arduino Uno R3 pin 7; LED, green pin A
- Arduino Uno R3 pin 6; LED, yellow pin A
- Arduino Uno R3 pin 2; Pushbutton pin 1a
- Arduino Uno R3 pin 5V; Light sensor (1) pin 1; Light sensor (2) pin 1; Light sensor (3) pin 1; Light sensor (4) pin 1; Hobby servo (1) pin VCC; Hobby servo (2) pin VCC; Current monitor, INA219 pin VCC
- Ground: Arduino Uno R3 pin GND; Resistor, 10k Ω (1) pin 2; Resistor, 10k Ω (2) pin 2; Resistor, 10k Ω (3) pin 2; Resistor, 10k Ω (4) pin 2; Hobby servo (1) pin GND; Hobby servo (2) pin GND; Current monitor, INA219 pin GND; Solar panel, 6 V 1 W pin -; Resistor, 36 Ω pin 2; Resistor, 220 Ω (1) pin 1; Resistor, 220 Ω (2) pin 1; Pushbutton pin 2a
- Arduino Uno R3 pin A0; Light sensor (1) pin 2; Resistor, 10k Ω (1) pin 1
- Arduino Uno R3 pin A1; Light sensor (2) pin 2; Resistor, 10k Ω (2) pin 1
- Arduino Uno R3 pin A2; Light sensor (3) pin 2; Resistor, 10k Ω (3) pin 1
- Arduino Uno R3 pin A3; Light sensor (4) pin 2; Resistor, 10k Ω (4) pin 1
- Current monitor, INA219 pin VIN-; Resistor, 36 Ω pin 1
- Current monitor, INA219 pin VIN+; Solar panel, 6 V 1 W pin +
- LED, green pin C; Resistor, 220 Ω (1) pin 2
- LED, yellow pin C; Resistor, 220 Ω (2) pin 2
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.