Librería ModBus TCP Master para Visual C#A continuación subimos un ejemplo simple en Visual C# para comunicarse al PLC utilizando protocolo
ModBus TCP.En este caso, el PLC actúa como Esclavo y desde Visual C# (una computadora) nos comunicamos como Maestro para escribir/leer registros o bits.
Aplicación de ejemploCargue en el PLC el proyecto
MbTcpServerCounter.zip para
StxLadder que adjuntamos.
Luego descargue el proyecto
ModBusTcpResponseTime.zip para
Visual C# y ejecute la aplicación "
ModBusTcpResponseTime.exe" de la carpeta "
bin\Release".
Especifique dirección del PLC (ejemplo 192.168.1.81) y haga click en botón "
Conectar". El puerto por defecto utilizado es 502. A continuación, si no hay errores de comunicación verá la siguiente pantalla (similar):

El funcionamiento es muy simple.
En el PLC, como esclavo ModBus, constantemente se incrementa una variable contador cada 1 mS y se la carga al registro ModBus número 40001.
En el caso que el registro 40001 se haga 0 (por ejemplo un Maestro escribió valor 0), la variable contador se hace cero nuevamente.
Por lo tanto, el valor en el registro 40001 representa el tiempo que pasó desde que un Maestro escribió el valor 0 y volvió a leer el valor del registro.
La aplicación en Visual C#, simplemente envía cada 10 mS una transacción ModBus de escritura de registro (escribe valor 0 en 40001) y luego intenta leer el mismo registro a través de ModBus.
Luego imprime dicho valor en pantalla, que representa el tiempo entre escritura y lectura ModBus.
También calcula el valor promedio de los últimos
X valores (definido en caja
Cantidad de muestras) y el valor máximo alcanzado.
Adicionalmente en el directorio del ejecutable, se guarda un archivo de texto llamado "
log.txt" que registra todas las lecturas de tiempo realizadas de la última conexión para realizar un mejor analisis.
Código en C#Abra el proyecto en
Visual C# (recomendado versión 2015 o superior).
La librería ModBus TCP Master, se encuentra en el archivo
stx8xxx.dll incluido en el directorio "
bin\release" del proyecto.
Es esta librería que incluimos como referencia en el proyecto (References) y debe ser empleada en su aplicación
Visual C# para tener acceso a las funciones
ModBus TCP.
Luego agregamos el espacio de nombre en el código fuente con:
using stx8xxx;
Creamos el objeto que accede a las funciones ModBus como maestro:
private ModBusTcpMaster MbMaster;
Para conectarnos, simplemente especificamos dirección IP y puerto (que se obtienen de los controles de la ventana):
MbMaster = new ModBusTcpMaster(textDeviceIP.Text, (ushort)numDevicePort.Value);
Luego especificamos dos eventos, que serán llamados cuando tengamos repuesta de datos y excepciones (un error):
MbMaster.OnResponseData += new ModBusTcpMaster.ResponseData(MbMaster_OnResponseData);
MbMaster.OnException += new ModBusTcpMaster.ExceptionData(MbMaster_OnException);
Posteriormente, en el código creamos una funcion para leer el registro ModBus 40001, asociado al contador del PLC:
private bool ReadPlcCounterRegister()
{
bool ErrorsFound = false;
// Check connection.
if (!IsConnectedMsg())
{
ErrorsFound = true;
return ErrorsFound;
}
// Unique transaction ID (only for identify this transaction on response event).
ushort ID = 2;
// Send "Read Holding Register" request.
MbMaster.ReadHoldingRegister(ID, 40001, 1);
// Return to caller.
return ErrorsFound;
}
Notar como usamos la función "MbMaster.
ReadHoldingRegister()" para leer un Holding Register del PLC en dirección "40001". El
ID, es un valor arbitrario que utilizamos para luego poder identificar en la repuesta que los datos recibidos corresponden a esta transacción y no a otra que podamos haber realizado.
También creamos una función para escribir el registro del PLC numero 40001:
private bool WritePlcCounterRegister(ushort Value)
{
bool ErrorsFound = false;
// Check connection.
if (!IsConnectedMsg())
{
ErrorsFound = true;
return ErrorsFound;
}
// Unique transaction ID (only for identify this transaction on response event).
ushort ID = 1;
// Get byte array from "Value".
byte[] Data = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((ushort)Value));
// Send "Write Single Holding Register" request.
MbMaster.WriteSingleRegister(ID, 40001, Data);
// Return to caller.
return ErrorsFound;
}
Nuevamente, notar como usamos la función "MbMaster.
WriteSingleRegister()" para escribir un Holding Register del PLC en dirección "40001". El
ID, es un valor arbitrario que utilizamos para luego poder identificar en la repuesta que los datos recibidos corresponden a esta transacción y no a otra que podamos haber realizado.
También, el objeto "
MbMaster" de la librería contiene más funciones para leer bits, o escribir/leer varios registros al mismo tiempo. Ver documentación en código ofrecida por Visual C#
Luego, desde un timer, llamamos a estas funciones cada 10 mS, donde comprobamos variable
ReadyForTransaction, que indica que estamos listos para comenzar una nueva transacción y luego comprobamos variable
StartWriteTransaction, si es "1", enviamos una transacción de escritura, sino, enviamos una transacción de lectura:
private void timerPolling_Tick(object sender, EventArgs e)
{
//
// Check if previous response was received or is ready for new transaction.
//
if (ReadyForTransaction)
{
//
// Check transaction to do: write or read counter register.
//
if (StartWriteTransaction)
{
// Write counter register with 0 value.
WritePlcCounterRegister(0);
}
else
{
// Read PLC counter register.
ReadPlcCounterRegister();
}
}
}
La respuesta a estas transacciones, las obtenemos del evento:
private void MbMaster_OnResponseData(ushort ID, byte function, byte[] values)
{
// ------------------------------------------------------------------------
// Seperate calling threads
// ------------------------------------------------------------------------
if (this.InvokeRequired)
{
this.BeginInvoke(new ModBusTcpMaster.ResponseData(MbMaster_OnResponseData), new object[] { ID, function, values });
return;
}
// ------------------------------------------------------------------------
// Identify requested data
// ------------------------------------------------------------------------
StringBuilder ResponseStr = new StringBuilder("Respuesta recibida: ");
//
// Check ModBus function code received.
//
switch (function)
{
case 3:
ResponseStr.Append("Read holding register ");
//
// Check transaction ID (counter read).
//
if (ID == 2)
{
//
// Get register value.
//
// UInt16 RegisterValue = (UInt16)(((UInt16)(values[0] << 8)) | ((UInt16)(values[1])));
//
// Check architecture endianess.
if (BitConverter.IsLittleEndian)
{
// Reverse array values.
Array.Reverse(values);
}
// Get register value from received bytes.
UInt16 RegisterValue = BitConverter.ToUInt16(values, 0);
// Update text box values on windows form.
UpdateTextBox(RegisterValue);
// Read register is done, now, write register on next timer tick.
StartWriteTransaction = true;
}
break;
case 6:
ResponseStr.Append("Write single register ");
//
// Check transaction ID (counter write).
//
if (ID == 1)
{
// Write register is done, now, read register on next timer tick.
StartWriteTransaction = false;
}
break;
default:
ResponseStr.Append("Unknown ");
break;
}
// Show message in status bar.
ResponseStr.AppendFormat("({0}) - data bytes/words ({1}/{2}).", function, values.Length, values.Length / 2);
PrintStatusBarMessage(ResponseStr.ToString());
// Response received, ready for next transaction.
ReadyForTransaction = true;
}
Cuando este evento es llamado, nos pasa 3 argumentos:
- ID: Identificación de transacción que colocamos al realizar una operación.
- function: Código de Función ModBus al que pertenece la repuesta.
- values[]: Bytes recibidos de la repuesta ModBus.
Notar como el siguiente fragmento es llamado, cuando hay una repuesta de escritura:
case 6:
ResponseStr.Append("Write single register ");
//
// Check transaction ID (counter write).
//
if (ID == 1)
{
// Write register is done, now, read register on next timer tick.
StartWriteTransaction = false;
}
Procesamos, si es "
function=6" (repuesta de una transacción Write single register) y "
ID=1" (lo establecimos en
WritePlcCounterRegister(), hacemos la variable
StartWriteTransaction=false para que en el próximo evento timer en
timerPolling_Tick() realicemos una operación de lectura.
En la repuesta de lectura, luego de llamar a función "
ReadPlcCounterRegister()" se entra al siguiente fragmento de código:
case 3:
ResponseStr.Append("Read holding register ");
//
// Check transaction ID (counter read).
//
if (ID == 2)
{
//
// Get register value.
//
// UInt16 RegisterValue = (UInt16)(((UInt16)(values[0] << 8)) | ((UInt16)(values[1])));
//
// Check architecture endianess.
if (BitConverter.IsLittleEndian)
{
// Reverse array values.
Array.Reverse(values);
}
// Get register value from received bytes.
UInt16 RegisterValue = BitConverter.ToUInt16(values, 0);
// Update text box values on windows form.
UpdateTextBox(RegisterValue);
// Read register is done, now, write register on next timer tick.
StartWriteTransaction = true;
}
Aquí obtenemos el registro leído en
RegisterValue (convertimos de valor de red a valor de computadora de 16-bits) y luego actualizamos las cajas de texto de la ventana con la función
UpdateTextBox().
Al final del evento se hace la variable
ReadyForTransaction igual a
true indicando que la aplicación esta lista para una nueva transacción ya que recibimos el dato que esperábamos (esto es simplemente como lógica en nuestro ejemplo, para no enviar una transacción nueva sí no recibimos la enviada previamente).
// Response received, ready for next transaction.
ReadyForTransaction = true;
}
Aún la librería no esta publicada en la página web oficialmente por estar en desarrollo, por ello cualquier duda, pueden utilizar nuestro foro.
Abrir los ejemplos para el PLC con StxLadder 1.8.9 o superior.
Las últimas versiones de la librería
STX8XXX.DLL pueden descargarse desde la página:
www.slicetex.com/hw/stx80xx/soft.php#SDKSaludos!