数据通信的协议帧如果有帧头,是否需要定义转译和在发送前转译数据?

bandaoyu 2020-02-19 08:49:41

我的小组定的协议帧格式,帧头是0xFF0xFE,但是没有看到有数据内包含与帧头一样的数据时如何转译的说明,所以我就有疑问,

我就问:

有串口通信的需求吗? 有的话,“协议头” 在串口通信时是不是应该说明转译规则(数据中含有和协议头一样的数据),或者明确两帧之间的发送时隙的要求。不然会不会出现粘包或多帧合并发送时没法拆包?

对方回答:


我们实际上会在缓存解包,分包与拆包,不会出现大量粘包现象的。



问题来了,我不太理解他说的:“在缓存解包,分包与拆包” 是什么意思? 是如何识别帧头帧尾,分包拆包的?
...全文
1389 36 打赏 收藏 转发到动态 举报
写回复
用AI写文章
36 条回复
切换为时间正序
请发表友善的回复…
发表回复
djc。 2022-01-20
  • 打赏
  • 举报
回复

我使用的串口通信,要对伺服读取实时示波器数据。也出现了粘包现象。实时数据最小采样间隔是0.1ms。

bandaoyu 2020-03-04
  • 打赏
  • 举报
回复
引用 34 楼 ba_wang_mao 的回复:
“单片机上如果上位机每次只发送一帧独立的报文(上位机如果不是将几个报文打包一起发的话),完全没有必须考虑粘包问题、分包问题,也就是说: (1)、如果上位机发送的2条报文之间的时间间隔大于10毫秒,就没有必须考虑粘包问题,粘包只会出现在TCP网络环境中。”
你这里说的单片机不考虑粘包,指的的串口通信吧? 是不是TCP和单片机有什么关系呢? 单片机也有支持TCP通信的呀。 我想你的意思应该是 串口通信,因为串口占用,所以只有一对一的通信,只要上位机每次只发一帧,所以不用考虑粘包问题。 而TCP 因为可以一对多,所以多个上位机来的数据可能都放到一个缓冲中,导致粘包 是这个意思吗
bandaoyu 2020-03-04
  • 打赏
  • 举报
回复
引用 34 楼 ba_wang_mao 的回复:
感谢!非常好,回答得很详细,认真~~
bandaoyu 2020-03-04
  • 打赏
  • 举报
回复
引用 40 楼 ba_wang_mao 的回复:
原来如此!你太厉害了,非常感谢!! 回答得很认真详细~~~
ba_wang_mao 2020-03-04
  • 打赏
  • 举报
回复
所以说单片机串口通信时,只需要前一帧报文和后一帧报文的间隔时间满足以下条件即可:单片机有充足的时间完成前一帧报文的接收和解析工作就OK啦!
ba_wang_mao 2020-03-04
  • 打赏
  • 举报
回复
1、有的单片机的串口通信也有【硬件发送缓冲区】和【硬件接收缓冲区】,只是缓冲区非常少,最大也就16字节(即最多可以缓存16个字节)。
2、单片机不粘包的原因是:它是串行通信的,硬件收到一个新的字节,就立即触发接收中断,程序员就可以在接收中断中读取,如果程序员不读取它,过一会又来了一个字节,就会把之前的字节覆盖掉。
3、而TCP粘包的原因是TCP协议算法的原因。
你要求程序发送一条报文,该报文长度很小,根据TCP协议,它认为这条报文还不到发送时间,它就把该报文暂存在系统中,当你又发送了一条报文,它认为达到发送条件了,它就把刚刚缓存的报文和现在的报文发送出去,所以就出现了粘包。

单片机串口通信就没有这样的机制,它不管三七二十一,当你往【硬件发送寄存器】装数据时,它就通过内部机制立即发送出去,不会像TCP通信那样由TCP协议来决定这条报文是否立即发送出去。




bandaoyu 2020-03-04
  • 打赏
  • 举报
回复
引用 38 楼 ba_wang_mao 的回复:
所谓粘包,是指某一个连接内传输报文时出现粘包,而不是指某1个连接和其它连接之间的报文粘在一起。
串口通信不需要考虑粘包,而TCP 需要考虑粘包,是因为“TCP有发送缓冲区,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;” 而串口通信是没有发送缓冲区,调用发送接口就直接发出去?
ba_wang_mao 2020-03-04
  • 打赏
  • 举报
回复
所谓粘包,是指某一个连接内传输报文时出现粘包,而不是指某1个连接和其它连接之间的报文粘在一起。
ba_wang_mao 2020-03-04
  • 打赏
  • 举报
回复
1、单片机的串口通信没有必须考虑粘包问题、分包问题。
2、如果你使用以太网转串口芯片通过单片机上的串口和上位机通信,也没有必须考虑粘包问题、分包问题。
3、如果你使用单片机上的以太网直接和上位机通信,必须考虑粘包问题、分包问题。
4、而TCP 因为可以一对多,所以多个上位机来的数据可能都放到一个缓冲中,导致粘包。
你上面的这句话理解有问题。
粘包和分包对于TCP来说是建立在一个TCP连接上的,而不是什么几台电脑这种概念。

(1)、如果1台电脑和你的单片机通过以太网建立了1个连接,
那么电脑通过连接1发送给单片机的报文就需要考虑粘包问题。
(2)、如果1台电脑和你的单片机通过以太网建立了2个连接
那么电脑通过连接1发送给单片机的报文就需要考虑粘包问题。
那么电脑通过连接2发送给单片机的报文就需要考虑粘包问题。
但是不会出现连接1的报文和连接2的报文混在一起这种情况滴!

可以把以太网中的【TCP连接】想像成RS232通信电缆一样:
第一个TCP连接,就相当于通过一根电缆连接到单片机的USART1串口,
第二个TCP连接,就相当于通过另外一根电缆连接到单片机的USART2串口,
第三个TCP连接,就相当于通过另外一根电缆连接到单片机的USART3串口,
以此类推,所以TCP上的报文不可能出现不同的连接的报文混在一起滴。

ba_wang_mao 2020-02-24
  • 打赏
  • 举报
回复


// 定时器中断
void TIM1_UP_IRQHandler(void)
{

if (TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
TIM_Cmd(TIM1, DISABLE);
MB_USART1.Outtime_mark = TRUE; //定时器溢出表示接收完一帧完整的报文,置接收完成标识
}
}



//串口中断方式
void USART1_IRQHandler(void)
{
uint8_t ch;


if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
ch = USART_ReceiveData(USART1);
if (MB_USART1.receCount < MSCOMM_BUFFER_LENGTH)
MB_USART1.mscomm_buffer[MB_USART1.receCount++] = ch;
else
MB_USART1.receCount = 0;

TIM_Cmd(TIM1, DISABLE);
TIM_SetCounter(TIM1, 0x00); // 清定时器的计数
TIM_Cmd(TIM1, ENABLE);
}

if (USART_GetITStatus(USART1, USART_IT_TC) != RESET)
{
USART_ClearFlag(USART1,USART_FLAG_TC);
if (MB_USART1.sendPosi < MB_USART1.sendCount)
USART_SendData(USART1, MB_USART1.send_buffer[MB_USART1.sendPosi++]);
else
{
USART1_RS485_RECIVE_enable();
USART_ITConfig(USART1, USART_IT_TC, DISABLE);
}
}
}





ba_wang_mao 2020-02-24
  • 打赏
  • 举报
回复
单片机上如果上位机每次只发送一帧独立的报文(上位机如果不是将几个报文打包一起发的话),完全没有必须考虑粘包问题、分包问题,也就是说:
(1)、如果上位机发送的2条报文之间的时间间隔大于10毫秒,就没有必须考虑粘包问题,粘包只会出现在TCP网络环境中。
(2)、如果上位机发送的每条报文都是一条独立的报文,则没有必须使用环形缓冲区。
只需要在串口接收中断中启动一个3.5字节接收时间的定时器即可,当定时器溢出,就表示一帧报文接收完毕。
假如定时器溢出时,接收总长度=20
接收缓冲区 【0】 = 报文第1个字节
接收缓冲区 【19】 = 报文最后个字节

打个比方:当上位机发送的报文分别是【报文1】、【报文2】、【报文3】时
上位机发送时,只会发送【报文1】
只会发送【报文2】
只会发送【报文3】
不会出现【报文1】+【报文2】
不会出现【报文1】+【报文3】
不会出现【报文2】+【报文3】
不会出现【报文1】+【报文2】+【报文3】
就没有必要搞个环形缓冲区。

(3)、如果上位机发送的每条报文,有时候是一个单独的报文,有时候是几个报文叠加在一起发,则需要使用环形缓冲区识别出具体的每一个报文。
打个比方:当GPS设备发送报文时,不是每次只单独分别发送GPGGA报文、GPTRA报文、GPRMC报文,
而是有时候发送 GPGGA报文+GPTRA报文
有时候发送GPGGA报文+GMRMC报文
有时候发送GPGGA报文+GPTRA报文+GPRMC报文
这个时候就要用到环形缓冲区解析报文。

总之,单片机中最简单的判断接收完一个报文的方法如下:
第一步:定义一个3.5字节接收时间的定时器
第二步:串口接收中断中只要收到一个字节,就清空定时器的计数
第三步:当定时器溢出,就表示一帧报文接收完毕。

bandaoyu 2020-02-23
  • 打赏
  • 举报
回复
引用 31 楼 丁劲犇 的回复:
我不是版主,不知为什么会显示版主,额,真的很奇怪。我也管理不了别人的帖子。没有人联系过我,估计是bug 对的,就是如果不通过校验,就要逐个字节去挪动。校验可以自己定算法,比如把头部字节按照uint16算数想加,或者异或都可以。
我还是不大明白你的意思,“比如把头部字节按照uint16算数想加,或者异或都可以”你说的”头部字节“是指帧头吗?校验是指校验出帧头? 我的疑问是如何 区分出真的帧头 和 假的帧头。 我的想法是这样的,比如下面这个缓存数据,第一帧的帧头不完整,且帧数据内有和帧头一样的数据。 在检出帧头的操作中,检到8,9,10 字节时,怀疑是”帧头“,然后再从该”帧头“的后面一个字节得到”长度“ send_buffer[12] = 0x89; ,然后再按照这个长度 获取出0x89长度字节的数据, 最后对获取出来的 帧头+长度+数据 进行CRC校验,再与 帧数据里面带的CRC对比,如果错误,说明 send_buffer[9] = '$'; send_buffer[10] = 'G'; send_buffer[11] = 'P'; 不是帧头。 继续从send_buffer[11]往后挪 找下一个帧头,而不是从send_buffer[11]+帧长度+0x89 之后的挪,也就是当前帧头错的话,就是从当前假帧头之后的字节继续往前找帧头。(这样的话,就要再给环形缓冲加一个 指针专门找帧头的指针,而是不用读指针边读边找帧头)我的想法对吗? 你的想法是什么样的? 缓存数据: //send_buffer[0] = '$' --------------------> 帧头="$GP" --->去掉$ 制造一个不完成帧 send_buffer[1] = 'G' send_buffer[2] = 'P' send_buffer[6] = 0 --------------------->帧长度 send_buffer[7] = 14;--------------------->帧长度=0014 send_buffer[8] = 0x11; send_buffer[9] = '$'; send_buffer[10] = 'G'; send_buffer[11] = 'P'; send_buffer[12] = 0x89; send_buffer[13] = 0x90; send_buffer[14] = '$' --------------------> 帧头="$GP" send_buffer[15] = 'G' send_buffer[16] = 'P' send_buffer[17] = 0 --------------------->帧长度 send_buffer[18] = 14;--------------------->帧长度=0014 send_buffer[19] = 0x11; send_buffer[20] = 0x12; send_buffer[21] = 0x13; send_buffer[22] = 0x14; send_buffer[23] = 0x89; send_buffer[24] = 0x90;
丁劲犇 2020-02-23
  • 打赏
  • 举报
回复
仔细看了楼上,已经很清楚了,环形缓存和头校验。只要没有频繁的内存动态分配,性能都差不多。pppoe,ip,asn,都是用于嵌入式的成熟协议,可以看看一些开源代码里是怎么折腾的。
丁劲犇 2020-02-23
  • 打赏
  • 举报
回复
等待的时候,是每个字节推过去。如果图像里有假头,校验不通过,还是逐个字节后推。直到校验通过了,才是直接取长度
丁劲犇 2020-02-23
  • 打赏
  • 举报
回复
第二种,因为有二进制总计校验,所以如果校验不对,就不是贞头,接着等待。二进制总计校验很节约时间。 第三个,适合封非常复杂的指令,可以用各种标志位,决定后面有哪些块。比如是不是有电机数据,温度数据。这个东西用语言描述结构,而后有工具asn4c asn4j直接把asn语言编译成c或者java
丁劲犇 2020-02-23
  • 打赏
  • 举报
回复
引用 30 楼 bandaoyu的回复:
[quote=引用 29 楼 丁劲犇 的回复:]
哇!原来是版主大人!!版主出马 又让我学了不少东西。 请问什么是“二进制总计校验”? 也就是说 二进制总计校验不通过的时候,不丢弃假头后面假“帧长度”的数据,而是从假头继续往后推进去找帧头然后按后面的“帧长度”取数据校验,通过了才把数据从环形缓存里面取出来对吗? [/quote] 我不是版主,不知为什么会显示版主,额,真的很奇怪。我也管理不了别人的帖子。没有人联系过我,估计是bug 对的,就是如果不通过校验,就要逐个字节去挪动。校验可以自己定算法,比如把头部字节按照uint16算数想加,或者异或都可以。
bandaoyu 2020-02-23
  • 打赏
  • 举报
回复
引用 29 楼 丁劲犇 的回复:
哇!原来是版主大人!!版主出马 又让我学了不少东西。 请问什么是“二进制总计校验”? 也就是说 二进制总计校验不通过的时候,不丢弃假头后面假“帧长度”的数据,而是从假头继续往后推进去找帧头然后按后面的“帧长度”取数据校验,通过了才把数据从环形缓存里面取出来对吗?
bandaoyu 2020-02-22
  • 打赏
  • 举报
回复
引用 24 楼 丁劲犇 的回复:
第一种:转译的方法我们知道的,需要发送和接收都需要转译,对性能影响应该不小,估计我同事就是因为这个放弃这个方法 第二种,固定头加长度加校验,比如参考ip。(这个方法,我就是还有一个疑惑不能解决: 这种方法就是 遇到帧头就安装后面的帧长度读出帧数据然后校验,校验错误就丢弃,从后面的数据继续找帧头……丢几帧后就会同步,不过有一种非常特殊的情况会造成 不管多少帧之后都无法同步,这种情况是这样的: 协议是 二进制数据的,你很难知道 帧数据中会出现什么样的数据,比如帧头是"$GP",如果对方每一帧都要传一张小图片,谁也无法保证图片中没有包含"$GP"这样的数据呢,这样的话如果首帧的帧头丢失,就会出现不管丢多少帧都无法验证通过无法同步的现象。 第三种,使用ASN.1数据规范,是3G里面广泛使用的,可灵活啦----这个能大概说说和前两种有什么不同吗
丁劲犇 2020-02-22
  • 打赏
  • 举报
回复
一种是类似pppoe,用字节7e分割,中间数据里7d转义,用7d5d代表7d,用7d5e代表7e。加不加校验自己选择。 第二种,固定头加长度加校验,比如参考ip。 第三种,使用ASN.1数据规范,是3G里面广泛使用的,可灵活啦。
丁劲犇 2020-02-22
  • 打赏
  • 举报
回复
参考pppoe吧。这个解决的很好了。
加载更多回复(16)

27,375

社区成员

发帖
与我相关
我的任务
社区描述
硬件/嵌入开发 单片机/工控
社区管理员
  • 单片机/工控社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧