0

I am using an Arduino Nano in combination with a NEO 6m GPS module. The GPS module is sending data, consisting of a few hundred chars, every second. Since I am communicating with my PC over the integrated serial RX and TX pins of the Arduino, I cannot use them for the sensor without being able to display anything sent from the Arduino on my PC. I am using the SoftwareSerial library to mark two digital pins as my new RX and TX to communicate with the GPS sensor.

The problem is that I have to actively listen to the RX pin at the moment the data is incoming, because otherwise data will be lost, since it's too much to be placed into the serial buffer completely. But on the other hand: I don't want to actively wait for the digital pin to receive data, so I would be in time to read it. It would be best handled with an interrupt every time the digital RX pin starts to receive data. But I read that SoftwareSerial is not compatible with this kind of interrupt, since it has a built-in interrupt itself everytime new data arrives to save it onto the buffer.

This is what I have tried:

#include <SoftwareSerial.h>

// The serial connection to the GPS module
SoftwareSerial ss(4, 3);
String serialString;

void setup() {
  Serial.begin(9600);
  ss.begin(9600);

  //Interrupts when a transmission begin in the RX-Pin is recognized
  attachInterrupt(digitalPinToInterrupt(4), readGpsSerialData, CHANGE);
}

void loop() {
  //processing data that takes too long to check frequently enough if data is being transmitted at the moment 
  Serial.println("In Loop");
  delay(1000);
}

//Read all the incomming data once transmission started
void readGpsSerialData() {
  String serialString = "";
  while (ss.available() > 0) {
    char serialChar = ss.read();
    serialString += serialChar;
  }
  lastGpsString = serialString;
  Serial.print("Data from Interrupt: ");Serial.println(lastGpsString);
}

I think the problem is that my interrupt activates, not just once when the transmission starts, but every time the RX digital value changes (so a lot of times in the transmission). A data RDY value would be nice for this interrupt trigger but the GPS module does not have one.

7
  • 1
    I guess the delay in loop stands for some other complex code that takes long to execute? I would try to get to a non-blocking coding style, so that you can check for serial data with higher frequency. Without knowing what this complex code is, we cannot advice at that.
    – chrisl
    Jan 5 at 13:24
  • Or you could increase the buffer size by editing the SoftwareSerial library. Though I would argue, that this is not a good solution, since it makes your code less portable (you would need to use that modified library everywhere you want to compile the code)
    – chrisl
    Jan 5 at 13:27
  • Does it really take longer than 1 second to run your loop code? Perhaps that should be optimized and then this wouldn't be a problem.
    – Delta_G
    Jan 5 at 18:40
  • You can use the hardware serial port both for reading from the GPS and for writing to the PC. You will not be able to also read from the PC though. Jan 5 at 20:07
  • Thanks, timemage - I'd busted an edit. Corrected here: Following on to @EdgarBonet's suggestion, you could swap the roles of Serial and SoftwareSerial. Then you'd have one channel dedicated to the PC and the other, faster one, dedicated to the GPS. You'd still need to make your code non-blocking to prevent dropped characters on either channel, and you'd want to service each channel more frequently (just for collecting the input, not necessarily processing it), for the same reason.
    – JRobert
    Jan 6 at 14:10

3 Answers 3

2

GPS is a really slow communication, it communicates in 9600bps and data only available once in every second. Any MCU running at much slower than an Arduino Uno running at 16MHz should be able to handle it, with a small rx buffer. If there is a problem, it is because of the user code implementation.

You didn't mention which Arduino board you are using. Arduino's implementation of attachInterrupt() function has quite limited digital pins that you could attach an interrupt to it. See the documentation. Even it can handle interrupt on pin4, the CHANGE means that it would be triggered in every single bit change, which is really unnecessary.

Secondly, there are a few "do and don't" for setting up an interrupt callback function, it all come down to "keep it short and simple", to have a while..loop and Serial print within an interrupt callback is one of those things that should be avoid, for that I suggest to read Nick Gammon's Interrupts.

There is a misunderstanding of "actively listen" of RX. The Serial.available() is a function that behave like an onEvent() callback internally, so it is non-block by simply doing a checking with if(Serial.available(), it will only blocking by putting it in a while loop. Furthermore, Serial.available() only turn from 0 to 1 when a full byte is received.

I'd suggest to not using interrupt, and make the following changes. This should provide plenty of time for handling other communication while receive GPS data stream.

#include <SoftwareSerial.h>


// The serial connection to the GPS module
SoftwareSerial ss(4, 3);

void setup() {
  Serial.begin(9600);   // change baudrate to 115200 to improve performance
  ss.begin(9600);
}

void loop() {
  String serialString = "";

  // this code block only execute when a character is fully received
  if (ss.available() > 0) {
    char serialChar = ss.read();
    serialString += serialChar;
    // only print the string when the `\n` is received (i.e. full GPS string)
    if (serialChar == '\n') {
      Serial.println(serialString);
    }
  }

  // there are plenty of time to handle the rest of the communication with the PC here
}
0

There are 2 aspects here.

The first is that the continuous flow of NMEA data is not THAT important. When you are ready to read, you can discard any pending input, ie flush the buffer, and start reading from the next $ onwards.

You can also limit the number of NMEA verbs displayed (there are commands to do that), so that less information is sent to Serial.

The second aspect is that most GPS modules send data at 9,600 bauds, which is quite slow, and every second (that's what the PPS pin is for). So you read data, store the lines in a buffer, and parse the buffer. Rinse and repeat. Even an Arduino Uno should be good enough. And as I said, if there's too much data, discard and start reading again.

0

To avoid a buffer overflow, I would recommend increasing the communication speed on the PC side. You can easily set Serial.begin(115200); or even higher, as the comment in your code suggests. The PC and also the Arduino work just fine with that. That will make sure you can send out all the input from the GPS receiver much faster than new data comes in, so you shouldn't loose anything. Just make sure that you use the same value on the Arduino and the PC.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.