台达(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 | read discrete inputs | X(输入继电器) |
| 0x03 | read holding registers | D(数据寄存器) |
| 0x05 | write single coil | M、Y |
| 0x06 | write single register | D |
| 0x0F | write multiple coils | M、Y |
| 0x10 | write multiple registers | D |
1.2 台达PLC地址映射
台达PLC的Modbus地址与内部软元件的对应关系(以DVP시리즈为例):
| 软元件 | Modbus주소(decimal) | 说明 |
|---|---|---|
| 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/Ccurrent value | 视型号而定 | 部分型号支持通过D寄存器映射读取,需查阅对应手册 |
1.3 通讯参数设置
- Modbus TCP:PLC IP주소 + 端口502,从站地址默认1
- Modbus RTU(COM1):9600bps,8数据位,无校验,1停止位(默认)
- Modbus RTU(COM2):9600bps,8数据位,无校验,1停止位(默认)
- slave 주소:可在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>
/// connectPLC(带重试)
/// </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($"连接失败(No.{i + 1}次):{ex.Message}");
Thread.Sleep(1000);
}
}
return false;
}
/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
_stream?.닫기();
_client?.닫기();
_client = null;
_stream = null;
}
/// <summary>
/// read holding registers(기능 코드03)- readDregister
/// </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)- readM/YRelay
/// </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>
/// read discrete inputs(기능 코드02)- readX输入
/// </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>
/// write single register(기능 코드06)- 写Dregister
/// </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>
/// write single coil(기능 코드05)- 写M/YRelay
/// </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},Exception Code=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>
/// readDregister value
/// </summary>
public ushort ReadD(int 주소)
{
return _modbus.ReadHolding회원가입s((ushort)주소, 1)[0];
}
/// <summary>
/// 批量读取Dregister
/// </summary>
public ushort[] ReadD(int 시작주소, int count)
{
return _modbus.ReadHolding회원가입s((ushort)시작주소, (ushort)count);
}
/// <summary>
/// 写入Dregister value
/// </summary>
public void WriteD(int 주소, ushort value)
{
_modbus.WriteSingle회원가입((ushort)주소, value);
}
/// <summary>
/// 批量写入Dregister
/// </summary>
public void WriteD(int 시작주소, ushort[] values)
{
_modbus.WriteMultiple회원가입s((ushort)시작주소, values);
}
/// <summary>
/// read32位整数(占用2个Dregister)
/// </summary>
public int ReadD32(int 시작주소)
{
ushort[] regs = _modbus.ReadHolding회원가입s((ushort)시작주소, 2);
return (regs[0] << 16) | regs[1];
}
/// <summary>
/// read32位浮点数(占用2个Dregister,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>
/// read32位浮点数(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>
/// readM辅助继电器状态
/// </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>
/// readX输入继电器状态
/// 注意:X输入在台达PLC中Modbus地址偏移为2048
/// </summary>
public bool ReadX(int 주소)
{
return _modbus.ReadDiscreteInputs((ushort)(주소 + 2048), 1)[0];
}
/// <summary>
/// readY输出继电器状态
/// 注意: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>
/// 读取定时器当前值(Tregister,偏移1024)
/// </summary>
public ushort ReadT(int 주소)
{
return _modbus.ReadHolding회원가입s((ushort)(주소 + 1024), 1)[0];
}
/// <summary>
/// 读取计数器当前值(Cregister,偏移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
{
// connectPLC
if (!plc.Connect())
{
Console.WriteLine("无法连接到PLC");
return;
}
Console.WriteLine("PLC连接成功!\n");
// 1. readDregister
Console.WriteLine("=== readDregister ===");
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. 写入Dregister
Console.WriteLine("\n=== 写入Dregister ===");
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. read32位数据
Console.WriteLine("\n=== read32位数据 ===");
int d32 = plc.ReadD32(0);
Console.WriteLine($"D0-D1(32位整数)= {d32}");
// 4. readMRelay
Console.WriteLine("\n=== readMRelay ===");
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. 写入MRelay
Console.WriteLine("\n=== 写入MRelay ===");
plc.WriteM(0, true);
Console.WriteLine("M0 置为 ON");
Thread.Sleep(500);
plc.WriteM(0, false);
Console.WriteLine("M0 置为 OFF");
// 6. readX输入
Console.WriteLine("\n=== readX输入 ===");
bool x0 = plc.ReadX(0);
bool x1 = plc.ReadX(1);
Console.WriteLine($"X0 = {(x0 ? "ON" : "OFF")}");
Console.WriteLine($"X1 = {(x1 ? "ON" : "OFF")}");
// 7. readY输出
Console.WriteLine("\n=== readY输出 ===");
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 = "Connected";
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 = "Not connected";
lblStatus.ForeColor = System.Drawing.Color.Red;
btnConnect.Enabled = true;
btnDisconnect.Enabled = false;
}
private void MonitorLoop()
{
while (_monitoring && _plc.IsConnected)
{
try
{
// readD0-D9
ushort[] values = _plc.ReadD(0, 10);
// readM0-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
{
// Read failed,忽略
}
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주소:ConfirmPLC的IP地址设置正确,与电脑在同一网段
- 检查矶钓:台达PLC Modbus TCP默认矶钓502,确认未被防火墙拦截
- 检查网线:确认网线连接正常,PLC网口指示灯亮
- 检查PLC设置:在ISPSoft中确认PLC已启用Modbus TCP通讯
- Ping测试:在命令行中ping PLC的IP주소,确认网络连通
5.2 读取数据为0或异常
- 地址错误:ConfirmModbus地址与PLC软元件地址的对应关系,注意偏移量
- 从站地址错误:ConfirmSlaveId与PLC中设置的一致
- 功能码不支持:部分台达PLC型号可能不支持某些功能码
- 地址越界:确认读取的地址范围在PLC支持的范围内
- PLC未运行:ConfirmPLC处于RUN模式,STOP模式下部分地址不可读
5.3 写入不生效
- 寄存器被程序覆盖:PLC程序中可能正在写入该地址,导致写入被覆盖
- 地址只读:部分寄存器(如X输入)是只读的,不能写入
- 权限限制:部分PLC需要设置通讯写入权限
- 数据类型错误:确认写入的数据类型与PLC中定义的一致
六、最佳实践
- 批量读取:尽量一次读取多个连续寄存器,减少通讯次数,提高效率
- 合理轮询间隔:建议轮询间隔不未来100ms,避免过快轮询导致PLC通讯过载
- 断线重连:添加自动重连机制,网络恢复后自动恢复通讯
- 超时设置:合理设置通讯超时,建议2-3秒
- 异常处理:捕获ModbusException,区分不同异常码进行处理
- 线程安全:多线程访问时使用lock保护,避免并发冲突
- 日志记录:记录通讯日志,便于排查问题
- 地址规划:提前规划D/M寄存器的使用,避免地址冲突
本文提供的C#代码实现了台达PLC的完整Modbus TCP通讯功能,包括Dregister、MRelay、X输入、Y输出、定时器、计数器的读写操作,以及Windows Forms上位机界面示例。代码可直接用于工业自动化项目开发,建议先使用台达ISPSoft软件监控PLC데이터,确认通讯正常后再集成到实际项目中。
Leave a Reply