程序员开发实例大全宝库

网站首页 > 编程文章 正文

基于Modbus Rtu/Ascii上位机编写(modbus tcp上位机软件)

zazugpt 2024-08-27 00:35:13 编程文章 15 ℃ 0 评论

一、开始准备:

1、VisualStudio2015或以上版本;

2、Modbus测试软件ModbusSlave;

3、虚拟串口工具VSPD;

4、以上软件大家皆可百度自行搜索;

二、Modbus 协议介绍:

控制器能设置为两种传输模式(ASCII或RTU)中的任何一种在标准的Modbus网络通信。用户选择想要的模式,包括串口通信参数(波特率、校验方式等),在配置每个控制器的时候,在一个Modbus网络上的所有设备都必须选择相同的传输模式和串口参数。

1、RTU发送通信报文格式如下:

地址 功能代码 数据数量 数据1 ... 数据n CRC低字节 CRC高字节

2、Ascii发送通信报文格式如下:

: 地址 功能代码 数据数量 数据1 ... 数据n LRC高字节 LRC低字节 回车 换行

3、Modbus支持的功能码:

  • 0x01 读取线圈的操作,
  • 0x02 读取离散的操作,
  • 0x03 读取寄存器的值,
  • 0x05 写一个线圈操作,
  • 0x06 写一个寄存器值,
  • 0x0F 批量写线圈操作,
  • 0x10 批量写寄存器值,

4、Modbus Rtu接收报文格式:

起始位 设备地址 功能代码 数据 CRC校验 结束符

5、4、Modbus Ascii接收报文格式:

起始位 设备地址 功能代码 数据 LRC校验 结束符

三、上位机软件编写:

1、软件界面添加如下图控件:

2、代码实现:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Modbus通讯协议
{
    public partial class Form1 : Form
    {
        SerialPort sp = new SerialPort();
        string str_Received = "";
        string words2 = "";
        byte[] byte_Received = new byte[0];//接收数据的字节数组
        int num_Byte = 0;///循环接收数据的自加1
        bool receive_Flag = true;
        Socket socket_Client; 
        public Form1()
{
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
{
            cmb_Com.SelectedIndex = 2;
            CheckForIllegalCrossThreadCalls = false;
        }
        public bool OpenSerialPort()
{            
            if (!sp.IsOpen)
            {
                sp.PortName = cmb_Com.Text;
                sp.Open();
                return true;
            }
            else
            {
                sp.Close();
                return false;
            }
        }
        /// <summary>
        /// LRC校验
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="start"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static byte[] Lrc(byte[] buffer, int start = 0, int len = 0)
        {
            if (buffer == null || buffer.Length == 0) return null;
            if (start < 0) return null;
            if (len == 0) len = buffer.Length - start;
            int length = start + len;
            if (length > buffer.Length) return null;
            byte lrc = 0;// Initial value
            for (int i = start; i < len; i++)
            {
                lrc += buffer[i];
            }
            lrc = (byte)((lrc ^ 0xFF) + 1);
            return new byte[] { lrc };
        }
        /// <summary>
        /// CRC校验
        /// </summary>
        /// <returns></returns>
        public string CRC16_Modbus(string text)
{
            //1.预置CRC寄存器为0xFFFF
            UInt16 CRC = 0xFFFF;
            //2.分割文本框输入的16进制待校验数据
            string[] arr = text.Split(' ');
            //3.将字符串数组转换为byte数组
            byte[] Barr = new byte[arr.Length];
            for (int i = 0; i < arr.Length; i++)
            {
                Barr[i] = Convert.ToByte(arr[i], 16);
            }

            for (int j = 0; j < Barr.Length; j++)
            {
                CRC ^= Barr[j];
                for (int i = 0; i < 8; i++)
                {
                    if ((CRC & 1) == 0)
                    {
                        CRC >>= 1;
                    }
                    else
                    {
                        CRC >>= 1;
                        CRC ^= 0xA001;
                    }
                }
            }
            UInt16 Res = 0xFFFF;
            Res &= CRC;
            Res <<= 8;
            CRC >>= 8;
            Res |= CRC;
            return Convert.ToString(Res, 16).ToUpper().PadLeft(4, '0');
        }

        private void btn_Send_Click(object sender, EventArgs e)
{
            if(sp.IsOpen)
            {
                txtBox_Recive.Text = "";
                str_Received = "";
                int m1 = 0;
                if (rb_Rtu.Checked)
                {
                    string crc = CRC16_Modbus(txtBox_Send.Text);
                    string CRCH = crc.Substring(0, 2);/////取CRC检验码高字节
                    string CRCL = crc.Substring(2, 2);/////取CRC检验码低字节
                    int num1 = txtBox_Send.Text.Replace(" ", "").Length;
                    byte[] byte_Send = new byte[num1 / 2 + 2];
                    string[] str = new string[num1 / 2];
                    for (int i = 0; i < num1; i = i + 2)
                    {
                        str[m1] = (txtBox_Send.Text.Replace(" ", "")).Substring(i, 2);//除去空格再放进str数组
                        byte_Send[m1] = Convert.ToByte(str[m1], 16);
                        m1++;
                    }
                    byte_Send[byte_Send.Length - 2] = Convert.ToByte(CRCH, 16);
                    byte_Send[byte_Send.Length - 1] = Convert.ToByte(CRCL, 16);
                    try
                    {
                        sp.DiscardOutBuffer();
                        sp.Write(byte_Send, 0, byte_Send.Length);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.ToString());
                    }
                }
                else
                {
                    int Mylenth = txtBox_Send.Text.Replace(" ", "").Length;
                    byte[] byte_Send = new byte[Mylenth + 5];
                    string[] arr = txtBox_Send.Text.Split(' ');
                    string[] str = new string[Mylenth];
                    char[] chr = new char[Mylenth];
                    //3.将字符串数组转换为byte数组
                    byte[] byte_Send_tmp = new byte[arr.Length];
                    for (int i = 0; i < arr.Length; i++)
                    {
                        byte_Send_tmp[i] = Convert.ToByte(arr[i], 16);
                    }
                    byte[] byte_Check = Lrc(byte_Send_tmp);
                    string Check = Convert.ToString(byte_Check[0], 16).ToUpper();
                    for (int i = 0; i < Mylenth; i++)
                    {
                        str[i] = (txtBox_Send.Text.Replace(" ", "")).Substring(i, 1);//除去空格再放进str数组
                        chr[i] = Convert.ToChar(str[i]);
                        short ich = (short)chr[i]; ////转换成ASCII码
                        byte_Send[i + 1] = Convert.ToByte(ich.ToString());
                    }
                    byte_Send[0] = Convert.ToByte(58);/////最低各字节数组放ASCII码自动以十进制加分号到接收那边会转换成16进制
                    byte_Send[Mylenth + 1] = Convert.ToByte(Check[0]);///检验码高位
                    byte_Send[Mylenth + 2] = Convert.ToByte(Check[1]);///检验码低位
                    byte_Send[Mylenth + 3] = Convert.ToByte(13);/////回车
                    byte_Send[Mylenth + 4] = Convert.ToByte(10);////换行
                    sp.DiscardOutBuffer();
                    sp.Write(byte_Send, 0, byte_Send.Length);/////////现在可以发送ASCII数据了
                }
            }
            else
            {
                MessageBox.Show("串口未打开!");
            }
        }

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
            byte[] byte_Received = new byte[2048];//定义一个接收的数组宁可大不可小
            while (sp.BytesToRead > 0) //如果接收缓冲区还有数据就一直接收直到没有数据
            {
                int a = sp.ReadByte();//读取当前接收缓冲区的字节数数量
                byte_Received[num_Byte] = Convert.ToByte(a);/////读取过来到字节数组中
                string ss = Convert.ToString(byte_Received[num_Byte], 16).ToUpper();///转换成16进制并且以大写形式给到SS中
                str_Received = str_Received + ss.PadLeft(2, '0') + " "; ///每个字节保持两个字符               
                num_Byte++;/////接着接收下一个字节
            }
            //str_Received = sp.ReadLine();
        }
        private void timer1_Tick(object sender, EventArgs e)
{
            txtBox_Recive.Text = str_Received;
            num_Byte = 0;
        }

        private void btn_Conn_Com_Click(object sender, EventArgs e)
{
            bool flag_Port = OpenSerialPort();
            if (flag_Port)
            {
                toolStripLabel1.Text = "Port is Open";
                toolStrip1.ForeColor = Color.Green;
                btn_Conn_Com.Text = "关闭串口";
                sp.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);
            }
            else
            {
                sp.Close();
                toolStripLabel1.Text = "Port is Close";
                toolStrip1.ForeColor = Color.Red;
                btn_Conn_Com.Text = "打开串口";
            }
            timer1.Interval = 200;
            timer1.Enabled = true;
        }
   }
}

2、串口测试软件Modbus Slave设置如下:


3、开始测试:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表