订阅收费文章或资源:公众号“毛线杂货铺”,淘bao店铺ID:122344852,可搜索商铺“幸福的孩子yyx”或复制以下内容:
59《ZuboWnWzq1R《 https://m.tb.cn/h.5D3Od4wRAQzgQpH MF6563 我分享给你了一个超赞的内容,快来看看吧
或:
最近在软件调试中,发现数据一长就经常丢失数据,所以耐下心了解了一些原理,用较为简单的理解方式记录下来。
题外话:
一般来说,半双工通信需要添加一个超时时间,当超过这个时间后,就判定接收失败了,之前的缓存数据也要清空。
个人选择定时器的方式,至于如何计算超时,公式如下:
数据所需时常=总数据长度10/波特率。
对于我的数据,最长为1237字节,波特率选择9600,则需要耗时123710/9600=1.28854…,基本为1.3秒,所以我设置了串口的超时时间为3秒。发送后如果3秒没接收到数据,就算超时,直接发下一条指令。
串口数据丢失:
数据丢失中间部分数据或者尾部数据。
问题原因:
未使用好串口的丢弃缓存函数
serialPort?.DiscardInBuffer();//丢弃接收缓冲区数据
个人习惯在下面的代码后面添加丢弃缓存函数,导致数据丢失
byte[] buffer = new byte[serialPort.BytesToRead];
serialPort.Read(buffer, 0, buffer.Length);
原因是我读取serialPort.BytesToRead
长度时,用buffer取缓存的时候,长度已经增加了,所以我丢弃缓存会将还没获取的缓存一并清除。
这里大家用read()方法的时候,其实读出来的数据在缓存中已经被清除了,不需要处理。
串口数据一包分很多次触发串口事件
为了串口一次得到完整包,一般我们在进入串口事件后会选择延时(根据完整包所需时长),再从缓存中获取数据。
但是一旦数据包字节很长的时候,这样的方式就不是很好,因为延时造成界面卡或者漏包,就选择将延时去掉。
使用9600的波特率接大于32字节的数据时,如果在串口事件中不延时,会发现每次触发触发事件可以接收到32个字节,所以需要拼接字节,得到最后的完整包。
个人代码如下:
private void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
if (!serialPort.IsOpen)//串口在关闭时不接收数据 为了防止关闭串口时卡死的问题
{
serialPort?.DiscardInBuffer();//丢弃接收缓冲区数据
return;
}
if (serialPort.BytesToRead < 4 && LineBuffer == null)
{
return;
}
byte[] buffer = new byte[serialPort.BytesToRead];
serialPort.Read(buffer, 0, buffer.Length);
InquireOutTimeCount = 0;
if (buffer == null || buffer.Length <= 0)
return;
Show(buffer, "串口接收");
if (buffer[0] == CommunicationHelper.CmdBagHead1 && buffer[1] == CommunicationHelper.CmdBagHead2)//判断是否是包头
{
LineBuffer = buffer;
}
else if (LineBuffer != null)//拼接
{
LineBuffer = CommunicationHelper.AddBytes(LineBuffer, buffer);
}
//之后去判断包是否完整,完整解析后清空自己的缓存
.........
}
有关串口事件说明
串口内部有一个缓存,串口数据都会保留在这里,可使用read()去读取。
经过调试,发现串口事件应该是一个时间触发的机制,例如9600的波特率每次触发串口事件最多能传递32个字节。
串口内部的缓存是类似队列的先进先出,进入串口事件后,读到的缓存长度serialPort.BytesToRead
只是读的时候缓存的长度,可能你下一行代码去取缓存的时候,缓存就增加了,你只能取到之前的缓存数量,若没有选择清空缓存,完成本次串口事件后,剩余的缓存会再次触发串口事件让你继续读取缓存。