C#与松下PLC进行MODBUS_RTU通讯

这个代码示例展示了如何使用C#实现MODBUS_RTU协议来与PLC进行通信。主要功能包括配置串口参数、读取和发送数据、读取和设置DT寄存器及R内部继电器的数据。CRC校验用于确保数据的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;

namespace xxxxx
{
class MODBUS_RTU
{
SerialPort serialPort;
object obj;//用来接收寄存器的数据,因为寄存器的数据有3种情况,所以不能直接用byte接收
//仅提供给monitorThread线程使用,多线程访问数据可能会发生错误
public byte r_data;//线圈的数据
public float dt_Fdata;//2个寄存器的数据(小数)
public short dt_Int16;//单个寄存器的数据
public int dt_Int32;//2个寄存器的数据(整数)

    public void ComConfig()
    {
        if (serialPort != null) return;
        serialPort = new SerialPort();
        serialPort.PortName = "COM7";
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.StopBits = StopBits.One;
        serialPort.Parity = Parity.None;
        serialPort.Open();
    }

    #region PLC读写
    /// <summary>
    /// 从缓冲区拿数据
    /// </summary>
    /// <returns></returns>
    public byte[] GetSerialPortData(int dataLength)
    {
        while (true)
        {
            if (serialPort.BytesToRead == dataLength)
            {
                int reallength = serialPort.BytesToRead;
                byte[] refbyte = new byte[reallength];
                serialPort.Read(refbyte, 0, reallength);
                return refbyte;
            }
        }
    }

    /// <summary>
    /// 发送数据
    /// </summary>
    /// <param name="sendbyte"></param>
    /// <returns></returns>
    public bool ComSend(byte[] sendbyte)
    {
        lock (this)
        {
            try
            {
                serialPort.DiscardInBuffer();
                serialPort.DiscardOutBuffer();
                serialPort.Write(sendbyte, 0, sendbyte.Length);
                //Thread.Sleep(10);
            }
            catch
            {
                //System.Windows.Forms.MessageBox.Show("下位机连接异常,发送数据失败!", "连接异常");
                return false;
            }
            return true;
        }
    }

    /// <summary>
    /// 读取DT寄存器数据数据
    /// </summary>
    /// <param name="station">PLC地址</param>
    /// <param name="addres">数据地址</param>
    /// <param name="addres_cout">数据地址个数</param>
    // dataType:0,int32;1,float,只有在addres_cout为2的时候才生效
    public void DT_ReadData(int station, int addres, int addres_cout, int dataType)
    {
        lock (this)
        {
            if (!serialPort.IsOpen) return;
            int dataLength = addres_cout * 2 + 5;//返回的数据长度
            #region 发送数据
            //松下PLC的数据在发送前要做右移和异或校验
            byte g_add = (byte)(addres >> 8);
            byte d_add = (byte)(addres & 0xFF);
            byte g_data = (byte)(addres_cout >> 8);
            byte d_data = (byte)(addres_cout & 0xFF);

            byte[] L_Data = new byte[] { (byte)station, 0x03, g_add, d_add, g_data, d_data };
            byte[] crcdata = RsCrc(L_Data);
            byte[] L_DL = new byte[] { (byte)station, 0x03, g_add, d_add, g_data, d_data, crcdata[0], crcdata[1] };
            ComSend(L_DL);
            #endregion

            #region 接收数据
            byte[] readData = GetSerialPortData(dataLength);
            if (readData == null) return;
            if (addres_cout == 1)
            {
                //读取单个寄存器
                byte[] k = { readData[4], readData[3] };
                dt_Int16 = System.BitConverter.ToInt16(k, 0);
            }
            else if (addres_cout == 2)
            {
                //读取2个寄存器
                byte[] k2 = { readData[4], readData[3], readData[6], readData[5] };
                if (dataType == 0)
                {
                    dt_Int32 = BitConverter.ToInt32(k2, 0);
                }
                if (dataType == 1)
                {
                    dt_Fdata = BitConverter.ToSingle(k2, 0);
                }
            }
            #endregion
        }
    }
    /// <summary>
    /// 设置单个DT寄存器数据
    /// </summary>
    /// <param name="station">PLC地址</param>
    /// <param name="addres">数据地址</param>
    /// <param name="ata">数据</param>
    /// <returns></returns>
    public void DT_SetData(int station, int addres, int data)
    {
        if (!serialPort.IsOpen) return;
        byte g_add = (byte)(addres >> 8);
        byte d_add = (byte)(addres & 0xFF);
        byte g_data = (byte)(data >> 8);
        byte d_data = (byte)(data & 0xFF);

        byte[] L_Data = new byte[] { (byte)station, 0x06, g_add, d_add, g_data, d_data };
        byte[] crcdata = RsCrc(L_Data);
        byte[] senddata = new byte[] { (byte)station, 0x06, g_add, d_add, g_data, d_data, crcdata[0], crcdata[1] };
        ComSend(senddata);
        //Thread.Sleep(20);
    }

    /// <summary>
    /// 读取R内部继电器(单线圈)数据
    /// </summary>
    /// <param name="station">PLC地址</param>
    /// <param name="addres">数据地址</param>
    /// <param name="wei">地址位</param>
    /// <param name="addres_cout">读取个数</param>
    /// <returns></returns>
    public void R_ReadData(int station, int addres, int wei, int addres_cout)
    {
        lock(this)
        {
            if (!serialPort.IsOpen) return;
            #region 发送数据
            addres = addres * 16 + 2048 + wei;

            byte g_add = (byte)(addres >> 8);
            byte d_add = (byte)(addres & 0xFF);
            byte g_data = (byte)(addres_cout >> 8);
            byte d_data = (byte)(addres_cout & 0xFF);

            byte[] L_Data = new byte[] { (byte)station, 0x01, g_add, d_add, g_data, d_data };
            byte[] crcdata = RsCrc(L_Data);

            ComSend(new byte[] { (byte)station, 0x01, g_add, d_add, g_data, d_data, crcdata[0], crcdata[1] });
            #endregion
            //接收到数据才会出来,所以这里不用加延时
            byte[] readData = GetSerialPortData(6);
            if (readData == null) return;
            r_data = readData[3];   
        }
    }

    public void R_GetManyData()
    {
        #region 获取多个线圈数据
        //地址中有ABCDEF时需要单独获取
        int[] test = { 0, 1, 4, 5, 6, 400, 800, 1200, 1600, 2493, 2000, 2400, 2800, 3200 };
        int remainer = 0;//位数
        for (int i = 0; i < test.Length; i++)
        {
            int address = 0;//R线圈地址
            //地址小于16时,线圈地址为0,余数为它本身
            if (test[i] < 16)
            {
                address = 0;
                remainer = test[i];
            }
            else
            {
                remainer = test[i] % 10;//获取各位数
                address = test[i] / 10;//获取线圈地址
            }
            R_ReadData(1, address, remainer, 1);
            //richTextBox1.AppendText("R:" + test[i] + "\tValue:" + obj.ToString() + "\n");
        }
        #endregion
    }

    /// <summary>
    /// 写入R内部继电器(单线圈)数据
    /// </summary>
    /// <param name="station">PLC地址</param>
    /// <param name="addres">数据地址</param>
    /// <param name="wei">数据地址位</param>
    /// <param name="addres_cout">1、0
    /// </param>
    /// <returns></returns>
    public void R_SetData(int station, int addres, int wei, int value)
    {
        lock (this)
        {
            if (!serialPort.IsOpen) return;

            addres = addres * 16 + 2048 + wei;

            byte g_data;
            byte d_data = 0x00;
            if (value == 1)
            {
                g_data = 0xFF;//开1
            }
            else
            {
                g_data = 0x00; //关0
            }

            byte g_add = (byte)(addres >> 8);
            byte d_add = (byte)(addres & 0xFF);

            byte[] L_Data = new byte[] { (byte)station, 0x05, g_add, d_add, g_data, d_data };
            byte[] crcdata = RsCrc(L_Data);
            byte[] senddata = new byte[] { (byte)station, 0x05, g_add, d_add, g_data, d_data, crcdata[0], crcdata[1] };
            ComSend(senddata);
        }
    }
    #endregion

    #region CRC

    /// <summary>
    /// CRC校验计算查表法,高位在前0,低位在后1
    /// </summary>
    /// <param name="a">校验数组</param>
    /// <returns>高位在前0,低位在后1</returns>
    public static byte[] RsCrc(byte[] a)
    {
        //A    01  03  01  (E1  30)
        //B    01  03  02  (A1  31)
        //C    01  03  03  (60  F1)
        //D    01  03  04  (21  33)

        //CRC循环中的索引
        int j = 0;                          //
        byte crc_h = 0xFF;                                //高CRC字节初始化
        byte crc_l = 0xFF;                                //低CRC字节初始化

        for (int i = 0; i < a.Length; i++)
        {
            j = crc_h ^ a[i];
            crc_h = Convert.ToByte(crc_l ^ auchCRCHi[j]);
            crc_l = Convert.ToByte(auchCRCLo[j]);
        }
        return new byte[] { crc_h, crc_l };
    }


    //CRC校验表
    //===============================================
    private static byte[] auchCRCHi = {

                                        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
                                        0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
                                        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
                                        0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
                                        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
                                        0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
                                        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
                                        0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
                                        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
                                        0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
                                        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
                                        0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
                                        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
                                        0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
                                        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
                                        0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
                                        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
                                        0x40


                                      };

    private static byte[] auchCRCLo ={

                                        0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
                                        0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
                                        0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
                                        0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
                                        0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
                                        0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
                                        0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
                                        0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
                                        0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
                                        0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
                                        0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
                                        0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
                                        0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
                                        0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
                                        0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
                                        0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
                                        0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
                                        0x40
                                     };

    #endregion
}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值