Handling objects while simplifying code to separate .c and .h files

Hey All!

First time poster, long time lurker. I’ve learned a boatload from this site, and have been inspired to start working on building some fun custom stuff! I’ve searched a LOT to try and answer this question, but I’m very stumped.

The current project I’m working on is the first one that it has felt very, very necessary to split the code/functions into separate files to simplify my main(). I’ve been quite successful, based on another post, at creating more simple separated files. However, I’m having a very difficult time when it comes to handling objects of libraries I’ve included, namely <TinyGPS++.h> and .

What I’d like to do, is have my main() cleaned up by making all my gps functions separate. But I can’t for the life of me figure out how to handle the objects required. I just keep getting compiler errors. Declaring/Instantiating objects in different area’s seems to switch up where I get the errors, but that just tells me that its all WRONG, haha. My gut tells me this is where pointer’s come in, but I don’t understand them despite my best efforts. My hope is, you fine gentlemen can point(pun intended) me in the right direction. And perhaps this, if pointers are the solution, will make them finally “Click” for me!

Here is my original program, that executes perfectly.

GPSTest.ino

#include <TinyGPS++.h>
#include <SoftwareSerial.h>

SoftwareSerial serial_connection(0, 1); //create SS object on RX = pin 0, TX = pin 1
TinyGPSPlus gps; //create a tinygps++ object

void setup() {
  Serial.begin(9600);
  serial_connection.begin(9600);
  Serial.println("GPS Start");
}

void loop() {
  while(serial_connection.available()){
    gps.encode(serial_connection.read()); //This feeds the serial NEMA data into the "GPS" library to be decoded
  }

  if(gps.location.isUpdated()){
    Serial.println("Satellite Count: ");
    Serial.println(gps.satellites.value());
    Serial.println("Latitude: ");
    Serial.println(gps.location.lat(), 6);
    Serial.println("Longitude: ");
    Serial.println(gps.location.lng(), 6);
    Serial.println("Altitude Feet: ");
    Serial.println(gps.altitude.feet());
    Serial.println("Time:");
    Serial.println(gps.time.value());
    Serial.println("");
  }
}

Long story short, it starts a serial connection between the computer and then the gps, queries the gps for data, and outputs it through serial to the computer. Simple enough.

Now in order for it to do this, to the best of my understanding, I use a <SoftwareSerial.h> object and a <TinyGPSPlus.h> object, which I instantiate before the setup() function on these lines:

SoftwareSerial serial_connection(0, 1); //create SS object on RX = pin 0, TX = pin 1
TinyGPSPlus gps; //create a tinygps++ object

I then use these repeatedly in the loop to store/recall data.

Onto my attempt to break the code down:

GPSTestBreakdown.ino

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include "gps.c"
#include "gps.h"

SoftwareSerial ssGPS(0, 1); //create SS object on RX = pin 0, TX = pin 1
TinyGPSPlus gps; //create a tinygps++ object 

void setup() {
  Serial.begin(9600);
  beginGPS(); //run start gps communication function
  Serial.println("GPS Start");
}

void loop() {
  getGPS(); //Run's a while loop to grab all available NMEA data and store it into TinyGPS++ Object "gps"
  printGPS(); //Print GPS data to serial if it's updated
}

then my gps.c file:

/* This gps.c file will serve as the container for handling
 *  all of the gps functions within the main() function.
 */

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include "gps.h"

SoftwareSerial ssGPS(0,1); //create SS object on RX = pin 0, TX = pin 1
TinyGPSPlus gps; //create a tinygps++ object 


void beginGPS(){
  ssGPS.begin(9600); //start communicating with ssGps to capture gps data from unit
}

void endGPS(){
  ssGPS.stop(); //end communication with the gps unit
}

void getGPS(){
  while(ssGPS.available()){
    gps.encode(ssGPS.read()); //this feeds the serial NMEA data into the "GPS" object to be decoded
  }
}

void printGPS(){
  if(gps.location.isUpdated()){
    Serial.println("Satellite Count: ");
    Serial.println(gps.satellites.value());
    Serial.println("Latitude: ");
    Serial.println(gps.location.lat(), 6);
    Serial.println("Longitude: ");
    Serial.println(gps.location.lng(), 6);
    Serial.println("Altitude Feet: ");
    Serial.println(gps.altitude.feet());
    Serial.println("Time:");
    Serial.println(gps.time.value());
    Serial.println("");
  } else {
    Serial.println("GPS Data not Updated");
  }
}

and finally my header file, gps.h:

//gps.h

#ifndef __GPS_HEADER__
#define __GPS_HEADER__

#include <TinyGPS++.h>
#include <SoftwareSerial.h>

void beginGPS();
void endGPS();
void getGPS();
void printGPS();

#endif

In this state, if I attempt to instantiate the SoftwareSerial/TinyGPSPlus objects in the gps.c file, I get the compiler error "Unknown type name ‘SoftwareSerial’. It also feels wrong because I’m instantiating the same object in two different places…which feels like a nono.

But if I don’t attempt to instantiate them, I get the following: “‘ssGPS’ undeclared (first use in this function)”. I assume this is because the object it’s trying to use, doesn’t exist yet, at least not within its scope.

As I said, my gut is telling me something to do with pointers…any help would be greatly appreciated!

Hello,

rockclimber399:
then my gps.c file:

That needs to be "gps.cpp".

In general, you should always use the C++ compiler.

To answer your specific question: All identifiers (like “ssGPS”) must be declared before they are used. Your INO file declares it, but the GPS.c file cannot “see” that declaration, because it is “external” to the GPS.c file. And like Coding Badly so quickly observed, it should be gps.cpp

You must either (1) add the external declaration to the GPS.cpp file, or (2) make a GPSTestBreakdown.h file with that declaration and include it in the GPS.cpp file.

For option 1, just add this line to your GPS.cpp file:

    extern SoftwareSerial ssGPS;

And now you must also include SoftwareSerial in the GPS.c file, too.

For option 2, this could be the GPSTestBreakdown.h file:

#ifndef GPSTESTBREAKDOWN_H  // the typical "guard" for header files
#define GPSTESTBREAKDOWN_H

#include <SoftwareSerial.h>
extern SoftwareSerial ssGPS;

#endif // of the guard

Then include it in your gps.cpp file:

#include "GPSTestBreakdown.h"

Do the same thing for the GPS variable.

This answers your specific question. However…

A serious mistake is using SoftwareSerial on the HardwareSerial port, pins 0 & 1. Just use Serial. If you want, you can define an “alias” for Serial that makes your sketch easier to read:

    #define gpsPort Serial

Then loop would look like this:

void loop() {
  while(gpsPort.available()){
    gps.encode(gpsPort.read()); //This feeds the serial NMEA data into the "GPS" library to be decoded
  }

That actually reads GPS characters from Serial.

Even if you wanted to use two other pins, you should be aware that SoftwareSerial is very inefficient, because it disables interrupts for long periods of time. This can interfere with other libraries or prevent receiving data on Serial. It cannot transmit and receive at the same time. You should seriously consider other alternatives. Using pins 0 & 1 is absolutely the best, but comes with the inconvenience of having to disconnect pin 0 to upload a new sketch over USB.

You should also be careful about the loop structure. With GPS devices, you have to constantly watch for GPS characters. If your program tries to print too much (it’s ok so far), or if your sketch or a library uses delay, the Serial input buffer will overflow if you don’t read the characters quickly enough. Several variations of this problem are described here.

In general, you should wait for a GPS update (normally once per second), and then use the GPS data at that time. For the rest of the second, no GPS operations are necessary. And code that depends on new GPS data does not need to run, either.

These problems (and others) led me to write a GPS library, NeoGPS. All the examples are structured to take advantage of the GPS quiet time. This lets you do the most work when the GPS isn’t sending data. This helps avoid input buffer overflow.

A secondary issue is that other libraries do not group all the GPS data from one second into one update. Your sketch could be printing the location from one GPS second with the altitude and time from a different second. I call this coherency. NeoGPS will give you new fix data exactly once per second. Other libraries can give you several updates per second as different “sentences” are received from the GPS device.

Just for fun, here is your sketch, modified to use NeoGPS and NeoSWSerial (one of the alternatives described above):

#include <NMEAGPS.h>

#define gpsPort Serial // read GPS data on RX = pin 0, TX = pin 1
NMEAGPS gps;
const float FEET_PER_METER = 3.28084;

void setup() {
  Serial.begin(9600);
  // gpsPort.begin(9600); //<-- not necessary, it's already started
  Serial.println( F("GPS Start") );
}

void loop() {
  while(gps.available( gpsPort )) {//This feeds the serial NMEA data into the "GPS" library to be decoded...
    gps_fix fix = gps.read(); // ... until a complete fix structure is ready.

    Serial.println( F("Satellite Count: ") );
    Serial.println( fix.satellites );
    Serial.println( F("Latitude: ") );
    Serial.println( fix.latitude(), 6);
    Serial.println( F("Longitude: ") );
    Serial.println( fix.longitude(), 6);
    Serial.println( F("Altitude Feet: ") );
    Serial.println( fix.altitude() * FEET_PER_METER ); // altitude() is in meters
    Serial.println( F("Time:") );
    Serial << fix.dateTime; // a C++ "streaming" operator for the dateTime structure
    Serial.println();
    Serial.println();
  }
}

Notice that there are not two separate tests, one for GPS characters and one for location updated. If a new GPS update is available, it simply prints the new data.

Your original sketch uses 8878 bytes of program space and 608 bytes of RAM.
Without SoftwareSerial, your sketch would use 7004/493 bytes.
The NeoGPS version uses 7522 bytes of program space and 230 bytes of RAM, one-third of the RAM. The program size is a little larger because the NeoGPS time incorporates real date/time calculations, not just the characters of the date/time. You’ll need the calculation capabilities if you want to shift the date/time to your local timezone.

But even this has another problem: it tries to print values even if the GPS didn’t send a value. For example, the GPS device may not know the altitude yet. Here is the same sketch with extra tests to make sure the values are really valid:

#include <NMEAGPS.h>

#define gpsPort Serial // read GPS data on RX = pin 0, TX = pin 1
NMEAGPS gps;
const float FEET_PER_METER = 3.28084;

void setup() {
  Serial.begin(9600);
  // gpsPort.begin(9600); //<-- not necessary, it's already started
  Serial.println( F("GPS Start") );
}

void loop() {
  while(gps.available( gpsPort )) {//This feeds the serial NMEA data into the "GPS" library to be decoded...
    gps_fix fix = gps.read(); // ... until a complete fix structure is ready.

    if (fix.valid.satellites) {
      Serial.println( F("Satellite Count: ") );
      Serial.println( fix.satellites );
    }
    if (fix.valid.location) {
      Serial.println( F("Latitude: ") );
      Serial.println( fix.latitude(), 6);
      Serial.println( F("Longitude: ") );
      Serial.println( fix.longitude(), 6);
    }
    if (fix.valid.altitude) {
      Serial.println( F("Altitude Feet: ") );
      Serial.println( fix.altitude() * FEET_PER_METER ); // altitude() is in meters
    }
    if (fix.valid.date || fix.valid.time) {
      Serial.println( F("Time:") );
      Serial << fix.dateTime; // a C++ "streaming" operator for the dateTime structure
      Serial.println();
    }
    Serial.println();
  }
}

If you’d like to try it, NeoGPS is available from the Arduino IDE Library Manager, under the menu Sketch → Include Library → Manage Libraries.

Cheers,
/dev

Thanks for the awesome reply! I made some headway and took your advice on ridding myself of the SoftwareSerial method.

Adding the extern allowed me to get past that compiler error, but as I suspected, there were many more things at play here.

I’m very interested in your NeoGPS function since I’m looking for a clean method to extract ONLY time, altitude, lat, long. TinyGPS++ is more complex than necessary, but it allows a platform for me to get the higher level issues I’m having tested since I’m already privy to how it works.

I’ve had to essentially rewrite my code from the ground up due to a lack of proper class syntax, and many other issues. After watching LOTS more videos and reading about library and .h file creation, as well as pointers, I’ve come up with something that’s at least compiling. But not returning the expected result.

Modeled after some of the code provided in THIS LINK, I came to find out that Serial, Serial1, Serial2, etc… are all objects of the class “HardwareSerial”. I’m using a teensy 3.5, so I have pins 0,1 available even with usb connected under “Serial1”. I tried to pass by reference “Serial1” into an initializer function within my gpsUltimate class, but it doesn’t seem to be working.

I’d really like to get this “passing of objects” concept locked down so that I can implement it with the 4-5 other sensors/motors I’ll need to include in this project, so this is proving to be an excellent learning exercise.

Here is the “Updated” version of my earlier design, with many changes to formatting and removal of the softwareserial stuff.

//GPSTestBreakdown3.ino

#include "gpsUltimate.h"
#include <TinyGPS++.h>


TinyGPSPlus tinyGPS; //instantiate object of TinyGPS++ class
gpsUltimate gps(&tinyGPS); //instantiate object of gpsUltimate class


void setup() {
  Serial.begin(9600);
  //Serial1.begin(9600);
  gps.initializePort(&Serial1, 9600);
  
}

void loop() {
  gps.update();

/*  
  while(Serial1.available()){
    tinyGPS.encode(Serial1.read()); //This feeds the serial NEMA data into the "GPS" library to be decoded
  }

  if(tinyGPS.location.isUpdated()){
    Serial.println("Satellite Count: ");
    Serial.println(tinyGPS.satellites.value());
    Serial.println("Latitude: ");
    Serial.println(tinyGPS.location.lat(), 6);
    Serial.println("Longitude: ");
    Serial.println(tinyGPS.location.lng(), 6);
    Serial.println("Altitude Feet: ");
    Serial.println(tinyGPS.altitude.feet());
    Serial.println("Time:");
    Serial.println(tinyGPS.time.value());
    Serial.println("");
  }
*/  

}

As you can see, i’ve commented the “Simple” version of my initializePort() and update() functions. This was for testing purposes. Obviously if i comment out the use of my “gpsUltimate” class, and uncomment the direct use of Serial1 and the tinyGPS object, I get the expected results, which is ~1sec intervals of gps data printed. I would expect that if I used the gps.initializePort(&Serial1, 9600) function instead of doing it directly, but uncommented the update/print section within the loop, that I would have communication started on the Serial1 port, and be able to utilize it. Obviously, poor practice, but its escaping me why it wouldn’t work.

on to the guts of my library attempt, header first…

#ifndef GPSULTIMATE_H
#define GPSULTIMATE_H

/////////////////////////////////
////////Class Definitions////////
/////////////////////////////////

#include <Arduino.h>
#include <TinyGPS++.h>

class gpsUltimate{
  public:
    //Constructor//
    gpsUltimate(TinyGPSPlus *tinyGPSIn);

    //gps device communication methods//
    void initializePort(HardwareSerial *serIn); //Pass "Serial" object in to set port. This is the harwareserial, I.E. "Serial1, Serial2"
    void initializePort(HardwareSerial *serIn, int baudIn); //Pass an object of "serial" and baud to set both.
    void begin(int baudIn); //Start comunication on gps port at specified baud.
    void update(); //encode TinyGPS++ with information from NMEA data
    void print(); //print the TinyGPS++ data to serial0(teensy
    
  private:
    HardwareSerial *_gpsPort;
    int _baud;
    TinyGPSPlus *_tinyGPS;
};

#endif

and my .cpp

//gpsUltimate.cpp

//////////////////////////////////////////////
////////////Member Definitions////////////////
//////////////////////////////////////////////
#include "gpsUltimate.h"


gpsUltimate::gpsUltimate(TinyGPSPlus *tinyGPSIn){
  _gpsPort = NULL;
  _tinyGPS = tinyGPSIn;
}

//initialize GPS Port with Serial object
void gpsUltimate::initializePort(HardwareSerial *serIn){
  _gpsPort = serIn;
  _gpsPort->begin(_baud);
}
//initialize GPS Port with Serial object and baud rate
void gpsUltimate::initializePort(HardwareSerial *serIn, int baudIn){
  _gpsPort = serIn;
  _gpsPort->begin(_baud);
}

//start communication with GPS 
void gpsUltimate::begin(int baudIn){
  _gpsPort->begin(_baud); //start communicating with gps to capture gps data from unit
}

//update object of TinyGPS++
void gpsUltimate::update(){
  Serial.println("Querying...");
  if(_gpsPort->available()){
    while(_gpsPort->available()){
      _tinyGPS->encode(_gpsPort->read()); //This feeds the serial NEMA data into the "GPS" library to be decoded
    }
  } else {
    Serial.println("_gpsPort unavailable"); 
  }

  if(_tinyGPS->location.isUpdated()){
    Serial.println("Satellite Count: ");
    Serial.println(_tinyGPS->satellites.value());
    Serial.println("Latitude: ");
    Serial.println(_tinyGPS->location.lat(), 6);
    Serial.println("Longitude: ");
    Serial.println(_tinyGPS->location.lng(), 6);
    Serial.println("Altitude Feet: ");
    Serial.println(_tinyGPS->altitude.feet());
    Serial.println("Time:");
    Serial.println(_tinyGPS->time.value());
    Serial.println("");
  }
}

//print TinyGPS++ to "Emulated Serial" or Serial 1
void gpsUltimate::print(){
  if(_tinyGPS->location.isUpdated()){
    Serial.println("Satellite Count: ");
    Serial.println(_tinyGPS->satellites.value());
    Serial.println("Latitude: ");
    Serial.println(_tinyGPS->location.lat(), 6);
    Serial.println("Longitude: ");
    Serial.println(_tinyGPS->location.lng(), 6);
    Serial.println("Altitude Feet: ");
    Serial.println(_tinyGPS->altitude.feet());
    Serial.println("Time:");
    Serial.println(_tinyGPS->time.value());
    Serial.println("");
  }
}

as you can see, I have the print() function essentially included in the update() because when my program initially output nothing after compiling I combined them for troubleshooting. If i can get the update function running appropriately, I assume i’ll be able to split the print function out, which actually will end up being a “log to sd card” kind of function.

As the program sits, it compiles fine, but just spams “Querying…”, then “_gpsPort unavailable” which are Serial output’s I added for testing. This tells me that the communication isn’t opening on the requested Serial1 as expected.

Thanks again for the awesome in depth reply. The amount of learning it sparked was perfect. I feel like I’m actually on the verge of cracking this thing.

it just spams "Querying...", then "_gpsPort unavailable"... This tells me that the communication isn't opening on the requested Serial1 as expected.

Actually, it is. You are printing too much in this function:

void gpsUltimate::update(){
Serial.println("Querying...");
if(_gpsPort->available()){
while(_gpsPort->available()){
_tinyGPS->encode(_gpsPort->read()); //This feeds the serial NEMA data into the "GPS" library to be decoded
}
} else {
Serial.println("_gpsPort unavailable");
}