GPSBots.com
Home :: [Photos] : [Tutorials] : [Projects] : [Resources] : [About Us]

Linux Tutorial: Serial Ports

serial_test.c
Using serial ports in Linux is easy, but there are some tricks that are hard to know when to use. This tutorial is specifically about how to write a program that can read NMEA data (such as from a GPS device) and to parse the data that is returned. The following code was compiled successfully under Slackware 10 on a Mini-ITX board connected to a GPS device.
To Compile: g++ tut_linux_serial.cpp -g -o tut_linux_serial -O3
-g to allow gdb to work
-o tut_linux_serial is the output name
-O3 is the optimization level

using namespace std;
#define SERIALPORT "/dev/ttyS0"      // port the device is plugged in to
#define BAUDRATE B38400      // baud rate the device spits out at
#define UPDATE_RATE 20      // update speed in Hz (probably set anywhere from 10-50

#include <iostream>
#include <sys/time.h>      // timers
#include <signal.h>      // timers / serial
#include <termios.h>      // serial
#include <unistd.h>      // serial, file
#include <fcntl.h>      // serial, file

string serial_buffer;      // Unprocessed data off the serial port
int fd_serial;

void timer_handler(int x);      // used in automation
void serial_handler(int status);      // interrupt function called on new data (position isn't guaranteed), pass to ReadSerial()


int main() {
     struct termios tty;      // will be used for new port settings
     struct termios oldtty;      // will be used to save old port settings

     fd_serial = open(SERIALPORT, O_RDWR | O_NOCTTY | O_NONBLOCK);
     if (fd_serial < 0) {
          printf("\nUnable to write to serial port (%s), are you root?\n", SERIALPORT);
          _exit(1);
     }
     tcgetattr(fd_serial, &oldtty);      // save current port settings
     bzero(&tty, sizeof(tty));      // Initialize the port settings structure to all zeros
     tty.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD | CRTSCTS;      // 8N1
     tty.c_iflag = IGNPAR;
     tty.c_oflag = 0;
     tty.c_lflag = 0;
     tty.c_cc[VMIN] = 0;      // 0 means use-vtime
     tty.c_cc[VTIME] = 1;      // time to wait until exiting read (tenths of a second)

     tcflush(fd_serial, TCIFLUSH);      // flush old data
     tcsetattr(fd_serial, TCSANOW, &tty);      // apply new settings
     fcntl(fd_serial, F_SETOWN, getpid());      // enable our PID to receive serial interrupts
     fcntl(fd_serial, F_SETFL, FASYNC);
     
     
     struct sigaction saio;      // set the serial interrupt handler
     saio.sa_handler = serial_handler;      // to this function
     sigemptyset(&saio.sa_mask);      // clear existing settings
     saio.sa_flags = 0;      // make sure sa_flags is cleared
     saio.sa_restorer = NULL;      // no restorer
     sigaction(SIGIO, &saio, NULL);      // apply new settings


     // set up the main timer
     struct itimerval timer1;      // set up the timers
     signal(SIGALRM, timer_handler);      // call this function on rollover
     timer1.it_interval.tv_sec = 0;      // reset val
     timer1.it_interval.tv_usec = (__suseconds_t)((1.0/(double)UPDATE_RATE)*1000000);      // reset val (converts UPDATE_RATE from Hz into microseconds
     timer1.it_value.tv_sec = timer1.it_interval.tv_sec;      // initial val
     timer1.it_value.tv_usec = timer1.it_interval.tv_usec;      // initial val
     setitimer(ITIMER_REAL, &timer1, NULL);      // apply new settings

     
     while (1) { }      // press ctrl-c to exit the program

     write(fd_serial, "t", 1);      // where t is the test char to send

     tcsetattr(fd_serial, TCSANOW, &oldtty);      // restore the old port settings before quitting
}



void serial_handler (int status) {
          // this function is called whenever there is new data to be read off the serial port.
          // It has to execute quickly, so the data processing is *not* done here.
     char temp_buffer[256*2];      // max chars to read at once, if you don't read the entire buffer then the function will get called again
     int len = read(fd_serial, temp_buffer, sizeof(temp_buffer));      // do the actual read
     temp_buffer[len] = 0;      // null terminate the string **important**
     serial_buffer += temp_buffer;      // append what we read to the serial_buffer of unprocessed data
}




void timer_handler(int x) {
          // this function gets called UPDATE_RATE times per second, it handles the data processing off the serial port
          // ** note that serial_buffer gets populated from serial_handler
     signal(SIGALRM, timer_handler);      // clear the interrupt flag, so it will trigger again when we exit

     int infinite_loop_preventer = 0;      // sanity check to make sure this section won't run forever
     if (serial_buffer.length() > 0) {      // unprocessed data exists
          int start_pos;      // position of the first $ char (all packets must start with a $
          int end_pos_n;      // position of the first \n char following the first $
          int end_pos_r;      // position of the first \r char following the first $
          int end_pos;      // set to the lesser of end_pos_n or end_pos_r

          do {
               infinite_loop_preventer++;
                    // 1) find start char
                    // 2) find end char (\n or \r)
                    // 3) process that data
                    // loop while there's more data

               start_pos = serial_buffer.find("$", 0);
               end_pos_n = serial_buffer.find("\n", start_pos);
               end_pos_r = serial_buffer.find("\r", start_pos);
               end_pos = end_pos_n < end_pos_r ? end_pos_n : end_pos_r;      // choose the lesser of the two ending points

               if (start_pos != string::npos && end_pos != string::npos) {
                    string data = serial_buffer.substr(start_pos, end_pos-start_pos);      // just the current packet
                    serial_buffer = serial_buffer.substr(end_pos+1);      // remove parsed section
     //                    cout << "Debug: ";
     //                    cout << " start_pos=" << start_pos << ";";
     //                    cout << " end_pos=" << end_pos << ";";
     //                    cout << " data.len=" << data.length() << ";";
     //                    cout << endl << " data=" << data << ";";
     //                    cout << endl << " serial_buffer=" << serial_buffer << ";";
     //                    cout << endl;
                    cout << data << endl;
               }
          } while (start_pos != string::npos && end_pos != string::npos && infinite_loop_preventer < 10000);      // keep processing if buffer has end char in it
     }
}

Sample Output:

GPGLL,3354.4970,N,11759.5354,W,025604,V,S*52
GPGLL,3354.4980,N,11759.5353,W,025605,V,S*55
GPGLL,3354.4990,N,11759.5355,W,025606,V,S*22