今回はOMRONのFINSコマンドを説明します。
※Ethernet=Binary通信のみ
※FINSコマンドのマニュアルが手元にないと理解できないので前回を参考に入手してください
フレーム構成の確認
コマンドフレーム構成(PC→PLC)
No | 項目 | 値 | 備考 |
1 | ※TCPのみ ヘッダ | FINS | Asciiコード (0x46494E53) |
2 | ※TCPのみ データ長 | ******** | No3以降のデータ長 |
3 | ※TCPのみ コマンド | 0x00000000 0x00000002 | ノードアドレス情報送信 通常コマンド |
4 | ※TCPのみ エラーコード | 0x00000000 | 固定値 |
5 | ※TCPのみ クライアント ノードアドレス | 0x00000000 | No3=0の場合のみ有効 Connect後に1回だけ送信 (2回以上送信しないこと) |
6 | ICF | 0x80 | Bit7=1固定 Bit6=コマンド(0) Bit0=レスポンス要(0) |
7 | RSV | 0x00 | 固定値 |
8 | GCT *許容ブリッジ通過数 | 0x02 (or 0x07) | 基本的に0x02 (8階層までのネットワーク越えを使う場合) |
9 | DNA *相手先ネットワークアドレス | 0x00 0x01~7F | 同一ネットワーク内 他のネットワーク→相手先ネットワーク |
10 | DA1 *相手先ノードアドレス | 0x00 (0x01~20) (0x01~FE) (0xFF) | 自PLC内 (Controller Linkの場合) (Ethernetの場合) (一斉同報) |
11 | DA2 *相手先号機アドレス | 0x00 (0xFE) (0x10~1F) (0xE1) | CPUユニット (ControllerLink or Ethernetユニット) (CPU高機能ユニット) (INNERボード) |
12 | SNA *発信元ネットワークアドレス | 0x00 0x01~7F | 同一ネットワーク内 他のネットワーク→発信元ネットワーク |
13 | SA1 *発信元ノードアドレス | 0x01~FE | TCP:レスポンスのNo5を指定 UDP:PC側IPアドレスの4桁目 |
14 | SA2 *発信元号機アドレス | 0x00 | 固定値(CPU扱い) |
15 | SID *サービスID | 0x00~FF | 任意 発信元の識別子 |
16 | コマンドコード | 0x0101 0x0102 など | 読出 書込 |
17 | テキスト | **** | デバイス、書込値など |
TCPの場合はConnect後に1回だけノードアドレス情報送信(≒問合せ)を実行し、その応答データ内にあるクライアントノードアドレスを使うのが確実です。(詳細は後述)
No1~No5はTCPの場合のみ必要になります。しかもFINSコマンドのマニュアルには記載されていないので注意してください。Ethernetユニットのアプリケーション構築編(SBCD-330E)を参照してください。(PDFファイル内の”FINS/TCPヘッダ”で検索すれば見つかるハズです。)
No9~No14についてはMCプロトコルと同様に他局(PLC間のネットワーク越しに)アクセスすることも可能ですが同一ネットワーク内でアクセスするパターンがほとんどと思うので上段側の値を使います。このあたりの説明はマニュアル内でコマンドとレスポンスを一緒くたに説明しているせいかすごく意味が分かりづらいです。
むしろ教えて欲しいくらい。。
レスポンスフレーム構成(PLC→PC)
No | 項目 | 値 | 備考 |
1 | ※TCPのみ ヘッダ | FINS | Asciiコード (0x46494E53) |
2 | ※TCPのみ データ長 | ******** | No3以降のデータ長 |
3 | ※TCPのみ コマンド | 0x00000001 0x00000002 0x00000003 | ノードアドレス情報送信 通常コマンド エラー通知→受信したら通信回線Close |
4 | ※TCPのみ エラーコード | ******** | 正常時は0x00000000 No3=2の場合はチェック不要 |
5 | ※TCPのみ クライアント側(PC側)の ノードアドレス | 0x00000000 ~ 0x000000FE | コマンドのNo3=0の場合のみ有効 コマンドのNo13(SA1)に指定するデータ |
6 | ※TCPのみ サーバー側(PLC側)の ノードアドレス | 0x00000000 ~ 0x000000FE | コマンドのNo3=0の場合のみ有効 基本はPLCのIPアドレスの4桁目 |
7 | ICF | 0xC1 | Bit7=1固定 Bit6=レスポンス(1) Bit0=レスポンス不要(1) |
8 | RSV | ** | システム使用→無視 |
9 | GCT *許容ブリッジ通過数 | ** | システム使用→無視 (送信値から越えたネットワーク数減算) |
10 | DNA *相手先ネットワークアドレス | 0x00 0x01~7F | 同一ネットワーク内 他のネットワーク→相手先ネットワーク |
11 | DA1 *相手先ノードアドレス | ** | PLCから見たPCのノードアドレス 基本はPC側のIPアドレスの4桁目 |
12 | DA2 *相手先号機アドレス | 0x00 | 固定値(CPU扱い) |
13 | SNA *発信元ネットワークアドレス | 0x00 (0x01~7F) | 自ネットワーク (相手先ネットワーク) |
14 | SA1 *発信元ノードアドレス | ** | (固定値=0?) |
15 | SA2 *発信元号機アドレス | 0x00 | CPU |
16 | SID *サービスID | ** | 送信したSIDと同じ値 |
17 | コマンドコード | 0x0101 0x0102 など | 読出 書込 |
18 | 終了コード | **** | 正常時は0x0000 |
19 | テキスト | **** | 読出データ 書込の場合はテキストが無い |
No1~No6はTCPの場合のみ必要になります。
チェックすべきはNo3、No5、No18、No19です。他は無視しても構いません。
No3で3を受信した場合はサーバ側(PLC側)が通信回線Closeするのでクライアント側(PC側)も通信回線をCloseする必要があります。(必要に応じて再度回線Open)
No18の終了コードは必ずチェックしてください。終了コードが0ならば正常ですが、通信自体が正常でも0以外になることがあります。終了コード(2Byte)のBit7(本体停止異常)とBit6(本体継続異常)は無視するのが手っ取り早い方法(0xFF3Fでマスク)です。
※PLCのバッテリ異常などが該当します。(プログラムモード(停止状態)も該当したような。。?)
コマンドとデバイスの指定
コマンドについて
※デバイス(=I/Oメモリ)の読み書きを抜粋
名称 | コマンド | 機能 |
I/Oメモリエリアの読出 | 0x0101 | 連続したエリアの読出 |
I/Oメモリエリアの書込 | 0x0102 | 連続したエリアへ書込 |
I/Oメモリエリアの複合読出 | 0x0104 | 不連続なエリアの読出 ※MCプロトコルのランダム読出相当 |
デバイス指定について
※CS/CJ/CP/NSJシリーズの場合(CVM1/CVシリーズはメモリ種別の値が異なる)
エリア種別 (デバイス種別) | データ 種類 | メモリ 種別 | 備考 |
チャネルI/O(CIO) | ビット | 0x30 | |
チャネル | 0xB0 | ||
内部補助リレー(W) | ビット | 0x31 | |
チャネル | 0xB1 | ||
保持リレー(H) | ビット | 0x32 | |
チャネル | 0xB2 | ||
特殊補助リレー(A) | ビット | 0x33 | |
チャネル | 0xB3 | ||
タイマアップフラグ(T) | ビット | 0x09 | |
タイマ現在値 | チャネル | 0x89 | |
カウンタアップフラグ(C) | ビット | 0x09 | 要アドレスオフセット |
カウンタ現在値 | チャネル | 0x89 | 要アドレスオフセット |
データメモリ(D) | ビット | 0x02 | |
チャネル | 0x82 | ||
拡張データメモリ(E) | ビット | 0x0A | ビットはCJ2のみアクセス可 |
※カレントバンク | チャネル | 0x98 | |
拡張データメモリ(E0~EF) | ビット | 0x20~2F | |
※バンク指定 | チャネル | 0xA0~AF | |
拡張データメモリ(E10~E18) | ビット | 0xE0~E8 | |
※バンク指定 | チャネル | 0x60~68 |
デバイス指定=I/Oメモリアドレス指定は4Byteです。
⇒メモリ種別1Byte+チャネル2Byte+ビット1Byte
※ビットは0~15(0x00~0F)、チャネル単位=ワード単位でアクセスする場合は0を指定
※タイマおよびカウンタのアップフラグのビット指定は常に0
タイマとカウンタはメモリ種別が同じことに注意してください。
このためカウンタのみ、2Byteのチャネル指定(カウンタ番号)に0x8000のオフセットが必要となります。
※マニュアルに一覧表があり、その直下に例としていくつかのI/Oメモリアドレス指定する値が記載されています。。が、その中にオフセットが必要なカウンタの例はありません。
○MRONさん、わざとですか?
と聞いてみたい。
ノード番号について
FINSコマンドのデータ送信時に発信元ノードアドレス(SA1)を指定する必要がありますが前述のとおりTCPの場合はConnect後にノードアドレス情報を問い合わせる方法が確実です。
その際のサンプルコードを下記に示します。
※CopyMemoryとして定義しているRTLMoveMemory APIについては前回を参照
Imports System.Net
Imports System.Net.Sockets
' 送受信バッファと変数間のコピーで使うAPI。ByRefにしてポインタ渡し。VBAやVB6.0以前だと"**** As Any"という型が使えたけどVB.net以降はAnyが使えない。
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Integer, ByVal length As Integer)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByRef Source As Byte, ByVal length As Integer)
Private _sock As Socket
Private _sa1 As Integer
Private Sub GetTCPNodeAddressSample()
Dim temp As Integer
Dim tx() As Byte ' 送信バッファ
Dim idx As Integer ' 送信バッファ アクセスIndex
Dim sentLen As Integer ' 送信Byte数
' 送信データ
ReDim tx(4095) ' バッファサイズは適当に。
idx = 0
' 固定値("FINS") *4Byte
tx(idx + 0) = CByte(&H46) ' F
tx(idx + 1) = CByte(&H49) ' I
tx(idx + 2) = CByte(&H4E) ' N
tx(idx + 3) = CByte(&H53) ' S
idx += 4
' データ長 *4Byte
' コマンド(4)+エラーコード(4)+クライアントノードアドレス(4)で計12Byte
temp = IPAddress.HostToNetworkOrder(12)
CopyMemory(tx(idx), temp, 4)
idx += 4
' コマンド(ノードアドレス情報送信) *4Byte
temp = 0
CopyMemory(tx(idx), temp, 4)
idx += 4
' エラーコード(=0)で固定 *4Byte
temp = 0
CopyMemory(tx(idx), temp, 4)
idx += 4
' クライアントノードアドレス *4Byte
' ※0を指定するとサーバ側(PLC側)がノードアドレスを返してくれる
temp = 0
CopyMemory(tx(idx), temp, 4)
idx += 4
' 送信
' _sockはSocketクラスで既にConnectされているものとする
sentLen = _sock.Send(tx, idx, SocketFlags.None)
Dim rx() As Byte ' 受信バッファ
Dim recvLen As Integer ' 受信Byte数
' 受信
ReDim rx(4095) ' バッファサイズは適当に。
recvLen = _sock.Receive(rx)
idx = 0
' チェック
If recvLen <> 24 Then Return ' この応答は24Byte以外そもそもNG。
' 固定値("FINS")
If rx(idx + 0) <> &H46 OrElse rx(idx + 1) <> &H49 OrElse rx(idx + 2) <> &H4E OrElse rx(idx + 3) <> &H53 Then Return
idx += 4
' データ長4Byteは無視
idx += 4
' コマンド4Byteはノードアドレス情報送信(=1)
CopyMemory(temp, rx(idx), 4)
temp = IPAddress.NetworkToHostOrder(temp)
If temp <> 1 Then Return
idx += 4
' エラーコード4Byte
CopyMemory(temp, rx(idx), 4)
temp = IPAddress.NetworkToHostOrder(temp)
If temp <> 0 Then Return
idx += 4
' クライアントノードアドレス4Byte
CopyMemory(temp, rx(idx), 4)
_sa1 = IPAddress.NetworkToHostOrder(temp) ' この値を次回以降の通信で指定するSA1として保持(値は1~254)
idx += 4
' サーバノードアドレス4Byteは無視
idx += 4
End Sub
このノードアドレス情報の問合せ・取得はConnect直後に1回だけ実行し、2回以上実行しないでください。
またマルチバイト(2,4,8などの複数Byte)の扱いには注意が必要です。FINSコマンドはビッグエンディアン、Windowsの内部処理はリトルエンディアンでバイトオーダーが逆となるため上位⇔下位Byteの並べ替えが必須になります。
上記では
System.Net.IPAddress.HostToNetworkOrderメソッド
System.Net.IPAddress.NetworkToHostOrderメソッド
を使用して並べ替えています。
ここでいうHostとはPC側=Windows内部処理データ、Networkとは実通信データを指します。内部処理データ→送信データにするときはHostToNetworkOrderを使い、受信データ→内部処理データにするときはNetworkToHostOrderを使うのが一般的です。が、実のところどちらを使っても処理結果自体は変わりません。(上位⇔下位の並べ替えなのでどちらもやることは同じ)
ちなみにUDPの場合は上記の方法が使えないのでPC側IPアドレスの4桁目をノードアドレスとして指定すればよいのですが、何かしらの手段でユーザが設定しなければならない作り方にすると使い勝手が良くありません。
なのでPC側のネットワークインターフェース情報を取得・検索する方法が便利です。
以下にサンプルコードを示します。
Option Explicit On
Imports System.Net
Imports System.Net.NetworkInformation
' 送受信バッファと変数間のコピーで使うAPI。ByRefにしてポインタ渡し。VBAやVB6.0以前だと"**** As Any"という型が使えたけどVB.net以降はAnyが使えない。
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByRef Source As Byte, ByVal length As Integer)
Private _sa1 As Integer
Private Sub GetUDPNodeAddressSample()
Dim nis() As NetworkInterface = NetworkInterface.GetAllNetworkInterfaces()
Dim ipprop As IPInterfaceProperties
Dim temp() As Byte
Dim plcip As Integer
Dim mask As Integer
Dim pcip As Integer
' PC側に接続先IPと同じサブネットが見つからない場合は仮に254としておく
_sa1 = 254
' 接続先PLCのIP(4Byte)情報
temp = IPAddress.Parse("192.168.1.1").GetAddressBytes()
CopyMemory(plcip, temp(0), 4)
' PCのネットワーク情報
For Each ni In nis
If ni.OperationalStatus = NetworkInformation.OperationalStatus.Up AndAlso _
ni.NetworkInterfaceType <> NetworkInformation.NetworkInterfaceType.Loopback AndAlso _
ni.NetworkInterfaceType <> NetworkInformation.NetworkInterfaceType.Tunnel Then
ipprop = ni.GetIPProperties()
If ipprop IsNot Nothing Then
For Each ip In ipprop.UnicastAddresses ' ←コレはIPv4とIPv6が含まれる
If ip.Address.AddressFamily = Sockets.AddressFamily.InterNetwork Then ' IPv4のみ検索対象とする
' サブネットマスク
temp = ip.IPv4Mask.GetAddressBytes()
CopyMemory(mask, temp(0), 4)
' IPアドレス
temp = ip.Address.GetAddressBytes()
CopyMemory(pcip, temp(0), 4)
' サブネットが接続先PLCと同じものを検出
If (pcip And mask) = (plcip And mask) Then
_sa1 = temp(3) ' IPの4桁目。
Return
End If
End If
Next
End If
End If
Next
End Sub
最近のPCにはネットワークインターフェースが複数あるパターン(無線LANと有線LANなど)も珍しくありませんので、何も考えずにPC内のネットワーク情報を取得したIPアドレスを使うような処理では使い物になりません。(たまたま運良く動く可能性もあるが。。)
接続先PLCのIPアドレスとサブネットマスクを使って同じネットワーク内のものであることをチェックすべきです。もし上記の処理で接続先PLCと同じサブネットのIPアドレスが見つからないのであればPLCと通信出来ないので。。
また知っているとは思いますが念のため。(仕事上でもたまに知らない人に出くわすことがあるんですよね。。)
IPアドレスとサブネットマスクで論理積(AND演算)をとった値(ネットワークアドレスなどと呼ぶ)が同じものは、同じサブネットである/同一ネットワークであると判別できます。
PLC | PC | ||
IPアドレス サブネットマスク ネットワークアドレス | 192.168.1.1 255.255.255.0 192.168.1.0 | 192.168.1.100 255.255.255.0 192.168.1.0 | 同じネットワーク |
IPアドレス サブネットマスク ネットワークアドレス | 192.168.1.1 255.255.255.0 192.168.1.0 | 192.168.2.100 255.255.255.0 192.168.2.0 | 別のネットワーク |
IPアドレス サブネットマスク ネットワークアドレス | 192.168.1.1 255.255.0.0 192.168.0.0 | 192.168.2.100 255.255.0.0 192.168.0.0 | 同じネットワーク (見かけない サブネットマスクだが。) |
デバイス読出
例としてD1000から100点読み出す場合のコードです。
Option Explicit On
Imports System.Net
Imports System.Net.Sockets
' 送受信バッファと変数間のコピーで使うAPI。ByRefにしてポインタ渡し。VBAやVB6.0以前だと"**** As Any"という型が使えたけどVB.net以降はAnyが使えない。
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Short, ByVal length As Integer)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Short, ByRef Source As Byte, ByVal length As Integer)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Integer, ByVal length As Integer)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByRef Source As Byte, ByVal length As Integer)
Private _sock As Socket
Private _sa1 As Integer
Private _IsTCP As Boolean
Private Sub ReadSample()
Dim temp As Integer
Dim temp2 As Short
Dim tx() As Byte ' 送信バッファ
Dim idx As Integer ' 送信バッファ アクセスIndex
Dim sentLen As Integer ' 送信Byte数
Dim cmd As Short = &H101 ' コマンド(読出)
Dim devTyp As Byte ' I/Oメモリ種別(デバイスコード)
Dim devChn As Short ' チャネル番号(デバイス番号)
Dim devBit As Byte ' ビット番号
Dim readLen As Short ' 読出点数
Dim iDNA As Integer = 0 ' 相手先ネットワークアドレス
Dim iDA1 As Integer = 0 ' 相手先ノードアドレス
Dim iDA2 As Integer = 0 ' 相手先号機アドレス
' 送信データ
ReDim tx(4095) ' バッファサイズは適当に。
idx = 0
' FINS/TCPヘッダ ※TCPの場合のみ
If _IsTCP Then
' 固定値("FINS") *4Byte
tx(idx + 0) = CByte(&H46) ' F
tx(idx + 1) = CByte(&H49) ' I
tx(idx + 2) = CByte(&H4E) ' N
tx(idx + 3) = CByte(&H53) ' S
idx += 4
' データ長 *4Byte
' コマンド(4)+エラーコード(4)+FINSヘッダ(10)+コマンドコード(2)で計12Byte
temp = IPAddress.HostToNetworkOrder(12)
CopyMemory(tx(idx), temp, 4)
idx += 4
' コマンド(通常コマンド) *4Byte
temp = IPAddress.HostToNetworkOrder(2)
CopyMemory(tx(idx), temp, 4)
idx += 4
' エラーコード(=0)で固定 *4Byte
temp = 0
CopyMemory(tx(idx), temp, 4)
idx += 4
End If
' FINSヘッダ *10Byte
tx(idx + 0) = CByte(&H80) ' ICF
tx(idx + 1) = CByte(&H0) ' RSV
tx(idx + 2) = CByte(&H2) ' GCT (2 or 7)
tx(idx + 3) = CByte(iDNA And &HFF) ' DNA
tx(idx + 4) = CByte(iDA1 And &HFF) ' DA1
tx(idx + 5) = CByte(iDA2 And &HFF) ' DA2
tx(idx + 6) = CByte(&H0) ' SNA
tx(idx + 7) = CByte(_sa1 And &HFF) ' SA1
tx(idx + 8) = CByte(&H0) ' SA2
tx(idx + 9) = CByte(&H0) ' SID
idx += 10
' コマンドコード *2Byte
temp2 = IPAddress.HostToNetworkOrder(cmd)
CopyMemory(tx(idx), temp2, 2)
idx += 2
' デバイス *1+2+1=4Byte
devTyp = CByte(&H82) ' D(チャネル単位)
idx += 1
devChn = 1000S ' チャネル番号
temp2 = IPAddress.HostToNetworkOrder(devChn)
CopyMemory(tx(idx), temp2, 2)
idx += 2
devBit = CByte(0) ' ビット番号
tx(idx) = devBit
idx += 1
' 読出点数 *2Byte
readLen = 100
temp2 = IPAddress.HostToNetworkOrder(readLen)
CopyMemory(tx(idx), temp2, 2)
idx += 2
' 送信
' _sockはSocketクラスで既にConnectされているものとする
sentLen = _sock.Send(tx, idx, SocketFlags.None)
Dim rx() As Byte ' 受信バッファ
Dim recvLen As Integer ' 受信Byte数
Dim resLen As Integer ' 応答データ長
Dim endCode As Short ' 終了コード
Dim readData() As Short ' 読出データ(デバイス値)
Dim i As Integer
' 受信
ReDim rx(4095) ' バッファサイズは適当に。
recvLen = _sock.Receive(rx)
idx = 0
' FINS/TCPヘッダ ※TCPの場合のみ
If _IsTCP Then
' 固定値("FINS")(4Byte)
If rx(idx + 0) <> &H46 OrElse rx(idx + 1) <> &H49 OrElse rx(idx + 2) <> &H4E OrElse rx(idx + 3) <> &H53 Then Return
idx += 4
' データ長(4Byte)
CopyMemory(temp, rx(idx), 4)
resLen = IPAddress.NetworkToHostOrder(temp)
If recvLen <> resLen + 8 Then Return
idx += 4
' コマンドコード(4Byte)
CopyMemory(temp, rx(idx), 4)
temp = IPAddress.NetworkToHostOrder(temp)
If temp = 3 Then
' FINSフレーム送信エラー通知
' ※通信回線Closeが必要
Return
End If
idx += 4
' エラーコード(4Byte)は無視
idx += 4
End If
' FINSヘッダ(10Byte)は無視
idx += 10
' コマンドコード(2Byte)は無視
idx += 2
' 終了コード(2Byte)
CopyMemory(temp2, rx(idx), 2)
endCode = IPAddress.NetworkToHostOrder(temp2)
endCode = endCode And &HFF3FS ' 本体停止異常(Bit7)と本体継続異常(Bit6)を無視 *PLC的には異常だが通信自体はOKなので。
idx += 2
If endCode <> 0 Then Return ' 正常時は0
' テキスト=読出データ(読出点数×2Byte)
If (recvLen - idx) = readLen * 2 Then Return ' ※チャネル単位読出は1要素=2Byte、ビット単位は1要素=1Byte
ReDim readData(readLen - 1)
For i = 0 To readLen - 1
CopyMemory(temp2, rx(idx), 2)
readData(i) = IPAddress.NetworkToHostOrder(temp2)
idx += 2
Next
End Sub
注意点は前回(MCプロトコル)とほぼ同じですが、MCプロトコルとは最大点数が異なります。
FINSコマンドでの1回の交信(送信&受信)の読出は最大999点(ワード)の制約があります。(ランダム読出の場合167)
デバイス書込
例としてW100.10をSET(1)する場合のコードです。
Option Explicit On
Imports System.Net
Imports System.Net.Sockets
' 送受信バッファと変数間のコピーで使うAPI。ByRefにしてポインタ渡し。VBAやVB6.0以前だと"**** As Any"という型が使えたけどVB.net以降はAnyが使えない。
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Short, ByVal length As Integer)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Short, ByRef Source As Byte, ByVal length As Integer)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Integer, ByVal length As Integer)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByRef Source As Byte, ByVal length As Integer)
Private _sock As Socket
Private _sa1 As Integer
Private _IsTCP As Boolean
Private Sub WriteSample()
Dim temp As Integer
Dim temp2 As Short
Dim tx() As Byte ' 送信バッファ
Dim idx As Integer ' 送信バッファ アクセスIndex
Dim sentLen As Integer ' 送信Byte数
Dim cmd As Short = &H102 ' コマンド(書込)
Dim devTyp As Byte ' I/Oメモリ種別(デバイスコード)
Dim devChn As Short ' チャネル番号(デバイス番号)
Dim devBit As Byte ' ビット番号
Dim writeLen As Short ' 書込点数
Dim iDNA As Integer = 0 ' 相手先ネットワークアドレス
Dim iDA1 As Integer = 0 ' 相手先ノードアドレス
Dim iDA2 As Integer = 0 ' 相手先号機アドレス
' 送信データ
ReDim tx(4095) ' バッファサイズは適当に。
idx = 0
' FINS/TCPヘッダ ※TCPの場合のみ
If _IsTCP Then
' 固定値("FINS") *4Byte
tx(idx + 0) = CByte(&H46) ' F
tx(idx + 1) = CByte(&H49) ' I
tx(idx + 2) = CByte(&H4E) ' N
tx(idx + 3) = CByte(&H53) ' S
idx += 4
' データ長 *4Byte
' コマンド(4)+エラーコード(4)+FINSヘッダ(10)+コマンドコード(2)で計12Byte
temp = IPAddress.HostToNetworkOrder(12)
CopyMemory(tx(idx), temp, 4)
idx += 4
' コマンド(通常コマンド) *4Byte
temp = IPAddress.HostToNetworkOrder(2)
CopyMemory(tx(idx), temp, 4)
idx += 4
' エラーコード(=0)で固定 *4Byte
temp = 0
CopyMemory(tx(idx), temp, 4)
idx += 4
End If
' FINSヘッダ *10Byte
tx(idx + 0) = CByte(&H80) ' ICF
tx(idx + 1) = CByte(&H0) ' RSV
tx(idx + 2) = CByte(&H2) ' GCT (2 or 7)
tx(idx + 3) = CByte(iDNA And &HFF) ' DNA
tx(idx + 4) = CByte(iDA1 And &HFF) ' DA1
tx(idx + 5) = CByte(iDA2 And &HFF) ' DA2
tx(idx + 6) = CByte(&H0) ' SNA
tx(idx + 7) = CByte(_sa1 And &HFF) ' SA1
tx(idx + 8) = CByte(&H0) ' SA2
tx(idx + 9) = CByte(&H0) ' SID
idx += 10
' コマンドコード *2Byte
temp2 = IPAddress.HostToNetworkOrder(cmd)
CopyMemory(tx(idx), temp2, 2)
idx += 2
' デバイス *1+2+1=4Byte
devTyp = CByte(&H31) ' W(ビット単位)
idx += 1
devChn = 100S ' チャネル番号
temp2 = IPAddress.HostToNetworkOrder(devChn)
CopyMemory(tx(idx), temp2, 2)
idx += 2
devBit = CByte(10) ' ビット番号
tx(idx) = devBit
idx += 1
' 書込点数 *2Byte
writeLen = 1
temp2 = IPAddress.HostToNetworkOrder(writeLen)
CopyMemory(tx(idx), temp2, 2)
idx += 2
' 送信
' _sockはSocketクラスで既にConnectされているものとする
sentLen = _sock.Send(tx, idx, SocketFlags.None)
Dim rx() As Byte ' 受信バッファ
Dim recvLen As Integer ' 受信Byte数
Dim resLen As Integer ' 応答データ長
Dim endCode As Short ' 終了コード
' 受信
ReDim rx(4095) ' バッファサイズは適当に。
recvLen = _sock.Receive(rx)
idx = 0
' FINS/TCPヘッダ ※TCPの場合のみ
If _IsTCP Then
' 固定値("FINS")(4Byte)
If rx(idx + 0) <> &H46 OrElse rx(idx + 1) <> &H49 OrElse rx(idx + 2) <> &H4E OrElse rx(idx + 3) <> &H53 Then Return
idx += 4
' データ長(4Byte)
CopyMemory(temp, rx(idx), 4)
resLen = IPAddress.NetworkToHostOrder(temp)
If recvLen <> resLen + 8 Then Return
idx += 4
' コマンドコード(4Byte)
CopyMemory(temp, rx(idx), 4)
temp = IPAddress.NetworkToHostOrder(temp)
If temp = 3 Then
' FINSフレーム送信エラー通知
' ※通信回線Closeが必要
Return
End If
idx += 4
' エラーコード(4Byte)は無視
idx += 4
End If
' FINSヘッダ(10Byte)は無視
idx += 10
' コマンドコード(2Byte)は無視
idx += 2
' 終了コード(2Byte)
CopyMemory(temp2, rx(idx), 2)
endCode = IPAddress.NetworkToHostOrder(temp2)
endCode = endCode And &HFF3FS ' 本体停止異常(Bit7)と本体継続異常(Bit6)を無視 *PLC的には異常だが通信自体はOKなので。
idx += 2
If endCode <> 0 Then Return ' 正常時は0
' テキスト=書込の場合は無し
End Sub
注意点は読出とほぼ同様です。
上記はビット1点書込ですがワード単位書込でも交信1回の最大数に制約があります。(詳細はマニュアルを参照ください)
※読出と書込では最大数が異なるので注意
PLC側の通信設定
OMRONの場合はCPU内蔵Ethernetと単体のEthernetユニットで設定はほぼ同じです。
- IPアドレス
任意値を設定します。192.168.***.***あたりを使うパターンが一般的に多いです。 - サブネットマスク
通常は255.255.255.0を使います。 - その他
基本的にFINSコマンドを使う上では設定不要です。
- FINS/UDPポート
初期値(9600)のままでOKです。ユーザ定義=任意値にすることも可能です。 - その他
基本的に初期値のままでOKです。
- FINS/TCPポート
初期値(9600)のままでOKです。ユーザ定義=任意値にすることも可能です。 - その他
基本的に初期値のままでOKです。
三菱と比較した場合、必要な設定はIPアドレスのみなので簡単です。
通信回線は同一ポートに対して16まで(?)使えます。
※マニュアルに記載されていたような気がするのですが記憶がイマイチ(笑)
コメント