串口是一种常见的通信外设,广泛应用于各种设备之间的数据通信。在串口通信中,设备之间需要约定通信协议,以确保数据的正确传输和解析。常见的串口协议由帧头、数据长度、帧类型、数据、校验等字段构成,这类协议设计较为完善,可以在接收的字节流中按照协议格式正确地提取一帧数据,对于出现粘包或者分包的情况也能正确解析。
然而,并不是所有的串口协议都设计得那么完善。有些串口协议可能没有用于同步的数据帧头和帧尾,如果一帧数据出现了分包(分多次接收),那么将无法解析出正确的数据。这种情况下,需要采取一些特殊的处理方法,以确保数据的正确解析和传输。
解决方法#
在串口通信中,数据的分包和粘包问题是比较常见的,会影响数据的正确解析和传输。为了解决这个问题,可以采用一些有效的处理方法。
一种常见的处理方法是加入帧超时机制。在串口接收数据时,不断地检测两个字节或者两次接收数据的间隔时间,当间隔时间超过某一值,则认为一帧数据接收完成,一帧数据接收完整之后再做数据解析和具体业务。默认的超时时间间隔可以设置为 50 ms,然后根据实际情况稍微调大或者调小超时时间间隔。这种方法可以有效地解决数据分包和粘包问题,但也会带来串口接收性能下降的问题。
处理逻辑可以参考下面的伪代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
| void UartInit(void)
{
// 初始化串口硬件参数
// 创建消息队列和ringbuff
ret = rtos_init_queue(&uart_queue, "uart_q", sizeof(uart_msg_t), 20);
rb_init(&g_uart_rb, g_uart1_rx_buff, sizeof(g_uart1_rx_buff));
// 创建一个定时器 50ms
ret = rtos_init_timer(&uart_timer, 50, uart_timer_cb, 0);
// 创建串口接收处理线程
ret = rtos_create_thread(&uart_main_thread,
20,
"uart_main",
uart_main_thread_entry,
4 * 1024,
NULL);
// 注册串口中断回调
uart_set_rx_callback(port, uart_rx_cb);
}
// 串口接收中断回调
static void uart_rx_cb(int uport, void *param)
{
uart_msg_t send_msg = {0};
send_msg.type = 0;
//有串口数据进来,发消息通知串口数据处理线程去读取串口数据
if (uart_queue)
rtos_push_to_queue(&uart_queue, &send_msg, 0);
}
// 串口数据接收处理任务
static void uart_main_thread_entry(void *arg)
{
OSStatus err = kNoErr;
int ret;
uint32_t read_len = 0, recv_len = 0, free_len=0;
uart_msg_t recv_msg = {0};
while (1)
{
err = rtos_pop_from_queue(&uart_queue, &recv_msg, 100);
if (err != kNoErr)
{
continue;
}
switch (recv_msg.type)
{
case 0:
//收到串口有数据消息。
//读取串口数据,写入到ringbuffer中先缓存,同时重新启动帧超时定时器
break;
case 1:
// 收到定时器超时消息
// 一帧数据接收完成,进行数据解析和业务处理
break;
default:
break;
}
}
}
// 定时器超时回调
static void uart_timer_cb( void *arg )
{
uart_msg_t send_msg = {0};
rtos_stop_timer(&uart_timer);
send_msg.type = 1;
// 发消息通知处理线程,完成一帧数据接收
if (uart_queue)
rtos_push_to_queue(&uart_queue, &send_msg, 0);
}
|
引入帧超时机制虽然解决了串口数据分包问题,但也带来了串口接收性能下降的问题。如果串口分包数据大小是固定的,比如每次 20 字节分包一次,那么还可以稍微优化一下接收速度,如果收到的数据不是固定分包字节的倍数,也可以认为一帧数据接收结束了,不必再去等定时器超时,这种方法可以优化串口接收数据的性能。
另外,如果多次连续发送串口数据,有可能会出现粘包现象,如果粘包对业务有影响,那么需要要求串口发送两帧数据之间的间隔要大于一定时间。最好是要求串口发送字节之间小于一定时间,比如 10 ms,发送数据帧之间也大于一定时间,比如 100 ms,这样分包和粘包的问题都得以解决。
在设计串口协议时,需要考虑到数据的同步、长度和校验等因素,确保数据能够正确解析和传输。没有数据同步标志的串口协议,可以加入帧超时机制解决数据分包问题,缺点就是牺牲一点串口接收数据的性能。