#include <Servo.h> // Biblioteca para controle dos servomotores
#include <PID_v1.h> // Biblioteca oficial para os cálculos matemáticos do algoritmo PID
#include <Wire.h> // Biblioteca para comunicação I2C
#include <LiquidCrystal_I2C.h> // Biblioteca para controle do display LCD via adaptador I2C
// =====================================================
// PINOS
// =====================================================
#define SERVO_HORIZONTAL_PIN 3 // Pino PWM que controla o servo do eixo horizontal (Esquerda/Direita)
#define SERVO_VERTICAL_PIN 5 // Pino PWM que controla o servo do eixo vertical (Cima/Baixo)
#define LDR_TOP_RIGHT A0 // Pino analógico conectado ao sensor Superior Direito (Top Right)
#define LDR_BOTTOM_RIGHT A1 // Pino analógico conectado ao sensor Inferior Direito (Bottom Right)
#define LDR_TOP_LEFT A2 // Pino analógico conectado ao sensor Superior Esquerdo (Top Left)
#define LDR_BOTTOM_LEFT A3 // Pino analógico conectado ao sensor Inferior Esquerdo (Bottom Left)
// =====================================================
// LCD
// =====================================================
// Inicializa o LCD no endereço I2C 0x27, com 16 colunas e 2 linhas
LiquidCrystal_I2C lcd(0x27, 16, 2);
// =====================================================
// LIMITES MECÂNICOS
// =====================================================
const int H_MAX = 180; // Limite físico máximo de rotação do servo horizontal (Leste)
const int H_MIN = 60; // Limite físico mínimo de rotação do servo horizontal (Oeste)
const int V_MAX = 120; // Limite físico máximo de inclinação do servo vertical (Zênite / Alto)
const int V_MIN = 20; // Limite físico mínimo de inclinação do servo vertical (Horizonte / Baixo)
// =====================================================
// POSIÇÕES INICIAIS
// =====================================================
int posH = 120; // Posição angular inicial do eixo horizontal (geralmente apontado para o Leste)
int posV = 60; // Posição angular inicial do eixo vertical (inclinação intermediária)
// =====================================================
// CONFIGURAÇÕES
// =====================================================
const int DEADBAND = 15; // Zona morta: variação mínima de luz necessária para o motor se mover (evita trepidação)
const unsigned long LOOP_INTERVAL = 250; // Intervalo de atualização do cálculo do PID e motores (250 milissegundos)
const unsigned long LCD_INTERVAL = 500; // Intervalo de atualização das informações textuais no LCD (500 milissegundos)
const int NIGHT_THRESHOLD = 15; // Limite de leitura analógica abaixo do qual o sistema entende que anoiteceu
// =====================================================
// SERVOS
// =====================================================
Servo servoH; // Criação do objeto de controle do servomotor Horizontal
Servo servoV; // Criação do objeto de controle do servomotor Vertical
// =====================================================
// PID
// =====================================================
double inputH; // Entrada do PID Horizontal: armazena a diferença de luz atual entre direita/esquerda
double outputH; // Saída do PID Horizontal: cálculo de ajuste angular (-5 a +5 graus)
double setpointH = 0; // Alvo do PID Horizontal: o objetivo é zerar a diferença de luz entre os lados
double inputV; // Entrada do PID Vertical: armazena a diferença de luz atual entre cima/baixo
double outputV; // Saída do PID Vertical: cálculo de ajuste angular (-5 a +5 graus)
double setpointV = 0; // Alvo do PID Vertical: o objetivo é zerar a diferença de luz entre cima e baixo
// Constantes de Sintonia do PID Horizontal (Ganho Proporcional, Integral e Derivativo)
double KpH = 0.45;
double KiH = 0.002;
double KdH = 0.25;
// Constantes de Sintonia do PID Vertical (Ganho Proporcional, Integral e Derivativo)
double KpV = 0.40;
double KiV = 0.002;
double KdV = 0.20;
// Vincula as variáveis ao motor matemático do PID Horizontal configurado em modo Direto
PID pidH(&inputH, &outputH, &setpointH, KpH, KiH, KdH, DIRECT);
// Vincula as variáveis ao motor matemático do PID Vertical configurado em modo Direto
PID pidV(&inputV, &outputV, &setpointV, KpV, KiV, KdV, DIRECT);
// =====================================================
// CONTROLE DE TEMPO
// =====================================================
unsigned long lastLoop = 0; // Armazena o tempo (millis) da última execução do loop de controle
unsigned long lastLCD = 0; // Armazena o tempo (millis) da última atualização do display LCD
// =====================================================
// FILTROS
// =====================================================
// Variáveis persistentes que guardam o valor suavizado de cada sensor
float filteredTR = 0;
float filteredTL = 0;
float filteredBR = 0;
float filteredBL = 0;
// =====================================================
// FUNÇÕES
// =====================================================
// Função que lê a porta analógica e aplica um filtro passa-baixas exponencial (suaviza ruídos)
int readLDRFiltered(int pin, float &filtered)
{
int raw = analogRead(pin); // Realiza a leitura bruta do ADC do Arduino (0 a 1023)
// Filtro digital: retém 85% do valor histórico e adiciona apenas 15% da nova leitura
filtered = (filtered * 0.85) + (raw * 0.15);
return (int)filtered; // Retorna o valor filtrado convertido para inteiro
}
// Ativa o envio de sinal elétrico para os pinos dos servomotores (prende o motor)
void attachServos()
{
if (!servoH.attached())
servoH.attach(SERVO_HORIZONTAL_PIN);
if (!servoV.attached())
servoV.attach(SERVO_VERTICAL_PIN);
}
// Desativa o sinal dos servomotores para economizar energia e evitar que fiquem tremendo parados
void detachServos()
{
servoH.detach();
servoV.detach();
}
// Escreve os ângulos armazenados diretamente nas posições físicas dos servomotores
void moveServos()
{
servoH.write(posH);
servoV.write(posV);
}
// Avalia se o nível médio de luz ambiente é menor que o limite configurado para a noite
bool isNight(float average)
{
return average < NIGHT_THRESHOLD;
}
// Rotina que move o painel gradualmente de volta para a posição inicial (apontado para o Leste) ao escurecer
void returnEast()
{
if (posH > 120) posH--;
if (posH < 120) posH++;
if (posV > 60) posV--;
if (posV < 60) posV++;
attachServos(); // Ativa os motores para realizar a movimentação de retorno
moveServos(); // Executa o passo mecânico de retorno
}
// Escreve as variáveis de depuração e status no display LCD
void updateLCD(int diffH, int diffV, float avgLight)
{
lcd.setCursor(0, 0); // Posiciona o cursor na primeira coluna, primeira linha
lcd.print("H:");
lcd.print(posH); // Mostra o ângulo atual do servo horizontal
lcd.print(" V:");
lcd.print(posV); // Mostra o ângulo atual do servo vertical
lcd.print(" "); // Limpa caracteres residuais
lcd.setCursor(0, 1); // Posiciona o cursor na primeira coluna, segunda linha
lcd.print("L:");
lcd.print((int)avgLight); // Mostra a média de luz recebida pelos sensores
lcd.print(" D:");
lcd.print(diffH); // Mostra a magnitude do erro horizontal atual
lcd.print(" "); // Limpa caracteres residuais
}
// =====================================================
// SETUP (Configuração Inicial de Inicialização)
// =====================================================
void setup()
{
Serial.begin(9600); // Inicializa a porta serial para envio de telemetria
// Inicialização do visor LCD
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Solar Tracker");
lcd.setCursor(0, 1);
lcd.print("Inicializando");
// Vincula os servos aos pinos digitais no momento do boot
servoH.attach(SERVO_HORIZONTAL_PIN);
servoV.attach(SERVO_VERTICAL_PIN);
moveServos(); // Força os motores a irem para a posição de partida configurada
// Limita a ação de correção do PID para no máximo +/- 5 graus por ciclo de processamento
pidH.SetOutputLimits(-5, 5);
pidV.SetOutputLimits(-5, 5);
// Ajusta o tempo de amostragem interna do PID para casar com o loop (250ms)
pidH.SetSampleTime(250);
pidV.SetSampleTime(250);
// Ativa o cálculo computacional do PID para modo Automático
pidH.SetMode(AUTOMATIC);
pidV.SetMode(AUTOMATIC);
delay(2000); // Mantém a tela de inicialização visível por 2 segundos
lcd.clear(); // Limpa o display para iniciar a operação padrão
}
// =====================================================
// LOOP (Execução Contínua)
// =====================================================
void loop()
{
// Temporização não-bloqueante: aguarda passar 250ms antes de processar o loop
if (millis() - lastLoop < LOOP_INTERVAL)
return;
lastLoop = millis(); // Reseta o cronômetro do loop interno
// =================================================
// LEITURA DOS LDRs
// =================================================
// Obtém as leituras analógicas de todos os quatro sensores já tratadas pelo filtro digital
int tr = readLDRFiltered(LDR_TOP_RIGHT, filteredTR);
int tl = readLDRFiltered(LDR_TOP_LEFT, filteredTL);
int br = readLDRFiltered(LDR_BOTTOM_RIGHT, filteredBR);
int bl = readLDRFiltered(LDR_BOTTOM_LEFT, filteredBL);
// =================================================
// MÉDIAS E CÁLCULO DOS ERROS (DIFERENÇAS)
// =================================================
int top = (tr + tl) / 2; // Média da intensidade de luz na parte superior
int bottom = (br + bl) / 2; // Média da intensidade de luz na parte inferior
int right = (tr + br) / 2; // Média da intensidade de luz no lado direito
int left = (tl + bl) / 2; // Média da intensidade de luz no lado esquerdo
int diffH = right - left; // Erro Horizontal: diferença que dita se o sol está mais para a direita ou esquerda
int diffV = top - bottom; // Erro Vertical: diferença que dita se o sol está mais alto ou mais baixo
// Média global de luz recebida nos 4 sensores para avaliação do Modo Noturno
float average = (tr + tl + br + bl) / 4.0;
// =================================================
// MODO NOTURNO
// =================================================
if (isNight(average))
{
returnEast(); // Se estiver escuro, ativa o retorno mecânico gradual ao Leste
detachServos(); // Desliga os servos para proteção mecânica e economia de energia à noite
lcd.setCursor(0, 1);
lcd.print("Modo Noturno "); // Informa o estado de repouso no visor
delay(500); // Pausa o processamento por 500ms durante o modo noturno
return; // Interrompe o loop atual aqui, pulando os blocos do PID
}
attachServos(); // Garante que os servos estão energizados se não for noite
// =================================================
// DEADBAND (ZONA MORTA)
// =================================================
// Se o erro for menor que a zona morta, ignora a diferença para evitar micro-movimentos incessantes
if (abs(diffH) < DEADBAND)
diffH = 0;
if (abs(diffV) < DEADBAND)
diffV = 0;
// =================================================
// COMPUTAÇÃO DO ALGORITMO PID
// =================================================
inputH = diffH; // Alimenta a entrada do PID Horizontal com o erro geométrico calculado
inputV = diffV; // Alimenta a entrada do PID Vertical com o erro geométrico calculado
pidH.Compute(); // Executa as equações matemáticas diferenciais e integrais do eixo Horizontal
pidV.Compute(); // Executa as equações matemáticas diferenciais e integrais do eixo Vertical
// Modifica a variável de posição somando o ajuste angular calculado pela saída do PID Horizontal
posH += (int)outputH;
// Modifica a variável de posição subtraindo (Inversão Proposital) o ajuste do PID Vertical
posV -= (int)outputV;
// =================================================
// LIMITES MECÂNICOS (CONSTRAIN)
// =================================================
// Trava as variáveis de posição dentro das balizas de segurança para não quebrar a estrutura física do rastreador
posH = constrain(posH, H_MIN, H_MAX);
posV = constrain(posV, V_MIN, V_MAX);
// =================================================
// MOVIMENTO EFETIVO
// =================================================
moveServos(); // Envia as novas coordenadas angulares corrigidas para os motores
// =================================================
// ATUALIZAÇÃO TEMPORIZADA DO LCD
// =================================================
if (millis() - lastLCD > LCD_INTERVAL)
{
lastLCD = millis(); // Atualiza a marca de tempo do visor
updateLCD(diffH, diffV, average); // Renderiza os dados em tempo real na tela do LCD
}
// =================================================
// SERIAL TELEMETRIA (DEBUG)
// =================================================
// Transmite os parâmetros do sistema via Serial para plotagem ou diagnóstico no computador
Serial.print("TR:"); Serial.print(tr);
Serial.print(" TL:"); Serial.print(tl);
Serial.print(" BR:"); Serial.print(br);
Serial.print(" BL:"); Serial.print(bl);
Serial.print(" H:"); Serial.print(posH);
Serial.print(" V:"); Serial.print(posV);
Serial.print(" DiffH:"); Serial.print(diffH);
Serial.print(" DiffV:"); Serial.println(diffV); // Envia quebrando a linha
}