台达(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-D9999 | 0-9999 | 데이터레지스터,유지 레지스터 |
| M0-M9999 | 0-9999 | 辅助릴레이,코일 |
| Y0-Y255 | 0-255(偏移2048) | 출력릴레이,코일 |
| X0-X255 | 0-255(偏移2048) | 입력릴레이,이산 입력 |
| S0-S999 | 0-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데이터,确认通讯正常后再통합到实际项目中。
답글 작성