                                          // PIC16F887 + mikroC PRO for PIC
// CAT28C16A EEPROM Programmer
// Real PCB mapping version
// 16 MHz crystal, HS oscillator
// UART1: RC6 TX, RC7 RX
// Baud: 9600
// Author: tomcii
//
// Protocol:
// P              -> OK EEPROM-PGM HW V2
// S              -> OK STATUS EEPROM-HW V2
// R 0x0000       -> D 0000 XX
// W 0x0000 0xAA  -> OK 0000 AA / ERR TIMEOUT
// D              -> Dump block Read
// E              -> Block Erase - Fast Erase Function
// O              -> LEDS OFF
// L              -> LEDS ON
// T              -> LED TEST

#define EEPROM_SIZE 2048
#define RXBUF_SZ    48

// ===================== REAL PCB MAPPING =====================
//
// DATA bus:
// D0..D5 -> RA0..RA5
// D6..D7 -> RB0..RB1
//
// ADDRESS bus:
// A0..A7  -> RD0..RD7
// A8..A10 -> RE0..RE2
//
// EEPROM control:
// /WE -> RB2
// /OE -> RB4
// /CE -> RB5
//
// LEDs:
// WRITE -> RC0
// READ  -> RC1
// ERROR -> RC2
// ABORT -> RC3
// CMD   -> RC4
// STBY  -> RC5

// Address bus
#define ADDR_LO_PORT     PORTD
#define ADDR_LO_TRIS     TRISD

#define ADDR_HI_PORT     PORTE
#define ADDR_HI_TRIS     TRISE

// EEPROM control signals, active LOW
#define WE_LAT           RB2_bit
#define OE_LAT           RB4_bit
#define CE_LAT           RB5_bit

#define WE_TRIS          TRISB2_bit
#define OE_TRIS          TRISB4_bit
#define CE_TRIS          TRISB5_bit

// LEDs, active HIGH
#define LED_WRITE_LAT    RC0_bit
#define LED_READ_LAT     RC1_bit
#define LED_ERR_LAT      RC2_bit
#define LED_ABORT_LAT    RC3_bit
#define LED_CMD_LAT      RC4_bit
#define LED_STBY_LAT     RC5_bit

#define LED_WRITE_TRIS   TRISC0_bit
#define LED_READ_TRIS    TRISC1_bit
#define LED_ERR_TRIS     TRISC2_bit
#define LED_ABORT_TRIS   TRISC3_bit
#define LED_CMD_TRIS     TRISC4_bit
#define LED_STBY_TRIS    TRISC5_bit

#define LED_ON_LEVEL     1
#define LED_OFF_LEVEL    0

// UART:
// RC6 = TX
// RC7 = RX

char rxbuf[RXBUF_SZ];
unsigned short rxlen = 0;

// ===================== LED HELPERS =====================

void leds_all_off(void)
{
    LED_WRITE_LAT = LED_OFF_LEVEL;
    LED_READ_LAT  = LED_OFF_LEVEL;
    LED_ERR_LAT   = LED_OFF_LEVEL;
    LED_ABORT_LAT = LED_OFF_LEVEL;
    LED_CMD_LAT   = LED_OFF_LEVEL;
    LED_STBY_LAT  = LED_OFF_LEVEL;
}

void leds_all_on(void)
{
    LED_WRITE_LAT = LED_ON_LEVEL;
    LED_READ_LAT  = LED_ON_LEVEL;
    LED_ERR_LAT   = LED_ON_LEVEL;
    LED_ABORT_LAT = LED_ON_LEVEL;
    LED_CMD_LAT   = LED_ON_LEVEL;
    LED_STBY_LAT  = LED_ON_LEVEL;
}

void led_error_pulse(void)
{
    LED_ERR_LAT = LED_ON_LEVEL;
    Delay_ms(80);
    LED_ERR_LAT = LED_OFF_LEVEL;
}

void command_begin(void)
{
    LED_STBY_LAT = LED_OFF_LEVEL;
    LED_CMD_LAT = LED_ON_LEVEL;
}

void command_end(void)
{
    LED_CMD_LAT = LED_OFF_LEVEL;
    LED_WRITE_LAT = LED_OFF_LEVEL;
    LED_READ_LAT = LED_OFF_LEVEL;
    LED_ERR_LAT = LED_OFF_LEVEL;
    LED_ABORT_LAT = LED_OFF_LEVEL;
    LED_STBY_LAT = LED_ON_LEVEL;
}

// ===================== BASIC UTILITIES =====================

void delay_us_safe(unsigned int us)
{
    while (us--)
        Delay_us(1);
}

void delay_ms_safe(unsigned int ms)
{
    while (ms--)
        Delay_ms(1);
}

void uart_puts(const char *s)
{
    while (*s)
        UART1_Write(*s++);
}

void uart_put_hex4(unsigned char v)
{
    const char *hex = "0123456789ABCDEF";
    UART1_Write(hex[v & 0x0F]);
}

void uart_put_hex8(unsigned char v)
{
    uart_put_hex4(v >> 4);
    uart_put_hex4(v);
}

void uart_put_hex16(unsigned int v)
{
    uart_put_hex8((unsigned char)((v >> 8) & 0xFF));
    uart_put_hex8((unsigned char)(v & 0xFF));
}

char is_space(char c)
{
    return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

unsigned char hex_digit_value(char c, unsigned char *ok)
{
    if (c >= '0' && c <= '9') {
        *ok = 1;
        return c - '0';
    }

    if (c >= 'a' && c <= 'f') {
        *ok = 1;
        return c - 'a' + 10;
    }

    if (c >= 'A' && c <= 'F') {
        *ok = 1;
        return c - 'A' + 10;
    }

    *ok = 0;
    return 0;
}

// Accepts:
// 123
// 0x123
// 000A
// AA
unsigned long parse_number(char **p)
{
    unsigned long val = 0;
    char *s = *p;
    char *start;
    unsigned char isHex = 0;
    unsigned char ok;
    unsigned char d;

    while (is_space(*s))
        s++;

    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
        isHex = 1;
        s += 2;
    } else {
        start = s;

        while ((*s >= '0' && *s <= '9') ||
               (*s >= 'a' && *s <= 'f') ||
               (*s >= 'A' && *s <= 'F')) {

            if ((*s >= 'a' && *s <= 'f') ||
                (*s >= 'A' && *s <= 'F')) {
                isHex = 1;
            }

            s++;
        }

        s = start;
    }

    if (isHex) {
        while (1) {
            d = hex_digit_value(*s, &ok);

            if (!ok)
                break;

            val = (val << 4) | d;
            s++;
        }
    } else {
        while (*s >= '0' && *s <= '9') {
            val = val * 10 + (*s - '0');
            s++;
        }
    }

    *p = s;
    return val;
}

unsigned char parse_byte(char **p)
{
    return (unsigned char)(parse_number(p) & 0xFF);
}


// ===================== EEPROM BUS LOW LEVEL =====================

void eeprom_idle(void)
{
    // Inactive state
    CE_LAT = 1;
    OE_LAT = 1;
    WE_LAT = 1;

    // Release data bus
    // D0..D5 -> RA0..RA5
    // D6..D7 -> RB0..RB1
    TRISA0_bit = 1;
    TRISA1_bit = 1;
    TRISA2_bit = 1;
    TRISA3_bit = 1;
    TRISA4_bit = 1;
    TRISA5_bit = 1;

    TRISB0_bit = 1;
    TRISB1_bit = 1;
}

void data_dir_input(void)
{
    TRISA0_bit = 1;
    TRISA1_bit = 1;
    TRISA2_bit = 1;
    TRISA3_bit = 1;
    TRISA4_bit = 1;
    TRISA5_bit = 1;

    TRISB0_bit = 1;
    TRISB1_bit = 1;
}

void data_dir_output(void)
{
    TRISA0_bit = 0;
    TRISA1_bit = 0;
    TRISA2_bit = 0;
    TRISA3_bit = 0;
    TRISA4_bit = 0;
    TRISA5_bit = 0;

    TRISB0_bit = 0;
    TRISB1_bit = 0;
}

void data_put(unsigned char v)
{
    RA0_bit = (v >> 0) & 1;
    RA1_bit = (v >> 1) & 1;
    RA2_bit = (v >> 2) & 1;
    RA3_bit = (v >> 3) & 1;
    RA4_bit = (v >> 4) & 1;
    RA5_bit = (v >> 5) & 1;

    RB0_bit = (v >> 6) & 1;
    RB1_bit = (v >> 7) & 1;
}

unsigned char data_get(void)
{
    unsigned char v;

    v = 0;

    if (RA0_bit) v |= 0x01;
    if (RA1_bit) v |= 0x02;
    if (RA2_bit) v |= 0x04;
    if (RA3_bit) v |= 0x08;
    if (RA4_bit) v |= 0x10;
    if (RA5_bit) v |= 0x20;

    if (RB0_bit) v |= 0x40;
    if (RB1_bit) v |= 0x80;

    return v;
}

void addr_set(unsigned int a)
{
    unsigned char hi;

    // A0..A7 -> RD0..RD7
    ADDR_LO_PORT = (unsigned char)(a & 0xFF);

    // A8..A10 -> RE0..RE2
    hi = ADDR_HI_PORT & 0xF8;
    hi |= (unsigned char)((a >> 8) & 0x07);
    ADDR_HI_PORT = hi;
}

unsigned char eep_read_byte(unsigned int a)
{
    unsigned char d;


    data_dir_input();
    addr_set(a);

    WE_LAT = 1;
    CE_LAT = 0;
    OE_LAT = 0;

    delay_us_safe(2);

    d = data_get();

    OE_LAT = 1;
    CE_LAT = 1;

    return d;
}

char eep_write_byte(unsigned int a, unsigned char v)
{
    unsigned int t;
    unsigned char d;

    LED_WRITE_LAT = LED_ON_LEVEL;

    // Safe/inactive before write
    OE_LAT = 1;
    WE_LAT = 1;
    CE_LAT = 1;

    addr_set(a);
    data_dir_output();
    data_put(v);

    delay_us_safe(2);

    CE_LAT = 0;

    // WE low pulse
    WE_LAT = 0;
    delay_us_safe(5);
    WE_LAT = 1;

    CE_LAT = 1;

    // Release bus before polling
    data_dir_input();

    // Poll until byte reads back
    for (t = 0; t < 25; t++) {
        d = eep_read_byte(a);

        if (d == v) {
            LED_WRITE_LAT = LED_OFF_LEVEL;
            return 1;
        }

        delay_ms_safe(1);
    }

    LED_WRITE_LAT = LED_OFF_LEVEL;
    return 0;
}

// ===================== COMMAND PROCESSOR =====================

void reply_err_cmd(void)
{
    uart_puts("ERR CMD\r\n");
    led_error_pulse();
}

void reply_err_addr(void)
{
    uart_puts("ERR ADDR\r\n");
    led_error_pulse();
}

void process_line(char *line)
{
    char *p = line;
    unsigned int address;
    unsigned char value;
    unsigned char readValue;
    unsigned char verifyValue;
    char ok;

    command_begin();

    // Skip garbage until known command.
    while (*p &&
           *p != 'P' &&
           *p != 'S' &&
           *p != 'R' &&
           *p != 'W' &&
           *p != 'D' &&
           *p != 'E' &&
           *p != 'O' &&
           *p != 'L' &&
           *p != 'T') {
        p++;
    }

    if (*p == 'P') {
        uart_puts("PING: OK EEPROM-PGM HW V2 tomcii\r\n");
    }

    else if (*p == 'S') {
        uart_puts("STATUS:OK EEPROM-HW V2 tomcii SIZE=2048 BAUD=9600\r\n");
    }

    else if (*p == 'R') {
        p++;
        address = (unsigned int)parse_number(&p);

        if (address >= EEPROM_SIZE) {
            reply_err_addr();
        } else {
            LED_READ_LAT = LED_ON_LEVEL;
            readValue = eep_read_byte(address);

            uart_puts("D ");
            uart_put_hex16(address);
            UART1_Write(' ');
            uart_put_hex8(readValue);
            uart_puts("\r\n");
        }
    }

    else if (*p == 'D') {
        unsigned int length;
        unsigned int i;

        p++;
        address = (unsigned int)parse_number(&p);
        length = (unsigned int)parse_number(&p);

        if (length == 0 || length > 64) {
            uart_puts("ERR LEN\r\n");
            led_error_pulse();
        }
        else if (address >= EEPROM_SIZE || ((unsigned long)address + length) > EEPROM_SIZE) {
            reply_err_addr();
        }
        else {
            LED_READ_LAT = LED_ON_LEVEL;

            uart_puts("B ");
            uart_put_hex16(address);
            UART1_Write(' ');
            uart_put_hex8((unsigned char)length);
            UART1_Write(' ');

            for (i = 0; i < length; i++) {
                readValue = eep_read_byte(address + i);
                uart_put_hex8(readValue);

                if (i + 1 < length)
                    UART1_Write(' ');
            }

            uart_puts("\r\n");
        }
    }


    else if (*p == 'W') {
        p++;
        address = (unsigned int)parse_number(&p);
        value = parse_byte(&p);

        if (address >= EEPROM_SIZE) {
            reply_err_addr();
        } else {
            ok = eep_write_byte(address, value);

            if (ok) {
                verifyValue = eep_read_byte(address);

                uart_puts("OK ");
                uart_put_hex16(address);
                UART1_Write(' ');
                uart_put_hex8(verifyValue);
                uart_puts("\r\n");
            } else {
                uart_puts("ERR TIMEOUT\r\n");
                led_error_pulse();
            }
        }
    }
    
    else if (*p == 'E') {
        unsigned int i;
        char eraseOk;

        eraseOk = 1;
        LED_WRITE_LAT = LED_ON_LEVEL;

        for (i = 0; i < EEPROM_SIZE; i++) {
            if (!eep_write_byte(i, 0xFF)) {
                eraseOk = 0;
                break;
            }

            // Progress every 256 bytes
            if (((i + 1) & 0x00FF) == 0) {
                uart_puts("P ");
                uart_put_hex16(i + 1);
                uart_puts("\r\n");
            }
        }

        LED_WRITE_LAT = LED_OFF_LEVEL;

        if (eraseOk) {
            uart_puts("OK ERASE\r\n");
        } else {
            uart_puts("ERR ERASE ");
            uart_put_hex16(i);
            uart_puts("\r\n");
            led_error_pulse();
        }
    }


    else if (*p == 'O') {
        leds_all_off();
        uart_puts("OK LEDS OFF\r\n");
    }

    else if (*p == 'L') {
        leds_all_on();
        uart_puts("OK LEDS ON\r\n");
    }

    else if (*p == 'T') {
        leds_all_off();

        LED_WRITE_LAT = LED_ON_LEVEL;
        Delay_ms(300);
        LED_WRITE_LAT = LED_OFF_LEVEL;

        LED_READ_LAT = LED_ON_LEVEL;
        Delay_ms(300);
        LED_READ_LAT = LED_OFF_LEVEL;

        LED_ERR_LAT = LED_ON_LEVEL;
        Delay_ms(300);
        LED_ERR_LAT = LED_OFF_LEVEL;

        LED_ABORT_LAT = LED_ON_LEVEL;
        Delay_ms(300);
        LED_ABORT_LAT = LED_OFF_LEVEL;

        LED_CMD_LAT = LED_ON_LEVEL;
        Delay_ms(300);
        LED_CMD_LAT = LED_OFF_LEVEL;

        LED_STBY_LAT = LED_ON_LEVEL;
        Delay_ms(300);
        LED_STBY_LAT = LED_OFF_LEVEL;

        uart_puts("OK LED TEST\r\n");
    }

    else {
        reply_err_cmd();
    }

    eeprom_idle();
    command_end();
}

// ===================== INIT =====================

void gpio_init(void)
{
    ANSEL = 0x00;
    ANSELH = 0x00;

    ADCON0.ADON = 0;

    // In case of mikroC/PIC16F887 Comparator disable
    C1ON_bit = 0;
    C2ON_bit = 0;

    PORTA = 0x00;
    PORTB = 0x00;
    PORTC = 0x00;
    PORTD = 0x00;
    PORTE = 0x00;

    // Address bus
    ADDR_LO_TRIS = 0x00;
    ADDR_LO_PORT = 0x00;

    ADDR_HI_TRIS = 0x00;
    ADDR_HI_PORT = 0x00;

    // EEPROM control lines
    WE_TRIS = 0;
    OE_TRIS = 0;
    CE_TRIS = 0;

    // Data bus safe default
    data_dir_input();

    // LED pins
    LED_WRITE_TRIS = 0;
    LED_READ_TRIS = 0;
    LED_ERR_TRIS = 0;
    LED_ABORT_TRIS = 0;
    LED_CMD_TRIS = 0;
    LED_STBY_TRIS = 0;

    leds_all_off();

    // UART pins
    TRISC6_bit = 0;
    TRISC7_bit = 1;

    eeprom_idle();

    LED_STBY_LAT = LED_ON_LEVEL;
}

// ===================== MAIN =====================

void main()
{
    gpio_init();

    UART1_Init(9600);
    Delay_ms(300);

    // Clear possible UART garbage after reset.
    while (UART1_Data_Ready()) {
        UART1_Read();
    }

    rxlen = 0;

    uart_puts("EEPROM-PGM HW READY V2\r\n");

    while (1) {
        if (UART1_Data_Ready()) {
            char c = UART1_Read();

            if (c == '\r' || c == '\n') {
                if (rxlen > 0) {
                    rxbuf[rxlen] = 0;
                    process_line(rxbuf);
                    rxlen = 0;
                }
            }
            else {
                // Accept only printable ASCII.
                if (c >= 32 && c <= 126) {
                    if (rxlen < RXBUF_SZ - 1) {
                        rxbuf[rxlen++] = c;
                    }
                    else {
                        rxlen = 0;
                        uart_puts("ERR LONG\r\n");
                        led_error_pulse();
                    }
                }
            }
        }
    }
}