C#中的串口通信SerialPort详解

今天这篇文章带大家学习下C#中的串口通讯。在日常的开发工作中,如果工作内容是CS方向的同学应该很容易接触到串口通讯方面的业务需求。那么也就很容易想到C#中SerialPort类,它就是专门来处理串口通讯相关的。

了解什么是串口通讯

  • 串口通讯是指外设和计算机间通过数据信号线,地线,控制线等,按位进行传输数据的一种通讯方式。
    这种通讯方式使用的数据线少,在远距离通信中可以节约成本,但其传输速度比并行通讯慢。
    串口是计算机上一种非常通用的设备通讯协议。大多数计算机包含两个基于RS-232的串口。
    串口也是仪器仪表设备通用的通讯协议,很多GPIB兼容的设备也带有RS-232口,同时串口通信协议也可以用于获取远程设备采集的数据。

串行接口是一种可以将接受来自CPU的并行数据字符转换为连续的串行数据流发送出去,同时也可将接受的串行数据流转换为并行的数据字符供给CPU的器件。

  • 一般完成这种功能的电路,我们称为串行接口电路。

串行按位(bit)发送和接收字节,尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。

img

这里提一句,我们笔记本的USB口,它不是串口,它是所谓的U口,我们的串口如果需要插入到USB口,那么就需要一个RS-232转U口的转换器才可以。
这里需要注意下,一般我们的硬件工程师都会在自己的硬件设备都内置这个小转换设备,很方便。

C#中的串口通讯SerialPort

从.NET Framework 2.0开始,微软就默认提供了System.IO.Ports.SerialPort类,根据SerialPort类提供的简单操作步骤,可以很容易的完成串口的信息收发程序,从特定的串口中接收到数据也可以进行数据的发送。

关于SerialPort类的应用也很容易,可以根据微软官方的介绍进行使用。

  • 配置SerialPort的名称,端口号,波特率等。
  • 打开/关闭串口
  • 接收/发送数据
1
2
3
4
5
6
7
8
9
10
11
12
SerialPort mySerialPort = new SerialPort("COM2");
mySerialPort.BaudRate = 9600;
mySerialPort.Parity=Parity.None;
mySerialPort.StopBits = StopBits.One;
mySerialPort.DataBits = 8;
mySerialPort.Handshake = Handshake.Non;

mySerialPort.DataReceived += new SerialDataReceivedEvenHandler(DataReceive_Method);

mySerialPort.Open();

mySerialPort.Close();

发送数据

1
2
3
4
5
6
7
8
9
10
using System.IO.Ports;
private static void SendSampleData()
{
SerialPort port = new SerialPort(
"COM1", 9600, Parity.None, 8, StopBits.One);
port.Open();
port.Write("Hello World");
port.Write(new byte[] { 0x0A, 0xE2, 0xFF }, 0, 3);
port.Close();
}

日常串口的使用多数情况是和线程进行的,一般都是在子线程中做这些耗时和重复的工作,在主线程或者界面只进行数据的展示处理即可。
在子线程的处理过程中需要特别处理串口的打开和关闭,要及时关闭串口的状态,防止出现无法捕获的异常错误。

c# 实现简单的串口通讯

第一步

  • 创建一个WinForm窗体,拉入一些界面元素

img

重点就是,图中用红框标出的,工具箱——组件——SerialPort,做.net串口通讯,这是必备控件

第二步

  • 设置SerialPort控件属性

用C#向串口发送数据没什么特别的,就是调用SerialPort的Write方法往串口写数据就行

但是从串口那里接收数据的方式就比较特别了:

  1. 首先,需要在代码里声明一个特别的事件函数
1
2
3
4
private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
this.Invoke(new EventHandler(UpdateUIText));
}

此函数是用来绑定到SerialPort控件的DataReceived事件

img

顾名思义,这个事件就是在接收到串口返回的数据时触发,里面就一句代码

对这句代码有兴趣的可以私下再去研究,这里就不赘述了

总之,这句代码的用途就是用来调动另一个函数,对界面UI元素的值进行更新(当然你也可以在里面执行其他操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void UpdateUIText(object s, EventArgs e)
{
try
{
//必须要阻塞线程一段时间,以免在交易超时的情况下,由于read太快导致读取不完整
System.Threading.Thread.Sleep(500);
string txt = serialPort.ReadExisting();
txt_Received.Text = txt;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}

第三步

开始写逻辑代码,废话不多说,直接贴上来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace WinForm串口通讯
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
             
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            string[] ports = System.IO.Ports.SerialPort.GetPortNames();
            if (ports.Length == 0)
            {
                MessageBox.Show("本机没有串口!");
            }
            Array.Sort(ports);
            serialPort.PortName = ports[0];//串口号COM3
            serialPort.BaudRate = 115200;//波特率
            serialPort.DataBits = 8;//数据位
            serialPort.StopBits = System.IO.Ports.StopBits.One;//停止位
            serialPort.Encoding = System.Text.Encoding.GetEncoding("GB2312");//此行非常重要,解决接收中文乱码的问题
 
            // 打开串口
            try
            {
                serialPort.Open();
            }
            catch (Exception ex)
            {
                //捕获到异常信息,创建一个新的comm对象,之前的不能用了。  
                serialPort = new System.IO.Ports.SerialPort();
                //将异常信息传递给用户。  
                MessageBox.Show(ex.Message);
                return;
            }
        }
 
        private void button1_Click(object sender, EventArgs e)
        {          
            string msgOrder = txt_Msg.Text;
            //MessageBox.Show(msgOrder);
            serialPort.Write(msgOrder);           
        }
 
 
        private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            this.Invoke(new EventHandler(UpdateUIText));
        }
 
        private void UpdateUIText(object s, EventArgs e)
        {
            try
            {
                //必须要阻塞线程一段时间,以免在交易超时的情况下,由于read太快导致读取不完整
                System.Threading.Thread.Sleep(500);
                string txt = serialPort.ReadExisting();
                txt_Received.Text = txt;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
             
        }
 
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                if (serialPort != null && serialPort.IsOpen)
                {
                    serialPort.Close();
                    serialPort.Dispose();
                }
            }
            catch (Exception ex)
            {
                //将异常信息传递给用户。  
                MessageBox.Show(ex.Message);
                return;
            }
        }  
    }
}

C#基于SerialPort类实现串口通讯详解

  • 本文实例为大家分享了C#基于SerialPort类实现串口通讯的具体代码,供大家参考,具体内容如下

img

  • 最终效果:

img

  • 窗体设置:

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Threading;
using System.Windows.Forms;

namespace SerialCommunication
{
    public partial class Form1 : Form
    {
        System.Threading.SynchronizationContext m_SyncContext = null;
        SerialPort serialPort = null;
        ToHexadecimalString toHexadecimalString = new ToHexadecimalString();
        public Form1()
        {
            m_SyncContext = SynchronizationContext.Current;
            InitializeComponent();
        }

        //Data initialization
        private void Form1_Load(object sender, EventArgs e)
        {
            string[] ports = SerialPort.GetPortNames();
            SerialPortNumber.Items.AddRange(ports);
            SerialPortNumber.SelectedIndex = SerialPortNumber.Items.Count > 0 ? 0 : -1;
            BaudRateCom.Text = "9600";  //Set parameters
            CheckBitCom.Text = "None";
            DataBitCom.Text = "8";
            StopBitCom.Text = "One";
        }

            //Send data
         private void Send_Click(object sender, EventArgs e)
        {
            try
            {
                //Send data and handle exceptions
                byte[] bytes = new byte[SendMessage.Text.Length];
                bytes = Encoding.Default.GetBytes(SendMessage.Text);
                serialPort.Write(bytes, 0, bytes.Length);
            }
            catch (Exception error)
            {
                MessageBox.Show(error.Message);
            }
        }
                // Byte to hexadecimal string 

        //Clear data
        private void Clear_Click(object sender, EventArgs e)
        {
            ReceiveMessage.Text = "";
        }

        private void Open_Click(object sender, EventArgs e)
        {
            string portName = SerialPortNumber.Text;
            int buadRate = int.Parse(BaudRateCom.Text);
            Parity parity = 0;
            switch (CheckBitCom.Text)
            {
                case "None":
                    parity = Parity.None; break;
                case "Odd":
                    parity = Parity.Odd; break;
                case "Even":
                    parity = Parity.Even; break;
                case "Mark":
                    parity = Parity.Mark; break;
                case "Space":
                    parity = Parity.Space; break;
            }
            int dataBit = int.Parse(DataBitCom.Text);
            StopBits stopBits = 0;
            switch (StopBitCom.Text)
            {
                case "None":
                    stopBits = StopBits.None; break;
                case "One":
                    stopBits = StopBits.One; break;
                case "Tow":
                    stopBits = StopBits.Two; break;
                case "OnePointFive":
                    stopBits = StopBits.OnePointFive; break;
            }
            try
            {
                if (Open.Text == "Open")
                {
                    serialPort = new SerialPort(portName, buadRate, parity, dataBit, stopBits);
                    serialPort.Open();
                    Open.Text = "Close";
                }
                else if (Open.Text == "Close")
                {
                    serialPort.Close();
                    Open.Text = "Open";
                }
            }
            catch (Exception errror)
            {
                MessageBox.Show(errror.Message);
            }
            serialPort.DataReceived += onDataReceived;

        }
        private void onDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                Byte[] bytes = new byte[serialPort.BytesToRead];
                serialPort.Read(bytes, 0, serialPort.BytesToRead);
                m_SyncContext.Post(new SendOrPostCallback((obj) =>
                {
                    ReceiveMessage.AppendText(Encoding.Default.GetString(bytes));
                }), bytes);
            }
            catch (Exception error)
            {
                MessageBox.Show(error.Message);
            }
        }
    }
}

C#基于WinForm实现串口通讯

串口通讯是一种计算机常用的数据传输方式。

程序运行如下:

img

首先,检查计算机的串口,并获取所有串口信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
private void CheckPort()//检查串口是否可用
        {
            myLog(2, "检测串口开始!");  //log记录函数
            comboBox1.Items.Clear();//清除控件中的当前值         
            string[] a = SerialPort.GetPortNames();
            if (a.Length != 0)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    comboBox1.Items.Add(a[i]);
                }
                comboBox1.SelectedIndex = 0;//??
                myLog(2, "检测串口完成");
            }
            else
            {
                myLog(2, "无可用串口...", true);
            }
        }
        private void SetPort()//设置串口
        {
            try
            {
                serialPort1.PortName = comboBox1.Text.Trim();//串口名给了串口类
                //波特率   虚拟串口的波特率好像不能修改,但得将其赋为空,不然报错         
                // serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text.ToString().Trim());        
                // 9600;
                serialPort1.BaudRate = Convert.ToInt32("".Trim());
                //奇偶效验
                if (comboBox5.Text.Trim() == "奇校验")
                {
                    serialPort1.Parity = Parity.Odd;//将奇校验位给了sp的协议
                }
                else if (comboBox5.Text.Trim() == "偶校验")
                {
                    serialPort1.Parity = Parity.Even;
                }
                else
                {
                    serialPort1.Parity = Parity.None;
                }
                //停止位
                if (comboBox4.Text.Trim() == "1.5")
                {
                    serialPort1.StopBits = StopBits.OnePointFive;//设置停止位有几位
                }
                else if (comboBox4.Text.Trim() == "2")
                {
                    serialPort1.StopBits = StopBits.Two;
                }
                else
                {
                    serialPort1.StopBits = StopBits.One;
                }
                serialPort1.DataBits = Convert.ToInt16(comboBox3.Text.ToString().Trim());//数据位
                serialPort1.Encoding = Encoding.UTF8;//串口通信的编码格式
                serialPort1.Open();
            }
            catch { }
        }
        /// <summary>
        /// Log记录
        /// </summary>
        /// <param name="aa">aa=  0为发送  1为接受 2为日常记录</param>
        /// <param name="mystr">记录内容</param>
        /// <param name="IsHint">true 显示弹框,false 不显示弹框 默认false</param>
        private void myLog(int aa, string mystr, bool IsHint = false)
        {
            //log存放路径
            string myypath = myPath + DateTime.Now.ToString("yyyyMMdd") + ".txt";
            StreamWriter sw = new StreamWriter(myypath, true);
            string tempSendstr;
            if (aa == 0)
            {
                tempSendstr = DateTime.Now.ToString("HH:mm:ss.fff") + "  " + serialPort1.PortName + " 发送--->>>  " + mystr;
            }
            else if (aa == 1)
            {
                tempSendstr = DateTime.Now.ToString("HH:mm:ss.fff") + "  " + serialPort1.PortName + " 接收<<<---  " + mystr;
            }
            else
            {
                tempSendstr = DateTime.Now.ToString("HH:mm:ss.fff") + "  " + mystr;
            }
            sw.WriteLine(tempSendstr);
            sw.Close();
            //弹框提示
            if (IsHint)
            {
                MessageBox.Show(mystr, "提示");
            }
            if (listBox1.Items.Count >= 10) listBox1.Items.Clear();
            listBox1.Items.Add(tempSendstr);
        }

大部分的笔记本都不带串口的,我们可以在网上下载 Configure Virtual Serial Port Driver,给自己的笔记本添加几对虚拟串口(虚拟串口都是成对且一一匹配的),再用串口调试助手就可以进行调试了。

数据的发送:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 //发送
private void button3_Click(object sender, EventArgs e)
        {
            if(!serialPort1.IsOpen)
            {
                MessageBox.Show("串口未打开,请检查!");
                return;
            }
            string mystr1 = textBox1.Text;
            byte[] a = Encoding.UTF8.GetBytes(mystr1);
            string mystr2 = Encoding.UTF8.GetString(a);
            serialPort1.Write(mystr2);//将数据写入串行端口输出缓冲区
            myLog(0, mystr2);
        }

数据接收:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//串口接收数据
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            Thread.Sleep(100);
            this.Invoke((EventHandler)(delegate //异步委托一个线程
            {
                if (serialPort1.BytesToRead > 0)
                {
                    byte[] a = new byte[serialPort1.BytesToRead];//读出缓冲区串口通信的字节                                              
                    int readbytes = 0;
                    while (serialPort1.Read(a, readbytes, serialPort1.BytesToRead - readbytes) <= 0) ;
                    string str = UTF8Encoding.UTF8.GetString(a);
                    textBox2.Text = str + "\r\n";
                    myLog(1, str);
                }
            }));
        }

这是串口之间字符串的发送与接收,如要进行十六进制的传输,只需要在数据发送和接收时将字符串转化成十六进制,再写进缓冲区或读取。

  • 多条信息发送时,可能会出现下面的情况。

img

  • 串口调试助手将三次发送的数据一起读出来了,此时我们只需要在发送时给上一定的延时即可。

img

这样就能实现串口多条信息的发送与接收了。


相关链接

  1. C#中的串口通信SerialPort详解
  2. C#基于SerialPort类实现串口通讯详解
  3. C#基于WinForm实现串口通讯

=================我是分割线=================

欢迎到公众号来唠嗑: