====== Common Coding Errors ======
Ensuring your payload is fully tested for the following errors will maximise your chances of a recovery. There are a number of common and avoidable errors in code that can result in inaccurate information being uploaded to the tracker. The image below is an example of a payload which had both padding and a meridian error. The main cause of these issues is the conversion from a floating point value to a string for transmission. Below we will go through the issues and explain how to avoid them.
{{:guides:paddingerrors2.jpg|}}
===== Coordinates and turning floats into strings ====
==== The easy way ====
**Wherever possible, use functions provided by libc and gcc rather than writing your own!**
Seriously. The GCC and libc functions are probably faster and much more likely to be free of bugs.
Here are two functions that can turn floats into strings:
char s[30];
double lat = 54.086611;
dtostrf(lat, 8, 6, s); // == "54.086611"
char s[30];
double lat = 54.086611;
snprintf(s, 30, "%.6f", lat); // == "54.086611"
Unfortunately the second one might not always compile. With limited memory on a typical microcontroller, the printf() series of functions in [[http://www.nongnu.org/avr-libc/|AVR Libc]] (used by Arduino) do not by default support printing of floating point values using the %f operator. You can enable floating point support in printf, though it's difficult.
==== The hard way ====
It is common to divide up a floating point value into two integers and print it with two %i operators. We quite often see code like the below. Honestly, using dtostrf should be much easier. The notes below might be a useful read if you're not familiar with these common C mistakes anyway.
char s[30];
double lat = 54.086611;
int i1 = lat; // == 54, the integer part
int i2 = (lat - i1) * 1000000; // == 21076 ... uh oh
snprintf(s, 30, "%i.%i", i1, i2); // == "54.21076" -- that's not good.
We wanted i1 to contain 54, i2 to contain 86611 and s to be the string "54.086611" - but that's not what happened.
Don't use this code! Let's have a look at its problems.
==== Overflow Issues ====
On the Arduino, an 'int' is 16 bits (and signed), so can only contain values from -32768 to 32767. Instead of 456611, the i2 contains -2414.
When converting the decimal part of the above into an integer value you should be using the long data type, which is 32 bits wide and can therefore store from −2,147,483,648 to 2,147,483,647.
Fixed code:
long i2 = (lat - i1) * 1000000; // == 86611, the decimal part.
==== Floating point rounding errors ====
You might actually get 86610 due to the wonders of floating point and the fact that the default conversion from float to int floors the float rather than rounds it. This is not really a big deal, but if you want to you can fix this:
long i2 = lround((lat - i1) * 1000000);
==== Padding Issues ====
Excellent! However, s now contains the string "54.86611", not "54.086611". Oops.
The padding error will appear when the decimal part is below 0.1. Our float of 54.086611 gets split into 54 and 86611, and the resulting string is "54.86611" instead of 54.086611. To correctly print this the second integer must be zero padded to the correct number of decimal places:
snprintf(s, 30, "%i.%06li", i1, i2);
In our example we are using six decimal places, so %06li will ensure that the second integer (86611) is printed as "086611" giving a final correct result of "54.086611".
The 'l' is there because i2 is now a long integer, and printf likes to know.
==== Prime Meridian ====
In the UK and especially with our launch sites located around Cambridge the [[http://en.wikipedia.org/wiki/Prime_Meridian|Prime Meridian]] comes into play. This is the 0' line of Longitude where the values of longitude go from east to west, or in decimal form from positive to negative.
Suppose we used the same code as above to print our longitude values.
char s[30];
double lon = -1.001234;
int i1 = lon; // == -1, the integer part
long i2 = lround((lon - i1) * 1000000); // == -1234 ... hmm
snprintf(s, 30, "%i.%06li", i1, i2); // == "-1.-1234" -- lol.
The solution is quite simple. We simply need to make sure i2 is always positive, using the labs() function.
long i2 = labs(lround((lon - i1) * 1000000)); // == 1234
There's another case to consider. Clearly the idea is to split the float into the an integer and a fraction of an integer (i1 and i2 respectively).
The intention is the above code is that the integer part will supply the negative sign if required. However, consider -0.7. i1 would be -0, but -0 == 0, and the result would be "0.7". We'll have to handle negative numbers explicitly:
snprintf(where, buf_size, "%s%i.%06li", (what < 0 ? "-" : ""), labs(i1), i2);
==== Equatorial ====
Practically the Equator is not really an issue for us but worth mentioning for our Kenyan viewers. Really, you should place your float to string code in a separate function and call it for latitude and longitude. This also means that this function can be unit tested.
==== Fixed version ====
You might need to #include stdlib for labs and lround.
#include
void print_float(float what, char *where, int buf_size)
{
int i1 = what;
long i2 = labs(lround((what - i1) * 1000000));
snprintf(where, buf_size, "%s%i.%06li", (what < 0 ? "-" : ""), labs(i1), i2);
}
void loop()
{
double latitude, longitude;
char lat_str[30], lon_str[30];
// get latitude, longitude
print_float(latitude, lat_str, 30);
print_float(longitude, lon_str, 30);
// do something with lat_str, lon_str
}
==== Negative Altitudes ====
It's advised you use a data type that can handle negative numbers for the altitude. Occasionally the GPS can report negative altitudes on the ground, if you're using an unsigned integer this overflows. As an example -4 meters would become 65531 metres. Impressive but we wouldn't be able to accept this as a record :)
However, note also that flights regularly go above 32767 metres. Therefore storing it in a signed integer (int) is probably not appropriate. Use a long.
====== Testing Your Payload ======
==== Checking the Tracker can Decode Your Telemetry Strings ====
You can view the debug log from the parser [[http://habitat.habhub.org/logtail/|here]]. Simply upload a string or two with dl-fldigi and check that it said that it parsed it.
==== Simulating a Flight ====
There are a number of ways to simulate a flight. In this example we replace the GPS with a Windows PC running TroSys GPS and playing back an actual flight that crossed the prime meridian. For this example you'll need some sort of serial break out. I used an [[http://www.ftdichip.com/Support/Documents/DataSheets/Modules/DS_UM232R.pdf|UM232R]].\\ \\ You will need to get the free version of [[http://www.trosys.com/|Tro Sys Gps Simulator Free]].\\
You can download the sample data here :{{:guides:icarus_nmea_data_sample.zip|}}
Test data around the Prime Meridian and Equator can be found here [[http://www.robertharrison.org/svn/filedetails.php?repname=the-icarus-project&path=%2Ftesting-software%2Flinux%2FGPSgen%2FHAB_Test_Flight.zip | GPS Data]]
This is what the above data should look like on google maps / tracker. [[http://maps.google.co.uk/maps?q=http:%2F%2Fwww.robertharrison.org%2Ffiles%2Fkml%2FHAB_Test_Flight.kml&hl=en&ll=0,0&spn=41.676556,70.444336&sll=53.800651,-4.064941&sspn=25.338753,70.444336&t=h&z=5 | Google Maps with link to KML]]
Connect your FT232 up to the PC and check the port number assigned in device manager. You may need to force it to use COM1 - 4 as the software won't select higher numbered COM ports.This can be done under Device Manager -> Properties on the port, Port settings then advanced. Connect the RXD and TXD to your flight computer in place of the GPS. You can use this code to check its working, load this code on the Arduino, open the Serial Monitor, then open the UM232R via hyperterm or similar. If you type on the hyperterm it should appear in the Serial Monitor window :
#include
#include
#include
int COUNT=0;
byte GPS_TX=3; //GPS TX PIN
byte GPS_RX=4; //GPS RX PIN
SoftwareSerial GPS = SoftwareSerial(GPS_TX, GPS_RX);
char SS_IN_BYTE;
void setup()
{
pinMode(GPS_TX, INPUT);
pinMode(GPS_RX, OUTPUT);
GPS.begin(4800); // Amend Baud Rate as suits
Serial.begin(9600);
Serial.println("Serial Echo Utility");
}
void loop()
{
while(1)
{
char SS_IN_BYTE = GPS.read();
Serial.print(SS_IN_BYTE);
}
}
Once this is working open up TroSys GPS Simulator. Select log file playback. Select the relevant file and click load file.Set the delay b/w sentences as appropriate.
Configure the COM port settings as appropriate and click Connect. Once done you can now hit Send. This should play the log back to your project and simulate a flight.
**There is also some software for doing this from various linux OS's**
GPSgen and GPS emulate written by Steve Randall with a couple of minor modifications by Robert Harrison
[[http://www.robertharrison.org/svn/listing.php?repname=the-icarus-project&path=%2Ftesting-software%2Flinux%2F#path_testing-software_linux_| Linux GPS emulation software]]
Compile up the C programs
GPSgen takes a KML file and converts it to NMEA GPS strings.
./GPSgen < test.kml > test.gps
GPSemulate takes a GPS file and send the data out every second like a GPS device does
./GPSemulate < test.gps > /dev/ttyUSB0
Obviously you need to set up the ttyUSB0 device to the correct baud rate this can be done using
stty -F /dev/ttyUSB0 4800