C#讀寫三菱PLC和西門子PLC數據 使用TCP/IP 協議
本文將使用一個NuGet公開的組件技術來讀寫三菱PLC和西門子plc數據,使用的是基於以太網的TCP/IP實現,不需要額外的組件,讀取操作只要放到后台線程就不會卡死線程,本組件支持超級方便的高性能讀寫操作
在Visual Studio 中的NuGet管理器中可以下載安裝,也可以直接在NuGet控制台輸入下面的指令安裝:
1
|
Install-Package HslCommunication
|
如果需要教程:Nuget安裝教程:http://www.cnblogs.com/dathlin/p/7705014.html
技術支持QQ群:592132877 (組件的版本更新細節也將第一時間在群里發布)最后編輯日期:2017年12月7日 11:03:56
里面各種小伙伴,為您解答數據交互,編程技巧,如果對本界面提供的API有任何疑問,都可以加群咨詢,如果有更好的建議,歡迎提出。
如果你需要在讀取PLC數據之后,還要群發客戶端來實現遠程辦公室同步監視,可以參考如下的項目(基於該組件擴展起來的,帶有賬戶驗證,版本控制,數據群發,公告管理等等功能)
https://github.com/dathlin/ClientServerProject
本文將展示如何配置網絡參數及怎樣使用代碼來訪問PLC數據,希望給有需要的人解決一些實際問題。主要對三菱Q系列PLC的X,Y,M,L,B,V,F,S,D,W,R區域的數據讀寫,對西門子PLC的M,Q,I,DB塊的數據讀寫,親測有效。
此處使用了網線直接的方式,如果PLC接進了局域網,就可以進行遠程讀寫了^_^
此處使用到了2個命名空間:
1
2
|
using
HslCommunication;
using
HslCommunication.Profinet;
|
隨便聊聊
當我們一個上位機需要讀取100台西門子PLC設備(此處只是舉個例子,凡是都是使用Modbus tcp的都是一樣的)的時候,你采用服務器主動去請求100台設備的機制對性能來說是個極大的考驗,如果開100個線程去輪詢100台設備,那么性能損失將是非常大的,更不用說再增加設備,如果搭建Modbus tcp服務器,就可以完美的解決性能問題,因為連接的壓力將會平均分攤給每一台PLC,服務器端只要新增一個時間戳就可以知道客戶端有沒有連接上。
我們在100台PLC里都增加發送Modbus tcp方法,將數據發送到服務器的ip和端口上去,服務器根據站號來區分設備。這樣就可以搭建一個高性能總站。 本組件支持快速搭建一個高性能的Modbus tcp總站。
http://www.cnblogs.com/dathlin/p/7782315.html
關於兩種模式
在PLC端,包括三菱和西門子篇二以及Modbus Tcp客戶端的訪問器上,都支持兩種模式,短連接模式和長連接模式,現在就來解釋下什么原理。
短連接:每次讀寫都是一個單獨的請求,請求完畢也就關閉了,如果服務器的端口僅僅支持單連接,那么關閉后這個端口可以被其他連接復用,但是在頻繁的網絡請求下,容易發生異常,會有其他的請求不成功,尤其是多線程的情況下。
長連接:創建一個公用的連接通道,所有的讀寫請求都利用這個通道來完成,這樣的話,讀寫性能更快速,即時多線程調用也不會影響,內部有同步機制。如果服務器的端口僅僅支持單連接,那么這個端口就被占用了,比如三菱的端口機制,西門子的Modbus tcp端口機制也是這樣的。以下代碼默認使用長連接,性能更高,還支持多線程同步。
在短連接的模式下,每次請求都是單獨的訪問,所以沒有重連的困擾,在長連接的模式下,如果本次請求失敗了,在下次請求的時候,會自動重新連接服務器,直到請求成功為止。另外,盡量所有的讀寫都對結果的成功進行判斷。
關於日志記錄
不管是三菱的數據訪問類,還是西門子的,還是Modbus tcp訪問類,都有一個LogNet屬性用來記錄日志,該屬性是一個接口類,ILogNet,凡事繼承該接口的都可以用來記錄日志,該日志會在訪問失敗時,尤其是因為網絡的原因導致訪問失敗時會進行日志記錄(如果你為這個LogNet屬性配置了真實的日志記錄器的話):如果你想使用該記錄日志的功能,請參照如下的博客進行實例化:
http://www.cnblogs.com/dathlin/p/7691693.html
演示項目
下面的三篇演示了具體如何去訪問PLC的數據,我們在訪問完成后,通常需要進行處理,以下的示例項目就演示了后台從PLC讀取數據后,前台顯示並推送給所有在線客戶端的功能,客戶端並進行圖形化顯示,具有一定的參考意義,項目地址為:
https://github.com/dathlin/RemoteMonitor
下面的圖片示例中的左邊程序就是服務器程序,它應該和PLC直接連接並接入局域網,然后把數據推送給客戶端顯示。注意:一個復雜高級的程序就應該把處理邏輯程序和界面程序分開,比如這里的服務器程序實現數據采集,推送,存儲。讓客戶端程序去實現數據的整理,分析,顯示,這樣即使客戶端程序因為BUG奔潰,服務器端仍然可以正常的工作。
三菱PLC篇(如果你的PLC內置了以太網,請配置MC協議通訊)
Q06UDV Plc的訪問測試感謝網友:hwdq0012
感想網友:小懶豬雨中人 的測試,VB程序也可以調用本通訊庫
環境:此處以GX Works2為示例,添加以太網模塊,型號為QJ71E71-100,組態里添加完成后進行以太網的參數配置,此處需要注意的是:參數的配置對接下來的代碼中配置參數要一一對應
注意:在PLC的以太網模塊的配置中,無法設置網絡號為0,也無法設置站號為0, 所以此處均設置為1,在C#程序中也使用上述的配置,在代碼中均配置為0,如果您自定義設置為網絡2, 站號8,那么在代碼中就要寫對應的數據。如果仍然通信失敗,重新測試0,0。
打開設置:在上圖中的打開設置選項,進行其他參數的配置,下圖只是舉了一個例子,開通了4個端口來支持讀寫操作:
端口號設置規則:
- 為了不與原先存在的系統發生沖突,您在添加自己的端口時盡量使用您自己的端口。
- 如果讀寫都需要,盡可能的將讀取端口和寫入端口區分開來,這樣做比較高性能。
- 如果您的網絡狀態不是特別穩定,讀取端口使用2個,一個受阻切換另一個讀取可以提升系統的穩定性。
本文檔僅作組件的測試,所以只用了一個端口作為讀寫。如果你的程序也使用了一個端口,那么你在讀取數據時候, 剛好也在寫入(異步操作可能發生這樣的情況),那么寫入會失敗!)(在長連接模式下沒有這個問題)
三菱PLC的數據主要由兩類數據組成,位數據和字數據,在位數據中,例如X,Y,M,L都是位數據,字數據例如D,W。 兩類的數據在讀取解碼上存在一點小差別。(事實上也可以先將16個M先賦值給一個D,讀取D數據再進行解析, 在讀取M的數量比較多的時候,這樣操作效率更高)
初始化訪問PLC對象
如果想使用本組件的數據讀取功能,必須先初始化數據訪問對象,根據實際情況進行數據的填入。 下面僅僅是測試中的數據:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private
MelsecNet melsec_net =
new
MelsecNet();
private
void
MelsecNetInitialization()
{
//初始化
melsec_net.PLCIpAddress = System.Net.IPAddress.Parse(
"192.168.0.7"
);
// PLC的IP地址
melsec_net.PortRead = 6000;
// 端口
melsec_net.PortWrite = 6001;
// 寫入端口,最好和讀取分開
melsec_net.NetworkNumber = 0;
// 網絡號
melsec_net.NetworkStationNumber = 0;
// 網絡站號
melsec_net.ConnectTimeout = 500;
// 連接超時時間
// 如果需要長連接,就取消下面這行代碼的注釋,對於數據讀寫的代碼,沒有影響
melsec_net.ConnectServer();
// 切換長連接,這行代碼可以放在其他任何地方
// melsec_net.ConnectClose(); // 關閉長連接,並切換為短連接,在系統退出時可以調用
}
|
說明:對象應該放在窗體類下面,此處僅僅針對讀取一台設備的plc,也可以在訪問的方法中實例化局部對象, 初始化數據,然后讀取,該對象幾乎不損耗內存,內存垃圾由CLR進行自動回收。此處測試方便,窗體的多個按鈕均連接同一台PLC 設備,所以本窗體實例化一個對象即可。
關於兩種地址的表示方式
第一種,使用系統的類來標識,比如M200,寫成(MelsecDataType.M, 200)的表示形式,這樣也可以去MelsecDataType里面找到所有支持的數據類型。
第二種,使用字符串表示,這個組件里所有的讀寫操作提供字符串表示的重載方法,所有的支持訪問的類型對應如下,字符串的表示方式存在十進制和十六進制的區別:
- 輸入繼電器:"X100","X1A0" // 字符串為十六進制機制
- 輸出繼電器:"Y100" ,"Y1A0" // 字符串為十六進制機制
- 內部繼電器:"M100","M200" // 字符串為十進制
- 鎖存繼電器:"L100" ,"L200" // 字符串為十進制
- 報警器: "F100", "F200" // 字符串為十進制
- 邊沿繼電器:"V100" , "V200" // 字符串為十進制
- 鏈接繼電器:"B100" , "B1A0" // 字符串為十六進制
- 步進繼電器:"S100" , "S200" // 字符串為十進制
- 數據寄存器:"D100", "D200" // 字符串為十進制
- 鏈接寄存器:"W100" ,"W1A0" // 字符串為十六進制
- 文件寄存器:"R100","R200" // 字符串為十進制
展示一些簡單實用基礎數據讀寫,這些數據的讀寫沒有進行嚴格的是否成功判斷(判斷方法參照后面的代碼),一般網絡良好的情況下都會成功,但不排除失敗,以下代碼僅作測試,所有沒有嚴格判斷是否成功:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
private
void
userButton22_Click(
object
sender, EventArgs e)
{
bool
M100 = melsec_net.ReadBoolFromPLC(
"M100"
).Content;
// 讀取M100是否通,十進制地址
bool
X1A0 = melsec_net.ReadBoolFromPLC(
"X1A0"
).Content;
// 讀取X1A0是否通,十六進制地址
bool
Y1A0 = melsec_net.ReadBoolFromPLC(
"Y1A0"
).Content;
// 讀取Y1A0是否通,十六進制地址
bool
B1A0 = melsec_net.ReadBoolFromPLC(
"B1A0"
).Content;
// 讀取B1A0是否通,十六進制地址
short
short_D1000 = melsec_net.ReadShortFromPLC(
"D1000"
).Content;
// 讀取D1000的short值 ,W3C0,R3C0 效果是一樣的
ushort
ushort_D1000 = melsec_net.ReadUShortFromPLC(
"D1000"
).Content;
// 讀取D1000的ushort值
int
int_D1000 = melsec_net.ReadIntFromPLC(
"D1000"
).Content;
// 讀取D1000-D1001組成的int數據
uint
uint_D1000 = melsec_net.ReadUIntFromPLC(
"D1000"
).Content;
// 讀取D1000-D1001組成的uint數據
float
float_D1000 = melsec_net.ReadFloatFromPLC(
"D1000"
).Content;
// 讀取D1000-D1001組成的float數據
long
long_D1000 = melsec_net.ReadLongFromPLC(
"D1000"
).Content;
// 讀取D1000-D1003組成的long數據
double
double_D1000 = melsec_net.ReadDoubleFromPLC(
"D1000"
).Content;
// 讀取D1000-D1003組成的double數據
string
str_D1000 = melsec_net.ReadStringFromPLC(
"D1000"
, 10).Content;
// 讀取D1000-D1009組成的條碼數據
melsec_net.WriteIntoPLC(
"M100"
,
true
);
// 寫入M100為通
melsec_net.WriteIntoPLC(
"Y1A0"
,
true
);
// 寫入Y1A0為通
melsec_net.WriteIntoPLC(
"X1A0"
,
true
);
// 寫入X1A0為通
melsec_net.WriteIntoPLC(
"B1A0"
,
true
);
// 寫入B1A0為通
melsec_net.WriteIntoPLC(
"D1000"
, (
short
)1234);
// 寫入D1000 short值 ,W3C0,R3C0 效果是一樣的
melsec_net.WriteIntoPLC(
"D1000"
, (
ushort
)45678);
// 寫入D1000 ushort值
melsec_net.WriteIntoPLC(
"D1000"
, 1234566);
// 寫入D1000 int值
melsec_net.WriteIntoPLC(
"D1000"
, (
uint
)1234566);
// 寫入D1000 uint值
melsec_net.WriteIntoPLC(
"D1000"
, 123.456f);
// 寫入D1000 float值
melsec_net.WriteIntoPLC(
"D1000"
, 123.456d);
// 寫入D1000 double值
melsec_net.WriteIntoPLC(
"D1000"
, 123456661235123534L);
// 寫入D1000 long值
melsec_net.WriteAsciiStringIntoPLC(
"D1000"
,
"K123456789"
);
// 寫入D1000 string值
}
|
下面再分別講解嚴格的操作,以及批量化的復雜的讀寫操作,假設你要讀取1000個M,循環讀取1千次可能要3秒鍾,如果用了下面的批量化讀取,只需要50ms,但是需要你對字節的原理比較熟悉才能得心應手的處理
X,Y,M,L,F,V,B,S位數據的讀寫說明
- X 輸入繼電器
- Y 輸出繼電器
- M 內部繼電器
- L 鎖存繼電器
- F 報警器
- V 邊沿繼電器
- B 鏈接繼電器
- S 步進繼電器
本小節將展示八種位數據的讀取,雖然更多的時候只是讀取D數據即可,或者是將位數據批量挪到D數據中, 但是在此處仍然進行介紹單獨的讀取X,Y,M,L,F,V,B,S,由於這八種讀取手法一致,故針對M數據進行介紹,其他的您可以自己測試。
如下方法演示讀取了M200-M209這10個M的值,注意:讀取長度必須為偶數,即時寫了奇數,也會補齊至偶數,讀取和寫入的最大長度為7168,否則報錯。如需實際需求確實大於7168的,請分批次讀取。
返回值解析:如果讀取正常則共返回10個字節的數據,以下示例數據進行批量化的讀取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
private
void
userButton20_Click(
object
sender, EventArgs e)
{
// M200-M209讀取顯示
OperateResult<
byte
[]> read = melsec_net.ReadFromPLC(
"M200"
, 10);
if
(read.IsSuccess)
{
// 成功讀取,True代表通,False代表不通
bool
M200 = read.Content[0] == 1;
bool
M201 = read.Content[1] == 1;
bool
M202 = read.Content[2] == 1;
bool
M203 = read.Content[3] == 1;
bool
M204 = read.Content[4] == 1;
bool
M205 = read.Content[5] == 1;
bool
M206 = read.Content[6] == 1;
bool
M207 = read.Content[7] == 1;
bool
M208 = read.Content[8] == 1;
bool
M209 = read.Content[9] == 1;
// 顯示
}
else
{
//失敗讀取,顯示失敗信息
MessageBox.Show(read.ToMessageShowString());
}
}
private
void
userButton21_Click(
object
sender, EventArgs e)
{
// X100-X10F讀取顯示
OperateResult<
byte
[]> read = melsec_net.ReadFromPLC(
"X200"
, 16);
if
(read.IsSuccess)
{
// 成功讀取,True代表通,False代表不通
bool
X200 = read.Content[0] == 1;
bool
X201 = read.Content[1] == 1;
bool
X202 = read.Content[2] == 1;
bool
X203 = read.Content[3] == 1;
bool
X204 = read.Content[4] == 1;
bool
X205 = read.Content[5] == 1;
bool
X206 = read.Content[6] == 1;
bool
X207 = read.Content[7] == 1;
bool
X208 = read.Content[8] == 1;
bool
X209 = read.Content[9] == 1;
bool
X20A = read.Content[10] == 1;
bool
X20B = read.Content[11] == 1;
bool
X20C = read.Content[12] == 1;
bool
X20D = read.Content[13] == 1;
bool
X20E = read.Content[14] == 1;
bool
X20F = read.Content[15] == 1;
// 顯示
}
else
{
//失敗讀取,顯示失敗信息
MessageBox.Show(read.ToMessageShowString());
}
}
private
void
userButton3_Click(
object
sender, EventArgs e)
{
// M100-M104 寫入測試 此處寫入后M100:通 M101:斷 M102:斷 M103:通 M104:通
bool
[] values =
new
bool
[] {
true
,
false
,
false
,
true
,
true
};
// 等同於 byte[] values = new byte[]{0x01,0x00,0x00,0x01,0x01}
OperateResult write = melsec_net.WriteIntoPLC(
"M100"
, values);
if
(write.IsSuccess)
{
TextBoxAppendStringLine(
"寫入成功"
);
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
錯誤說明:有可能因為站號網絡號沒有配置正確返回有錯誤代號沒有錯誤信息, 也有可能因為網絡問題導致沒有連接上,此時會有連接不上的錯誤信息。
下面展示的是后台線程循環讀取的情況,事實上在實際的使用過程中經常會碰見的情況。下面的方法需要 放到單獨的線程中,同理,訪問D數據時也是按照下面循環就行,此處不再贅述。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//后台循環讀取PLC數據 M200開始10個字 也即是M200-M209
while
(
true
)
{
OperateResult<
byte
[]> read = melsec_net.ReadFromPLC(
"M200"
, 10);
if
(read.IsSuccess)
{
//成功讀取,委托顯示
textBox2.BeginInvoke(
new
Action(
delegate
{
textBox2.Text =
"M201:"
+ (read.Content[1] == 1 ?
"通"
:
"斷"
);
}));
}
else
{
//失敗讀取,應該對失敗信息進行日志記錄,不應該顯示,測試訪問時才適合顯示錯誤信息
LogHelper.save(read.ToMessageShowString());
}
System.Threading.Thread.Sleep(1000);
//決定了訪問的頻率
}
|
D,W,R字數據的讀寫操作
此處讀取針對中間存在整數數據的情況,因為兩者讀取方式相同,故而只演示一種數據讀取, 使用該組件讀取數據,一次最多讀取或寫入960個字,超出則失敗。 如果讀取的長度確實超過限制,請考慮分批讀取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
private
void
button1_Click(
object
sender, EventArgs e)
{
//讀取PLC數據 D6000開始21個字 也即是D6000-D6020 最大長度980
OperateResult<
byte
[]> read = melsec_net.ReadFromPLC(
"D6000"
, 21);
if
(read.IsSuccess)
{
//成功讀取
textBox2.Text =
"D6000:"
+ melsec_net.GetShortFromBytes(read.Content, 0);
//textBox2.Text = "D6001:" + melsec_net.GetShortFromBytes(read.Content, 1);
//textBox2.Text = "D6002:" + melsec_net.GetShortFromBytes(read.Content, 2);
//textBox2.Text = "D6003:" + melsec_net.GetShortFromBytes(read.Content, 3);
//textBox2.Text = "D6004:" + melsec_net.GetShortFromBytes(read.Content, 4);
//================================================================================
//這兩種方式一樣的,
//textBox2.Text = "D6000:" + BitConverter.ToInt16(read.Content, 0);
//textBox2.Text = "D6001:" + BitConverter.ToInt16(read.Content, 2);
//textBox2.Text = "D6002:" + BitConverter.ToInt16(read.Content, 4);
//textBox2.Text = "D6003:" + BitConverter.ToInt16(read.Content, 6);
//textBox2.Text = "D6004:" + BitConverter.ToInt16(read.Content, 8);
}
else
{
//失敗讀取
MessageBox.Show(read.ToMessageShowString());
}
}
private
void
button2_Click(
object
sender, EventArgs e)
{
short
[] values =
new
short
[4] { 1335, 8765, 1234, 4567 };
//決定了寫多少長度的D
//寫入PLC數據 D6000為1234,D6001為8765,D6002為1234,D6003為4567
OperateResult write = melsec_net.WriteIntoPLC(MelsecDataType.D, 6000, values);
if
(write.IsSuccess)
{
textBox2.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
//顯示失敗原因
}
}
|
ASCII字符串數據的讀寫
在實際項目中,有可能會碰到PLC存儲了規格數據,或是條碼數據,這些數據是以ASCII編碼形式存在, 我們需要把數據進行讀取出來用於顯示,保存等操作。下面演示讀取指定長度的條碼數據,數據的數據存放在D2000-D2004中, 長度應該為存儲條碼的最大長度,也即是占用了5個D,一個D可以存儲2個ASCII碼字符:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private
void
button7_Click(
object
sender, EventArgs e)
{
//讀取字符串數據,共計10個字節長度
OperateResult<
byte
[]> read = melsec_net.ReadFromPLC(MelsecDataType.D, 2000, 5);
if
(read.IsSuccess)
{
//成功讀取
textBox2.Text = Encoding.ASCII.GetString(read.Content);
}
else
{
//失敗讀取
MessageBox.Show(read.ToMessageShowString());
}
}
private
void
button8_Click(
object
sender, EventArgs e)
{
//寫字符串,如果寫入K12345678這9個字符,讀取出來時末尾會補0
OperateResult write = melsec_net.WriteAsciiStringIntoPLC(MelsecDataType.D, 2000,
"K123456789"
);
if
(write.IsSuccess)
{
textBox2.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
需要注意的是,如果第一次在D2000-D2004中寫入了"K123456789",第二次寫入了"K6666",那么讀取D2000-D2004的條碼數據會讀取到 K666656789,如果要避免這種情況,則需要在寫入條碼的時候,指定總長度,該長度必須為偶數, 不然也會自動補0,小於該長度時,自動補零,大於該長度時,自動截斷數據,具體的使用方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button8_Click(
object
sender, EventArgs e)
{
//寫字符串,本次寫入指定了10個長度的字符,其余的D的數據將被清空,是一種安全的寫入方式
OperateResult write = melsec_net.WriteAsciiStringIntoPLC(MelsecDataType.D, 2000,
"K6666"
, 10);
if
(write.IsSuccess)
{
textBox2.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
中文及特殊字符的讀寫
在需要讀寫復雜的字符數據時,上述的ASCII編碼已經不能滿足要求,雖然使用讀寫的基礎方法可以實現任意數據的讀寫, 但是此處為了方便,還是提供了一個方便的方法來讀寫中文數據,采用Unicode編碼的字符, 該編碼下的一個字符占用一個D或W來存儲。如下將演示,讀寫方法,基本用途和上述 ASCII編碼的讀寫一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
private
void
button9_Click(
object
sender, EventArgs e)
{
//讀中文,存儲在D3000-D3009
OperateResult<
byte
[]> read = melsec_net.ReadFromPLC(MelsecDataType.D, 3000, 10);
if
(read.IsSuccess)
{
//解析數據
textBox2.Text = Encoding.Unicode.GetString(read.Content);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
private
void
button10_Click(
object
sender, EventArgs e)
{
//寫中文 D3000-D3009,該10含義為中文字符數
OperateResult write = melsec_net.WriteUnicodeStringIntoPLC(MelsecDataType.D, 3000,
"測試數據test"
, 10);
if
(write.IsSuccess)
{
textBox2.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
一個實際中復雜的例子演示
實際中可能碰到的情況會很復雜,一台設備中需要上傳的數據包含了溫度,壓力,產量,規格等等信息,在一串數據中 會包含各種各樣的不同的數據,上述的讀取D,讀取M,讀取條碼的方式不太好用,所以此處做一個完整示例的演示,假設我們需要讀取 D4000-D4009的數據,假設D4000存放了溫度數據,55.1℃在D中為551,D4001存放了壓力數據,1.23MPa在D中存放為123,D4002存放了 設備狀態,0為停止,1為運行,D4003存放了產量,1000就是指1000個,D4004備用,D4005-D4009存放了規格,以下代碼演示如何去解析數據:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private
void
button29_Click(
object
sender, EventArgs e)
{
//解析復雜數據
OperateResult<
byte
[]> read = melsec_net.ReadFromPLC(MelsecDataType.D, 4000, 10);
if
(read.IsSuccess)
{
double
溫度 = BitConverter.ToInt16(read.Content, 0) / 10d;
//索引很重要
double
壓力 = BitConverter.ToInt16(read.Content, 2) / 100d;
bool
IsRun = BitConverter.ToInt16(read.Content, 4) == 1;
int
產量 = BitConverter.ToInt16(read.Content, 6);
string
規格 = Encoding.ASCII.GetString(read.Content, 10, 10);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
究極數據讀取展示,用於測試你自己的報文以及擴展自己的更高級,更變態的API,以下演示,使用這個高級模式,寫入M100,True的操作:
我們要寫入的字節數組HEX表示形式為:50 00 00 FF FF 03 00 0D 00 0A 00 01 14 01 00 64 00 00 90 01 00 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private
void
userButton23_Click(
object
sender, EventArgs e)
{
byte
[] buffer = HslCommunication.BasicFramework.SoftBasic.HexStringToBytes(
"50 00 00 FF FF 03 00 0D 00 0A 00 01 14 01 00 64 00 00 90 01 00 10"
);
// 直接使用報文進行
OperateResult<
byte
[]> operate = melsec_net.ReadFromServerCore(buffer);
if
(operate.IsSuccess)
{
// 返回PLC的報文反饋,需要自己對報文進行結果分析
MessageBox.Show(HslCommunication.BasicFramework.SoftBasic.ByteToHexString(operate.Content));
}
else
{
// 網絡原因導致的失敗
MessageBox.Show(operate.ToMessageShowString());
}
}
|
西門子篇一(S5兼容的FETCH/WRITE協議,配置比較麻煩,不再維護,s300系列和1200系列參照西門子篇二)
環境:此處使用了STEP 7V5.5 sp4編程軟件作為示例,在添加以太網模塊(6GK7 343-1EX30-0E0 CP343-1)到組態中時,可以設置IP地址及子網掩碼, 此處測試使用,所以不使用路由器,如果您的西門子需要連接到內網中的話,需要配置路由器。目前只支持M,I,Q數據的讀寫。 然后點擊新建,創建一個Ethernet(1)網絡。以太網參數配置如下圖:
將以太網的模塊添加到機架中以后,現在打開網絡組態 ,打開后點擊組態上的PLC模塊。會出現如下界面,在箭頭出進行雙擊操作,可以彈出對話框,並進行一系列操作:
按照上面一套操作下來,創建了一個讀取的端口,端口號為2000,后面有用,需要記住, 按照上述的步驟再創建一個寫入的端口,只有最后一步不一致,如下:
配置完之后的效果圖如下,新建了兩個端口,一個用於讀取數據,一個用於寫入數據。 <strong>注意:設置完成后一定要寫入到PLC才算真的完成。</strong>
如上圖所示,共配置了2000,2001兩個端口號,配置完成后需要進行重啟PLC,端口的配置原則如下:
端口號設置規則:
- 為了不與原先存在的系統發生沖突,您在添加自己的端口時盡量使用您自己的端口。
- 如果讀寫都需要,盡可能的將讀取端口和寫入端口區分開來,這樣做比較高性能。
- 如果您的網絡狀態不是特別穩定,讀取端口使用2個,一個受阻切換另一個讀取可以提升系統的穩定性。
西門子PLC的數據種類其實只有一種,就是byte數據,更大的數據用多個byte來組合,位數據就是byte上的位,所以會比三菱的簡單一點點,好處理一點。
初始化訪問PLC對象
如果想使用本組件的數據讀取功能,必須先初始化數據訪問對象,根據實際情況進行數據的填入。 下面僅僅是測試中的數據:
1
2
3
4
5
6
7
8
9
10
|
private
SiemensNet siemens_net =
new
SiemensNet();
private
void
FormPlcTest_Load(
object
sender, EventArgs e)
{
//初始化,此處的值參考了上面的配置參數
siemens_net.ConnectTimeout = 500;
siemens_net.PortRead = 2000;
siemens_net.PortReadBackup = 2002;
//備用讀端口,也可以不指定,默認負數,不會切換負數端口
siemens_net.PortWrite = 2001;
siemens_net.PLCIpAddress = System.Net.IPAddress.Parse(
"192.168.0.6"
);
}
|
說明:對象應該放在窗體類下面,此處僅僅針對讀取一台設備的plc,也可以在訪問的方法中實例化局部對象, 初始化數據,然后讀取,該對象幾乎不損耗內存,內存垃圾由CLR進行自動回收。此處測試方便,窗體的多個按鈕均連接同一台PLC 設備,所以本窗體實例化一個對象即可。
M,I,Q數據讀寫
由於西門子這三個數據種類相同,操作時一模一樣,所以此處只展示讀寫M寄存器的例子。
如下方法演示讀取了M100-M101這2個M的值,返回值解析:如果讀取正常則共返回2個字節的數據,以下示例數據
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
private
void
button3_Click(
object
sender, EventArgs e)
{
OperateResult<
byte
[]> read = siemens_net.ReadFromPLC(SiemensDataType.M, 100, 2);
if
(read.IsSuccess)
{
textBox4.Text =
"M100:"
+ read.Content[0] +
" M101:"
+ read.Content[1];
bool
M100_0 = (read.Content[0] & 0x01) == 0x01;
//M100.0的通斷
bool
M100_1 = (read.Content[0] & 0x02) == 0x02;
//M100.1的通斷
bool
M100_2 = (read.Content[0] & 0x04) == 0x04;
//M100.2的通斷
bool
M100_3 = (read.Content[0] & 0x08) == 0x08;
//M100.3的通斷
bool
M100_4 = (read.Content[0] & 0x10) == 0x10;
//M100.4的通斷
bool
M100_5 = (read.Content[0] & 0x20) == 0x20;
//M100.5的通斷
bool
M100_6 = (read.Content[0] & 0x40) == 0x40;
//M100.6的通斷
bool
M100_7 = (read.Content[0] & 0x80) == 0x80;
//M100.7的通斷
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
private
void
button4_Click(
object
sender, EventArgs e)
{
//寫入結果是M100:81 M101:22 M102:124
OperateResult<
byte
[]> write = siemens_net.WriteIntoPLC(SiemensDataType.M, 100,
new
byte
[] { 81, 22, 124 });
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
錯誤說明:有可能因為站號網絡號沒有配置正確返回有錯誤代號沒有錯誤信息, 也有可能因為網絡問題導致沒有連接上,此時會有連接不上的錯誤信息。
下面展示的是后台線程循環讀取的情況,事實上在實際的使用過程中經常會碰見的情況。下面的方法需要 放到單獨的線程中,同理,訪問D數據時也是按照下面循環就行,此處不再贅述。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//后台循環讀取PLC數據 M200開始10個字 也即是M200-M209
while
(
true
)
{
OperateResult<
byte
[]> read = siemens_net.ReadFromPLC(SiemensDataType.M, 100, 2);
if
(read.IsSuccess)
{
//成功讀取,委托顯示
textBox2.BeginInvoke(
new
Action(
delegate
{
textBox4.Text =
"M100:"
+ read.Content[0] +
" M101:"
+ read.Content[1];
}));
}
else
{
//失敗讀取,應該對失敗信息進行日志記錄,不應該顯示,測試訪問時才適合顯示錯誤信息
LogHelper.save(read.ToMessageShowString());
}
System.Threading.Thread.Sleep(1000);
//決定了訪問的頻率
}
|
整數數據讀寫(一個數據由2個byte組成)
雖然上述實現了M數據的讀寫,但是只能表示0-255的數據,想要支持更大的數據,需要自己指定規則, 這就需要你對數據和字節原理非常清晰才能實現,為了方便,此處提供了讀寫雙字節數據的功能,先演示讀取M100-M106 的數據,對應有三個雙字節數據,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private
void
button15_Click(
object
sender, EventArgs e)
{
//整數讀取
OperateResult<
byte
[]> read = siemens_net.ReadFromPLC(SiemensDataType.M, 100, 6);
if
(read.IsSuccess)
{
short
[] result = siemens_net.GetArrayFromBytes(read.Content);
textBox4.Text =
"M100:"
+ result[0] +
",M102:"
+ result[1] +
",M104:"
+ result[2];
//或者下述的方法或許,可以發現和三菱的D數據獲取是一模一樣的
//short m100 = BitConverter.ToInt16(read.Content, 0);
//short m102 = BitConverter.ToInt16(read.Content, 2);
//short m104 = BitConverter.ToInt16(read.Content, 4);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
接下來介紹寫入整數數據的操作,例如,我們要使得M100,M101=2456,M102,M103=4567,M104,M105=-124,那么代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button16_Click(
object
sender, EventArgs e)
{
//整數寫入,數組的長度為x,那么占用的M字節數為x乘以2
OperateResult write = siemens_net.WriteIntoPLC(SiemensDataType.M, 100,
new
short
[] { 2456, 4567, -124 });
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
ASCII字符串數據的讀寫
在實際項目中,有可能會碰到PLC存儲了規格數據,或是條碼數據,這些數據是以ASCII編碼形式存在, 我們需要把數據進行讀取出來用於顯示,保存等操作。下面演示讀取指定長度的條碼數據,數據的數據存放在M100-M109中, 長度應該為存儲條碼的最大長度,也即是占用了10個M,一個M可以存儲1個ASCII碼字符:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button12_Click(
object
sender, EventArgs e)
{
//讀取字符串,長度為10
OperateResult<
byte
[]> read = siemens_net.ReadFromPLC(SiemensDataType.M, 100, 10);
if
(read.IsSuccess)
{
textBox4.Text = Encoding.ASCII.GetString(read.Content);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
下面演示寫入條碼數據,地址在M100-M109中,所以需要寫入10個字符:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button14_Click(
object
sender, EventArgs e)
{
//寫字符串,字符串的長度決定了需要占用多少個M字節,兩者是相等的
OperateResult write = siemens_net.WriteAsciiStringIntoPLC(SiemensDataType.M, 100,
"K123456789"
);
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
需要注意的是,如果第一次在M100-M109中寫入了"K123456789",第二次寫入了"K6666",那么讀取M100-M109的條碼數據會讀取到K666656789,如果要避免這種情況,則需要在寫入條碼的時候,指定總長度,該長度 可單數可偶數,具體的使用方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button14_Click(
object
sender, EventArgs e)
{
//寫字符串
OperateResult write = siemens_net.WriteAsciiStringIntoPLC(SiemensDataType.M, 100,
"K6666"
, 10);
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
中文及特殊字符的讀寫
在需要讀寫復雜的字符數據時,上述的ASCII編碼已經不能滿足要求,雖然使用讀寫的基礎方法可以實現任意數據的讀寫, 但是此處為了方便,還是提供了一個方便的方法來讀寫中文數據,采用Unicode編碼的字符, 該編碼下的一個字符占用兩個M來存儲。如下將演示,讀寫方法,基本用途和上述 ASCII編碼的讀寫一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button11_Click(
object
sender, EventArgs e)
{
//讀取中文 指定讀取的長度必須雙數,否則解碼失敗
OperateResult<
byte
[]> read = siemens_net.ReadFromPLC(SiemensDataType.M, 200, 12);
if
(read.IsSuccess)
{
textBox4.Text = Encoding.Unicode.GetString(read.Content);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
在寫入的過程中,只演示寫入指定長度的(實際中也應該使用這個方法),指定長度的意思為多少個中文。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button13_Click(
object
sender, EventArgs e)
{
//中文寫入
OperateResult write = siemens_net.WriteUnicodeStringIntoPLC(SiemensDataType.M, 200,
"測試de東西"
, 10);
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
一個實際中復雜的例子演示
實際中可能碰到的情況會很復雜,一台設備中需要上傳的數據包含了溫度,壓力,產量,規格等等信息,在一串數據中 會包含各種各樣的不同的數據,所以此處做一個完整示例的演示,假設我們需要讀取 M100-M116的數據,假設M100,M101存放了溫度數據,55.1℃在M中為551,M102,M103存放了壓力數據,1.23MPa在M中存放為123,M104存放了 設備狀態,0為停止,1為運行,M105,M106存放了產量,1000就是指1000個,M107-M116存放了規格,以下代碼演示如何去解析數據:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private
void
button29_Click(
object
sender, EventArgs e)
{
//解析復雜數據
OperateResult<
byte
[]> read = siemens_net.ReadFromPLC(SiemensDataType.M, 200, 17);
if
(read.IsSuccess)
{
double
溫度 = BitConverter.ToInt16(read.Content, 0) / 10d;
//索引很重要
double
壓力 = BitConverter.ToInt16(read.Content, 2) / 100d;
bool
IsRun = read.Content[4] == 1;
int
產量 = BitConverter.ToInt16(read.Content, 5);
string
規格 = Encoding.ASCII.GetString(read.Content, 7, 10);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
西門子篇二(采用S7協議下的tcp直接通訊,配置簡單,一般PLC都支持)
測試通過的PLC:1200系列 本人親測
200smart 感謝 無名①終止^^ 的測試
上述的西門子篇一的通訊配置相對太麻煩,采用西門子官方的庫的話,又是DEMO版本,會有廣告,而且攜帶組件過多,最新學習了西門子的TCP/IP報文格式,報文格式在官網沒有找到,參考了如下地址的格式:
http://www.itpub.net/thread-2052649-1-1.html
https://wenku.baidu.com/view/d93b88b06394dd88d0d233d4b14e852459fb3912.html
如果你擅長於網絡通信和組件開發,可以通過報文格式開發出自己的西門子通信庫,我所做的就是基於報文格式進行了二次封裝,隱藏了socket通信的細節,還包含了異常處理,提供了簡單方便的API來讀寫數據。提供了整數數據的讀寫,字符串讀寫,來豐富各種需求,從事實上來說,只要可以讀寫字節,相當於任何數據了。
准備:在西門子PLC上配置好IP地址,就只有一個IP地址就夠了,然后打開電腦的cmd指令,只要能ping通西門子PLC即可。
實例化:
1
2
3
4
5
|
private
SiemensTcpNet siemensTcpNet =
new
SiemensTcpNet(SiemensPLCS.S1200)
{
PLCIpAddress = System.Net.IPAddress.Parse(
"192.168.1.195"
),
ConnectTimeout = 5000,
// 連接超時時間,默認5秒,可以不設置
};
|
切換長連接(可以根據自己的需求來確認是否切換),也可以放在窗口的Load方法中,一般建議使用長連接,速度更快,又是線程安全的:
1
2
3
4
|
private
void
userButton16_Click(
object
sender, EventArgs e)
{
siemensTcpNet.ConnectServer();
}
|
一行代碼就可以切換到長連接模式,長連接的模式通訊更加穩定(如果網絡確實好的話),這行代碼可以放到Form的Load事件方法中。
只要放到form窗口下即可,實例化需要指定訪問的是1200系列還是300系列,然后指定IP地址,端口號不需要指定,西門子有個默認的端口號102,支持讀寫操作。
演示一些簡單使用的數據讀寫操作,以下代碼沒有進行對讀寫結果嚴格判斷(判斷是否讀寫成功,參照更下面的代碼),網絡良好的情況下幾乎不會失敗,但不保證完全沒有錯誤,生產使用時盡可能的完善:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private
void
userButton40_Click(
object
sender, EventArgs e)
{
// 讀取操作,這里的M100可以替換成I100,Q100,DB20.100效果時一樣的
bool
M100_7 = siemensTcpNet.ReadBoolFromPLC(
"M100.7"
).Content;
// 讀取M100.7是否通斷,注意M100.0等同於M100
byte
byte_M100 = siemensTcpNet.ReadByteFromPLC(
"M100"
).Content;
// 讀取M100的值
short
short_M100 = siemensTcpNet.ReadShortFromPLC(
"M100"
).Content;
// 讀取M100-M101組成的字
ushort
ushort_M100 = siemensTcpNet.ReadUShortFromPLC(
"M100"
).Content;
// 讀取M100-M101組成的無符號的值
int
int_M100 = siemensTcpNet.ReadIntFromPLC(
"M100"
).Content;
// 讀取M100-M103組成的有符號的數據
uint
uint_M100 = siemensTcpNet.ReadUIntFromPLC(
"M100"
).Content;
// 讀取M100-M103組成的無符號的值
float
float_M100 = siemensTcpNet.ReadFloatFromPLC(
"M100"
).Content;
// 讀取M100-M103組成的單精度值
long
long_M100 = siemensTcpNet.ReadLongFromPLC(
"M100"
).Content;
// 讀取M100-M107組成的大數據值
ulong
ulong_M100 = siemensTcpNet.ReadULongFromPLC(
"M100"
).Content;
// 讀取M100-M107組成的無符號大數據
double
double_M100 = siemensTcpNet.ReadDoubleFromPLC(
"M100"
).Content;
// 讀取M100-M107組成的雙精度值
string
str_M100 = siemensTcpNet.ReadStringFromPLC(
"M100"
, 10).Content;
// 讀取M100-M109組成的ASCII字符串數據
// 寫入操作,這里的M100可以替換成I100,Q100,DB20.100效果時一樣的
siemensTcpNet.WriteIntoPLC(
"M100.7"
,
true
);
// 寫位,注意M100.0等同於M100
siemensTcpNet.WriteIntoPLC(
"M100"
, (
byte
)0x33);
// 寫單個字節
siemensTcpNet.WriteIntoPLC(
"M100"
, (
short
)12345);
// 寫雙字節有符號
siemensTcpNet.WriteIntoPLC(
"M100"
, (
ushort
)45678);
// 寫雙字節無符號
siemensTcpNet.WriteIntoPLC(
"M100"
, 123456789);
// 寫雙字有符號
siemensTcpNet.WriteIntoPLC(
"M100"
, (
uint
)3456789123);
// 寫雙字無符號
siemensTcpNet.WriteIntoPLC(
"M100"
, 123.456f);
// 寫單精度
siemensTcpNet.WriteIntoPLC(
"M100"
, 1234556434534545L);
// 寫大整數有符號
siemensTcpNet.WriteIntoPLC(
"M100"
, 523434234234343UL);
// 寫大整數無符號
siemensTcpNet.WriteIntoPLC(
"M100"
, 123.456d);
// 寫雙精度
siemensTcpNet.WriteAsciiStringIntoPLC(
"M100"
,
"K123456789"
);
// 寫ASCII字符串
}
|
如果上面的指令不能滿足你的需求,下面再分別講解嚴格的操作,以及批量化的復雜的讀寫操作,假設你要讀取1000個M,循環讀取1千次可能要3秒鍾,如果用了下面的批量化讀取,只需要50ms,但是需要你對字節的原理比較熟悉才能得心應手的處理
批量位數據寫入:(如果長度剛好為8的倍數,比如24個,那就剛好寫3個字節的數據,如果像下面的代碼寫10個長度,那么實際上會改變M200-M201共16個開關點,這個一定要注意)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
private
void
userButton14_Click(
object
sender, EventArgs e)
{
bool
[] data =
new
bool
[10]
{
false
,
// M200.0 = false
false
,
// M200.1 = false
false
,
// M200.2 = false
true
,
// M200.3 = true
true
,
// M200.4 = true
false
,
// M200.5 = false
true
,
// M200.6 = true
false
,
// M200.7 = false
true
,
// M201.0 = true
false
// M201.1 = false
};
OperateResult write = siemensTcpNet.WriteIntoPLC(
"M200"
, data);
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
M,I,Q的讀寫(此處演示批量讀取,自己根據需求來解析數據):
這三個數據的讀寫是一致的,為了區分之前舊的一個通訊類,此處的數據地址格式變更為字符串,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
private
void
button3_Click(
object
sender, EventArgs e)
{<br>
// 示例:M100,I100,Q100,DB20.100
OperateResult<
byte
[]> read = siemensTcpNet.ReadFromPLC(
"M100"
, 2);
if
(read.IsSuccess)
{
textBox4.Text =
"M100:"
+ read.Content[0] +
" M101:"
+ read.Content[1];
bool
M100_0 = (read.Content[0] & 0x01) == 0x01;
//M100.0的通斷
bool
M100_1 = (read.Content[0] & 0x02) == 0x02;
//M100.1的通斷
bool
M100_2 = (read.Content[0] & 0x04) == 0x04;
//M100.2的通斷
bool
M100_3 = (read.Content[0] & 0x08) == 0x08;
//M100.3的通斷
bool
M100_4 = (read.Content[0] & 0x10) == 0x10;
//M100.4的通斷
bool
M100_5 = (read.Content[0] & 0x20) == 0x20;
//M100.5的通斷
bool
M100_6 = (read.Content[0] & 0x40) == 0x40;
//M100.6的通斷
bool
M100_7 = (read.Content[0] & 0x80) == 0x80;
//M100.7的通斷
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
private
void
button4_Click(
object
sender, EventArgs e)
{
//寫入結果是M100:81 M101:22 M102:124
OperateResult write = siemensTcpNet.WriteIntoPLC(
"M100"
,
new
byte
[] { 81, 22, 124 });
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
如果是輸入的話,就是把"M100"換成"I100","Q100",效果是一樣的,這樣的就可以對數據進行讀寫了。而DB塊的格式有些區別,比如我們要寫DB塊20的100地址數據,我們需要寫成"DB20.100"這樣就可以正常讀寫了。DB塊數據並沒有進行嚴格測試,如果你需要使用這個功能,最好需要測試下是否真的能讀取數據。
DB塊的讀寫測試感謝我的好朋友:吃飽睡好
整數數據讀寫(一個數據由2個byte組成)
雖然上述實現了M數據的讀寫,但是只能表示0-255的數據,想要支持更大的數據,需要自己指定規則, 這就需要你對數據和字節原理非常清晰才能實現,為了方便,此處提供了讀寫雙字節數據的功能,先演示讀取M100-M106 的數據,對應有三個雙字節數據,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private
void
button15_Click(
object
sender, EventArgs e)
{
//整數讀取
OperateResult<
byte
[]> read = siemensTcpNet.ReadFromPLC(
"M100"
, 6);
if
(read.IsSuccess)
{
short
[] result = siemensTcpNet.GetArrayFromBytes(read.Content);
textBox4.Text =
"M100:"
+ result[0] +
",M102:"
+ result[1] +
",M104:"
+ result[2];
// 或者使用下述的方法
//short m100 = siemensTcpNet.GetShortFromBytes(read.Content, 0);
//short m102 = siemensTcpNet.GetShortFromBytes(read.Content, 2);
//short m104 = siemensTcpNet.GetShortFromBytes(read.Content, 4);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
接下來介紹寫入整數數據的操作,例如,我們要使得M100,M101=2456,M102,M103=4567,M104,M105=-124,那么代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button16_Click(
object
sender, EventArgs e)
{
//整數寫入,數組的長度為x,那么占用的M字節數為x乘以2
OperateResult write = siemensTcpNet.WriteIntoPLC(
"M100"
,
new
short
[] { 2456, 4567, -124 });
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
ASCII字符串數據的讀寫
在實際項目中,有可能會碰到PLC存儲了規格數據,或是條碼數據,這些數據是以ASCII編碼形式存在, 我們需要把數據進行讀取出來用於顯示,保存等操作。下面演示讀取指定長度的條碼數據,數據的數據存放在M100-M109中, 長度應該為存儲條碼的最大長度,也即是占用了10個M,一個M可以存儲1個ASCII碼字符:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button12_Click(
object
sender, EventArgs e)
{
//讀取字符串,長度為10
OperateResult<
byte
[]> read = siemensTcpNet.ReadFromPLC(
"M100"
, 10);
if
(read.IsSuccess)
{
textBox4.Text = Encoding.ASCII.GetString(read.Content);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
下面演示寫入條碼數據,地址在M100-M109中,所以需要寫入10個字符:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button14_Click(
object
sender, EventArgs e)
{
//寫字符串,字符串的長度決定了需要占用多少個M字節,兩者是相等的
OperateResult write = siemensTcpNet.WriteAsciiStringIntoPLC(
"M100"
,
"K123456789"
);
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
需要注意的是,如果第一次在M100-M109中寫入了"K123456789",第二次寫入了"K6666",那么讀取M100-M109的條碼數據會讀取到K666656789,如果要避免這種情況,則需要在寫入條碼的時候,指定總長度,該長度 可單數可偶數,具體的使用方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button14_Click(
object
sender, EventArgs e)
{
//寫字符串
OperateResult write = siemensTcpNet.WriteAsciiStringIntoPLC(
"M100"
,
"K6666"
, 10);
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
中文及特殊字符的讀寫
在需要讀寫復雜的字符數據時,上述的ASCII編碼已經不能滿足要求,雖然使用讀寫的基礎方法可以實現任意數據的讀寫, 但是此處為了方便,還是提供了一個方便的方法來讀寫中文數據,采用Unicode編碼的字符, 該編碼下的一個字符占用兩個M來存儲。如下將演示,讀寫方法,基本用途和上述 ASCII編碼的讀寫一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button11_Click(
object
sender, EventArgs e)
{
//讀取中文 指定讀取的長度必須雙數,否則解碼失敗
OperateResult<
byte
[]> read = siemensTcpNet.ReadFromPLC(
"M200"
, 12);
if
(read.IsSuccess)
{
textBox4.Text = Encoding.Unicode.GetString(read.Content);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
在寫入的過程中,只演示寫入指定長度的(實際中也應該使用這個方法),指定長度的意思為多少個中文。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
button13_Click(
object
sender, EventArgs e)
{
//中文寫入
OperateResult write = siemensTcpNet.WriteUnicodeStringIntoPLC(
"M200"
,
"測試de東西"
, 10);
if
(write.IsSuccess)
{
textBox4.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
一個實際的復雜例子:
實際中可能碰到的情況會很復雜,一台設備中需要上傳的數據包含了溫度,壓力,產量,規格等等信息,在一串數據中 會包含各種各樣的不同的數據,所以此處做一個完整示例的演示,假設我們需要讀取 M100-M116的數據,假設M100,M101存放了溫度數據,55.1℃在M中為551,M102,M103存放了壓力數據,1.23MPa在M中存放為123,M104存放了 設備狀態,0為停止,1為運行,M105,M106存放了產量,1000就是指1000個,M107-M116存放了規格,以下代碼演示如何去解析數據:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private
void
button49_Click(
object
sender, EventArgs e)
{
//解析復雜數據,西門子有個問題在於多字節數據的高地位是需要反轉的,而內置的GetShortFromBytes已經反轉了數據
OperateResult<
byte
[]> read = siemensTcpNet.ReadFromPLC(
"M200"
, 17);
if
(read.IsSuccess)
{
double
溫度 = siemensTcpNet.GetShortFromBytes(read.Content, 0) / 10d;
//索引很重要
double
壓力 = siemensTcpNet.GetShortFromBytes(read.Content, 2) / 100d;
bool
IsRun = read.Content[4] == 1;
int
產量 = siemensTcpNet.GetShortFromBytes(read.Content, 5);
string
規格 = Encoding.ASCII.GetString(read.Content, 7, 10);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
散亂數據的讀取:
實際中我們需要讀取PLC中的數據,並不是連續的數據塊,最好的方法是將所有需要的數據塊挪到一個連續的區塊,比如M塊,這樣可以加速數據的讀取,系統的性能也能更加高效,即時需要讀取1000個M點,也是毫秒級的事情,如果1000個M點分成1000次來讀取,那么使用本組件的效率是非常低下的,循環1000次的siemensTcpNet.ReadFromPLC("M100", 1);非常的耗時甚至可能達到幾秒的量級,因為這個方法每次調用都會重新請求網絡連接,然后初始化連接,請求數據,斷開連接,所以最好的方法就是所有的數據都挪到一個統一的數據區塊。
但是如果你確實有需求讀取多個地址的數據,比如做成訪問PLC的數據是可配置的,在配置文件里追加一個M100,長度4的int型數據,這種情況就不太適合將散亂的數據進行挪到統一的區塊,所以本組件提供了一個高性能數組讀取(但是仍然比一次讀取連續區塊慢一點,基本上是同一個量級的),聲明如下:
1
2
3
4
5
6
7
|
/// <summary>
/// 一次性從PLC獲取所有的數據,按照先后順序返回一個統一的Buffer,需要按照順序處理,兩個數組長度必須一致
/// </summary>
/// <param name="address">起始地址數組</param>
/// <param name="count">數據長度數組</param>
/// <returns></returns>
public
OperateResultBytes ReadFromPLC(
string
[] address,
ushort
[] count);
|
address和count數組都不能為空,否則報錯,兩者的長度必須一致,否則報錯
接下來我們舉例訪問PLC數據,比如我們需要讀取M100開始的4個字節(這是一個int數據),M150開始的4個字節(這是一個float數據),M200開始的2個字節(這是一個short數據),I300開始的一個字節(普通的byte數據)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private
void
button29_Click_1(
object
sender, EventArgs e)
{
// 數組讀取
OperateResult<
byte
[]> read = siemensTcpNet.ReadFromPLC(
new
string
[] {
"M100"
,
"M150"
,
"M200"
,
"I300"
},
new
ushort
[] { 4, 4, 2, 1});
if
(read.IsSuccess)
{
int
value1 = siemensTcpNet.GetIntFromBytes(read.Content, 0);
// 第一個int數據
float
value2 = siemensTcpNet.GetFloatFromBytes(read.Content, 4);
// 第二個float數據
short
value3 = siemensTcpNet.GetShortFromBytes(read.Content, 8);
// 第三個short數據
byte
value4 = read.Content[10];
// 第四個byte數據
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
究極數據的讀取:
此處提供一個核心的報文讀取機制,你可以自己傳入自己的報文,然后接收服務器的報文,再自己解析操作,可以根據報文格式實現任意的操作,當然,前提是需要報文支持。假設我要實現寫入M100,為0x3B,那么最終的報文為
03 00 00 24 02 F0 80 32 01 00 00 00 01 00 0E 00 05 05 01 12 0A 10 02 00 01 00 00 83 00 03 20 00 04 00 08 3B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private
void
userButton23_Click_1(
object
sender, EventArgs e)
{
byte
[] buffer = HslCommunication.BasicFramework.SoftBasic.HexStringToBytes(
"03 00 00 24 02 F0 80 32 01 00 00 00 01 00 0E 00 05 05 01 12 0A 10 02 00 01 00 00 83 00 03 20 00 04 00 08 3B"
);
OperateResult<
byte
[]> operate = siemensTcpNet.ReadFromServerCore(buffer);
if
(operate.IsSuccess)
{
// 顯示服務器返回的報文
TextBoxAppendStringLine(HslCommunication.BasicFramework.SoftBasic.ByteToHexString(operate.Content));
}
else
{
// 顯示網絡錯誤
MessageBox.Show(operate.ToMessageShowString());
}
}
|