3

I am trying to read complete messages from my GPS via serial port.

The message I am looking for starts with:

0xB5 0x62 0x02 0x13

So I read from the serial port like so

while (running !=0)
{

int n = read (fd, input_buffer, sizeof input_buffer); 


for (int i=0; i<BUFFER_SIZE; i++)
{   



if (input_buffer[i]==0xB5  && input_buffer[i+1]== 0x62 && input_buffer[i+2]== 0x02 && input_buffer[i+3]== 0x13    && i<(BUFFER_SIZE-1) )
     { 

            // process the message.
     }

}

The problem I am having is that I need to get a complete message. Half of a message could be in the buffer one iteration. And the other half could come into the message the next iteration.

Somebody suggested that free the buffer up from the complete message. And then I move the rest of data in the buffer to the beginning of the buffer.

How do I do that or any other way that make sure I get every complete selected message that comes in?

edit// enter image description here

I want a particular class and ID. But I can also read in the length

001
  • 13,291
  • 5
  • 35
  • 66
Ariel Baron
  • 331
  • 4
  • 13
  • You could read 1 byte at a time in a loop until you have a complete message. – 001 Apr 07 '17 at 14:37
  • Does the file close at the end of the message? What terminates a message? What is the general message format? – Galik Apr 07 '17 at 14:38
  • @Galik I added the message structure – Ariel Baron Apr 07 '17 at 14:43
  • @JohnnyMopp What if that byte is in the middle of a message. Wouldn't I miss that message? – Ariel Baron Apr 07 '17 at 14:44
  • Per your update, you can break it up into 2 reads: read N bytes (up and including the LENGTH field - not sure how many bytes each field is). Then read the next LENGTH bytes. – 001 Apr 07 '17 at 14:51
  • the size of the length is 2 bytes, after the length comes the payload which is however many bytes long the length says it is – Ariel Baron Apr 07 '17 at 14:54

2 Answers2

3

To minimize the overhead of making many read() syscalls of small byte counts, use an intermediate buffer in your code.
The read()s should be in blocking mode to avoid a return code of zero bytes.

#define BLEN    1024
unsigned char rbuf[BLEN];
unsigned char *rp = &rbuf[BLEN];
int bufcnt = 0;

static unsigned char getbyte(void)
{
    if ((rp - rbuf) >= bufcnt) {
        /* buffer needs refill */
        bufcnt = read(fd, rbuf, BLEN);
        if (bufcnt <= 0) {
            /* report error, then abort */
        }
        rp = rbuf;
    }
    return *rp++;
}

For proper termios initialization code for the serial terminal, see this answer. You should increase the VMIN parameter to something closer to the BLEN value.

Now you can conveniently access the received data a byte at a time with minimal performance penalty.

#define MLEN    1024  /* choose appropriate value for message protocol */
unsigned char mesg[MLEN];

while (1) {
    while (getbyte() != 0xB5)
        /* hunt for 1st sync */ ;
retry_sync:
    if ((sync = getbyte()) != 0x62) {
        if (sync == 0xB5)
            goto retry_sync;
        else    
            continue;    /* restart sync hunt */
    }

    class = getbyte();
    id = getbyte();

    length = getbyte();
    length += getbyte() << 8;

    if (length > MLEN) {
        /* report error, then restart sync hunt */
        continue;
    }
    for (i = 0; i < length; i++) {
        mesg[i] = getbyte();
        /* accumulate checksum */
    }

    chka = getbyte();
    chkb = getbyte();
    if ( /* valid checksum */ ) 
        break;    /* verified message */

    /* report error, and restart sync hunt */
}

/* process the message */
switch (class) {
case 0x02:
    if (id == 0x13) {
        ... 
...
Community
  • 1
  • 1
sawdust
  • 16,103
  • 3
  • 40
  • 50
1

You can break the read into three parts. Find the start of a message. Then get the LENGTH. Then read the rest of the message.

// Should probably clear these in case data left over from a previous read
input_buffer[0] = input_buffer[1] = 0;

// First make sure first char is 0xB5
do {
    n = read(fd, input_buffer, 1); 
} while (0xB5 != input_buffer[0]);

// Check for 2nd sync char
n = read(fd, &input_buffer[1], 1);

if (input_buffer[1] != 0x62) {
     // Error
     return;
}

// Read up to LENGTH
n = read(fd, &input_buffer[2], 4); 

// Parse length
//int length = *((int *)&input_buffer[4]);
// Since I don't know what size an int is on your system, this way is better
int length = input_buffer[4] | (input_buffer[5] << 8);

// Read rest of message
n = read(fd, &input_buffer[6], length);

// input_buffer should now have a complete message

You should add error checking...

001
  • 13,291
  • 5
  • 35
  • 66
  • The message diagram clearly shows two bytes for sync, yet your code only bothers to check for the first one. The `length` is indicated as little-endian, and your code treats it as big-endian. – sawdust Apr 07 '17 at 20:12
  • @sawdust Ok. I've added a check for that. – 001 Apr 07 '17 at 20:23
  • Your code is still has bugs. The attempt to get the bytes for the length, `n = read(fd, &input_buffer[2], 4)`, could fetch less than the 4 bytes requested. See https://stackoverflow.com/questions/43871939/serial-port-read-is-not-complete So then the subsequent **read()** for the rest of the message uses a bogus value of `length`. To compound this error, the return codes are not checked. – sawdust Sep 28 '17 at 21:09