Cargador Solar MPPT Basado en Arduino para Cosecha Óptima de Energía FV
Componentes y suministros
| Componente | Cantidad | Notas |
|---|---|---|
| Arduino Nano R3 | 1 | |
| Inductor 100 uH | 1 | La corriente nominal debe coincidir con la potencia requerida |
| MOSFET N-Channel de potencia | 1 | |
| Pantalla LCD estándar Adafruit 16x2 (blanco sobre azul) | 1 |
Herramientas y máquinas necesarias
- Soldador (genérico)
Aplicaciones y servicios en línea
- Arduino IDE
Sobre el proyecto
Existen muchos controladores de carga en el mercado, pero los controladores baratos ordinarios no son eficientes para usar la máxima potencia de los paneles solares. Y los que son eficientes son muy costosos.
Por eso decidí hacer mi propio controlador de carga para que sea eficiente y lo suficientemente inteligente para entender las necesidades de la batería y las condiciones solares. Toma las acciones adecuadas para extraer la máxima potencia disponible del sol y cargar la batería de forma muy eficiente.
Si te gusta mi esfuerzo, por favor vota por este tutorial.
Paso 1: ¿Qué es MPPT y por qué lo necesitamos?
Nuestros paneles solares son "tontos" y no entienden las condiciones de la batería. Supongamos que tenemos un panel solar de 12V/100W que entrega entre 18V-21V según el fabricante, pero las baterías son de 12V nominales. A carga completa estarán a 13.6V y a descarga completa a 11.0V. Supongamos que la batería está a 13V cargando, el panel entrega 18V, 5.5A al 100% de eficiencia (no es posible, pero supongamos). Los controladores ordinarios tienen un regulador de voltaje PWM que reduce el voltaje a 13.6V sin ganancia de corriente. Solo protegen contra sobrecarga y corriente de fuga a los paneles por la noche.
Para contrarrestar esto, usé un convertidor buck SMPS. Este tipo de convertidor tiene más del 90% de eficiencia. Incluso el 90% se considera bajo.
El segundo problema es la salida no lineal de los paneles solares. Necesitan operar a un voltaje específico para cosechar la máxima potencia disponible. Su salida varía durante el día.
Para resolver esto se usan algoritmos MPPT. MPPT (Seguimiento del Punto de Máxima Potencia) como su nombre indica, este algoritmo rastrea la máxima potencia disponible de los paneles y varía los parámetros de salida para mantener la condición.
Así, usando MPPT, nuestros paneles generarán la máxima potencia disponible y el convertidor buck cargará esta energía eficientemente en las baterías.
Paso 2: ¿Cómo funciona MPPT?
No entraré en detalles, pero si quieres entenderlo, consulta este enlace: ¿Qué es MPPT?
En este proyecto he rastreado las características V-I de entrada y salida. Multiplicando la V-I de entrada y salida obtenemos la potencia en vatios.
Digamos que tenemos 17V 5A, es decir, 17x5 = 85 vatios en un momento del día. Al mismo tiempo, nuestra salida es 13V 6A, es decir, 13x6 = 78 vatios.
Ahora MPPT aumentará o disminuirá el voltaje de salida comparando con la potencia de entrada/salida anterior.
Si la potencia de entrada anterior era alta y el voltaje de salida era menor que el actual, entonces el voltaje de salida se reducirá de nuevo para volver a la alta potencia. Y si el voltaje de salida era alto, el voltaje actual se aumentará al nivel anterior. Así oscila alrededor del punto de máxima potencia. Estas oscilaciones se minimizan con algoritmos MPPT eficientes.
Paso 3: Implementando MPPT en Arduino
Este es el cerebro del cargador. Abajo está el código de Arduino para regular la salida e implementar MPPT en un solo bloque.
// Iout = corriente de salida // Vout = voltaje de salida // Vin = voltaje de entrada
// Pin = potencia de entrada, Pin_previous = última potencia de entrada
// Vout_last = último voltaje de salida, Vout_sense = voltaje de salida actual
void regulate(float Iout, float Vin, float Vout) {
if ((Vout > Vout_max) || (Iout > Iout_max) ||
((Pin > Pin_previous && Vout_sense < Vout_last) ||
(Pin < Pin_previous && Vout_sense > Vout_last))) {
if (duty_cycle > 0) { duty_cycle -= 1; }
analogWrite(buck_pin, duty_cycle);
} else if ((Vout < Vout_max) && (Iout < Iout_max) &&
((Pin > Pin_previous && Vout_sense > Vout_last) ||
(Pin < Pin_previous && Vout_sense < Vout_last))) {
if (duty_cycle < 240) { duty_cycle += 1; }
analogWrite(buck_pin, duty_cycle);
}
Pin_previous = Pin;
Vin_last = Vin;
Vout_last = Vout;
}
Paso 4: Convertidor Buck

He usado un MOSFET N-channel para hacer el convertidor buck. Generalmente se elige MOSFET P-channel para conmutación en lado alto y si se elige N-channel para el mismo propósito se requeriría un driver IC o circuito bootstrap.
Pero modifiqué el circuito buck para tener conmutación en lado bajo usando MOSFET N-channel. Uso N-channel porque son de bajo costo, altas potencias nominales y menor disipación. Este proyecto usa MOSFET IRFZ44N de nivel lógico, por lo que puede ser impulsado directamente por un pin PWM de Arduino.
Para mayor corriente de carga, se debe usar un transistor para aplicar 10V en la gate y llevar el MOSFET a saturación completa minimizando la disipación. He hecho lo mismo.
Como se ve en el circuito, he colocado el MOSFET en el voltaje negativo, usando +12V del panel como tierra. Esta configuración me permite usar un MOSFET N-channel para el convertidor buck con mínimos componentes.
Pero también tiene inconvenientes. Al tener ambos lados -Ve separados, ya no tienes una referencia de tierra común. Así que la medición de voltajes es muy delicada.
He conectado el Arduino en los terminales de entrada solar, usando su línea -Ve como tierra para Arduino. Podemos medir fácilmente el voltaje de entrada en este punto usando un divisor de voltaje según nuestros requisitos. Pero no podemos medir el voltaje de salida tan fácilmente porque no tenemos tierra común.
Ahora hay un truco. En lugar de medir el voltaje a través del capacitor de salida, he medido el voltaje entre las dos líneas -Ve. Usando -Ve solar como tierra para Arduino y -Ve de salida como la señal/voltaje a medir. El valor obtenido con esta medición debe restarse del voltaje de entrada medido y obtendrás el voltaje real de salida a través del capacitor de salida.
Vout_sense_temp = Vout_sense_temp * 0.92 + float(raw_vout) * volt_factor * 0.08; // mide voltaje entre GND entrada y GND salida
Vout_sense = Vin_sense - Vout_sense_temp - diode_volt; // convierte diferencia de tierras a voltaje de salida
Para mediciones de corriente he usado módulos ACS-712. Han sido alimentados por Arduino y conectados a GND de entrada.
Los temporizadores internos se modificaron para obtener PWM de 62.5 kHz en pin D6, que impulsa el MOSFET. Se requerirá un diodo de bloqueo de salida para protección contra fuga inversa y polaridad inversa, usar diodo Schottky de la corriente nominal deseada. El valor del inductor depende de la frecuencia y requisitos de corriente de salida. Puedes usar calculadoras de convertidor buck disponibles en línea o usar 100uH 5A-10A carga. Nunca excedas la corriente máxima de salida del inductor en más del 80%-90%.
Paso 5: Acabado final
También puedes añadir características adicionales a tu cargador. El mío tiene LCD para mostrar parámetros y 2 interruptores para entrada del usuario.
Actualizaré pronto el código final y el diagrama completo del circuito.
Paso 6: Diagrama de circuito real, BOM y código

ACTUALIZACIÓN: He subido el código, BOM y circuito. Es ligeramente diferente del mío, porque es más fácil de hacer.
Solar_charger_tutorial_code.inoCódigo completo
Solar_charger_tutorial_code.ino (Arduino)
///////////////////////////////////////////////////////////////////////////////////////
// Terms of use
///////////////////////////////////////////////////////////////////////////////////////
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND...
///////////////////////////////////////////////////////////////////////////////////////
#include <LiquidCrystal.h>
#include <avr/wdt.h>
#include <EEPROM.h>
#define vin_pin A1
#define vout_pin A0
#define iout_pin A2
#define iin_pin A3
#define lm35 A4
#define fan 5
#define buck_pin 6
#define menu 3
#define button 2
#define led 13
#define charge_led A5
#define light 4
uint8_t auto_mode = 1;
float Pin = 0, Pout = 0, Pin_previous = 0;
float efficiency = 0.0;
int raw_vin = 0, raw_vout = 0, raw_iout = 0, raw_iin = 0, raw_lm35 = 0;
float Vout_boost = 14.5, Vout_max = 15.0, Iout_max = 5.0, Vout_float = 13.5, Iout_min = 0.00, Vin_thresold = 10.0;
float Iout_sense, Iin_sense, Iin;
float Vout_sense, Vin_last;
float heat_sink_temp;
float Vin_sense;
uint8_t duty_cycle = 0;
float volt_factor = 0.05376; // Cambia este valor para calibrar lecturas de voltaje...
String mode = "";
bool startup = true, lcd_stat = true, charge = true, mppt_init = true;
unsigned int count = 0;
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
void lcd_show(String data, int column, int row);
void UI();
void set_limits(int cmd, int temp);
void mem_read();
void mem_write();
void mppt();
void setup() {
wdt_disable();
watchdogSetup();
Serial.begin(115200);
mem_read();
pinMode(light, OUTPUT);
pinMode(charge_led, OUTPUT);
digitalWrite(charge_led, LOW);
digitalWrite(light, HIGH);
pinMode(led, OUTPUT);
pinMode(fan, OUTPUT);
pinMode(menu, INPUT);
pinMode(button, INPUT);
digitalWrite(menu, HIGH);
digitalWrite(button, HIGH);
TCCR0B = TCCR0B & 0b11111000 | 0x01; // PWM a 62.5 kHz
analogWrite(buck_pin, 0);
lcd.begin(16, 2);
lcd_show("Solar Charger", 0, 0);
delay(64000);
wdt_reset();
delay(64000);
wdt_reset();
lcd_show("Vi Vb Ib ", 0, 0);
// Promediado de lecturas iniciales
for (int i = 0; i < 10; i++) {
raw_iout += analogRead(iout_pin) - 513;
raw_iin += analogRead(iin_pin) - 513;
raw_vin += analogRead(vin_pin);
raw_vout += analogRead(vout_pin);
raw_lm35 += analogRead(lm35);
delay(2);
}
raw_iout /= 10; raw_iin /= 10; raw_vout /= 10; raw_vin /= 10;
Iout_sense = float(raw_iout) * 5 / 1023 / 0.066;
Iin_sense = float(raw_iin) * 5 / 1023 / 0.066;
Vout_sense_temp = float(raw_vout) * volt_factor;
Vin_sense = float(raw_vin) * volt_factor;
}
void watchdogSetup(void) {
cli();
wdt_reset();
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = (1 << WDIE) | (1 << WDE) | (1 << WDP3) | (0 << WDP2) | (0 << WDP1) | (1 << WDP0);
sei();
}
ISR(WDT_vect) {
Serial.println("WDT reset.");
asm volatile (" jmp 0");
}
void regulate(float Iout, float Vin, float Vout) {
mode = "Buck mode";
if ((Vout > Vout_max) || (Iout > Iout_max) ||
((Pin > Pin_previous && Vin_sense < Vin_last) ||
(Pin < Pin_previous && Vin_sense > Vin_last))) {
if (duty_cycle > 0) { duty_cycle -= 1; }
analogWrite(buck_pin, duty_cycle);
} else if ((Vout < Vout_max) && (Iout < Iout_max) &&
((Pin > Pin_previous && Vin_sense > Vin_last) ||
(Pin < Pin_previous && Vin_sense < Vin_last))) {
if (duty_cycle < 250) { duty_cycle += 1; }
analogWrite(buck_pin, duty_cycle);
}
Pin_previous = Pin;
Vin_last = Vin;
}
void auto_cutoff(float Iout, float Vin, float Vout) {
if (Vout <= Vout_float && Iout > Iout_min + 1) { charge = true; }
if ((Vout > Vout_max) && (Iout < Iout_min) && (charge == true)) {
charge = false;
Serial.println("Charging Completed.");
digitalWrite(led, HIGH);
digitalWrite(charge_led, LOW);
} else if (Vin < Vin_thresold) {
duty_cycle = 0;
analogWrite(buck_pin, duty_cycle);
Serial.println("LOW Input Voltage.");
lcd_show("Input Volt. Low ", 0, 1);
wdt_reset();
for (int i = 0; i < 10; i++) {
digitalWrite(led, HIGH); digitalWrite(charge_led, LOW); delay(6000);
digitalWrite(charge_led, HIGH); digitalWrite(led, LOW); delay(6000);
}
wdt_reset();
} else if (heat_sink_temp > 80.0) {
duty_cycle = 0;
analogWrite(buck_pin, duty_cycle);
Serial.println("Over Heat Shutdown");
lcd_show("Over Heat Fail ", 0, 1);
wdt_reset();
for (int i = 0; i < 10; i++) {
digitalWrite(led, HIGH); digitalWrite(charge_led, LOW); delay(4000);
digitalWrite(charge_led, HIGH); digitalWrite(led, LOW); delay(4000);
}
wdt_reset();
} else {
charge = true;
digitalWrite(charge_led, HIGH);
regulate(Iout_sense, Vin_sense, Vout_sense);
digitalWrite(led, LOW);
}
}
void soft_start() {
for (int i = 0; i < 20; i++) {
regulate(Iout_sense, Vin_sense, Vout_sense);
Serial.print("Vin= "); Serial.println(Vin_sense);
Serial.print("Vout= "); Serial.println(Vout_sense);
Serial.print("Iout= "); Serial.println(Iout_sense);
Serial.print("Duty cycle= "); Serial.println(duty_cycle);
Serial.print("Charger MODE: "); Serial.println(mode);
Serial.println("Soft Start Activated");
delay(32000);
}
startup = false;
mppt_init = false;
}
void lcd_show(String data, int column, int row) {
lcd.setCursor(column, row);
if (data.length() > 0) {
for (int i = 0; i < data.length(); i++) { lcd.print(" "); }
lcd.setCursor(column, row);
lcd.print(data);
}
}
void lcd_num(float num, int column, int row) {
lcd.setCursor(column, row);
for (int i = 0; i < 6; i++) { lcd.print(" "); }
lcd.setCursor(column, row);
lcd.print(num);
}
void UI() {
int cmd = 0, i = 0;
while (i == 0) {
wdt_reset();
if (digitalRead(menu) == LOW) { cmd += 1; delay(64000); }
switch (cmd) {
case 1: lcd_show("Vb Boost=", 0, 0); lcd_num(Vout_boost, 9, 0);
while (digitalRead(menu) == HIGH) {
wdt_reset();
if (digitalRead(button) == LOW) {
Vout_boost += 0.02; if (Vout_boost > 17.0) Vout_boost = 0;
delay(8000); lcd_num(Vout_boost, 9, 0);
}
} break;
case 2: lcd_show("Vb Float=", 0, 0); lcd_num(Vout_float, 9, 0);
while (digitalRead(menu) == HIGH) {
wdt_reset();
if (digitalRead(button) == LOW) {
Vout_float += 0.02; if (Vout_float > 17.0) Vout_float = 0;
delay(8000); lcd_num(Vout_float, 9, 0);
}
} break;
case 3: lcd_show("Ib max.= ", 0, 0); lcd_num(Iout_max, 9, 0);
while (digitalRead(menu) == HIGH) {
wdt_reset();
if (digitalRead(button) == LOW) {
Iout_max += 0.05; if (Iout_max > 20.0) Iout_max = 0;
delay(8000); lcd_num(Iout_max, 9, 0);
}
} break;
case 4: lcd_show("Ib min.= ", 0, 0); lcd_num(Iout_min, 9, 0);
while (digitalRead(menu) == HIGH) {
wdt_reset();
if (digitalRead(button) == LOW) {
Iout_min += 0.05; if (Iout_min > 20.0) Iout_min = 0.0;
delay(8000); lcd_num(Iout_min, 9, 0);
}
} break;
case 5: lcd_show("Vin min.= ", 0, 0); lcd_num(Vin_thresold, 9, 0);
while (digitalRead(menu) == HIGH) {
wdt_reset();
if (digitalRead(button) == LOW) {
Vin_thresold += 0.02; if (Vin_thresold > 18.0) Vin_thresold = 0;
delay(8000); lcd_num(Vin_thresold, 9, 0);
}
} break;
case 6: lcd_show("Choose Mode ", 0, 0); lcd_show("Auto Mode ", 0, 1);
while (digitalRead(menu) == HIGH) {
wdt_reset();
if (digitalRead(button) == LOW) {
auto_mode = ~auto_mode;
if (auto_mode) lcd_show("Auto Mode ON ", 0, 1);
else lcd_show("Auto Mode OFF ", 0, 1);
delay(32000);
}
} break;
case 7: lcd_show("Press Men+u to ", 0, 0); lcd_show("Save & Exit. ", 0, 1);
while (digitalRead(menu) == HIGH) { wdt_reset(); }
mem_save();
setup();
i = 1;
break;
}
}
}
void mem_save() {
uint8_t value = 0;
for (int i = 0; i < 7; i++) { EEPROM.write(i, 0); }
value = Vout_boost * 10; EEPROM.write(1, value);
value = Vout_float * 10; EEPROM.write(2, value);
value = Iout_max * 10; EEPROM.write(3, value);
value = Iout_min * 10; EEPROM.write(4, value);
value = Vin_thresold * 10; EEPROM.write(6, value);
}
void mem_read() {
Vout_boost = float(EEPROM.read(1)) / 10;
Vout_float = float(EEPROM.read(2)) / 10;
Iout_max = float(EEPROM.read(3)) / 10;
Iout_min = float(EEPROM.read(4)) / 10;
Vin_thresold = float(EEPROM.read(6)) / 10;
for (int i = 0; i < 7; i++) { Serial.println(float(EEPROM.read(i)) / 10); }
}
void set_limits(int cmd, int temp) {
switch (cmd) {
case 1: Vout_boost = float(temp) / 10; break;
case 2: Vout_float = float(temp) / 10; break;
case 3: Iout_max = float(temp) / 10; break;
case 4: Iout_min = float(temp) / 10; break;
case 5: auto_mode = ~auto_mode; break;
case 6: Vin_thresold = float(temp) / 10; break;
}
}
void loop() {
wdt_reset();
if (charge) { Vout_max = Vout_boost; } else { Vout_max = Vout_float; }
raw_vin = 0; raw_vout = 0; raw_iout = 0; raw_lm35 = 0;
if (digitalRead(menu) == LOW) { UI(); }
if (Serial.available() > 0) {
String data = Serial.readString();
int temp = data.toInt();
int func = temp % 10;
temp = temp / 10;
set_limits(func, temp);
}
for (int i = 0; i < 10; i++) {
raw_iout += analogRead(iout_pin) - 513;
raw_iin += analogRead(iin_pin) - 513;
raw_vin += analogRead(vin_pin);
raw_vout += analogRead(vout_pin);
raw_lm35 += analogRead(lm35);
delay(1);
}
raw_iout /= 10; raw_iin /= 10; raw_vout /= 10; raw_vin /= 10; raw_lm35 /= 10;
Iout_sense = float(raw_iout) * 5 / 1023 / 0.066;
Iin_sense = float(raw_iin) * 5 / 1023 / 0.066;
Vin_sense = Vin_sense * 0.92 + float(raw_vin) * volt_factor * 0.08;
Vout_sense = Vout_sense * 0.92 + float(raw_vout) * volt_factor * 0.08;
heat_sink_temp = heat_sink_temp * 0.92 + float(raw_lm35) * 0.48 * 0.08;
if (Iout_sense < 0.0) Iout_sense *= -1;
if (Iin_sense < 0.0) Iin_sense *= -1;
Pin = Vin_sense * Iin_sense;
Pout = Vout_sense * Iout_sense;
efficiency = Pout * 100 / Pin;
if (efficiency < 0.0) efficiency = 0.0;
if (count > 100) {
Serial.print("heat_sink_temp = "); Serial.println(heat_sink_temp);
Serial.print("Vin= "); Serial.println(Vin_sense);
Serial.print("Iin= "); Serial.println(Iin_sense);
Serial.print("Vout= "); Serial.println(Vout_sense);
Serial.print("Iout= "); Serial.println(Iout_sense);
Serial.print("Duty cycle= "); Serial.print(duty_cycle / 2.55); Serial.println("%");
Serial.print("PV power = "); Serial.println(Pin);
Serial.print("Output power = "); Serial.println(Pout);
Serial.print("Efficiency = "); Serial.print(efficiency); Serial.println("%");
Serial.print("Converter MODE: "); Serial.println(mode);
if (lcd_stat) {
lcd_num(Iin_sense, 0, 1);
lcd_show("T-", 0, 0); lcd_num(heat_sink_temp, 2, 0);
lcd_show("E-", 8, 0); lcd_num(efficiency, 10, 0); lcd_show("%", 15, 0);
lcd_stat = false;
} else {
lcd_num(Vin_sense, 0, 1);
lcd_show(" ", 15, 0);
if (charge) lcd_show("Vi Vb-B Ib ", 0, 0);
else lcd_show("Vi Vb-F Ib ", 0, 0);
lcd_stat = true;
}
lcd_num(Vout_sense, 6, 1);
lcd_num(Iout_sense, 12, 1);
digitalWrite(led, HIGH);
delay(16000);
count = 0;
}
if (startup == false) {
if (auto_mode == 1) { auto_cutoff(Iout_sense, Vin_sense, Vout_sense); }
else { digitalWrite(charge_led, HIGH); regulate(Iout_sense, Vin_sense, Vout_sense); digitalWrite(led, ~digitalRead(led)); }
}
if (heat_sink_temp > 45.0) { digitalWrite(fan, HIGH); }
else if (heat_sink_temp < 37.0) { digitalWrite(fan, LOW); }
count++;
}
Esquemáticos
Proceso de manufactura
- Tecnología de bulldozers: diseño, fabricación e innovaciones futuras
- 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
- Construye un Spybot controlado por Bluetooth con Arduino
- FlickMote: Mando IR por gestos para Smart Home
- TV‑B‑Gone DIY con Arduino: Mando universal para apagar cualquier TV
- Construye un Reloj Maestro LED Personalizado con Alarma – Visible desde 12 Metros
- Find Me: Localizador Inteligente de Objetos con Arduino y Bluetooth
- Soluciones de Alimentación Optimizadas para Proyectos Arduino
- Rastreador Solar V2.0 – Sistema Avanzado DIY de Seguimiento Solar
- Energía eólica: Conversión del movimiento del aire en electricidad limpia