Ultimate GPS with TinyGPS

Adafruit Ethernet, Motor, Proto, Wave, Datalogger, GPS Shields - etc!

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
wriggler
 
Posts: 2
Joined: Mon Sep 23, 2013 6:43 pm

Ultimate GPS with TinyGPS

Post by wriggler »

Hi there,

I'm using your Ultimate GPS breakout together with TinyGPS++ (new version of TinyGPS), rather than using your own Adafruit_GPS library, since TinyGPS++ offers more of the functionality I need.

However, I like your use of a timer interrupt in your example code, and wanted to substitute TinyGPS++ into it. It's not working, though, as far as I can see. Can you help?

My code (simplified):

Code: Select all

void setup() {
  Serial.begin(115200);
  ss.begin(GPS_BAUD);
  startTimer0();
}

void startTimer0() {
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
}

// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
  while (ss.available() > 0) {
    gps.encode(ss.read());
  }
}

void loop() {
  Serial.println(gps.location.lat());
}
However, Serial.print(gps.location.lat()) (for example) in the rest of my code yields nothing, as if the GPS output wasn't parsed when I use the code above. When I use the same code in my loop(), it works as normal.

Could you point me in the right direction?

Thanks!

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: Ultimate GPS with TinyGPS

Post by adafruit_support_mike »

I don't know anything about the TinyGPS++ library, but at a guess I'd say you're seeing interrupt reentrance.

Your interrupt handler contains a loop, but doesn't have any limits to ensure the loop will terminate in less than 1ms, and also doesn't block interrupt processing while the loop is running. If a second interrupt comes along while you're still running the loop for the first one, the program's behavior will be undefined.

As a rule, interrupt handlers want to execute as quickly as possible. Set a flag in the interrupt handler if it needs to trigger a long operation, then do the actual processing in the main loop, shutting the interrupts off if possible:

Code: Select all

    boolean AVAILABLE = false;

    SIGNAL(TIMER0_COMPA_vect) {
        if (ss.available() > 0) {
            AVAILABLE = true;
        }
    }

    void loop() {
        if (AVAILABLE) {
            TIMSK0 &= ~_BV(OCIE0A);                 //  shut off interrupts while we're working
        
            while (ss.available() > 0) {
                gps.encode(ss.read());
            }
            Serial.println(gps.location.lat());     //  print the results
            AVAILABLE = false;                      //  clear the flag
            TIMSK0 |= _BV(OCIE0A);                  //  re-enable interrupts
        }
    }
Even that isn't strictly perfect.. it's possible for an interrupt to arrive between the if() in the main loop and the next line which shuts the interrupts off. That won't cause problems in this specific case because the extra interrupt will just write the same value to the boolean AVAILABLE again. The state of the program will be the same whether the spurious interrupt happens or not, so allowing it to happen is acceptable, but it imposes subtle restrictions on the order of operations.

Writing that section this way would be flat-out wrong, for instance:

Code: Select all

        if (AVAILABLE) {
            AVAILABLE = false;                      //  clear the flag
            TIMSK0 &= ~_BV(OCIE0A);                 //  shut off interrupts while we're working
        
            while (ss.available() > 0) {
                gps.encode(ss.read());
            }
            Serial.println(gps.location.lat());     //  print the results
            TIMSK0 |= _BV(OCIE0A);                  //  re-enable interrupts
        }
A pessimally-timed interrupt could set AVAILABLE back to true before the interrupts get shut off.

Subtleties like that are generally bad, but the art of managing locks to prevent code reentrance is inherently subtle and full of ways to make unexpected mistakes.

wriggler
 
Posts: 2
Joined: Mon Sep 23, 2013 6:43 pm

Re: Ultimate GPS with TinyGPS

Post by wriggler »

Hi Mike,

Thanks for your very detailed reply, which is much appreciated.

My reason for looking at processing the GPS data inside an interrupt, as you do with your own GPS library, is because the loop() section of my code contains a lot of other things to do that (apparently) mean it can't loop over the incoming GPS data fast enough to pick up the (for example) 5hz updates.

If I loop over the GPS data alone, I can see in debugging that I'm picking up fresh data every 200ms, as desired. However, when I add some kind of processing of that data (for instance, writing to LCD or SD), my debugging shows that I'm now only picking up fresh GPS data erratically - perhaps every 500-2000ms.

Anyway, if you do have any more thoughts I'd love to hear them. How can I get around the problem of time-consuming code in the loop when it also needs to be responsive to incoming GPS data?

Thanks!

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: Ultimate GPS with TinyGPS

Post by adafruit_support_mike »

wriggler wrote:My reason for looking at processing the GPS data inside an interrupt, as you do with your own GPS library, is because the loop() section of my code contains a lot of other things to do [...]
That's common, but the interrupt-based example only reads-and-prints one character per interrupt, and even then it uses a shortcut that's faster than Serial.print().

That might be the source of the trouble in fact.. taking line overhead into account (parity, start and stop bits, etc) we usually assume it takes 10 link-layer bits to transmit one 8-bit data byte. Dividing a rate of 115200 by 10 gives us a nominal data rate of about 11520 bytes per second, and dividing that by 1000 gives us a maximum data rate of 11 complete characters per millisecond, excluding any other processing overhead.

The only way around that problem in a single processor doing multiple tasks is to amortize your communications overhead. Keep the interrupt handler short and sweet, and use it to decide whether input has arrived for processing. Arrange your main loop as a checklist of things-to-do that calls a set of non-blocking, terminating functions. Encode your program's state in a set of global variables, and use those to decide what operations should run on each pass through the main loop:

Code: Select all

    void loop (void) {
        if ( NEED_TO_DO_X ) {
            handlerForX();
        }
        if ( NEED_TO_DO_Y ) {
            handlerForY();
        }
        if ( NEED_TO_DO_Z ) {
            handlerForZ();
        }
    }
You can get the same effect by moving the conditionals to short-circuit clauses in each of the handlers:

Code: Select all

    void loop (void) {
        handleX();
        handleY();
        handleZ();
    }

    void handleX (void) {
        if ( ! NEED_TO_DO_X ) {
            return;
        }
        //  main code
    }

    void handleY (void) {
        if ( ! NEED_TO_DO_Y ) {
            return;
        }
        //  main code
    }

    void handleZ (void) {
        if ( ! NEED_TO_DO_Z ) {
            return;
        }
        //  main code
    }
The only other alternative is to use multiple processors so each "can't be interrupted by anything else" task has complete ownership of the processor running it.

Locked
Please be positive and constructive with your questions and comments.

Return to “Arduino Shields from Adafruit”