この記事ではPLCNEXT AXC-F-2152 ControllerからMODBUS-TCP/IP Serverを立ち上げ、CodesysとTwinCAT3+TF6250からModbus TCP/IP Clientを実装しデータをやりとりします。PLCNEXT EnginneringではModbus TCP/IPのライブラリがDefaultインストールされていないのでPLCNEXT StoreからDownloadすることができます。
どうぞよろしくお願いします。
Library Download
下記のLinkにアクセスします。
https://www.plcnextstore.com/world/app/1443
Loginをクリックします。
Username や Passwordし、Loginをします。
Choose your Version
LibraryはダウンロードするVersionが赤枠で設定できます。
Prices
もちろん、無料のものがあれば有料なLibraryもあります。
Library?Function Extension?Runtime?
LibraryというのはPLCNEXT EngineerにImportできるFileです。ImportされたLibraryはFunction Blockとしてプロジェクト内で使用できます。
Function Extension/Runtimeは直接PLCNEXT Runtimeにインストールするもので、場合によりインストールしたあとに設定Fileを修正する必要があります。
Supported Devices
そしてSupportできるDeviceにもList-upされています。
Manual
LibraryのインストールやFunction Block説明のManualもDownloadできます。
Change Logs
そして各Versionのメイン変更も記載しています。
Download
Loginしたら、Installのところがクリックできなくなります。
DownloadをクリックしLibraryをDownloadします。
ライセンスに同意し>Downloadします。
Zip FileがDownloadしました。
解凍すると、DocumentsとFilesがあります。
Function Block
LibraryのすべてのFunction Blockを紹介するのは無理ありますので、今回記事で使うFunction Blockだけ紹介します。
MB_TCP_Server
こちらのFunction Blockを使用することによりPLCNEXT AXC F2152 Cotnroller内でModbus TCP/IP Serverを立ち上げることができます。
VAR_INPUT
xActivate | BOOL | 立ち上げ=Block有効たち下げ=Block無効 |
xAcknowledge | BOOL | 立ち上げ=Error MessageをClearする(でもBlock自体は初期化しない) |
xAutoAck | BOOL | True=Errorを自動AckするErro発生するとwDiagCodeとwAddDiagCodeが1Cycle出力する |
xUDP | BOOL | Communication Protocol選択。False=Modbus-TCP,True=Modbu-UDP |
strBindIp | STRING | SeverのSocketIP、文字列です。Example:”192.168.1.1”Emptyや0.0.0.0ならSystemが自動的適切なEthernet Adapterを選択する。 |
uiBindPort | UINT | ServerのPort番号を設定します。0=Systemをランダムで選ぶ。注意するのはModeにより変わります。UDP=使用しないTCP=Port指定、1つ以上のServerが存在する場合Portを被らないようにしてください。 |
strDestIp | STRING | ClientのIPを指定する。TCP=0.0.0.0は任意のClientUDP=使用しない |
uiDestPort | UINT | ClientのPortを指定するTCP=0なら任意のPortUDP=使用しない |
tReconnectDelay | TIME | 再接続の時間間隔です。 |
tTimeout | Time | Timeoutです。 |
VAR_OUTPUT
xActive | BOOL | False=Block実行してないTrue=Block実行中 |
xConnected | BOOL | TCPの場合:True=Clientと接続するUDPの場合:True=MB_TCP_SocketがConnectionを開通した |
xError | BOOL | 1=Errorあり |
wDiagCode | WORD | Error発生ときの情報1 |
wAddDiagCode | WORD | Error発生ときの情報2 |
udtDiag | MB_TCP_UDT_SER_DIAG | Error発生ときの情報3 |
VAR_INOUT
arrHoldingRegisters | MB_TCP_ARR_W_0_65535 | 長さ65535のWord配列、Holding registersです。Function Codes3,6,16,23でアクセスできます。 |
arrInputRegisters | MB_TCP_ARR_W_0_65535 | 長さ65535のWord配列、Input registersです。Function Codes4でアクセスできます。 |
arrInputs | MB_TCP_ARR_X_0_65535 | 長さ65535のBit配列、Discrete inputsです。Function Codes4でアクセスできます。 |
arrCoils | MB_TCP_ARR_X_0_65535 | 長さ65535のBit配列、coilsです。Function Codes1,15でアクセスできます。 |
PLCNEXT Side
ではDownloadしたLibraryをProjectにImportし、Modbus TCP/IP Serverを立ち上げるプログラムを作成します。
Reference Link
http://soup01.com/ja/category/plcnext/
Add Library
Components>Libraries>右クリック>Add User Libraryします。
PLCNEXT StoreからダウンロードしたLibraryを選び>Openします。
Components>Programmingを開くとMdobus_TCP_XXのFolderが出てきます。
そのXXはライブラリのバージョンです。
.
Program
LibraryをImportしたところで、次はModbus TCP/IP Serverを立ち上げたいと思います。
Variables
以下の変数を追加します。
この4つはModbusのRegister変数です。0-65535の固定長さだそうです。
Variables | Type |
arrCoils | MB_TCP_ARR_X_0_65535 |
arrHoldingRegisters | MB_TCP_ARR_W_0_65535 |
arrInputRegisters | MB_TCP_ARR_W_0_65535 |
arrInputs | MB_TCP_ARR_X_0_65535 |
Code
Modbus TCP/IP Serverを起動するFunction Blockを呼び出します。
Server側がエラー発生した場合xActivateをFalseし>xErrorがFalseになったらまたxActivateをTrueし、Modbus TCP/IP Serverを立ち上げる簡単Loopです。
myServer( xActivate:=xActivate ,xAcknowledge:=xAcknowledge ,xAutoAck:=xAutoAck ,xUDP:=FALSE ,strBindIp:=’192.168.1.11′ ,uiBindPort:=502 ,strDestIp:=’0.0.0.0′ ,uiDestPort:=0 ,tTimeout:=T#2s ,tReconnectDelay:=T#1s ,arrHoldingRegisters:=arrHoldingRegisters ,arrInputRegisters:=arrInputRegisters ,arrCoils:=arrCoils ,arrInputs:=arrInputs ,xActive=>xActive ,xConnected=>xConnected ,xError=>xError ,wDiagCode=>wDiagCode ,wAddDiagCode=>wAddDiagCode ,udtDiag=>udtDiag ); r1(CLK:=myServer.xError); if r1.Q THEN count:=count+1; xActivate:=False; END_IF if not xActivate and not xError THEN xActivate:=True; END_IF //write the value arrInputRegisters[0]:=16#EF02; arrHoldingRegisters[1]:=16#ABEC; // arrInputs[1]:=TRUE; arrInputs[2]:=TRUE; |
Play with Codesys
最初にプログラムなしのCodesysから検証していきたいと思います。
Reference Link
Add Modbus-TCP Client
Modbus TCP/IP MasterとModbus TCP/IP Slaveを追加します。
Point1 – IP
Codesys Runtime実行してるMachineのIPを同じSubnetに設定してください。
Point2 – Master update time/Auto update
もしつながってTimeoutなど頻繁に発生したら、Response timeout(ms)やSocket timeout(ms)を調整してください。
Auto=ReconnectのCheck-boxを入れるとCodesys Runtimeが自動的に再接続します。
Point3 – Client Connection Setup
もちろんModbus TCP/IP Server側のIP(今回の記事はPLC NEXT AXC F 2152 Controller)、
あおtはUnit ID、TimeoutとアクセスPortを設定してください。
Point4 – Modbus slave Channel setup
Slave ChannelにInput Registers/Multiple Registers/Discrete Inputs/Multiple Coilsを作成します。
Point5 – Slave I/O Mapping
もちろんUser Programの変数と紐付けを忘れずに。
Program
プログラムは難しくないので、単なるいくつかのBytesを与え変更し、両方も確認するだけです。
VAR
PROGRAM PLC_PRG VAR data:WORD; Modbus_Function16:ARRAY[0..99]OF WORD; //Q Modbus_Function4:ARRAY[0..99]OF WORD; //I Modbus_Function2:ARRAY[0..12]OF BYTE; //I Modbus_Function15:ARRAY[0..12]OF BYTE; //Q END_VAR |
Code
Modbus_Function16[0]:=16#1234; Modbus_Function16[99]:=16#4567; Modbus_Function16[10]:=Modbus_Function4[0]; Modbus_Function16[11]:=Modbus_Function4[1]; Modbus_Function15[0].0:=TRUE; Modbus_Function15[0].7:=TRUE; Modbus_Function15[1].1:=Modbus_Function2[0].1; Modbus_Function15[1].2:=Modbus_Function2[0].2; |
Result
ProjectをDownloadしClientとServerが接続してるかを確認します。
そしてPLCNEXT EngineeringとCodesys IDEで両方も現在値を確認しましょう。
Play with TwinCAT
Reference Link
FlowChart
制御の流れはすごく簡単です。Connectionパラメタを初期化>Holding Regisiterに与えを書き込む>Input Registerの現在値を読み込む>Inputsを読み込む>Coilsを書き込む>また最初にから始まるLoopです。
途中でエラーが出たらErrorのStepに飛び込み、Resetしたらまた最初にからやり直す。
Program
FB_MBWriteRegs・FB_MBReadInputRegs・FB_MBReadInputs・FB_MBWriteCoilsを使用しModbus TCP/IP Serverをアクセスデータをやり取りします。
VAR
PROGRAM MAIN VAR //Function Blocks MB_WriteRegs :FB_MBWriteRegs; MB_ReadInputRegs :FB_MBReadInputRegs; MB_ReadInputs :FB_MBReadInputs; MB_WriteCoils :FB_MBWriteCoils; //Send.Receive Buffer ArraMB_Reads :ARRAY[0..99]OF WORD; ArraMB_Write :ARRAY[0..99]OF WORD; ArrayMB_ReadsInput :ARRAY[0..99]OF WORD; ArrayMB_ReadInputCoils :ARRAY[0..99]OF WORD; ArrayMB_WriteCoils :ARRAY[0..99]OF WORD; //System Flags bReset :BOOL; bError :BOOL; iStep :INT:=0; w0,w1,w2,w3 :WORD; b0,b1,b2,b3 :BOOL; wErrorWord :WORD; wErrorID :UDINT; i:INT; //Connection Parameters sIPAddr :STRING(15); nTCPPort :UINT; END_VAR VAR CONSTANT ciStepModbusConfigurationInit :INT:=0; ciStepModbusWriteRegisiters :INT:=10; ciStepModbusRegInputRegisters :INT:=20; ciStepMdobusReadInputs :INT:=30; ciStepModbusWriteCoils :INT:=40; ciStepModbusError :INT:=8000; END_VAR |
Code
CASE iStep OF ciStepModbusConfigurationInit: //IP and Port sIPAddr:=’192.168.1.11′; nTCPPort:=502; //Error Flag Reset wErrorID:=0; wErrorWord:=0; bError:=FALSE; bReset:=FALSE; //Function Execute Flags MB_WriteRegs.bExecute :=FALSE; MB_ReadInputRegs.bExecute :=FALSE; MB_ReadInputs.bExecute :=FALSE; MB_WriteCoils.bExecute :=FALSE; //Init MB_WriteRegs(); MB_ReadInputRegs(); MB_WriteCoils(); MB_ReadInputs(); //Step Jump IF NOT MB_WriteRegs.bBusy AND NOT MB_ReadInputRegs.bBusy AND NOT MB_ReadInputs.bBusy AND NOT MB_WriteCoils.bBusy THEN iStep:=ciStepModbusWriteRegisiters; END_IF ciStepModbusWriteRegisiters: //Write the Regisiters ArraMB_Write[0]:=ArraMB_Write[0]+1; ArraMB_Write[1]:=ArraMB_Write[1]-1; ArraMB_Write[98]:=ArraMB_Write[98]+1; ArraMB_Write[99]:=ArraMB_Write[99]-1; MB_WriteRegs.bExecute:=TRUE; //Function Blocks MB_WriteRegs( nUnitID:=16#34 ,nMBAddr:=300 ,nQuantity:=100 ,cbLength:=SIZEOF(ArraMB_Write) ,pSrcAddr:=ADR(ArraMB_Write) ); //Result IF NOT MB_WriteRegs.bBusy THEN IF MB_WriteRegs.bError THEN iStep:=ciStepModbusError; ELSE FOR i:=0 TO 99 DO ArrayMB_ReadsInput[i]:=16#00; END_FOR iStep:=ciStepModbusRegInputRegisters; END_IF END_IF ciStepModbusRegInputRegisters: //Flags MB_ReadInputRegs.bExecute:=TRUE; //Function Blocks MB_ReadInputRegs( nUnitID:=16#35 ,nMBAddr:=200 ,nQuantity:=100 ,cbLength:=SIZEOF(ArrayMB_ReadsInput) ,pDestAddr:=ADR(ArrayMB_ReadsInput) ); //Result IF NOT MB_ReadInputRegs.bBusy THEN IF MB_ReadInputRegs.bError THEN iStep:=ciStepModbusError; ELSE w0:=ArrayMB_ReadsInput[0]; w1:=ArrayMB_ReadsInput[1]; w2:=ArrayMB_ReadsInput[98]; w3:=ArrayMB_ReadsInput[99]; FOR i:=0 TO 99 DO ArrayMB_ReadInputCoils[i]:=16#00; END_FOR iStep:=ciStepMdobusReadInputs; END_IF END_IF ciStepMdobusReadInputs: //Flags MB_ReadInputs.bExecute:=TRUE; //Function Blocks MB_ReadInputs( nUnitID:=16#36 ,nQuantity:=100 ,nMBAddr:=0 ,cbLength:=SIZEOF(ArrayMB_ReadInputCoils) ,pDestAddr:=ADR(ArrayMB_ReadInputCoils) ); //Result IF NOT MB_ReadInputs.bBusy THEN IF MB_ReadInputs.bError THEN iStep:=ciStepModbusError; ELSE b0:=ArrayMB_ReadInputCoils[0].0; b1:=ArrayMB_ReadInputCoils[0].1; b2:=ArrayMB_ReadInputCoils[0].2; b3:=ArrayMB_ReadInputCoils[0].3; FOR i:=0 TO 99 DO ArrayMB_ReadInputCoils[i]:=16#00; END_FOR iStep:=ciStepModbusWriteCoils; END_IF END_IF ciStepModbusWriteCoils: //Flags MB_WriteCoils.bExecute:=TRUE; ArrayMB_WriteCoils[0].0:=TRUE; ArrayMB_WriteCoils[0].2:=TRUE; ArrayMB_WriteCoils[0].4:=TRUE; ArrayMB_WriteCoils[0].6:=TRUE; ArrayMB_WriteCoils[0].8:=TRUE; ArrayMB_WriteCoils[1]:=16#FFFF; //Function Blocks MB_WriteCoils( nUnitID:=16#38 ,nQuantity:=100 ,nMBAddr:=0 ,cbLength:=128 ,pSrcAddr:=ADR(ArrayMB_WriteCoils) ,cbLength:=SIZEOF(ArrayMB_WriteCoils) ); //Result IF NOT MB_WriteCoils.bBusy THEN IF MB_WriteCoils.bError THEN iStep:=ciStepModbusError; ELSE iStep:=ciStepModbusConfigurationInit; END_IF END_IF ciStepModbusError: bError:=TRUE; IF MB_WriteRegs.bError THEN wErrorWord:=1; wErrorID:=MB_WriteRegs.nErrId; ELSIF MB_ReadInputRegs.bError THEN wErrorWord:=2; wErrorID:=MB_ReadInputRegs.nErrId; ELSIF MB_ReadInputs.bError THEN wErrorWord:=3; wErrorID:=MB_ReadInputs.nErrId; ELSIF MB_WriteCoils.bError THEN wErrorWord:=4; wErrorID:=MB_WriteCoils.nErrId; END_IF; IF bReset THEN iStep:=ciStepModbusConfigurationInit; END_IF; END_CASE |
Result
両方の与えを確認しデータ一致しているかをCheckしましょう。
Source Code
ここでPLCNEXTとTwinCATのProject filesを一式ダウンロードできます。
https://github.com/soup01Threes/TwinCAT3/blob/main/TwinCAT%20ProjectModbusTCP_Client_withPLCNEXT.zip