先上代码
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{//串口的波特率为9600
List<byte> received=new List<byte>() ;
int loopCount=0;//检测共分几次读取串口
while (serialPort1.BytesToRead > 0)
{
loopCount++;
int len = serialPort1.BytesToRead;
byte[] temp = new byte[len];
serialPort1.Read(temp, 0, len);
received.AddRange(temp);
Thread.Sleep(10);//延迟10ms,等待串口的数据缓冲好
}
//其余处理代码
}
问题是:经过调试发现,同样的波特率,但是却发现接收的不稳定,
第一组数据从下位机发送了21个字节过来,有时是1次就读取了(通过查看loopCount的值发现), 有时是2次读取串口。当1次性读取串口时,第一次serialPort1.BytesToRead=21;分为两次读取时,第一次serialPort1.BytesToRead=16,第二次循环时,serialPort1.BytesToRead=5,很奇怪这是为什么呢还有种情况是:第一次读取了16个字节,在Thread.Sleep(10)后,serialPort1.BytesToRead=0,于是数据就接收不完整了,此时就会跳出while (serialPort1.BytesToRead > 0)循环,我很奇怪呀,这10ms的时间里,用示波器可以看到下位机是有数据传送过来的,为什么这些数据没有进入serialPort1的缓冲区呢?
一样的代码,一样的波特率,为什么有时候进入触发事件时,缓冲区读到的字节数是16,有时候会超过16?为什么有时候Thread.Sleep(10);剩下的数据会存入缓冲区,有时候却不会?不知道描述清楚了没,求达人解答呀
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{//串口的波特率为9600
List<byte> received=new List<byte>() ;
int loopCount=0;//检测共分几次读取串口
while (serialPort1.BytesToRead > 0)
{
loopCount++;
int len = serialPort1.BytesToRead;
byte[] temp = new byte[len];
serialPort1.Read(temp, 0, len);
received.AddRange(temp);
Thread.Sleep(10);//延迟10ms,等待串口的数据缓冲好
}
//其余处理代码
}
问题是:经过调试发现,同样的波特率,但是却发现接收的不稳定,
第一组数据从下位机发送了21个字节过来,有时是1次就读取了(通过查看loopCount的值发现), 有时是2次读取串口。当1次性读取串口时,第一次serialPort1.BytesToRead=21;分为两次读取时,第一次serialPort1.BytesToRead=16,第二次循环时,serialPort1.BytesToRead=5,很奇怪这是为什么呢还有种情况是:第一次读取了16个字节,在Thread.Sleep(10)后,serialPort1.BytesToRead=0,于是数据就接收不完整了,此时就会跳出while (serialPort1.BytesToRead > 0)循环,我很奇怪呀,这10ms的时间里,用示波器可以看到下位机是有数据传送过来的,为什么这些数据没有进入serialPort1的缓冲区呢?
一样的代码,一样的波特率,为什么有时候进入触发事件时,缓冲区读到的字节数是16,有时候会超过16?为什么有时候Thread.Sleep(10);剩下的数据会存入缓冲区,有时候却不会?不知道描述清楚了没,求达人解答呀
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{//串口的波特率为9600
int length=serialPort1.BytesToRead;
//other codes
}
即使是这样,进行多次调试,每次从下位机传送过来的一帧数据都是21个字节,并且保证波特率一样,通过示波器可以证明下位机传输的数据不存在问题,但是读取的length值是不一样的,有时是16,有时是21,为什么啊,想不通想不通。
List<byte> received=new List<byte>() ;
int loopCount=0;//检测共分几次读取串口
while (serialPort1.BytesToRead > 0 || received.Length < 16)
{
}
一开始我也怀疑是下位机发送数据不连续,但是从示波器读取的总线电平来看,下位机发送的数据是很连续的。
首先,你做了一个判断,当有数据的时候你就会去接收 while (serialPort1.BytesToRead > 0)
我们知道串口是有延时性的,当第一次21个字节的时候,你碰巧是一次性读完了,那么这个时候一次成功,但是有时候缓存里只有16个字节,你已经开始读了,此时你读的只有16个字节,于是进入到你的循环,,延时了10MS,导致后5个字字节也读进来了,建议,延时放在循环前,让串口有足够的时间去接收,。。这样就可以保证一次读完了个人见解
延时了10MS,导致后5个字字节也读进来了-------------正如你所言,我也是这个意思,延时就是为了让数据进入缓冲区,然后会继续判断 while (serialPort1.BytesToRead > 0),但是我发现的问题是,串口一直有数据过来,但是延时了10ms后,这些数据并没有进入缓冲区,导致serialPort1.BytesToRead=0,于是循环就进行不下去了
看来我要延迟的时间长一点呀,确认数据都已经进入缓冲区。
在写代码的时候,想的都是程序在一直不断的运行,却忽略了系统的机制。
你这个代码,首先一点,你既然用while来检测数据并接收,那就别用DataReceived这个事件。
直接把while放线程了不就得了。不然的话,你在while循环,而那边却又因为新的数据接收导致触发了DataReceived事件,可能又在进入另一个while。
this.DataReceived+=new SerialDataReceivedEventHandler(RSSerialPort_DataReceived);
你说的这个确实是我疏忽了,可能在while的时候,又会再次触发DataReceived事件,不过这个我也不懂啦,不知道DataReceived事件没处理完的话会不会再次触发进入。
之所以用DataReceived事件是因为采取的一问一答的方式,确认接收到的数据没问题后,再发送下一个命令,不用触发事件,不晓得该怎么处理接收到的数据,也许这个是我太固执了,喜好用DataReceivd事件。不过你要是有好的想法,我非常愿意倾听,一个人的想法难免陷入死胡同。简略描述下我要做的事情:按下一个button后,发送第一个命令,AA 01 00 01 55,然后单片机返回数据是AA 02 00 (16个字节的数据)(校验和)55,PC端确认校验和正确之后发送下一个命令 AA 01 01 02 55,单片机继续返回数据为AA 02 01 (1个字节的数据)(校验和)55,PC再次确认校验和,正确就发送下一个指令,错误就重发指令。如此类推,一共大约10个命令,基本要求就是检验单片机发送过来的数据是否符合格式,检验和是否正确,正确了就发送下一个指令,错误就重发指令。从头至尾只按过一次button,后面的都要自发完成,不要再手动。可能我思维定式了,想来想去就觉得DataReceived事件好处理点,不知你有什么好的思路不
如
协议号
长度
内容
这三个
要是不明白百度一下,Socket粘包原理,跟这个一样的。这个事件要这么写 private ByteQueue queue=new ByteQueue()<-这里要自己写个环型Buffer的缓冲区
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{//串口的波特率为9600
int len = serialPort1.BytesToRead;
if(len>0)
{
byte[] temp = new byte[len];
serialPort1.Read(temp, 0, len); //下面主要是要处理好贴包
queue.EnQueue(tmp,0.len) <- 装入缓冲区
while(queue.Length>0)
{
这要里处理收到的包是否满足条件。
包号
包长
if(包长<queue.Length)
{
读包
}else
{
break;
}
}
}
}
谢谢,额,不懂TCP
我的想法是加长延时,不过这不是我最关心的,毕竟解决办法是有的。
我比较奇怪的是,为什么在进行多次试验时,明明串口总线上的数据是一样的,但是读取缓冲区的时候却存在差别,有时读到的是16字节,有时是21字节,甚至有时是8字节,从发帖到现在,对于这个问题,我看到合理点的就是1楼的说法了,即系统对进程的调度,但真正的原因是什么我却不得而知。希望有知道的大神来解答下,或者是说说自己的看法。
另外,不知道你说的buffer是指什么,由于我要从下位机接收到多组数据,接受一组解析一组,然后发送下一个指令。如果采用查询的方式,不知道该在何时处理接收到的数据了
谢谢你的回复;协议我是肯定定好的,不然不知道收到的数据是否符合要求,以及数据的含义。
不懂为什么要while(queue.Length>0),这个循环是干嘛用的,不会死循环吗?
上面那个 读包 就是把数据读出来后,queue.Count自然就item就少了。
还有最好你把那个协议说明弄上来吧,我可以帮你看看,前一段时间写过COM口喷码机的协议做法就是我自己所说的那样也是用serialPort1_DataReceived这个事件来接收,
PC发送 AA 01 00 01 55
单片机回答:AA 02 00 16个字节的数据 和校验 55PC发送 AA 01 01 02 55
单片机回答:AA 02 01 10个字节的数据 和校验 55PC发送AA 01 02 03 55
单片机回答:AA 02 02 12个字节的数据 和校验 55
......一共10个命令,按下一个button后,完成所有这些通讯,PC接收到数据后,先判断和校验是否正确,正确的话就存储下数据,并发送下一个命令。至于数据长度,每个序号对应不同的长度,只要知道了AA 02后面的那个字节是什么就可以知道数据长度。
你有什么好想法参考下不
其实就是先找到AA,然后再找55,如果和校验是正确就认为数据有效,那么就发送下一个指令。如果接收到的数据不符合要求就无效,重复指令
1、在使用断点调试时和不用断点调试时,你看到的结果是不一样的。
如果在断点的情况下,21个字节,可能第一次只能接收到5个byte,第二次可以收到16个字节。。
如果是非断点的情况下,多半这21个字节可以一次性收完,因为数据量很小。所以,千万不要让断点调试误导你。
2、这里没有必要使用thread.sleep,我认为只有在打开串后,和马上向串口写入的情况下,需要短暂sleep下。见msdn
3、既然是固定长度的报文,可以在事件中判断serialPort1.BytesToRead的值,如果小于21,则直接return.
否则的话,你就声明一个byte[21]的数组,而且你仅从缓冲区中读取21个填充进来。
未读取下的数据在下次事件触发时,再进行读取(再读取21个)。
这里也存在这样的一个问题,如果下拉机发的非常的快,而你每次只读取21个的话,就有可能超出缓冲区的大小(导致很多数据丢失,缓冲区溢出),因为缓冲区中存在很多数据,而你每次只读走了21个。。
这个问题就可以通过声明一个字段级的List<byte>来解决。
进行多次调试的时候,我没有用断点,是通过查看loopCount知道共读取了几次串口的。有时是1次,有时是2次,所以才感觉不稳定,因此我真正想问的是,为什么会出现这种现象呢。
另外,接收的数据不是固定长度的,只是第一组数据是21个字节,后面的是十几个字节的,也有几个字节的。
用sleep是因为我希望在一次DataReceived事件里就把数据读取完毕,而不是进入多次DataReceived事件。
最后,缓冲区的是不会溢出的,因为PC端发送一个指令时,下位机只回答一次。第一次的时候,下位机只发了21个字节。谢谢你的回答,但是那个,还是希望能看清我的问题一定是我没注意分段,没用红字标明,才会模糊问题的焦点
就是搞不懂为什么同样的情况,为什么有时候是这样,有时候是那样,能说下引起这种现象的原因吗。为什么有时候触发DataReceived事件的时候,串口已经有21个字节进入缓冲区(当然可能会更多),有时却只有16个字节,甚至有时是8个字节?说下你的见解嘛,我比较笨,懂的又不多,想不出有什么原因可能导致这么个结果。
serialPort = new System.IO.Ports.SerialPort();
serialPort.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(serialPort_DataReceived);
..........
serialPort.Open();
void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.AboveNormal;//调高优先级,保证线程不会被其它线程阻塞。
if (e.EventType == System.IO.Ports.SerialData.Chars)
{
while (serialPort.BytesToRead > 0)
{
BuffEventArgs arg = new BuffEventArgs();
arg.Connection = this;
arg.Data = new byte[512];
arg.Length = serialPort.Read(arg.Data, 0, arg.Data.Length);
CallOnReceiveDataInvoke(this, arg); //使用异步方式引发接收事件
}
}
}
你的程序里有的我都没见过,还要先去查下MSDN资料,伤不起。
另外你在接受数据的事件处理中没必要加延时10ms,这样反而会出问题(我猜的哈)。每次收到的数据个数不一样应该就是系统底层调度的问题,每次你的这个线程获取到线程锁的时间长度是不一样的,所以就会出现这样的问题。
我最近也在用串口通讯,自己对SerialPort又做了一个封装:支持协议加头、校验、尾、以及数据完整性的校验(做法就是每收到一些字节就放到自己建立的缓冲区里,去找指令头,找到之后就去读取指令头后面两个字节定义的长度个字节,如果找到了协议尾,那就把这条完整的指令作为EventArgs发给订阅者),这样使用起来很方便,上层业务不需要了解具体的实现,能保证数据的完整性和正确性。
帮你实现了简单的字节缓冲器。 /// <summary>
/// 字节缓冲器
/// </summary>
public class ByteQueue
{
private List<byte> m_buffer = new List<byte>();
public bool Find()
{
if (m_buffer.Count == 0)
return false;
int HeadIndex = m_buffer.FindIndex(o => o == 0xAA); if (HeadIndex == -1)
{
m_buffer.Clear();
return false; //没找到AA
} else if (HeadIndex != 0) //不为开头移掉之前的字节
{
if (HeadIndex > 1)
m_buffer.RemoveRange(0, HeadIndex);
} int length= GetLength(); if (m_buffer.Count <length)
{
return false;
} int TailIndex = m_buffer.FindIndex(o => o == 0x55); //查找55的位置 if (TailIndex == -1)
{
//这一步为防止连发一个AA开头的包后,没发55,而又发了一个AA
int head = m_buffer.FindLastIndex(o => o == 0xAA);
if (head > -1)
{
m_buffer.RemoveRange(0, head);
}
return false;
}
else if (TailIndex + 1 != length) //计算包尾是否与包长度相等
{
m_buffer.RemoveRange(0, TailIndex);
return false;
} return true;
} /// <summary>
/// 命令类型
/// </summary>
/// <returns></returns>
public byte Cmd()
{
if (m_buffer.Count >= 2)
{
return m_buffer[1];
}
return 0;
} /// <summary>
/// 序号
/// </summary>
/// <returns></returns>
public byte Number()
{
if (m_buffer.Count >= 3)
{
return m_buffer[2];
}
return 0;
} /// <summary>
/// 包长度
/// </summary>
/// <returns></returns>
public int GetLength()
{
int len = 5;//AA 命令类型 序号 校验和 55
if (m_buffer.Count >= 3)
{
switch (m_buffer[2]) //第三字节为序号
{
case 0x00: //序号
return len + 16;
case 0x01: //序号
return len + 10;
case 0x02: //序号
return len + 12;
}
}
return 0;
}
/// <summary>
/// 提取数据
/// </summary>
public void Dequeue(byte[] buffer, int offset,int size)
{
m_buffer.CopyTo(0,buffer,offset,size);
m_buffer.RemoveRange(0, size);
} /// <summary>
/// 队列数据
/// </summary>
/// <param name="buffer"></param>
public void Enqueue(byte[] buffer)
{
m_buffer.AddRange(buffer);
}
}这是调用例子 private ByteQueue queue = new ByteQueue();
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int len = serialPort1.BytesToRead;
if (len > 0)
{
byte[] temp = new byte[len];
serialPort1.Read(temp, 0, len);
queue.Enqueue(temp);
while (queue.Find()) //while可处理同时接收到多个AA ... 55 ,AA...55的包
{
int length = queue.GetLength();
byte[] readBuffer = new byte[len];
queue.Dequeue(readBuffer, 0, length);
OnReceiveData(readBuffer); //<这里自己写一个委托吧就OK了
} } }
执行int len = serialPort1.BytesToRead;
len怎么着也是大于0的,这个判断有点无意义。
好想法,之前我都不晓得List是个啥东西,现在才发现很好用啊,可以任意的Add和Remove。
你思路和想法很好,但是0x55也有可能在数据部分出现,不一定是结束字节,我要是想拿过来用的话还要再改改。我的想法是:
1、找到AA,AA之前的字节移除
2、根据AA之后的两个字节判断数据长度,与queue的长度比较,queue的长度不够就继续接收
3、判断结束字节是不是55,比如开头为AA 00 00,那就判断AA之后的第20个字节是不是55,如果是就判断校验和是否正确,不是55就寻找下一个AA但是如果在数据的传输中,有字节被遗漏或者受干扰出错了,那样的话就会一直在等待正确的数据了,此时串口已经没有数据发送过来了,但程序还是一直在等串口发送正确的数据过来。不知道对于这种情况你是怎么处理的?
请问下这句的详解:CallOnReceiveDataInvoke(this, arg); //使用异步方式引发接收事件
怎么用异步方式引发?
额,可能我就是那种喜欢打破砂锅问到底的吧,一定要弄明白谢谢你的建议
如果内容中有55 那就把Find()中的改一下就行了
int length= GetLength(); if (m_buffer.Count <length)
{
return false;
} if(m_Buffer[length]!=0x55)
{
m_buffer.RemoveRange(0, length);
return false;
}
这样就可以了。接下来自己要怎么判断还是怎么判断你所说的
但是如果在数据的传输中,有字节被遗漏或者受干扰出错了,那样的话就会一直在等待正确的数据了,此时串口已经没有数据发送过来了,但程序还是一直在等串口发送正确的数据过来。不知道对于这种情况你是怎么处理的? 字节出错问题 如果数据没有接到收完整的包,他是要一直等待的,这是正常的。你要是防止等待过长,那就加个超时功能,在向单片机发送数据后,如果超过你指定的时间内,没收到回应。那先将缓冲区的数据清空,重新发一个指令给单片机就是了。
你光打字人家看不懂呀,直接给Code来的快些
因为你说的我都懂,对我遇到的问题木有帮助哎
不过很感谢你的回答
sorry啦,就只有100分,就只给了对我的问题有帮助的回复
我也说过了,我的基类是实战所用,百分百可靠通讯。至于你用不用,看不看,则随便了。我当初写出来也是让想用的人多一种思路。
用很多有代表性的数据进行了多次测试,猜测如下:串口硬件在收到8个字节的数据时,会将这些数据存到SerialPort的输入缓存区,不足8个字节时,会等待足够8个字节,但在一定时间内还不足8个字节,还是会将这些数据存至SerialPort的输入缓存区,这个时间为10ms~15ms。
总结起来就是:串口每接收到8个字节便会送到SerialPort的输入缓存区,不足8个字节,每10ms~15ms将接受到的数据存至SerialPort的输入缓存区。(这也就不难理解,为什么在用触发事件读取串口时,当数据多于8个字节时,会多次进入接收事件,每8个字节触发一次)以上是根据自己进行多次试验得出的结论,不敢保证百分百正确。关于串口硬件和软件衔接方面的资料太少了,没找到相关的,只能自己用数据试验。