/****************************************************************************
compass.c

This program receives NMEA-0183 heading data from a GPS
and displays it on an array of LEDs.


                   +5
                    |
                    14
                ----------
               |          |-6----- N
    ~SerIn --7-|          |-9----- NE
               |          |-10---- E
       XTAL-15-|  16F628  |-11---- SE
       XTAL-16-|          |-2----- S
       CLK -12-|          |-1----- SW
      DATA -13-|          |-18---- W
      MCLR --4-|          |-17---- NW
                ----------
                    5
                    |        CLK, DATA, MCLR are optional programming port.
                   Gnd

***************************************************************************/
#case
#include < 16F628.h >
#include < jonsinc.h >

#define EOF             0x00
#define COMMA           ','
#define CR              13
#define SPACE           ' '
#define PERIOD          '.'
#define DOLLAR          '$'
#define NULL            0
#define GPRMC_CODE      75
#define RX_BUFFER_SIZE  70
#define LED_N           PIN_B0
#define LED_NE          PIN_B3
#define LED_E           PIN_B4
#define LED_SE          PIN_B5
#define LED_S           PIN_A3
#define LED_SW          PIN_A2
#define LED_W           PIN_A1
#define LED_NW          PIN_A0
#define ROTATE_MS       30
#define RX_IN           PIN_B1
#define PROG_CLK        PIN_B6
#define PROG_DAT        PIN_B6
#define PROG_MCLR       PIN_A5

#separate char GetField ( void );
#separate void AllOff ( void );
#separate void GetHeading ( void );
#separate void DisplayHeading ( void );
#separate long TrueToMag ( long iH );
#separate long FieldFiveToLong ( void );
#separate void InitRxBuffer ( char cCode );
#separate char GetRxChar ( void );

#fuses HS, NOPROTECT, PUT, NOWDT, BROWNOUT, NOLVP, NOCPD, NOMCLR

#use standard_io ( A )
#use standard_io ( B )
#use delay ( clock = 8000000 )
#use rs232 ( baud=4800, xmit=PIN_B2, rcv=PIN_B1, ERRORS )    // XMIT must be assigned to enable hardward USART

static char cC [ 10 ];      // local buffer
static char cTimeOut;
static char cRxBuffer [ RX_BUFFER_SIZE ];    // Fifo
static char cRxByteCnt;         // Number of bytes in the recv fifo
static char *cRxBufferWritePtr;    // Pointers for the Rx buffer
static char *cRxBufferReadPtr;
static char cRxIsrState, cRxMsgTypeReceived, cRxMsgTypeDesired;
static char cRxMsgReady, cReceiveFlag;
static long iVar, iHdg;
static char cRxErrorFlag, cVarDir, cError;

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

#int_rtcc
void Timer0Interrupt ( void )
    {
    // Gets here every 16.52mS
    // Handles data timeout
    if ( cTimeOut != 0 )
        {
        cTimeOut--;
        }
    }

#int_rda
void SerialInterrupt ( void )
    {
    /*
    Reads incoming data from the USART and puts in in a rolling buffer
    ( but in this application, it should never roll.)
    If the buffer is full, this routine just discards the received byte.
    Not checking the LRC byte at the end of the NMEA-0183 sentence.
    */
    char cChar;

    if ( rs232_errors & 0x04 )  // get framing error bit from Rx status reg
        {
        cRxErrorFlag = ON;
        }
    cChar = getchar();       // get char from UART, clear any errors
    if ( cRxByteCnt == RX_BUFFER_SIZE ) // is recv fifo full ???
        {
        goto done;
        }
    switch ( cRxIsrState )
        {
        case 0:
            {
            if ( cChar == DOLLAR )  // if start of NMEA0183 message
                {
                cRxByteCnt = 0;     // reset byte count
                cReceiveFlag = OFF;     // default to off
                cRxMsgTypeReceived = NULL;  // set hashed value to null
                cRxIsrState++;                 // next state
                }
            break;
            }
        case 1:                           // five type characters to obtain
        case 2:
        case 3:
        case 4:
        case 5:
            {
            cRxMsgTypeReceived ^= cChar;      // hash in msg type
            if ( cRxIsrState++ == 5 )        // if time to check message type
                {
                if ( cRxMsgTypeReceived == cRxMsgTypeDesired )  // if good
                    {
                    cReceiveFlag = YES;            // enable receiving
                    cRxBufferWritePtr = cRxBuffer;    // reset to beginning of buffer
                    }
                else                    // don't want this message
                    {
                    cRxIsrState = 0;    // reset to look for next msg
                    }
                }
            break;
            }
        case 6:
            {
            /* Case 6 skips the comma character following msg type */
            cRxIsrState++;
            break;
            }
        default:                          // remainder of characters
            {
            if ( cReceiveFlag == YES )        // if this message is wanted
                {
                *cRxBufferWritePtr = cChar;     // put char in fifo
                cRxBufferWritePtr++;            // increment pointer
                if ( cRxBufferWritePtr == ( cRxBuffer + RX_BUFFER_SIZE ) ) // pointer past end ?
                    {
                    cRxBufferWritePtr = cRxBuffer;      // set pointer to start of fifo
                    }
                cRxByteCnt++;              // Increment byte count
                if ( cChar == CR )
                    {
                    cRxMsgReady = YES;         // signal that message is ready
                    cReceiveFlag = NO;      // no more receive
                    }
                }
            }
        }
    done:;               // label
    }

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

void main ( void )
    {
    char cX;

    /* INITIALIZE */
    output_float ( RX_IN );             // ensure Rx input is HiZ
    output_float ( PROG_CLK );
    output_float ( PROG_DAT );
    output_float ( PROG_MCLR );

    // SETUP TIMER 0
    // Need 8-bit Timer0 to roll over every 13mS, approximately.
    // Roll time = 256 * 1 / ( clock_freq / prescaler setting / 4 )
    setup_counters ( RTCC_INTERNAL, RTCC_DIV_128 );   // ~13mS timer wrap

    /* INTERRUPTS */
    enable_interrupts ( INT_TIMER1 );   // enable Timer1 interrupt
    enable_interrupts ( INT_RTCC );     // enable Timer0 interrupt
    enable_interrupts ( INT_RDA );      // enable serial interrupt
    enable_interrupts ( GLOBAL );       // enable all interrupts

    /* VARIABLES */
    iVar = NULL;                        // default, no variation yet
    cVarDir = SPACE;                    // default, no variation yet
    cRxErrorFlag = OFF;

    for ( cX = 0; cX < 5; cX++ )
        {
        output_low ( LED_NW ); output_high ( LED_N ); delay_ms ( ROTATE_MS );
        output_low ( LED_N ); output_high ( LED_NE ); delay_ms ( ROTATE_MS );
        output_low ( LED_NE ); output_high ( LED_E ); delay_ms ( ROTATE_MS );
        output_low ( LED_E ); output_high ( LED_SE ); delay_ms ( ROTATE_MS );
        output_low ( LED_SE ); output_high ( LED_S ); delay_ms ( ROTATE_MS );
        output_low ( LED_S ); output_high ( LED_SW ); delay_ms ( ROTATE_MS );
        output_low ( LED_SW ); output_high ( LED_W ); delay_ms ( ROTATE_MS );
        output_low ( LED_W ); output_high ( LED_NW ); delay_ms ( ROTATE_MS );
        }
    output_low ( LED_NW );

    /* MAIN LOOP */
    while ( TRUE )
        {
        cTimeOut = 242;                 // 242 * 0.0165mS = 4 seconds
        cRxErrorFlag = OFF;             // default to off
        cError = OFF;
        InitRxBuffer( GPRMC_CODE );     // set code and turn on serial interrupt
        while ( ( cRxMsgReady == NO ) && ( cTimeOut != 0 ) );   // wait for RMC sentence
        //disable_interrupts ( INT_RDA ); // ignore rest of messages
        if ( ( cTimeOut != 0 ) && ( cRxErrorFlag == OFF ) )   // if no errors
            {
            GetHeading();               // get heading and variation
            AllOff();                   // turn all LED's off
            if ( cError == OFF )
                {
                DisplayHeading();           // display heading
                }
            }
        else
            {
            AllOff();                   // error, data timeout or framing error
            }
        //enable_interrupts ( INT_RDA );  // enable Rx again
        }
    }

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

#separate void GetHeading ( void )
    {
    GetField();                     // skip UTC
    GetField();                     // A = OK, V = warning
    if ( cC [ 0 ] == 'A' )          // if valid
        {
        GetField();                 // skip LAT
        GetField();                 // skip N/S
        GetField();                 // skip LON
        GetField();                 // skip E/W
        GetField();                 // skip SPD
        GetField();                 // get HDG
        iHdg = FieldFiveToLong();   // convert
        GetField();                 // skip FIX DATE
        GetField();                 // get MAG VAR
        iVar = FieldFiveToLong();   // convert
        GetField();                 // get EW variation direction
        cVarDir = cC [ 0 ];         // save variation direction
        iHdg = TrueToMag ( iHdg );  // factor variation into heading
        }
    else
        {
        cError = ON;                // error, corrupted data from satellite
        }
    }

#separate void AllOff ( void )
    {
    output_low ( LED_N );
    output_low ( LED_NE );
    output_low ( LED_E );
    output_low ( LED_SE );
    output_low ( LED_S );
    output_low ( LED_SW );
    output_low ( LED_W );
    output_low ( LED_NW );
    }

#separate void DisplayHeading ( void )
    {
    if ( ( ( iHdg > 337 ) && ( iHdg <= 359 ) ) || ( ( iHdg >= 0 ) && ( iHdg <= 23 ) ) )     // N
        {
        output_high ( LED_N );
        }
    if ( ( iHdg > 23 ) && ( iHdg <= 68 ) )       // NE
        {
        output_high ( LED_NE );
        }
    if ( ( iHdg > 68 ) && ( iHdg <= 113 ) )      // E
        {
        output_high ( LED_E );
        }
    if ( ( iHdg > 113 ) && ( iHdg <= 158 ) )     // SE
        {
        output_high ( LED_SE );
        }
    if ( ( iHdg > 158 ) && ( iHdg <= 203 ) )     // S
        {
        output_high ( LED_S );
        }
    if ( ( iHdg > 203 ) && ( iHdg <= 248 ) )     // SW
        {
        output_high ( LED_SW );
        }
    if ( ( iHdg > 248 ) && ( iHdg <= 293 ) )     // W
        {
        output_high ( LED_W );
        }
    if ( ( iHdg > 293 ) && ( iHdg <= 337 ) )     // NW
        {
        output_high ( LED_NW );
        }
    }

#separate long FieldFiveToLong ( void )
    {
    /* Converts ABC.D to long ABC, rounds fraction part up or down */
    long iX;

    iX = 100 * ( long ) ( cC [ 0 ] - 0x30 );
    iX += 10 * ( long ) ( cC [ 1 ] - 0x30 );
    iX += ( long ) ( cC [ 2 ] - 0x30 );
    if ( ( cC [ 3 ] == PERIOD ) && ( cC [ 4 ] >= '5' ) )
        {
        iX++;           // round up
        }
    return ( iX );
    }

#separate long TrueToMag ( long iH )
    {
    /* Magnetic variation information comes from the RMC sentence */

    if ( cVarDir == 'W' )
        {
        iH += iVar;
        }
    else
        {
        if ( iH >= iVar )
            {
            iH -= iVar;     // OK as-is
            }
        else
            {
            iH = iH + 360 - iVar;   // correct for below zero
            }
        }
    if ( iH >= 360 )
        {
        iH -= 360;
        }
    return ( iH );
    }

#separate char GetField ( void )
    {
    char cX, cIndex;

    // Get next field from cRxBuffer and put in cC buffer.
    cX = NULL;
    cIndex = 0;
    while ( cTimeOut != 0 )
        {
        cX = GetRxChar();
        if ( ( cX == COMMA ) || ( cX == CR ) )
           {
           break;
           }
        cC [ cIndex++ ] = cX;
        }
    cC [ cIndex ] = EOF;
    return ( cIndex );         // return number of characters in field
    }

/* RS232 FUNCTIONS ================================================== */

#separate void InitRxBuffer ( char cCode )
    {
    disable_interrupts ( INT_RDA );
    cRxBufferWritePtr = cRxBuffer;      // point to beginning of buffer
    cRxBufferReadPtr = cRxBuffer;
    cRxByteCnt = 0;
    cRxIsrState = 0;
    cRxMsgReady = NO;
    cRxMsgTypeDesired = cCode;
    enable_interrupts ( INT_RDA );
    }

#separate char GetRxChar ( void )
    {
    // Get the next available byte in the recv fifo.
    // Call this function ONLY if the recv fifo contains data.
    char cValue;

    cValue = 0;
    if ( cRxByteCnt > 0 )       // For safety, check if there is any data
        {
        cValue = *cRxBufferReadPtr++;     // Read byte from fifo
        if ( cRxBufferReadPtr == ( cRxBuffer + RX_BUFFER_SIZE ) ) // Did tail ptr wrap ?
            {
            cRxBufferReadPtr = cRxBuffer;    // If so, reset it to start of buffer
            }
        cRxByteCnt--; // Decrement byte count
        }
    return ( cValue );
    }