tags:

views:

248

answers:

2

I am writing a program that reads the data from the serial port on Linux. The data are sent by another device with the following frame format:

|start | Command | Data               | CRC  | End |
|0x02  | 0x41    | (0-127 octets)     |      | 0x03|
----------------------------------------------------

The Data field contains 127 octets as shown and octet 1,2 contains one type of data; octet 3,4 contains another data. I need to get these data

I know how to write and read data to and from a serial port in Linux, but it is just to write and read a simple string (like "ABD")

My issue is that I do not know how to parse the data frame formatted as above so that I can:

  • get the data in octet 1,2 in the Data field
  • get the data in octet 3,4 in the Data field
  • get the value in CRC field to check the consistency of the data

Here the sample snip code that read and write a simple string from and to a serial port in Linux:

int writeport(int fd, char *chars) {
    int len = strlen(chars);
    chars[len] = 0x0d; // stick a <CR> after the command
    chars[len+1] = 0x00; // terminate the string properly
    int n = write(fd, chars, strlen(chars));
    if (n < 0) {
        fputs("write failed!\n", stderr);
        return 0;
    }
    return 1;                                                                                                           
}

int readport(int fd, char *result) {
    int iIn = read(fd, result, 254);
    result[iIn-1] = 0x00;
    if (iIn < 0) {
        if (errno == EAGAIN) {
            printf("SERIAL EAGAIN ERROR\n");
            return 0;
        } else {
            printf("SERIAL read error %d %s\n", errno, strerror(errno));
            return 0;
        }
    }                    
    return 1;
}

Does anyone please have some ideas? Thanks all.

+5  A: 

result is an array of chars, which are 1 octet wide.

to read octet n use:

char octet_n = result[n];

So to do what you want you need:

// skip the start and command fields
char *data_field = result + 2; 

int octet_1_2 = data_field[1] | (data_field[2] << 8);
int octet_3_4 = data_field[3] | (data_field[4] << 8);

// crc is at byte 128 + 2 = 130
int crc = result[130];

Edit: An explanation for this line:

int octet_1_2 = data_field[1] | (data_field[2] << 8);

You want to read two consecutive octets into one 16-bit word:

            1
       bits 5        8 7       0
            --------------------
octet_1_2 = | octet 2 | octet 1|
            --------------------

So you want to take bits 7:0 of octet 1 and put them in bits 7:0 of octet_1_2:

octet_1_2 = data_field[1];

Then you want to take bits 7:0 of octet 2 and put them in bits 15:8 of octet_1_2. You do this by shifting octet 2 8 bits to the left, and OR'ing the result into octet_1_2:

octet_1_2 |= data_field[2] << 8;

These two lines can be merged into one as I did above.

Nathan Fellman
Also, one needs to know the endianness of the byte-stream. @Nathan is assuming little-endian in his answer.
Judge Maygarden
Good point. If it is big-endian simply reverse the indeces.
Nathan Fellman
Thank you very much for your answer.I want to ask another one:Could you please explain more how can I get data by doing this:int octet_1_2 = data_field[1] | (data_field[2] << 8);int octet_3_4 = data_field[3] | (data_field[4] << 8);I really do not understand.Thanks a lot.
ipkiss
Thank you very much for your very clear explanation.
ipkiss
Won't `data_field[2] << 8` compute to 0, given that data_field[2] is 8 bits wide? I'd do `octet_1_2 = data_field[2]; octet_1_2 <<= 8; octet_1_2 |= data_field[1];` or cast it to an int before shifting: `((int)data_field[2]) << 8`.
roe
May I ask one more question?I received the CRC function to check the consistency of the data:char crc_checking (char * Data, int Length);I want to ask:- the Data parameter is the whole frame or just the data in the Data field of the frame?- the length parameter is the length of the whole frame or just the length of the Data filed?Thanks a lot.
ipkiss
You'll have to check with whoever supplied that function.
Nathan Fellman
how can I test if your formula is correct.Can I do this: on the sender side://I want to send the number 12: (just ignore other fields in the frame)char t[254];t[0] = 0x34; // 1t[1] = 0x33; //2// then write to the serial portwriteport(fd,t);On the receiver side:char result[254];readport(fd,result)// and use: int r = result[0] | result[1] << 8;And the result is not the number 12.I am not sure on the sender side I did the correct sending behavior as this will be done by another device.Could you please give your suggestion?Thanks a lot.
ipkiss
The result should be `0x3334`. Is that what you see?
Nathan Fellman
Yes, that what I got. But how can I switch it back to number 12?
ipkiss
You have to go out of your way to parse that as a string. Something like `char bla[3]; bla[0] = result[1]; bla[1] = result[2]; bla[2] = '\0';` Then `bla` will have *string* "12" which is equivalent to the sequence 0x33 0x34 0x00.
Nathan Fellman
OK, thanks a lot. I still have more questions. In the above frame, the first octet is 0x02 means STX which contains 3 characters. so if I put it as the first element in the frame like this: char sCmd[254]; sCmd[0] = 0x02; // start frame fields sCmd[1] = 0x41; // command filedand construct other fields like the above frame format and then send it ,then the order of elements in the array will be automatically changed leading to wrong results on the receiver side. Am I thinking something wrong? (I have no idea how the other device will send the message, just know the frame format as above)
ipkiss
@ipkiss, you'll get much better answers if you just ask all these as separate questions. The formatting in comments leaves much to be desired, precisely for this reason. I don't understand what you're asking here.
Nathan Fellman
@nathan: could you please help me out with these, please: http://stackoverflow.com/questions/2531779/clear-data-at-serial-port-in-linux-in-c and http://stackoverflow.com/questions/2531860/extract-wrong-data-from-a-frame-in-c
ipkiss
A: 
Opera
Sometimes the compiler adds padding between members in a struct to improve access performance. I'd add some compiler pragmas that guarantee no padding is used before going with an approach like this. Google #pragma pack for more info about it.
Laserallan
+1 laserallan - reading structs in this case is not a good idea
KevinDTimm
@laserallan; the only reason a compiler would have to pad the structure is alignment, isn't it? Why would it need to align chars? I'm not saying this answer is good. Just saying it's not necessarily wrong, with the addition that it's all chars.
roe
Actually, based on the example in the question, both the sender and the receiver are under his control. If they both can share a header file, they can share the definition of the struct, so there's no need to worry about padding and endianness, since both sides have the same.
Nathan Fellman
Still, this is less portable the the answer from @Nathan.
Judge Maygarden
@nathan it is not under his control if he doesn't choose the compiler on the both sides himself, packing conventions is hardware and compiler dependent. He's explicitly writing that the data is being sent by another device.@roe most likely it's not a problem in this particular case. It's just about being defensive in the coding. If someone not familiar with the issue finds it a good idea to introduce a word in the struct it may break the whole thing in a non obvious way.
Laserallan