I'm overhauling my custom GPS library for Arduino and I'm running into a slight problem. I'm trying to parse NMEA sentences using Serial Event based programming and have a string buffer. I know that the use of "String" variables can cause memory issues, but using the .reserve() method should mitigate this problem. The only problem is if I try to do the following in the global section of my library's .cpp file:
// reserve 70 bytes for the inputString:
inputString.reserve(70);
I get this compile-time error:
inputString.reserve(70);
^
Error compiling for board Teensy 3.5.
Now, if I comment that specific line out - the library works exactly as intended (so far). It grabs the data when available and stuffs it into the string buffer inputString.
Any ideas on how to fix this problem?
Here's my test sketch:
#include <neo6mGPS.h>
void setup()
{
Serial.begin(2000000);
while (!Serial);
Serial3.begin(9600);
//initialize GPS class
myGPS.begin(Serial3, Serial);
}
void loop()
{
myGPS.grabData_LatLong();
}
The code for my library files are quite large - I've attached them to this post. Again, the offending line is in the library's .cpp (neo6mGPS.cpp).
#include "neo6mGPS.h"
//create GPS class
neo6mGPS myGPS;
String inputString = "";Â Â Â // a String to hold incoming data
bool stringComplete = false;Â // whether the string is complete
// reserve 70 bytes for the inputString:
inputString.reserve(70);
//initialize the GPS data extractor class and the GPS itself
void neo6mGPS::begin(HardwareSerial &_GPS_SERIAL, usb_serial_class &_PC_SERIAL_USB)
{
//update serial streams
GPS_SERIAL = &_GPS_SERIAL;
PC_SERIAL_USB = &_PC_SERIAL_USB;
//setup GPS
setupGPS();
return;
}
//initialize the GPS data extractor class and the GPS itself
void neo6mGPS::begin(HardwareSerial &_GPS_SERIAL, HardwareSerial &_PC_SERIAL)
{
//update serial streams
GPS_SERIAL = &_GPS_SERIAL;
PC_SERIAL = &_PC_SERIAL;
//setup GPS
setupGPS();
return;
}
//initialize the GPS data extractor class and the GPS itself
void neo6mGPS::begin(HardwareSerial &_GPS_SERIAL)
{
//update serial stream
GPS_SERIAL = &_GPS_SERIAL;
//setup GPS
setupGPS();
return;
}
//change the UART buffer timeout (10ms by default)
void neo6mGPS::setReceiveTimout(byte _timeout)
{
timeout = _timeout;
return;
}
//https://www.arduino.cc/en/Tutorial/SerialEvent
void serialEvent1()
{
if (myGPS.GPS_SERIAL == &Serial1)
{
while (Serial1.available())
{
// get the new byte:
char inChar = (char)Serial1.read();
// add it to the inputString:
if (!stringComplete)
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n')
{
stringComplete = true;
}
}
}
return;
}
//https://www.arduino.cc/en/Tutorial/SerialEvent
void serialEvent2()
{
if (myGPS.GPS_SERIAL == &Serial2)
{
while (Serial2.available())
{
// get the new byte:
char inChar = (char)Serial2.read();
// add it to the inputString:
if (!stringComplete)
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n')
{
stringComplete = true;
}
}
}
return;
}
//https://www.arduino.cc/en/Tutorial/SerialEvent
void serialEvent3()
{
if (myGPS.GPS_SERIAL == &Serial3)
{
while (Serial3.available())
{
// get the new byte:
char inChar = (char)Serial3.read();
// add it to the inputString:
if(!stringComplete)
inputString += inChar;
//Serial.write(inChar);
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n')
{
stringComplete = true;
}
}
}
return;
}
//https://www.arduino.cc/en/Tutorial/SerialEvent
void serialEvent4()
{
if (myGPS.GPS_SERIAL == &Serial4)
{
while (Serial4.available())
{
// get the new byte:
char inChar = (char)Serial4.read();
// add it to the inputString:
if (!stringComplete)
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n')
{
stringComplete = true;
}
}
}
return;
}
//https://www.arduino.cc/en/Tutorial/SerialEvent
void serialEvent5()
{
if (myGPS.GPS_SERIAL == &Serial5)
{
while (Serial5.available())
{
// get the new byte:
char inChar = (char)Serial5.read();
// add it to the inputString:
if (!stringComplete)
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n')
{
stringComplete = true;
}
}
}
return;
}
//https://www.arduino.cc/en/Tutorial/SerialEvent
void serialEvent6()
{
if (myGPS.GPS_SERIAL == &Serial6)
{
while (Serial6.available())
{
// get the new byte:
char inChar = (char)Serial6.read();
// add it to the inputString:
if (!stringComplete)
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n')
{
stringComplete = true;
}
}
}
return;
}
//setup GPS and load non-default configuration settings
void neo6mGPS::setupGPS()
{
if (PC_SERIAL != 0)
PC_SERIAL->println("Starting NEO-6M GPS auto-configuration...");
else if (PC_SERIAL_USB != 0)
PC_SERIAL_USB->println("Starting NEO-6M GPS auto-configuration...");
// Disable NMEA messages by sending appropriate packets.
disableNmea();
// Enable NMEA messages by sending appropriate packets.
enableNmea();
// Increase frequency to 100 ms.
changeFrequency();
// Switch the receiver serial to the wanted baudrate.
changeBaudrate();
if (PC_SERIAL != 0)
PC_SERIAL->println("Finished NEO-6M GPS auto-configuration!");
else if (PC_SERIAL_USB != 0)
PC_SERIAL_USB->println("Finished NEO-6M GPS auto-configuration!");
return;
}
//send a set of packets to the receiver to disable NMEA messages.
void neo6mGPS::disableNmea()
{
if (PC_SERIAL != 0)
PC_SERIAL->println("\tTurning off all NMEA sentences...");
else if (PC_SERIAL_USB != 0)
PC_SERIAL_USB->println("\tTurning off all NMEA sentences...");
writeConfigPacket(turn_Off_GPGGA, NMEA_LEN);
writeConfigPacket(turn_Off_GPGLL, NMEA_LEN);
writeConfigPacket(turn_Off_GPGSA, NMEA_LEN);
writeConfigPacket(turn_Off_GPGLV, NMEA_LEN);
writeConfigPacket(turn_Off_GPRMC, NMEA_LEN);
writeConfigPacket(turn_Off_GPVTG, NMEA_LEN);
return;
}
//send a set of packets to the receiver to enable NMEA messages.
void neo6mGPS::enableNmea()
{
if (PC_SERIAL != 0)
PC_SERIAL->println("\tTurning on desired NMEA sentences...");
else if (PC_SERIAL_USB != 0)
PC_SERIAL_USB->println("\tTurning on desired NMEA sentences...");
//comment or uncomment based on what sentences desired
//writeConfigPacket(turn_On_GPGGA, NMEA_LEN);
//writeConfigPacket(turn_On_GPGLL, NMEA_LEN);
//writeConfigPacket(turn_On_GPGSA, NMEA_LEN);
//writeConfigPacket(turn_On_GPGLV, NMEA_LEN);
writeConfigPacket(turn_On_GPRMC, NMEA_LEN);
//writeConfigPacket(turn_On_GPVTG, NMEA_LEN);
return;
}
//send a packet to the receiver to change baudrate to 115200.
void neo6mGPS::changeBaudrate()
{
if (PC_SERIAL != 0)
PC_SERIAL->println("\tChanging baud rate...");
else if (PC_SERIAL_USB != 0)
PC_SERIAL_USB->println("\tChanging baud rate...");
writeConfigPacket(changeBaud, BAUD_LEN);
delay(100); // Little delay before flushing.
GPS_SERIAL->flush();
GPS_SERIAL->begin(115200); //reset serial port to new baud
return;
}
//send a packet to the receiver to change frequency to 100 ms.
void neo6mGPS::changeFrequency()
{
if (PC_SERIAL != 0)
PC_SERIAL->println("\tUpdating refresh frequency...");
else if (PC_SERIAL_USB != 0)
PC_SERIAL_USB->println("\tUpdating refresh frequency...");
writeConfigPacket(updateFreq, FREQ_LEN);
return;
}
//update lat and lon in the GPS_data array
int neo6mGPS::grabData_LatLong()
{
// print the string when a newline arrives:
if (stringComplete)
{
if (inputString.indexOf("RMC") > 0)
{
Serial.println(inputString);
}
// clear the string:
inputString = "";
stringComplete = false;
}
//nothing found
return NO_DATA;
}
//extract lat and lon data from the GPS stream buffer (Example NMEA Sentence: $GPRMC,025629.10,A,3946.03316,N,08407.09471,W,0.095,,040119,,,A*67)
void neo6mGPS::extractLatLong(byte startingIndex)
{
//Serial.println(startingIndex);
/*GPS_data[LAT_POS] = (((buff[18] - '0') * 10) + (buff[19] - '0')) //degrees
 + ((((buff[20] - '0') * 10) + (buff[21] - '0')) / 60.0) //minutes
 + ((((buff[23] - '0') * 10) + (buff[24] - '0') + ((buff[25] - '0') / 10.0) + ((buff[26] - '0') / 100.0) + ((buff[27] - '0') / 1000.0)) / 3600.0); //seconds
GPS_data[LON_POS] = (((buff[31] - '0') * 100) + ((buff[32] - '0') * 10) + (buff[33] - '0')) //degrees
 + ((((buff[34] - '0') * 10) + (buff[35] - '0')) / 60.0) //minutes
 + ((((buff[37] - '0') * 10) + (buff[37] - '0') + ((buff[39] - '0') / 10.0) + ((buff[40] - '0') / 100.0) + ((buff[41] - '0') / 1000.0)) / 3600.0); //seconds
if (buff[29] == 'S')
{
GPS_data[LAT_POS] = -GPS_data[LAT_POS];
}
if (buff[43] == 'W')
{
GPS_data[LON_POS] = -GPS_data[LON_POS];
}
for (byte i = 0; i < buffLen; i++)
{
if (buff[i] == ' ')
{
break;
}
buff[i] = ' ';
}*/
return;
}
//send the packet specified to the receiver.
void neo6mGPS::writeConfigPacket(byte packet[], byte len)
{
for (byte i = 0; i < len; i++)
{
GPS_SERIAL->write(packet[i]);
}
return;
}
That's very odd and unfortunate. Here's what I get:
E:\electronics\arduino\libraries\neo6mGPS\neo6mGPS.cpp:13:1: error: 'inputString' does not name a type
inputString.reserve(70);
^
Error compiling for board Teensy 3.5.
You can see there is a helpful error message "'inputString' does not name a type
". Are you certain that's missing from your output?
The reason for the error is that you're calling inputString.reserve() from outside a function. you can't do that. If you move that inside the begin function, the error will be fixed.
pert:
You can see there is a helpful error message "'inputString' does not name a type
". Are you certain that's missing from your output?
Facepalm Oops - I forgot
pert:
The reason for the error is that you're calling inputString.reserve() from outside a function. you can't do that. If you move that inside the begin function, the error will be fixed.
Got it, thanks! Curious though, why does it need to be called in a function?
This is an instance method call, cannot be resolved at compile time.
C++ is not an interpreted language and, to simplify a bit, things that can’t be resolved at compile time or are not related to a global object instanciation (for those the constructor is called automatically by code generated by the compiler before main() is called), need to be called manually from the program when it starts executing, from the main() function. Since arduino generated the main() for you and you can’t put code there, where you would put such initialization would be in the setup()
You could alternatively modify the String class to have the reserve in the constructor or ensure the constructor is called for you enforcing 70 characters - like initializing your String with a 70 chars String. String testStr = String(“0123456789012345678901234567890123456789012345678901234567890123456789”); will create the String object with a 70 char buffer.
I would not see this as a best practice though (it’s a hack “guessing” the internals of the class). Just call reserve in setup() would be the recommended approach (well the recommended one would be don’t use the String class)
It is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. This can happen after the program has been running perfectly for some time. Just use cstrings - char arrays terminated with '\0' (NULL).