/****************************************************************************
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 );
}