Plataforma Seguidora Solar de Precisión con Servos para Captura Óptima de Energía
Componentes y materiales
| Componente | Cantidad | Notas |
|---|---|---|
| Fotorresistencias (LDR) | 4 | |
| LED RGB NeoPixel (Adafruit Flora, pack 4) | 1 | O LED RGB multicolor equivalente |
| Pulsadores 12 mm (normalmente abiertos) | 2 | SparkFun o equivalentes |
| Sensor de temperatura | 1 | |
| Arduino UNO | 1 | |
| Servos 180° (RobotGeek o Futaba S3003) | 2 | Uno para giro (base), otro para inclinación |
| Resistencias 10 kΩ | 6 | |
| Resistencia 220 Ω | 1 | Para LED |
| Condensadores 100 µF | 2 | |
| Panel solar 80×80 mm | 1 | Amazon o similar |
Herramientas necesarias
- Herramientas manuales, tornillos y tuercas pequeñas para fabricar soportes
- Base sólida para montar servo de "giro" (bloque de madera)
- Chapa de aluminio doblable (≈3 mm, <2 cm ancho) para soporte en "L"
- Tira de acero delgada para soporte del panel y montaje en servo de giro (tipo sujeción conductos HVAC, con agujeros pretaladrados)
Aplicaciones y servicios
- Arduino IDE
- Microsoft Excel (documentación y notas)
Descripción del proyecto
Otra implementación usando dos servos de 180° y cuatro fotorresistencias para orientar un pequeño panel solar hacia la zona de mayor intensidad lumínica.
Los motores realizan pequeños movimientos para apuntar el panel al área más brillante.
Incluye un LED multicolor que indica si el sistema está habilitado. Dos pulsadores permiten activar/desactivar los motores.
Hay rutinas para mover los motores a posiciones comandadas vía puerto serie, usando códigos numéricos. Los movimientos independientes están programados para ser suaves, evitando golpes mecánicos por cambios bruscos de posición.
Código (Versión 2.0 - Q2)
// Scott Mangiacotti, Tucson, Arizona, Mayo 2018 - Q2 Version 2.0
#include <Servo.h>
const int GIVE_BACK_TIME = 125;
// Pines entrada
const int I_RED_BUTTON_PIN = 4;
const int I_GREEN_BUTTON_PIN = 2;
const int I_LDR1_PIN = A3; // sup-izq
const int I_LDR2_PIN = A2; // sup-der
const int I_LDR3_PIN = A1; // inf-izq
const int I_LDR4_PIN = A0; // inf-der
// Pines salida
const int O_RED_LED_PIN = 9;
const int O_GREEN_LED_PIN = 11;
const int O_BLUE_LED_PIN = 10;
const int O_TWIST_SERVO_PIN = 5;
const int O_TILT_SERVO_PIN = 6;
// Variables globales
bool gRunning = false;
bool gTrackToLightEnabled = false;
bool gVerboseDiagMode = false;
Servo gServoTwist;
int gServoTwistPositionCommand;
bool gServoTwistMoveIP;
Servo gServoTilt;
bool gServoTiltMoveIP;
int gServoTiltPositionCommand;
int gLDR1, gLDR2, gLDR3, gLDR4;
void setup() {
Serial.begin(9600);
pinMode(I_RED_BUTTON_PIN, INPUT);
pinMode(I_GREEN_BUTTON_PIN, INPUT);
pinMode(O_RED_LED_PIN, OUTPUT);
pinMode(O_GREEN_LED_PIN, OUTPUT);
pinMode(O_BLUE_LED_PIN, OUTPUT);
startup();
}
void loop() {
// Procesar comandos serie
if (Serial.available() > 0) {
int iControlCode = Serial.parseInt();
processSerialMessage(iControlCode);
}
// Botón verde: activar seguimiento
int iGreenButton = digitalRead(I_GREEN_BUTTON_PIN);
if (iGreenButton == HIGH && gRunning == false) {
enableTracking();
gTrackToLightEnabled = true;
}
// Botón rojo: desactivar
int iRedButton = digitalRead(I_RED_BUTTON_PIN);
if (iRedButton == HIGH && gRunning == true) {
disableTracking();
}
// Leer fotorresistencias
readPhotoResistors();
averageTopTwoSensors();
averageBottomTwoSensors();
// Ajustar servos según luz
if (gRunning == true) {
if (gTrackToLightEnabled == true) {
trackToLightSensors();
} else {
smoothMoveTwist();
smoothMoveTilt();
}
}
delay(GIVE_BACK_TIME);
}
void enableTracking() {
gRunning = true;
gServoTwist.attach(O_TWIST_SERVO_PIN);
gServoTilt.attach(O_TILT_SERVO_PIN);
digitalWrite(O_GREEN_LED_PIN, HIGH);
digitalWrite(O_RED_LED_PIN, LOW);
Serial.println("servos enabled");
}
void disableTracking() {
gRunning = false;
gTrackToLightEnabled = false;
gServoTwist.detach();
gServoTilt.detach();
gServoTwistPositionCommand = gServoTwist.read();
gServoTwistMoveIP = false;
gServoTiltPositionCommand = gServoTilt.read();
gServoTiltMoveIP = false;
digitalWrite(O_RED_LED_PIN, HIGH);
digitalWrite(O_GREEN_LED_PIN, LOW);
Serial.println("servos disabled");
}
void trackToLightSensors() {
float fTop, fBottom, fLeft, fRight;
int iTwistMoveCommand, iTiltMoveCommand;
int iMoveAmount = 5; // Grados por escaneo (combinado con GIVE_BACK_TIME define agresividad)
iTwistMoveCommand = gServoTwist.read();
iTiltMoveCommand = gServoTilt.read();
fTop = averageTopTwoSensors();
fBottom = averageBottomTwoSensors();
fLeft = averageLeftTwoSensors();
fRight = averageRightTwoSensors();
// Giro (twist)
if (fLeft > fRight) iTwistMoveCommand += iMoveAmount;
else if (fRight > fLeft) iTwistMoveCommand -= iMoveAmount;
// Inclinación (tilt)
if (fTop > fBottom) iTiltMoveCommand += iMoveAmount;
else if (fBottom > fTop) iTiltMoveCommand -= iMoveAmount;
// Límites
if (iTwistMoveCommand < 0) iTwistMoveCommand = 0;
if (iTwistMoveCommand > 179) iTwistMoveCommand = 179;
if (iTiltMoveCommand < 45) iTiltMoveCommand = 45;
if (iTiltMoveCommand > 135) iTiltMoveCommand = 135;
gServoTwist.write(iTwistMoveCommand);
gServoTilt.write(iTiltMoveCommand);
if (gVerboseDiagMode) {
Serial.println("tl,tr,bl,br,top,btm,left,right,twist,tilt:");
Serial.print(gLDR1); Serial.print(", "); Serial.print(gLDR2); Serial.print(", ");
Serial.print(gLDR3); Serial.print(", "); Serial.print(gLDR4); Serial.print(", ");
Serial.print(fTop); Serial.print(", "); Serial.print(fBottom); Serial.print(", ");
Serial.print(fLeft); Serial.print(", "); Serial.print(fRight); Serial.print(", ");
Serial.print(iTwistMoveCommand); Serial.print(", "); Serial.println(iTiltMoveCommand);
}
}
void readPhotoResistors() {
gLDR1 = analogRead(I_LDR1_PIN);
gLDR2 = analogRead(I_LDR2_PIN);
gLDR3 = analogRead(I_LDR3_PIN);
gLDR4 = analogRead(I_LDR4_PIN);
}
void smoothMoveTwist() {
int iCurrentPos = gServoTwist.read();
int iMoveAmountPerScan = 1; // Grados por escaneo
int iNewMoveCommand;
if (iCurrentPos == gServoTwistPositionCommand) {
gServoTwistMoveIP = false; return;
} else gServoTwistMoveIP = true;
iNewMoveCommand = iCurrentPos;
if (iCurrentPos < gServoTwistPositionCommand) iNewMoveCommand += iMoveAmountPerScan;
else iNewMoveCommand -= iMoveAmountPerScan;
if (iNewMoveCommand < 0) iNewMoveCommand = 0;
if (iNewMoveCommand > 179) iNewMoveCommand = 179;
gServoTwist.write(iNewMoveCommand);
if (gVerboseDiagMode) {
Serial.print("Twist move: "); Serial.print(iNewMoveCommand); Serial.print(", "); Serial.println(gServoTwistPositionCommand);
}
}
void smoothMoveTilt() {
int iCurrentPos = gServoTilt.read();
int iMoveAmountPerScan = 1;
int iNewMoveCommand;
if (iCurrentPos == gServoTiltPositionCommand) {
gServoTiltMoveIP = false; return;
} else gServoTiltMoveIP = true;
iNewMoveCommand = iCurrentPos;
if (iCurrentPos < gServoTiltPositionCommand) iNewMoveCommand += iMoveAmountPerScan;
else iNewMoveCommand -= iMoveAmountPerScan;
if (iNewMoveCommand < 0) iNewMoveCommand = 0;
if (iNewMoveCommand > 179) iNewMoveCommand = 179;
gServoTilt.write(iNewMoveCommand);
if (gVerboseDiagMode) {
Serial.print("Tilt move: "); Serial.print(iNewMoveCommand); Serial.print(", "); Serial.println(gServoTiltPositionCommand);
}
}
float averageTopTwoSensors() { return (gLDR1 + gLDR2) / 2.0; }
float averageBottomTwoSensors() { return (gLDR3 + gLDR4) / 2.0; }
float averageLeftTwoSensors() { return (gLDR1 + gLDR3) / 2.0; }
float averageRightTwoSensors() { return (gLDR2 + gLDR4) / 2.0; }
void processSerialMessage(int iControlCode) {
int iControlCommand = iControlCode / 1000;
int iControlValue = iControlCode % 1000;
Serial.print("control code: "); Serial.println(iControlCode);
Serial.print("command: "); Serial.println(iControlCommand);
Serial.print("value: "); Serial.println(iControlValue);
if (iControlCommand == 10) { // Miscelánea
switch (iControlValue) {
case 0: gVerboseDiagMode = true; digitalWrite(O_BLUE_LED_PIN, HIGH); Serial.println("diagnostics started"); break;
case 1: gVerboseDiagMode = false; digitalWrite(O_BLUE_LED_PIN, LOW); Serial.println("diagnostics stopped"); break;
case 2: reportProductInfo(); break;
case 3: digitalWrite(O_RED_LED_PIN, HIGH); Serial.println("red led on"); break;
case 4: digitalWrite(O_RED_LED_PIN, LOW); Serial.println("red led off"); break;
case 5: digitalWrite(O_GREEN_LED_PIN, HIGH); Serial.println("green led on"); break;
case 6: digitalWrite(O_GREEN_LED_PIN, LOW); Serial.println("green led off"); break;
case 7: digitalWrite(O_BLUE_LED_PIN, HIGH); Serial.println("blue led on"); break;
case 8: digitalWrite(O_BLUE_LED_PIN, LOW); Serial.println("blue led off"); break;
case 9: Serial.print("LDR1: "); Serial.println(gLDR1); break;
case 10: Serial.print("LDR2: "); Serial.println(gLDR2); break;
case 11: Serial.print("LDR3: "); Serial.println(gLDR3); break;
case 12: Serial.print("LDR4: "); Serial.println(gLDR4); break;
case 13: enableTracking(); break;
case 14: disableTracking(); break;
case 19: if (gRunning && !gServoTwistMoveIP && !gServoTiltMoveIP) { gServoTwistPositionCommand = 90; gServoTiltPositionCommand = 90; Serial.println("servos to 90 deg"); } break;
case 21: if (gRunning) { gTrackToLightEnabled = true; Serial.println("light tracking enabled"); } break;
case 22: gTrackToLightEnabled = false; Serial.println("light tracking disabled"); break;
default: Serial.print("invalid value: "); Serial.println(iControlValue);
}
} else if (iControlCommand == 11) { // Servo giro
if (iControlValue >= 0 && iControlValue <= 179 && gRunning && !gServoTwistMoveIP) {
gServoTwistPositionCommand = iControlValue; gServoTwistMoveIP = true;
Serial.print("Twist cmd: "); Serial.println(gServoTwistPositionCommand);
} else Serial.print("invalid value: "); Serial.println(iControlValue);
} else if (iControlCommand == 12) { // Servo inclinación
if (iControlValue >= 0 && iControlValue <= 179 && gRunning && !gServoTiltMoveIP) {
gServoTiltPositionCommand = iControlValue; gServoTiltMoveIP = true;
Serial.print("Tilt cmd: "); Serial.println(gServoTiltPositionCommand);
} else Serial.print("tilt not ready: "); Serial.print(gRunning); Serial.print(", "); Serial.println(gServoTiltMoveIP);
}
Serial.println("-----");
}
void startup() {
int iDelay = 500;
reportProductInfo(); delay(iDelay);
processSerialMessage(10003); delay(iDelay); // Red ON
processSerialMessage(10004); processSerialMessage(10005); delay(iDelay); // Red OFF, Green ON
processSerialMessage(10006); processSerialMessage(10007); delay(iDelay); // Green OFF, Blue ON
processSerialMessage(10008); processSerialMessage(10009); processSerialMessage(10010); processSerialMessage(10011); processSerialMessage(10012); delay(iDelay); // LDRs
enableTracking(); delay(iDelay);
processSerialMessage(10019); delay(iDelay); // Home position
disableTracking();
Serial.println("startup completed");
}
void reportProductInfo() {
Serial.println("q version 2");
Serial.println("tucson, arizona usa");
Serial.println("may 2018");
Serial.print("checksum "); Serial.println("58BA-D969-2F82-08FD-2078-2777-396D-E1AA");
}
Partes personalizadas
Archivos de ensamblaje: q2_assembly_details.zip
Esquemático
Archivo Fritzing: q2_schematic.fzz
Proceso de manufactura
- Sistemas de calefacción solar: de soluciones pasivas a activas para calefacción sostenible del hogar
- Células solares: de los primeros experimentos a la fotovoltaica moderna – tecnología, producción y perspectivas
- Monitorización en Tiempo Real de Panel Solar con Arduino: Sistema de Adquisición de Datos de Bajo Coste
- Persianas Inteligentes: Control Automático de Luz con Arduino y Energía Solar
- Seguidor Solar IoT: Gestión Inteligente de Energía con Arduino
- SmartPostBox: Buzón Inteligente IoT con Notificaciones por Email
- Seguidor Solar de Doble Eje con Control Automático y Manual: Sistema Eficiente de Seguimiento Solar
- Cargador solar portátil con seguidor solar Arduino y control de servo
- Aprovechamiento de la energía solar: guía completa para una energía limpia y fiable
- Calidad de paneles solares: puntos de control esenciales para rendimiento óptimo
- Cómo se fabrican los paneles solares: guía paso a paso para producción de energía solar de alta calidad