델타 PLC 와의 Modbus 통신을 위한 C # 기반 호스트 인스턴스

유료 ¥8.99유료 ¥8.99

이 콘텐츠는 회원/유료 시스템과 연동되어 있으며, 잠금 해제 후 전체 자료를 보거나 첨부파일을 다운로드할 수 있습니다.

보기解锁방식
델타 PLC 와의 Modbus 통신을 위한 C # 기반 호스트 인스턴스

台达(Delta)PLC是国内산업 자동화领域적용最广泛的PLC品牌之一,支持Modbus RTU和Modbus TCP통신 프로토콜。C#作为Windows平台上位机개발的主流언어,与台达PLC的Modbus通讯是산업 자동화项目中的常见需求。本文提供完整的C#上位机与台达PLC Modbus통신实例,包括연결管理、레지스터读写、데이터분석、断线重连、多设备管理以及Windows Forms界面예제。

一、台达PLC Modbus通讯개요

1.1 台达PLC支持的Modbus功能

기능 코드功能台达PLC对应区域
0x01读코일M(辅助릴레이)、Y(출력릴레이)
0x02读이산 입력X(입력릴레이)
0x03读유지 레지스터D(데이터레지스터)
0x05写单个코일M、Y
0x06写单个레지스터D
0x0F写多个코일M、Y
0x10写多个레지스터D

1.2 台达PLC주소映射

台达PLC的Modbus주소与内部软元件的对应关系(以DVP시리즈为例):

软元件Modbus주소(10진수)설명
D0-D99990-9999데이터레지스터,유지 레지스터
M0-M99990-9999辅助릴레이,코일
Y0-Y2550-255(偏移2048)출력릴레이,코일
X0-X2550-255(偏移2048)입력릴레이,이산 입력
S0-S9990-999(偏移4096)状态릴레이,코일
T0-T255触点1024-1279定时器触点状态,코일(기능 코드01/05)
C0-C255触点1536-1791计数器触点状态,코일(기능 코드01/05)
T/C当前值视型号而定部分型号支持通过D레지스터 매핑읽기,需查阅对应手册

1.3 通讯매개변수设置

  • Modbus TCP:PLC IP주소 + 포트502,슬레이브 주소默认1
  • Modbus RTU(COM1):9600bps,8데이터 비트,패리티 없음,1정지 비트(默认)
  • Modbus RTU(COM2):9600bps,8데이터 비트,패리티 없음,1정지 비트(默认)
  • 슬레이브 주소:可在PLC中设置,默认1
  • 통신 프로토콜:需在PLC编程软件中设置为Modbus RTU Slave或Modbus TCP

二、C# Modbus通讯类实现

2.1 Modbus TCP클라이언트类

using System;
using System.Net.Sockets;
using System.Threading;

namespace DeltaPlcModbus
{
    public class ModbusTcpClient : IDisposable
    {
        private TcpClient _client;
        private NetworkStream _stream;
        private ushort _transactionId = 0;
        private readonly object _lock = new object();

        public string Ip주소 { get; }
        public int Port { get; }
        public byte SlaveId { get; set; } = 1;
        public int Timeout { get; set; } = 3000;
        public bool IsConnected => _client?.Connected ?? false;

        public ModbusTcpClient(string ip, int port = 502)
        {
            Ip주소 = ip;
            Port = port;
        }

        /// <summary>
        /// 연결PLC(带重试)
        /// </summary>
        public bool Connect(int retryCount = 3)
        {
            for (int i = 0; i < retryCount; i++)
            {
                try
                {
                    _client = new TcpClient();
                    _client.ReceiveTimeout = Timeout;
                    _client.SendTimeout = Timeout;
                    _client.Connect(Ip주소, Port);
                    _stream = _client.GetStream();
                    Console.WriteLine($"已연결到PLC:{Ip주소}:{Port}");
                    return true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"연결失败(第{i + 1}次):{ex.Message}");
                    Thread.Sleep(1000);
                }
            }
            return false;
        }

        /// <summary>
        /// 断开연결
        /// </summary>
        public void Disconnect()
        {
            _stream?.닫기();
            _client?.닫기();
            _client = null;
            _stream = null;
        }

        /// <summary>
        /// 读유지 레지스터(기능 코드03)- 읽기D레지스터
        /// </summary>
        public ushort[] ReadHolding회원가입s(ushort 시작Addr, ushort count)
        {
            lock (_lock)
            {
                byte[] request = BuildReadRequest(0x03, 시작Addr, count);
                byte[] response = SendAndReceive(request);

                if (response[7] != 0x03)
                    throw new ModbusException(response[7], response[8]);

                byte byteCount = response[8];
                ushort[] result = new ushort[byteCount / 2];
                for (int i = 0; i < result.Length; i++)
                {
                    result[i] = (ushort)((response[9 + i * 2] << 8) | response[9 + i * 2 + 1]);
                }
                return result;
            }
        }

        /// <summary>
        /// 读코일(기능 코드01)- 읽기M/Y릴레이
        /// </summary>
        public bool[] ReadCoils(ushort 시작Addr, ushort count)
        {
            lock (_lock)
            {
                byte[] request = BuildReadRequest(0x01, 시작Addr, count);
                byte[] response = SendAndReceive(request);

                if (response[7] != 0x01)
                    throw new ModbusException(response[7], response[8]);

                byte byteCount = response[8];
                bool[] result = new bool[count];
                for (int i = 0; i < count; i++)
                {
                    result[i] = (response[9 + i / 8] & (1 << (i % 8))) != 0;
                }
                return result;
            }
        }

        /// <summary>
        /// 读이산 입력(기능 코드02)- 읽기X입력
        /// </summary>
        public bool[] ReadDiscreteInputs(ushort 시작Addr, ushort count)
        {
            lock (_lock)
            {
                byte[] request = BuildReadRequest(0x02, 시작Addr, count);
                byte[] response = SendAndReceive(request);

                if (response[7] != 0x02)
                    throw new ModbusException(response[7], response[8]);

                byte byteCount = response[8];
                bool[] result = new bool[count];
                for (int i = 0; i < count; i++)
                {
                    result[i] = (response[9 + i / 8] & (1 << (i % 8))) != 0;
                }
                return result;
            }
        }

        /// <summary>
        /// 写单个레지스터(기능 코드06)- 写D레지스터
        /// </summary>
        public void WriteSingle회원가입(ushort addr, ushort value)
        {
            lock (_lock)
            {
                byte[] request = new byte[12];
                BuildMBAPHeader(request, 6);
                request[7] = 0x06;
                request[8] = (byte)(addr >> 8);
                request[9] = (byte)(addr & 0xFF);
                request[10] = (byte)(value >> 8);
                request[11] = (byte)(value & 0xFF);

                byte[] response = SendAndReceive(request);
                if (response[7] != 0x06)
                    throw new ModbusException(response[7], response[8]);
            }
        }

        /// <summary>
        /// 写单个코일(기능 코드05)- 写M/Y릴레이
        /// </summary>
        public void WriteSingleCoil(ushort addr, bool value)
        {
            lock (_lock)
            {
                byte[] request = new byte[12];
                BuildMBAPHeader(request, 6);
                request[7] = 0x05;
                request[8] = (byte)(addr >> 8);
                request[9] = (byte)(addr & 0xFF);
                request[10] = (byte)(value ? 0xFF : 0x00);
                request[11] = 0x00;

                byte[] response = SendAndReceive(request);
                if (response[7] != 0x05)
                    throw new Mod bus Ex ception (res ponse [7 ], response [8]);
            }
        }

        /// >
        /// 쓰 기 다 중 레 지 스터 (기 능 코드 16) - D 레 지 스터 를 대 량 쓰 기
        /// >
        public vo id Write M ulti ple Reg ister s (ush ort 시작 Add r , ush ort [ ] values)
        {
            잠 금 (_ lock)
            {
                by te by te Co unt = (byte) (valu es . Leng th * 2) ;
                by te [ ] request = new by te [ 13 + by te Co unt ];
                Bu ild M BA P He ader (re quest , (ush ort) ( 7 + by te Co unt));
                요청 [7] = 0 x 10 ;
                request[8] = (byte)(시작Addr 
                request[9] = (byte)(시작Addr 
                request[10] = (byte)(values.Length 
                request[11] = (byte)(values.Length 
                request [11] = by te Co unt ;

                for (int i = 0; i 
                {
                    request[13 + i * 2] = (byte)(values[i] 
                    request[13 + i * 2 + 1] = (byte)(values[i] 
                }

                by te [ ] response = Send And Re ce ive (re quest);
                if (res ponse [7] ! =) 0 x 10)
                    throw new Mod bus Ex ception (res ponse [7 ], response [8]);
            }
        }

        /// >
        / / / 여러 코 일 쓰 기 (기 능 코드 15) - M / Y 릴 레이 대 량 쓰 기
        /// >
        public vo id Write M ulti ple Co ils (ush ort 시작 Add r , bool [ ] values)
        {
            잠 금 (_ lock)
            {
                by te by te Co unt = (byte) (( values . Leng th + 7) / 8);
                by te [ ] request = new by te [ 13 + by te Co unt ];
                Bu ild M BA P He ader (re quest , (ush ort) ( 7 + by te Co unt));
                요청 [7] = 0 x 0 F ;
                request[8] = (byte)(시작Addr 
                request[9] = (byte)(시작Addr 
                request[10] = (byte)(values.Length 
                request[11] = (byte)(values.Length 
                request [11] = by te Co unt ;
                
                for (int i = 0; i 
                {
                    if (val ue [ i ])
                        요청 [ 13 + i / 8 ]|= (바 이트) ( 1 (i % 8));<<
                }
                
                by te [ ] response = Send And Re ce ive (re quest);
                if (res ponse [7] ! =) 0 x 0 F)
                    throw new Mod bus Ex ception (res ponse [7 ], response [8]);
            }
        }
        
        읽기 요청 만들기
        private by te [ ] Bu ild Read Re quest (byte fuk C ode , ush ort 시작 Add r , ush ort count)
        {
            by te [ ] request = new by te [12] ;
            Bu ild M BA P He ader (re quest , 6) ;
            request [7] = function C ode ;
            request[8] = (byte)(시작Addr 
            request[9] = (byte)(시작Addr 
            request[10] = (byte)(count 
            request[11] = (byte)(count 
            반환 요청 ;
        }

        / / M BA P 헤 더 생성
        private vo id Bu ild M BA P He ader (byte [ ] bu ffer , ush ort length)
        {
            _ tr ansa ction Id ++ ;
            buffer[0] = (byte)(_transactionId 
            buffer[1] = (byte)(_transactionId 
            버 퍼 [2] = 0 x 00 ;
            bu ffer [3] = 0 x 00 ;
            buffer[4] = (byte)(length 
            buffer[5] = (byte)(length 
            bu ffer [6] = Sla ve Id ;
        }

        보내 기 및 수신 하기
        private by te [ ] Send And Re ce ive (byte [ ] request)
        {
            if (_ stream == null )||  !_클 라이언 트 . 접속 됨)
                throw new In valid O per ation Ex ception (" P LC 에 연결 되지 않 음 ")

            _ stream . Write (re quest , 0, request . Leng th);
            _ stream . fl ush () ;

            by te [ ] bu ffer = new by te [2 60 ];
            int by tes Read = _ stream . Read (bu ffer , 0, bu ffer . Leng th);

            by te [ ] response = new by te [ byt es Read ];
            Ar ray . 복사 (bu ffer , response , by tes Read);
            응답 을 반환 합니다 ;
        }

        public vo id Dis pose ()
        {
            연결 끊 기 ();
        }
    }

    public 클래 스 ModbusException : Exception
    {
        public byte 기능Code { get; }
        public byte ExceptionCode { get; }

        public ModbusException(byte funcCode, byte exceptionCode)
            : base($"Modbus异常:기능 코드=0x{funcCode:X2},异常码=0x{exceptionCode:X2}")
        {
            기능Code = funcCode;
            ExceptionCode = exceptionCode;
        }
    }
}

2.2 台达PLC专用操作类

using System;

namespace DeltaPlcModbus
{
    /// <summary>
    /// 台达PLC专用操作类,封装D/M/X/Y软元件的读写
    /// </summary>
    public class DeltaPlcClient
    {
        private readonly ModbusTcpClient _modbus;

        public DeltaPlcClient(string ip, int port = 502, byte slaveId = 1)
        {
            _modbus = new ModbusTcpClient(ip, port);
            _modbus.SlaveId = slaveId;
        }

        public bool Connect() => _modbus.Connect();
        public void Disconnect() => _modbus.Disconnect();
        public bool IsConnected => _modbus.IsConnected;

        /// <summary>
        /// 읽기D레지스터 값
        /// </summary>
        public ushort ReadD(int 주소)
        {
            return _modbus.ReadHolding회원가입s((ushort)주소, 1)[0];
        }

        /// <summary>
        /// 批量읽기D레지스터
        /// </summary>
        public ushort[] ReadD(int 시작주소, int count)
        {
            return _modbus.ReadHolding회원가입s((ushort)시작주소, (ushort)count);
        }

        /// <summary>
        /// 쓰기D레지스터 값
        /// </summary>
        public void WriteD(int 주소, ushort value)
        {
            _modbus.WriteSingle회원가입((ushort)주소, value);
        }

        /// <summary>
        /// 批量쓰기D레지스터
        /// </summary>
        public void WriteD(int 시작주소, ushort[] values)
        {
            _modbus.WriteMultiple회원가입s((ushort)시작주소, values);
        }

        /// <summary>
        /// 읽기32位整数(占用2个D레지스터)
        /// </summary>
        public int ReadD32(int 시작주소)
        {
            ushort[] regs = _modbus.ReadHolding회원가입s((ushort)시작주소, 2);
            return (regs[0] << 16) | regs[1];
        }

        /// <summary>
        /// 읽기32位浮点数(占用2个D레지스터,ABCD大端序)
        /// </summary>
        public float ReadDFloat(int 시작주소)
        {
            ushort[] regs = _modbus.ReadHolding회원가입s((ushort)시작주소, 2);
            // ABCD大端序:高字在前,高바이트在前
            uint bits = ((uint)regs[0] << 16) | regs[1];
            byte[] bytes = BitConverter.GetBytes(bits);
            // BitConverter.GetBytes返回的是小端序数组,需要反转
            if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
            return BitConverter.ToSingle(bytes, 0);
        }
        
        /// <summary>
        /// 읽기32位浮点数(CDAB字交换序)
        /// </summary>
        public float ReadDFloatCDAB(int 시작주소)
        {
            ushort[] regs = _modbus.ReadHolding회원가입s((ushort)시작주소, 2);
            // CDAB:低字在前,高바이트在前
            uint bits = ((uint)regs[1] << 16) | regs[0];
            byte[] bytes = BitConverter.GetBytes(bits);
            if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
            return BitConverter.ToSingle(bytes, 0);
        }

        /// <summary>
        /// 읽기M辅助릴레이状态
        /// </summary>
        public bool ReadM(int 주소)
        {
            return _modbus.ReadCoils((ushort)주소, 1)[0];
        }

        /// <summary>
        /// 批量읽기M辅助릴레이
        /// </summary>
        public bool[] ReadM(int 시작주소, int count)
        {
            return _modbus.ReadCoils((ushort)시작주소, (ushort)count);
        }

        /// <summary>
        /// 쓰기M辅助릴레이
        /// </summary>
        public void WriteM(int 주소, bool value)
        {
            _modbus.WriteSingleCoil((ushort)주소, value);
        }

        /// <summary>
        /// 읽기X입력릴레이状态
        /// 注意:X입력在台达PLC中Modbus주소偏移为2048
        /// </summary>
        public bool ReadX(int 주소)
        {
            return _modbus.ReadDiscreteInputs((ushort)(주소 + 2048), 1)[0];
        }

        /// <summary>
        /// 읽기Y출력릴레이状态
        /// 注意:Y출력在台达PLC中Modbus주소偏移为2048
        /// </summary>
        public bool ReadY(int 주소)
        {
            return _modbus.ReadCoils((ushort)(주소 + 2048), 1)[0];
        }

        /// <summary>
        /// 쓰기Y출력릴레이
        /// </summary>
        public void WriteY(int 주소, bool value)
        {
            _modbus.WriteSingleCoil((ushort)(주소 + 2048), value);
        }

        /// <summary>
        /// 읽기定时器当前值(T레지스터,偏移1024)
        /// </summary>
        public ushort ReadT(int 주소)
        {
            return _modbus.ReadHolding회원가입s((ushort)(주소 + 1024), 1)[0];
        }

        /// <summary>
        /// 읽기计数器当前值(C레지스터,偏移1536)
        /// </summary>
        public ushort ReadC(int 주소)
        {
            return _modbus.ReadHolding회원가입s((ushort)(주소 + 1536), 1)[0];
        }
    }
}

三、完整使用예제

using System;
using System.Threading;

namespace DeltaPlcModbus
{
    class Program
    {
        static void Main(string[] args)
        {
            // 创建台达PLC클라이언트
            var plc = new DeltaPlcClient("192.168.1.10", 502, slaveId: 1);

            try
            {
                // 연결PLC
                if (!plc.Connect())
                {
                    Console.WriteLine("无法연결到PLC");
                    return;
                }
                Console.WriteLine("PLC연결成功!\n");

                // 1. 읽기D레지스터
                Console.WriteLine("=== 읽기D레지스터 ===");
                ushort d0 = plc.ReadD(0);
                ushort d1 = plc.ReadD(1);
                ushort d2 = plc.ReadD(2);
                Console.WriteLine($"D0 = {d0}");
                Console.WriteLine($"D1 = {d1}");
                Console.WriteLine($"D2 = {d2}");

                // 批量읽기
                ushort[] dValues = plc.ReadD(0, 10);
                Console.WriteLine("\nD0-D9:");
                for (int i = 0; i < dValues.Length; i++)
                {
                    Console.WriteLine($"  D{i} = {dValues[i]}");
                }

                // 2. 쓰기D레지스터
                Console.WriteLine("\n=== 쓰기D레지스터 ===");
                plc.WriteD(10, 1234);
                Console.WriteLine("D10 쓰기 1234");
                ushort check = plc.ReadD(10);
                Console.WriteLine($"D10 回读 = {check}");

                // 批量쓰기
                plc.WriteD(20, new ushort[] { 100, 200, 300, 400, 500 });
                Console.WriteLine("D20-D24 批量쓰기完成");

                // 3. 읽기32位데이터
                Console.WriteLine("\n=== 읽기32位데이터 ===");
                int d32 = plc.ReadD32(0);
                Console.WriteLine($"D0-D1(32位整数)= {d32}");

                // 4. 읽기M릴레이
                Console.WriteLine("\n=== 읽기M릴레이 ===");
                bool m0 = plc.ReadM(0);
                bool m1 = plc.ReadM(1);
                Console.WriteLine($"M0 = {(m0 ? "ON" : "OFF")}");
                Console.WriteLine($"M1 = {(m1 ? "ON" : "OFF")}");

                // 批量읽기
                bool[] mValues = plc.ReadM(0, 8);
                Console.WriteLine("\nM0-M7:");
                for (int i = 0; i < mValues.Length; i++)
                {
                    Console.WriteLine($"  M{i} = {(mValues[i] ? "ON" : "OFF")}");
                }

                // 5. 쓰기M릴레이
                Console.WriteLine("\n=== 쓰기M릴레이 ===");
                plc.WriteM(0, true);
                Console.WriteLine("M0 置为 ON");
                Thread.Sleep(500);
                plc.WriteM(0, false);
                Console.WriteLine("M0 置为 OFF");

                // 6. 읽기X입력
                Console.WriteLine("\n=== 읽기X입력 ===");
                bool x0 = plc.ReadX(0);
                bool x1 = plc.ReadX(1);
                Console.WriteLine($"X0 = {(x0 ? "ON" : "OFF")}");
                Console.WriteLine($"X1 = {(x1 ? "ON" : "OFF")}");

                // 7. 읽기Y출력
                Console.WriteLine("\n=== 읽기Y출력 ===");
                bool y0 = plc.ReadY(0);
                Console.WriteLine($"Y0 = {(y0 ? "ON" : "OFF")}");

                // 8. 控制Y출력
                Console.WriteLine("\n=== 控制Y출력 ===");
                plc.WriteY(0, true);
                Console.WriteLine("Y0 置为 ON");
                Thread.Sleep(1000);
                plc.WriteY(0, false);
                Console.WriteLine("Y0 置为 OFF");

                // 9. 읽기定时器/计数器触点状态
                Console.WriteLine("\n=== 읽기定时器/计数器触点 ===");
                bool t0 = plc.ReadT연락처(0);
                bool c0 = plc.ReadC연락처(0);
                Console.WriteLine($"T0 触点 = {(t0 ? "ON" : "OFF")}");
                Console.WriteLine($"C0 触点 = {(c0 ? "ON" : "OFF")}");
                Console.WriteLine("注:定时器/计数器当前值请查阅对应型号手册的Modbus映射");

                // 10. 持续监控(10秒)
                Console.WriteLine("\n=== 持续监控D0-D3(10秒)===");
                for (int i = 0; i < 10; i++)
                {
                    ushort[] monitor = plc.ReadD(0, 4);
                    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] D0={monitor[0]}, D1={monitor[1]}, D2={monitor[2]}, D3={monitor[3]}");
                    Thread.Sleep(1000);
                }
            }
            catch (ModbusException ex)
            {
                Console.WriteLine($"Modbus异常:{ex.Message}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"系统异常:{ex.Message}");
            }
            finally
            {
                plc.Disconnect();
                Console.WriteLine("\nPLC연결已닫기");
            }

            Console.WriteLine("\n按任意键退出...");
            Console.ReadKey();
        }
    }
}

四、Windows Forms上位机界面예제

using System;
using System.Windows.Forms;
using System.Threading;

namespace DeltaPlcModbus
{
    public partial class MainForm : Form
    {
        private DeltaPlcClient _plc;
        private Thread _monitorThread;
        private volatile bool _monitoring = false;

        public MainForm()
        {
            InitializeComponent();
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                _plc = new DeltaPlcClient(txtIp.Text, int.Parse(txtPort.Text), byte.Parse(txtSlaveId.Text));
                if (_plc.Connect())
                {
                    lblStatus.Text = "已연결";
                    lblStatus.ForeColor = System.Drawing.Color.Green;
                    btnConnect.Enabled = false;
                    btnDisconnect.Enabled = true;
                    _monitoring = true;
                    _monitorThread = new Thread(MonitorLoop);
                    _monitorThread.IsBackground = true;
                    _monitorThread.Start();
                }
                else
                {
                    MessageBox.Show("연결失败!");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"연결异常:{ex.Message}");
            }
        }

        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            _monitoring = false;
            _plc?.Disconnect();
            lblStatus.Text = "연결되지 않음";
            lblStatus.ForeColor = System.Drawing.Color.Red;
            btnConnect.Enabled = true;
            btnDisconnect.Enabled = false;
        }

        private void MonitorLoop()
        {
            while (_monitoring && _plc.IsConnected)
            {
                try
                {
                    // 읽기D0-D9
                    ushort[] values = _plc.ReadD(0, 10);
                    // 읽기M0-M7
                    bool[] mValues = _plc.ReadM(0, 8);

                    // 更新UI(跨线程调用)
                    this.Invoke(new Action(() =>
                    {
                        txtD0.Text = values[0].ToString();
                        txtD1.Text = values[1].ToString();
                        txtD2.Text = values[2].ToString();
                        txtD3.Text = values[3].ToString();
                        lblM0.Text = mValues[0] ? "ON" : "OFF";
                        lblM1.Text = mValues[1] ? "ON" : "OFF";
                        lblM2.Text = mValues[2] ? "ON" : "OFF";
                        lblM3.Text = mValues[3] ? "ON" : "OFF";
                    }));
                }
                catch
                {
                    // 읽기失败,忽略
                }
                Thread.Sleep(500);
            }
        }

        private void btnWriteD_Click(object sender, EventArgs e)
        {
            try
            {
                int addr = int.Parse(txtWriteAddr.Text);
                ushort value = ushort.Parse(txtWriteValue.Text);
                _plc.WriteD(addr, value);
                MessageBox.Show($"D{addr} 쓰기 {value} 成功");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"쓰기失败:{ex.Message}");
            }
        }

        private void btnM0On_Click(object sender, EventArgs e)
        {
            _plc.WriteM(0, true);
        }

        private void btnM0Off_Click(object sender, EventArgs e)
        {
            _plc.WriteM(0, false);
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            _monitoring = false;
            _plc?.Disconnect();
            base.OnFormClosing(e);
        }
    }
}

五、常见문제与排查

5.1 연결失败

  • 检查IP주소:确认PLC的IP주소设置正确,与电脑在同一网段
  • 检查矶钓:台达PLC Modbus TCP默认矶钓502,确认未被防火墙拦截
  • 检查网线:确认网线연결正常,PLC网口指示灯亮
  • 检查PLC设置:在ISPSoft中确认PLC已활성화Modbus TCP通讯
  • Ping测试:在命令行中ping PLC的IP주소,确认네트워크连通

5.2 읽기데이터为0或异常

  • 주소错误:确认Modbus주소与PLC软元件주소的对应关系,注意偏移量
  • 슬레이브 주소错误:确认SlaveId与PLC中设置的一致
  • 기능 코드不支持:部分台达PLC型号可能不支持某些기능 코드
  • 주소越界:确认읽기的주소范围在PLC支持的范围内
  • PLC未运行:确认PLC处于RUN모드,STOP모드下部分주소不可读

5.3 쓰기不生效

  • 레지스터被程序覆盖:PLC程序中可能正在쓰기该주소,导致쓰기被覆盖
  • 주소只读:部分레지스터(如X입력)是只读的,不能쓰기
  • 权限限制:部分PLC需要设置通讯쓰기权限
  • 데이터유형错误:确认쓰기的데이터유형与PLC中定义的一致

六、最佳实践

  • 批量읽기:尽量一次읽기多个连续레지스터,减少通讯次数,提高效率
  • 合理轮询间隔:建议轮询间隔不未来100ms,避免过快轮询导致PLC通讯过载
  • 断线重连:추가自动重连机制,네트워크恢复后自动恢复通讯
  • 超时设置:合理设置通讯超时,建议2-3秒
  • 异常处理:捕获ModbusException,区分不同异常码进行处理
  • 线程安全:多线程访问时使用lock保护,避免并发冲突
  • 로그记录:记录通讯로그,便于排查문제
  • 주소规划:提前规划D/M레지스터的使用,避免주소冲突

本文提供的C#代码实现了台达PLC的完整Modbus TCP通讯功能,包括D레지스터、M릴레이、X입력、Y출력、定时器、计数器的读写操作,以及Windows Forms上位机界面예제。代码可直接用于산업 자동화项目개발,建议先使用台达ISPSoft软件监控PLC데이터,确认通讯正常后再통합到实际项目中。

실제 프로젝트에 이 자료를 활용하시겠습니까?

도구 센터에서 프레임 분석, CRC 검증, 장비 디버깅을 진행하거나 요청을 제출하여 선정 및 연동 제안을 받아보세요.

엔지니어 회원

이 글을 실행 가능한 디버깅 자료로 활용하세요

가입하면 고급 프레임 분석, 자료 패키지 다운로드, 코드 예제, 엔지니어링 사례, 우선 기술 지원을 이용할 수 있어 실제 프로젝트 납품에 적합합니다.

고급 도구 무제한 이용
자료 패키지와 코드 패키지
완전한 엔지니어링 사례 라이브러리
우선 기술 지원 채널

답글 작성

이메일 주소는 공개되지 않습니다. 必填项已用 * 标注