C# SerialPort 串口读写

本文作者分享了在经历多次尝试后,如何基于C#编写一个可靠的串口读写类库。文章详细介绍了实现过程,包括定义串口操作类,实现DataReceived事件处理数据接收,通过后台线程监控数据接收完成,并定义多个事件通知数据状态。作者还提供了源码示例,供读者下载参考。

之前一直从博客园看别人的技术文章,下载别人的资源,今天终于忍不住自己写下这篇博客。为了实现稳定串口读写最近也折腾了很久,但是在网上找了很久都没有找到比较好的代码。最后去了一些外国的网站学习了别人的思想后,通过自己的理解终于写了一个测试程序,本程序经过测试发送接收大量数据均无bug出现。


首先介绍一下实现流程。

1.首先我们需要定义一个专门的串口操作类。

2.类中实现串口事件DataReceived接收数据。

3.需要后台开启一个监控线程来监控是否数据接收完成,因为串口事件是触发式,在双方没有固定协议的情况下不知道什么时候才标志接收完整的数据包。在这里思考了很久,目前本人只想到了通过时间来判定,似乎没有其他更好方法,如果在指定的时间内没有收到数据我们则认为数据接收终止。

4.定义3个事件,用来通知数据接收完成、数据发送完成、每次接收数据。

好了不多说了下面先贴上该类的源码吧


串口主要处理类

  /// <summary>
    /// 委托处理结果
    /// </summary>
    /// <param name="data"></param>
    public delegate void HandResult(object sender,SerialPortEvents e);

    /// <summary>
    /// 委托处理接收数据完成
    /// </summary>
    /// <param name="data"></param>
    public delegate void OnReceivedData(object sender,SerialPortEvents e);

    /// <summary>
    /// 发送数据回调
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void OnSendData(object sender,SerialPortEvents e);

    public class SerialPortListener
    {
        #region 成员变量
        /// <summary>
        /// 定义接收缓冲区数据
        /// </summary>
        private List<byte> m_buffer = null;

        public event HandResult SerialPortResult;

        public event OnReceivedData OnSerialPortReceived;

        public event OnSendData OnSeriaPortSend;

        delegate void AsyncCallResult(byte[] data);

        delegate void AsyncSend(byte[] data);

        bool m_isReceiving = false;

        /// <summary>
        /// 串口对象
        /// </summary>
        SerialPort m_serialPort = null;

        Thread m_threadMonitor = null;

        /// <summary>
        /// 结束符号
        /// </summary>
        char m_endChar = '\0';
        public char EndChar
        {
            get { return m_endChar; }
            set { m_endChar = value; }
        }

        /// <summary>
        /// 发送缓冲区大小
        /// </summary>
        int m_writeBufferSize = 1024;
        public int WriteBufferSize
        {
            get { return m_writeBufferSize; }
            set { m_writeBufferSize = value;}
        }

        /// <summary>
        /// 是否启用结束符
        /// </summary>
        bool m_useEndChar = true;
        public bool UseEndChar
        {
            get { return m_useEndChar; }
            set { m_useEndChar = value; }
        }


        /// <summary>
        /// 发送间隔时间
        /// </summary>
        int m_sendInterval = 100;

        public int SendInterval
        {
            get { return m_sendInterval; }
            set { m_sendInterval = value; }
        }

        /// <summary>
        /// 连续接收超时时间
        /// </summary>
        int m_receiveTimeout =1000;

        public int ReceiveTimeout
        {
            get { return m_receiveTimeout; }
            set { m_receiveTimeout = value; }
        }

        /// <summary>
        /// 最后接收时间
        /// </summary>
        DateTime m_lastReceiveTime;

        public DateTime LastReceiveTime
        {
            get { return m_lastReceiveTime; }
        }

        /// <summary>
        /// 缓冲区接收大小
        /// </summary>
        private int m_bufferSize = 1;

        public int BufferSize
        {
            get { return m_bufferSize; }
            set { m_bufferSize = value; }
        }

        /// <summary>
        /// 串口名称
        /// </summary>
        private string m_portName = "COM1";

        public string PortName
        {
            get { return m_portName; }
            set { m_portName = value; }
        }

        /// <summary>
        /// 波特率
        /// </summary>
        private int m_baudRate = 9600;

        public int BaudRate
        {
            get { return m_baudRate; }
            set { m_baudRate = value; }
        }

        /// <summary>
        /// 停止位长度
        /// </summary>
        private StopBits m_stopBits = StopBits.None;

        public StopBits StopBits
        {
            get { return m_stopBits; }
            set { m_stopBits = value; }
        }

        /// <summary>
        /// 基偶校验位
        /// </summary>
        private Parity m_parity = Parity.None;

        public Parity Parity
        {
            get { return m_parity; }
            set { m_parity = value; }
        }

        /// <summary>
        /// 控制协议
        /// </summary>
        private Handshake m_handshake = Handshake.None;

        public Handshake Handshake
        {
            get { return m_handshake; }
            set { m_handshake = value; }
        }

        /// <summary>
        /// 标准数据长度
        /// </summary>
        private int m_dataBits = 8;

        public int DataBits
        {
            get { return m_dataBits; }
            set { m_dataBits = value; }
        }

        /// <summary>
        /// 缓冲区大小
        /// </summary>
        private int m_readBufferSize = 4;

        public int ReadBufferSize
        {
            get { return m_readBufferSize; }
            set { m_readBufferSize = value; }
        }

        /// <summary>
        /// 输入缓冲区中的字节数
        /// </summary>
        private int m_receivedBytesThreshold = 1;

        public int ReceivedBytesThreshold
        {
            get { return m_receivedBytesThreshold; }
            set { m_receivedBytesThreshold = value;}
        }

        /// <summary>
        /// 获取当前是否打开
        /// </summary>
        public bool IsOpen {
            get {
                return m_serialPort == null ? false : m_serialPort.IsOpen;
            }
        }
        #endregion

        #region 构造方法
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="portName"></param>
        /// <param name="baudRate"></param>
        public SerialPortListener(string portName, int baudRate)
        {
            m_portName = portName;
            m_baudRate = baudRate;
            m_buffer = new List<byte>();
            m_serialPort = new SerialPort(portName, baudRate);
            m_serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);
        }
        #endregion

        #region 开启关闭串口
        /// <summary>
        /// 开始接收
        /// </summary>
        public bool Start()
        {
            try
            {
                m_serialPort.Parity = m_parity;
                m_serialPort.StopBits = m_stopBits;
                m_serialPort.Handshake = m_handshake;
                m_serialPort.DataBits = m_dataBits;
                m_serialPort.ReadBufferSize = m_readBufferSize;
                m_serialPort.ReceivedBytesThreshold = m_receivedBytesThreshold;
                m_serialPort.WriteBufferSize = m_writeBufferSize;
                m_serialPort.Open();
                m_threadMonitor = new Thread(new ThreadStart(SerialPortMonitor));
                m_threadMonitor.IsBackground = true;
                m_threadMonitor.Start();
                return true;
            }
            catch
            {
                throw new SerialPortException(string.Format("无法打开串口:{0}",m_serialPort.PortName));
            }
        }

        /// <summary>
        /// 停止
        /// </summary>
        public void Stop()
        {
            m_threadMonitor.Abort();
            m_serialPort.Close();
            m_buffer.Clear();
            GC.Collect();
        }
        #endregion

        #region 接收数据
        /// <summary>
        /// 接收数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SerialPort_DataReceived(object sender, EventArgs e)
        {
            StartReceive();
            int availCount = m_serialPort.BytesToRead;
            byte[] bs = new byte[availCount];
            bool bEnd = false;
            for (int i = 0; i < availCount; i++)
            {
                //单个字节读取
                byte b = (byte)m_serialPort.ReadByte();
                if (m_useEndChar && b == m_endChar)
                {
                    //结束接收
                    bEnd = true;
                    break;
                }
                bs[i] = b;
            }
            lock (m_buffer)
                m_buffer.AddRange(bs);
            EndReceive(bs);
            if (bEnd)
                CallEndResult();
        }

        /// <summary>
        /// 开始接收
        /// </summary>
        void StartReceive()
        {
            if (!m_isReceiving)
                m_isReceiving = true;
        }

        /// <summary>
        /// 结束接收
        /// </summary>
        void EndReceive(byte[] bs)
        {
            //设置最后接收时间
            m_lastReceiveTime = DateTime.Now;
            m_isReceiving = false;
            if (OnSerialPortReceived != null
                &&bs!=null
                && bs.Length != 0)
            {
                AsyncCallResult call = new AsyncCallResult(CalReceived);
                call.BeginInvoke(bs, null, null); 
            }  
        }

        void CalReceived(byte[] data)
        {
            OnSerialPortReceived(this, new SerialPortEvents(data));
        }

        /// <summary>
        /// 读取结束监控
        /// </summary>
        void SerialPortMonitor()
        {
            if (!m_isReceiving)
            {
                if (DateTime.Now.Subtract(m_lastReceiveTime).TotalMilliseconds > m_receiveTimeout && m_buffer.Count != 0)
                {
                    CallEndResult();
                }
            }
            Thread.Sleep(50);
            SerialPortMonitor();
        }

        /// <summary>
        /// 处理接收数据完成
        /// </summary>
        void CallEndResult()
        {
            byte[] result = CopyBuffer(true);
            if (result != null && result.Length > 0)
            {
                if (SerialPortResult != null)
                {
                    //异步通知接收完成
                    AsyncCallResult call = new AsyncCallResult(CalResult);
                    call.BeginInvoke(result, null, null);      
                }
            }
        }

        void CalResult(byte[] data)
        {
            SerialPortResult(this, new SerialPortEvents(data));   
        }

        byte[] CopyBuffer(bool isClean=false)
        {
            byte[] bs=null;
            lock(m_buffer)
            {
                bs = new byte[m_buffer.Count];
                m_buffer.CopyTo(bs);
                if (isClean)
                    m_buffer.Clear();
            }
            return bs;
        }
        #endregion

        #region 发送数据
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="bs"></param>
        public void Send(byte[] bs)
        {
            if (!m_serialPort.IsOpen)
            {
                throw new SerialPortException("不能在串口关闭状态发送数据");
            }
            //异步发送
            AsyncSend call = new AsyncSend(DoAsyncSend);
            call.BeginInvoke(bs, null, null);
        }
        
        /// <summary>
        /// 异步发送
        /// </summary>
        /// <param name="data"></param>
        void DoAsyncSend(byte[] data)
        {
            int s = 0;

            if (data == null || data.Length == 0)
                return;
            if (m_useEndChar)
            {
                Array.Resize<byte>(ref data, data.Length + 1);
                data[data.Length - 1] = BitConverter.GetBytes(m_endChar)[0];
            }
            if (data.Length > m_writeBufferSize)
            {
                //分包发送
                int sendCount = 0;
                while (sendCount < data.Length)
                {
                    byte[] sendBytes;
                    if (sendCount + m_writeBufferSize > data.Length)
                    {
                        sendBytes = new byte[data.Length - sendCount];
                        Array.Copy(data, sendCount == 0 ? 0 : sendCount - 1, sendBytes, 0, sendBytes.Length);
                        sendCount = data.Length;
                    }
                    else
                    {
                        sendBytes = new byte[m_writeBufferSize];
                        Array.Copy(data, sendCount == 0 ? 0 : sendCount - 1, sendBytes, 0, sendBytes.Length);
                        sendCount += m_writeBufferSize;
                    }
                    //发送数据
                    m_serialPort.Write(sendBytes, 0, sendBytes.Length);
                    s += sendBytes.Length;
                    if (OnSeriaPortSend != null)
                        OnSeriaPortSend(this, new SerialPortEvents(sendBytes));
                    Array.Resize<byte>(ref sendBytes, 0);
                    Thread.Sleep(m_sendInterval);
                }
            }
            else
            { 
                //一次发送
                m_serialPort.Write(data, 0, data.Length);
            }
            //清理数据
            Array.Clear(data, 0, data.Length);
            data = null;
        }
        #endregion
    }

自定义事件类

    public class SerialPortEvents:EventArgs
    {
        /// <summary>
        /// 传递数据
        /// </summary>
        private byte[] m_bufferData;
        public byte[] BufferData
        {
            get { return m_bufferData; }
            set { m_bufferData = value; }
        }

        public SerialPortEvents()
            : base()
        { 
        }

        public SerialPortEvents(byte[] data)
            : base()
        {
            m_bufferData = data;
        }

    }

调用方法:

  SerialPortListener m_SerialPort = null;
            if (m_SerialPort == null)
            {
                m_SerialPort = new SerialPortListener(tbPort.Text, Convert.ToInt32(tbFrequency.Text));
                m_SerialPort.Parity = Parity.None;
                m_SerialPort.StopBits = StopBits.One;
                m_SerialPort.Handshake = Handshake.None;
                m_SerialPort.DataBits = 8;
                m_SerialPort.ReadBufferSize = 100;
                m_SerialPort.ReceivedBytesThreshold = 1;
                m_SerialPort.BufferSize = 4;
                m_SerialPort.ReceiveTimeout = Convert.ToInt32(tbTimeout.Text);
                m_SerialPort.WriteBufferSize = 100;
                m_SerialPort.SendInterval = 100;
                m_SerialPort.SerialPortResult += new HandResult(SerialPort_Result);
                m_SerialPort.OnSerialPortReceived += new OnReceivedData(SerialPort_Received);
                m_SerialPort.OnSeriaPortSend += new OnSendData(SerialPort_Send);
            }

            if (m_SerialPort.IsOpen)
            {
                m_SerialPort.Stop();
                btnListen.Text = "启动监听";
            }
            else
            {
                m_SerialPort.Start();
                btnListen.Text = "关闭监听";
            }

定义三个事件,用来通知UI线程:

1.收到结果触发

        void SerialPort_Result(object sender,SerialPortEvents e)
        {
            this.Invoke(new MethodInvoker(() => {
                //处理结果
                rtbResult.Text += Encoding.GetEncoding("GB2312").GetString(e.BufferData);
            }));
        }

2.串口有数据接收的时候触发

        void SerialPort_Received(object sender, SerialPortEvents e)
        {
            this.Invoke(new MethodInvoker(() =>
            {
                long receivedCount = Convert.ToInt64(lbReceivedCount.Text);
                if (e.BufferData != null)
                {
                    receivedCount += e.BufferData.Length;
                }
                lbReceivedCount.Text = receivedCount.ToString();
            }));
        }

3.发送数据的时候触发

        void SerialPort_Send(object sender, SerialPortEvents e)
        {
            this.Invoke(new MethodInvoker(() =>
            {
                long sendCount = Convert.ToInt64(lbSendCount.Text);
                if (e.BufferData != null)
                {
                    sendCount += e.BufferData.Length;
                }
                lbSendCount.Text = sendCount.ToString();
            }));
        }


附上效果图:



发送界面



接收界面:



系统源码已上传到博客园,下载地址:http://download.csdn.net/detail/kolvin2008/9655420


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值