?1、客戶端選擇
客戶端可以是一個程序或一個設(shè)備,這里我以C#WINFORM程序來實現(xiàn)客戶機(jī)與PLC的Modbustcp服務(wù)器通信,開發(fā)環(huán)境是VS2019,.NET Framework版本是4.7.2
2、創(chuàng)建winform程序
?創(chuàng)建類庫
?
編寫C#各種類的轉(zhuǎn)換庫,該庫由我提供,不用操心,文章最后提供。
項目引入這個類庫?
?
3、引入Nmodbus4協(xié)議
找到項目,找到引用,右鍵“管理nuget程序”,在下面對話框操作
?4、界面布局如下:
布局中用到的是下拉框combobox,文本框textbox,按鈕button,標(biāo)簽label
?這個IP地址和端口號是與這里對應(yīng)
?
5、窗體定義兩個變量,并引入對應(yīng)的命令空間
? ? ? ? ModbusIpMaster master = null;//modbus對象
? ? ? ? TcpClient tcpClient = null;//tcp客戶端對象
6、連接按鈕代碼
private void btnOpen_Click(object sender, EventArgs e)
{
string ip = txtIPAddress.Text.Trim();
bool t = IsIP(ip);
if (t)
{
try
{
int port = int.Parse(txtPort.Text.Trim());
tcpClient = new TcpClient();
tcpClient.Connect(ip, port);//連接到主機(jī)
master = ModbusIpMaster.CreateIp(tcpClient);//Ip 主站
master.Transport.ReadTimeout = 1000;//讀超時
master.Transport.WriteTimeout = 1000;//寫超時
master.Transport.Retries = 3;//嘗試重復(fù)連接次數(shù)
master.Transport.WaitToRetryMilliseconds = 200;//嘗試重復(fù)連接間隔
lblMessage.Text = "連接成功!";
btnOpen.Enabled = false;
}
catch (Exception ex)
{
MessageBox.Show("連接失敗," + ex.Message);
}
}
else
{
MessageBox.Show("無效的ip地址!");
}
}
?7、讀取的代碼--ushort類型
本例子中只用到了讀取保存寄存器這個功能碼,即ReadHoldingRegisters(從站地址,開始地址,寄存器數(shù)量)
/// <summary>
/// 讀取
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void myread_Click(object sender, EventArgs e)
{
//由于NModbus4讀取到寄存器的數(shù)據(jù)都是ushort類型
//功能碼
string readType = cboReadTypes.Text.Trim();
//從站地址
byte slaveAddr = byte.Parse(txtRSlaveId.Text.Trim());
//開始地址
ushort startAddr = ushort.Parse(txtRStartAddress.Text.Trim());
//讀取數(shù)量
ushort readCount = ushort.Parse(txtRCount.Text.Trim());
switch (readType)
{
case "讀線圈":
bool[] blVals = master.ReadCoils(slaveAddr, startAddr, readCount);
txtReadDatas1.Text = string.Join(",", blVals.Select(b => b ? "1" : "0"));
break;
case "讀輸入線圈":
bool[] blInputVals = master.ReadInputs(slaveAddr, startAddr, readCount);
txtReadDatas1.Text = string.Join(",", blInputVals.Select(b => b ? "1" : "0"));
break;
case "讀保持寄存器":
//情況1:ushort到ushort類型:即讀取無符號的整數(shù),如23,89,處理方法是:原封不動
//ushort[] uDatas = master.ReadHoldingRegisters(slaveAddr, startAddr, readCount);
//txtReadDatas.Text = string.Join(",", uDatas);
//功能碼
string dataType = cmddatatype.Text.Trim();
switch (dataType)
{
case "ushort":
//利用token循環(huán)讀取
ushortctsRead = new CancellationTokenSource();
Task.Run(new Action(() =>
{
ReadUshortFromPLC(slaveAddr, startAddr, readCount);
}), ushortctsRead.Token);
break;
case "short":
//利用token循環(huán)讀取
shortctsRead = new CancellationTokenSource();
Task.Run(new Action(() =>
{
ReadShortFromPLC(slaveAddr, startAddr, readCount);
}), shortctsRead.Token);
break;
case "float":
//利用token循環(huán)讀取
floatctsRead = new CancellationTokenSource();
Task.Run(new Action(() =>
{
ReadFloatFromPLC(slaveAddr, startAddr, readCount);
}), floatctsRead.Token);
break;
}
break;
case "讀輸入寄存器":
ushort[] uDatas1 = master.ReadInputRegisters(slaveAddr, startAddr, readCount);
txtReadDatas1.Text = string.Join(",", uDatas1);
break;
}
}
這里要注意,
NModbus4讀取到寄存器的數(shù)據(jù)都是ushort類型
NModbus4讀取到寄存器的數(shù)據(jù)都是ushort類型
代碼中用到ReadUshortFromPLC方法,ReadShortFromPLC方法,ReadFloatFromPLC方法在本文最后鏈接都會提供
運(yùn)行程序,連接成功,讀取數(shù)據(jù)
?注意這里,從站地址一般都是1,除非你改了,開始地址是0,表示寄存器的起始地址,數(shù)量是3,表示讀取3個寄存器數(shù)量,也就是前面3個變量,m1-speed,m1-duaror,m1-level
?這里為什么數(shù)量不能是4,因為第4個變量是real,它占2個寄存器,即占4個字節(jié),它不是ushort類型,這里地址也不能是%DB3.DBW4這種寫法,這不是S7協(xié)議讀取變量,是MODBUS讀取寄存器,兩者不一樣的,別糊涂了,各位長老。
8、讀取的代碼--float類型
?很多人搞不清楚這個開始地址和數(shù)量,這個開始地址是Modbus的地址,Modbus地址編號從0開始,因此8個變量的地址就是0,1,2,3,4,5,6,7,數(shù)量是指要讀取的寄存器個數(shù),word占一個,real占2個,這里很難理解,比較繞比較暈,一個是PLC地址,一個是MODBUS地址
我們要讀的溫度是第3個寄存器,它是real類型,占2個寄存器數(shù)量
如果要讀取“摩頭2溫度”,怎么讀了?
?
?大家想想,為什么開始地址是8?
9、寫入的代碼--ushort類型
/// <summary>
/// 寫入
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnWrite_Click(object sender, EventArgs e)
{
//功能碼
string writeType = cboWriteTypes.Text.Trim();
//從站地址
byte slaveAddr = byte.Parse(txtWSlaveId.Text.Trim());
//開始地址
ushort startAddr = ushort.Parse(txtWStartAddress.Text.Trim());
//數(shù)量
//實際數(shù)量
string objWriteVals = "";
string dataType = cmddatatype2.Text.Trim();
switch (dataType)
{
case "ushort":
objWriteVals = txtWriteDatas1.Text.Trim();
break;
case "short":
objWriteVals = txtWriteDatas2.Text.Trim();
break;
case "float":
objWriteVals = txtWriteDatas3.Text.Trim();
break;
}
ushort writeCount = ushort.Parse(txtWCount.Text.Trim());
ushort objWCount = (ushort)objWriteVals.Split(',').Length;
//實際數(shù)量與要求數(shù)量不一致,不允許操作
if (writeCount != objWCount)
{
MessageBox.Show("寫入值的數(shù)量不正確!");
return;
}
string vals = objWriteVals;
switch (writeType)
{
case "寫單線圈":
bool blVal = vals == "1" ? true : false;
try
{
master.WriteSingleCoil(slaveAddr, startAddr, blVal);
MessageBox.Show("【單線圈】寫入成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
break;
case "寫單保持寄存器":
ushort uVal01 = ushort.Parse(vals);
try
{
master.WriteSingleRegister(slaveAddr, startAddr, uVal01);
MessageBox.Show("【單保持寄存器】寫入成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
break;
case "寫多線圈":
bool[] blVals = vals.Split(',').Select(s => s == "1" ? true : false).ToArray();//bool數(shù)組
try
{
master.WriteMultipleCoils(slaveAddr, startAddr, blVals);
MessageBox.Show("【多線圈】寫入成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
break;
case "寫多保持寄存器":
try
{
//功能碼
//string dataType = cmddatatype2.Text.Trim();
switch (dataType)
{
case "ushort":
情況1:寫入無符號的整數(shù),即寫入ushort數(shù)據(jù),如寫入33,44
ushort[] uVals01 = vals.Split(',').Select(s => ushort.Parse(s)).ToArray();
master.WriteMultipleRegisters(startAddr, uVals01);
break;
case "short":
//情況2:寫入有符號的整數(shù),即寫入short數(shù)據(jù),如寫入-133,-65,98等,處理方法是:short[]=>byte[]=>ushort[],情況2包括了情況1
short[] uVals02 = vals.Split(',').Select(s => short.Parse(s)).ToArray();
byte[] y2 = ByteArrayLib.GetByteArrayFromShortArray(uVals02);
ushort[] ushorts2 = UShortLib.GetUShortArrayFromByteArray(y2);
master.WriteMultipleRegisters(startAddr, ushorts2);
MessageBox.Show("【short類型數(shù)據(jù)】寫入成功!");
break;
case "float":
//情況3:寫入有符號的小數(shù),即寫入float數(shù)據(jù),如寫入-6.3,-2.65,56.893,51,-465等,處理方法是:float[]=>byte[]=>ushort[],情況3包括了情況2和情況1
float[] uVals03 = vals.Split(',').Select(s => float.Parse(s)).ToArray();
byte[] y3 = ByteArrayLib.GetByteArrayFromFloatArray(uVals03);
ushort[] ushorts3 = UShortLib.GetUShortArrayFromByteArray(y3);
master.WriteMultipleRegisters(startAddr, ushorts3);
MessageBox.Show("【float類型數(shù)據(jù)】寫入成功!");
break;
}
情況2:寫入有符號的整數(shù),即寫入short數(shù)據(jù),如寫入-133,-65,98等,處理方法是:short[]=>byte[]=>ushort[],情況2包括了情況1
//short[] uVals02 = vals.Split(',').Select(s => short.Parse(s)).ToArray();
//byte[] y = ByteArrayLib.GetByteArrayFromShortArray(uVals02);
//ushort[] ushorts = UShortLib.GetUShortArrayFromByteArray(y);
//master.WriteMultipleRegisters(slaveAddr, startAddr, ushorts);
情況3:寫入有符號的小數(shù),即寫入float數(shù)據(jù),如寫入-6.3,-2.65,56.893,51,-465等,處理方法是:float[]=>byte[]=>ushort[],情況3包括了情況2和情況1
//float[] uVals02 = vals.Split(',').Select(s => float.Parse(s)).ToArray();
//byte[] y = ByteArrayLib.GetByteArrayFromFloatArray(uVals02);
//ushort[] ushorts = UShortLib.GetUShortArrayFromByteArray(y);
//master.WriteMultipleRegisters(slaveAddr, startAddr, ushorts);
MessageBox.Show("【多保持寄存器】寫入成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
break;
}
}
?寫入成功,同時讀取的也是剛才寫的值,在博途的監(jiān)控表中看到
10、寫入的代碼--float類型
?
寫入負(fù)數(shù)
?我們向“摩頭2溫度”這個寄存器寫入數(shù)據(jù)
再看博途中的數(shù)據(jù)
?
11、小結(jié)
客戶端創(chuàng)建tcp client對象,然后modbus利用tcp對象創(chuàng)建modbus通信,然后通過不同數(shù)據(jù)類型讀寫PLC數(shù)據(jù),成功了
代碼鏈接:
鏈接:https://pan.baidu.com/s/1aCqv3eSX-7SXAdGtrGNpTw?
提取碼:kyqo??
文章來源:http://www.zghlxwxcb.cn/news/detail-668001.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-668001.html
到了這里,關(guān)于C#與西門子PLC1500的ModbusTcp服務(wù)器通信4--搭建ModbusTcp客戶端的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!