/*
    3-6-06
    Copyright Spark Fun Electronics© 2006
    Nathan Seidle 
    spark@sparkfun.com
    
    Multi-channel control of 12V Sign String/LEDs

    Basically we are going to read GPS time data from the Lassen iQ and display it
    on a series of very large 7-segment displays.

    I used the 4511 7-segment binary decoder coupled with the ULN2003 driver. The 
    binary decoder can't handle the 50-100mA that the LED strings will draw, but the
    ULN2003 can handle 500mA per channel! Huge. 

    I used RJ45 jacks on either end so that I could use very cheap, pre-terminated
    CAT5 cable to run power and signal to the 7-segment wall panels. I am not entirely
    sure UL would approve ;), but it'll get the job done.

    There are 6 drivers so we can control up to 6 7-segment displays. Adding more is not
    a problem, just addtional I/O boards.
    
*/
//#define Clock_8MHz
//#define Baud_9600

#define STATUS_LED      PORTE.0

#define BUTTON_UP       PORTE.1
#define BUTTON_DOWN     PORTE.2
#define BUTTON_SELECT   PORTA.4

#define OFF 0
#define ON  1

#define DRIVER1         PORTD.0
#define DRIVER2         PORTD.1
#define DRIVER3         PORTD.2
#define DRIVER4         PORTD.3
#define DRIVER5         PORTD.4
#define DRIVER6         PORTD.5

#define CONTROL1        PORTB.0
#define CONTROL2        PORTB.1
#define CONTROL3        PORTB.2
#define CONTROL4        PORTB.3

#define AM  0
#define PM  1
#define SPOT_LOCAL_HOUR 1

#define PUSHED  0

#define TRUE    1
#define FALSE   0

#include "\Global\Code\C\16F877.h" // Device hardware map
#include "\Global\Code\C\int16CXX.H" // Device interrupt definitions

//#pragma config |= 0x2902
#pragma origin 4 //Used for boot loading and interrupts

//Global Variables
//============================
uns8 RX_In; //Pointer to the next free position

//Receiver buffer
#define FIFO_SIZE   96

#pragma rambank 2
uns8 RX_Array[FIFO_SIZE]; //Receive buffer 1
//$GPGGA,185407.00,4000.0907,N,10514.2805,W,1,06,1.51,01622,M,-020,M,,*5E
//$GPVTG,000.0,T,349.4,M,000.0,N,000.0,K,A*29
#pragma rambank 0

uns8 m_seconds;
uns8 seconds;
uns16 total_seconds; //Used in VB for thresholds

uns8 satellites_in_view;
bit gps_lock;
uns8 gps_hours;
uns8 gps_minutes;
uns8 gps_seconds;
bit gps_ampm;
uns8 local_hour;
uns8 pure_gps_hours;

uns8 high_hours;
uns8 low_hours;
uns8 high_minutes;
uns8 low_minutes;
uns8 high_seconds;
uns8 low_seconds;

uns8 rt_seconds;
//============================
//End Global Variables

//Interrupt Vectors
//============================
interrupt serverX( void)
{
    int_save_registers
    char sv_FSR = FSR;  // save FSR if required

    if(RCIF) //Receive interrupt
    {
        RX_In++;
        if(RX_In == FIFO_SIZE) RX_In = 0;
        
        RX_Array[RX_In] = RCREG; //Clears interrupt flag

        CREN = 0;
        CREN = 1;
    }

    if (TMR1IF) //RTC 32kHz xtal
    {
        TMR1H = 0x80; //TMR1L = 0x00; //No need to reset the Low byte, let it run
        
        rt_seconds++; //Real Time

        TMR1IF = 0; 
    }

    FSR = sv_FSR;               // restore FSR if saved
    int_restore_registers 
}
//============================

//Function definitions
//============================
//#include "d:\Pics\code\Delay.c"   // Delays
//#include "d:\Pics\code\Stdio.c"   // Print routines

void boot_up(void);
void delay_ms(uns16 x);

void put_number(uns8 digit, uns8 channel);
void gps_parser(void);
void set_time(void);
void time_printer(void);
void loop_printer(void);
void colon_control(void);

uns8 onboard_eeread(uns8 e_address);
void onboard_eewrite(uns8 e_data, uns8 e_address);

void putc(uns8);
uns8 getc(void);
uns8 scanc(void);
uns8 bin2Hex(char x);
void printf(const char *nate, int16 my_byte);

//============================
//End Function Definitions

void main()
{
    uns8 choice;
    
    boot_up();

    colon_control(); //Un-Comment out this line if this board is controlling the colons

    //Used for testing
    //==============================
    satellites_in_view = 5;

    high_hours = 1;
    low_hours = 1;
    high_minutes = 4;
    low_minutes = 6;
    high_seconds = 3;
    low_seconds = 9;

    time_printer();

    while(1);
    loop_printer();
    //==============================
    
    while(1)
    {
        if (RX_In > 90)
        {
            gps_parser();
            
            time_printer();

            if (BUTTON_UP == 0 && BUTTON_DOWN == 0) set_time();

            STATUS_LED ^= 1;
        }
    }
  
}//End Main

//This function is meant for the colon control
//Let's just flash the colon dots 
//Or maybe just turn them on
void colon_control(void)
{
    
    while(1)
    {
        //Turn on all channels
        put_number(8, 1);
        put_number(8, 2);
        put_number(8, 3);
        put_number(8, 4);
        put_number(8, 5);
        put_number(8, 6);
        
        delay_ms(1000); //Pause a second
        
        //Turn off all channels
        put_number(10, 1);
        put_number(10, 2);
        put_number(10, 3);
        put_number(10, 4);
        put_number(10, 5);
        put_number(10, 6);
        
        delay_ms(1000);
    }
}

//This function just prints from 0 to 999,999 very fast
void loop_printer(void)
{
    uns8 x = 0, m, temp;
    uns24 display_number;
    uns24 my_byte;
    uns8 decimal_output[7];
    
    //Clear the current cells
    put_number(10, 1);
    put_number(10, 2);
    put_number(10, 3);
    put_number(10, 4);
    put_number(10, 5);
    put_number(10, 6);

    display_number = 334576;
    
    while(1)
    {
        
        display_number++;
        
        if (display_number > 999999)
        {
            display_number = 0;

            put_number(10, 1);
            put_number(10, 2);
            put_number(10, 3);
            put_number(10, 4);
            put_number(10, 5);
            put_number(10, 6);
        }
        
        my_byte = display_number;

        //Divide number by a series of 10s
        for(m = 6 ; my_byte > 0 ; m--)
        {
            temp = my_byte % (uns16)10;
            decimal_output[m] = temp;
            my_byte = my_byte / (uns16)10;               
        }
    
        for(m++ ; m < 7 ; m++)
        {
            put_number(decimal_output[m], 7 - m); 
            //putc(bin2Hex(decimal_output[m]));
        }

        //printf(" - %d\n", display_number);
        //delay_ms(5);
        STATUS_LED ^= 1;
    }

}

//Check all the numbers to make sure we are kosher, then put them out to the clock cells
//Do any kind of special effects here
void time_printer(void)
{
    bit time_error = FALSE;
    
    if (gps_hours > 12) time_error = TRUE;
    if (satellites_in_view > 10) time_error = TRUE;
    
    if (time_error == TRUE)
    {
        //Display a roving error condition on the clock
        
        uns8 num, chan;
        
        for(num = 0 ; num < 10 ; num++)
        {
            for(chan = 1 ; chan < 7 ; chan++)
            {
                put_number(10, 6);
                put_number(10, 5);
                put_number(10, 4);
                put_number(10, 3);
                put_number(10, 2);
                put_number(10, 1);
                
                put_number(num, chan);
                
                delay_ms(250);
            }
        }
    }
    else
    {
        //Hour
        put_number(high_hours, 6); //High hour on channel 6
        put_number(low_hours, 5); 
        put_number(high_minutes, 4);
        put_number(low_minutes, 3);
        put_number(high_seconds, 2);
        put_number(low_seconds, 1);

    }
    
}

//Set time allows the user to select the local offset from UTC time
//as well as select between 12 and 24 hour representation
void set_time(void)
{
    
    uns8 temp_hours;
    bit temp_ampm;
    
    printf("Setting offset: Press select to exit\n\n", 0);
    
    //Blink something for 3 seconds
//    delay_ms(3000);

    while(BUTTON_UP == PUSHED || BUTTON_DOWN == PUSHED); //Wait for user to remove their finger

    while(1)
    {
        //Convert UTC hours to local current time using local_hour
        //======================================
        temp_hours = pure_gps_hours;
        
        if(temp_hours < local_hour)
            temp_hours += 24; //add 24 and then subtract local offset
    
        temp_hours -= local_hour;
        
        if(temp_hours > 12) //Get rid of military time
        { 
            temp_hours -= 12; 
            temp_ampm = PM;
        }
        else
            temp_ampm = AM;
        //======================================
    
        printf("\n\nCurrent Hour = %d", temp_hours);
        if(temp_ampm == AM) printf(" AM",0);
        if(temp_ampm == PM) printf(" PM",0);

        //printf("  Current Offset = %d", local_hour);


        while(BUTTON_UP != PUSHED && BUTTON_DOWN != PUSHED && BUTTON_SELECT != PUSHED); //Wait for user to press button
        
        if(BUTTON_SELECT == PUSHED) break;
        if(BUTTON_UP == PUSHED) local_hour--;
        if(BUTTON_DOWN == PUSHED) local_hour++;
        
        while(BUTTON_UP == PUSHED || BUTTON_DOWN == PUSHED || BUTTON_SELECT == PUSHED); //Wait for user to release button

        if (local_hour == 255) local_hour = 23;
        if (local_hour == 24) local_hour = 0;
    }
    
    onboard_eewrite(local_hour, SPOT_LOCAL_HOUR);
    
}

//GPS Parser pulls out the lock, the number of satellites, and the current time
void gps_parser(void)
{

//$GPGGA,185407.00,4000.0907,N,10514.2805,W,1,06,1.51,01622,M,-020,M,,*5E
//$GPVTG,000.0,T,349.4,M,000.0,N,000.0,K,A*29

    //Turn off RX interrupts while parsing
    RCIE = 0;

    uns8 gps_offset, x;

    //Find the offset
    for(gps_offset = 0 ; gps_offset < FIFO_SIZE ; gps_offset++)
        if(RX_Array[gps_offset] == '$') //Look for $
            if(RX_Array[gps_offset + 5] == 'A') //We found GGA
                break;
    
    //Error check 1
    if(gps_offset == FIFO_SIZE)
    {
        printf("&", 0);
        goto EXIT;
    }

    //Convert GPS Time to Integers
    //======================================
    gps_hours = RX_Array[gps_offset + 7]; // '1'
    gps_hours -= '0'; //1
    gps_hours *= 10; //10
    
    gps_hours += RX_Array[gps_offset + 8]; // 10 + '8'
    gps_hours -= '0'; //18
    
    gps_minutes = RX_Array[gps_offset + 9]; //'3'
    gps_minutes -= '0'; //3
    gps_minutes *= 10; //30
    
    gps_minutes += RX_Array[gps_offset + 10]; //30 + '4'
    gps_minutes -= '0'; //34

    gps_seconds = RX_Array[gps_offset + 11]; //'3'
    gps_seconds -= '0'; //3
    gps_seconds *= 10; //30
    
    gps_seconds += RX_Array[gps_offset + 12]; //30 + '4'
    gps_seconds -= '0'; //34
    //======================================

    //Convert SIV to Integers
    //======================================
    satellites_in_view = RX_Array[gps_offset + 45]; //'6' Satellites
    satellites_in_view -= '0'; //6
    //======================================

    //Convert UTC hours to local current time using local_hour
    //======================================
    pure_gps_hours = gps_hours;
    
    if(gps_hours < local_hour)
        gps_hours += 24; //add 24 and then subtract local offset

    gps_hours -= local_hour;
    
    if(gps_hours > 12) //Get rid of military time
    { 
        gps_hours -= 12; 
        gps_ampm = PM;
    }
    else
        gps_ampm = AM;
    //======================================

    //Convert the integers to individual integers
    //======================================
    high_hours = gps_hours / 10;
    low_hours = gps_hours % 10;
    
    high_minutes = gps_minutes / 10;
    low_minutes = gps_minutes % 10;
    
    high_seconds = gps_seconds / 10;
    low_seconds = gps_seconds % 10;
    //======================================

    //Debug printing
    //======================================

/*
    printf("\nRaw: ", 0);
    for(x = 0 ; x < FIFO_SIZE ; x++)
        printf("%u", RX_Array[x]);
    printf("\n\n", 0);
*/
    printf("\nCurrent Time : %d:", gps_hours);
    printf("%d.", gps_minutes);
    printf("%d", gps_seconds);

    if(gps_ampm == AM) printf(" AM",0);
    if(gps_ampm == PM) printf(" PM",0);

    printf(" Sats :%d", satellites_in_view); //Satellites
    
EXIT:

    //All done, now we need to clear the RX buffer
    for(x = 0 ; x < FIFO_SIZE ; x++)
        RX_Array[x] = '0';
    
    RX_In = 0;

    CREN = 0;
    CREN = 1; //This clears the jam-up in the RX buffer

    //W = RCREG;
    RCIE = 1;
    
}

//Put a specific number onto a specific channel
void put_number(uns8 digit, uns8 channel)
{
    PORTB = digit;
    
    switch(channel)
    {
        case 1:
            DRIVER1 = 0;
            DRIVER1 = 1;
            break;
        case 2:
            DRIVER2 = 0;
            DRIVER2 = 1;
            break;
        case 3:
            DRIVER3 = 0;
            DRIVER3 = 1;
            break;
        case 4:
            DRIVER4 = 0;
            DRIVER4 = 1;
            break;
        case 5:
            DRIVER5 = 0;
            DRIVER5 = 1;
            break;
        case 6:
            DRIVER6 = 0;
            DRIVER6 = 1;
            break;
    }
    
}

//Init vars and ports
void boot_up(void)
{

    //Blank all segments
    PORTB = 0b.0011.1111;
    PORTD = 0b.0000.0000;
    PORTD = 0b.0011.1111;
    
    //Turn all ports to Digital Outputs
    ADCON1 = 0b.0000.0111;

    PORTA = 0b.0000.0000;
    TRISA = 0b.0001.0000;  //0 = Output, 1 = Input (BUTTON_SELECT on RA4)

    PORTB = 0b.0011.1111;
    TRISB = 0b.0000.0000;  //0 = Output, 1 = Input (CONTROL1-4 on RB0-3)

    PORTC = 0b.0000.0000;
    TRISC = 0b.1000.0011;  //0 = Output, 1 = Input (RX on RC7) (RTC Xtal on RC0/1)

    PORTD = 0b.0000.0000;
    TRISD = 0b.0000.0000;  //0 = Output, 1 = Input (DRIVER1-6 on RD0-5)
    PORTD = 0b.0011.1111;

    PORTE = 0b.0000.0110;
    TRISE = 0b.0000.0110;  //0 = Output, 1 = Input (Buttons on RE1/2 Status LED on RE0)
    
    //Setup the hardware UART module
    //=============================================================
    //SPBRG = 31; //20MHz for 9600 inital communication baud rate
    SPBRG = 64; //20MHz for 4800 inital communication baud rate
    
    TXSTA = 0b.0010.0000; //8-bit asych mode, *normal* speed uart enabled
    RCSTA = 0b.1001.0000; //Serial port enable, 8-bit asych continous receive mode
    //=============================================================

    local_hour = onboard_eeread(SPOT_LOCAL_HOUR);
    if(local_hour > 24)
    {
        local_hour = 2; //Default to the middle of the ocean
        onboard_eewrite(local_hour, SPOT_LOCAL_HOUR);
    }
    
    //Setup interrupts
    //=============================================================
    TMR1IF = 0;
    T1CON = 0b.0000.1111; //Enable Timer1 for the watch crystal for sleep and rt mode
    TMR1IE = 1;

    RCIE = 1; //RX Interrupt
    PEIE = 1;
    GIE = 1;
    //=============================================================
    
    printf("\rSpark Fun Electronics\r", 0);
    printf("GPS Wall Clock v01\r", 0);
}    

//Reads e_data from the onboard eeprom at e_address
//Reads e_data from the onboard eeprom at e_address
uns8 onboard_eeread(uns8 e_address)
{
    EEPGD = 0; //Read from EEPROM memory and not program memory
    EEADR = e_address; //Set the address to read
    RD = 1; //Read it

    return(EEDATA); //Read that EEPROM value
}    

//Write e_data to the onboard eeprom at e_address
void onboard_eewrite(uns8 e_data, uns8 e_address)
{
    bit temp_GIE = GIE;
    
    EEPGD = 0; //Read from EEPROM memory and not program memory
    
    EEIF = 0; //Clear the write completion intr flag
    EEADR = e_address; //Set the address
    EEDATA = e_data; //Give it the data
    WREN = 1; //Enable EE Writes
    GIE = 0; //Disable Intrs
    
    //Specific EEPROM write steps
    EECON2 = 0x55;
    EECON2 = 0xAA;
    WR = 1;
    //Specific EEPROM write steps

    while(WR == 1); //Wait for write to complete
    WREN = 0; //Disable EEPROM Writes

    GIE = temp_GIE; //Set GIE to its original state
}

//Sends nate to the Transmit Register
void putc(uns8 nate)
{
    while(TXIF == 0);
    TXREG = nate;
}

uns8 getc(void)
{
    while(RCIF == 0);
    return (RCREG);
}    

uns8 scanc(void)
{
    uns16 counter = 0;
    
    while(RCIF == 0)
    {
        counter++;
        if(counter == 1000) return 0;
    }
    
    return (RCREG);
}    

//Returns ASCII Decimal and Hex values
uns8 bin2Hex(char x)
{
   skip(x);
   #pragma return[16] = "0123456789ABCDEF"
}

//Prints a string including variables
void printf(const char *nate, int16 my_byte)
{
  
    uns8 i, k, m, temp;
    uns8 high_byte = 0, low_byte = 0;
    uns8 y, z;
    
    uns8 decimal_output[5];
    
    for(i = 0 ; ; i++)
    {
        //delay_ms(3);
        
        k = nate[i];

        if (k == '\0') 
            break;

        else if (k == '%') //Print var
        {
            i++;
            k = nate[i];

            if (k == '\0') 
                break;
            else if (k == '\\') //Print special characters
            {
                i++;
                k = nate[i];
                
                putc(k);
                

            } //End Special Characters
            else if (k == 'b') //Print Binary
            {
                for( m = 0 ; m < 8 ; m++ )
                {
                    if (my_byte.7 == 1) putc('1');
                    if (my_byte.7 == 0) putc('0');
                    if (m == 3) putc(' ');
                    
                    my_byte = my_byte << 1;
                }
            } //End Binary               
            else if (k == 'd') //Print Decimal
            {
                //Print negative sign and take 2's compliment
                
                if(my_byte < 0)
                {
                    putc('-');
                    my_byte *= -1;
                }
                
                
                if (my_byte == 0)
                    putc('0');
                else
                {
                    //Divide number by a series of 10s
                    for(m = 4 ; my_byte > 0 ; m--)
                    {
                        temp = my_byte % (uns16)10;
                        decimal_output[m] = temp;
                        my_byte = my_byte / (uns16)10;               
                    }
                
                    for(m++ ; m < 5 ; m++)
                        putc(bin2Hex(decimal_output[m]));
                }
    
            } //End Decimal
            else if (k == 'h') //Print Hex
            {
                //New trick 3-15-04
                putc('0');
                putc('x');
                
                if(my_byte > 0x00FF)
                {
                    putc(bin2Hex(my_byte.high8 >> 4));
                    putc(bin2Hex(my_byte.high8 & 0b.0000.1111));
                }

                putc(bin2Hex(my_byte.low8 >> 4));
                putc(bin2Hex(my_byte.low8 & 0b.0000.1111));

                /*high_byte.3 = my_byte.7;
                high_byte.2 = my_byte.6;
                high_byte.1 = my_byte.5;
                high_byte.0 = my_byte.4;
            
                low_byte.3 = my_byte.3;
                low_byte.2 = my_byte.2;
                low_byte.1 = my_byte.1;
                low_byte.0 = my_byte.0;
        
                putc('0');
                putc('x');
            
                putc(bin2Hex(high_byte));
                putc(bin2Hex(low_byte));*/
            } //End Hex
            else if (k == 'f') //Print Float
            {
                putc('!');
            } //End Float
            else if (k == 'u') //Print Direct Character
            {
                //All ascii characters below 20 are special and screwy characters
                //if(my_byte > 20) 
                    putc(my_byte);
            } //End Direct
                        
        } //End Special Chars           

        else
            putc(k);
    }    
}

//General short delay
void delay_ms(uns16 x)
{
    uns8 y, z;
    //Clocks out to 1.00ms per 1ms
    //9.99 ms per 10ms
    for ( ; x > 0 ; x--)
        for ( y = 0 ; y < 4 ; y++)
            for ( z = 0 ; z < 176 ; z++);
}