【问题标题】:Parse and read data frame in C?在 C 中解析和读取数据帧?
【发布时间】:2010-03-23 14:14:03
【问题描述】:

我正在编写一个从 Linux 上的串口读取数据的程序。 数据由其他设备发送,帧格式如下:

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

Data 字段包含 127 个八位字节,如图所示,八位字节 1,2 包含一种类型的数据;八位字节 3,4 包含另一个数据。我需要获取这些数据

我知道如何在 Linux 中向串行端口写入和读取数据,但它只是写入和读取一个简单的字符串(如“ABD”)

我的问题是我不知道如何解析上述格式的数据框,以便我可以:

  • 在数据字段中获取八位字节 1,2 中的数据
  • 在数据字段中获取 octet 3,4 中的数据
  • 获取CRC字段中的值以检查数据的一致性

这里是在 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;
}

请问有人有什么想法吗?

【问题讨论】:

  • 把你的代码放在一个“代码”块中,这样会更容易阅读。

标签: c linux serial-port frame


【解决方案1】:

result 是一个由chars 组成的数组,其宽度为 1 个八位字节。

读取八位字节n使用:

char octet_n = result[n];

所以要做你想做的事:

// 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];

编辑:对这一行的解释:

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

您想将两个连续的八位字节读入一个 16 位字:

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

所以你想取八位字节 1 的 7:0 位并将它们放入 octet_1_2 的 7:0 位中:

octet_1_2 = data_field[1];

然后你想取字节 2 的 7:0 位并将它们放入 octet_1_2 的位 15:8 中。为此,您可以将八位字节 2 向左移动 8 位,然后 OR' 将结果转换为 octet_1_2

octet_1_2 |= data_field[2] << 8;

这两行可以像我上面那样合并成一条。

【讨论】:

  • 另外,需要知道字节流的字节序。 @Nathan 在他的回答中假设小端。
  • 非常感谢您的回答。我想问另一个:你能否解释一下我如何通过这样做来获取数据:int octet_1_2 = data_field[1] | (data_field[2]
  • 不会 data_field[2] &lt;&lt; 8 计算为 0,因为 data_field[2] 是 8 位宽?我会做 octet_1_2 = data_field[2]; octet_1_2 &lt;&lt;= 8; octet_1_2 |= data_field[1]; 或在转移之前将其转换为 int :((int)data_field[2]) &lt;&lt; 8
  • 我可以再问一个问题吗?我收到了CRC函数来检查数据的一致性:char crc_checking(char * Data, int Length);我想问: - Data 参数是整个帧还是只是帧的 Data 字段中的数据? - 长度参数是整个帧的长度还是只是数据字段的长度?非常感谢。
  • 您必须与提供该功能的人核实。
【解决方案2】:

在 C 中读取格式化数据的最佳方法是读取结构。 鉴于您拥有的帧格式,我将执行以下操作。


typedef struct special_data
{
    char first_data[2];
    char second data[2];
} special_data_t;

union data_u
{
    special_data_t my_special_data;
    char           whole_data[128];
};

typedef struct data_frame
{
    unsigned char start;
    unsigned char cmd;
    union data_u  data;
    unsigned char crc;
    unsigned char end;
} data_frame_t;

void func_read(int fd)
{
    data_frame_t my_data;

    if (read(fd, &my_data, sizeof(my_data)) != -1)
    {
        // Do something
    }
    return;
}

这样您就可以通过结构字段访问您需要的数据。第一个结构和联合只是访问帧数据字段中所需字节的助手。

【讨论】:

  • 有时编译器会在结构中的成员之间添加填充以提高访问性能。我会添加一些编译器编译指示,以保证在使用这样的方法之前不使用填充。谷歌#pragma pack 了解更多信息。
  • +1 laserallan - 在这种情况下读取结构不是一个好主意
  • @laserallan;编译器必须填充结构的唯一原因是对齐,不是吗?为什么需要对齐字符?我并不是说这个答案很好。只是说不一定是错的,而且都是字符。
  • 其实根据问题中的例子,发送者和接收者都在他的控制之下。如果他们都可以共享一个头文件,他们可以共享结构的定义,所以不需要担心填充和字节序,因为双方都是一样的。
  • @nathan 如果他自己不选择双方的编译器,则不受他的控制,打包约定取决于硬件和编译器。他明确写道,数据正在由另一台设备发送。 @roe 在这种特殊情况下很可能不是问题。这只是在编码中进行防御。如果不熟悉这个问题的人发现在结构中引入一个词是个好主意,它可能会以一种不明显的方式破坏整个事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-29
  • 1970-01-01
  • 2021-04-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多