NeoSWSerial to feed TinyGPS with Interrupt

Dear all,

I am using NeoSWSerial and TinyGPS to retrieve data from my GPS. The "regular" way works like a charm, something like:

while (gps_serial.available() > 0)
  {

    gps.encode(gps_serial.read());

  }

However, I would like to make use of the interrupt functionality of NeoSWSerial. By doing it like the below, TinyGPS does no longer find any sentences to decode. I double checked that it receives the data, but it looks like it is not able to handle single characters or something like this?

(drastically shoretened code:)

void setup(){
  gps_serial.attachInterrupt(feed_gps);
}

void feed_gps(char character) {
  gps.encode(character);
}

TinyGPS reports many proessed characters, but no processed sentences. Any ideas?

(drastically shortened reply)

Try declaring your gps object with the volatile modifier.

Thanks for your idea!

So here's what I did:

volatile TinyGPS gps;
NeoSWSerial gps_serial(8, 7);

void setup() {
  gps_serial.attachInterrupt(feed_gps);
  gps_serial.begin(9600);     
}

void feed_gps(char character) {
  gps.encode(character);
}

unfortunately, still the same behaviour. Many processes characters, many checksum failures, but no processed sentences.

Edit: I just noticed that some stuff seems to get through. I now have a latitude, but no longitude for example. And here's the "statistics":

Processed chars: 164019
Received sentences: 3
Failed checksums: 594

I thought I might be missing characters that for some reason don't trigger the interrupt. But the buffer is always empty and I now also forwarded the incoming characters (with Serial.write) to the serial port. Here's what it looks like. Definitely some issues. Looks like some issues with converting dataformats? Any ideas?

$GPVTG,254.52,T,,M,0.00,N,0.00,K,A*39
$GPZDA,172933.000,22,07,2018,00,00*57
$GPGGA,172934.000,⸮x24.4374,⸮	⸮š⸮rº⸮⸮b*⸮⸮b⸮⸮b⸮r⸮b⸮⸮⸮r⸮bj⸮⸮r⸮bj⸮bR⸮⸮j
$GPGLL,4x24.4374,N,00x32.8723,E,172934.000,A,A*55
$GPGSA,A,3,17,03,19,14,11,01,32,22,18,28,08,,1.3,0.9,1.0*3B
$GPGSV,4,1,13,01,82,100,32,03,65,266,49,08,12,175,24,11,61,164,18*79
$GPGSV,4,2,13,14,39,058,21,17,33,309,42,18,53,121,20,19,13,323,33*71
$GPGSV,4,3,13,22,85,006,36,23,25,193,,28,08,260,10,31,15,090,*77
$GPGSV,4,4,13,32,17,043,26*4F
$GPRMC,172934.000,A,4724.4374,ł⸮š⸮rº⸮⸮b*⸮⸮r⸮⸮b⸮⸮⸮r⸮⸮b⸮⸮⸮⸮⸮⸮bbb
⸮⸮⸮j

(masked coordinates on purpose with an 'x')

EDIT: Interestingly, it seems like it fails always at a similar position. E.g. the GGA sentence never contains the longitude.

Here's the same output if I just forward the buffer like this:

  while (gps_serial.available() > 0)
  {
Serial.write(gps_serial.read());
  }

Here's the result:

$GPVTG,63.60,T,,M,0.00,N,0.00,K,A*0E
$GPZDA,174829.000,22,07,2018,00,00*5B
$GPGGA,174830.000,4x24.4407,N,00x32.8870,E,1,08,1.5,509.2,M,0.0,M,,*6B
$GPGLL,4x24.4407,N,00x32.8870,E,174830.000,A,A*5C
$GPGSA,A,3,17,32,18,19,11,01,03,22,,,,,2.4,1.5,1.9*3C
$GPGSV,3,1,11,01,74,121,20,03,72,281,54,11,52,163,15,14,33,052,*70
$GPGSV,3,2,11,17,38,301,49,18,45,127,18,19,19,320,43,22,78,047,27*70
$GPGSV,3,3,11,23,34,194,,31,19,082,,32,10,041,23*47
$GPRMC,174830.000,A,4x24.4407,N,00x32.8870,E,0.00,63.60,220718,,,A*56

So there must be something with that interrupt...

Can you post all the code you are testing with.

I would imagine the serial input from the GPS is getting corrupted, causing the non-ascii characters. Can't imagine why as yet.

Thanks for your support!

here’s the code that provides above garbled result (the whole GPS part is actually no longer required here, as the issue seems to lie with the serial communication):

#include <TinyGPS.h>
#include <NeoSWSerial.h>

volatile TinyGPS gps;
NeoSWSerial gps_serial(8, 7);

void setup()
{
  Serial.begin(115200);                   
  gps_serial.attachInterrupt(feed_gps);
  gps_serial.begin(9600);              
}

static void feed_gps(byte character) 
{
  Serial.write(character);
  gps.encode(character);
}

void loop()
{
}

Added some more code to see whether the actual "decimal value" of the byte coming to the feed_gps() is wrong, or the conversion to the character. Looks like it is the byte that is wrong, as the conversion looks good to me:

105: i
68: D
65: A
75: K
166: ⸮
138: ⸮
197: ⸮
154: ⸮
77: M
9: 	
130: ⸮
193: ⸮
150: ⸮
38: &
19: 
9: 	
186: ⸮
177: ⸮
153: ⸮
166: ⸮
19: 
98: b
193: ⸮
152: ⸮
75: K
193: ⸮
149: ⸮
77: M
83: S
72: H
68: D
58: :
80: P
170: ⸮
43: +
197: ⸮
193: ⸮
49: 1
75: K
138: ⸮
177: ⸮
152: ⸮
38: &
41: )
78: N
170: ⸮
81: Q
202: ⸮
175: ⸮
144: ⸮

It could be something dodgy going on with the serial coming in, but you shouldn't put Serial.write() inside an interrupt handler - it will likely cause problems.
I believe NeoSWSerial relies on pin change interrupts and a timer to interpret the incoming serial signal. I suppose it could be fairly sensitive to clock inaccuracies, maybe if the MCU or GPS clock is running slow or fast it could cause an issue. But that's just me speculating.

NeoSWSerial was written by the forum member -dev, so if you are lucky he may turn up with a few suggestions.

Thanks for your commen!

I was hoping I could rule out issues with the serial communication as such due to the fact that it works perfectly with the buffer.

Regarding your comment with the write within the interrupt, it made me thinking whether it could still be TinyGPS that causes the issues here, and voilà! Removing the encoding from the interrupt got rid of the garbled messages.
Now I need to find a solution for this…maybe collecting data in an array and only calling the encoding every once in a while, while detaching the interrupt temporarily and afterwards checking whether anything is in the buffer?

Fundamentally an interrupt handler (ISR) should be as short and quick as possible because, while inside the ISR, interrupts are turned off such that other important stuff doesn’t get done when it should. I did take a look at the code for “encode()” and didn’t see anything too obscene, but maybe it takes longer than is desirable.

Your workaround is a reasonable idea. Use a ring buffer of say 5 or 10 characters, add each character to the buffer in the ISR and call encode() regularly in the loop(). Don’t forget to mark all variables shared between normal program flow and the ISR as volatile.
Kind of negates the convenience of using the ISR in the first place, but that’s life.

Why do you need interrupts? Just use the regular method. That provides the ring buffer for you.

MorganS:
Why do you need interrupts? Just use the regular method. That provides the ring buffer for you.

That's also a valid point - go back to your original code... thanks MorganS :smiley:

Thanks all for your suggestions.

At least temporary, I got rid of the interrupt and continued to read from the buffer, however, instead of calling the encode everytime I receive something on the serial, I read it in another buffer and then send it to the GPS library all at once.

This did make the quality much better!

However, I still have some failures, and I can also see them very clearly displayed in the messages. It is usually exactly one character that is wrong, maybe let's say 1 in 2000. That looks like an awesome rate, but that one character destroys the whole message, making me lose a significant amount of the messages.

Here is one sample (error is in first line):

$GPGGA,202908.000,xx24.4392,N,00x32.⸮799,E,1,06,1.2,527.0,M,0.0,M,,*67
$GPGLL,xx24.4392,N,00x32.8799,E,202908.000,A,A*57
$GPGSA,A,3,06,03,02,09,19,07,,,,,,,2.1,1.2,1.7*35
$GPGSV,3,1,11,02,22,315,30,03,36,103,21,04,,,25,06,59,275,24*45
$GPGSV,3,2,11,07,23,176,13,09,78,248,42,17,07,226,,19,17,246,26*7F
$GPGSV,3,3,11,22,15,109,,23,66,060,03,26,07,054,*43
$GPRMC,202908.000,A,xx24.4392,N,00x32.8799,E,0.00,187.75,230718,,,A*63
$GPVTG,187.75,T,,M,0.00,N,0.00,K,A*31
$GPZDA,202908.000,23,07,2018,00,00*5A
$GPTXT,01,01,01,ANTENNA OPEN*25
$GPGGA,202909.000,xx24.4392,N,00x32.8799,E,1,06,1.2,527.0,M,0.0,M,,*66
$GPGLL,xx24.4392,N,00x32.8799,E,202909.000,A,A*56
$GPGSA,A,3,06,03,02,09,19,07,,,,,,,2.1,1.2,1.7*35
$GPGSV,3,1,11,02,22,315,30,03,36,103,21,04,,,25,06,59,275,24*45
$GPGSV,3,2,11,07,23,176,13,09,78,248,42,17,07,226,,19,17,246,26*7F
$GPGSV,3,3,11,22,15,109,,23,66,060,03,26,07,054,*43
$GPRMC,202909.000,A,xx24.4392,N,00x32.8799,E,0.00,187.75,230718,,,A*62
$GPVTG,187.75,T,,M,0.00,N,0.00,K,A*31
$GPZDA,202909.000,23,07,2018,00,00*5B
$GPTXT,01,01,01,ANTENNA OPEN*25
$GPGGA,202910.000,xx24.4392,N,00x32.8797,E,1,06,1.2,527.0,M,0.0,M,,*60
$GPGLL,xx24.4392,N,00x32.8797,E,202910.000,A,A*50
$GPGSA,A,3,06,03,02,09,19,07,,,,,,,2.1,1.2,1.7*35
$GPGSV,3,1,11,02,22,315,30,03,36,103,21,04,,,26,06,59,275,24*46
$GPGSV,3,2,11,07,23,176,13,09,78,248,42,17,07,226,,19,17,246,26*7F
$GPGSV,3,3,11,22,15,109,,23,66,060,15,26,07,054,*44
$GPRMC,202910.000,A,xx24.4392,N,00x32.8797,E,0.00,187.75,230718,,,A*64
$GPVTG,187.75,T,,M,0.00,N,0.00,K,A*31
$GPZDA,202910.000,23,07,2018,00,00*53
$GPTXT,01,01,01,ANTENNA OPEN*25
$GPGGA,202911.000,xx24.4392,N,00x32.8796,E,1,06,1.2,527.0,M,0.0,M,,*60
$GPGLL,xx24.4392,N,00x32.8796,E,202911.000,A,A*50
$GPGSA,A,3,06,03,02,09,19,07,,,,,,,2.1,1.2,1.7*35
$GPGSV,3,1,11,02,22,315,30,03,36,103,21,04,,,26,06,59,275,24*46
$GPGSV,3,2,11,07,23,176,13,09,78,248,42,17,07,226,,19,17,246,26*7F
$GPGSV,3,3,11,22,15,109,,23,66,060,15,26,07,054,*44
$GPRMC,202911.000,A,xx24.4392,N,00x32.8796,E,0.00,187.75,230718,,,A*64
$GPVTG,187.75,T,,M,0.00,N,0.00,K,A*31
$GPZDA,202911.000,23,07,2018,00,00*52
$GPTXT,01,01,01,ANTENNA OPEN*25
$GPGGA,202912.000,xx24.4392,N,00x32.8796,E,1,06,1.2,527.0,M,0.0,M,,*63
$GPGLL,xx24.4392,N,00x32.8796,E,202912.000,A,A*53
$GPGSA,A,3,06,03,02,09,19,07,,,,,,,2.1,1.2,1.7*35
$GPGSV,3,1,11,02,22,315,30,03,36,103,21,04,,,26,06,59,275,24*46
$GPGSV,3,2,11,07,23,176,13,09,78,248,42,17,07,226,,19,17,246,26*7F
$GPGSV,3,3,11,22,15,109,,23,66,060,15,26,07,054,*44
$GPRMC,202912.000,A,xx24.4392,N,00x32.8796,E,0.00,187.75,230718,,,A*67
$GPVTG,187.75,T,,M,0.00,N,0.00,K,A*31
$GPZDA,202912.000,23,07,2018,00,00*51
$GPTXT,01,01,01,ANTENNA OPEN*25
$GPGGA,202913.000,xx24.4393,N,00x32.8795,E,1,06,1.2,527.0,M,0.0,M,,*60
$GPGLL,xx24.4393,N,00x32.8795,E,202913.000,A,A*50

While this is somewhat ok, I know that this exact same PCB is able to make 0 errors with different code. I also use the RadioHead RF library, which might cause these errors. Saying whenever that library is in use, one character gets fucked up - or something like this..

The problem is in the code you didn't post. What does it do that might interfere with SWSerial at the interval of 1 in 2000?

That’s what I just figured out, thanks! It looks like I managed to get rid of all issues besides one:

It’s this stuff here that creates that one issue. When I comment out everything between the lines, I don’t have any failed characters anymore. The build_payload() is just an operation that builds those payload arrays, but I don’t get any issues leaving it in.
Th rf22 is a radio module controlled by RadioHead library.

void send_data() {
  if (millis() >= last_transmission + interval) { //check if enough time has passed to send data

    digitalWrite(led, HIGH);        //initiate blinking

    build_payload();

---------------------------------------------------------

    byte data1[sizeof(payload1)];                   //create byte array for transmission
    byte data2[sizeof(payload2)];

    for (int i = 0; i < sizeof(payload1); i++) {
      data1[i] = (byte)payload1[i];          //load into byte array for transmission
    }

    for (int i = 0; i < sizeof(payload2); i++) {
      data2[i] = (byte)payload2[i];
    }

    if (debug) {
      Serial.print("Data to be sent: ");
      Serial.println((char*)data1);
      Serial.print("Data to be sent: ");
      Serial.println((char*)data2);
      Serial.println("sending..... ");
    }

    if (rf22.send(data1, sizeof(data1))) {
      if (debug) {
        Serial.println("Success!");
      }
    } else {
      if (debug) {
        Serial.println("Error");
      }
    }

    rf22.waitPacketSent();    //waits for previous package to be sent

    if (rf22.send(data2, sizeof(data2))) {
      if (debug) {
        Serial.println("Success!");
      }
    } else {
      if (debug) {
        Serial.println("Error");
      }
    }
    rf22.waitPacketSent();    //waits for previous package to be sent

---------------------------------------------------------

    last_transmission = millis();   //save time of last transmission

    digitalWrite(led, LOW);     //stop blinking
  }
}