Tuto. 7 - GPS Receiver
PIC tutorial 7 - GPS Receiver
Fig. 1a Board 3 with GPS receiver and graphic LCD Display
Description:
Now we can display things on a graphical or alphanumeric LCD display, we'll use tutorial 7 to introduce serial communication and GPS data stream processing. We will build a library to get serial communication going and another one to manage the GPS receiver. A serial GPS receiver from Fastrax is used in this tutorial, but the tutorial can easily be adapted to other receiver.
The tutorial will display date, time, longitude, latitude, number of satellite used in calculation, fix mode, speed and altitude on the LCD screen if the custom sentence is selected, or the selected NMEA sentence.
The GPS library read a NMEA sentence from the receiver, or build a custom sentence with: Time,latitude,N/S,longitude,E/W,fix,number of satellites, altitude, speed, course and date (by default).
The graphic display shows:
| Graphic LCD | Alphanumeric LCD | |
|---|---|---|
|
17/05/10 17:50 |
17/05/10 17:50 |
The boards can be powered by one of the PSU boards (powered from USB port), or between 3.3 to 5V, depending on the display used.
Requirements:
Processor boardAny board with 4K or more (board 3 in example)
Extension(s)GPS Receiver
graphic or alphanumeric LCD Display
Power supplyPSU 1, 2 or 3.
LanguageCCS C
ProgrammerPicKIT 2 2
Program:
#define board_id 3
#define _NMEACode 0 // Set the string to receive to the custom sentence
#include "lib\mainboard.c"
#if (board_id == 4 ) // Include alphanumeric LCD library if board 4
#include "lib\lcd_lib.c"
#else // else use graphic display
#include "lib\nok3310_lib.c"
#endif
#include "lib\rs232_lib.c" // Load serial library
#include "lib\GPS_lib.c" // Load GPS library
#include <stdlib.h> // Load standard C library
int1 blinkFlag=0; // Bit with second flag (used for displaying blinking ':' in time)
void display_GPS () { // Displays custom sentence on display one field after the other
float tempf; // Temporary float
int8 fieldLength;
fieldLength = GPS_GetField(); // get first field of sentence (time)
display_gotoxy(9,0); // Go to time position on display
if (fieldLength > 0) {
display_putc(GPS_ctrl.Word[0]); // Display 2 digits of hours
display_putc(GPS_ctrl.Word[1]);
if (blinkFlag == 0) {display_putc(0x20); blinkFlag = 1;} // if second flag not set, display space
else {display_putc(0x3a); blinkFlag = 0;} // else display ':'
display_putc(GPS_ctrl.Word[2]); // Display 2 digits of time
display_putc(GPS_ctrl.Word[3]);
} else {
printf(display_putc, "**:**");
}
fieldLength = GPS_GetField(); // get 2nd field (latitude)
display_gotoxy(0,1);
if (fieldLength > 0) {
printf(display_putc, GPS_ctrl.Word);
} else {
printf(display_putc, "%d", fieldLength);
}
display_putc(' ');
fieldLength = GPS_GetField(); // get 3rd field (latitude N/S)
if (fieldLength > 0) {
display_putc(GPS_ctrl.word[0]);
} else {
printf(display_putc, "* ");
}
fieldLength = GPS_GetField(); // get 4th field (longitude)
display_gotoxy(0,2);
if (fieldLength > 0) {
printf(display_putc, GPS_ctrl.Word);
} else {
printf(display_putc, "%d", fieldLength);
} display_putc(' ');
fieldLength = GPS_GetField(); // get 5th field longitude (E/W)
if (fieldLength > 0) {
display_putc(GPS_ctrl.word[0]);
} else {
printf(display_putc, "* ");
}
fieldLength = GPS_GetField(); // get 6th field (fix)
display_gotoxy(13,2);
if (fieldLength > 0) {
display_putc(GPS_ctrl.word[0]);
} else {
display_putc('*');
}
fieldLength = GPS_GetField(); // get 7th field (nb satellites)
display_gotoxy(12,1);
if (fieldLength > 1) {
display_putc(GPS_ctrl.word[0]);
display_putc(GPS_ctrl.word[1]);
} else if (fieldLength > 0) {
display_putc(' ');
display_putc(GPS_ctrl.word[0]);
} else {
printf(display_putc, " *");
}
fieldLength = GPS_GetField(); // get 8th field (altitude)
if (fieldLength > 0) {
display_gotoxy(13-fieldLength,3);
printf(display_putc, GPS_ctrl.Word);
display_putc('m');
} else {
display_gotoxy(8,3);
display_putc('*');
}
fieldLength = GPS_GetField(); // get speed
display_gotoxy(0,3);
if (fieldLength > 0) {
tempf = atof(GPS_ctrl.Word);
#if (GPS_SPEED_UNIT == 2 )
printf(display_putc, "%3LuKm/h", (int16) (tempf*1.852));
#elif (GPS_SPEED_UNIT == 3 )
printf(display_putc, "%3Lum/h", (int16) (tempf*1.151));
#else
printf(display_putc, "%3LuKnot", (int16) (tempf));
#endif
} else {
printf(display_putc, " **.*");
}
GPS_SkipField(1); // skip course
fieldLength = GPS_GetField(); // get date
display_gotoxy(0,0);
if (fieldLength > 0) {
display_putc(GPS_ctrl.Word[0]);
display_putc(GPS_ctrl.Word[1]);
display_putc(0x2f);
display_putc(GPS_ctrl.Word[2]);
display_putc(GPS_ctrl.Word[3]);
display_putc(0x2f);
display_putc(GPS_ctrl.Word[4]);
display_putc(GPS_ctrl.Word[5]);
} else {
printf(display_putc, "**/**/**");
}
}
void main() {
boot_up();
Init_display();
Init_GPS(); // Initialise GPS receiver
printf(display_putc, "Waiting GPS");
InitGPSBuffer(_NMEACode);
while (TRUE) {
if (GPS_ctrl.State == GPS_STATE_IDLE) { // If buffer ready to receive
InitGPSBuffer(_NMEACode); // set code to receive (0 to get custom sentence)
Read_GPS();
}
if (GPS_ctrl.State == GPS_STATE_READY) { // Sentence in buffer
Clear_display(); // clear screen
#if (_NMEACode == 0 ) display_GPS(); // use function to display custom sentence
#else printf(display_putc, GPS_ctrl.Buffer); // or just diaplsy the sentence
#endif
GPS_ctrl.State = GPS_STATE_IDLE;
}
}
}
The program defines the processor board used, and loads the library files. If board 4 is used, it will compile to use the alphanumeric LCD display, else it will use the graphic display.
We then load the serial, gps and standard C libraries.
The blinkFlag bit is used to be able to have the ':' character blinking in the time displayed.
The display_GPS function simply displays each field of the custom sentence on the screen, at a given position. The GPS_GetField function get the current field from the GPS buffer in the GPS_ctrl.Word array. It is then displayed. This introduces various standard C functions (atof...). We check the length of the value returned before displaying it, to make sure we have a relevant value.
The main program initialise the board with the function boot_up and initialise the display and GPS receiver.
It then enters a loop that sets the GPS buffer to read the custom GPS sentence and reads the GPS stream to build it. Once received, it clears the screen and displays the various data on the display.
It is easy to receive in the GPS buffer another NMEA sentence by changing the value of _NMEACode variables to one of the value available in the GPS library (75, 86, 85, 66). The value is the 5 characters of the NMEA header hashed. When receiving a sentence, it is checked against the message received to see if it is the once requested.
Now if this value is changed, then the sentence is simply displayed on the screen.
Note: on the alphanumeric display, the displays fills line 1, then 3, then 2 and eventually 4. The NMEA sentence might be displayed incorrectly if it has more than twice the number of characters/per line for the display.
The board used for this tutorial is board 3, with a PIC 16F873A processor.
Now, the serial library. The serial port is implemented differently on different processor. Some have hardware UART interface, some don't, and would use a software emulation.
#ifndef _RS232_LIB
#define _RS232_LIB
#ifndef board_id
#error "variable board_id not defined"
#endif
// 1: 16F819, 2: 16F628, 3: 16F873A, 4: 18F2320
#if (board_id == 1 )
#define RXD PIN_B1
#define TXD PIN_B2
#use rs232 ( baud=9600, xmit=TXD, rcv=RXD, PARITY=N, BITS=8 )
#elif (board_id == 2)
#define RXD PIN_B1
#define TXD PIN_B2
#use rs232 ( baud=9600, UART1, PARITY=N, BITS=8, ERRORS )
#elif (board_id == 3)
#define RXD PIN_C7
#define TXD PIN_C6
#use rs232 ( baud=9600, UART1, PARITY=N, BITS=8, ERRORS )
#elif (board_id == 4)
#define RXD PIN_C7
#define TXD PIN_C6
#use rs232 ( baud=9600, UART1, PARITY=N, BITS=8, ERRORS )
#else
#error "Board board_id not supported"
#endif
#endif
The library checks that it knows what processor board is used so it knows how to configure the serial port.
If it is, it defines the RX and TX pins and setup the serial port using the built-in function in CCS: use rs232. If the board has a hardware interface, then it uses it, else it uses software emulation.
The library setup the serial interface at 9600 bauds, no parity and 8 bits. The serial function of CCS are now available, and it is fairly easy to implement. The GPS library will use it to communicate with the receiver, mainly with the function getc, which receives a character from the serial port.
#ifndef _RS232_LIB
#error "RS232 library not loaded"
#else
#ifndef _GPS_LIB
#define _GPS_LIB
#ifndef DEBUG_MODE
#define DEBUG_MODE 0
#endif
#ifndef GPS_BAUDRATE
#define GPS_BAUDRATE 9600
#endif
#define GPS_BUFFER_SIZE 70
#ifndef DEGREE
#define DEGREE 0xdf
#endif
#define GPRMC_CODE 75 // NMEA messages code XORed
#define GPGGA_CODE 86
#define GPGSV_CODE 85
#define GPGSA_CODE 66
#ifndef _NMEACode
#define _NMEACode 0
#endif
#define GPS_STATE_IDLE 0 // GPS Buffer ready to receive
#define GPS_STATE_READY 255 // GPS Buffer has sentence
typedef struct {
int8 State; // Status of GPS module
int8 Buffer[GPS_BUFFER_SIZE]; // The actual buffer data
int8 *BufferPtr; // Points to the buffer
int8 MsgTypeReceived; // Hashed message code received
int8 MsgTypeDesired; // Hashed message code desired
int8 ByteCnt; // Number of bytes in the buffer
int8 rmcPtr; // point to byte number of start of GGA data in buffer
int8 Word[10]; // Holds fieds (one GPS data)
} GPS_control_t;
GPS_control_t GPS_ctrl;
// function prototypes
void Init_GPS(void); // Initialize serial port for GPS receiver
void InitGPSBuffer(int8 NMEACode); // Initialize GPS buffer to receive NMEACode sentence
void Read_GPS(void); // put NMEA sentence defined by _NMEACode into GPS buffer
void GPS_SkipField (int8 Cnt); // skip Cnt GPS field(s) in GPS buffer
int8 GPS_GetChar(void); // Get character from GPS buffer
int8 GPS_GetField(void); // Get GPS field from GPS buffer into GPSword array
void Init_GPS() {
#use rs232(baud=GPS_BAUDRATE, xmit=TXD, rcv=RXD, PARITY=N, BITS=8, stream=GPSstream)
GPS_Ctrl.rmcPtr = 0;
//printf("$PMTK104*37\r\n"); // Reset GPS receiver (check with datasheet)
}
void InitGPSBuffer (int8 NMEACode) {
GPS_ctrl.BufferPtr = GPS_ctrl.buffer;
GPS_ctrl.ByteCnt = 0;
GPS_ctrl.State = GPS_STATE_IDLE;
GPS_ctrl.MsgTypeDesired = NMEACode;
if (GPS_Ctrl.rmcPtr == 0)
memset(GPS_ctrl.Buffer, 0, GPS_BUFFER_SIZE); // reset buffer content
}
void Read_GPS() {
int8 ch, i;
int1 skip=0;
do {
ch=getc(); // get caracter from serial port
if (ch == '$') { // if start of NMEA0183 message
GPS_ctrl.ByteCnt = 0; // reset byte count
GPS_ctrl.MsgTypeReceived = 0; // set hashed value to null
GPS_ctrl.State ++; // next state
for ( i=0; i<5; i++ ) { // the next 5 caracters are the NMEA header
ch=getc();
GPS_ctrl.MsgTypeReceived ^= ch; // hash in msg type
}
if (GPS_ctrl.MsgTypeDesired == 0) { // Process data to build default string (hhmmss,lat,N/S,long,E/W,fix,nbSat,altitude,speed,course,date)
ch=getc(); // skip comma after message type
if (GPS_ctrl.MsgTypeReceived == GPGGA_CODE) { // we received GGA
for ( i=0; i<9; i++) { // process 9 fields from GGA sentence
do {
ch=getc();
if (i==0 && ch=='.') skip = 1; // skip decimal of time (time is first field)
if (!skip && i!=7) { // do not store if skip or 7th field (Horizontal dilution)
GPS_ctrl.Buffer[GPS_ctrl.ByteCnt] = ch;
GPS_ctrl.ByteCnt++;
}
} while (ch != ','); // Store data until ',' is received (end of field)
if (skip) {
GPS_ctrl.Buffer[GPS_ctrl.ByteCnt] = ','; // Add separator to received sentence
GPS_ctrl.ByteCnt++;
}
skip = 0;
}
GPS_ctrl.rmcPtr = GPS_ctrl.ByteCnt; // GGA data received, we will receive RMC data next.
} else if (GPS_ctrl.MsgTypeReceived == GPRMC_CODE) { // we received RMC
GPS_ctrl.ByteCnt = GPS_ctrl.rmcPtr;
for ( i=0; i<9; i++) { // process 9 fields from RMC sentence
do {
ch=getc();
if (i>5) { // start recording from 6th field (Speed)
GPS_ctrl.Buffer[GPS_ctrl.ByteCnt] = ch;
GPS_ctrl.ByteCnt++;
}
} while (ch != ',');
}
GPS_ctrl.rmcPtr = 0; // reset pointer for received data
GPS_ctrl.Buffer[GPS_ctrl.ByteCnt-1] = 0; // Set end of data caracter
GPS_ctrl.State = GPS_STATE_READY; // Buffer is now ready
}
} else if (GPS_ctrl.MsgTypeReceived == GPS_ctrl.MsgTypeDesired) { // if it is the NMEA sentence requested
ch=getc(); // skip comma after message type
do {
ch=getc();
if (ch != '*') { // record characters received until the end character '*'
GPS_ctrl.Buffer[GPS_ctrl.ByteCnt] = ch;
GPS_ctrl.ByteCnt++;
}
} while (ch != '*');
GPS_Ctrl.rmcPtr = 0;
GPS_ctrl.Buffer[GPS_ctrl.ByteCnt+1] = 0;
GPS_ctrl.State = GPS_STATE_READY;
}
}
} while(GPS_ctrl.State != GPS_STATE_READY); // Read messages until the sentence is built
}
void GPS_SkipField (int8 Cnt) {
int8 X;
for ( X = 0; X < Cnt; X++ ) {
while (GPS_GetChar() != ',');
}
}
int8 GPS_GetChar (void) { // Get the next available byte in the recv fifo.
int8 Value;
if (GPS_ctrl.State == GPS_STATE_READY) {
Value = 0;
if (GPS_ctrl.ByteCnt > 0) { // For safety, check if there is any data
Value = *GPS_ctrl.BufferPtr++; // Read byte from fifo
if (GPS_ctrl.BufferPtr == (GPS_ctrl.Buffer + GPS_BUFFER_SIZE)) { // Did tail ptr wrap ?
GPS_ctrl.BufferPtr = GPS_ctrl.Buffer; // If so, reset it to start of buffer
}
GPS_ctrl.ByteCnt--; // Decrement byte count
} return (Value);
}
}
int8 GPS_GetField (void) {
int8 X=0, Index=0;
while (true) {
X = GPS_GetChar();
if ((X == ',') || (X == 13) || (X == 10) || (X == 0)) {
break;
}
GPS_ctrl.Word[Index++] = X;
}
GPS_ctrl.Word[Index] = 0x00;
return (Index); // return number of characters in field
}
#endif
#endif
Fig. 1b Board 3 with GPS receiver and alphanumeric LCD Display
First, we need to check if the serial library is loaded. A few variables are then defined, like the baud rate of the GPS module, the buffer size, the default NMEA message wanted. The NMEA message can be one of the values above (GPRMC_CODE, GPGGA_CODE, GPGSV_CODE, GPGSA_CODE ) to receive the equivalent NMEA message. If set to 0, then a custom message is built in the following format: Time,latitude,N/S,longitude,E/W,fix,number of satellites, altitude, speed, course and date.
A structure holds all the variables used in managing the GPS module: receive buffer, various pointers, status, counters...
A set of functions is then defined to manage communication with the GPS module.
- Init_GPS: initialize the GPS module and serial port at the right speed. The PMTK104 string reset the module from any custom configuration. The initialisation string might be different with other GPS module.
- InitGPSBuffer: initialise the control structure and sets which NMEA sentence needs to be received.
- Read_GPS: parse NMEA sentence to fill the GPS receive buffer. It reads the NMEA sentence defined when calling InitGPSBuffer. If it was 0, then the function parses several NMEA sentences to build the custom sentence. The code is commented a bit. It basically parse each character received and store it or not, depending whether it is wanted or not.
- GPS_SkipField: Once a sentence has been received in the buffer, a set of function allows to retrieve the various data from the sentence. Skip field will skip n fields from the received sentence. The pointer is pointing at the next data in the sentence.
- GPS_GetField: It puts the data of the current field in the received sentence in an array (GPS_ctrl.Word) and returns the length of it. It is the function used to retrieve the data from the pointed field.
- GPS_GetChar: This function just returns the character pointed to in the receive buffer.
Compile the project and program the target with a PicKIT 2 programmer for example. Connect the processor board to the LCD display board on port B and the GPS module on port C, making sure the jumpers on the GPS board match your processor board for the Txd and Rxd signals.
To use the alphanumeric display, either use another processor board (4 in the example), or change the display library loaded. Recompile and program the board. That's it.
Power up the boards.
The GPS module will need to get a fix, and the time it takes depends on the chip used and location. Position the receiver outside or near a window to get a quicker fix. The end of line 3 will display a value greater than 0 once a valid fix is acquired. You'll also be able to see the number of satellites in view. Usually, the date and time will become available before a valid fix. It takes only 1 satellites to get it, but at least 3 to get a proper position fix.
Files and links:
Tutorial 7 source files.Processor boards.
Graphic LCD Display board.
GPS Receiver board.

