如何处理缓冲串行数据
How to handle buffering serial data
我正在尝试找到一个很好的读取串行数据的解决方案,以及当 read()
完成但包含不完整消息时该怎么做。
设备之间的预期消息具有定义的开始和结束字节,因此很容易看到消息何时开始和结束。
我可以正常打开串口并从串口读取。但是我遇到计算机读取速度比数据通过速度快,我收到一条不完整的消息。
对于这个例子,假设预期的消息是
0x10 0xFF 0xFF 0xFF 0xFF 0x11
以0x10开始,0x11结束,0xFF为数据字节
我是 C 的新手,所以我可能遗漏了一些明显的东西,
我目前的解决方案
int main() {
/* Ommited serial port opening and checking*/
char read_buffer[80];
char message_buffer[80];
int message_buffer_index = 0;
int start_index = -1;
int end_index = -1;
int read_bytes;
read_bytes = read(serial_port, read_buffer, sizeof(read_buffer) - 1);
/* Now lets say read_bytes returns 3 and read buffer is {0x10, 0xFF, 0xFF} */
/* What should I do with the read_buffer? Currently appending to message buffer*/
memcpy(&message_buffer[message_buffer_index], &read_buffer[0], read_bytes);
/* Now check the message buffer for a full message */
for (int i = 0; i < 80; i++) {
if (message_buffer[i] = 0x10) {
start_index = i;
continue;
}
if (message_buffer[i] = 0x11) {
end_index = i;
}
if (start_index != -1 && end_index != -1) {
/* Found a message, do something with it, not super important here */
process_message();
/* Now how to erase the full message from the
buffer and push any non processed data to the
front? */
remove_message();
}
}
}
int process_message();
int remove_message();
您需要循环缓冲区。将数据放在缓冲区中,例如当有足够的数据或在任何方便的时候,进程会使用它们。
维基百科上有关于它的优秀文章https://en.wikipedia.org/wiki/Circular_buffer
阅读时最好使用 stdio,像这样:
FILE *fp = fdopen(serial_port, "r");
while (blabla) {
while (fgetc(fp) != 0x10)
; // wait until start
while ((c = fgetc(fp)) != 0x11)
message_buffer[message_buffer_index++] = c;
// here you have a complete message
}
如果需要,插入 EOF 和错误检查
为了尽量减少多次 read() 小字节数系统调用的开销(例如,一次读取一个字节的错误解决方案),请在您的文件中使用中间缓冲区代码。
串行终端的 read() 应处于阻塞模式以避免 return 零字节代码。
#define BLEN 1024
unsigned char rbuf[BLEN];
unsigned char *rp = &rbuf[BLEN];
int bufcnt = 0;
/* get a byte from intermediate buffer of serial terminal */
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++;
}
有关串行终端的正确 termios 初始化代码,请参阅 this answer。您应该将 VMIN 参数增加到更接近 BLEN 值的值或至少是最长预期消息的长度,并且 VTIME 为 1。
现在您可以一次一个字节方便地访问接收到的数据,而性能损失最小。
#define MLEN 1024 /* choose appropriate value for message protocol */
int main()
{
unsigned char mesg[MLEN];
...
while (1) {
while (getbyte() != 0x10)
/* discard data until start found */ ;
length = 0;
while ((mesg[length] = getbyte()) != 0x11) {
/* accumulate data until end found */
length++;
}
/* process the message */
...
} /* loop for next message */
...
}
请注意,您对消息帧的检测并不可靠。
如果数据是二进制的,因此可以使用与这些开始和结束字节相同的值,那么对接收到的数据的这种解析很容易出现未对齐的消息帧。
有关正确算法的说明,请参阅 this answer。
我正在尝试找到一个很好的读取串行数据的解决方案,以及当 read()
完成但包含不完整消息时该怎么做。
设备之间的预期消息具有定义的开始和结束字节,因此很容易看到消息何时开始和结束。
我可以正常打开串口并从串口读取。但是我遇到计算机读取速度比数据通过速度快,我收到一条不完整的消息。
对于这个例子,假设预期的消息是
0x10 0xFF 0xFF 0xFF 0xFF 0x11
以0x10开始,0x11结束,0xFF为数据字节
我是 C 的新手,所以我可能遗漏了一些明显的东西, 我目前的解决方案
int main() {
/* Ommited serial port opening and checking*/
char read_buffer[80];
char message_buffer[80];
int message_buffer_index = 0;
int start_index = -1;
int end_index = -1;
int read_bytes;
read_bytes = read(serial_port, read_buffer, sizeof(read_buffer) - 1);
/* Now lets say read_bytes returns 3 and read buffer is {0x10, 0xFF, 0xFF} */
/* What should I do with the read_buffer? Currently appending to message buffer*/
memcpy(&message_buffer[message_buffer_index], &read_buffer[0], read_bytes);
/* Now check the message buffer for a full message */
for (int i = 0; i < 80; i++) {
if (message_buffer[i] = 0x10) {
start_index = i;
continue;
}
if (message_buffer[i] = 0x11) {
end_index = i;
}
if (start_index != -1 && end_index != -1) {
/* Found a message, do something with it, not super important here */
process_message();
/* Now how to erase the full message from the
buffer and push any non processed data to the
front? */
remove_message();
}
}
}
int process_message();
int remove_message();
您需要循环缓冲区。将数据放在缓冲区中,例如当有足够的数据或在任何方便的时候,进程会使用它们。
维基百科上有关于它的优秀文章https://en.wikipedia.org/wiki/Circular_buffer
阅读时最好使用 stdio,像这样:
FILE *fp = fdopen(serial_port, "r");
while (blabla) {
while (fgetc(fp) != 0x10)
; // wait until start
while ((c = fgetc(fp)) != 0x11)
message_buffer[message_buffer_index++] = c;
// here you have a complete message
}
如果需要,插入 EOF 和错误检查
为了尽量减少多次 read() 小字节数系统调用的开销(例如,一次读取一个字节的错误解决方案),请在您的文件中使用中间缓冲区代码。
串行终端的 read() 应处于阻塞模式以避免 return 零字节代码。
#define BLEN 1024
unsigned char rbuf[BLEN];
unsigned char *rp = &rbuf[BLEN];
int bufcnt = 0;
/* get a byte from intermediate buffer of serial terminal */
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++;
}
有关串行终端的正确 termios 初始化代码,请参阅 this answer。您应该将 VMIN 参数增加到更接近 BLEN 值的值或至少是最长预期消息的长度,并且 VTIME 为 1。
现在您可以一次一个字节方便地访问接收到的数据,而性能损失最小。
#define MLEN 1024 /* choose appropriate value for message protocol */
int main()
{
unsigned char mesg[MLEN];
...
while (1) {
while (getbyte() != 0x10)
/* discard data until start found */ ;
length = 0;
while ((mesg[length] = getbyte()) != 0x11) {
/* accumulate data until end found */
length++;
}
/* process the message */
...
} /* loop for next message */
...
}
请注意,您对消息帧的检测并不可靠。
如果数据是二进制的,因此可以使用与这些开始和结束字节相同的值,那么对接收到的数据的这种解析很容易出现未对齐的消息帧。
有关正确算法的说明,请参阅 this answer。