C#实现串口通信解析

1. 串口硬件信号定义

串口通信(Serial Communications)是指外设和计算机间通过数据信号线、地线等按位(bit)进行传输数据的一种通信方式,属于串行通信方式,能够实现远距离通信,长度可达1200米。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。

串口通信的接口标准有很多,有 RS-232C、RS-232、RS-422A、RS-485 等。常用的就是 RS-232 和 RS-485。串口通信使用的大多都是 DB9 接口,如下图。
在这里插入图片描述

1 载波检测(DCD)
2 接受数据(RXD)
3 发出数据(TXD)
4 数据终端准备好(DTR)
5 信号地线(SG)
6 数据准备好(DSR)
7 请求发送(RTS)
8 清除发送(CTS)
9 振铃指示(RI)
在这里插入图片描述
DB9 Connector 信号定义。串口测试将2、3针脚短接即可。在这里插入图片描述

这里我们以 RS-232 接口进行演示。

1、数据包格式定为(10bytes):

帧头(0xAA,0x55),命令字(1byte),地址位(2bytes),数据位(2bytes),校验位(1byte,和校验),帧尾(0xFE,0xFE)

地址位和数据位都是高位在前。

2、串口端口号搜索

string[] portList = System.IO.Ports.SerialPort.GetPortNames();
for (int i = 0; i < portList.Length; i++)
{
    string name = portList[i];
    comboBox.Items.Add(name);
}

还有一种通过调用API的方法来获取实现,可以获取详细的完整串口名称,对于USB-to-COM虚拟串口来说特别适用。

3、串口属性参数设置

SerialPort mySerialPort = new SerialPort("COM2");//端口
mySerialPort.BaudRate = 9600;//波特率
mySerialPort.Parity = Parity.None;//校验位
mySerialPort.StopBits = StopBits.One;//停止位
mySerialPort.DataBits = 8;//数据位
mySerialPort.Handshake = Handshake.Non;
mySerialPort.ReadTimeout = 1500;
mySerialPort.DtrEnable = true;//启用数据终端就绪信息
mySerialPort.Encoding = Encoding.UTF8;
mySerialPort.ReceivedBytesThreshold = 1;//DataReceived触发前内部输入缓冲器的字节数
mySerialPort.DataReceived += new SerialDataReceivedEvenHandler(DataReceive_Method);

mySerialPort.Open();

4、串口发送信息

  • Write(Byte[], Int32, Int32) :将指定数量的字节写入串行端口
  • Write(Char[], Int32, Int32) :将指定数量的字符写入串行端口
  • Write(String) :将指定的字符串写入串行端口
  • WriteLine(String) :将指定的字符串和NewLine值写入输出缓冲区
// Write a string
port.Write("Hello World");

// Write a set of bytes
port.Write(new byte[] { 0x0A, 0xE2, 0xFF }, 0, 3);

// Close the port
port.Close();

5. 串口接收信息

  • Read(Byte[], Int32, Int32):从SerialPort输入缓冲区读取一些字节,并将那些字节写入字节数组中指定的偏移量处
  • ReadByte():从SerialPort输入缓冲区中同步读取一个字节
  • ReadChar(): 从SerialPort输入缓冲区中同步读取一个字符
  • ReadExisting() :在编码的基础上,读取SerialPort对象的流和输入缓冲区中所有立即可用的字节
  • ReadLine() :一直读取到输入缓冲区中的NewLine值
  • ReadTo(String) :一直读取到输入缓冲区中的指定value的字符串
string serialReadString;
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    serialReadString = port.ReadExisting());
    this.txt1.Invoke( new MethodInvoker(delegate { this.txt1.AppendText(serialReadString); }));
}

6、循环接收数据

void com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    // Use either the binary OR the string technique (but not both)
    // Buffer and process binary data
    while (com.BytesToRead > 0)
        bBuffer.Add((byte)com.ReadByte());
    ProcessBuffer(bBuffer);

    // Buffer string data
    sBuffer += com.ReadExisting();
    ProcessBuffer(sBuffer);
}

private void ProcessBuffer(string sBuffer)
{
    // Look in the string for useful information
    // then remove the useful data from the buffer
}

private void ProcessBuffer(List<byte> bBuffer)
{
    // Look in the byte array for useful information
    // then remove the useful data from the buffer
}

7、 C# 串口接收数据不完整解决办法

/针对数据协议:head + len + playload + check 类型
 private List<byte> buffer = new List<byte>(4096);
 
private void sp_DataReceived(objectsender, EventArgs e) //sp是串口控件
{
    int n = sp.BytesToRead;
    byte[] buf = new byte[n];
    sp.Read(buf, 0, n);
 
    //1.缓存数据
    buffer.AddRange(buf);
 
    //2.完整性判断
    while (buffer.Count >= 4) //至少包含帧头(2字节)、长度(1字节)、校验位(1字节);根据设计不同而不同
    {
        //2.1 查找数据头
        if (buffer[0] == 0x01) //传输数据有帧头,用于判断
        {
            int len = buffer[2];
            
            if (buffer.Count < len + 4) //数据区尚未接收完整
            {
                break;
            }
 
            //得到完整的数据,复制到ReceiveBytes中进行校验
            byte[] ReceiveBytes = new byte[len + 4];
            buffer.CopyTo(0, ReceiveBytes, 0, len + 4);
 
            byte jiaoyan; //开始校验---自定义实现
            jiaoyan = this.JY(ReceiveBytes);//
            if (jiaoyan != ReceiveBytes[len+3]) //校验失败,最后一个字节是校验位
            {
                buffer.RemoveRange(0, len + 4);
                MessageBox.Show("数据包不正确!");
                continue;
            }
 
            buffer.RemoveRange(0, len + 4);
              
            ///执行对数据进行处理操作RunReceiveDataCallback(ReceiveBytes);
        }
        else //帧头不正确时,记得清除
        {
            buffer.RemoveAt(0);
        }
    }
}
//针对协议类型: head + len +cmd + seq+ playload +check + tail; 
 
private List<byte> buffer = new List<byte>(4096);
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        { 
            try
            {
                try
                {
                    int nCount = serialPort1.BytesToRead;
                    if (nCount == 0)
                    {
                        return;
                    }
                 
                    byte[] btAryBuffer = new byte[nCount];
                    serialPort1.Read(btAryBuffer, 0, nCount);
 
                    //缓存数据
                    buffer.AddRange(btAryBuffer);
 
                    int index = 1;
                    while (buffer.Count>0x07)  //最短协议长度
                    {
                        if (buffer[0] == 0x01)   //协议头
                        {
 
                            if (buffer[index] != 0x03)  //查询协议尾
                            {
 
                                 index++;
 
                                if (index > buffer.Count)    //没有接受到帧尾 0x03
                                {
                                    break;              //退出继续接收
 
                                }
 
                            }
                            else  // 接收到协议尾 得到完整一帧数据
                            {
                                byte[] ReceiveBytes = new byte[index+1];
                                buffer.CopyTo(0, ReceiveBytes, 0, index+1);
 
                                RunReceiveDataCallback(ReceiveBytes);
 
                                buffer.RemoveRange(0, index);
                            }
 
                        }
                        else
                        {
                            buffer.RemoveAt(0);
 
                        }
                    }            
                }
                catch (System.Exception ex)
                {
 
                }
             
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                handerListening = false;
            }
        }

8、封装

1、数据封装方法:

//数据打包
private byte[] DataPackage(byte cmd, int addr, int data)
        {
            byte[] package = new byte[10];
            package[0] = 0xAA;//帧头
            package[1] = 0x55;
            package[2] = cmd;//命令字    
            byte[] dataaddr = IntToByteArray(addr);
            package[3] = dataaddr[0];//地址高字节
            package[4] = dataaddr[1];//地址低字节
            byte[] value = IntToByteArray(data);
            package[5] = value[0];//数据高字节
            package[6] = value[1];//数据低字节
            package[7] = CheckSum(package);//校验位
            package[8] = 0xFE;//帧尾
            package[9] = 0xFE;
            return package;
        }
 
        //将int转换成2位数组
        private static byte[] IntToByteArray(int value)
        {
            int hvalue = (value >> 8) & 0xFF;
            int lValue = value & 0xFF;
            byte[] arr = new byte[] { (byte)hvalue, (byte)lValue };
            return arr;
        }
 
        //得到和校验码
        private byte CheckSum(byte[] package)
        {
            byte checksum = 0;
            for (int i = 0; i < package.Length; i++)
            {
                checksum += package[i];
            }
            return checksum;
        }

2、串口调用封装类CommHelper.cs

internal class CommHelper
    {
        //委托
        public delegate void EventHandle(byte[] readBuffer);
        public event EventHandle DataReceived;
 
        public SerialPort serialPort;
        private Thread thread;
        volatile bool _keepReading;
 
        public CommHelper()
        {
            serialPort = new SerialPort();
            
            thread = null;
            _keepReading = false;
 
            serialPort.ReadTimeout = -1;
            serialPort.WriteTimeout = 1000;
        }
 
        //获取串口打开状态
        public bool IsOpen
        {
            get
            {
                return serialPort.IsOpen;
            }
        }
 
        //开始读取
        private void StartReading()
        {
            if (!_keepReading)
            {
                _keepReading = true;
                thread = new Thread(new ThreadStart(ReadPort));
                thread.Start();
            }
        }
 
        //停止读取
        private void StopReading()
        {
            if (_keepReading)
            {
                _keepReading = false;
                thread.Join();
                thread = null;
            }
        }
 
        //读数据
        private void ReadPort()
        {
            while (_keepReading)
            {
                if (serialPort.IsOpen)
                {
                    int count = serialPort.BytesToRead;
                    if (count > 0)
                    {
                        byte[] readBuffer = new byte[count];
                        try
                        {
                            Application.DoEvents();
                            serialPort.Read(readBuffer, 0, count);
                            DataReceived?.Invoke(readBuffer);
                            Thread.Sleep(100);
                        }
                        catch (TimeoutException)
                        {
                        }
                    }
                }
            }
        }
 
        //写数据
        public void WritePort(byte[] send, int offSet, int count)
        {
            if (IsOpen)
            {
                serialPort.Write(send, offSet, count);
            }
        }
 
        //打开
        public void Open()
        {
            Close();
            try
            {
                serialPort.Open();
                serialPort.RtsEnable = true;
                if (serialPort.IsOpen)
                {
                    StartReading();
                }
                else
                {
                    MessageBox.Show("串口打开失败!");
                }
            }
            catch
            {
 
            }
        }
 
        //关闭
        public void Close()
        {
            StopReading();
            serialPort.Close();            
        }
    }
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是刘彦宏吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值