// Compilar con:
// sdcc --use-non-free -mpic16 -p18f4550 --use-crt=crt0.o -D_XTAL_FREQ=40000000 EJERC_07.c

#include <pic18f4550.h>
#include <stdio.h>
#include <string.h>

#pragma config FOSC = HSPLL_HS, PLLDIV = 5, CPUDIV = OSC1_PLL2, USBDIV = 2
#pragma config PWRT = OFF, BOR = OFF, BORV = 3, VREGEN = OFF
#pragma config STVREN = ON, LVP = OFF, ICPRT = OFF, XINST = OFF, WDT = OFF


#define EEPROM_ADDR 0xA0  // 24C512 con A0=A1=A2=GND (0b10100000 = 0xA0) 
#define MSG_LEN 32

const char msg1[] = "\r\nSistema iniciado. Envie 1, 2 o 3 para probar.\r\n";
const char msg2[] = "Probando conexion I2C con EEPROM...\n";
const char msg3[] = "Recibido valor 3\n";
const char msg_unknown[] = "Valor no reconocido\n";


// Prototipos
void init_uart(void);
void putch(char c);
char getch(void);
void i2c_init(void);
void i2c_start(void);
void i2c_stop(void);
unsigned char i2c_write(unsigned char data);
unsigned char i2c_read(unsigned char ack);
void eeprom_read(unsigned int addr, char *buffer, unsigned char length);
void eeprom_write(unsigned int addr, char *buffer, unsigned char length);
void delay_ms(unsigned int ms);
unsigned char i2c_ping(void);
void init_eeprom_data(void);
unsigned char is_eeprom_empty(unsigned int addr);
void send_serial(const char *str);
char receive_serial(void);


// Direcciones
#define ADDR_MSG1 0x0000
#define ADDR_MSG2 0x0020
#define ADDR_MSG3 0x0040

void main(void) {
    char comando;
    char mensaje[MSG_LEN + 1];
    
    init_uart();
    i2c_init();
    
    delay_ms(500);
    send_serial(msg1);
    
    // Prueba de conexión I2C
    send_serial(msg2);
    if(i2c_ping()) {
        send_serial("OK\r\n");
    } else {
        send_serial("FALLO!\r\n");
        send_serial("Verifique:\r\n");
        send_serial("- Conexiones SCL(RB0) y SDA(RB1)\r\n");
        send_serial("- Resistores pull-up (4.7kΩ) en SCL/SDA\r\n");
        send_serial("- A0,A1,A2 en GND\r\n");
        send_serial("- Alimentacion 5V\r\n");
        while(1);
    }

    // Inicializar datos en EEPROM si está vacía
    init_eeprom_data();

    while(1) {
        comando = getch();
        putch(comando); // Eco
        send_serial("\r\n");
        
        char debug_msg[50];
        strcpy(debug_msg, "\r\nCaracter recibido: 0x");
        char hex[3];
        hex[0] = "0123456789ABCDEF"[(comando >> 4) & 0x0F];
        hex[1] = "0123456789ABCDEF"[comando & 0x0F];
        hex[2] = '\0';
        strcat(debug_msg, hex);
        strcat(debug_msg, "\r\n");
        send_serial(debug_msg);
        
        switch(comando) {
            case '1':
                eeprom_read(ADDR_MSG1, mensaje, MSG_LEN);
                break;
            case '2':
                eeprom_read(ADDR_MSG2, mensaje, MSG_LEN);
                break;
            case '3':
                eeprom_read(ADDR_MSG3, mensaje, MSG_LEN);
                break;
            default:
                strcpy(mensaje, "Comando no valido");
                break;
        }
        
        char output[MSG_LEN + 20];
        strcpy(output, "Mensaje leido: ");
        strcat(output, mensaje);
        strcat(output, "\r\n\r\n");
        send_serial(output);
    }
}

// Función para probar conexión I2C
unsigned char i2c_ping(void) {
    unsigned char ack;
    i2c_start();
    ack = i2c_write(EEPROM_ADDR); // Intenta escribir
    i2c_stop();
    return ack; // 1=exito, 0=fallo
}

// Verifica si la EEPROM está vacía en una dirección específica
unsigned char is_eeprom_empty(unsigned int addr) {
    char buffer[MSG_LEN + 1];
    eeprom_read(addr, buffer, MSG_LEN);
    
    // Verifica si todos los bytes son 0xFF (valor por defecto de EEPROM vacía)
    for(unsigned char i = 0; i < MSG_LEN; i++) {
        if(buffer[i] != 0xFF) {
            return 0; // No está vacía
        }
    }
    return 1; // Está vacía
}

// Inicializa los datos en la EEPROM si es necesario
void init_eeprom_data(void) {
    send_serial("Verificando datos en EEPROM...\r\n");
    
    if(is_eeprom_empty(ADDR_MSG1)) {
        send_serial("Inicializando mensajes en EEPROM...\r\n");
        char msg1[] = "Enviando mensaje 1";
        char msg2[] = "Enviando mensaje 2";
        char msg3[] = "Enviando mensaje 3";
        
        eeprom_write(ADDR_MSG1, msg1, strlen(msg1)+1);
        eeprom_write(ADDR_MSG2, msg2, strlen(msg2)+1);
        eeprom_write(ADDR_MSG3, msg3, strlen(msg3)+1);
        
        send_serial("Mensajes inicializados correctamente.\r\n");
    } else {
        send_serial("EEPROM ya contiene datos, no se inicializa.\r\n");
    }
}

// Escritura en EEPROM
void eeprom_write(unsigned int addr, char *buffer, unsigned char length) {
    unsigned char high_addr = (addr >> 8) & 0xFF;
    unsigned char low_addr = addr & 0xFF;
    unsigned char i;
    
    for(i = 0; i < length; i++) {
        // Intenta la operación de escritura
        i2c_start();
        if(!i2c_write(EEPROM_ADDR)) {
            i2c_stop();
            return;
        }
        
        if(!i2c_write(high_addr)) {
            i2c_stop();
            return;
        }
        
        if(!i2c_write(low_addr)) {
            i2c_stop();
            return;
        }
        
        if(!i2c_write(buffer[i])) {
            i2c_stop();
            return;
        }
        
        i2c_stop();
        
        // Espera a que termine la escritura (tiempo tWR)
        delay_ms(5);
        
        // Incrementa la dirección para el próximo byte
        low_addr++;
        if(low_addr == 0) {
            high_addr++;
        }
    }
}

// Configurar UART a 9600 baudios (para 40 MHz)
void init_uart(void) {
    TRISCbits.TRISC6 = 0;   // TX (RC6) como salida
    TRISCbits.TRISC7 = 1;   // RX (RC7) como entrada
    
    SPBRG = 64;             // 9600 baudios @ 40 MHz
    TXSTAbits.BRGH = 1;     // Alta velocidad
    TXSTAbits.SYNC = 0;     // Modo asíncrono
    RCSTAbits.SPEN = 1;     // Habilitar puerto serial
    TXSTAbits.TXEN = 1;     // Habilitar transmisión
    RCSTAbits.CREN = 1;     // Habilitar recepción continua

// Esperar a que se estabilice
    delay_ms(100);
}    

void putch(char c) {
    while(!PIR1bits.TXIF); // Esperar hasta que el buffer de transmisión esté vacío
    TXREG = c;
}

char getch(void) {
    while(!PIR1bits.RCIF); // Esperar hasta que llegue un dato
    return RCREG;
}

// I2C
void i2c_init(void) {
    SSPCON1 = 0b00101000; // I2C Master, clock = FOSC/(4*(SSPADD+1))
    SSPCON2 = 0;
    SSPADD = ((_XTAL_FREQ/4)/100000)-1; // 100kHz
    SSPSTAT = 0;
    TRISBbits.TRISB0 = 1; // SCL (RB0) como entrada
    TRISBbits.TRISB1 = 1; // SDA (RB1) como entrada
}

void i2c_start(void) {
    SSPCON2bits.SEN = 1;
    while(SSPCON2bits.SEN);
}

void i2c_stop(void) {
    SSPCON2bits.PEN = 1;
    while(SSPCON2bits.PEN);
}

unsigned char i2c_write(unsigned char data) {
    SSPBUF = data;
    while(!PIR1bits.SSPIF);
    PIR1bits.SSPIF = 0;
    return !SSPCON2bits.ACKSTAT; // Retorna 1 si recibió ACK
}

unsigned char i2c_read(unsigned char ack) {
    SSPCON2bits.RCEN = 1;
    while(!SSPSTATbits.BF);
    unsigned char data = SSPBUF;
    SSPCON2bits.ACKDT = !ack;
    SSPCON2bits.ACKEN = 1;
    while(SSPCON2bits.ACKEN);
    return data;
}

// Lectura EEPROM mejorada con manejo de errores
void eeprom_read(unsigned int addr, char *buffer, unsigned char length) {
    unsigned char high_addr = (addr >> 8) & 0xFF;
    unsigned char low_addr = addr & 0xFF;
    unsigned char i;
    
    // Intenta la operación de lectura
    i2c_start();
    if(!i2c_write(EEPROM_ADDR)) {
        i2c_stop();
        strcpy(buffer, "Error: No ACK en direccion EEPROM");
        return;
    }
    
    
    if(!i2c_write(high_addr)) {
        i2c_stop();
        strcpy(buffer, "Error: No ACK en addr alta");
        return;
    }
    
    if(!i2c_write(low_addr)) {
        i2c_stop();
        strcpy(buffer, "Error: No ACK en addr baja");
        return;
    }
    
    i2c_start();
    if(!i2c_write(EEPROM_ADDR | 0x01)) {
        i2c_stop();
        strcpy(buffer, "Error: No ACK en modo lectura");
        return;
    }
    
    for(i = 0; i < length-1; i++) {
        buffer[i] = i2c_read(1);
    }
    buffer[i] = i2c_read(0);
    i2c_stop();
    buffer[length] = '\0';
      
    
    
}

void delay_ms(unsigned int ms) {
    unsigned int i, j;
    for(i = 0; i < ms; i++)
        for(j = 0; j < 1000; j++);
}

// Enviar cadena por UART
void send_serial(const char *str) {
    while (*str) {
        while (!TXSTAbits.TRMT);
        TXREG = *str++;
    }
}

// Recibir carácter por UART (retorna 0 si no hay dato)
char receive_serial(void) {
    if (PIR1bits.RCIF) {        // Si hay dato recibido
        if (RCSTAbits.OERR) {    // Si hay overrun error
            RCSTAbits.CREN = 0;  // Limpiar error
            RCSTAbits.CREN = 1;  // Rehabilitar recepción
        }
        return RCREG;           // Retornar carácter recibido
    }
    return 0;                   // Retornar 0 si no hay dato
}
