interrupt driven gps handler

11 Oct 2010 . Edited: 11 Oct 2010

Hi,

I want to make a robot with a couple of sensors and an xbee module connected to the 3 uarts. The compass sensor and the xbee modules only send messages shorter than 16 characters, so the messages fit in the uart buffer. So I can read them when it suits me. But i have a problem with the gps module. This module sends a lot of data, and only a small part is interesting. Every second it sends a couple of lines:

 

 

$GPGGA,184943.000,3409.0533,N,11817.0188,W,1,07,1.4,144.1,M,-33.5,M,,0000*63
$GPRMC,184943.000,A,3409.0533,N,11817.0188,W,0.00,,200306,,,A*62
$GPVTG,,T,,M,0.00,N,0.0,K,A*13

 

 

This is some sample data i have found on the internet, my module does like 5 of these strings. But the only interesting thing is this:

3409.0533,N,11817.0188,W

What is the best way to capture this data? I can't check the buffer in a while loop in the main code. My code is busy with a PID controller and a lot of floating point calculations.

11 Oct 2010

I already have code that does exactly what you want. Not yet ready to publish the project but if you message me your email address I'll send you the module I have that reads the incoming serial data and just exposes a simple API to get the info you want.

11 Oct 2010

Thanks! Message send

11 Oct 2010

Your Gmail account didn't like zip attachments :(

11 Oct 2010

Although NOT completed, I published an "interim" version for you to cherry pick the code out here

http://mbed.org/users/AjK/programs/SOWB/lg489e

11 Oct 2010 . Edited: 11 Oct 2010

Now I am playing around with the serialbuffereddemo (http://mbed.org/users/jarkman/programs/SerialBufferedDemo/80cd523739507c8bade52dfce149d900) And I made this: bootje. This works nice! And with the sure compass module: bootjeAny comments?

11 Oct 2010 . Edited: 11 Oct 2010
10 Nov 2010

I finaly found time to carry on with this project. Now as a test I want to read a byte from a serial port and dimming a led:

#include "mbed.h"
#include "SerialBuffered.h"

float longitude;
float latitude;
int lock;
int tijd = 0;
int siv = 0;
char msg[256];

Serial loggerSerial(USBTX, USBRX);
Serial kompas(p9, p10);  // tx, rx
DigitalOut led2(LED2);
SerialBuffered *b = new SerialBuffered( 1001, p28, p27);
PwmOut led1(LED1);

float trunc(float v) {
    if (v < 0.0) {
        v*= -1.0;
        v = floor(v);
        v*=-1.0;
    } else {
        v = floor(v);
    }
    return v;
}



void getline() {

    while (b->getc() != '$');   // wait for the start of a line
    for (int i=0; i<256; i++) {
        msg[i] = b->getc();
        if (msg[i] == '\r') {
            msg[i] = 0;

            return;
        }
    }
}


int sample() {

    char ns, ew;


    while (1) {


        getline();
        // Check if it is a GPGGA msg (matches both locked and non-locked msg)
        if (sscanf(msg, "GPGGA,%d,%f,%c,%f,%c,%d", &tijd, &latitude, &ns, &longitude, &ew, &lock) >= 1) {
            if (!lock) {
                longitude = 0.0;
                latitude = 0.0;
                return 1;
            } else {
                if (ns == 'S') {
                    latitude  *= -1.0;
                }
                if (ew == 'W') {
                    longitude *= -1.0;
                }

                latitude = latitude / 100;
                latitude = trunc(latitude) + ((latitude - trunc(latitude))/60)*100;
                longitude = longitude / 100;
                longitude = trunc(longitude) + ((longitude -trunc(longitude))/60)*100;



                return 1;
            }
        }





    }

}


int main() {
    char xas;
    loggerSerial.baud(19200);
    b->baud( 9600 );
    b->setTimeout( 1 );
    led1 = 0.5;
    led2 =0;
    while (1) {

        if (sample()) {
            tijd = tijd + 10000; //UTC to local time
            if (tijd > 240000)
                tijd = tijd - 240000;
            loggerSerial.printf("I'm at %f, %f, and the time is: %d And fix is: %d\n\r", longitude, latitude, tijd, lock);
        }


        while (loggerSerial.readable()) {
            xas = loggerSerial.getc();
            led1 = xas / 255.0;
        }

      




    }

}
But the problem is that the GPS function is blocking, even if there is no data. So the serial port is only read once and then the mbed hangs waiting for a new line from the GPS module. I already tried adding if(b->readable()). But that doesn't work either.

 

How to fix this?

10 Nov 2010

You can also use .attach in the Serial API for mbed and it will setup a function to handle serial interrupts.

 

http://mbed.org/users/simon/programs/SerialInterrupt/73bd78f86460d1d81bc5f362d0883ef7/docs/main_8cpp_source.html

 

10 Nov 2010 . Edited: 10 Nov 2010

Ok, this works, but if the data is comming in too fast (meaning faster than 1hz), the whole mbed stops. The output stops and the led turns 100% on. Code:

 

#include "mbed.h"
#include "SerialBuffered.h"

float longitude;
float latitude;
int lock;
int tijd = 0;
int siv = 0;
char msg[256];

Serial loggerSerial(USBTX, USBRX);
Serial kompas(p9, p10);  // tx, rx
DigitalOut led2(LED2);
SerialBuffered *b = new SerialBuffered( 1001, p28, p27);
PwmOut led1(LED1);

void serieel_lezen() {
    char xas;
    while (loggerSerial.readable()) {
        xas = loggerSerial.getc();
        led1 = xas / 255.0;
    }
    }


float trunc(float v) {
    if (v < 0.0) {
        v*= -1.0;
        v = floor(v);
        v*=-1.0;
    } else {
        v = floor(v);
    }
    return v;
}



void getline() {

    while (b->getc() != '$');   // wait for the start of a line
    for (int i=0; i<256; i++) {
        msg[i] = b->getc();
        if (msg[i] == '\r') {
            msg[i] = 0;

            return;
        }
    }
}


int sample() {

    char ns, ew;


    while (1) {


        getline();
        // Check if it is a GPGGA msg (matches both locked and non-locked msg)
        if (sscanf(msg, "GPGGA,%d,%f,%c,%f,%c,%d", &tijd, &latitude, &ns, &longitude, &ew, &lock) >= 1) {
            if (!lock) {
                longitude = 0.0;
                latitude = 0.0;
                return 1;
            } else {
                if (ns == 'S') {
                    latitude  *= -1.0;
                }
                if (ew == 'W') {
                    longitude *= -1.0;
                }

                latitude = latitude / 100;
                latitude = trunc(latitude) + ((latitude - trunc(latitude))/60)*100;
                longitude = longitude / 100;
                longitude = trunc(longitude) + ((longitude -trunc(longitude))/60)*100;



                return 1;
            }
        }





    }

}


int main() {

    loggerSerial.baud(19200);
    loggerSerial.attach(&serieel_lezen);
    b->baud( 9600 );
    b->setTimeout( 1 );
    led1 = 0.5;
    led2 =0;
    while (1) {

        if (sample()) {
            tijd = tijd + 10000; //UTC to local time
            if (tijd > 240000)
                tijd = tijd - 240000;
            loggerSerial.printf("I'm at %f, %f, and the time is: %d And fix is: %d\n\r", longitude, latitude, tijd, lock);
        }

    }

}

PS.

 

The function and variable names are in dutch, is this a problem?

11 Nov 2010

My int and main routine worked better with only getc and putc. I suspected printf and in your case perhaps scanf might disable interrupts too long or have some other issues. Someone else suggested this in another forum post and it seemed to be the case with my code. I had mine running OK at 57600 baud. Its a real bummer, if you need a lot of complex printfs. I wonder if putting it in a buffer with sprintf and then a loop with putcs would work?

11 Nov 2010 . Edited: 11 Nov 2010

 

You may find this of use:- GPS

11 Nov 2010

thanks, problem solved!

 

The problem still exists, but if I comment out  the pc.printf stuff it works.

11 Nov 2010

Interesting that even a printf to a different serial port does not work with serial receive interrupts. In my code where I got rid of the printfs to fix it, it was all on the same serial port (P9,P10).

 

 

 

 

11 Nov 2010

Try to avoid printf()s in anything that's an interrupt handler or called by an interrupt handler. If you look at the examples in the library I did printfs are always in "regular code" (ie non-interrupt context).

11 Nov 2010

the printf IS in the main code...

11 Nov 2010

Without printf you need quite a bit of code for formatting and number to ASCII conversions for a complex application. Adding printf pulls in about 20K+ of system library code. Quite a bit to write on your own. I still wonder if sprintf to a buffer followed by a loop of putc's will work - but that is still kind of ugly. If I get a chance, I will try it in the lab.

11 Nov 2010

The problem is that an mbed has no debugging at all, so we don't know what the problem is. Is it the conversion, or filling the serial port buffer? My guess is that the problem is that a whole line of characters is too big for the write buffer, so the printf instruction has to wait while the buffer is being written. Then the printf fills the buffer again. So there is a blocking wait loop in the instruction.

11 Nov 2010

I believe that printf() is inherently a blocking function, so when it called nothing happens until the last character in the formatted string has been written to the UART buffer. If the string is longer than the a buffer fill, then you have to wait baud rate increments for each character of the string that does not fit in the buffer.

A work-around for this problem is to use sprintf() to get the advantage of formating, a buffer to hold the formated string, and some means of moving characters from the string buffer to the UART buffer when the buffer is not full, without blocking!

A "UART buffer not full" ISR will work, but I prefer to do it in the main loop and thus make console communications the lowest priority activity. The sprintf() call is fast, assuming the string buffer is always big enough.

The example code below is for an AVR microcontroller so the low level UART instructions need to be changed

unsigned char TA_Buf[32];
unsigned char TAX_Ptr, CharsToGo = FALSE;
unsigned char NewMsg = FALSE;

void main(void) {

   char *B;
    
    B = TA_Buf;
    sprintf(B,"\fCom Test v2.00\r\n");
    SendMsg();
   for(;;)
   {
      if (NewMsg)
      {
        CmdDecode();
        NewMsg = FALSE;
      }
      else if (CharsToGo) SendChar();     
      else CheckForMsg();
}

void SendMsg(void)
{
    TAX_Ptr = 0;
    CharsToGo = TRUE;
}


void SendChar(void)
{
    unsigned int j;

   if (UCSR0A & (1 << UDRE0))
   {
      if (TA_Buf[TAX_Ptr] == '\n')
      {
         UDR0 = TA_Buf[TAX_Ptr];
        CharsToGo = FALSE;
      }
      else UDR0 = TA_Buf[TAX_Ptr++];
   }   
}

11 Nov 2010 . Edited: 11 Nov 2010

http://mbed.org/handbook/Serial has a writeable function already that could be used instead of directly reading UART status bits for a loop like you have in send characters above. For USB serial, that would be tricky without it.

http://mbed.org/cookbook/Assembly-Language has another debug option in the last section that could be used instead of printf - to debug printf with interrupts.

 

11 Nov 2010

This code works!

#include "mbed.h"
#include "GPS.h"


Serial pc(USBTX, USBRX);
DigitalOut led1(LED1);
DigitalOut led3(LED3);
DigitalOut led4(LED4);
GPS gps(NC, p25);
PwmOut led2(LED2);
Timeout t2, t3, t4;
char buffer[30];


void send_message() {
    unsigned char x = 0;

    while (buffer[x] != 10) { //10 , 13 = \n
        while (pc.writeable() == 0);
        pc.putc(buffer[x]);
        x++;
    }
    pc.putc(10);
    pc.putc(13);

}


void serieel_lezen() {
    char xas;
    while (pc.readable()) {
        xas = pc.getc();
        led2 = xas / 255.0;
    }

}

int main() {
    GPS_Time q1;

    pc.baud(19200);
    pc.attach(&serieel_lezen);
    gps.baud(9600);
    gps.format(8, GPS::None, 1);





    while (1) {
        // Every 3 seconds, flip LED1 and print the basic GPS info.
        wait(3);
        led1 = 1;

        sprintf(buffer, "abcdefghijklmnopqrstuvwxyz\n");
        send_message();
        led1 = 0;

    }
}
Why isn't this a standard function of printf?

11 Nov 2010

Especially so for a different serial port!

Might also want to add a if busy..wait delay inside the while loops when the buffer status stops it - about the time that it takes to send out a character at that baud rate. My thinking on that is save wasted CPU cycles for other interrupt routines. But then again, I am not sure how wait is actually implemented - hopefully with a timer.

 

11 Nov 2010

That's right; you can create a timer interrupt which generates an interrupt at a rate it takes to send one character. Then every interrupt you check if there is a character ready to send. This is useful if most of the time there actually is a character ready. If most times this is not the case, it is a waste of CPU cycles.

11 Nov 2010

Willem, did you get that from my library? :) GPS

http://mbed.org/users/AjK/libraries/GPS/lhph3y/docs/example1_8cpp_source.html If you want to publish code then you'll need to tell people to import the library or it'll fail to compile at #include "GPS.h"

I published that as I saw a number of people having fun with GPS and since that module is from my project that works fine thought I'd share it.

11 Nov 2010

user Willem Melching wrote:

That's right; you can create a timer interrupt which generates an interrupt at a rate it takes to send one character. Then every interrupt you check if there is a character ready to send. This is useful if most of the time there actually is a character ready. If most times this is not the case, it is a waste of CPU cycles.

Willem, use a timer at the same rate as one char send to generate an interrupt? Why? Why not just setup the Uart to either generate an interrupt when the THRE register is empty (ie, it's now in the TSR register go it's way onto the wire at the baud rate, you can then just put your next char into the THRE register. Standard practice. See SerialBuffered which has a standard _get() function that uses a combination of THRE, buffer and interrupts to send long streams. There's then no need "to check if there's another char ready to go". It's all automatic. Works just fine as long as you have enough buffer space to handle your largest expected stream size to baud rate ratio.

11 Nov 2010

I'm new to ARM controllers. I am used to the really limited 8 bit PIC microcontrollers, so I didn't knew it was possible to assign an interrupt to things like that. But I really like the ARM controller, especially the mbed, mainly because of its community. The only thing I have to add to this code is a PID controller, servo and some math but all these functions are included in a ready to use library. So soon this project will be finished, and of course I will publish the complete code.

11 Nov 2010

There is also an optional second argument to .attach - it can be type Serial::TxIrq or Serial::RxIrq - so I guess you could have one interrupt function for each direction. Default is RxIrq and have not tried two yet.

 

 

 

13 Nov 2010 . Edited: 14 Nov 2010

Had to try it with two serial interrupt routines (TX,RX)  with circular buffers. Uses sprintf and reads back with sscanf in a loop checking for the same number. Seems solid and never locks up, but I could spend a bit more time double checking buffer conditions.

 

 

#include "mbed.h" // Serial TX & RX interrupt loopback test using formatted IO - sprintf and sscanf // Connect TX to RX (p9 to p10) // or can also use USB and type back in the number printed out in a terminal window // Sends out ASCII numbers in a loop and reads them back // If not the same number LED4 goes on // LED1 and LED2 indicate RX and TX interrupt routine activity // LED3 changing indicate main loop running

Serial device(p9, p10);  // tx, rx // Can also use USB and type back in the number printed out in a terminal window // Serial monitor_device(USBTX, USBRX); DigitalOut led1(LED1); DigitalOut led2(LED2); DigitalOut led3(LED3); DigitalOut led4(LED4);

void Tx_interrupt(); void Rx_interrupt(); void send_line(); void read_line();

// Circular buffers for serial TX and RX data - used by interrupt routines const int buffer_size = 255; char tx_buffer[buffer_size]; char rx_buffer[buffer_size]; // Circular buffer pointers volatile int tx_in=0; volatile int tx_out=0; volatile int rx_in=0; volatile int rx_out=0; // Line buffers for sprintf and sscanf char tx_line[80]; char rx_line[80];

// main test program int main() {     int i=0;     int rx_i=0;     device.baud(9600);

// Setup a serial interrupt function to receive data     device.attach(&Rx_interrupt, Serial::RxIrq); // Setup a serial interrupt function to transmit data     device.attach(&Tx_interrupt, Serial::TxIrq);

// Formatted IO test using send and receive serial interrupts // with sprintf and sscanf     while (1) { // Loop to generate different test values - send value in hex, decimal, and octal and  then read back         for (i=0; i<0xFFFF; i++) {             led3=1; // Print ASCII number to tx line buffer in hex             sprintf(tx_line,"%x\r\n",i); // Copy tx line buffer to large tx buffer for tx interrupt routine             send_line(); // Print ASCII number to tx line buffer in decimal             sprintf(tx_line,"%d\r\n",i); // Copy tx line buffer to large tx buffer for tx interrupt routine             send_line(); // Print ASCII number to tx line buffer in octal             sprintf(tx_line,"%o\r\n",i); // Copy tx line buffer to large tx buffer for tx interrupt routine             send_line();             led3=0;

// Read a line from the large rx buffer from rx interrupt routine             read_line(); // Read ASCII number from rx line buffer             sscanf(rx_line,"%x",&rx_i); // Check that numbers are the same             if (i != rx_i) led4=1; // Read a line from the large rx buffer from rx interrupt routine             read_line(); // Read ASCII number from rx line buffer             sscanf(rx_line,"%d",&rx_i); // Check that numbers are the same             if (i != rx_i) led4=1; // Read a line from the large rx buffer from rx interrupt routine             read_line(); // Read ASCII number from rx line buffer             sscanf(rx_line,"%o",&rx_i); // Check that numbers are the same             if (i != rx_i) led4=1;         }     } }

// Copy tx line buffer to large tx buffer for tx interrupt routine void send_line() {     int i;     char temp_char;     bool empty;     i = 0; // Start Critical Section - don't interrupt while changing global buffer variables     NVIC_DisableIRQ(UART1_IRQn);     empty = (tx_in == tx_out);     while ((i==0) || (tx_line[i-1] != '\n')) { // Wait if buffer full         if (((tx_in + 1) % buffer_size) == tx_out) { // End Critical Section - let interrupt routine empty buffer by sending             NVIC_EnableIRQ(UART1_IRQn);             while (((tx_in + 1) % buffer_size) == tx_out) {             } // Start Critical Section - don't interrupt while changing global buffer variables             NVIC_DisableIRQ(UART1_IRQn);         }         tx_buffer[tx_in] = tx_line[i];         i++;         tx_in = (tx_in + 1) % buffer_size;     }     if (device.writeable() && (empty)) {         temp_char = tx_buffer[tx_out];         tx_out = (tx_out + 1) % buffer_size; // Send first character to start tx interrupts, if stopped         device.putc(temp_char);     } // End Critical Section     NVIC_EnableIRQ(UART1_IRQn);     return; }

// Read a line from the large rx buffer from rx interrupt routine void read_line() {     int i;     i = 0; // Start Critical Section - don't interrupt while changing global buffer variables     NVIC_DisableIRQ(UART1_IRQn); // Loop reading rx buffer characters until end of line character     while ((i==0) || (rx_line[i-1] != '\r')) { // Wait if buffer empty         if (rx_in == rx_out) { // End Critical Section - to allow rx interrupt to get new characters for buffer             NVIC_EnableIRQ(UART1_IRQn);             while (rx_in == rx_out) {             } // Start Critical Section - don't interrupt while changing global buffer variables             NVIC_DisableIRQ(UART1_IRQn);         }         rx_line[i] = rx_buffer[rx_out];         i++;         rx_out = (rx_out + 1) % buffer_size;     }     rx_line[i-1] = 0; // End Critical Section     NVIC_EnableIRQ(UART1_IRQn);     return; }

// Interupt Routine to read in data from serial port void Rx_interrupt() {     led1=1; // Loop just in case more than one character is in UART's receive FIFO buffer // Stop if buffer full     while ((device.readable()) || (((rx_in + 1) % buffer_size) == rx_out)) {         rx_buffer[rx_in] = device.getc(); // Uncomment to Echo to USB serial to watch data flow //        monitor_device.putc(rx_buffer[rx_in]);         rx_in = (rx_in + 1) % buffer_size;     }     led1=0;     return; }

// Interupt Routine to write out data to serial port void Tx_interrupt() {     led2=1; // Loop to fill more than one character in UART's transmit FIFO buffer // Stop if buffer empty     while ((device.writeable()) && (tx_in != tx_out)) {         device.putc(tx_buffer[tx_out]);         tx_out = (tx_out + 1) % buffer_size;     }     led2=0;     return; }