本文將使用一個(gè)Github開(kāi)源的組件庫(kù)技術(shù)來(lái)讀寫(xiě)三菱PLC和西門(mén)子plc數(shù)據(jù),使用的是基于以太網(wǎng)的TCP/IP實(shí)現(xiàn),不需要額外的組件,讀取操作只要放到后臺(tái)線程就不會(huì)卡死線程,本組件支持超級(jí)方便的高性能讀寫(xiě)操作
github地址:https://github.com/dathlin/HslCommunication 如果喜歡可以star或是fork,還可以打賞支持,打賞請(qǐng)認(rèn)準(zhǔn)源代碼項(xiàng)目。
在Visual Studio 中的NuGet管理器中可以下載安裝,也可以直接在NuGet控制臺(tái)輸入下面的指令安裝:
1 | Install-Package HslCommunication
|
如果需要教程:Nuget安裝教程:http://www.cnblogs.com/dathlin/p/7705014.html
技術(shù)支持QQ群:群1:592132877(滿) 群2:948305931 (組件的版本更新細(xì)節(jié)也將第一時(shí)間在群里發(fā)布)最后編輯日期:2018年4月9日 11:35:43
里面各種小伙伴,為您解答數(shù)據(jù)交互,編程技巧,如果對(duì)本界面提供的API有任何疑問(wèn),都可以加群咨詢,如果有更好的建議,歡迎提出。
組件的完整信息和API介紹參照:http://www.cnblogs.com/dathlin/p/7703805.html 組件的使用限制,更新日志,都在該頁(yè)面里面。
如果你需要在讀取PLC數(shù)據(jù)之后,還要群發(fā)客戶端來(lái)實(shí)現(xiàn)遠(yuǎn)程辦公室同步監(jiān)視,可以參考如下的項(xiàng)目(基于該組件擴(kuò)展起來(lái)的,帶有賬戶驗(yàn)證,版本控制,數(shù)據(jù)群發(fā),公告管理等等功能)
https://github.com/dathlin/ClientServerProject
本文將展示如何配置網(wǎng)絡(luò)參數(shù)及怎樣使用代碼來(lái)訪問(wèn)PLC數(shù)據(jù),希望給有需要的人解決一些實(shí)際問(wèn)題。主要對(duì)三菱Q系列PLC的X,Y,M,L,B,V,F(xiàn),S,D,W,R區(qū)域的數(shù)據(jù)讀寫(xiě),對(duì)西門(mén)子PLC的M,Q,I,DB塊的數(shù)據(jù)讀寫(xiě),親測(cè)有效。
此處使用了網(wǎng)線直接的方式,如果PLC接進(jìn)了局域網(wǎng),就可以進(jìn)行遠(yuǎn)程讀寫(xiě)了^_^
此處使用到了2個(gè)命名空間:
1 2 | using HslCommunication;
using HslCommunication.Profinet;
|
隨便聊聊
當(dāng)我們一個(gè)上位機(jī)需要讀取100臺(tái)西門(mén)子PLC設(shè)備(此處只是舉個(gè)例子,凡是都是使用Modbus tcp的都是一樣的)的時(shí)候,你采用服務(wù)器主動(dòng)去請(qǐng)求100臺(tái)設(shè)備的機(jī)制對(duì)性能來(lái)說(shuō)是個(gè)極大的考驗(yàn),如果開(kāi)100個(gè)線程去輪詢100臺(tái)設(shè)備,那么性能損失將是非常大的,更不用說(shuō)再增加設(shè)備,如果搭建Modbus tcp服務(wù)器,就可以完美的解決性能問(wèn)題,因?yàn)檫B接的壓力將會(huì)平均分?jǐn)偨o每一臺(tái)PLC,服務(wù)器端只要新增一個(gè)時(shí)間戳就可以知道客戶端有沒(méi)有連接上。
我們?cè)?00臺(tái)PLC里都增加發(fā)送Modbus tcp方法,將數(shù)據(jù)發(fā)送到服務(wù)器的ip和端口上去,服務(wù)器根據(jù)站號(hào)來(lái)區(qū)分設(shè)備。這樣就可以搭建一個(gè)高性能總站。 本組件支持快速搭建一個(gè)高性能的Modbus tcp總站。
http://www.cnblogs.com/dathlin/p/7782315.html
關(guān)于兩種模式
在PLC端,包括三菱,西門(mén)子,歐姆龍以及Modbus Tcp客戶端的訪問(wèn)器上,都支持兩種模式,短連接模式和長(zhǎng)連接模式,現(xiàn)在就來(lái)解釋下什么原理。
短連接:每次讀寫(xiě)都是一個(gè)單獨(dú)的請(qǐng)求,請(qǐng)求完畢也就關(guān)閉了,如果服務(wù)器的端口僅僅支持單連接,那么關(guān)閉后這個(gè)端口可以被其他連接復(fù)用,但是在頻繁的網(wǎng)絡(luò)請(qǐng)求下,容易發(fā)生異常,會(huì)有其他的請(qǐng)求不成功,尤其是多線程的情況下。
長(zhǎng)連接:創(chuàng)建一個(gè)公用的連接通道,所有的讀寫(xiě)請(qǐng)求都利用這個(gè)通道來(lái)完成,這樣的話,讀寫(xiě)性能更快速,即時(shí)多線程調(diào)用也不會(huì)影響,內(nèi)部有同步機(jī)制。如果服務(wù)器的端口僅僅支持單連接,那么這個(gè)端口就被占用了,比如三菱的端口機(jī)制,西門(mén)子的Modbus tcp端口機(jī)制也是這樣的。以下代碼默認(rèn)使用長(zhǎng)連接,性能更高,還支持多線程同步。
在短連接的模式下,每次請(qǐng)求都是單獨(dú)的訪問(wèn),所以沒(méi)有重連的困擾,在長(zhǎng)連接的模式下,如果本次請(qǐng)求失敗了,在下次請(qǐng)求的時(shí)候,會(huì)自動(dòng)重新連接服務(wù)器,直到請(qǐng)求成功為止。另外,盡量所有的讀寫(xiě)都對(duì)結(jié)果的成功進(jìn)行判斷。
關(guān)于日志記錄
不管是三菱的數(shù)據(jù)訪問(wèn)類(lèi),還是西門(mén)子的,還是Modbus tcp訪問(wèn)類(lèi),都有一個(gè)LogNet屬性用來(lái)記錄日志,該屬性是一個(gè)接口類(lèi),ILogNet,凡事繼承該接口的都可以用來(lái)記錄日志,該日志會(huì)在訪問(wèn)失敗時(shí),尤其是因?yàn)榫W(wǎng)絡(luò)的原因?qū)е略L問(wèn)失敗時(shí)會(huì)進(jìn)行日志記錄(如果你為這個(gè) LogNet 屬性配置了真實(shí)的日志記錄器的話):如果你想使用該記錄日志的功能,請(qǐng)參照如下的博客進(jìn)行實(shí)例化:
http://www.cnblogs.com/dathlin/p/7691693.html
訪問(wèn)測(cè)試項(xiàng)目
下面的一個(gè)項(xiàng)目是這個(gè)組件的訪問(wèn)測(cè)試項(xiàng)目,您可以進(jìn)行初步的訪問(wèn)的測(cè)試,免去了您寫(xiě)測(cè)試程序的麻煩,三菱的界面和西門(mén)子的界面幾乎是一致的??梢酝瑫r(shí)參考。該項(xiàng)目位于本篇文章開(kāi)始處的Gitbub源代碼里面的
下載地址為:HslCommunicationDemo.zip

演示項(xiàng)目
下面的三篇演示了具體如何去訪問(wèn)PLC的數(shù)據(jù),我們?cè)谠L問(wèn)完成后,通常需要進(jìn)行處理,以下的示例項(xiàng)目就演示了后臺(tái)從PLC讀取數(shù)據(jù)后,前臺(tái)顯示并推送給所有在線客戶端的功能,客戶端并進(jìn)行圖形化顯示,具有一定的參考意義,并且推送給網(wǎng)頁(yè)前端,項(xiàng)目地址為:
https://github.com/dathlin/RemoteMonitor
下面的圖片示例中的左邊程序就是服務(wù)器程序,它應(yīng)該和PLC直接連接并接入局域網(wǎng),然后把數(shù)據(jù)推送給客戶端顯示。注意:一個(gè)復(fù)雜高級(jí)的程序就應(yīng)該把處理邏輯程序和界面程序分開(kāi),比如這里的服務(wù)器程序?qū)崿F(xiàn)數(shù)據(jù)采集,推送,存儲(chǔ)。讓客戶端程序去實(shí)現(xiàn)數(shù)據(jù)的整理,分析,顯示,這樣即使客戶端程序因?yàn)锽UG奔潰,服務(wù)器端仍然可以正常的工作。

三菱PLC篇(下面列舉了三種配置方法,本組件支持二進(jìn)制和ASCII通訊,支持1E幀兼容協(xié)議訪問(wèn))
Q06UDV Plc的訪問(wèn)測(cè)試感謝:hwdq0012
fx5u plc的訪問(wèn)測(cè)試感謝:山楂
Q02CPU, L02CPU-CM : 本人測(cè)試
感謝:小懶豬雨中人 的測(cè)試,VB程序也可以調(diào)用本通訊庫(kù)
環(huán)境1:此處以GX Works3為示例,fx5u的配置如下:(感謝 山楂 提供的圖片)


環(huán)境2:此處以GX Works2為示例,測(cè)試PLC為L(zhǎng)02CPU,內(nèi)置了以太網(wǎng)協(xié)議


環(huán)境3:此處以GX Works2為示例,添加以太網(wǎng)模塊,型號(hào)為QJ71E71-100,組態(tài)里添加完成后進(jìn)行以太網(wǎng)的參數(shù)配置,此處需要注意的是:參數(shù)的配置對(duì)接下來(lái)的代碼中配置參數(shù)要一一對(duì)應(yīng)

注意:在PLC的以太網(wǎng)模塊的配置中,無(wú)法設(shè)置網(wǎng)絡(luò)號(hào)為0,也無(wú)法設(shè)置站號(hào)為0, 所以此處均設(shè)置為1,在C#程序中也使用上述的配置,在代碼中均配置為0,如果您自定義設(shè)置為網(wǎng)絡(luò)2, 站號(hào)8,那么在代碼中就要寫(xiě)對(duì)應(yīng)的數(shù)據(jù)。如果仍然通信失敗,重新測(cè)試0,0。
打開(kāi)設(shè)置:在上圖中的打開(kāi)設(shè)置選項(xiàng),進(jìn)行其他參數(shù)的配置,下圖只是舉了一個(gè)例子,開(kāi)通了4個(gè)端口來(lái)支持讀寫(xiě)操作:

端口號(hào)設(shè)置規(guī)則:
- 為了不與原先存在的系統(tǒng)發(fā)生沖突,您在添加自己的端口時(shí)盡量使用您自己的端口。
- 如果讀寫(xiě)都需要,盡可能的將讀取端口和寫(xiě)入端口區(qū)分開(kāi)來(lái),這樣做比較高性能。
- 如果您的網(wǎng)絡(luò)狀態(tài)不是特別穩(wěn)定,讀取端口使用2個(gè),一個(gè)受阻切換另一個(gè)讀取可以提升系統(tǒng)的穩(wěn)定性。
本文檔僅作組件的測(cè)試,所以只用了一個(gè)端口作為讀寫(xiě)。如果你的程序也使用了一個(gè)端口,那么你在讀取數(shù)據(jù)時(shí)候, 剛好也在寫(xiě)入(異步操作可能發(fā)生這樣的情況),那么寫(xiě)入會(huì)失敗!)(在長(zhǎng)連接模式下沒(méi)有這個(gè)問(wèn)題)
三菱PLC的數(shù)據(jù)主要由兩類(lèi)數(shù)據(jù)組成,位數(shù)據(jù)和字?jǐn)?shù)據(jù),在位數(shù)據(jù)中,例如X,Y,M,L都是位數(shù)據(jù),字?jǐn)?shù)據(jù)例如D,W。 兩類(lèi)的數(shù)據(jù)在讀取解碼上存在一點(diǎn)小差別。(事實(shí)上也可以先將16個(gè)M先賦值給一個(gè)D,讀取D數(shù)據(jù)再進(jìn)行解析, 在讀取M的數(shù)量比較多的時(shí)候,這樣操作效率更高)
初始化訪問(wèn)PLC對(duì)象
注意:如果你想采用ASCII來(lái)讀寫(xiě)數(shù)據(jù),請(qǐng)使用MelsecMcAsciiNet類(lèi),如果想采用1E幀協(xié)議,使用MelsecA1ENet類(lèi),除了實(shí)例化,其他的數(shù)據(jù)交互都是一樣的。
如果想使用本組件的數(shù)據(jù)讀取功能,必須先初始化數(shù)據(jù)訪問(wèn)對(duì)象,根據(jù)實(shí)際情況進(jìn)行數(shù)據(jù)的填入。 下面僅僅是測(cè)試中的數(shù)據(jù):
1 | private MelsecMcNet melsec_net = new MelsecMcNet( "192.168.0.1" , 6000 );
|
如上圖所示,只要指定了IP地址和端口號(hào)就完成了初始化的搭建了,當(dāng)然還支持一些額外的信息配置
1 2 3 | melsec_net.ConnectTimeOut = 2000; // 網(wǎng)絡(luò)連接的超時(shí)時(shí)間
melsec_net.NetworkNumber = 0x00; // 網(wǎng)絡(luò)號(hào)
melsec_net.NetworkStationNumber = 0x00; // 網(wǎng)絡(luò)站號(hào)
|
打開(kāi)連接,并可以判斷是否連接上
1 | melsec_net.ConnectClose( );
|
如果需要判斷,那么按照如下的操作
1 2 3 4 5 6 7 8 9 | OperateResult connect = melsec_net.ConnectServer( );
if (connect.IsSuccess)
{
MessageBox.Show( "連接成功!" );
}
else
{
MessageBox.Show( "連接失??!" );
}
|
說(shuō)明:對(duì)象應(yīng)該放在窗體類(lèi)下面,此處僅僅針對(duì)讀取一臺(tái)設(shè)備的plc,也可以在訪問(wèn)的方法中實(shí)例化局部對(duì)象, 初始化數(shù)據(jù),然后讀取,該對(duì)象幾乎不損耗內(nèi)存,內(nèi)存垃圾由CLR進(jìn)行自動(dòng)回收。此處測(cè)試方便,窗體的多個(gè)按鈕均連接同一臺(tái)PLC 設(shè)備,所以本窗體實(shí)例化一個(gè)對(duì)象即可。
關(guān)于兩種地址的表示方式
第一種,使用系統(tǒng)的類(lèi)來(lái)標(biāo)識(shí),比如M200,寫(xiě)成(MelsecDataType.M, 200)的表示形式,這樣也可以去MelsecDataType里面找到所有支持的數(shù)據(jù)類(lèi)型。
第二種,使用字符串表示,這個(gè)組件里所有的讀寫(xiě)操作提供字符串表示的重載方法,所有的支持訪問(wèn)的類(lèi)型對(duì)應(yīng)如下,字符串的表示方式存在十進(jìn)制和十六進(jìn)制的區(qū)別:
- 輸入繼電器:"X100","X1A0" // 字符串為十六進(jìn)制機(jī)制
- 輸出繼電器:"Y100" ,"Y1A0" // 字符串為十六進(jìn)制機(jī)制
- 內(nèi)部繼電器:"M100","M200" // 字符串為十進(jìn)制
- 鎖存繼電器:"L100" ,"L200" // 字符串為十進(jìn)制
- 報(bào)警器: "F100", "F200" // 字符串為十進(jìn)制
- 邊沿繼電器:"V100" , "V200" // 字符串為十進(jìn)制
- 鏈接繼電器:"B100" , "B1A0" // 字符串為十六進(jìn)制
- 步進(jìn)繼電器:"S100" , "S200" // 字符串為十進(jìn)制
- 數(shù)據(jù)寄存器:"D100", "D200" // 字符串為十進(jìn)制
- 鏈接寄存器:"W100" ,"W1A0" // 字符串為十六進(jìn)制
- 文件寄存器:"R100","R200" // 字符串為十進(jìn)制
展示一些簡(jiǎn)單實(shí)用基礎(chǔ)數(shù)據(jù)讀寫(xiě),這些數(shù)據(jù)的讀寫(xiě)沒(méi)有進(jìn)行嚴(yán)格的是否成功判斷(判斷方法參照后面的代碼),一般網(wǎng)絡(luò)良好的情況下都會(huì)成功,但不排除失敗,以下代碼僅作測(cè)試,所有沒(méi)有嚴(yán)格判斷是否成功:
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 | bool [] M100 = melsec_net.ReadBool( "M100" ,1).Content; // 讀取M100是否通,十進(jìn)制地址
bool [] X1A0 = melsec_net.ReadBool( "X1A0" ,1).Content; // 讀取X1A0是否通,十六進(jìn)制地址
bool [] Y1A0 = melsec_net.ReadBool( "Y1A0" ,1).Content; // 讀取Y1A0是否通,十六進(jìn)制地址
bool [] B1A0 = melsec_net.ReadBool( "B1A0" ,1).Content; // 讀取B1A0是否通,十六進(jìn)制地址
short short_D1000 = melsec_net.ReadInt16( "D1000" ).Content; // 讀取D1000的short值 ,W3C0,R3C0 效果是一樣的
ushort ushort_D1000 = melsec_net.ReadUInt16( "D1000" ).Content; // 讀取D1000的ushort值
int int_D1000 = melsec_net.ReadInt32( "D1000" ).Content; // 讀取D1000-D1001組成的int數(shù)據(jù)
uint uint_D1000 = melsec_net.ReadUInt32( "D1000" ).Content; // 讀取D1000-D1001組成的uint數(shù)據(jù)
float float_D1000 = melsec_net.ReadFloat( "D1000" ).Content; // 讀取D1000-D1001組成的float數(shù)據(jù)
long long_D1000 = melsec_net.ReadInt64( "D1000" ).Content; // 讀取D1000-D1003組成的long數(shù)據(jù)
ulong ulong_D1000 = melsec_net.ReadUInt64( "D1000" ).Content; // 讀取D1000-D1003組成的long數(shù)據(jù)
double double_D1000 = melsec_net.ReadDouble( "D1000" ).Content; // 讀取D1000-D1003組成的double數(shù)據(jù)
string str_D1000 = melsec_net.ReadString( "D1000" , 10).Content; // 讀取D1000-D1009組成的條碼數(shù)據(jù)
melsec_net.Write( "M100" , new bool [] { true } ); // 寫(xiě)入M100為通
melsec_net.Write( "Y1A0" , new bool [] { true } ); // 寫(xiě)入Y1A0為通
melsec_net.Write( "X1A0" , new bool [] { true } ); // 寫(xiě)入X1A0為通
melsec_net.Write( "B1A0" , new bool [] { true } ); // 寫(xiě)入B1A0為通
melsec_net.Write( "D1000" , ( short )1234); // 寫(xiě)入D1000 short值 ,W3C0,R3C0 效果是一樣的
melsec_net.Write( "D1000" , ( ushort )45678); // 寫(xiě)入D1000 ushort值
melsec_net.Write( "D1000" , 1234566); // 寫(xiě)入D1000 int值
melsec_net.Write( "D1000" , ( uint )1234566); // 寫(xiě)入D1000 uint值
melsec_net.Write( "D1000" , 123.456f); // 寫(xiě)入D1000 float值
melsec_net.Write( "D1000" , 123.456d); // 寫(xiě)入D1000 double值
melsec_net.Write( "D1000" , 123456661235123534L); // 寫(xiě)入D1000 long值
melsec_net.Write( "D1000" , "K123456789" ); // 寫(xiě)入D1000 string值
|
下面再分別講解嚴(yán)格的操作,以及批量化的復(fù)雜的讀寫(xiě)操作,假設(shè)你要讀取1000個(gè)M,循環(huán)讀取1千次可能要3秒鐘,如果用了下面的批量化讀取,只需要50ms,但是需要你對(duì)字節(jié)的原理比較熟悉才能得心應(yīng)手的處理
X,Y,M,L,F,V,B,S位數(shù)據(jù)的讀寫(xiě)說(shuō)明
- X 輸入繼電器
- Y 輸出繼電器
- M 內(nèi)部繼電器
- L 鎖存繼電器
- F 報(bào)警器
- V 邊沿繼電器
- B 鏈接繼電器
- S 步進(jìn)繼電器
本小節(jié)將展示八種位數(shù)據(jù)的讀取,雖然更多的時(shí)候只是讀取D數(shù)據(jù)即可,或者是將位數(shù)據(jù)批量挪到D數(shù)據(jù)中, 但是在此處仍然進(jìn)行介紹單獨(dú)的讀取X,Y,M,L,F,V,B,S,由于這八種讀取手法一致,故針對(duì)M數(shù)據(jù)進(jìn)行介紹,其他的您可以自己測(cè)試。
如下方法演示讀取了M200-M209這10個(gè)M的值,注意:讀取長(zhǎng)度必須為偶數(shù),即時(shí)寫(xiě)了奇數(shù),也會(huì)補(bǔ)齊至偶數(shù),讀取和寫(xiě)入的最大長(zhǎng)度為7168,否則報(bào)錯(cuò)。如需實(shí)際需求確實(shí)大于7168的,請(qǐng)分批次讀取。
返回值解析:如果讀取正常則共返回10個(gè)字節(jié)的數(shù)據(jù),以下示例數(shù)據(jù)進(jìn)行批量化的讀取
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< bool []> read = melsec_net.ReadBool( "M200" , 10);
if (read.IsSuccess)
{
// 成功讀取,True代表通,F(xiàn)alse代表不通
bool M200 = read.Content[0];
bool M201 = read.Content[1];
bool M202 = read.Content[2];
bool M203 = read.Content[3];
bool M204 = read.Content[4];
bool M205 = read.Content[5];
bool M206 = read.Content[6];
bool M207 = read.Content[7];
bool M208 = read.Content[8];
bool M209 = read.Content[9];
// 顯示
}
else
{
//失敗讀取,顯示失敗信息
MessageBox.Show(read.ToMessageShowString());
}
}
private void userButton21_Click( object sender, EventArgs e)
{
// X100-X10F讀取顯示
OperateResult< bool []> read = melsec_net.ReadBool( "X200" , 16);
if (read.IsSuccess)
{
// 成功讀取,True代表通,F(xiàn)alse代表不通
bool X200 = read.Content[0];
bool X201 = read.Content[1];
bool X202 = read.Content[2];
bool X203 = read.Content[3];
bool X204 = read.Content[4];
bool X205 = read.Content[5];
bool X206 = read.Content[6];
bool X207 = read.Content[7];
bool X208 = read.Content[8];
bool X209 = read.Content[9];
bool X20A = read.Content[10];
bool X20B = read.Content[11];
bool X20C = read.Content[12];
bool X20D = read.Content[13];
bool X20E = read.Content[14];
bool X20F = read.Content[15];
// 顯示
}
else
{
//失敗讀取,顯示失敗信息
MessageBox.Show(read.ToMessageShowString());
}
}
private void userButton3_Click( object sender, EventArgs e)
{
// M100-M104 寫(xiě)入測(cè)試 此處寫(xiě)入后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.Write( "M100" , values);
if (write.IsSuccess)
{
TextBoxAppendStringLine( "寫(xiě)入成功" );
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
錯(cuò)誤說(shuō)明:有可能因?yàn)檎咎?hào)網(wǎng)絡(luò)號(hào)沒(méi)有配置正確返回有錯(cuò)誤代號(hào)沒(méi)有錯(cuò)誤信息, 也有可能因?yàn)榫W(wǎng)絡(luò)問(wèn)題導(dǎo)致沒(méi)有連接上,此時(shí)會(huì)有連接不上的錯(cuò)誤信息。
下面展示的是后臺(tái)線程循環(huán)讀取的情況,事實(shí)上在實(shí)際的使用過(guò)程中經(jīng)常會(huì)碰見(jiàn)的情況。下面的方法需要 放到單獨(dú)的線程中,同理,訪問(wèn)D數(shù)據(jù)時(shí)也是按照下面循環(huán)就行,此處不再贅述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //后臺(tái)循環(huán)讀取PLC數(shù)據(jù) M200開(kāi)始10個(gè)字 也即是M200-M209
while ( true )
{
OperateResult< bool []> read = melsec_net.ReadFromPLC( "M200" , 10);
if (read.IsSuccess)
{
//成功讀取,委托顯示
textBox2.BeginInvoke( new Action( delegate
{
textBox2.Text = "M201:" + (read.Content[1] ? "通" : "斷" );
}));
}
else
{
//失敗讀取,應(yīng)該對(duì)失敗信息進(jìn)行日志記錄,不應(yīng)該顯示,測(cè)試訪問(wèn)時(shí)才適合顯示錯(cuò)誤信息
LogHelper.save(read.ToMessageShowString());
}
System.Threading.Thread.Sleep(1000); //決定了訪問(wèn)的頻率
}
|
D,W,R字?jǐn)?shù)據(jù)的讀寫(xiě)操作
此處讀取針對(duì)中間存在整數(shù)數(shù)據(jù)的情況,因?yàn)閮烧咦x取方式相同,故而只演示一種數(shù)據(jù)讀取, 使用該組件讀取數(shù)據(jù),一次最多讀取或?qū)懭?60個(gè)字,超出則失敗。 如果讀取的長(zhǎng)度確實(shí)超過(guò)限制,請(qǐng)考慮分批讀取。
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 | private void userButton2_Click( object sender, EventArgs e)
{
// D100-D104讀取
OperateResult< byte []> read = melsec_net.Read( "D100" , 5);
if (read.IsSuccess)
{
// 成功讀取,提取各自的值,此處的值有個(gè)前提假設(shè),假設(shè)PLC上的數(shù)據(jù)是有符號(hào)的數(shù)據(jù),表示-32768-32767
short D100 = melsec_net.ByteTransform.TransInt16(read.Content, 0);
short D101 = melsec_net.ByteTransform.TransInt16( read.Content, 2);
short D102 = melsec_net.ByteTransform.TransInt16( read.Content, 4);
short D103 = melsec_net.ByteTransform.TransInt16( read.Content, 6);
short D104 = melsec_net.ByteTransform.TransInt16( read.Content, 8);
TextBoxAppendStringLine( "D100:" + D100);
TextBoxAppendStringLine( "D101:" + D101);
TextBoxAppendStringLine( "D102:" + D102);
TextBoxAppendStringLine( "D103:" + D103);
TextBoxAppendStringLine( "D104:" + D104);
}
else
{
//失敗讀取
MessageBox.Show(read.ToMessageShowString());
}
}
private void userButton4_Click( object sender, EventArgs e )
{
short [] values = new short [5] { 1335, 8765, 1234, 4567, -2563 };
// D100為1234,D101為8765,D102為1234,D103為4567,D104為-2563
OperateResult write = melsec_net.Write( "D6000" , values );
if (write.IsSuccess)
{
//成功寫(xiě)入
TextBoxAppendStringLine( "寫(xiě)入成功" );
}
else
{
MessageBox.Show( write.ToMessageShowString( ) );
}
}
|
ASCII字符串?dāng)?shù)據(jù)的讀寫(xiě)
在實(shí)際項(xiàng)目中,有可能會(huì)碰到PLC存儲(chǔ)了規(guī)格數(shù)據(jù),或是條碼數(shù)據(jù),這些數(shù)據(jù)是以ASCII編碼形式存在, 我們需要把數(shù)據(jù)進(jìn)行讀取出來(lái)用于顯示,保存等操作。下面演示讀取指定長(zhǎng)度的條碼數(shù)據(jù),數(shù)據(jù)的數(shù)據(jù)存放在D2000-D2004中, 長(zhǎng)度應(yīng)該為存儲(chǔ)條碼的最大長(zhǎng)度,也即是占用了5個(gè)D,一個(gè)D可以存儲(chǔ)2個(gè)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)
{
//讀取字符串?dāng)?shù)據(jù),共計(jì)10個(gè)字節(jié)長(zhǎng)度
OperateResult< byte []> read = melsec_net.Read( "D2000" , 5);
if (read.IsSuccess)
{
//成功讀取
textBox2.Text = Encoding.ASCII.GetString(read.Content);
}
else
{
//失敗讀取
MessageBox.Show(read.ToMessageShowString());
}
}
private void button8_Click( object sender, EventArgs e)
{
//寫(xiě)字符串,如果寫(xiě)入K12345678這9個(gè)字符,讀取出來(lái)時(shí)末尾會(huì)補(bǔ)0
OperateResult write = melsec_net.WriteAsciiString( "D2000" , "K123456789" );
if (write.IsSuccess)
{
textBox2.Text = "寫(xiě)入成功" ;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
需要注意的是,如果第一次在D2000-D2004中寫(xiě)入了"K123456789",第二次寫(xiě)入了"K6666",那么讀取D2000-D2004的條碼數(shù)據(jù)會(huì)讀取到 K666656789,如果要避免這種情況,則需要在寫(xiě)入條碼的時(shí)候,指定總長(zhǎng)度,該長(zhǎng)度必須為偶數(shù), 不然也會(huì)自動(dòng)補(bǔ)0,小于該長(zhǎng)度時(shí),自動(dòng)補(bǔ)零,大于該長(zhǎng)度時(shí),自動(dòng)截?cái)鄶?shù)據(jù),具體的使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | private void button8_Click( object sender, EventArgs e)
{
//寫(xiě)字符串,本次寫(xiě)入指定了10個(gè)長(zhǎng)度的字符,其余的D的數(shù)據(jù)將被清空,是一種安全的寫(xiě)入方式
OperateResult write = melsec_net.WriteAsciiString( "D2000" , "K6666" , 10);
if (write.IsSuccess)
{
textBox2.Text = "寫(xiě)入成功" ;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
中文及特殊字符的讀寫(xiě)
在需要讀寫(xiě)復(fù)雜的字符數(shù)據(jù)時(shí),上述的ASCII編碼已經(jīng)不能滿足要求,雖然使用讀寫(xiě)的基礎(chǔ)方法可以實(shí)現(xiàn)任意數(shù)據(jù)的讀寫(xiě), 但是此處為了方便,還是提供了一個(gè)方便的方法來(lái)讀寫(xiě)中文數(shù)據(jù),采用Unicode編碼的字符, 該編碼下的一個(gè)字符占用一個(gè)D或W來(lái)存儲(chǔ)。如下將演示,讀寫(xiě)方法,基本用途和上述 ASCII編碼的讀寫(xiě)一致。
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)
{
//讀中文,存儲(chǔ)在D3000-D3009
OperateResult< byte []> read = melsec_net.Read( "D3000" , 10);
if (read.IsSuccess)
{
//解析數(shù)據(jù)
textBox2.Text = Encoding.Unicode.GetString(read.Content);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
private void button10_Click( object sender, EventArgs e)
{
//寫(xiě)中文 D3000-D3009,該10含義為中文字符數(shù)
OperateResult write = melsec_net.WriteUnicodeString( "D3000" , "測(cè)試數(shù)據(jù)test" , 10);
if (write.IsSuccess)
{
textBox2.Text = "寫(xiě)入成功" ;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
一個(gè)實(shí)際中復(fù)雜的例子演示
實(shí)際中可能碰到的情況會(huì)很復(fù)雜,一臺(tái)設(shè)備中需要上傳的數(shù)據(jù)包含了溫度,壓力,產(chǎn)量,規(guī)格等等信息,在一串?dāng)?shù)據(jù)中 會(huì)包含各種各樣的不同的數(shù)據(jù),上述的讀取D,讀取M,讀取條碼的方式不太好用,所以此處做一個(gè)完整示例的演示,假設(shè)我們需要讀取 D4000-D4009的數(shù)據(jù),假設(shè)D4000存放了溫度數(shù)據(jù),55.1℃在D中為551,D4001存放了壓力數(shù)據(jù),1.23MPa在D中存放為123,D4002存放了 設(shè)備狀態(tài),0為停止,1為運(yùn)行,D4003存放了產(chǎn)量,1000就是指1000個(gè),D4004備用,D4005-D4009存放了規(guī)格,以下代碼演示如何去解析數(shù)據(jù):
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)
{
//解析復(fù)雜數(shù)據(jù)
OperateResult< byte []> read = melsec_net.Read( "D4000" , 10);
if (read.IsSuccess)
{
double 溫度 = melsec_net.ByteTransform.TransInt16(read.Content, 0) / 10d; //索引很重要
double 壓力 = melsec_net.ByteTransform.TransInt16(read.Content, 2) / 100d;
bool IsRun = melsec_net.ByteTransform.TransInt16(read.Content, 4) == 1;
int 產(chǎn)量 = BitConverter.ToInt16(read.Content, 6);
string 規(guī)格 = Encoding.ASCII.GetString(read.Content, 10, 10);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
究極數(shù)據(jù)讀取展示,用于測(cè)試你自己的報(bào)文以及擴(kuò)展自己的更高級(jí),更變態(tài)的API,以下演示,使用這個(gè)高級(jí)模式,寫(xiě)入M100,True的操作:
我們要寫(xiě)入的字節(jié)數(shù)組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" );
// 直接使用報(bào)文進(jìn)行
OperateResult< byte []> operate = melsec_net.ReadFromCoreServer(buffer);
if (operate.IsSuccess)
{
// 返回PLC的報(bào)文反饋,需要自己對(duì)報(bào)文進(jìn)行結(jié)果分析
MessageBox.Show(HslCommunication.BasicFramework.SoftBasic.ByteToHexString(operate.Content));
}
else
{
// 網(wǎng)絡(luò)原因?qū)е碌氖?/code>
MessageBox.Show(operate.ToMessageShowString());
}
}
|
更詳細(xì)的信息,可以參照源代碼里面的測(cè)試項(xiàng)目。
創(chuàng)作不易,感謝打賞
西門(mén)子篇參見(jiàn)另一篇博客:http://www.cnblogs.com/dathlin/p/8685855.html
|