Carregador Solar MPPT Baseado em Arduino para Colheita Ótima de Energia Fotovoltaica
Componentes e Materiais
| Componente | Quantidade | Observações |
|---|---|---|
| Arduino Nano R3 | 1 | |
| Indutor 100 µH | 1 | A corrente nominal deve corresponder à potência requerida |
| MOSFET de Potência Canal-N | 1 | |
| Display LCD Padrão Adafruit 16x2 (Branco sobre Azul) | 1 |
Ferramentas Necessárias
- Ferro de Solda (genérico)
Software e Serviços Online
- Arduino IDE
Sobre o Projeto
Existem muitos controladores de carga no mercado, mas os controladores baratos comuns não são eficientes para uso com potência máxima de painéis solares. E os eficientes são muito caros.
Por isso, decidi fazer meu próprio controlador de carga para ser eficiente e inteligente o suficiente para entender as necessidades da bateria e as condições solares. Ele toma as ações apropriadas para extrair a potência máxima disponível do painel solar e colocá-la na bateria de forma muito eficiente.
Passo 1: O que é MPPT e Por Que Precisamos Disso?
Nossos painéis solares são "burros" e não inteligentes o suficiente para entender as condições da bateria. Assumamos que temos um painel solar de 12V/100W que fornece saída entre 18V-21V dependendo do fabricante, mas as baterias são classificadas para tensão nominal de 12V. Em carga completa, elas estarão a 13,6V e em descarga completa a 11,0V. Agora, assumamos que nossas baterias estão a 13V carregando, painéis fornecendo 18V, 5,5A a 100% de eficiência (não é possível ter 100%, mas vamos assumir). Controladores comuns têm um circuito regulador de tensão PWM que reduz a tensão para 13,6V sem nenhum ganho de corrente. Eles apenas fornecem proteção contra sobrecarga e corrente de fuga para os painéis durante a noite.
Para contornar este problema, usei um conversor buck SMPS. Este tipo de conversor tem eficiência acima de 90%. Mesmo 90% é considerado ruim.
O segundo problema é a saída não linear dos painéis solares. Eles precisam operar em uma tensão específica para colher a potência máxima disponível. Sua saída varia ao longo do dia.
Para resolver isso, algoritmos MPPT são usados. MPPT (Maximum Power Point Tracking - Rastreamento do Ponto de Potência Máxima) como o nome sugere, este algoritmo rastreia a potência máxima disponível dos painéis e varia os parâmetros de saída para sustentar a condição.
Assim, usando MPPT, nossos painéis gerarão a potência máxima disponível e o conversor buck colocará esta carga nas baterias de forma eficiente.
Passo 2: Como o MPPT Funciona?
Não vou discutir em detalhes, então se quiser entender, veja este link - O que é MPPT?
Neste projeto, rastreio as características V-I de entrada e saída também. Multiplicando a V-I de entrada e a V-I de saída, podemos obter a potência em watts.
Digamos que temos 17V 5A, ou seja, 17x5 = 85 watts, em qualquer momento do dia. No mesmo momento, nossa saída é 13V 6A, ou seja, 13x6 = 78 Watts.
Agora, o MPPT aumentará ou diminuirá a tensão de saída comparando com a potência de entrada/saída anterior.
Se a potência de entrada anterior era alta e a tensão de saída era menor que a atual, a tensão de saída será reduzida novamente para voltar à alta potência. E se a tensão de saída era alta, a tensão atual será aumentada para o nível anterior. Assim, ele continua oscilando em torno do ponto de potência máxima. Estas oscilações são minimizadas por algoritmos MPPT eficientes.
Passo 3: Implementando MPPT no Arduino
Este é o cérebro do carregador. Abaixo está o código Arduino para regular a saída e implementar MPPT em um único bloco de código.
// Iout = corrente de saída // Vout = tensão de saída // Vin = tensão de entrada
// Pin = potência de entrada, Pin_previous = última potência de entrada
// Vout_last = última tensão de saída, Vout_sense = tensão de saída atual
void regular(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;
}
Passo 4: Conversor Buck
Usei MOSFET canal-N para fazer o conversor buck. Geralmente as pessoas escolhem MOSFET canal-P para chaveamento do lado alto e, se escolherem MOSFET canal-N para o mesmo propósito, será necessário um CI driver ou circuito de bootstrap.
Mas modifiquei o circuito do conversor buck para ter chaveamento do lado baixo usando MOSFET canal-N. Uso canal-N porque são de baixo custo, altas classificações de potência e menor dissipação. Este projeto usa MOSFET IRFZ44N de nível lógico, então pode ser acionado diretamente por um pino PWM do Arduino.
Para correntes de carga maiores, deve-se usar um transistor para aplicar 10V no gate para levar o MOSFET à saturação completa e minimizar a dissipação de potência. Fiz o mesmo.
Como pode ver no circuito acima, coloquei o MOSFET na tensão -Ve, usando +12V do painel como terra. Esta configuração me permite usar um MOSFET canal-N para o conversor buck com componentes mínimos.
Mas também tem desvantagens. Como você tem ambos os lados -Ve separados, não tem mais uma referência de terra comum. Então a medição de tensões fica muito complicada.
Conectei o Arduino nos terminais de entrada solar, usando sua linha -Ve como terra para o Arduino. Podemos medir facilmente a tensão de entrada neste ponto usando um divisor de tensão conforme nossa necessidade. Mas não podemos medir a tensão de saída tão facilmente pois não temos terra comum.
Agora, para fazer isso há um truque. Em vez de medir a tensão no capacitor de saída, medi a tensão entre as duas linhas -Ve. Usando o -Ve solar como terra para o Arduino e o -Ve de saída como o sinal/tensão a ser medida. O valor obtido com esta medição deve ser subtraído da tensão de entrada medida e você obterá a tensão real de saída no capacitor de saída.
Vout_sense_temp = Vout_sense_temp * 0.92 + float(raw_vout) * volt_factor * 0.08; // mede tensão entre GND entrada e GND saída
Vout_sense = Vin_sense - Vout_sense_temp - diode_volt; // converte diferença entre dois grounds para tensão de saída
Para medições de corrente, usei módulos de sensor de corrente ACS712. Eles foram alimentados pelo Arduino e conectados ao GND de entrada.
Timers internos foram modificados para obter PWM de 62,5 kHz no pino D6, usado para acionar o MOSFET. Um diodo de bloqueio de saída será necessário para fornecer proteção contra fuga reversa e polaridade reversa - use diodo Schottky da classificação de corrente desejada. O valor do indutor depende da frequência e requisitos de corrente de saída. Você pode usar calculadoras de conversor buck disponíveis online ou usar 100µH para carga 5A-10A. Nunca exceda a corrente máxima de saída do indutor em 80%-90%.
Passo 5: Acabamento Final
Você também pode adicionar recursos adicionais ao seu carregador. O meu tem LCD para exibir parâmetros e 2 chaves para entrada do usuário.
Atualizarei o código final e o diagrama completo do circuito muito em breve.
Passo 6: Diagrama Real do Circuito, BOM e Código
ATUALIZAÇÃO: Carreguei o código, BOM e circuito. É ligeiramente diferente do meu, porque este é mais fácil de fazer.
Solar_charger_tutorial_code.ino
Código Completo
Solar_charger_tutorial_code.ino (Arduino)
///////////////////////////////////////////////////////////////////////////////////////
// Termos de uso
///////////////////////////////////////////////////////////////////////////////////////
// O SOFTWARE É FORNECIDO "COMO ESTÁ", SEM GARANTIA DE NENHUM TIPO, EXPRESSA OU
// IMPLÍCITA, INCLUINDO MAS NÃO LIMITADO ÀS GARANTIAS DE COMERCIALIZAÇÃO,
// ADEQUAÇÃO A UM PROPÓSITO ESPECÍFICO E NÃO VIOLAÇÃO. EM NENHUM CASO OS
// AUTORES OU DETENTORES DE DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER
// REIVINDICAÇÃO, DANOS OU OUTRA RESPONSABILIDADE, SEJA EM UMA AÇÃO DE CONTRATO,
// DELITO OU OUTRA FORMA, DECORRENTE DE, FORA DE OU EM CONEXÃO COM O SOFTWARE
// OU O USO OU OUTRAS NEGOCIAÇÕES NO SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////
#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; // altere este valor para calibrar leituras de tensão
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("Carregador Solar", 0, 0);
delay(64000); wdt_reset();
delay(64000); wdt_reset();
lcd_show("Vi Vb Ib ", 0, 0);
// Calibração inicial
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 regular(float Iout, float Vin, float Vout) {
mode = "Modo Buck";
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("Carregamento Concluido.");
digitalWrite(led, HIGH);
digitalWrite(charge_led, LOW);
} else if (Vin < Vin_thresold) {
duty_cycle = 0; analogWrite(buck_pin, duty_cycle);
Serial.println("Tensao de Entrada Baixa.");
lcd_show("Tensao Entr. Baixa", 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("Desligamento por Superaquecimento");
lcd_show("Falha Superaquec.", 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);
regular(Iout_sense, Vin_sense, Vout_sense);
digitalWrite(led, LOW);
}
}
void soft_start() {
for (int i = 0; i < 20; i++) {
regular(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("Ciclo util= "); Serial.println(duty_cycle);
Serial.print("MODO CARREGADOR: "); Serial.println(mode);
Serial.println("Soft Start Ativado");
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("Escolha Modo ", 0, 0); lcd_show("Modo Auto ", 0, 1);
while (digitalRead(menu) == HIGH) { wdt_reset();
if (digitalRead(button) == LOW) { auto_mode = ~auto_mode;
if (auto_mode) lcd_show("Modo Auto ON ", 0, 1); else lcd_show("Modo Auto OFF", 0, 1);
delay(32000);
}
} break;
case 7: lcd_show("Pressione Men+u", 0, 0); lcd_show("Salvar e Sair. ", 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("temp_dissipador = "); 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("Ciclo util= "); Serial.print(duty_cycle / 2.55); Serial.println("%");
Serial.print("Potencia PV = "); Serial.println(Pin);
Serial.print("Potencia Saida = "); Serial.println(Pout);
Serial.print("Eficiencia = "); Serial.print(efficiency); Serial.println("%");
Serial.print("MODO CONVERSOR: "); 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); regular(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
Processo de manufatura
- Tecnologia de Tratores de Esteira (Bulldozers): Design, Fabricação e Inovações Futuras
- Células Solares: Dos Primeiros Experimentos à Fotovoltaica Moderna – Tecnologia, Produção e Perspectivas Futuras
- Monitoramento em Tempo Real de Painel Solar com Arduino: Sistema de Aquisição de Dados de Baixo Custo
- Construa um Spybot Controlado por Bluetooth com Arduino
- FlickMote: Controle Remoto IR por Gestos para Casa Inteligente
- DIY Arduino TV B-Gone: Controle Remoto Universal para Todas as TVs
- Construa um Relógio Mestre LED Personalizado com Alarme – Visível a 12 Metros
- Find Me: Localizador Inteligente de Objetos com Arduino e Bluetooth
- Rastreador Solar V2.0 – Sistema Avançado de Rastreamento Solar DIY
- Energia Eólica: Convertendo o Movimento do Ar em Eletricidade Limpa