La comunicación entre el PLC y Visual Basic se realiza mediante el protocolo ADS (Automation Device Specification) y funciona sobre los protocolos TCP/IP o UDP/IP. En el PLC no es necesario realizar ninguna configuración especial. Simplemente se deben tener claras las variables que necesitamos comunicar para poder agruparlas y leerlas y/o escribirlas en una única operación. Mis primeras pruebas leían y escribían variables individualmente, pero al pasar de un pequeño número de operaciones de lectura/escritura el programa se ralentizaba. Entonces opté por agrupar todas las variables necesarias en dos estructuras, una con los datos que se van a leer desde la aplicación en Visual Basic (estructura de lectura) y otra con los datos que la aplicación podrá modificar (estructura de escritura). Como ejemplo pondré estas dos estructuras declaradas en 'Data types':
(**********************************************************)Para trabajar con las estructuras las declaro como variables globales:
(*** DATOS DE PLC A VISUAL BASIC **************************)
(**********************************************************)
TYPE DATOS_PLC_VB :
STRUCT
TABLA_BOOL: ARRAY [0..7] OF BOOL;
VALOR_INT: INT;
VALOR_LREAL: LREAL;
END_STRUCT
END_TYPE
(**********************************************************)
(*** DATOS DE VISUAL BASIC A PLC **************************)
(**********************************************************)
TYPE DATOS_VB_PLC :
STRUCT
TABLA_BOOL: ARRAY [0..7] OF BOOL;
VALOR_INT: INT;
VALOR_LREAL: LREAL;
END_STRUCT
END_TYPE
VAR_GLOBALAhora deberemos tomar nota del tamaño de las estructuras. Tengo una tabla de ocho booleanos, un entero y un real largo. Cada booleano ocupa un byte, un entero son 2 bytes y un real largo 8 bytes, con lo que tenemos un total de 18 bytes para cada una de las estructuras.
PLC_VB: DATOS_PLC_VB;
VB_PLC: DATOS_VB_PLC;
END_VAR
En la web de Beckhoff hay una explicación de transferencia de estructuras con programas de ejemplo.
Ahora vamos a Visual Basic y creamos un proyecto nuevo. Lo primero será incluir las referencias a bibliotecas necesarias. En la pestaña 'References', en la parte inferior 'Imported namespaces' marcamos 'System.IO'.
A continuación pulsamos el botón 'Add...' para añadir una nueva referencia y en la ventana que aparece vamos a la pestaña 'Browse'. Vamos a añadir la DLL en .NET para ADS de Beckhoff, que se incluye con la instalación de TwinCAT. Buscamos el directorio 'c:\TwinCAT\ADS Api\.NET\v2.0xxx' (puede variar en función de qué versión de TwinCAT hayamos instalado) y marcamos el fichero 'TwinCAT.Ads.dll'.
Nos aseguramos también de marcar en 'Imported namespaces' las casillas de 'TwinCAT', 'TwinCAT.Ads' y 'TwinCAT.Ads.Internal'.
Con esto ya tenemos en nuestro proyecto acceso a todas las funciones para realizar la comunicación, así que voy a continuación con la programación. Lo que he hecho es agrupar todo el código necesario en una clase que voy a ir comentando a continuación:
En la declaración de variables de la clase puede ser necesario cambiar la constante Num_Var, si aparte de las estructuras de lectura y escritura es necesario acceder a otras variables o estructuras.
'Conexión con un PLC Beckhoff a través de ADSEn el constructor de la clase es necesario adaptar el tamaño de las variables tipo 'AdsStream' al tamaño de las estructuras que se van a leer o escribir, en mi caso 18 bytes.
'GR - notasdeautomatización.blogspot.com
Public Class Conex_ADS
#Region "Variables"
'Nos dice si ya existe una conexión activa
Private Conectado As Boolean
'Variables para comunicación ADS (Beckhoff)
Private tcAds As TcAdsClient
Private DSRead As AdsStream
Private DSWrite As AdsStream
Private BR As BinaryReader
Private BW As BinaryWriter
Private VarHandle() As Integer
Public Mensaje As String 'Mensaje que indica el éxito de la operación
Public Detalle_Error As String 'Si hay algún error se indica en esta variable
Private Const Num_Var As Integer = 2 'Número máximo de variables que se controlaran con 'Handles'
#End Region
#Region "Funciones"La conexión con el PLC se hace con la función Conectar, a la que debemos darle la dirección ADS del PLC y el puerto a través del cual nos comunicaremos. Cada variable que queramos comunicar debe tener asignado un 'handle', si añadimos alguna debemos recordar asignárselo en esta función (ver comentarios en el código).
'Constructor
Public Sub New()
'Al crear el objeto, no estamos conectados
Conectado = False
DSRead = New AdsStream(18)
DSWrite = New AdsStream(18)
BR = New BinaryReader(DSRead)
BW = New BinaryWriter(DSWrite)
End Sub
'Realizar una conexión con el PLCEsta función nos desconecta ordenadamente del PLC.
'El primer parámetro es la dirección del ADS del PLC
'(si el PLC es el propio PC (local) debe ser una cadena vacía "")
'El segundo parámetro es el puerto a través del cual nos comunicaremos (por defecto 801)
Public Function Conectar(ByVal Dir As String, ByVal Puerto As Integer) As Boolean
' Si ya estamos conectados se devuelve error
If Conectado Then
Mensaje = "Error conexión PLC."
Detalle_Error = "Se ha intentado crear una conexión al PLC cuando ya hay una creada."
Conectar = False
Exit Function
End If
Try
Mensaje = "Conectando con el PLC..."
tcAds = New TcAdsClient
If Dir = "" Then
tcAds.Connect(Puerto)
Else
tcAds.Connect(Dir, Puerto)
End If
Catch ex As Exception
Detalle_Error = "Error: " & ex.ToString
Conectado = False
Conectar = False
Exit Function
End Try
tcAds.ReadState()
If tcAds.IsConnected Then
Mensaje = "Conexión con el PLC realizada correctamente."
'Si la conexión fue exitosa hay que indicar las variables que se podrán transmitir
ReDim VarHandle(Num_Var)
Try
'Incluir aquí las variables necesarias para la comunicación
'Se indica el nombre de la variable en el programa PLC precedido de un punto
'Pueden indicarse elementos de tablas y/o estructuras, p. ej. ".D_LECTURA.BYTES[3]"
VarHandle(0) = tcAds.CreateVariableHandle(".PLC_VB") 'Datos que voy a leer del PLC
VarHandle(1) = tcAds.CreateVariableHandle(".VB_PLC") 'Datos que voy a escribir en el PLC
'Fin de las variables de la conexión
Catch ex As Exception
Mensaje = "Fallo en la conexión con las variables."
Detalle_Error = "Error:" & ex.ToString
Conectar = False
End Try
Detalle_Error = ""
Conectado = True
Conectar = True
Else
Mensaje = "Fallo en la conexión con el PLC."
Detalle_Error = "tcAds.IsConnected = False"
Conectado = False
Conectar = False
End If
End Function
'Desconectar del PLCLa función 'Actualizar_Lectura' lee en bruto la estructura del PLC, en este caso los 18 bytes y los coloca en el lector binario (BinaryReader). Si necesitamos leer datos continuamente podemos llamar esta función periódicamente con un temporizador.
Public Function Desconectar() As Boolean
Dim Índice As Integer
For Índice = 0 To Num_Var
tcAds.DeleteVariableHandle(VarHandle(Índice))
Next
Try
Mensaje = "Desconectando..."
tcAds.Dispose()
Catch ex As Exception
Detalle_Error = "Error: " & ex.ToString
Desconectar = False
Exit Function
End Try
Mensaje = "Desconexión realizada correctamente."
Detalle_Error = ""
Conectado = False
Desconectar = True
End Function
'Actualizar los valores de la estructura de lectura desde el PLCLa función 'Actualizar_Escritura' escribe en bruto los 18 bytes que hayamos puesto en un escritor binario (BinaryWriter) en la estructura del PLC.
Public Function Actualizar_Lectura() As Boolean
If Not Conectado Then
Mensaje = "Error al leer DATOS"
Detalle_Error = "No hay conexión para leer datos."
Actualizar_Lectura = False
Exit Function
End If
DSRead.Position = 0
Try
tcAds.Read(VarHandle(0), DSRead)
Catch ex As Exception
Detalle_Error = ex.ToString
Mensaje = "Error al leer datos."
Actualizar_Lectura = False
Exit Function
End Try
Actualizar_Lectura = True
Mensaje = ""
Detalle_Error = ""
End Function
'Actualizar los valores de la estructura de escritura al PLCPara leer y escribir variables individuales debemos diseccionar la tabla de valores binarios. Las siguientes funciones son ejemplos de como leer y escribir booleanos, enteros y reales, siendo necesario adaptarlas a nuestras necesidades, ya que solo son válidas para las estructuras de ejemplo. Debemos tener claro en qué byte empieza cada dato y qué tamaño tiene para direccionarlo correctamente.
Public Function Actualizar_Escritura() As Boolean
If Not Conectado Then
Mensaje = "Error al escribir DATOS"
Detalle_Error = "No hay conexión para escribir datos."
Actualizar_Escritura = False
Exit Function
End If
DSWrite.Position = 0
Try
tcAds.Write(VarHandle(1), DSWrite)
Catch ex As Exception
Detalle_Error = ex.ToString
Mensaje = "Error al escribir datos."
Actualizar_Escritura = False
Exit Function
End Try
Actualizar_Escritura = True
Mensaje = ""
Detalle_Error = ""
End Function
NOTA: las variables tendrán que estar dispuestas en el mismo orden en el que las hayamos declarado en el PLC, recordando que el tipo de datos BOOL ocupa inevitablemente 1 byte.
'Función para escribir un valor booleanoLa forma de trabajar con esta implementación es primero declarando una variable del tipo Conex_ADS:
Public Function Escribir_Bool(ByVal Indice As Integer, ByVal Valor As Boolean) As Boolean
If Not Conectado Then
Mensaje = "Error."
Detalle_Error = "No hay conexión para escribir datos."
Escribir_Bool = False
Exit Function
End If
'Los booleanos ocupan un byte (1 - true; 0 - false)
Dim Bit As Byte
'En la estructura de datos de lectura los 8 primeros bytes son los booleanos
DSWrite.Position = Indice
If Valor Then
Bit = 1
Else
Bit = 0
End If
BW.Write(Bit)
Escribir_Bool = True
Mensaje = ""
Detalle_Error = ""
End Function
'Función para leer un valor booleano
Public Function Leer_Bool(ByVal Indice As Integer) As Boolean
If Not Conectado Then
Mensaje = "Error."
Detalle_Error = "No hay conexión para leer datos."
Leer_Bool = False
Exit Function
End If
'En la estructura de datos de lectura los 8 primeros bytes son los booleanos
DSRead.Position = Indice
Leer_Bool = BR.ReadBoolean()
Mensaje = ""
Detalle_Error = ""
End Function
'Función para escribir un valor entero (16 bits)
Public Function Escribir_Int(ByVal Valor As Int16) As Boolean
If Not Conectado Then
Mensaje = "Error."
Detalle_Error = "No hay conexión para escribir datos."
Escribir_Int = False
Exit Function
End If
'En la estructura de datos de lectura los 8 primeros bytes son los booleanos
'A continuación están el entero de 16 bits (2 bytes)
DSWrite.Position = 8
BW.Write(Valor)
Escribir_Int = True
Mensaje = ""
Detalle_Error = ""
End Function
'Función para leer un valor entero (16 bits)
Public Function Leer_Int() As Int16
If Not Conectado Then
Mensaje = "Error."
Detalle_Error = "No hay conexión para leer datos."
Leer_Int = False
Exit Function
End If
'En la estructura de datos de lectura los 8 primeros bytes son los booleanos
'A continuación están el entero de 16 bits (2 bytes)
DSRead.Position = 8
Leer_Int = BR.ReadInt16()
Mensaje = ""
Detalle_Error = ""
End Function
'Función para escribir un valor real largo (64 bits)
Public Function Escribir_Lreal(ByVal Valor As Double) As Boolean
If Not Conectado Then
Mensaje = "Error."
Detalle_Error = "No hay conexión para escribir datos."
Escribir_Lreal = False
Exit Function
End If
'En la estructura de datos de lectura los 8 primeros bytes son los booleanos
'A continuación están el entero de 16 bits (2 bytes)
'Finalmente está el valor LREAL (8 bytes)
DSWrite.Position = 8 + 2
BW.Write(Valor)
Escribir_Lreal = True
Mensaje = ""
Detalle_Error = ""
End Function
'Función para leer un valor real largo (64 bits)
Public Function Leer_Lreal() As Double
If Not Conectado Then
Mensaje = "Error."
Detalle_Error = "No hay conexión para leer datos."
Leer_Lreal = False
Exit Function
End If
'En la estructura de datos de lectura los 8 primeros bytes son los booleanos
'A continuación están el entero de 16 bits (2 bytes)
'Finalmente está el valor LREAL (8 bytes)
DSRead.Position = 8 + 2
Leer_Lreal = BR.ReadDouble
Mensaje = ""
Detalle_Error = ""
End Function
#End Region
End Class
Private ADS As Conex_ADSA continuación realizamos la conexión:
ADS = New Conex_ADS
If Not ADS.Conectar("",801) ThenAhora con las funciones Actualizar_Escritura y Actualizar_Lectura llamadas periodicamente podemos mantener la comunicación con el PLC.
'Si hay un error en la conexión se visualiza
MsgBox(ADS.Mensaje)
MsgBox(ADS.Detalle_Error)
Exit Sub
End If
ADS.Actualizar_Lectura()Para leer y escribir las variables individuales usaré las funciones Leer_Bool, Escribir_Int y similares, teniendo en cuenta que la comunicación no se realizará realmente hasta que las funciones de actualización se ejecuten.
ADS.Actualizar_Escritura()
Dim Entero as Integer = ADS.Leer_Int()Y hasta aquí estas notas. Esto solo es un ejemplo del que partir para implementar nuestra comunicación, que se puede hacer muy diversas maneras en función de nuestras necesidades. En la página de Beckhoff puedes encontrar más ejemplos de programación.
ADS.Escribir_Bool(1,False)
Como siempre, agradeceré cualquier comentario.
soy nuevo en esto de la comunicasion entre el visual basic y el plc, me gustaria conocer mas acerca de este proyecto y si se aplica igual para cualquier plc como el allen bradley, ya que quiero realizar un proyecto con este plc micrologix 1500 lsp serie A, y la variable a manejar seria la del tiempo para monitorear maquinas continuas
ResponderEliminarSupongo que Allen Bradley dispondrá de sus propias bibliotecas para acceder al PLC desde Visual Basic. Hace años que no toco esa marca de PLCs, siento no ser de mucha ayuda.
ResponderEliminarHabra algun limite en el numero de variables
ResponderEliminarque podemos Visualizar en VB.Net con ADS