This article about how to connection PLC to Unity3D Game Engine using Modbus Serial Communication.
in Unity3D Game Engine using C# (C Sharp) Script for Modbus Serial Communication and as a Modbus Master with serial communication setting 9600-even-8-one.
in PLC (Programmable Logic Controller) using PLC Ladder Programming for Modbus Serial Communication and as a Modbus Slave with Slave Address 1 (one).
In Case Study:
How to Fan speed control with 3D model of Unity3D using PLC input, and
how to turning ON/OFF in PLC outputs using simple 3D model of Unity3D.
Let's watch: my video demonstration on YouTube
Free Download : click here
in Unity3D Game Engine using C# (C Sharp) Script for Modbus Serial Communication and as a Modbus Master with serial communication setting 9600-even-8-one.
in PLC (Programmable Logic Controller) using PLC Ladder Programming for Modbus Serial Communication and as a Modbus Slave with Slave Address 1 (one).
In Case Study:
How to Fan speed control with 3D model of Unity3D using PLC input, and
how to turning ON/OFF in PLC outputs using simple 3D model of Unity3D.
Let's watch: my video demonstration on YouTube
PLC Connect to Unity3D Game Engine Using Modbus Serial Communication
Hardware List for PLC and Unity3D Game Engine
- PLC Modbus, I use Siemens S7-200 PLC
- RS232 PLC Cable, I use Simatic S7-200 RS232 PPI Multi-Master Cable
- USB to Serial RS232, I use BAFO USB to RS232 Converter
- Optional : Push Button connect to PLC Input
Hardware Connections of PLC and Unity3D Game Engine
Game Engine Software
Unity3D Free Download : https://unity3d.com/get-unity/downloadUnity3D Application for PC Standalone
Unity3D modbus application for Target Platform : Windows and Architecture : x86_64Free Download : click here
Project File for Unity3D Game Engine and PLC:
- Unity3D Project File : click here
- PLC Ladder Programming for Siemens S7-200 : click here
Input/output on Siemens PLC
- Input I0.0 for Fan Speed is -400
- Input I0.1 for Fan Speed is Increments -300
- Input I0.2 for Fan Speed is Increments 300
- Input I0.3 for Fan Speed is 400
- Input I0.4 for Fan Speed is 0 or Fan Stop
- If cylinder model clicked in Unity3D then all PLC output QB0 is ON
Unity3D Scripts
Unity3D c#
serialport.csusing UnityEngine;
using System.Collections;
using System.IO.Ports;
public class serialport : MonoBehaviour {
private SerialPort sp;
private const int BUFFER_SIZE = 128;
private byte[] frame16 = new byte[BUFFER_SIZE];
private byte[] frame03 = new byte[BUFFER_SIZE];
private byte state=0;
public string PortName ="COM1";
private float Scan_Time = 0.03f; //0.03 Seconds
public byte Connection_Status = 0;
//FUNCTION 03
public long F03_requests = 0;
public long F03_successful_requests = 0;
public long F03_failed_requests = 0;
public long F03_exception_errors =0;
private byte F03_Slave_Address = 1;
private byte F03_Function = 3;
private int F03_Starting_Address = 0;
private int F03_NumberofRegisters = 2;
private byte F03_Byte_Count;
public int[] F03_values;
//FUNCTION 16
public long F16_requests = 0;
public long F16_successful_requests = 0;
public long F16_failed_requests = 0;
public long F16_exception_errors =0;
private byte F16_Slave_Address = 1;
private byte F16_Function = 16;
private int F16_Starting_Address = 0;
private int F16_NumberofRegisters = 1;
private byte F16_Byte_Count;
public int[] F16_values;
void OnGUI() {
GUI.Label(new Rect(10, Screen.height-35, Screen.width, 35),"MODBUS SLAVE=1, SERIAL PORT=" + PortName + ", BAUD=9600, PARITY=EVEN, DATA=8BIT, STOPBITS=ONE");
}
void Start () {
Application.runInBackground = true;
F16_values = new int[F16_NumberofRegisters];
F03_values = new int[F03_NumberofRegisters];
bool Existing = false;
foreach(string str in SerialPort.GetPortNames())
{
if(str==PortName)Existing = true;
}
if (Existing) {
//Debug.Log(string.Format("Existing COM port: {0}", PortName));
}else{
//Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
Connection_Status = 5;
return;
}
sp = new SerialPort( PortName
, 9600
, Parity.Even
, 8
, StopBits.One);
OpenConnection();
state=0;
InvokeRepeating("Modbus_Update", 1.0f, Scan_Time);
}
public void OpenConnection() {
if (sp != null)
{
if (sp.IsOpen)
{
sp.Close();
print(PortName + " Closing port, because it was already open!");
Connection_Status = 1;
}
else
{
sp.Open();
sp.ReadTimeout = 1;
sp.WriteTimeout = 10;
print(PortName + " Port Opened!");
sp.BaseStream.Flush();
Connection_Status = 2;
}
}
else
{
if (sp.IsOpen)
{
print(PortName + " Port is already open");
Connection_Status = 3;
}
else
{
print(PortName + " Port == null");
Connection_Status = 4;
}
}
}
void OnApplicationQuit() {
if (sp != null) {
if (sp.IsOpen)sp.Close ();
}
}
void end() {
if (sp != null) {
if (sp.IsOpen)sp.Close ();
}
}
int calculateCRC(byte[] crcframe,int bufferSize)
{
int temp, temp2, flag;
temp = 0xFFFF;
for (int i = 0; i < bufferSize; i++)
{
temp = temp ^ crcframe[i];
for (int j = 1; j <= 8; j++)
{
flag = temp & 0x0001;
temp >>= 1;
if (flag==1)
temp ^= 0xA001;
}
}
temp2 = temp >> 8;
temp = (temp << 8) | temp2;
temp &= 0xFFFF;
return temp;
}
void Func16(){
//Debug.Log("FUNCTION 16");
F16_requests++;
F16_NumberofRegisters = (int)(F16_values.Length);
if (F16_NumberofRegisters < 1)F16_NumberofRegisters = 1;
if (F16_Starting_Address < 0)F16_Starting_Address = 0;
F16_Byte_Count = (byte)(F16_NumberofRegisters * 2);
int frameSize = 9 + (2 * F16_NumberofRegisters);
frame16[0] = F16_Slave_Address;
frame16[1] = F16_Function;
frame16[2] = (byte)(F16_Starting_Address >> 8);
frame16[3] = (byte)F16_Starting_Address;
frame16[4] = (byte)(F16_NumberofRegisters >> 8);
frame16[5] = (byte)F16_NumberofRegisters;
frame16[6] = F16_Byte_Count;
for (int i = 0; i < F16_NumberofRegisters; i++)
{
frame16[7 + 2 * i] = (byte)(F16_values[i] >> 8);
frame16[8 + 2 * i] = (byte)(F16_values[i]);
}
int crc16 = calculateCRC(frame16, frameSize - 2);
byte crcHi, crcLo;
crcHi = (byte)((crc16 >> 8)& 0xFF);
crcLo = (byte)(crc16 & 0xFF);
frame16[frameSize - 2] = crcHi;
frame16[frameSize - 1] = crcLo;
try {
sp.Write(frame16, 0, frameSize);
}
catch (System.Exception ex) {
if(ex.Message.Contains("The device does not recognize the command")){
//Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
//Application.Quit();
Connection_Status = 5;
return;
}
//Debug.LogWarning("Func16 " + ex.Message);
F16_exception_errors++;
Connection_Status = 6;
return;
}
sp.BaseStream.Flush();
}
void RecvFunc16(){
int Size = 0;
byte tempB = 0;
try {
tempB = (byte) sp.ReadByte();
while (tempB == F16_Slave_Address) {
while (Size<8) {
if(Size<BUFFER_SIZE)frame16[Size] = (byte) (tempB);
if(Size==7)break;
tempB = (byte) sp.ReadByte();
Size++;
Connection_Status = 100;
}
break;
}
}
catch (System.Exception ex) {
if(ex.Message.Contains("The device does not recognize the command")){
//Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
//Application.Quit();
Connection_Status = 5;
return;
}
//Debug.LogWarning("RecvFunc16 " + ex.Message);
F16_exception_errors++;
Connection_Status = 6;
return;
}
if(Size==7){
if(frame16[0]==F16_Slave_Address && frame16[1]==F16_Function){
int crc16 = calculateCRC(frame16, Size - 1);
int received_crc = ((frame16[Size - 1] << 8) | frame16[Size]);
if(crc16==received_crc){
F16_successful_requests++;
Connection_Status = 101;
return;
}
}
}
F16_failed_requests++;
}
void Func03(){
//Debug.Log("FUNCTION 03");
F03_requests++;
int frameSize = 8;
F03_NumberofRegisters = (int) (F03_values.Length);
if (F03_NumberofRegisters < 1)F03_NumberofRegisters = 1;
if (F03_Starting_Address < 0)F03_Starting_Address = 0;
F03_Byte_Count = (byte)(F03_NumberofRegisters * 2);
frame03[0] = F03_Slave_Address;
frame03[1] = F03_Function;
frame03[2] = (byte)(F03_Starting_Address >> 8);
frame03[3] = (byte)F03_Starting_Address;
frame03[4] = (byte)(F03_NumberofRegisters >> 8);
frame03[5] = (byte)F03_NumberofRegisters;
int crc16 = calculateCRC(frame03, frameSize - 2);
byte crcHi, crcLo;
crcHi = (byte)((crc16 >> 8)& 0xFF);
crcLo = (byte)(crc16 & 0xFF);
frame03[frameSize - 2] = crcHi;
frame03[frameSize - 1] = crcLo;
try {
sp.Write(frame03, 0, frameSize);
}
catch (System.Exception ex) {
if(ex.Message.Contains("The device does not recognize the command")){
//Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
//Application.Quit();
Connection_Status = 5;
return;
}
//Debug.LogWarning("Func03 " + ex);
F03_exception_errors++;
Connection_Status = 6;
return;
}
sp.BaseStream.Flush();
}
void RecvFunc03(){
int Size = 0;
byte tempB = 0;
int maxrev = 5 + F03_Byte_Count;
try {
tempB = (byte) sp.ReadByte();
while (tempB == F03_Slave_Address) {
while (Size<maxrev) {
frame03[Size] = (byte) (tempB);
if(Size==(maxrev-1))break;
tempB = (byte) sp.ReadByte();
Size++;
Connection_Status = 100;
}
break;
}
}
catch (System.Exception ex) {
if(ex.Message.Contains("The device does not recognize the command")){
//Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
//Application.Quit();
Connection_Status = 5;
return;
}
//Debug.LogWarning("RecvFunc03 " + ex.Message);
F03_exception_errors++;
Connection_Status = 6;
return;
}
if(Size==(maxrev-1)){
if(frame03[0]==F03_Slave_Address && frame03[1]==F03_Function){
int crc16 = calculateCRC(frame03, Size - 1);
int received_crc = ((frame03[Size - 1] << 8) | frame03[Size]);
if(crc16==received_crc && frame03[2]==F03_Byte_Count){
int index = 3;
for (int i = 0; i < F03_NumberofRegisters; i++)
{
F03_values[i] = (int)((frame03[index] << 8) | frame03[index + 1]);
index += 2;
Connection_Status = 102;
}
F03_successful_requests++;
return;
}
}
}
F03_failed_requests++;
}
void Modbus_Update()
{
switch (state)
{
case 0:
Func16();
state=1;
break;
case 1:
RecvFunc16();
state=2;
break;
case 2:
Func03();
state=3;
break;
case 3:
RecvFunc03();
state=0;
break;
}
}
}
Unity3D c#
moveobject.csusing UnityEngine;
using System.Collections;
public class moveobject : MonoBehaviour {
public GameObject SerialportObject;
public serialport script;
public int fan_speed = 0;
private short modbus_read;
private short modbus_write;
private bool conn = false;
void OnGUI() {
GUI.Label(new Rect((Screen.width/2)-75, 10, 150, 25),"program-plc.blogspot.com");
switch (script.Connection_Status)
{
case 0:
GUI.Label(new Rect(10, 40, Screen.width, 25),"Connection Status : Common Error");
break;
case 1:
GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Closing port, because it was already open!");
break;
case 2:
GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Port Opened!");
break;
case 3:
GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Port is already open");
break;
case 4:
GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Port == null");
break;
case 5:
GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Not Existing " + script.PortName.ToString() + " , Please ReStart");
break;
case 6:
GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Exception Errors");
break;
case 100:
GUI.Label(new Rect(10, 40, Screen.width, 25),"Connection Status : OK");
conn = true;
break;
case 101:
GUI.Label(new Rect(10, 40, Screen.width, 25),"Connection Status : OK");
conn = true;
break;
case 102:
GUI.Label(new Rect(10, 40, Screen.width, 25),"Connection Status : OK");
conn = true;
break;
}
GUI.Label(new Rect(10, 70, 250, 25),"Modbus 40001 <= Push Button : " + modbus_write.ToString());
GUI.Label(new Rect(10, 100, 250, 25),"Modbus 40002 => Fan Speed : " + modbus_read.ToString());
}
void Start () {
script = SerialportObject.GetComponent<serialport>();
}
void Update () {
if (Input.GetMouseButtonDown(0) && conn)
{
GameObject PB;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray,out hit))
{
if(hit.collider.gameObject.name== "Push_Button"){
PB = GameObject.Find(hit.collider.gameObject.name);
PB.transform.localPosition = new Vector3(0, 0.4f,0);
if(script.F16_values.Length>0){
script.F16_values[0]=1;
}
}
}
}
if (Input.GetMouseButtonUp(0)){
GameObject PB;
PB = GameObject.Find("Push_Button");
PB.transform.localPosition = new Vector3(0, 1.2f,0);
if(script.F16_values.Length>0){
script.F16_values[0]=0;
}
}
if (script.F03_values.Length > 1){
modbus_write = (short) script.F03_values [0];
modbus_read = (short) script.F03_values [1];
fan_speed = modbus_read;
}
if (fan_speed !=0){
GameObject FAN;
FAN = GameObject.Find("Fan");
FAN.transform.Rotate(0,fan_speed*Time.deltaTime,0);
}
}
}