在Form窗口添加SerialPort控件
在构造函数中添加语句
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
新创建的线程不能访问UI线程创建的窗口控件,需要设置为跨线程访问
1、窗口加载函数
串口一般使用Modbus RTU协议采集传感器数据,配置传感器常用的属性即可
private void Form1_Load(object sender, EventArgs e) { cmbPorts.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames()); cmbPorts.Text = "COM21"; cmbBaud.Items.AddRange(new string []{ "4800", "9600","115200"}); cmbBaud.Text = "9600"; cmbDatabits.Items.AddRange(new string[] { "7", "8" }); cmbDatabits.Text = "8"; cmbStopbits.Items.AddRange(new string[] { "1", "1.5", "2" }); cmbStopbits.Text = "1"; cmbParity.Items.AddRange(new string[] { "奇校验", "偶校验", "无校验" }); cmbParity.Text = "无校验"; } |
System.IO.Ports.SerialPort.GetPortNames()方法,作用是获取当前计算机的串口端口名的数组。
comboBox.Items属性,获取一个对象,该对象表示此ComboBox中所含的项的集合
comboBox.Items.AddRange()方法,向comboBox的项列表添加项的数组
comboBox.Text 属性 获取或设置与此控件关联的文本
2、打开端口
当端口未打开时,执行按钮动作,端口获取配置的属性后打开端口,此时“端口状态”显示为"端口已打开"
同时执行数据接收函数
serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(SerialPort1_DataReceived);
表示serialPort1接收到数据时执行函数SerialPort1_DataReceived
若打开失败则弹出消息框提示“串口打开失败”
private void Btn_OpenPort_Click(object sender, EventArgs e) { if (!serialPort1.IsOpen) { try { serialPort1.PortName = cmbPort.Text; serialPort1.BaudRate = Convert.ToInt32(cmbBaud.Text); serialPort1.DataBits = Convert.ToInt32(cmbStopbit.Text); if (cmbDatabits.Text == "无校验") { serialPort1.Parity = Parity.None; } else if (cmbDatabits.Text == "奇校验") { serialPort1.Parity = Parity.Even; } else if (cmbDatabits.Text == "偶校验") { serialPort1.Parity = Parity.Odd; } if (cmbParity.Text == "1") { serialPort1.StopBits = StopBits.One; } else if (cmbParity.Text == "1.5") { serialPort1.StopBits = StopBits.OnePointFive; } else if (cmbParity.Text == "2") { serialPort1.StopBits = StopBits.Two; } serialPort1.Open(); lbl_PortStatus.Text = "串口已打开"; lbl_PortStatus.ForeColor = System.Drawing.Color.Green; serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(SerialPort1_DataReceived); } catch { MessageBox.Show("串口打开失败"); } } } |
IsOpen属性:获取一个值,该值指示serialPort1的打开或关闭状态
PortName属性: 获取或设置通信端口,包括但不限于所有可用的COM端口
BaudRate属性:获取或设置串行波特率
DataBits属性:获取或设置每个字节的标准数据位长度
Parity属性:获取或设置奇偶校验检查协议
StopBits属性:获取或设置每个字节的标准停止位数
Open()方法:打开一个新的串行端口连接
DataReceived事件: 指示已通过由SerialPortd1端口接收的数据
3 、关闭串口
private void btnClosePort_Click(object sender, EventArgs e) { if (serialPort1.IsOpen) { try { serialPort1.Close(); lbl_PortStatus.Text = "串口已关闭"; lbl_PortStatus.ForeColor = System.Drawing.Color.Red; } catch { MessageBox.Show("串口关闭失败"); } } } |
4 、数据接收
使用一个复选框"Hex显示"来确认接收字符串数据或字节数据
将接收到的数据显示在richTextBox控件中
(1)当接收到的数据为字符串时,使用ReadExisting()方法
ReadExisting()方法:读取SerialPort对象的流和输入缓冲区中所有立即可用的字节。
(2)当接收到的数据为字节时,使用Read(Byte[],Int32,Int32)方法
Read(Byte[] buffer,Int32 offset,Int32 count)方法 :从SerialPort输入缓冲区读取一些字节并将字节写入字节数组中指定的偏移量处
buffer参数:将要写入到其中的字节数组
offset参数:要写入字节的buffer中的偏移量(起始位置)
count参数:最多读取的字节数,如果count大于输入缓冲区中的字节数,则读取较少的字节
count字节数可由serialPort对象的BytesToRead属性获取
BytesToRead属性:获取接收缓冲区中数据的字节数
private void SerialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (!chkHex.Checked)//字符串显示 { if (serialPort1.IsOpen) { string str = serialPort1.ReadExisting(); if (str.Length == 0) return; rtxReceive.AppendText(DateTime.Now.ToString() + ":"+str + "\r\n"); } } else//字节显示 { Thread.Sleep(100);// 等待100ms,防止读取不完的情况 if (serialPort1.IsOpen) { int length = serialPort1.BytesToRead; byte[] data = new byte[length]; int len = serialPort1.Read(data, 0, length); if (len == 0) { return; } if (len > 0) { string str=BitConverter.ToString(data, 0, len);//将接收到的字节转化为字符串形式显示 rtxReceive.AppendText(DateTime.Now.ToString() + ":" + str + "\r\n"); } } } } |
5、发送数据
使用一个复选框"Hex发送"来选择发送字符串数据或字节数据
(1)当发送字符串数据时,使用Write(string)方法
Write(String)方法:将指定的字符串写入串行端口。
(2)当发送字节数据时,使用Write(Byte[],Int32,int32)
Write(Byte[],Int32,int32)方法:使用缓冲区中的数据将指定的字节写入串行端口
private void btnSend_Click(object sender, EventArgs e) { try { string sendMessage = txtSend.Text; byte[] sendArray = Encoding.UTF8.GetBytes(sendMessage); if (!chkHexSend.Checked)//字符串发送 { if (serialPort1.IsOpen) { try { serialPort1.Write(sendMessage); rtxReceive.AppendText(DateTime.Now.ToString() + ":" + sendMessage + "\r\n"); } catch { } } } else //字节发送 { string str = txtSend.Text.Replace(" ", ""); if (str.Length == 0 || str.Length % 2 != 0) rtxReceive.AppendText(DateTime.Now.ToString() + "【错误提示】:请输入正确的报文格式" + "\r\n"); byte[] data = CreateSendArray(str); if (serialPort1.IsOpen) { try { serialPort1.Write(data, 0, data.Length); rtxReceive.AppendText("【串口发送】" + DateTime.Now.ToString() + ":" + BitConverter.ToString(data) + "\r\n"); } catch { } }
else { } } } catch { } } |
以字节发送是以Modbus报文双字节格式发送,需要将字符串转发为双字节数组
byte[] CreateSendArray(string sendStr) { int length = sendStr.Length / 2; byte[] data = new byte[length]; for (int i = 0; i < length; i++) { data[i] = Convert.ToByte(sendStr.Substring(i * 2, 2), 16); } if (ChkCRC.Checked) { data = CRC16(data, data.Length); } return data; } |
同时添加CRC校验函数
public byte[] CRC16(byte[] ucpdata, int length) { int crc = 0xFFFF; for (int i = 0; i < length; i++) { crc = ucpdata[i] ^ crc; for (int n = 0; n < 8; n++) { int temp; temp = crc & 1; crc >>= 1; crc &= 0x7fff; if (temp == 1) { crc ^= 0xa001; } crc &= 0xffff; } } byte[] crc16 = new byte[length + 2]; for (int r = 0; r < length; r++) { crc16[r] = ucpdata[r]; } crc16[crc16.Length - 1] = (byte)((crc >> 8) & 0xff); crc16[crc16.Length - 2] = (byte)(crc & 0xff); return crc16; } |
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.Windows.Forms;
using System.IO.Ports;
using System.Threading;
using System.Net;
namespace SerialPort
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
}
private void Form1_Load(object sender, EventArgs e)
{
cmbPorts.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
cmbPorts.Text = "COM21";
cmbBaud.Items.AddRange(new string []{ "4800", "9600","115200"});
cmbBaud.Text = "9600";
cmbDatabits.Items.AddRange(new string[] { "7", "8" });
cmbDatabits.Text = "8";
cmbStopbits.Items.AddRange(new string[] { "1", "1.5", "2" });
cmbStopbits.Text = "1";
cmbParity.Items.AddRange(new string[] { "奇校验", "偶校验", "无校验" });
cmbParity.Text = "无校验";
}
//打开串口
private void btnOpenPort_Click(object sender, EventArgs e)
{
if (!serialPort1.IsOpen)
{
try
{
serialPort1.PortName=cmbPorts.Text;
serialPort1.BaudRate=Convert.ToInt32(cmbBaud.Text);//将字符串"9600"转化为整型数字9600
serialPort1.DataBits = Convert.ToInt32(cmbDatabits.Text);
if (cmbDatabits.Text == "无校验") { serialPort1.Parity = Parity.None; }
else if (cmbDatabits.Text == "奇校验") { serialPort1.Parity = Parity.Even; }
else if (cmbDatabits.Text == "偶校验") { serialPort1.Parity = Parity.Odd; }
if (cmbParity.Text == "1") { serialPort1.StopBits = StopBits.One; }
else if (cmbParity.Text == "1.5") { serialPort1.StopBits = StopBits.OnePointFive; }
else if (cmbParity.Text == "2") { serialPort1.StopBits = StopBits.Two; }
serialPort1.Open();
lblStatus.Text = "端口已打开";
lblStatus.ForeColor = System.Drawing.Color.Green;
serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(SerialPort1_DataReceived);
}
catch { MessageBox.Show("串口打开失败"); }
}
}
//关闭端口
private void btnClosePort_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
try
{
serialPort1.Close();
lblStatus.Text = "串口已关闭";
lblStatus.ForeColor = System.Drawing.Color.Red;
}
catch
{
MessageBox.Show("串口关闭失败");
}
}
}
//数据接收函数
private void SerialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (!chkHexReceive.Checked)//字符串显示
{
if (serialPort1.IsOpen)
{
string str = serialPort1.ReadExisting();
if (str.Length == 0)
return;
rtxReceive.AppendText(DateTime.Now.ToString() + ":"+str + "\r\n");
}
}
else//字节显示
{
Thread.Sleep(100);// 等待100ms,防止读取不完的情况
if (serialPort1.IsOpen)
{
int length = serialPort1.BytesToRead;
byte[] data = new byte[length];
int len = serialPort1.Read(data, 0, length);
if (len == 0) { return; }
if (len > 0)
{
string str=BitConverter.ToString(data, 0, len);
rtxReceive.AppendText(DateTime.Now.ToString() + ":" + str + "\r\n");
}
}
}
}
//发送消息
private void btnSend_Click(object sender, EventArgs e)
{
try
{
string sendMessage = txtSend.Text;
byte[] sendArray = Encoding.UTF8.GetBytes(sendMessage);
if (!chkHexSend.Checked)//字符串发送
{
if (serialPort1.IsOpen)
{
try
{
serialPort1.Write(sendMessage);
rtxReceive.AppendText(DateTime.Now.ToString() + ":" + sendMessage + "\r\n");
}
catch { }
}
}
else //字节发送
{
string str = txtSend.Text.Replace(" ", "");
if (str.Length == 0 || str.Length % 2 != 0)
rtxReceive.AppendText(DateTime.Now.ToString() + "【错误提示】:请输入正确的报文格式" + "\r\n");
byte[] data = CreateSendArray(str);
if (serialPort1.IsOpen)
{
try
{
serialPort1.Write(data, 0, data.Length);
rtxReceive.AppendText("【串口发送】" + DateTime.Now.ToString() + ":" + BitConverter.ToString(data) + "\r\n");
}
catch { }
}
else { }
}
}
catch { }
}
//发送信息转换为字节函数
byte[] CreateSendArray(string sendStr)
{
int length = sendStr.Length / 2;
byte[] data = new byte[length];
for (int i = 0; i < length; i++)
{
data[i] = Convert.ToByte(sendStr.Substring(i * 2, 2), 16);
}
if (chkCRC.Checked)
{ data = CRC16(data, data.Length); }
return data;
}
//CRC函数
public byte[] CRC16(byte[] ucpdata, int length)
{
int crc = 0xFFFF;
for (int i = 0; i < length; i++)
{
crc = ucpdata[i] ^ crc;
for (int n = 0; n < 8; n++)
{
int temp;
temp = crc & 1;
crc >>= 1;
crc &= 0x7fff;
if (temp == 1)
{
crc ^= 0xa001;
}
crc &= 0xffff;
}
}
byte[] crc16 = new byte[length + 2];
for (int r = 0; r < length; r++)
{
crc16[r] = ucpdata[r];
}
crc16[crc16.Length - 1] = (byte)((crc >> 8) & 0xff);
crc16[crc16.Length - 2] = (byte)(crc & 0xff);
return crc16;
}
}
}