GPS EM-406 example

I got my GPS module working with some filtering for the right lines. I program mainly by trial and error so the code might not be elegant or by the book. But it works. I started with the softserial example from Heather: http://www.arduino.cc/en/Tutorial/SoftwareSerial. I changed it here and there and made it to filter out a specific NMEA rule.

I was quite suprised when I discovered how easy it is to use avr-libc library.

Any comments are welcome.

//Created August 15 2006
//Heather Dewey-Hagborg
//http://www.arduino.cc
//reworked to GPS reader
//Dirk, december 2006

#include <ctype.h>
#include <string.h>

#define bit9600Delay 84  
#define halfBit9600Delay 42
#define bit4800Delay 188 
#define halfBit4800Delay 94 

byte rx = 6;
byte tx = 7;
byte SWval;
char dataformat[7] = "$GPRMC";
char messageline[80] = "";
int i= 0;

void setup() {
  pinMode(rx,INPUT);
  pinMode(tx,OUTPUT);
  digitalWrite(tx,HIGH);
  digitalWrite(13,HIGH); //turn on debugging LED
  SWprint('h');  //debugging hello
  SWprint('i');
  SWprint(10); //carriage return
  Serial.begin(9600);
}

void SWprint(int data)
{
  byte mask;
  //startbit
  digitalWrite(tx,LOW);
  delayMicroseconds(bit4800Delay);
  for (mask = 0x01; mask>0; mask <<= 1) {
    if (data & mask){ // choose bit
      digitalWrite(tx,HIGH); // send 1
    }
    else{
      digitalWrite(tx,LOW); // send 0
    }
    delayMicroseconds(bit4800Delay);
  }
  //stop bit
  digitalWrite(tx, HIGH);
  delayMicroseconds(bit4800Delay);
}

char SWread()
{
  byte val = 0;
  while (digitalRead(rx));
  //wait for start bit
  if (digitalRead(rx) == LOW) {
    delayMicroseconds(halfBit4800Delay);
    for (int offset = 0; offset < 8; offset++) {
      delayMicroseconds(bit4800Delay);
      val |= digitalRead(rx) << offset;
    }
    //wait for stop bit + extra
    delayMicroseconds(bit4800Delay); 
    delayMicroseconds(bit4800Delay);
    return val;
  }
}
void char2string()
{
  i = 0;
  messageline[0] = SWread();
  if (messageline[0] == 36) //string starts with $
  {
    i++;
    messageline[i] = SWread();
    while(messageline[i] != 13 & i<80) //carriage return or max size
    {
      i++;
      messageline[i] = SWread();
    }
    messageline[i+1] = 0; //make end to string
  } 
}
void loop()
{
  digitalWrite(13,HIGH);
 //only print string with the right dataformat
  char2string();
  if (strncmp(messageline, dataformat, 6) == 0 & i>4)
  {
    for (int i=0;i<strlen(messageline);i++)
    {
      Serial.print(messageline[i], BYTE);
    }
  }
  
  //Serial.print(SWread(),BYTE); //use this to get all GPS output, comment out from char2string till here
  
}

This is a good example of software serial I/O. I did spot something potentially very inefficient:

for (int i=0;i<strlen(messageline);i++)

this causes strlen() to be called on each iteration, counting how many characters there are in the string. If the string is very short, it does not matter, much, but as the string gets longer and longer, the amount of CPU time consumed by the loop goes up as the square of the number of characters. This is because there are N characters, each strlen takes order(N) time, and there are N loops, resulting in order(N*N). Instead, I would recommend this:

for (int i=0; messageline != '\0'; i++)

This is a great contribution. I have been trolling the forum lately looking for Arduino> Hardware info to use in teaching, as my students always want to hook up the oddest things... In that light, I added your GPS code example to the Playground. Thanks.

for (int i=0; messageline != '\0'; i++) [/quote]
I never thought about this construction, great tip, thanks!
> This is a great contribution. I have been trolling the forum lately looking for Arduino> Hardware info to use in teaching, as my students always want to hook up the oddest things... In that light, I added your GPS code example to the Playground.
This is a very simple program but it is exactly the kind of program that is useful for me to get started. So, now I finally managed to make up something myself I had to contribute.

for (int i=0; messageline != '\0'; i++) [/quote]
I never thought about this construction, great tip, thanks!
> This is a great contribution. I have been trolling the forum lately looking for Arduino> Hardware info to use in teaching, as my students always want to hook up the oddest things... In that light, I added your GPS code example to the Playground.
[/quote]
This is puzzling ... in char2string() you tie off messageline with a 0. So why are we adding code to do the byte-banging of the string out tha already exists in the Serial core library???? Why not just replace this code
//----------------------------------------------------------------

  • char2string();*
  • if (strncmp(messageline, dataformat, 6) == 0 & i>4)*
  • {*
    _ for (int i=0; messageline != '\0'; i++)_
    * {*
    _ Serial.print(messageline*, BYTE);
    }
    }
    //-------------------------------------------------------
    with this code
    //----------------------------------------------------------------
    char2string();
    if (strncmp(messageline, dataformat, 6) == 0 & i>4)
    {
    Serial.println(messageline);
    }
    //-------------------------------------------------------*
    Or did I miss something?
    cheers ... BBR_

That might be easier. Sometimes you just don't see the easier solution

First post - cool hardware BTW...

Thanks bigengineer and everyone else - I now have my EM406A talking to my Arduino Decimilla (4 wires, 5V/GND/TX/RX) using the code posted. I even tried all three examples of how to print the resulting strings to the Debug Window. My problem is that they all look like this:

These are printing ALL strings BTW. I understand that the conversation from the GPS unit is 4800 baud (hence the 4800 delays) and received thru Digital pin 6 on the Arduino. I also understand that the strings are output via the onboard debug serial lines (hardware uart that the USB FTDI chip uses - with the following in Setup(): Serial.begin(9600); ) hence them showing up at 9600 baud in the Serial Debug Monitor. I get the same garbage if I set the output to 4800 via Serial.begin(4800); in Setup();

What am I missing here? TIA

mopowered

What am I missing here? TIA

That looks very close to NEMA output so your very close.
The problem must be with the serial stuff.

@mopowered

I agree with Cheater: looks like you have the commas in between the data, but not the data in corect form. Check to see if your data needs to go through an inverter like the 74LS04 (sometimes you need to invert RS232 TTL-level data in order to read it), and also check the serial port speed in pull-down menu in the serial monitor window.

D

Strange, the comma's and $'s are ok but the characters are not. Can you post your code? Or did you just use the code without any modifications?

One of these days I should make an example program with the software serial library.

Sure the following is the code used:

//Created August 15 2006
//Heather Dewey-Hagborg
//http://www.arduino.cc
//reworked to GPS reader
//Dirk, december 2006

#include <ctype.h>
#include <string.h>

#define bit9600Delay 84  
#define halfBit9600Delay 42
#define bit4800Delay 188 
#define halfBit4800Delay 94 

byte rx = 6;
byte tx = 7;
byte SWval;
char dataformat[7] = "$GPRMC";
char messageline[80] = "";
int i= 0;

void setup() {
  pinMode(rx,INPUT);
  pinMode(tx,OUTPUT);
  digitalWrite(tx,HIGH);
  digitalWrite(13,HIGH); //turn on debugging LED
  SWprint('h');  //debugging hello
  SWprint('i');
  SWprint(10); //carriage return
  Serial.begin(9600);  // this is used to echo what is read from the GPS out the USB->SerialDebugMonitor
}

void SWprint(int data)
{
  byte mask;
  //startbit
  digitalWrite(tx,LOW);
  delayMicroseconds(bit4800Delay);
  for (mask = 0x01; mask>0; mask <<= 1) {
    if (data & mask){ // choose bit
      digitalWrite(tx,HIGH); // send 1
    }
    else{
      digitalWrite(tx,LOW); // send 0
    }
    delayMicroseconds(bit4800Delay);
  }
  //stop bit
  digitalWrite(tx, HIGH);
  delayMicroseconds(bit4800Delay);
}

char SWread()
{
  byte val = 0;
  while (digitalRead(rx));
  //wait for start bit
  if (digitalRead(rx) == LOW) {
    delayMicroseconds(halfBit4800Delay);
    for (int offset = 0; offset < 8; offset++) {
      delayMicroseconds(bit4800Delay);
      val |= digitalRead(rx) << offset;
    }
    //wait for stop bit + extra
    delayMicroseconds(bit4800Delay); 
    delayMicroseconds(bit4800Delay);
    return val;
  }
}
void char2string()
{
  i = 0;
  messageline[0] = SWread();
  if (messageline[0] == 36) //string starts with $
  {
    i++;
    messageline[i] = SWread();
    while(messageline[i] != 13 & i<80) //carriage return or max size
    {
      i++;
      messageline[i] = SWread();
    }
    messageline[i+1] = 0; //make end to string
  } 
}

void loop()
{
  digitalWrite(13,HIGH);
  
  //
  // 1st way to print only the $GPRMC message
  //
  
  //only print string with the right dataformat
  // char2string();
  // if (strncmp(messageline, dataformat, 6) == 0 & i>4)
  // {
  //   for (int i=0; messageline[i] != '\0'; i++) 
  //   {
  //      Serial.print(messageline[i], BYTE);
  //   }
  // }
  
  //
  // 2nd way to print only the $GPRMC message
  //
  
  //   char2string();
  //   if (strncmp(messageline, dataformat, 6) == 0 & i>4)
  //   {
  //     Serial.println(messageline);
  //   } 
  
  //
  // way to print ALL RECEIVED MESSAGES!
  //
  
   Serial.print(SWread(),BYTE); //use this to get all GPS output, comment out from char2string till here
  
}

The last bytewise print of the raw stream gave the best results of the 3 different ways to print output strings.

Must be a problem in the SoftwareSerial read routine because I got the exact same results (unreadable nmea-like strings) with the following code - here I'm only using the software serial for the read part and the hardware uart for the write results to debug console. You'll notice in the main loop() the same results come from either the readbyte/printbyte method or readline/printline method (commented out). I also changed the name of the char2string function to getLine:

#include <ctype.h>

#define bit9600Delay 84  
#define halfBit9600Delay 42
#define bit4800Delay 188 
#define halfBit4800Delay 94 

byte rx = 6;   // EM406A GPS tx line connected to pin 6 on Arduino (4800baud)
byte SWval;
char line[80]="";

void setup() {
  pinMode(rx,INPUT);
  digitalWrite(13,HIGH);     // turn on debugging LED
  Serial.begin(4800);        // Setup USB serial port monitor to 4800baud
}

int SWread()
{
  byte val = 0;
  while (digitalRead(rx));
  //wait for start bit
  if (digitalRead(rx) == LOW) {
    delayMicroseconds(halfBit4800Delay);
    for (int offset = 0; offset < 8; offset++) {
     delayMicroseconds(bit4800Delay);
     val |= digitalRead(rx) << offset;
    }
    //wait for stop bit + extra
    delayMicroseconds(bit4800Delay); 
    delayMicroseconds(bit4800Delay);
    return val;
  }
}

void getLine()
{
  int i = 0;
  line[0] = SWread();
  if (line[0] == 36) //string starts with $
  {
    i++;
    line[i] = SWread();
    while(line[i] != 13 & i<80) //carriage return or max size
    {
      i++;
      line[i] = SWread();
    }
    line[i+1] = 0; //make end to string
  } 
} // end getLine()


void loop()
{
    SWval = SWread(); 
    Serial.print(SWval);
    
    // getLine();
    // Serial.println(line);
    
}

Am currently using the code at the following URL with perfect results using the EM-406 instead of the Parallax module (also connected via 3 wires pwr/gnd/tx):

http://www.arduino.cc/playground/Tutorials/GPS

In regards to mopowered's problem, I checked the decimal value of those characters you were getting and it looks like they're all off by 128. Try something like a

if( val > 128 )
return val-128;
else
return val;

It's hacky but it's a quick check to see if you can get good characters from it. I remember running into a problem with chars and bytes before in regards to this 128 offset.

if( val > 128 )
return val-128;
else
return val;

which hopefully your compiler will be clever enough to convert to:

return val & 0x7f;

hej, does i understand this right? it reads the nmea and then seriel print it?

hmm does anyone successfully got a GPS running over the
SoftSerial Port?

Geko

I have an Arduino - Duemilanove 328, I connect the GSM / GPRS Module - SM5100B and GPS Shield with EM-406A.
Shield calls connect to each TX (1) and RX (0). How do I connect power and functioning properly.
Hector Albornoz

had garbled output like everyone else... but now...

Acquired Data

Lat/Long(10^-5 deg): 4312611, -8029204 Fix age: 298ms.
Lat/Long(float): 43.12611, -80.29204 Fix age: 415ms.
Date(ddmmyy): 130111 Time(hhmmsscc): 1090400 Fix age: 544ms.
Date: 1/13/2011 Time: 1:9:4.0 Fix age: 648ms.
Alt(cm): 22040 Course(10^-2 deg): 27382 Speed(10^-2 knots): 37
Alt(float): 220.40 Course(float): 273.82
Speed(knots): 0.37 (mph): 0.43 (mps): 0.19 (kmph): 0.69
Stats: characters: 5699 sentences: 41 failed checksum: 4

Problem was that data was INVERTED...

do this:
NewSoftSerial nss(2, 3, true);

Here is my modified code:

#include <NewSoftSerial.h>
#include <TinyGPS.h>

/* This sample code demonstrates the normal use of a TinyGPS object.
   It requires the use of NewSoftSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 2(rx) and 3(tx).
*/

TinyGPS gps;
NewSoftSerial nss(2, 3, true); // my gps data is inverted!!

void gpsdump(TinyGPS &gps);
bool feedgps();
void printFloat(double f, int digits = 2);

void setup()
{
  Serial.begin(4800);
  nss.begin(4800);
  
  Serial.print("Testing TinyGPS library v. "); Serial.println(TinyGPS::library_version());
  Serial.println("by Mikal Hart");
  Serial.println();
  Serial.print("Sizeof(gpsobject) = "); Serial.println(sizeof(TinyGPS));
  Serial.println();
}

void loop()
{
  bool newdata = false;
  unsigned long start = millis();

  // Every 5 seconds we print an update
  while (millis() - start < 5000)
  {
    if (feedgps())
      newdata = true;
  }
  
  if (newdata)
  {
    Serial.println("Acquired Data");
    Serial.println("-------------");
    gpsdump(gps);
    Serial.println("-------------");
    Serial.println();
  }
}

void printFloat(double number, int digits)
{
  // Handle negative numbers
  if (number < 0.0)
  {
     Serial.print('-');
     number = -number;
  }

  // Round correctly so that print(1.999, 2) prints as "2.00"
  double rounding = 0.5;
  for (uint8_t i=0; i<digits; ++i)
    rounding /= 10.0;
  
  number += rounding;

  // Extract the integer part of the number and print it
  unsigned long int_part = (unsigned long)number;
  double remainder = number - (double)int_part;
  Serial.print(int_part);

  // Print the decimal point, but only if there are digits beyond
  if (digits > 0)
    Serial.print("."); 

  // Extract digits from the remainder one at a time
  while (digits-- > 0)
  {
    remainder *= 10.0;
    int toPrint = int(remainder);
    Serial.print(toPrint);
    remainder -= toPrint; 
  } 
}

void gpsdump(TinyGPS &gps)
{
  long lat, lon;
  float flat, flon;
  unsigned long age, date, time, chars;
  int year;
  byte month, day, hour, minute, second, hundredths;
  unsigned short sentences, failed;

  gps.get_position(&lat, &lon, &age);
  Serial.print("Lat/Long(10^-5 deg): "); Serial.print(lat); Serial.print(", "); Serial.print(lon); 
  Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms.");
  
  feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors

  gps.f_get_position(&flat, &flon, &age);
  Serial.print("Lat/Long(float): "); printFloat(flat, 5); Serial.print(", "); printFloat(flon, 5);
  Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms.");

  feedgps();

  gps.get_datetime(&date, &time, &age);
  Serial.print("Date(ddmmyy): "); Serial.print(date); Serial.print(" Time(hhmmsscc): "); Serial.print(time);
  Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms.");

  feedgps();

  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
  Serial.print("Date: "); Serial.print(static_cast<int>(month)); Serial.print("/"); Serial.print(static_cast<int>(day)); Serial.print("/"); Serial.print(year);
  Serial.print("  Time: "); Serial.print(static_cast<int>(hour)); Serial.print(":"); Serial.print(static_cast<int>(minute)); Serial.print(":"); Serial.print(static_cast<int>(second)); Serial.print("."); Serial.print(static_cast<int>(hundredths));
  Serial.print("  Fix age: ");  Serial.print(age); Serial.println("ms.");
  
  feedgps();

  Serial.print("Alt(cm): "); Serial.print(gps.altitude()); Serial.print(" Course(10^-2 deg): "); Serial.print(gps.course()); Serial.print(" Speed(10^-2 knots): "); Serial.println(gps.speed());
  Serial.print("Alt(float): "); printFloat(gps.f_altitude()); Serial.print(" Course(float): "); printFloat(gps.f_course()); Serial.println();
  Serial.print("Speed(knots): "); printFloat(gps.f_speed_knots()); Serial.print(" (mph): ");  printFloat(gps.f_speed_mph());
  Serial.print(" (mps): "); printFloat(gps.f_speed_mps()); Serial.print(" (kmph): "); printFloat(gps.f_speed_kmph()); Serial.println();

  feedgps();

  gps.stats(&chars, &sentences, &failed);
  Serial.print("Stats: characters: "); Serial.print(chars); Serial.print(" sentences: "); Serial.print(sentences); Serial.print(" failed checksum: "); Serial.println(failed);
}
  
bool feedgps()
{
  while (nss.available())
  {
    if (gps.encode(nss.read()))
      return true;
  }
  return false;
}

Problem was that data was INVERTED...

do this:
NewSoftSerial nss(2, 3, true);

This is not the solution to everybody. Apparently the EM-406 needs not to be inverted. Mine and lots of others with this module have them work fine.