在 C 中解析字节流(嵌入式编程)
Parse Bytestream in C (embedded programming)
所以我在两个微控制器之间有一个通信系统,我在它们之间发送数据,即将传感器数据从一个 µC 发送到另一个,然后返回命令。
传感器数据从一个结构中获取并放入一个框架中,它看起来像这样(标识符 "SENSORFRAME" 不是常量;它取决于框架中的内容):
sprintf(message, "\:\SENSORFRAME\:%.2f;%.2f;%.2f;%d;%d;%u\r\r",
input.temperature, input.current, input.voltage,
input.dutycycle,input.lightsensor, input.message);
生成这样的帧:
:\SENSORFRAME:-12.40;1.42;0.53;500;1200;8\r\r
或者对于命令帧:
sprintf(message, "\:\COMMANDFRAME\:%u;%.5f\r\r", input.command, input.data);
生成这样的帧:
:\COMMANDFRAME:3;1.42\r\r
当字节流到达一个微控制器时,它被写入一个简单的环形缓冲区,直到它被处理。
现在我的两个问题是,首先,识别字节流中帧的最佳方法是什么,即“:\”和“\r\r”之间的所有内容,其次如何有效地解析它进入结构——strtok
(“;
”和“:
”)和atoi
/atof
?
的某种组合
如果在读完末尾的双回车 return (CR) 之前不处理帧类型,然后在双回车之后插入一个空字节,那么您可以提取使用 sscanf()
或其他解析技术的帧类型信息。
char frame_type[32];
char colon[2];
int offset;
if (sscanf(ring_buffer, ":\%31[^:]%[:]%n", frame_type, colon, &offset) != 2)
…deal with malformatted frame…
现在 frame_type
中的帧类型是一个字符串。有点奇怪的 %[:]
只匹配一个冒号;不过,重要的是它会被计为一次成功的转换,并且 %n
转换将有效。如果改为:
if (sscanf(":\%31[^:]:%n", frame_type, &offset) != 1)
…deal with malformatted frame…
你不知道尾随的冒号是否匹配,你也不知道 offset
是否包含有效值(字符串中前导字符冒号的偏移量) ).
您至少有两种框架类型;总共有多少(目前,将来可能会有)?在一个层面上,这并不重要。您可以对可能的帧类型使用一系列字符串比较,或者您可以使用帧类型字符串的散列并将其与有效散列值集进行比较,或者您可以设计另一种机制。
一旦您知道您拥有哪种帧类型,您就知道使用什么格式字符串来读取其余数据。由于您阅读了双 CR,您知道结尾包含那个 — 您不需要再次验证它。
例如,对于传感器框架,您可以使用:
if (sscanf(ring_buffer + offset, "%.2f;%.2f;%.2f;%d;%d;%u",
&input.temperature, &input.current, &input.voltage,
&input.dutycycle, &input.lightsensor, &input.message) != 6)
…deal with malformatted sensor frame…
或者,对于命令框架,您可以使用:
if (sscanf(ring_buffer + offset, "%u;%.5f\r\r", &input.command, &input.data) != 2)
…deal with malformatted command frame…
唯一的复杂因素是您使用的是环形缓冲区。这可能意味着您的传感器帧被拆分,因此前 5 个字节位于环形缓冲区的末尾,其余字节位于缓冲区的开头。坦率地说,如果你能负担得起 space 和复制,将环形缓冲区转换为常规(线性?)缓冲区将是最简单的。如果这绝对不是一个选项,那么您可能根本不使用 sscanf()
;您要么需要编写自己的 sscanf()
变体,它可以告知环形缓冲区的形状并使用它,要么您将不得不一次处理字符。
也许您的自定义函数是:
int rbscanf(const char *rb_base, int rb_len, int rb_off, const char *format, ...);
环形缓冲区从 rb_base
开始,总共 rb_len
字节长;数据从 &rb_base[rb_off]
开始。您可能需要指定缓冲区长度和数据长度(rb_len
和 rb_nbytes
)。您可能已经有一个描述环形缓冲区的结构,在这种情况下,您可以将其(指向)传递给函数。
或者,如果您在读取整个帧之前处理数据,那么您可以在读取字节时进行验证。您仍然需要积累字符串和数字以进行转换。您可能会使用 strtol()
和 strtod()
而不是 atoi()
和 atof()
;您将需要了解错误,包括尾随未转换的字符,atoi()
和 atof()
函数无法告诉您这些错误。 strtoX()
函数需要小心,但它们很有效。
所以我在两个微控制器之间有一个通信系统,我在它们之间发送数据,即将传感器数据从一个 µC 发送到另一个,然后返回命令。 传感器数据从一个结构中获取并放入一个框架中,它看起来像这样(标识符 "SENSORFRAME" 不是常量;它取决于框架中的内容):
sprintf(message, "\:\SENSORFRAME\:%.2f;%.2f;%.2f;%d;%d;%u\r\r",
input.temperature, input.current, input.voltage,
input.dutycycle,input.lightsensor, input.message);
生成这样的帧:
:\SENSORFRAME:-12.40;1.42;0.53;500;1200;8\r\r
或者对于命令帧:
sprintf(message, "\:\COMMANDFRAME\:%u;%.5f\r\r", input.command, input.data);
生成这样的帧:
:\COMMANDFRAME:3;1.42\r\r
当字节流到达一个微控制器时,它被写入一个简单的环形缓冲区,直到它被处理。
现在我的两个问题是,首先,识别字节流中帧的最佳方法是什么,即“:\”和“\r\r”之间的所有内容,其次如何有效地解析它进入结构——strtok
(“;
”和“:
”)和atoi
/atof
?
如果在读完末尾的双回车 return (CR) 之前不处理帧类型,然后在双回车之后插入一个空字节,那么您可以提取使用 sscanf()
或其他解析技术的帧类型信息。
char frame_type[32];
char colon[2];
int offset;
if (sscanf(ring_buffer, ":\%31[^:]%[:]%n", frame_type, colon, &offset) != 2)
…deal with malformatted frame…
现在 frame_type
中的帧类型是一个字符串。有点奇怪的 %[:]
只匹配一个冒号;不过,重要的是它会被计为一次成功的转换,并且 %n
转换将有效。如果改为:
if (sscanf(":\%31[^:]:%n", frame_type, &offset) != 1)
…deal with malformatted frame…
你不知道尾随的冒号是否匹配,你也不知道 offset
是否包含有效值(字符串中前导字符冒号的偏移量) ).
您至少有两种框架类型;总共有多少(目前,将来可能会有)?在一个层面上,这并不重要。您可以对可能的帧类型使用一系列字符串比较,或者您可以使用帧类型字符串的散列并将其与有效散列值集进行比较,或者您可以设计另一种机制。
一旦您知道您拥有哪种帧类型,您就知道使用什么格式字符串来读取其余数据。由于您阅读了双 CR,您知道结尾包含那个 — 您不需要再次验证它。
例如,对于传感器框架,您可以使用:
if (sscanf(ring_buffer + offset, "%.2f;%.2f;%.2f;%d;%d;%u",
&input.temperature, &input.current, &input.voltage,
&input.dutycycle, &input.lightsensor, &input.message) != 6)
…deal with malformatted sensor frame…
或者,对于命令框架,您可以使用:
if (sscanf(ring_buffer + offset, "%u;%.5f\r\r", &input.command, &input.data) != 2)
…deal with malformatted command frame…
唯一的复杂因素是您使用的是环形缓冲区。这可能意味着您的传感器帧被拆分,因此前 5 个字节位于环形缓冲区的末尾,其余字节位于缓冲区的开头。坦率地说,如果你能负担得起 space 和复制,将环形缓冲区转换为常规(线性?)缓冲区将是最简单的。如果这绝对不是一个选项,那么您可能根本不使用 sscanf()
;您要么需要编写自己的 sscanf()
变体,它可以告知环形缓冲区的形状并使用它,要么您将不得不一次处理字符。
也许您的自定义函数是:
int rbscanf(const char *rb_base, int rb_len, int rb_off, const char *format, ...);
环形缓冲区从 rb_base
开始,总共 rb_len
字节长;数据从 &rb_base[rb_off]
开始。您可能需要指定缓冲区长度和数据长度(rb_len
和 rb_nbytes
)。您可能已经有一个描述环形缓冲区的结构,在这种情况下,您可以将其(指向)传递给函数。
或者,如果您在读取整个帧之前处理数据,那么您可以在读取字节时进行验证。您仍然需要积累字符串和数字以进行转换。您可能会使用 strtol()
和 strtod()
而不是 atoi()
和 atof()
;您将需要了解错误,包括尾随未转换的字符,atoi()
和 atof()
函数无法告诉您这些错误。 strtoX()
函数需要小心,但它们很有效。