// ---------------------------------------------------------
// LIBRERÍAS
// ---------------------------------------------------------
#include <Adafruit_GFX.h> // Librería gráfica base para pantallas Adafruit
#include <Adafruit_ILI9341.h> // Controlador específico para pantalla TFT ILI9341
#include <TinyGPSPlus.h> // Librería para procesar datos del GPS
#include <SoftwareSerial.h> // Permite usar pines digitales como puerto serial adicional
#include <math.h> // Funciones matemáticas (sin, cos, asin, etc.)
#include <avr/pgmspace.h> // Para almacenar datos en la memoria Flash (PROGMEM)
// ---------------------------------------------------------
// DEFINICIÓN DE PINES DE LA PANTALLA TFT
// ---------------------------------------------------------
#define TFT_DC 8
#define TFT_CS 10
#define TFT_RST 9
// Inicialización de pantalla TFT
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
// Inicialización del GPS en pines 4 (RX) y 3 (TX)
SoftwareSerial gpsSerial(4, 3);
TinyGPSPlus gps; // Objeto para procesar los datos NMEA del GPS
int step = 0; // Contador auxiliar para animaciones o movimiento simulado
// ---------------------------------------------------------
// ESTRUCTURA PARA ESTRELLAS REALES (RA = Ascensión recta, DEC = Declinación)
// ---------------------------------------------------------
struct Star {
float ra; // Ascensión recta (en horas)
float dec; // Declinación (en grados)
uint16_t color; // Color de la estrella
};
// Lista de estrellas con coordenadas astronómicas reales
// Se almacenan en memoria Flash (PROGMEM) para optimizar RAM
const Star stars[] PROGMEM = {
{6.752, -16.716, ILI9341_RED}, // Sirius
{6.399, -52.695, ILI9341_BLUE}, // Canopus
{14.261, 19.182, ILI9341_GREEN}, // Arcturus
{18.615, 38.783, ILI9341_MAGENTA},// Vega
{5.278, 45.997, ILI9341_CYAN}, // Capella
{5.243, -8.202, ILI9341_ORANGE}, // Rigel
{7.655, 5.225, ILI9341_YELLOW}, // Procyon
{1.628, -57.236, ILI9341_RED}, // Achernar
{5.919, 7.407, ILI9341_BLUE}, // Betelgeuse
{14.063, -60.373, ILI9341_GREEN}, // Hadar
{16.490, -26.432, ILI9341_ORANGE},// Antares
{12.795, -59.688, ILI9341_CYAN}, // Mimosa
{10.139, 11.967, ILI9341_YELLOW}, // Regulus
{5.418, 6.349, ILI9341_RED}, // Bellatrix
{13.420, -11.161, ILI9341_BLUE}, // Spica
{19.846, 8.868, ILI9341_GREEN}, // Altair
{7.576, 26.716, ILI9341_MAGENTA}, // Castor
{7.578, 28.026, ILI9341_CYAN}, // Pollux
// Duplicadas para efecto visual o brillo
{5.919, 7.407, ILI9341_YELLOW}, // Betelgeuse
{1.493, -49.339, ILI9341_RED}, // Achernar
{17.560, -37.103, ILI9341_ORANGE},// Shaula
{16.888, -44.322, ILI9341_CYAN}, // Kaus Australis
{16.509, -26.432, ILI9341_GREEN}, // Antares
{6.752, -16.716, ILI9341_YELLOW}, // Sirius
{5.919, 7.407, ILI9341_RED}, // Betelgeuse
{14.063, -60.373, ILI9341_BLUE}, // Hadar
{13.792, -60.373, ILI9341_ORANGE},// Acrux
{16.490, -26.432, ILI9341_CYAN}, // Antares
{6.399, -52.695, ILI9341_GREEN}, // Canopus
{7.655, 5.225, ILI9341_MAGENTA} // Procyon
};
const int numStars = sizeof(stars)/sizeof(stars[0]); // Total de estrellas
// Nombres de las estrellas más brillantes (solo las primeras)
const char* starNames[] = {
"Sirius", "Canopus", "Arcturus", "Vega", "Capella",
"Rigel", "Procyon", "Achernar", "Betelgeuse", "Hadar",
"Antares", "Mimosa", "Regulus", "Bellatrix", "Spica"
};
// ---------------------------------------------------------
// ESTETICA, FONDO
// ---------------------------------------------------------
struct StarPoint { int x, y; bool visible; };
const int MAX_STARS = 50; // Número máximo de estrellas de fondo
StarPoint backgroundStars[MAX_STARS]; // Arreglo para fondo dinámico
// ---------------------------------------------------------
// FUNCIONES AUXILIARES
// ---------------------------------------------------------
// Conversión de grados a radianes
float deg2rad(float deg){ return deg * PI / 180.0; }
// Conversión de radianes a grados
float rad2deg(float rad){ return rad * 180.0 / PI; }
// Conversión de coordenadas ecuatoriales (RA, DEC) a horizontales (Alt, Az)
void raDecToAltAz(float ra, float dec, float lat, float lon, float utcHour, float &alt, float &az){
ra = deg2rad(ra * 15.0); // RA en grados -> radianes
dec = deg2rad(dec);
lat = deg2rad(lat);
// Cálculo simplificado del Tiempo Sideral Local (LST)
float lst = deg2rad(fmod(100.46 + 0.985647 * 2459766 + lon + 15*utcHour, 360.0));
float ha = lst - ra; // Ángulo horario
// Fórmulas trigonométricas para altitud y acimut
alt = asin(sin(dec)*sin(lat) + cos(dec)*cos(lat)*cos(ha));
az = acos((sin(dec) - sin(alt)*sin(lat)) / (cos(alt)*cos(lat)));
// Convertir resultados a grados
alt = rad2deg(alt);
az = rad2deg(az);
// Ajuste del acimut según el signo del ángulo horario
if(sin(ha) > 0) az = 360 - az;
}
// Inicializa el fondo con estrellas aleatorias
void setupStarsBackground(){
for(int i=0;i<MAX_STARS;i++){
backgroundStars[i].x = random(0,240);
backgroundStars[i].y = random(40,240);
backgroundStars[i].visible = random(0,2);
}
}
// Actualiza el parpadeo de las estrellas del fondo
void updateStarsBackground(){
for(int i=0;i<MAX_STARS;i++){
tft.drawPixel(backgroundStars[i].x, backgroundStars[i].y, ILI9341_BLACK);
if(random(0,10)>7) backgroundStars[i].visible = !backgroundStars[i].visible;
if(backgroundStars[i].visible) tft.drawPixel(backgroundStars[i].x, backgroundStars[i].y, ILI9341_YELLOW);
}
}
// Dibuja los puntos cardinales (N, S, E, W)
void drawCardinals(){
tft.setTextColor(ILI9341_YELLOW);
tft.setTextSize(2);
tft.setCursor(115, 25); tft.print("N");
tft.setCursor(115, 220); tft.print("S");
tft.setCursor(5, 110); tft.print("W");
tft.setCursor(220, 110); tft.print("E");
}
// ---------------------------------------------------------
// CONFIGURACIÓN INICIAL
// ---------------------------------------------------------
void setup(){
tft.begin(); // Inicializa la pantalla
tft.setRotation(0); // Orientación vertical
tft.fillScreen(ILI9341_BLACK); // Limpia pantalla
gpsSerial.begin(9600); // Inicia comunicación con el GPS
setupStarsBackground(); // Genera el fondo estelar
drawCardinals(); // Muestra los puntos cardinales
}
// ---------------------------------------------------------
// BUCLE PRINCIPAL
// ---------------------------------------------------------
void loop(){
// Lee los datos del GPS
while(gpsSerial.available() > 0) gps.encode(gpsSerial.read());
// Si no hay GPS, usa valores simulados (oscilantes)
float lat = gps.location.isValid() ? gps.location.lat() : 19.38 + 0.001*sin(step*0.2);
float lon = gps.location.isValid() ? gps.location.lng() : -99.16 + 0.001*cos(step*0.2);
float utcHour = gps.time.isValid() ? gps.time.hour() + gps.time.minute()/60.0 : 18.0;
step++;
updateStarsBackground(); // Actualiza fondo
// Calcula posición y dibuja cada estrella
for(int i=0;i<numStars;i++){
Star s;
memcpy_P(&s, &stars[i], sizeof(Star)); // Lee datos desde PROGMEM
float alt, az;
raDecToAltAz(s.ra, s.dec, lat, lon, utcHour, alt, az); // Conversión de coordenadas
if(alt > 0){ // Solo dibuja si la estrella está sobre el horizonte
int x = map((int)az, 0, 360, 0, 240); // Acimut -> eje X
int y = map((int)alt, 0, 90, 240, 40); // Altitud -> eje Y
tft.fillCircle(x, y, 2, ILI9341_WHITE); // Punto base
tft.fillTriangle(x-3, y-6, x+3, y-6, x, y-12, s.color); // Triángulo brillante
if(i < 15){ // Nombres para las estrellas más brillantes
tft.setCursor(x+2, y-14);
tft.setTextColor(s.color);
tft.setTextSize(1);
tft.print(starNames[i]);
}
}
}
// Muestra datos GPS en la parte superior
tft.fillRect(0, 0, 240, 20, ILI9341_BLACK);
tft.setCursor(5, 0);
tft.setTextColor(ILI9341_CYAN);
tft.setTextSize(1);
tft.print("LAT: "); tft.print(lat,6);
tft.print(" LON: "); tft.print(lon,6);
drawCardinals(); // Redibuja puntos cardinales
delay(500); // Pausa breve
}