文档库 最新最全的文档下载
当前位置:文档库 › C#串口侦听的实现

C#串口侦听的实现

相信很多和我一样需要编程操纵硬件的人都遇到过这样的问题,就是通过串口来接收硬件发来的数据,或是通过串口向硬件发送某种格式的命令。在C#平台上,编写串口通讯程序是一件非常简单的事情。废话少说,入正题~

网上众多C#串口编程的例子,主要讨论的都是如何侦听串口数据(向串口写数据较为简单,略),仿照例子,我编写了如下代码来侦听数据:


using System.IO.Ports;
namespace SerialTest
{
public class SerialTest
{
//串口对象
private SerialPort _Comm = null;

//串口控件初始化
public void Init()
{
_Comm = new SerialPort();
//串口参数设定
_Comm.BaudRate = 9600;
_Comm.Parity = Parity.None; //Parity为C#预定义枚举型变量
_Comm.DataBits = 8;
_Comm.StopBits = StopBits.One; //StopBits为C#预定义的枚举量
_Comm.PortName = "COM1";//假定串口名为COM1

//为控件添加事件处理函数
_Comm.DataReceived +=
new SerialDataReceivedEventHandler(CommDataReceived);
//串口控件成员变量,字面意思为接收字节阀值,
//串口对象在收到这样长度的数据之后会触发事件处理函数
//一般都设为1
_Comm.ReceivedBytesThreshold = 1;
try
{
_Comm.Open(); //打开串口
}
catch (Exception e)
{
Console.WriteLine("串口打开失败");
}
}

//串口数据处理函数
public void CommDataReceived
(Object sender, SerialDataReceivedEventArgs e)
{
//Comm.BytesToRead中为要读入的字节长度
int len = _Comm.BytesToRead;
Byte[] readBuffer = new Byte[len];
_Comm.Read(readBuffer, 0, len); //将数据读入缓存
//处理readBuffer中的数据
....
}
//千万别忘了用完之后关闭串口
public void Stop()
{
_Comm.Close();
}
}
}
为了简单起见,上面的代码里只给出了设置串口对象的主要代码,主要工作就是如何设定串口对象的的参数,这些参数大都是在进行串口编程之前根据硬件设备的通讯协议就预设好的,不用做太多解释。需要解释的就是两个参数:
https://www.wendangku.net/doc/cd9923332.html,m.ReceivedBytesThreshold: 串口接收字节阀值,串口每次在收到这样长度的字节之后就会调用事件处理函数处理串口数据。
https://www.wendangku.net/doc/cd9923332.html,m.DataReceived 利用了C#的托管类和事件机制,收到数据后会触发其中的事件处理函数。
在事件处理函数中,我们首先要通过Comm.BytesToRead来确定读入了多少字节,然后申请同样大小的字节缓冲,最后调用控件的Read函数将数据读入缓存。如何处理缓存中的数据就因人而已啦~

让串口对象运行起来很简单,但是在实际运行中,却遇到这样一个问题,在串口收到FC-FD-

01-13这样四字节长的数据时,往往接收不完全,在事件处理函数中收到的有时是FC 一个字节,或者FC–FD-01这样的三字节,或者是完整的四字节。为啥??

也许很多人都遇到过类似的问题,其实原因就在于_Comm.ReceivedBytesThreshold这个参数的设置上,因为设置了阀值为1,所以每收到一个字节都会触发事件处理函数。那好 我们将这个值设为4,也就是我希望的字节长度,问题解决了~

但是等等!硬件设备通过串口发送的数据报文并不一定都是定长,也可以是变长的,在其开始和结束时通过设定预定义的标志位来表示一段报文的开始结束。就比如说在我的应用中,数据可能是形如FC-FD-01-13这样的四字节报文,也可能是FB-01-13这样的三字节(假定我们用13来标定报文结束),那该怎么办??

再次上网找找看,有朋友推荐这样一个做法,就是仍将阀值设为1,在读到第一个字节之后,马上将阀值设为10000(或是任何一个大数),以停止Comm的事件触发,紧接着调用Comm的ReadByte函数,一个一个字节来读取,直到读到想要的字节(如‘13’)或是读到null为之,再将阀值改回到1,即重新设定触发串口事件处理函数条件使其可以继续工作。

这样做好么??我没有做实验验证,但心里总觉得不妥,主要的担忧在于ReceivedBytesThreshold一直在被Comm控件访问,而我们在事件处理函数中会改变这一参数,是否会使Comm的工作不再线程安全??比方说这样一种情况:

假定硬件以非常快的速度发来两个单独的字节,00和01,在收到00时Comm会触发事件处理函数,在事件处理函数改变阀值之前Comm又收到了01,之后才被改变,这时Comm该如何处理新读入的01?而对00的处理是否又如我们希望的那样?

这里我采用了用线程监控的办法来解决问题,虽复杂一些,但满足我的需要,放在这里和大家一起讨论下:

///
/// 专门用于计算并收取串口数据的读入器
///
internal class CommReader
{
//单个缓冲区最大长度
private const int MAX = 4;
//数据计数器
private int count = 0;
//数字缓冲区
private Byte[] buffer = new Byte[max];

///
/// 串口控件
///
private SerialPort _Comm;
///
/// 扫描的时间间隔 单位毫秒
///
private Int32 _interval;

//数据处理函数
public delegate void HandleCommData(Byte [] data);
//事件侦听
private event HandleCommData Handlers;

//负责读写Comm的线程
private Thread _workerThread;
internal CommReader(SerialPort comm,Int32 interval)
{
_Comm = comm;
//创建读取线程
_workerThread = new Thread(new ThreadStart(ReadComm));
//确保扫

描时间间隔不要太小,造成线程长期占用cpu
if (interval < 10)
_interval = 10;
else
_interval = interval;
}
//读取串口数据,为线程执行函数
public void ReadComm()
{
while (true)
{
Object obj = null;
try
{
//每隔一定时间,从串口读入一字节
//如未读到,obj为null
obj = _Comm.ReadByte();
}
catch (Exception e)
{
}

if (obj == null)
{ //未读到数据,线程休眠
Thread.Sleep(_interval);
continue;
}
//将读到的一字节数据存入缓存,这里需要做一转换
buffer[count++] = Convert.ToByte(obj);
//当达到指定长度时,这里的判断条件可以根据要求变为:
// 判断当前读到的字节是否为结束位,等等
if (count == max)
{
//复制数据,并清空缓存,计数器也置零
Byte[] data = new Byte[max];
Array.Copy(buffer, data, max);
count = 0;
Array.Clear(buffer, 0, max);
//通知处理器处理数据
if(Handlers != null)
Handlers(data);
}
}
}

//启动读入器
public void Start()
{
//启动读取线程
if(_workerThread.IsAlive)
return;
_Comm.Open()
_workerThread.Start();
while (!_workerThread.IsAlive) ;
}
//停止读入
public void Stop()
{
//停止读取线程
if (_workerThread.IsAlive)
{
_workerThread.Abort();
_workerThread.Join();
}
_Comm.Close();
}
}


这个读入器的工作原理很简单,就是启动一个线程,周期性的扫描串口读入一个字节,如读到,则根据每次读入的字节判定是否读取到需要的数据来结束一段报文,收到完整报文后将调用事件处理器来处理数据

使用读入器的代码如下:

public static void Main(String[] args)
{
//串口对象初始化
SerialPort comm = new SerialPort();
//参数设置,略
...
CommReader reader = new CommReader(comm, 100);
reader.Handlers += new HandleCommData(HandleCommData);

reader.Start();
///在程序结束时,结束读入
reader.Stop();
}

public void HandleCommData(Byte[] data)
{
//处理
...
}
经过测试,CommReader达到了我想要的效果,如果大家需要,可以改变判断条件,或引入新的类来设定判定报文结束的条件。

相关文档