C# SerialPort Modbus报文

本文介绍了如何在WindowsForm中添加SerialPort控件,配置串口通信参数,实现串口的打开、关闭、数据接收和发送,以及使用CRC校验功能。重点讲解了使用ModbusRTU协议采集传感器数据的过程。
摘要由CSDN通过智能技术生成

在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;
        }
    }
}

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值