// MANUAL RACE START CHECKS
// 0 = no checks,    1 = finish line check only,    2 = start gate and finish line check
#define START_CHECKS     2

/***************************************************************************
AGP03.C

This program controls an Awana Grad Prix racetrack.
Times out at 9.999 seconds.
Uses a start switch (local N.O. pushbutton)
and a remote gate switch (N.C.)
and a remote gate actuator.
With serial communication

Elapsed time is 1mS interrupt driven.
Serial communication is interrupt driven, 9600-8-E-1.

WORKING CODE, RACE VERSION!!!

                         +5                             +5
                          |                              |
                         14                              2
                      ----------                     ----------
 -Start---ProgM----4-|          |-10-------------11-|DB4 Vdd   |
 -StopR-----------17-|          |-11-------------12-|DB5       |
 -StopB-----------18-|          |-12----ProgC----13-|DB6       |
 -StopG------------1-|  16F628  |-13----ProgD----14-|DB7     Vo| 3----20K pot
 -StopY------------2-|          |-9---------------6-|EN  LCD   |
 -RemSw------------3-|          |-6---------------4-|RS        |
        6MHz XTAL-16-|          |-7--Rx             |          |
              TAL-15-|          |-8--Tx             | RW   Vss |
                      ----------                     ----------
                           5                           1   5
                           |------------ProgG          |   |
                          Gnd                         Gnd Gnd

**************************************************************************/

#include < 16F628.h >
#include < jonsinc.h >
#fuses HS, NOPROTECT, PUT, NOWDT, BROWNOUT, NOMCLR, NOLVP

#use standard_io ( A )
#use standard_io ( B )
#use delay ( clock = 6000000 )
#use rs232 ( baud = 9600, xmit = PIN_B2, rcv = PIN_B1 )

#byte PORT_A = 5
#define LCD_D7      PIN_B7
#define LCD_D6      PIN_B6
#define LCD_D5      PIN_B5
#define LCD_D4      PIN_B4
#define LCD_EN      PIN_B3
#define LCD_RS      PIN_B0
#define TRK_R_INPUT PIN_A0
#define TRK_B_INPUT PIN_A1
#define TRK_G_INPUT PIN_A2
#define TRK_Y_INPUT PIN_A3
#define SERVO_OUT   PIN_A4
#define PB_SWITCH   PIN_A5
#define LINE_1      0x00
#define LINE_2      0x40
#define LINE_3      0x10
#define LINE_4      0x50
#define CLEAR_DISP  0x01
#define TRACK_R     0b00000001
#define TRACK_B     0b00000010
#define TRACK_G     0b00000100
#define TRACK_Y     0b00001000
#define TEXT_DELAY  2000
#define CMD_NUL     0
#define GATE_OPEN   0
#define GATE_CLOSE  1
#define ERR_NONE    0
#define ERR_GATE    1
#define ERR_FINISH  2

#separate void SendTime ( char cLane, long iTime );
#separate char CheckFinishLine ( void );
#separate char CheckStartGate ( void );
#separate char GetSerial ( char cChar );
#separate void Servo ( char cX );
#separate void SendAllTimes ( void );
#separate void DisplayTie ( char cLoc );
#separate void Display ( char cTie, char cPlace, char cTrackColor, long iTime, char * cDisplayFlag );
#separate void StartTimer ( void );
#separate void LCD_Init ( void );
#separate void LCD_SetPosition ( unsigned int cX );
#separate void LCD_PutChar ( unsigned int cX );
#separate void LCD_PutCmd ( unsigned int cX );
#separate void LCD_PulseEnable ( void );
#separate void LCD_SetData ( unsigned int cX );

static long iTime, iRTime, iBTime, iGTime, iYTime;
static char cDisplayLine, cPlaceNum, cDisplayCnt;
static char cRTie, cBTie, cGTie, cYTie, cStarted;
static char cRPlace, cBPlace, cGPlace, cYPlace;
static char cRDisplayFlag, cBDisplayFlag, cGDisplayFlag, cYDisplayFlag;
static char cDisplayLocTable [ 4 ] = { 0x06, 0x46, 0x16, 0x56 };
static char cFirstTime, cFirstTimeFlag;
static char cSerialCmd;

#int_rda
void SerialInterrupt ( void )
    {
    // Reads incoming data from the USART
    cSerialCmd = getchar();       // get char from UART
    // command recipient resets cSerialCmd to CMD_NUL
    }

#int_rtcc
void TimerInterrupt ( void )
    {
    /*
    Gets here every 1mS.  Samples the track inputs, saves the
    current time for any tracks that have finished during this
    interrupt pass.  Increments the time count if not overflowed
    past 9999mS.
    */

    char cFinish;

    set_rtcc ( 73 );            // restart timer, adjusted to 1mS
    // GRAB ONE SAMPLE OF ALL FOUR TRACKS FOR THIS 1MS TIME SLICE
    cFinish = ( PORT_A ^ 0xff ) & 0x0f;   // invert Port A and mask on lower four bits
    // EXAMINE THE SAMPLE, ONE TRACK AT A TIME
    if ( ( ( cFinish & TRACK_R ) == TRACK_R ) && ( cRPlace == 0 ) )    // Track R finished
        {
        iRTime = iTime;         // save the present time
        if ( ( iRTime == iBTime ) || ( iRTime == iGTime ) || ( iRTime == iYTime ) )
            {
            cRTie = YES;        // if this matches any other track, indicate TIE
            }
        cRDisplayFlag = YES;      // time to display this track
        cRPlace = cPlaceNum++;    // take the place number, increment place number
        }
    if ( ( ( cFinish & TRACK_B ) == TRACK_B ) && ( cBPlace == 0 ) )    // Track B finished
        {
        iBTime = iTime;         // save the present time
        if ( ( iBTime == iRTime ) || ( iBTime == iGTime ) || ( iBTime == iYTime ) )
            {
            cBTie = YES;        // if this matches any other track, indicate TIE
            }
        cBDisplayFlag = YES;        // time to display this track
        cBPlace = cPlaceNum++;    // take the place number, increment place number
        }
    if ( ( ( cFinish & TRACK_G ) == TRACK_G ) && ( cGPlace == 0 ) )    // Track G finished
        {
        iGTime = iTime;         // save the present time
        if ( ( iGTime == iRTime ) || ( iGTime == iBTime ) || ( iGTime == iYTime ) )
            {
            cGTie = YES;        // if this matches any other track, indicate TIE
            }
        cGDisplayFlag = YES;        // time to display this track
        cGPlace = cPlaceNum++;    // take the place number, increment place number
        }
    if ( ( ( cFinish & TRACK_Y ) == TRACK_Y ) && ( cYPlace == 0 ) )    // Track Y finished
        {
        iYTime = iTime;         // save the present time
        if ( ( iYTime == iRTime ) || ( iYTime == iBTime ) || ( iYTime == iGTime ) )
            {
            cYTie = YES;        // if this matches any other track, indicate TIE
            }
        cYDisplayFlag = YES;        // time to display this track
        cYPlace = cPlaceNum++;    // take the place number, increment place number
        }
    if ( cFirstTimeFlag == ON )
        {
        cFirstTime = NO;    // prevent any other LED's from going on
        }
    if ( iTime < 9999)      // if not overflow, increment the clock
        {
        iTime++;
        }
    else                // make unfinished races = 9.999 seconds
        {
        if ( cRPlace == 0 )
            {
            iRTime = 9999;
            cRPlace = cPlaceNum++;      // save the available place number
            cRDisplayFlag = YES;        // time to display this track
            }
        if ( cBPlace == 0 )
            {
            iBTime = 9999;
            cBPlace = cPlaceNum++;      // save the available place number
            cBDisplayFlag = YES;        // time to display this track
            }
        if ( cGPlace == 0 )
            {
            iGTime = 9999;
            cGPlace = cPlaceNum++;      // save the available place number
            cGDisplayFlag = YES;        // time to display this track
            }
        if ( cYPlace == 0 )
            {
            iYTime = 9999;
            cYPlace = cPlaceNum++;      // save the available place number
            cYDisplayFlag = YES;        // time to display this track
            }
        }
    }

void main ( void )
    {
    char cCnt, cX;

    delay_ms ( 200 );
    port_b_pullups ( ON );
    output_float ( TRK_R_INPUT );
    output_float ( TRK_B_INPUT );
    output_float ( TRK_G_INPUT );
    output_float ( TRK_Y_INPUT );
    output_float ( SERVO_OUT );
    setup_counters ( RTCC_INTERNAL, RTCC_DIV_8 );   // 256 * 4uS = 1.024mS timer wrap

    LCD_Init();             // set up LCD for 4-wire bus, etc.

    if ( input ( PB_SWITCH ) == LOW )       // diag mode
        {
        LCD_PutCmd ( CLEAR_DISP );
        LCD_SetPosition ( LINE_1 + 0 );
        printf ( LCD_PutChar, "DIAGNOSTICS MODE" );        // welcome screen
        LCD_SetPosition ( LINE_3 + 0 );
        printf ( LCD_PutChar, "Start gates:  " );
        LCD_SetPosition ( LINE_4 + 0 );
        printf ( LCD_PutChar, "Lane finish: " );
        while ( input ( PB_SWITCH ) == LOW )
            {
            LCD_SetPosition ( LINE_3 + 12 );
            if ( input ( SERVO_OUT ) == HIGH )
                {
                printf ( LCD_PutChar, "DOWN" );
                }
            else
                {
                printf ( LCD_PutChar, "UP  " );
                }
            LCD_SetPosition ( LINE_4 + 12 );
            if ( ( ( PORT_A ^ 0xff ) & TRACK_R ) != 0 )
                {
                printf ( LCD_PutChar, "R" );
                }
            else
                {
                printf ( LCD_PutChar, " " );
                }
            LCD_SetPosition ( LINE_4 + 13 );
            if ( ( ( PORT_A ^ 0xff ) & TRACK_B ) != 0 )
                {
                printf ( LCD_PutChar, "B" );
                }
            else
                {
                printf ( LCD_PutChar, " " );
                }
            LCD_SetPosition ( LINE_4 + 14 );
            if ( ( ( PORT_A ^ 0xff ) & TRACK_G ) != 0 )
                {
                printf ( LCD_PutChar, "G" );
                }
            else
                {
                printf ( LCD_PutChar, " " );
                }
            LCD_SetPosition ( LINE_4 + 15 );
            if ( ( ( PORT_A ^ 0xff ) & TRACK_Y ) != 0 )
                {
                printf ( LCD_PutChar, "Y" );
                }
            else
                {
                printf ( LCD_PutChar, " " );
                }
            delay_ms ( 10 );
            }
        }

    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( LINE_1 + 0 );
    printf ( LCD_PutChar, "AWANA GRAND PRIX" );        // welcome screen
    LCD_SetPosition ( LINE_2 + 0 );
    printf ( LCD_PutChar, "Christ Memorial" );
    LCD_SetPosition ( LINE_3 + 5 );
    printf ( LCD_PutChar, "Church" );
    LCD_SetPosition ( LINE_4 + 0 );
    printf ( LCD_PutChar, "Jon Fick v011207" );
    delay_ms ( TEXT_DELAY );

    Servo ( GATE_CLOSE );           // reset gate

    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( LINE_2 + 5 );
    printf ( LCD_PutChar, "Ready" );

    cStarted = FALSE;
    enable_interrupts ( GLOBAL );   // enable all interrupts
    enable_interrupts ( INT_RDA );  // enable serial interrupt
    cSerialCmd = CMD_NUL;           // reset to no command

    while ( TRUE )
        {
        while ( TRUE )
            {
            if ( input ( PB_SWITCH ) == LOW )     // PUSHBUTTON MANUAL RACE START
                {
                cX = ERR_NONE;    // default
                #if START_CHECKS == 1
                cX |= CheckFinishLine();   // check that all lanes are clear at the finish line
                #endif
                #if START_CHECKS == 2
                cX |= CheckStartGate();    // check that the start gate is closed
                cX |= CheckFinishLine();   // check that all lanes are clear at the finish line
                #endif
                if ( cX == ERR_NONE )
                    {
                    break;
                    }
                if ( ( cX & ERR_GATE ) == ERR_GATE )
                    {
                    LCD_PutCmd ( CLEAR_DISP );
                    LCD_SetPosition ( LINE_2 + 3 );
                    printf ( LCD_PutChar, "CLOSE THE" );
                    LCD_SetPosition ( LINE_3 + 3 );
                    printf ( LCD_PutChar, "START GATE" );
                    }
                else
                    {
                    if ( ( cX & ERR_FINISH )== ERR_FINISH )
                        {
                        LCD_PutCmd ( CLEAR_DISP );
                        LCD_SetPosition ( LINE_2 + 2 );
                        printf ( LCD_PutChar, "FINISH LINE" );
                        LCD_SetPosition ( LINE_3 + 2 );
                        printf ( LCD_PutChar, "IS BLOCKED" );
                        }
                    }
                }
            if ( GetSerial ( 'R' ) )            // "READY" command from computer
                {
                LCD_PutCmd ( CLEAR_DISP );
                LCD_SetPosition ( LINE_2 + 5 );
                printf ( LCD_PutChar, "Ready" );
                }
            if ( GetSerial ( 'T' ) )            // "RACE START" command from computer
                {
                break;          // break out, time to start this race (heat)
                }
            if ( GetSerial ( 'C' ) )            // "CHECK" command from computer
                {
                // signal if start gate switch is not closed or photocell is blocked
                if ( ( CheckStartGate() != ERR_NONE ) || ( CheckFinishLine() != ERR_NONE ) )
                    {
                    printf ( "NG" );
                    }
                }
            if ( GetSerial ( 'A' ) )            // "GET ALL TIMES" command from computer
                {
                SendAllTimes();
                }
            if ( GetSerial ( 'M' ) )            // ? command from computer
                {
                // unused at the present time
                }
            }
        // GETS HERE EITHER BY MANUAL PUSHBUTTON OR BY COMPUTER COMMAND WHEN TIMING NEEDS TO START
        LCD_PutCmd ( CLEAR_DISP );
        LCD_SetPosition ( LINE_2 + 3 );
        printf ( LCD_PutChar, "STARTING!" ); // signal that the race has begun
        StartTimer();
        Servo ( GATE_OPEN );
        cStarted = FALSE;
        enable_interrupts ( INT_RTCC );     // otherwise just allow the timer interrupt, which will start timer later
        delay_ms ( 1000 );              // wait 1 second
        LCD_PutCmd ( CLEAR_DISP );      // clear display and put in 1st, 2nd, etc.
        LCD_SetPosition ( LINE_1 );
        printf ( LCD_PutChar, "1st" );
        LCD_SetPosition ( LINE_2 );
        printf ( LCD_PutChar, "2nd" );
        LCD_SetPosition ( LINE_3 );
        printf ( LCD_PutChar, "3rd" );
        LCD_SetPosition ( LINE_4 );
        printf ( LCD_PutChar, "4th" );
        Servo ( GATE_CLOSE );

        while ( TRUE )
            {
            Display ( cRTie, cRPlace, 'R', iRTime, &cRDisplayFlag );  // check and display Track 1
            Display ( cBTie, cBPlace, 'B', iBTime, &cBDisplayFlag );  // check and display Track 2
            Display ( cGTie, cGPlace, 'G', iGTime, &cGDisplayFlag );  // check and display Track 3
            Display ( cYTie, cYPlace, 'Y', iYTime, &cYDisplayFlag );  // check and display Track 4
            delay_ms ( 100 );
            if ( GetSerial ( 'F' ) )    // "EARLY DONE" command from computer
                {
                if ( cRPlace == 0 )
                    {
                    cRPlace = 5;
                    iRTime = 9999;
                    }
                if ( cBPlace == 0 )
                    {
                    cBPlace = 5;
                    iBTime = 9999;
                    }
                if ( cGPlace == 0 )
                    {
                    cGPlace = 5;
                    iGTime = 9999;
                    }
                if ( cYPlace == 0 )
                    {
                    cYPlace = 5;
                    iYTime = 9999;
                    }
                SendAllTimes();
                break;
                }
            if ( cDisplayCnt == 4 )     // if all four lines actually written to display
                {
                SendAllTimes();
                break;
                }
            }
        }
    }

#separate char CheckStartGate ( void )
    {
    // signal if start gate switch is not closed
    if ( input ( SERVO_OUT ) == HIGH )
        {
        return ( ERR_GATE );
        }
    return ( ERR_NONE );        // otherwise OK
    }

#separate char CheckFinishLine ( void )
    {
    // signal if photocell is blocked
    if ( ( ( PORT_A ^ 0xff ) & 0x0f ) != 0 )  // invert Port A and mask
        {
        return ( ERR_FINISH );
        }
    return ( ERR_NONE );        // otherwise OK
    }

#separate char GetSerial ( char cChar )
    {
    if ( cSerialCmd == cChar )
        {
        cSerialCmd = CMD_NUL;

        return ( YES );
        }
    else
        {
        return ( NO );
        }
    }

#separate void Servo ( char cX )
    {
    char cCnt;

    switch ( cX )
        {
        case GATE_OPEN:
            {
            for ( cCnt = 0; cCnt < 15; cCnt++ )
               {
               output_high ( SERVO_OUT );
               delay_us ( 1000 );
               output_low ( SERVO_OUT );
               delay_ms ( 30 );
               }
            break;
            }
        case GATE_CLOSE:
            {
            for ( cCnt = 0; cCnt < 15; cCnt++ )
                {
                output_high ( SERVO_OUT );
                delay_us ( 2000 );
                output_low ( SERVO_OUT );
                delay_ms ( 30 );
                }
            break;
            }
        }
    output_float ( SERVO_OUT );     // go back to hi-z
    }


#separate void SendAllTimes ( void )
    {
    // send lane time if that lane finished
    if ( cRPlace != 0 )
        {
        SendTime ( 1, iRTime );
        }
    if ( cBPlace != 0 )
        {
        SendTime ( 2, iBTime );
        }
    if ( cGPlace != 0 )
        {
        SendTime ( 3, iGTime );
        }
    if ( cYPlace != 0 )
        {
        SendTime ( 4, iYTime );
        }
    }

#separate void SendTime ( char cLane, long iTime )
    {
    printf ( "%u %01lu.%03lu ", cLane, iTime/1000, iTime%1000 );
    }

#separate void StartTimer ( void )
    {
    disable_interrupts ( INT_RTCC );    // turn off timer interrupt while resetting time
    iRTime = 65535;         // set the individual track times off zero, to max
    iBTime = 65535;
    iGTime = 65535;
    iYTime = 65535;
    cRDisplayFlag = OFF;    // allow one-time display
    cBDisplayFlag = OFF;
    cGDisplayFlag = OFF;
    cYDisplayFlag = OFF;
    cRTie = NO;             // preset to no tie conditions
    cBTie = NO;
    cGTie = NO;
    cYTie = NO;
    cRPlace = 0;            // preset to no place
    cBPlace = 0;
    cGPlace = 0;
    cYPlace = 0;
    iTime = 0;              // zero the count
    cDisplayLine = 1;       // preset to first display line
    cPlaceNum = 1;          // start with first place
    cStarted = TRUE;        // signal: running
    cDisplayCnt = 0;
    enable_interrupts ( INT_RTCC );     // turn on timer interrupt to begin timing
    }

#separate void Display ( char cTie, char cPlace, char cTrackColor, long iTime, char *cDisplayFlag )
    {
    char cLedDrive, cTablePtr;

    if ( *cDisplayFlag == YES ) // if this track's done flag was turned on by the interrupt
        {
        cTablePtr = cPlace - 1; // place value determines which display line
        LCD_SetPosition ( cDisplayLocTable [ cTablePtr ] );
        printf ( LCD_PutChar, "%c  %2lu.%03lu", cTrackColor, iTime/1000, iTime%1000 );
        if ( cTie == YES )
            {
            DisplayTie ( cDisplayLocTable [ cTablePtr ] );    // point to line
            DisplayTie ( cDisplayLocTable [ cTablePtr - 1 ] );    // point to line above
            }
        *cDisplayFlag = NO;             // prevent displaying again
        cDisplayCnt += 1;
        }
    }

#separate void DisplayTie ( char cLoc )
    {
    LCD_SetPosition ( cLoc - 6 );    // point to beginning of this line
    printf ( LCD_PutChar, "TIE!" );
    }

#separate void LCD_Init ( void )
    {
    LCD_SetData ( 0x00 );
    delay_ms ( 200 );       // wait enough time after Vdd rise
    output_low ( LCD_RS );
    LCD_SetData ( 0x03 );   // init with specific nibbles to start 4-bit mode
    LCD_PulseEnable();
    LCD_PulseEnable();
    LCD_PulseEnable();
    LCD_SetData ( 0x02 );   // set 4-bit interface
    LCD_PulseEnable();      // send dual nibbles hereafter, MSN first
    LCD_PutCmd ( 0x2C );    // function set (all lines, 5x7 characters)
    LCD_PutCmd ( 0x0C );    // display ON, cursor off, no blink
    LCD_PutCmd ( 0x01 );    // clear display
    LCD_PutCmd ( 0x06 );    // entry mode set, increment & scroll left
    }

#separate void LCD_SetPosition ( unsigned int cX )
    {
    // this subroutine works specifically for 4-bit Port A
    LCD_SetData ( swap ( cX ) | 0x08 );
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );
    LCD_PulseEnable();
    }

#separate void LCD_PutChar ( unsigned int cX )
    {
    // this subroutine works specifically for 4-bit Port A
    output_high ( LCD_RS );
    LCD_SetData ( swap ( cX ) );     // send high nibble
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );     // send low nibble
    LCD_PulseEnable();
    output_low ( LCD_RS );
    }

#separate void LCD_PutCmd ( unsigned int cX )
    {
    // this subroutine works specifically for 4-bit Port A
    LCD_SetData ( swap ( cX ) );     // send high nibble
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );     // send low nibble
    LCD_PulseEnable();
    }

#separate void LCD_PulseEnable ( void )
    {
    output_high ( LCD_EN );
    delay_us ( 10 );
    output_low ( LCD_EN );
    delay_ms ( 5 );
    }

#separate void LCD_SetData ( unsigned int cX )
    {
    output_bit ( LCD_D4, cX & 0x01 );
    output_bit ( LCD_D5, cX & 0x02 );
    output_bit ( LCD_D6, cX & 0x04 );
    output_bit ( LCD_D7, cX & 0x08 );
    }