Go Down

Topic: Model Rocket GPS Telemetry Tracker w/ Hc-12 code help (Read 3956 times) previous topic - next topic

willhartley

Hey everyone! I tried not to come here for help but honestly I'm stumped and I know it is probably something super easy… any help will be appreciated and if you want, I'll give you the best hug EVER…

Right to it. I am trying to get my code to work. A little about the project first. I am attempting to build my own GPS Tracker/Telemetry logger that transmits data wirelessly on the 433mHz range so I can track it real time with the raw NMEA sentences. So far, I have 2 Arduino Unos, a neo-6m gps tracker and 2 HC-12 wireless adapters. The Arduino in the rocket calculates everything needed and only sends the sentences that I want through the HC-12 which is GGA and RMC. The second Uno will receive the data if distance isn't too far, output to serial, which will plot points on Google Earth,


What's happening so far? I get the first Arduino to read the GPS data. It takes it in and perfectly sorts through it [if (GPSReadBuffer.startsWith("$GPRMC")||....("$GPGGA"))].  It outputs to the serial no problem but when sending through the HC-12, I can only get ONE sentence to populate, RMC. I believe I am filling the buffer on the TX uno because when I make a delay in the code and set it high, I get other NMEA sentences somehow through the serial, if only for a short moment.
I will post my original code that works, tracks everything but only sends one sentence through HC-12 but both through serial. The second code is sort of a v2 but somehow breaks and only sends the same stale data over and over.


Some people are going to say, why you don't just use TinyGps++. I have tried a lot of these libraries and they all offer great bells and whistles for parsing the data but what I want is the RAW nmea sentence to go through serial, but not all just 2 and I have yet to figure out how to do that with TinyGps++. I can get the pretty data, just not the raw nmea sentences that it encodes. Later on, I will add code for TinyGps to read the data and then make it pretty by outputting to an sd card.


Please help! I have been stuck and have been trying to do this 100% on my own. I'm not a noob but I think I lack knowledge on higher functions to make this possible. All code attached.

Please check my gist for the code files. Too long to post here

ArduinoTX_GPS v1 - listed below
ArduinoTX_GPS_v2.5 - github
ArduinoRX_GPS_v1.1 - github


ArduinoTX_GPS
Code: [Select]
#include <SoftwareSerial.h>

#define setPin 6 // "SET" Pin on HC12

// Create Software Serial Ports for HC12
// Software Serial ports Rx and Tx are opposite the HC12 Rxd and Txd
SoftwareSerial HC12(5, 4); // HC-12 TX Pin, HC-12 RX Pin
SoftwareSerial GPS(8,7);   // GPS TX Pin, GPS RX Pin

//--- Begin variable declarations ---//
char byteIn;                                        // Temporary variable
String HC12ReadBuffer = "";                         // Read/Write Buffer 1 for HC-12
String GPSReadBuffer = "";                          // Read/Write Buffer 3 for GPS
boolean HC12End = false;                            // Flag for End of HC12 String
boolean GPSEnd = false;                             // Flag for End of GPS String
//--- End variable declarations ---//


void setup() {
  HC12ReadBuffer.reserve(82);                       // Reserve 82 bytes for message
  GPSReadBuffer.reserve(82);                        // Reserve 82 bytes for longest NMEA sentence
  pinMode(setPin, OUTPUT);             // Output High for Transparent / Low for Command
  digitalWrite(setPin, HIGH);         // Enter Transparent mode
  delay(80);                               // 80 ms delay before operation per datasheet
  HC12.begin(9600);                                 // Open software serial port to HC12 at 9600 Baud
  GPS.begin(9600);                                  // Open software serial port to GPS at 9600 Baud
  GPS.listen();                                    // Listen to GPS instead, not taking commands right now
}

void loop() {
  while (HC12.available()) {                        // If Arduino's HC12 rx buffer has data
    byteIn = HC12.read();                           // Store each character in byteIn
    HC12ReadBuffer += char(byteIn);                 // Write each character of byteIn to HC12ReadBuffer
    if (byteIn == '\n') {                           // At the end of the line
      HC12End = true;                               // Set HC12End flag to true.
    }
  }

  while (GPS.available()) {
    byteIn = GPS.read();
    GPSReadBuffer += char(byteIn);
    if (byteIn == '\n') {
      GPSEnd = true;
    }
  }
  
  if (HC12End) {                                    // If HC12End flag is true
    if (HC12ReadBuffer.startsWith("AT")) {          // Check to see if a command was received
      digitalWrite(setPin, LOW);                 // If true, enter command mode
      delay(80);                                    // Delay before writing command
      HC12.print(HC12ReadBuffer);                   // Send incoming command to HC12
      delay(1000);                                  // Wait 1.0s for reply
      digitalWrite(setPin, HIGH);               // Exit command / enter transparent mode
      delay(80);                                    // Delay before proceeding
      HC12.println("Remote Command Executed");
    }
    if (HC12ReadBuffer.startsWith("GPS")) { // Check for switching to GPS.listen
      GPS.listen();
      //GPSLocal = false;
    }
    HC12ReadBuffer = "";                            // Empty Buffer
    HC12End = false;                                // Reset Flag
  }

  if (GPSEnd) {
    // Options include GPRMC, GPGGA, GPGLL, etc... // Look for target GPS sentence
    if (GPSReadBuffer.startsWith("$GPRMC")||GPSReadBuffer.startsWith("$GPGGA")|) {
        HC12.print("Remote GPS:");                  // Arduino responds to remote request
        HC12.print(GPSReadBuffer);                  // Transmit RMC, GGA, and GLL sentences to remote
 GPSReadBuffer = "";                         // Clear GPS buffer
    } else {
        GPSReadBuffer = "";                         // Delete unwanted strings
    }
    GPSEnd = false;                                 // Reset GPS
  }
  

-dev

Quote
I tried not to come here for help but honestly I'm stumped and I know it is probably something super easy…
Actually, GPS behaviour can be frustratingly, subtly complex.  There are several things interacting in your sketch that will keep it from working.

Code: [Select]
SoftwareSerial HC12(5, 4); // HC-12 TX Pin, HC-12 RX Pin
SoftwareSerial GPS(8,7);   // GPS TX Pin, GPS RX Pin

This will never work.

SoftwareSerial cannot listen to two ports at the same time.  It is very inefficient, because it disables interrupts for the entire time that a character is received, about 1ms.  That's an eternity for a 16MHz Arduino.  While listening to one port, all other activity is disabled.

Please read this for better alternatives.  Much better alternatives.
Quote
String HC12ReadBuffer = "";                         // Read/Write Buffer 1 for HC-12
Don't use String™.  There are many reasons to avoid it, and there are many alternatives that are much better.  Here's a good summary of the pitfalls:  The Evils of Arduino Strings.  Although you may think you have avoided these problems by pre-extending a String, your code does not check for a maximum length.  It is possible for your sketch to exhaust the heap.

Quote
what I want is the RAW nmea sentence to go through serial
Why?

*  The RAW NMEA sentence is 10x to 80x more data than the parsed information fields.  This has the same effect on radio transmission time and, therefore, power consumption and error counts.

*  The NMEA checksum is very weak, so errors during receipt (more likely with a software serial library) and retansmission (over radio) are not detected.

*  The NMEA sentences have duplicate information.  The two you have identified are GGA and RMC.  According to this table, 5 fields are repeated.  This is ~35 characters repeated in each ~80 character sentence.  :P

Quote
Some people are going to say, why you don't just use TinyGps++. I have tried a lot of these libraries and they all offer great bells and whistles for parsing the data but what I want is the RAW nmea sentence to go through serial, but not all just 2 and I have yet to figure out how to do that with TinyGps++. I can get the pretty data, just not the raw nmea sentences that it encodes. Later on, I will add code for TinyGps to read the data and then make it pretty by outputting to an sd card.
Well, I won't suggest TinyGPS++, but I will suggest my NeoGPS library.  It's smaller, faster, more reliable and more accurate than all other GPS libraries.  There are other advantages, related to the GPS quiet time, printing too much data and coherency.  Even if you don't use it, there are lots of tips on the Troubleshooting page and the Installation page.

You can configure it to parse only the GGA and RMC sentences (that is the default).  It will save all fields from those two sentences into one coherent structure.  This fix structure only uses 31 bytes and is suitable for transmitting over the radio... instead of ~160 RAW bytes.

By using one of the NeoXXSerial libraries, you can also parse the GPS data in the background (during the RX character interrupt).  This helps you avoid losing GPS data if an HC-12 or SD operation takes too long (>64ms).  Or if you use delay, like this:

        delay(1000);        // Wait 1.0s for reply

You will lose GPS data during that time.  Your String buffer will not start with valid data.

Here is a NeoGPS + NeoHWSerial + AltSoftSerial version of your sketch:

Code: [Select]
#include <NMEAGPS.h>
NMEAGPS gps; // the NMEA parser
gps_fix fix; // the parsed fields, all in one struct

#define setPin 6 // "SET" Pin on HC12

#include <AltSoftSerial.h>
AltSoftSerial HC12; // RX pin 8, TX pin 9 on an UNO
char HC12buffer[ 80 ];
const size_t HC12_MAX = sizeof(HC12buffer);
size_t HC12count = 0;

#include <NeoHWSerial.h>
#define gpsPort NeoSerial  // Hook GPS TX to pin 0, GPS RX to pin 1

// Parse GPS characters in the background (even during delay)
static void GPSisr( uint8_t c )
{
  gps.handle( c );

} // GPSisr


void setup() {

  pinMode(setPin, OUTPUT);             // Output High for Transparent / Low for Command
  digitalWrite(setPin, HIGH);         // Enter Transparent mode

  delay(80);                               // 80 ms delay before operation per datasheet
  HC12.begin(9600);

  gpsPort.attachInterrupt( GPSisr );
  gpsPort.begin(9600);
}

void loop() {

  while (gps.available()) {
    fix = gps.read();  //  A new structure is all ready, save it for later
  }

  while (HC12.available()) {
    char c = HC12.read();

    // end of line?
    if ((c == '\n') || (c == '\r')) {

      // An empty line?
      if (HC12count > 0) {

        HC12buffer[ HC12count ] = '\0'; // NUL-terminate C string (lowercase "string")

        if (strstr( HC12buffer, "AT" ) == &HC12buffer[0]) {
          // Execute a command

          digitalWrite(setPin, LOW);                // If true, enter command mode
          delay(80);                                // Delay before writing command
          HC12.println( HC12buffer );               // Send incoming command to HC12
          delay(1000);                              // Wait 1.0s for reply
          digitalWrite(setPin, HIGH);               // Exit command / enter transparent mode
          delay(80);                                    // Delay before proceeding
          HC12.println( F("Remote Command Executed") );

        } else if (strstr( HC12buffer, "GPS" ) == &HC12buffer[0]) {
          // Requested GPS data

          HC12.print( F("Remote GPS:") );                  // Arduino responds to remote request
          HC12.write( (uint8_t *) &fix, sizeof(fix) );     // Transmit binary structure to remote
          HC12.println(); // optional
        }
     
        HC12count = 0; // reset counter
      }

    } else if (HC12count < HC12_MAX-1) {
      // save another character, if there's room
      HC12buffer[ HC12count++ ] = c;
    }
  }
}

This addresses all of those issues:

*  It parses GPS data, even during the delay.

*  It avoids the nasty String class and related heap issues.

*  It sends much shorter radio transmissions (they could be even smaller).

*  It can receive HC12 commands at the same time as it is receiving GPS data.

*  It works.  ;)

If you want to try it, all these libraries are available from the Arduino IDE Library Manager, under the menu Sketch -> Include Library -> Manage Libraries.

Cheers,
/dev
Really, I used to be /dev.  :(

willhartley

Cheers!

I honsestly would never have guessed the person that created the neo gps library would comment on my post   :) . I really... REALLY appreciate the time you took to post everything. I have been trying to get your library to work, which it does flawlessly and I love the configuration, but what I need right now is the raw nmea data.

You asked why in your post, that is because in order to plot this information real-time on the fly with an applicatoin like earthbridge, I need to pass these raw strings through serial which is read by another application like google earth to enable live data tracking. With your library and the others, I am able to get the parsed information and output nice and neatly but this destroys my need for real time tracking.

I need to do this in order to launch and follow where my rocket ends up as it may reach 30k+ feet.

Again, thank you a ton for the information and the examples. I will run the code later today and show everyone what happens when I get the data and what happens when it is connected to Earth Bridge and what the output is.

Using adafruit gps library, for some reason, I was stuck in Africa? When Im in Michigan   :smiley-eek-blue:

gfvalvo

You asked why in your post, that is because in order to plot this information real-time on the fly with an applicatoin like earthbridge, I need to pass these raw strings through serial which is read by another application like google earth to enable live data tracking. With your library and the others, I am able to get the parsed information and output nice and neatly but this destroys my need for real time tracking.
Maybe you could use the library and code as suggested to transport the fix data in the gps_fix struct and then recreate the NMEA sentences on the receive side?
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

willhartley

Looking through everything again on your support page, your right! I am getting way too much duplicate data. I really only need the other sentence because it provides me with speed.

Now this changes my dynamic to only needing GGA & VTG fixes.

Now the question will be if I can output the data in a way that makes sense for the app to read for my live tracking.

willhartley

Maybe you could use the library and code as suggested to transport the fix data in the gps_fix struct and then recreate the NMEA sentences on the receive side?
This was something that I was thinking of doing actually! Im not sure what these programs need in order to give an accurate mapping of the cordinates. I thought it was the GGA- VTG ID field that is probably in some if statement in their program. When I try the example neo-gps examples, everything works perfectly, cept I don't get any live data.

Maybe I can create a this in fake sort of like you recommend. If i can assemble fake GGA and VTg sentences with my own data parsed through neo-gps, I may just be able to get this to work.

Because you both are right! Strings are bad and being able to send and receive from one arduino has been difficult so far.


-dev

LOL, you guys...

Great minds, etc.  See attached.  For testing, I tried calling the new routines like this:

Code: [Select]
 while (gps.available()) {
    fix = gps.read();  //  A new structure is all ready, save it for later

    NMEAprintRMC( &Serial, fix ); // or whatever serial port you want to use
    NMEAprintGGA( &Serial, fix );
  }

It *should* display the RMC and GGA sentences for that fix.  So if you can just send the fix structure (31 bytes), the receiver would use those routines to output the "re-created" NMEA sentences.

Cheers,
/dev

P.S.  You also need to get my NeoTee library.
Really, I used to be /dev.  :(

srnet

If your requirement is to receive (on the ground) the raw NMEA sentences coming from the GPS in the rocket then setup the HC12 in the rocket for the appropriate baud rate and connect it direct to the TX output of the GPS. No need for GPS libraries, parsing etc in the rocket at all.

You dont even need a Arduino on the ground to log it, just connect the output of the ground HC12 to an openlog micro SD card logger, which are cheap as chips on eBay.

If you want to track the rocket in real time on the ground just treat the HC12 just as a serial bridge to the rockets GPS, so the ground station can then use faster Arduinos, Due, ESP8266, ESP32, STM etc.

http://www.50dollarsat.info/
http://www.loratracker.uk/

-dev

Oh, yes...

Quote
Using adafruit gps library, for some reason, I was stuck in Africa? When Im in Michigan   :smiley-eek-blue:
That library does not verify the checksum, so you may have been using invalid data.  :(
Really, I used to be /dev.  :(

-dev

Quote from: srnet
If your requirement is to receive (on the ground) the raw NMEA sentences coming from the GPS in the rocket then setup the HC12 in the rocket for the appropriate baud rate and connect it direct to the TX output of the GPS. No need for GPS libraries, parsing etc in the rocket at all.
Well, the disadvantages I listed in Reply #1 still apply...

Something I haven't mentioned so far is that transmitting the NMEA data from the rocket could cause radio "collisions" with the base station if it tries to transmit a command.  I'm not familiar with the HC-12, so I don't know if (1) its baud rate is faster than 9600 (affects transmission time), or (2) it performs its own collision avoidance (network access technique), error detection and correction, or acknowledgements.
Really, I used to be /dev.  :(

willhartley

If your requirement is to receive (on the ground) the raw NMEA sentences coming from the GPS in the rocket then setup the HC12 in the rocket for the appropriate baud rate and connect it direct to the TX output of the GPS. No need for GPS libraries, parsing etc in the rocket at all.

You dont even need a Arduino on the ground to log it, just connect the output of the ground HC12 to an openlog micro SD card logger, which are cheap as chips on eBay.

If you want to track the rocket in real time on the ground just treat the HC12 just as a serial bridge to the rockets GPS, so the ground station can then use faster Arduinos, Due, ESP8266, ESP32, STM etc.


Well, the disadvantages I listed in Reply #1 still apply...

Something I haven't mentioned so far is that transmitting the NMEA data from the rocket could cause radio "collisions" with the base station if it tries to transmit a command.  I'm not familiar with the HC-12, so I don't know if (1) its baud rate is faster than 9600 (affects transmission time), or (2) it performs its own collision avoidance (network access technique), error detection and correction, or acknowledgements.
One of the reasons for not wanting to send data that way is I will eventually end in the SD card reader so I will still have to pass the data through to parse through neo then output to text file.

I thought about this for a moment actually. I was like huh, i could just do that but then I would lose the ability to log on the rocket too when I inevitably lose signal 60k+ feet.

srnet

I thought about this for a moment actually. I was like huh, i could just do that but then I would lose the ability to log on the rocket too when I inevitably lose signal 60k+ feet.
There is no compelling reason I can see to 'parse' the data coming from the GPS at all, its already in CSV format, so post processing with a spreadsheet after the event should be easy enough.

As for needing to log in the rocket as well, attach an openlog to the GPS, the TX from the GPS can easily feed the HC12 and openlog at the same time.

Openlogs are cheap too, £4 ish.

I dont know the line of sight range of the HC12, but I guess you might be getting close at 20-30km. Easy solution to that is to put a decent low noise amplifier in front of the ground based HC12, that should provide around 12dB of signal gain in a typical FSK device such as the HC12 and that represents a range increase of 4 times.   
http://www.50dollarsat.info/
http://www.loratracker.uk/

willhartley

Code: [Select]
while (gps.available()) {
    fix = gps.read();  //  A new structure is all ready, save it for later

    NMEAprintRMC( &Serial, fix ); // or whatever serial port you want to use
    NMEAprintGGA( &Serial, fix );
  }


I ended up getting the first program to compile. Captures the data seems to run smoothly. When I go to add this line of code in, it doesn't like the face that I amusing hardware serial and want to use this as well. I think I am missing something that Neotee needs.

I get some error on the lines of unable to use that hardware serial port, already called in function. I am trying to point it to the HC12 instead with no success. I will try again after work and update everyone.

Pretty close to finishing this now with you guys' help. <3

-dev

Here's the key:

   // or whatever serial port you want to use

Because NeoHWSerial replaces the Arduino Serial class, you can't mix NeoSerial with Serial.  You'll get a link error.  Just use

    NMEAprintRMC( &NeoSerial, fix );

Cheers,
/dev
Really, I used to be /dev.  :(

-dev

Quote from: srnet
There is no compelling reason I can see to 'parse' the data coming from the GPS
How about knowing when some "GeoFence" value is reached.

Maybe start sending the GPS location after max altitude is reached, even if a "GPS" command is not received.

Or test the range, and when it gets more than 10km away from the start point (save the first fix), send the GPS fix once a minute.

After it lands (altitude reaches a minimum value and doesn't change much), send the GPS fix once every 5 minutes to save batteries?

Lots of things you can do.
Really, I used to be /dev.  :(

Go Up