#include <xc.h>
#include <stdint.h>
#include "config_bits.h"

void uart_initialize(void);
void uart_transmit(uint8_t *c);
void uart_receive(uint8_t *c);
void __interrupt() high_isr(void);

uint8_t data_rec;       // received data storage variable
uint8_t nack_flag = 0;  // used to identify error routine / normal operation routine
uint8_t ack = 0x06;     // acknowledgement code  
uint8_t nack = 0x15;    // negative acknowledgement code


// INITIALIZE UART REGISTERS
void uart_initialize(void){
    
    // RX and TX inputs port initialization
    TRISCbits.TRISC6 = 1;
    TRISCbits.TRISC7 = 1;
    
    SPBRG = 25;            // SPBRG = ((F_osc/BaudRate)/64)-1 => at F_osc = 4MHz, BaudRate = 2400, low speed
    TXSTAbits.BRGH = 0;    // 8-bit data mode setting for transmitter/receiver
    TXSTAbits.SYNC = 0;    // asynchronous mode
    RCSTAbits.SPEN = 1;    // RX and TX set as serial port pins
    TXSTAbits.TXEN = 1;    // transmitter enable
    RCSTAbits.CREN = 1;    // receiver enable
    
    INTCONbits.GIEH = 1;   // must be set for HP interrupt to be generated
    INTCONbits.GIEL = 1;   // must be set for LP interrupt to be generated
    PIE1bits.RCIE = 1;     // enable USART receive interrupt
}

// UART TRANSMISSION FUNCTION
void uart_transmit(uint8_t *tran){
    TXREG = *tran;
    while(TXSTAbits.TRMT == 0 && nack_flag == 0);   // before proceeding, wait for transmission to end
                                                    // if there was error (nack_flag = 1), don't wait for it
}

// UART RECEPTION FUNCTION
void uart_receive(uint8_t *rec){
    
    // MAIN ERROR - overrun (if read takes too long)
    if(RCSTAbits.OERR){
        LATCbits.LATC0 = 1;     // error indication set (blue LED)
        RCSTAbits.CREN = 0;     // to clear error, reception module must be reset
        RCSTAbits.CREN = 1;
        RCREG;                  // dummy read - part of module reset
        RCREG;
        
        nack_flag = 1;           // end "uart_transmit" immediately after loading data into TXREG and *bad-data* verification in ISR
        uart_transmit(&nack);   // send NACK
        // after it sends NACK, it must go back to ISR and exit it ! BEFORE ! new ISR is requested
        // (because data from transmitter is being re-transmitted)
        // because ack_flag = 1, uart_transmit is exited immediately after load instruction
        // and program execution ends up well in main() well before new ISR is requested
    }    
    // MINOR ERROR - framing (noise on the line or baud rate incompatibility)
    else if(RCSTAbits.FERR){
        LATCbits.LATC1 = 1;     // error indication set (green LED)
        RCREG;                  // dummy read - part of module reset 
        
        nack_flag = 1;           // end "uart_transmit" immediately after loading data into TXREG and *bad-data* verification in ISR
        uart_transmit(&nack);   // send NACK
    }
    // NORMAL OPERATION
    else{
        LATCbits.LATC0 = 0;     // error indication LED reset
        LATCbits.LATC1 = 0;
        *rec = RCREG;             // store received data to "recc"
        
        nack_flag = 1;           // end "uart_transmit" immediately after loading data into TXREG
        uart_transmit(&ack);    // send ACK
        nack_flag = 0;           // *good-data* verification in ISR
    }
    
}

// ISR WHEN REQUEST FOR RECEPTION HAPPENS
void __interrupt() high_isr(void){
    INTCONbits.GIEH = 0;
    
    if(PIR1bits.RCIF){
        uart_receive(&data_rec);    // acquire data
        
        if(nack_flag == 0) PORTD = data_rec; // output data
        // no data is written into PORTD (old data remains), if nack_flag = 1; it awaits for new data
    }
    
    INTCONbits.GIEH = 1;
}

void main(void) {
    
    TRISC = 0;
    TRISD = 0;
    LATC = 0;
    LATD = 0;
    
    uart_initialize();
    
    while(1){
        // program loop as it waits for ISR; after ISR it ends up here 
    }
    
    return;
}
