Mas sobre sinópticos. Inkscape, MWSnap3, Beneton Movie Gif.

Me ha parecido muy interesantes estos software para crear sinópticos. Uno es Inkscape que viene a ser una alternativa libre a CorelDraw. O dicho de otra forma un editor libre de gráficos vectoriales. Con el se pueden hacer cositas interesantes. Ahí van unas muestras:

Para poder realizar estas animaciones he tenido que utilizar algún programas más. Uno que me permita capturar las imágenes en cada movimiento MWSnap 3, y otro para generar el gif Beneton Movie Gif .Viendo estos programas, está claro que no hay excusas para poder hacer buenos trabajos, y menos para usar Crack y cosas por el estilo.

Servidor Web y MySql

Hoy empezaré una serie de post dedicados a al registro de datos y mostrar estos datos en paginas web. Para ello voy a utilizar xammp. Xammp es un servidor web muy fácil de instalar que incluye MySql. En la red podéis encontrar infinidad de información sobre este software. La idea es generar aplicaciones en vb.net que se comuniquen con el plc y registren los datos en la bd MySql. Luego haremos script en php para recoger estos datos y mostrarlos vía web. Todo esto implica cierto conocimientos de php, Sql, html, etc pero realmente no es tan fiero el lobo como lo pintan.

Para empezar debemos instalar xammp en nuestro pc. Es sencillo y sólo estará activo mientras queramos utilizarlo. De esta manera evitaremos cargar el pc con tareas innecesarias, y desinstalarlo sera tan fácil como borrar una carpeta. No hay excusa para no probarlo.

Lo primero es descargar xammp desde aquí.

Una vez descargado lo ejecutamos. Lo que hace es descomprimir el archivo en C:\ por defecto. Una vez terminado el proceso se ejecuta setup_xammp.bat y nos muestra la siguiente ventana:

¿Quieres crear una acceso directo en el menú inicio?. Yo le digo que no.

Esto es para configurar las rutas relativas y absolutas. Le decimos que sí (y) y pa lante:

Esta opción es para utilizar xammp desde una unidad portatil (o eso creo, jijiji). La opción por defecto (n) y seguimos:

Pues ya hemos terminado. Ponemos x, enter y listo. Mi consejo es arrancar y poner en marcha xammp desde estos accesos de la siguiente manera:

Nos vamos a  C:\Xammp

Tal y como muestra la imagen, para poner en marcha nuestro servidor web y mysql solo tenemos que hacer doble click sobre xammp_start y xammp_stop para parar.
Bien, haz doble click sobre xammp_start, abre el navegador y pon esta dirección http://localhost

Si hemos hecho todo correctamente debería aparecer el logo de xammp, pulsa sobre Español y se termino por hoy. Puedes probar a entrar desde otro ordenador de la red donde estés, simplemente poniendo la ip de tu ordenador en el navegador.

En la próxima entrada  entraremos un poco en la base de datos y haremos  el típico “Hola mundo”.

Interface grafica I.

En esta entrada he realizado un pequeño ejemplo de una interface grafica tipo HMI con vb.net. El programa en Vb.net simula el funcionamiento de una cámara frigorífica y controla los elementos graficos, que como veremos es la parte mas fácil.

Para aumentar el efecto y simplificar la realización he utilizado imagenes .gif, que como todos sabemos son las tipicas imágenes con movimiento, para muestra un botón.

Bueno, la idea era hacer un ejemplo, no una maravilla. Para realizar esta imagen he utilizado un programita Java, muy sencillo, vamos hipersencillo. Lo podéis descargar desde aquí. Solo hay que pinchar en abrir, seleccionar todas la imágenes que forma el gif, cambiar el tiempo de refresco  y listo. Aquí un vídeo demostrativo.

El sinóptico esta creado con google Scketchup y es este:

Está coloreado con el paint, recortamos los bloques, compresor, ventilador, solenoide, etc., y los coloreamos y giramos para formas los gif necesarios:

El resto del proceso es muy sencillo. Ponemos un picturebox en nuestro formulario principal, con el tamaño que queramos, que tenga el sinóptico. Y luego vamos poniendo otros picturebox con los gifs y le cambiamos la propiedad visible a false. Puedes ver los anteriores post para repasar el proceso.

Como dije al principio, he añadido el código necesario para simular el funcionamiento. Os dejo el proyecto completo aquí. Y si quereis algún gif, sólo teneis que descargarlos directamente, derecho y guardar imagen, aunque creo que son mejorables.

Este es el código de vb.net:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Public Class Form1
    'Variables globales
    Dim Temp, Pa, Pb As Integer
    Dim C, Sol, Co1, Co2, Evap As Boolean
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Timer1.Enabled = True
        Button2.Tag = False
    End Sub
 
    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Timer1.Enabled = False
 
 
        'Simula las entradas de calor a la camara
        Temp = Temp + 1
        'Simula el descenso de temperatura por el equipo frigorifico
        If C Then Temp = Temp - 2
        'Termostato
        If Temp > 120 Then
            Sol = True
        ElseIf Temp < 100 Then
            Sol = False
        End If
        'Simula el cambio de presión en baja
        If Sol And Not C Then
            Pb = Pb + 1
        ElseIf C And Not Sol Then
            Pb = Pb - 1
        End If
 
        If Sol And Not C Then
            Pb = Pb + 1
        End If
        'Arranque del compresor por pb (Presión de baja)
        If Pb > 30 Then
            C = True
        ElseIf Pb < 20 Then
            C = False
        End If
        'Simula la presión de alta
        If C And Not Co2 Then
            Pa = Pa + 1
        ElseIf Co2 Then
            Pa = Pa - 2
        End If
        'Arranque de condensador para control de condensación
        If Pa > 150 Then
            Co2 = True
        ElseIf Pa < 145 Then
            Co2 = False
        End If
 
        'El primer vent de condensador entra con el compresor y el vent de evaporador con la solenoide
        Co1 = C
        Evap = Sol
 
        'Asignar imagen
        Img(pbCompresor, C)
        Img(pbSolenoide, Sol)
        Img(pbVentCo1, Co1)
        Img(pbVentCo2, Co2)
        Img(pbVentEvap, Evap)
        'Tempreturas y presiones
        laTemp.Text = Temp / 10
        laPa.Text = Pa / 10
        laPb.Text = Pb / 10
        'Si se pulso en parar no reactivamos el timer para detener la simulación
        If Button2.Tag = False Then
            Timer1.Enabled = True
        End If
 
    End Sub
 
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        'para parar el simulador
        sender.Tag = True
    End Sub
 
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Temp = 100
        Pa = 100
        Pb = 20
    End Sub
    Private Sub Img(ByVal Pb As PictureBox, ByVal Estado As Boolean)
        If Estado Then
            Pb.Visible = True
        Else
            Pb.Visible = False
        End If
    End Sub
End Class

SFC. Rutina de inicio.

En esta entrada, mostraré un pequeño ejemplo en lenguaje SFC (Secuencial Funtion Chart, diagrama de secuencia de funciones). La verdad es que nunca he hecho nada en este lenguaje, pero está claro que puede ser un importante apoyo para trabajar con ST. Total que intentaré ir aprendiendo y mostrando los avances.
Bueno al grano. En este primer ejemplo he realizado una pequeña secuencia como muestra la figura:

Como puede verse, he creado dos variables globales Rst y OK. La secuencia es la siguiente:
El plc se pone en Run y se activa al paso EsperaOff durante  el tiempo T_espera. Transcurrido este tiempo, se activa el  paso BorrarAlarmas del que saldrá transcurrido el tiempo T_Rst. Al entrar en este paso (E) , se activa la marca Rst y al salir se borra. El siguiente paso sera SistemaOn, y hay permanece mientras el PLC no se reinicie. Esta rutina puede servir para temporizar todos los equipos ante una falta de suministro eléctrico y un borrado de alarmas antes de volver arrancar.

EsperaOff.
En este paso he añadido un tiempo mínimo, de manera que aunque la condición para continuar es True, no lo hará hasta no completar el tiempo mínimo. Esto lo hacemos con el menú contextual y Step Attributes. En este caso he utilizado una variable time pero igualmente podríamos haber utilizado un tiempo directamente (T#5s).

BorrarAlarmas.
Ademas del atributo de tiempo, he añadido una Acción de entrada (E) y otra de salida (X). Lo hacemos, igualmente con el menú contextual y Add Entry-Action o Add Exit-Action. Al hacer click sobre una de estas acciones nos preguntará en que lenguaje queremos añadir la entrada. En mi caso he elegido ST, se abre el editor y ponemos Rst:=TRUE; para la acción de entrada y Rst:=FALSE; para la acción de salida.

SistemaOn.
En este paso he añadido una acción, hacemos click sobre el paso, nos pregunta el lenguaje, marcamos ST  y en el editor ponemos Ok:=TRUE;

La siguiente figura muestra el menú contextual:

Tipos de datos V. Pointer.

Esto es lo que dice el manual de Beckhoff:

En los punteros se guardan las direcciones de variables o bloques de
funciones para el tiempo de ejecución de un programa.
Las declaraciones de
puntero tienen la siguiente sintaxis:

: POINTER TO
función>;

Un puntero puede indicar cualquier tipo de datos y bloque de funciones,
incluso de definición propia.

Con el operador de dirección ADR se asigna al
puntero una dirección de una variable o bloque de función.

La desreferenciación de un puntero se produce mediante el operador de
contenidos “^” tras el identificador del puntero.


 

Please note: A pointer is counted up
byte-wise ! You can get it counted up like it is usual in the C-Compiler by
using the instruction p=p+SIZEOF(p^);.

Y esto es lo que digo yo:
Un puntero nos permite cambiar, en tiempo de ejecución, la variable que queremos leer. Por ejemplo, si tenemos un variador de frecuencia que funciona con diferentes parámetros,  lo que todos llamamos recetas. Pues bien, podemos crear una estructura con esos valores, por ejemplo:

  • Velocidad.
  • Tiempo de aceleración.
  • Tiempo de deceleración.

y mediante un puntero cambiar la receta con la que vamos a trabajar, como muestra el ejemplo:

Como podemos ver en la figura, tenemos dos variables de tipo estructura que serán las dos recetas:

  • Conf1
  • Conf2

Y una tercera variable de este tipo (ConfActual),  será la que reciba la receta actual para enviarla a los variadores. Mediante la variable Selec1, seleccionamos la receta a utilizar.

El puntero V1 almacena la direción de la variable, y con el operador de desreferenciación (la ostia que palabra, tú) ^ la pasamos a ConfActual.

De este modo el proceso es el siguiente:
Mediante ADR pasamos la dirección de la variable a el puntero.

1
V1 := ADR(Conf1);

Mediante ^ pasamos el valor de la variable apuntada.

1
ConfActual := V1^;

Código completo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(*ZONA Variables*)
PROGRAM MAIN(*                         ---xxX POINTER Xxx---*)
VAR
	Conf1:Conf;
        Conf2:Conf;
        ConfActual:Conf;
        V1:POINTER TO Conf;
	Selec1:BOOL;
END_VAR
(*ZONA Programa*)
IF Selec1 THEN
	V1 := ADR(Conf1);
ELSE
	V1 := ADR(Conf2);
END_IF;
ConfActual := V1^;

Tipos de datos IV. Enumeraciones.

El tipo de dato Enumeración que forma parte de los tipos definidos por el usuario, nos permite llamar a las cosas por su nombre y aun así, poder sumar, restar, etc. ¿Que diría de esto la de sumar peras con manzanas?, en fin, eso es otro tema.

Para declararlo lo hacemos igual que con las estructuras, borramos lo de estructuras y ponemos la enumeración, como muestra la siguiente imagen:

En este caso hemos utilizado el recurrido recurso del semáforo. El siguiente ejemplo muestra un ejemplo de programa donde simulamos el funcionamiento de un semáforo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PROGRAM MAIN
VAR
	Color1: Colores;
	Cambiar:BOOL;
	TiempoAm: TON;
	TiempoRoj: TON;
	TiempoVer: TON;
END_VAR
 
IF Cambiar = TRUE THEN
	Color1 := Color1 +1;
	IF Color1 > 2 THEN Color1 := 0;END_IF;
	Cambiar := FALSE;
END_IF;
 
TiempoAm(IN:= Color1 = Amarillo, PT:= T#5s);
TiempoRoj(IN:= Color1 = rojo, PT:= T#10s);
TiempoVer(IN:= Color1 = verde, PT := T#7s);
 
IF TiempoAm.Q OR TiempoRoj.Q OR TiempoVer.Q THEN
	Cambiar := TRUE;
END_IF;

Y esta es la declaración y uso de enumeraciones en vb.net

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Public Class Form1
    Enum Colores
        Amarillo
        Rojo
        Verde
    End Enum
    Dim Sema1 As Colores
 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
 
        Sema1 += 1
 
 
    End Sub
End Class

Tipos de datos III. Structuras.

Podemos decir que una estructura es una forma de organizar una serie de variables relacionadas. Un ejemplo puede ser el control de un registro. Supongamos que tenemos una temperatura y queremos controlar que no supere unos valores.
Generaremos las siguientes variables dentro de la estructura:

1
2
3
4
	Valor: INT;
	Maximo: INT;
	Minimo: INT;
	Alarma: BOOL;

Declaración de structuras:

Para declarar las estructuras tenemos que ir a Data types, pinchamos con el derecho y Add Object.

Y declaramos las diferentes variables con sus tipos:

Al definir la estructura hemos definido un tipo pero aún no tenemos declarada ninguna variable de este tipo. Para trabajar con las estructuras deberemos declara una variable de este tipo, en este caso Reg.

El ejemplo anterior muestra como declarar esta variable y como utilizarla posteriormente en el programa. Con este ejemplo quizás no le veamos demasiado sentido a este tipo de dato, pero veamos un ejemplo algo más complejo creando una matriz de estructura.

Ejemplo del uso de estructuras y matrices de estructuras para Codesys.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(*ZONA DECLARACIÓN DE VARIABLES*)
PROGRAM MAIN
VAR
	Reg1:ARRAY[0..100] OF Reg;
	i:BYTE;
END_VAR
(*ZONA DE PROGRAMA*)
FOR i := 0 TO 100 DO
	IF Reg1[i].Valor > Reg1[i].Maximo OR Reg1[i].Valor < Reg1[i].MInimo THEN
		Reg1[i].Alarma := TRUE;
	ELSE
		Reg1[i].Alarma := FALSE;
	END_IF;
END_FOR;

Y este es el resultado

Ejemplo del uso de estructuras y matrices de estructuras para Vb.net. Para que el programa funcione de una manera analoga a como lo Plc Control he creado un control timer que hará que el código se lea de forma recurrente cada seg.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
Public Class Form1
    Structure Reg
        Dim Valor As Short
        Dim Maximo As Short
        Dim Minimo As Short
        Dim Alarma As Boolean
    End Structure
    Dim Reg1(101) As Reg
 
    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Dim i As Integer
        For i = 0 To 100
            If Reg1(i).Valor > Reg1(i).Maximo Or Reg1(i).Valor < Reg1(i).Minimo Then
                Reg1(i).Alarma = True
            Else
                Reg1(i).Alarma = False
            End If
        Next
    End Sub
End Class

Y este es el resultado:

Como se puede ver en la imagen he creado un BreakPoint para poder ver lo que pasa en el código. Si nos situamos encima de la variable Reg1 podremos ver los valores, así como modificarlos pinchando en el valor.

Tipos de datos II. Arrays.

Antes de continuar deciros que podéis ampliar información en la ayuda de Plc Control, en Information System.

Arrays.

En Plc Control se pueden crear arrays de hasta tres dimensiones.

Ejemplos de declaración Codesys.

Array1 :ARRAY [0..9] OF INT;
 
Array2 :ARRAY[0..9,0..9,0..9] OF INT;

Ejemplo de declaración de array en Vb.net.

Dim Array1(10) As Short
 
Dim Array2(10,10) As Short

Programa ejemplo para Codesys.

(*ZONA DE DECLARACIÖN*)
PROGRAM MAIN
VAR
	Memo :ARRAY[0..9] OF INT;
	Suma:INT;
	Valor:INT;
	MemoValor:INT;
	Media:INT;
	Indice:INT;
	i: BYTE;
	conf :BOOL;
END_VAR
(*ZONA DE PROGRAMA*)
(*En este ejemplo calculamos la media de la variable valor. En lugar de hacerlo en un tiempo
determinado lo haremos por cambio de valor de la variable de proceso. De esta manera la media
se actualiza mas rápido cuando los cambio son rápidos. Pero TIENE EL GRAN INCONVENIENTE
QUE SI NO CAMBIAN EL VALOR NO CAMBIARA LA MEDIA. Es solo un ejemplo*)
 
(*Configuración para el primer ciclo de scan
cuando la variable Valor es distinta de 0 llenamos la matriz con ese valor *)
IF NOT Conf AND Valor <> 0 THEN
	FOR i := 0 TO 9 DO
		Memo[i] := Valor;
	END_FOR;
	Conf := TRUE;
	MemoValor := Valor;
END_IF ;
(*Si cambia la variable Valor llenamos el siguiente registro de la matriz*)
IF Valor <> MemoValor THEN
	Memo[indice] := Valor;
	Indice := Indice + 1;
	IF Indice > 9 THEN Indice := 0; END_IF;
	MemoValor := Valor;
END_IF;
(*Calculamos la media*)
Suma := 0;
i:= 0;
FOR i := 0 TO 9 DO
	Suma :=Suma +  Memo[i];
END_FOR;
IF Suma <> 0 THEN Media := Suma / 10; ELSE Media := 0; END_IF;
(*FIN*)

Programa ejemplo para Vb.net

Public Class Form1
    Dim Memo(10) As Short
    Dim MemoValor As Short
    Dim Conf As Short
    Dim Indice As Byte
 
    Private Sub buActualizar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles buActualizar.Click
        Dim Valor As Short = TextBox1.Text
        Dim Media As Short
        Dim Suma As Short
        Dim i As Integer
        'Configuración para la primera actualización
        If Not Conf And Valor <> 0 Then
            For i = 0 To 9
                Memo(i) = Valor
            Next
            Conf = True
            MemoValor = Valor
        End If
        'Si cambia la variable valor llenamos el siguiente registro de la matriz
        If Valor <> MemoValor Then
            Memo(Indice) = Valor
            Indice += 1
            If Indice > 9 Then Indice = 0
            MemoValor = Valor
        End If
        'Calculamos la media
        For i = 0 To 9
            Suma += Memo(i)
        Next
        'Comprobamos que la  suma no sea 0 para no dividir por 0
        If Suma <>  0 Then Media = Suma / 10 Else Media = 0
 
        laMedia.Text = Media
 
    End Sub
End Class

NOTA: He modificado el programa de las cajas de código para poder ver el ST, aun quedan algunos detalles pero espero ir retocándolo (cuando tenga un poco de tiempo).

Tipos de datos I. Tipos de datos estándar.

En esta entrada repasare los tipos de datos en Codesys y el equivalente en vb.net.

Resumen de tipos de datos estándar.

Tipo de dato Codesys vb.net Limites Uso memoria
BOOL BOOL Boolean TRUE ò FALSE 1 bit
BYTE BYTE Byte 0 a 255 8 Bit
WORD WORD UShort 0 a 65535 16 Bit
DWORD UInteger 0 a 4294967295 32 Bit
Simple integer SINT Sbyte -128 a 127 8 Bit
Unsigned Simple Integer USINT Byte 0 a 255 8 Bit
Integer INT Short -32768 a 32767 16 Bit
Unsigned integer UINT UShort 0 a 65535 16 Bit
Double integer DINT Integer -2147483648 a 2147483647 32 Bit
UDINT UInteger 0 a 4294967295 32 Bit
REAL Single* -3.402823 E38 a 3.402823 E38 32 Bit
LREAL Double* -1.79769313486231E308 a 1.79769313486232E308 64 Bit
Cadena de caracteres STRING String* Nota 1
TIME Date* T#0ms a T#71582m47s295ms 32 Bit
TIME_OF_DAY Date* TOD#00:00 a TOD#1193:02:47.295 32 Bit
Fecha DATE Date* D#1970-01-01 D#2106-02-06 32 Bit
Fecha y hora DATE_AND_TIME Date* DT#1970-01-01-00:00 a DT#2106-02-06-06:28:15 32 Bit

*Estos tipos no tienen una equivalencia exacta.

NOTA 1: El tamaño por defecto es 80, para otro numero de caracteres se debe especificar en la declaración (str:STRING(35):=’Esto es una declaración String’;). Uso de memoria [BYTE] = Tamaño + 1 Byte para carácter nulo de terminación.

Todos son bit.

Entiendo que en un principio esto puede ser un poco confuso. Para mi la clave está en pensar que todo son bit y lo único que cambia es la forma de interpretar estos bit. Para muestra un botón. En la siguiente figura puedes ver tres tipos diferentes de variables (INT, UINT, TIME) direccionadas a la misma área de memoria y como se visualizan en función del tipo. También he declarado 16 variables del tipo BOOL y los he asignados a cada bit de VarINT.

No todos son bit.

TwinCat, cuando corre en PC guarda los datos boolean en Byte. Si no utilizamos las variables direccionadas (AT %MX) no nos importara pero hay que tenerlo en cuenta cuando se direccionan.

Variables STRING.

Las variables string almacenan en BYTE el código ascii equivalente al carácter en cuestión. En la siguiente imagen puedes ver dos variables, la variable VarVer es un ARRAY (Matriz o vector) de 11 elementos (0 a 10) de tipo BYTE. La variable VarStr es una variable string(10), ambas están direccionadas en el mismo área de memoria, por lo tanto lo que escribimos en una, lo podemos ver en la otra. Como puedes comprobar en la tabla del código ascii, cada BYTE almacena el código ascii equivalente. También podréis ver que el ultimo BYTE se reserva para el caracter NUL(0).

Código ASCII.

Los 32 primeros códigos pertenecen al grupo de los no imprimibles y son utilizados para enviar códigos como retorno de carro, fin de mensaje en protocolos de comunicación, etc.

Tipos de datos de tiempo.

Creo que merecen un post exclusivo que le dedicaré en breve.

F Psicrómetro

Un psicrómetro es un aparato utilizado en meteorología para medir la humedad o contenido de vapor de agua en el aire, distinto a los higrómetros corrientes. Los psicrómetros constan de un termómetro de bulbo húmedo y un termómetro de bulbo seco. La humedad puede medirse a partir de la diferencia de temperatura entre ambos aparatos. El húmedo medirá una temperatura inferior producida por la evaporación de agua. Es importante para su correcto funcionamiento que el psicrómetro se instale aislado de vientos fuertes y de la luz solar directa.

FUENTE: Wikipedia.

Pues igual que lo hacemos con termómetros de mercurio igual lo podemos hacer con sondas y un PLC. Esto nos sirve de excusa para introducir el uso de FB con tablas de datos. Algunos procesos no pueden ser calculados de forma directa y deben ser obtenidos a través de tablas de datos, como en este caso.

En este caso vamos a usar esta tabla para calcular la humedad en función de la temperatura del bulbo seco y la diferencia con el bulbo húmedo.


Y este es la función:


FUNCTION Psicometro : INT
VAR_INPUT
	TSeco :INT;
	THumedo :INT;
END_VAR
VAR
	Hr:INT;
	Dif_S_H: INT;
END_VAR
 
TSeco := TSeco / 10;
THumedo := THumedo / 10;
 
Dif_S_H := TSeco - THumedo;
 
(*Calculamos humedad en funcion de la parte entera*)
CASE TSeco OF
	5: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 85;			2: Psicometro := 71;			3: Psicometro := 59;			4: Psicometro := 48;			5: Psicometro := 39;
		6: Psicometro := 30;			7: Psicometro := 22;			8: Psicometro := 18;			9: Psicometro := 18;			10: Psicometro := 4;
	   END_CASE ;
	6: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 85;			2: Psicometro := 72;			3: Psicometro := 61;			4: Psicometro := 50;			5: Psicometro := 41;
		6: Psicometro := 33;			7: Psicometro := 25;			8: Psicometro := 21;			9: Psicometro := 20;			10: Psicometro := 7;
	   END_CASE ;
	7: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 86;			2: Psicometro := 73;			3: Psicometro := 62;			4: Psicometro := 52;			5: Psicometro := 43;
		6: Psicometro := 35;			7: Psicometro := 28;			8: Psicometro := 24;			9: Psicometro := 23;			10: Psicometro :=10;
	   END_CASE ;
	8: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 86;			2: Psicometro := 74;			3: Psicometro := 63;			4: Psicometro := 54;			5: Psicometro := 45;
		6: Psicometro := 37;			7: Psicometro := 30;			8: Psicometro := 26;			9: Psicometro := 25;			10: Psicometro := 13;
	   END_CASE ;
	9: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 86;			2: Psicometro := 75;			3: Psicometro := 65;			4: Psicometro := 55;			5: Psicometro := 47;
		6: Psicometro := 39;			7: Psicometro := 32;			8: Psicometro := 28;			9: Psicometro := 27;			10: Psicometro := 16;
	   END_CASE;
	10: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 87;			2: Psicometro := 76;			3: Psicometro := 66;			4: Psicometro := 57;			5: Psicometro := 48;
		6: Psicometro := 41;			7: Psicometro := 34;			8: Psicometro := 30;			9: Psicometro := 29;			10: Psicometro := 18;
	    END_CASE;
	11: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 88;			2: Psicometro := 77;			3: Psicometro := 67;			4: Psicometro := 58;			5: Psicometro := 50;
		6: Psicometro := 43;			7: Psicometro := 36;			8: Psicometro := 32;			9: Psicometro := 31;			10: Psicometro := 20;
	    END_CASE;
	12: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 88;			2: Psicometro := 78;			3: Psicometro := 68;			4: Psicometro := 59;			5: Psicometro := 52;
		6: Psicometro := 44;			7: Psicometro := 38;			8: Psicometro := 34;			9: Psicometro := 33;			10: Psicometro :=22;
	    END_CASE;
	13: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 89;			2: Psicometro := 78;			3: Psicometro := 69;			4: Psicometro := 61;			5: Psicometro := 53;
		6: Psicometro := 46;			7: Psicometro := 40;			8: Psicometro := 36;			9: Psicometro := 34;			10: Psicometro :=25;
	    END_CASE;
	14: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 89;			2: Psicometro := 79;			3: Psicometro := 70;			4: Psicometro := 62;			5: Psicometro := 54;
		6: Psicometro := 47;			7: Psicometro := 41;			8: Psicometro := 37;			9: Psicometro := 36;			10: Psicometro :=26;
	    END_CASE;
	15: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 89;			2: Psicometro := 80;			3: Psicometro := 71;			4: Psicometro := 63;			5: Psicometro := 55;
		6: Psicometro := 49;			7: Psicometro := 43;			8: Psicometro := 39;			9: Psicometro := 37;			10: Psicometro :=28;
	    END_CASE;
	16: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 90;			2: Psicometro := 80;			3: Psicometro := 72;			4: Psicometro := 64;			5: Psicometro := 57;
		6: Psicometro := 50;			7: Psicometro := 44;			8: Psicometro := 40;			9: Psicometro := 39;			10: Psicometro := 30;
	    END_CASE;
	17: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 90;			2: Psicometro := 81;			3: Psicometro := 72;			4: Psicometro := 65;			5: Psicometro := 58;
		6: Psicometro := 52;			7: Psicometro := 46;			8: Psicometro := 42;			9: Psicometro := 40;			10: Psicometro :=31;
	    END_CASE;
	18: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 90;			2: Psicometro := 81;			3: Psicometro := 73;			4: Psicometro := 66;			5: Psicometro := 59;
		6: Psicometro := 53;			7: Psicometro := 47;			8: Psicometro := 43;			9: Psicometro := 41;			10: Psicometro :=33;
	    END_CASE;
	19: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 91;			2: Psicometro := 82;			3: Psicometro := 74;			4: Psicometro := 66;			5: Psicometro := 60;
		6: Psicometro := 54;			7: Psicometro := 48;			8: Psicometro := 44;			9: Psicometro := 42;			10: Psicometro :=34;
	    END_CASE;
	20: CASE Dif_S_H OF
		0: Psicometro := 100;			1: Psicometro := 91;			2: Psicometro := 82;			3: Psicometro := 74;			4: Psicometro := 67;			5: Psicometro := 61;
		6: Psicometro := 55;			7: Psicometro := 49;			8: Psicometro := 46;			9: Psicometro := 43;			10: Psicometro :=36;
	   END_CASE;
 
ELSE
		Psicometro := 0;
END_CASE;

Mediante las dos primeras instrucciones dejamos la parte entera. El Fb esta hecho para temperatura en ºC x 10, por lo tanto el ultimo dígito es el decimal que quitaremos puesto que la tabla solo contempla valores enteros.  La variable Dif_S_H almacena la diferencia entre los dos termómetros.

Lo siguiente es la primera instrucción Case mediante la cual seleccionamos la temperatura de bulbo seco, de aquí pasara a la siguiente función Case donde asignara un valor de humedad en función de la diferencia (Dif_S_H).

No se si esto puede tener una aplicación practica. Quizás sea mas interesante hacerlo en Vb.net con un par de sondas de temperatura 1 wire y un pequeño ventilador. (Me lo apunto para hacerlo un día que tenga tiempo).