/*
    1-16-04
    Copyright Spark Fun Electronics 2004
    
    Uses 16F630 to drive Parallel LCD with Serial Commands.

    Thanks to Olimex for providing the original C test code for the PIC-MT-C w/ 16F873
    
    1-17-04 Started migrating 16F873A code to 16F630

    1-17-04pm Works well from user keyboard input. No busy flag checking yet.
              Ok, busy flag works.

    1-19-04 Got commands to pass through after decimal 254 is seen. The special 8-bit data
            mode command is also correctly ignored.
    
*/
#define Clock_4MHz
#define Baud_9600

#include "d:\Pics\c\16F630.h"  // device dependent interrupt definitions
#include "d:\Pics\c\int16CXX.H"

#pragma config|= 0x3194 //Internal Oscilator

//Hardware port definitions
#define		E		        PORTC.1
#define		R_W		        PORTC.0
#define		RS		        PORTA.1
#define     Serial_In_BB    PORTA.2
#define     Serial_Out_BB   PORTA.5
#define     D7              PORTC.2
#define     D6              PORTC.3
#define     D5              PORTC.4
#define     D4              PORTC.5

//Constant definitions
#define		FALSE		0
#define		TRUE		1

#define		CLR_DISP		0b.0000.0001	//Clear display

#define     CUR_HOME        0b.0000.0010    //Move cursor home and clear screen memory
#define     CUR_RIGHT       0b.0001.0100    //Move cursor one to right
#define     CUR_LEFT        0b.0001.0000    //Move cursor one to left

#define     SCROLL_RIGHT    0b.0001.1100    //Scroll entire screen right one space
#define     SCROLL_LEFT     0b.0001.1000    //Scroll entire screen left one space

#define     DISP_ON         0b.0000.1100    //Turn visible LCD on
#define     DISP_OFF        0b.0000.1000    //Turn visible LCD off

#define     UNDERLINE_ON    0b.0000.1110    //Turn on underlined cursor
#define     UNDERLINE_OFF   0b.0000.1100    //Turn off underlined cursor

#define     BLINKCUR_ON     0b.0000.1101    //Turn on blinking box cursor
#define     BLINKCUR_OFF    0b.0000.1100    //Turn off blinking box cursor

#define     DUALCUR_ON      0b.0000.1111    //Turn on blinking box and underline cursor
#define     DUALCUR_OFF     0b.0000.1100    //Turn off blinking box and underine cursor

#define     SET_CURSOR      0b.1000.0000    //SET_CURSOR + X : Sets cursor position to X

#define		ENTRY_INC		0b.0000.0110	//
#define		DD_RAM_ADDR		0b.1000.0000	//
#define		DD_RAM_ADDR2	0b.1100.0000	//
#define		CG_RAM_ADDR		0b.0100.0000	//

uns8 data_byte_in;
uns8 cursor_position;

//Interrupt Vectors
#pragma origin 4
interrupt serverX( void)
{
    int_save_registers
    char sv_FSR = FSR;  // save FSR if required

    if(INTF) //RA2 Change Interrupt
    {
        uns8 j, i;
        
        for(i = 0 ; i < 20 ; i++);
    
        for(j = 0 ; j < 7 ; j++)
        {
            data_byte_in = rr(data_byte_in);
            //data_byte_in.7 = !Serial_In_BB; //Reverse polarity for RS232 systems
            data_byte_in.7 = Serial_In_BB; //Sample the port, should be in middle of the bit
            for(i = 0 ; i < 12 ; i++);
            nop(); nop(); nop(); //Clocks out to 104us between each bit
        }
    
        data_byte_in = rr(data_byte_in);
        data_byte_in.7 = Serial_In_BB; //Sample the port, should be in middle of the bit

        //Clear INT Flag
        INTF = 0;
    }

    FSR = sv_FSR;               // restore FSR if saved
    int_restore_registers 
}

void boot_up(void);
void send_char(uns8);
void send_cmd(uns8);
void delay_ms(uns16);
void send_string(const char* incoming_string);
void delay_ms(uns16);
bit busy_flag(void);

void main( void )
{
    //Initialize LCD and PIC
    boot_up();
    
    while(1)
    {
        //Wait for incoming command to wake us up...
        sleep();
        nop(); //Executes after wake-up and before INTF runs
        
        //Check for special LCD command
        if (data_byte_in == 254)
        {
            //Wait for incoming command to wake us up...
            sleep();
            nop(); //Executes after wake-up and before INTF runs
            
            //Ignore the one command that will send the LCD into 8-bit mode
            if ( (data_byte_in >> 4) != 3 ) //If not 0b.0000.0011, then send it to LCD
                send_cmd(data_byte_in);
        }    
        else
        {     
            send_char(data_byte_in);

            cursor_position++;

            if (cursor_position == 16) //End of line 1
            {
                send_cmd(SET_CURSOR + 64);
                cursor_position = 64;
            }
            else if (cursor_position == 80) //End of line 2
            {
                send_cmd(SET_CURSOR + 0);
                cursor_position = 0;
            }
        }
            
   }


}

//Initializes the various ports and interrupts
//Also inits the LCD
void boot_up(void)
{
    //Setup Ports
    //ANSEL = 0b.0000.0000; //Disable ADC on all pins - Only on the 16F676
    CMCON = 0b.0000.0111; //Turn off comparator on RA port 

    PORTA = 0b.0000.0000;
    TRISA = 0b.0000.0100;

    PORTC = 0b.0000.0000;
    TRISC = 0b.0000.0000;   //0 = Output, 1 = Input

    //Init LCD
    RS = 0;               
    R_W = 0;

    //Tell the LCD we are using 4bit data communication
    delay_ms(1500);
    D7 = 0;
    D6 = 0;
    D5 = 1;
    D4 = 1;
    E = 1; E = 0;

    delay_ms(100);
    D7 = 0;
    D6 = 0;
    D5 = 1;
    D4 = 1;
    E = 1; E = 0;

    delay_ms(100);
    D7 = 0;
    D6 = 0;
    D5 = 1;
    D4 = 1;
    E = 1; E = 0;

    delay_ms(10);
    D7 = 0;
    D6 = 0;
    D5 = 1;
    D4 = 0;
    E = 1; E = 0;

    send_cmd(DISP_ON);
    send_cmd(CLR_DISP);
    //LCD Init Complete
    
    send_string("  SparkFun.com");
    send_cmd(SET_CURSOR + 64);
    send_string("   SerLCD v1.0");
    delay_ms(2500);
    send_cmd(CLR_DISP);
    
    cursor_position = 0;
    
    //Setup interrupts for incoming RX
    //INTEDG = 1; //Int on rising edge change - Used for direct RS232 interfaces
    INTEDG = 0; //Int on rising edge change - Used for TTL embedded systems
    INTE = 1; //Enable the RA2 Int, for incoming serial commands/characters
    GIE = 1;
    
}

//Checks and returns the busy_flag
bit busy_flag(void)
{
    bit i;
    
    TRISC = 0b.0011.1100;   //0 = Output, 1 = Input

    RS = 0;               
    R_W = 1; //Tell LCD to output status

    E = 1; 
    i = D7; //Read data bit 7 - Busy Flag
    E = 0;

    E = 1; E = 0; //Toggle E to get the second four bits of the status byte - but we don't care

    return(i);
    
}

//Sends an ASCII character to the LCD
void send_char(uns8 c)
{
    while(busy_flag());

    R_W = 0; //set LCD to write
    RS = 1; //set LCD to data mode
    
    //Control lines will always be output
    //And here the data lines are output
    TRISC = 0b.0000.0000;   //0 = Output, 1 = Input

    D7 = c.7;
    D6 = c.6;
    D5 = c.5;
    D4 = c.4;
    E = 1; E = 0; //Toggle the Enable Pin

    D7 = c.3;
    D6 = c.2;
    D5 = c.1;
    D4 = c.0;
    E = 1; E = 0;
}

//Sends an LCD command
void send_cmd(uns8 d)
{
    while(busy_flag());

    R_W = 0; //set LCD to write
    RS = 0; //set LCD to data mode

    //Control lines will always be output
    //And here the data lines are output
    TRISC = 0b.0000.0000;   //0 = Output, 1 = Input

    D7 = d.7;
    D6 = d.6;
    D5 = d.5;
    D4 = d.4;
    E = 1; E = 0;

    D7 = d.3;
    D6 = d.2;
    D5 = d.1;
    D4 = d.0;
    E = 1; E = 0;
}

//Sends a given string to the LCD. Will start printing from
//current cursor position.
void send_string(const char *incoming_string)
{
    uns8 x;
    
    for( x = 0 ; incoming_string[x] != '\0' ; x++ )
        send_char(incoming_string[x]);
            
}

//General short delay
void delay_ms(uns16 x)
{
    //Clocks out at 1002us per 1ms
    int y;
    for ( ; x > 0 ; x--)
        for ( y = 0 ; y < 108 ; y++);
}