Читать книгу LabVIEW: Entorno gráfico de programación - José Pelegrí Sebastià - Страница 8

Оглавление

Capítulo 2

Estructuras

Las instrucciones de control son una de las herramientas básicas de todo lenguaje de programación y permiten a un programa ejecutar un código de forma condicional o repetirlo cierto número de veces.

En LabVIEW estas instrucciones son estructuras que encierran en su interior el código al que afectan. Se encuentran en el menú Programming > Structures.


Figura 2-1. Estructuras.

A continuación se va a tratar cada una de las estructuras que aparecen en la figura 2-1.

2.1. SEQUENCE

En los lenguajes tradicionales basados en texto, el orden de ejecución se corresponde con el orden en que las instrucciones están escritas. Ya se ha visto que el sistema de ejecución de LabVIEW sigue el modelo de flujo de datos (dataflow): un nodo necesita tener disponibles los datos en todas sus entradas para ejecutarse. Pero si hay dos nodos en condición de ejecutarse, no se podrá determinar, en principio, el orden de ejecución; esto en la mayoría de casos no será un problema, es más, será incluso beneficioso. No obstante, puede haber ocasiones en las que haya nodos independientes, ambos en situación de ejecutarse, pero se necesita fijar el orden de ejecución de los mismos.

Las estructuras de tipo SEQUENCE sirven precisamente para esto: establecen el orden de ejecución del código que está en su interior.

Su diseño recuerda a los fotogramas de una película. En una película los fotogramas colocados al principio se visualizarán antes que los siguientes, con un orden secuencial. Las estructuras SEQUENCE también tienen fotogramas o frames ordenados, en el interior de cada frame se situará una sección de código. La ejecución comenzará por el primer frame y, cuando acabe, se pasará a ejecutar el siguiente, y así sucesivamente.

Hay dos tipos de SEQUENCE: STACKED SEQUENCE y FLAT SEQUENCE.

La primera era la única disponible en versiones más antiguas de LabVIEW. Tiene un menú en la parte superior donde se indica la numeración del frame que se muestra, el número total de frames que contiene y además da la opción de situarse en otro. En la figura 2-2 se muestran superpuestos los dos frames de la misma estructura.


Figura 2-2. Frames de una secuencia.

El menú contextual puede desplegarse presionando con el botón secundario del ratón en el borde de la estructura. Este menú permite crear frames antes y después del mostrado, además de otras opciones. La opción Sequence Local crea unos ‘túneles’ entre un frame y los demás para compartir datos; en uno de ellos se escribirá un valor (simbolizado con una flecha hacia el exterior del frame) y en los posteriores podrá leerse (con una flecha hacia el interior); no se podrá leer en frames anteriores al de escritura porque evidentemente el dato en el túnel aún no ha sido escrito.


Figura 2-3. Sequence Local: permite compartir datos entre frames.

También pueden pasarse y recibirse datos desde una estructura SEQUENCE al exterior a través de túneles, representados mediante un pequeño cuadrado en el borde de la estructura. Cuando hay un dato de salida, sólo un frame de la estructura puede escribir valores en él.

FLAT SEQUENCE funciona de igual forma, sólo que es más visual: los frames se ven uno a continuación del siguiente, el orden de ejecución será de izquierda a derecha. En este caso no hay Sequence Local y los datos podrán cablearse directamente desde un frame a otro a través de túneles. El menú contextual también será el que permita añadir y eliminar frames. Se puede cambiar de un tipo de SEQUENCE a otro de forma automática.

En la figura 2-4 puede verse una estructura FLAT SEQUENCE con dos frames: en el primero se genera un número aleatorio y se pasa al segundo frame a través de un túnel.


Figura 2-4. FLAT SEQUENCE con dos frames.

2.2. CASE

La estructura CASE es el equivalente a varias de los lenguajes basados en texto: IF, SWITCH y TRY. Su utilidad es ejecutar un código u otro dependiendo de una condición.

Al igual que una estructura SEQUENCE, en este caso también se tiene un menú en la parte superior donde se puede elegir el subdiagrama que se muestra. En este menú se puede ver la condición para ejecutar el código del subdiagrama correspondiente. En la figura 2-5 se muestra un CASE con dos subdiagramas: uno se ejecutará cuando la condición que se evalúa sea FALSE y otro cuando sea TRUE.


Figura 2-5. Estructura CASE.

El terminal que aparece en el lado izquierdo marcado con el símbolo «?» es llamado selector. El valor que llega a este selector es la condición que se evalúa para seleccionar el subdiagrama a ejecutar. Si el tipo de datos que se conecta al selector del CASE es booleano, éste actuará como una sentencia IF… THEN…ELSE de un lenguaje de texto tradicional. También pueden conectarse otros tipos de datos; en este caso actuará como un SWITCH…CASE. Pueden conectarse al selector datos booleanos, numéricos (incluidos enum y ring), strings y clusters de error. En la figura 2-6 pueden verse todos estos casos.


Figura 2-6. Tipos de datos que son válidos en un CASE.

Para un selector booleano sólo se tendrán dos casos: verdadero o falso. Para numéricos, la condición será que el dato del selector sea igual al mostrado en el menú del CASE, para datos enum o ring se puede escribir el nombre del ítem en lugar del valor numérico. Con los strings sucede lo mismo que con los numéricos enum o ring: el valor mostrado en el menú aparecerá como un texto encerrado en comillas dobles. En caso de conectar al selector un cluster de error, cambiará el color del borde de la estructura, y los subdiagramas se ejecutarán dependiendo de si el selector marca un error o no.

Cuando el selector se conecta a un string o a un dato numérico, es obligatorio tener algún caso que se ejecute por defecto, es decir, debe haber un caso que se ejecute cuando en el selector haya un valor que no esté asignado explícitamente a algún subdiagrama. Para hacer que un subdiagrama sea ejecutado por defecto, debe seleccionarse Make This The Default Case en el menú contextual.

Se pueden agrupar varios casos en los CASE numéricos y strings escribiendo los valores separados por comas. Por ejemplo, en el caso de strings se podría escribir «“texto1”, “texto2”».

Además, en los numéricos se puede asignar un rango de valores; por ejemplo, si se desea ejecutar el mismo código cuando la entrada tiene el valor 4, 5, 6, 7 y 8, en el menú del CASE se escribirá «4..8». También se puede combinar esta técnica con la anterior.

Para pasar datos a los subdiagramas del CASE se utilizarán los túneles. En el caso de datos de salida, todos los subdiagramas deben proporcionar un valor; hasta que esto no ocurra LabVIEW indicará el error missing assignament to tunnel y aparecerá el túnel con el interior vacío, como se muestra en la parte derecha de la figura 2-7. También existe la opción de marcar sobre el túnel Use Default If Unwired, con lo que se consigue que se asigne el valor por defecto para todos aquellos casos en los que no se ha cableado un valor en el túnel de salida. Finalmente, a través del menú contextual del túnel se pueden cablear de forma automática los túneles en todos los subdiagramas con la opción Linked Input Tunnel.


Figura 2-7. Error en un CASE: túnel vacío.

Por último, también hay que decir que se puede cambiar el orden de los subdiagramas desde la opción Rearrange Cases… del menú contextual.

2.3. WHILE

El bucle WHILE repetirá el código de su interior hasta que se cumpla una condición, que es evaluada en cada iteración.

En la figura 2-8 puede verse el aspecto de este bucle, en el que se aprecian dos terminales:

• El terminal de iteración es el cuadrado azul con el símbolo «i». El valor de este terminal es un número entero que irá aumentando en una unidad por cada iteración del bucle, empezando a contar desde cero.

• La condición de stop es el terminal verde de la esquina inferior derecha de la imagen. A este terminal se podrá conectar bien un valor booleano, bien un cluster de error. A través del menú contextual podrá elegirse para los booleanos que el bucle se detenga cuando el valor sea TRUE (Stop if True) o FALSE (Continue if True). En el caso de los cluster de error sucede algo parecido con Stop on Error y Continue while Error.


Figura 2-8. Estructura WHILE.

Otra de las opciones que muestra el menú contextual es Add Shift Register. Esta herramienta añade dos terminales a cada lado de la estructura, terminales que sirven para transferir un valor desde una iteración del bucle a la siguiente. Los valores se pasarán a la siguiente iteración en el terminal de la derecha y se leerán en el de la izquierda. Si se conecta un valor al terminal de la izquierda en el exterior de la estructura, éste será el valor inicial que circulará por ese cable en la primera iteración.

En la figura 2-9 (a) se muestra el uso de un shift register. En la primera iteración se leerá el valor 10 del terminal de la izquierda, se le sumará 1 y se escribirá en el terminal de la derecha el valor 11. En la siguiente iteración este valor será el leído en el terminal de la izquierda, después se le volverá a sumar 1 y así sucesivamente hasta que se cumpla la condición de parada del bucle, en este caso que el número sea mayor o igual a quince. Este proceso puede comprobarse ejecutando el código de la figura con la opción de Highlight Execution activada.


Figura 2-9. (a) Shift register en un bucle WHILE. (b) Varios shift registers.

El shift register de la izquierda puede extenderse para mostrar más terminales. El terminal superior tendrá el valor que se escribió en la derecha en la iteración anterior; el siguiente terminal tendrá el valor que se escribió en la derecha dos iteraciones antes, y así sucesivamente. El concepto es similar al retraso (z-1) en procesado digital de señales.

En el ejemplo de la figura 2-9 (b) se puede ver un ejemplo del uso de varios shift register. La tabla 2-1 muestra los valores que tendrán cada uno de los terminales en cada iteración.


Tabla 2-1. Valores de los shift register en cada iteración.

Hay una utilidad que funciona de igual forma que un shift register: es el Feedback Node. Éste consta de dos terminales:

• El terminal inicializador permite dar un valor inicial al nodo, y equivale a conectar un valor al terminal izquierdo de shift register. Este terminal se coloca siempre en el borde izquierdo de la estructura a la misma altura que el Feedback Node.

• El Feedback Node es el otro terminal y tiene forma de flecha. En el extremo derecho se le conectará la salida, cuyo valor será leído por el extremo izquierdo en la siguiente iteración. En la primera iteración el valor leído por el extremo izquierdo será el conectado al terminal inicializador. Lógicamente, siempre se ejecuta primero la lectura y después la escritura.

El código de la figura 2-10 ilustra el uso del Feedback Node. El programa es igual al de la figura 2-9 (a).


Figura 2-10. Feedback Node.

En el menú Tools > Options > Block Diagram se tiene la opción de insertar automáticamente Feedback Nodes dentro de los bucles cuando sea necesario.

A través del menú contextual de los Feedback Nodes o de su panel de propiedades podremos configurar su aspecto, uno como el mostrado en la figura 2-10, y otro con z-1 (menú contextual > Z-Transform Delay Node), lo cual puede ser más natural en programas de procesado de señal. También se puede configurar cómo se le da el valor inicial (al compilarse el VI, al cargarse en memoria o al comenzar el bucle), el sentido de los datos (derecha a izquierda o al revés), mostrar un terminal de habilitación o el número de iteraciones que hay que esperar antes de que el Feedback Node comience a actuar.

Al igual que en otras estructuras, los datos que entren y salgan de una estructura WHILE lo harán a través de túneles. Una opción muy interesante de los túneles de los bucles (WHILE y FOR) es el Autoindexing, que se puede habilitar a través del menú contextual del túnel cuando se quiera trabajar con arrays. Cuando se cablea un array desde el exterior al interior de un bucle y se habilita el autoindexing, los valores leídos en el interior del bucle de ese terminal serán los elementos que componen el array, uno por cada iteración, en este caso la estructura funciona como un FOREACH. Cuando el autoindexing está en una salida del bucle ocurre lo contrario: se construirá un array cuyos elementos serán los generados en cada iteración.

En la figura 2-11 puede verse un programa parecido al anterior. En el borde derecho pueden verse tres terminales: el superior es un túnel, el siguiente un shift register y el inferior una salida indexada. El valor de los dos primeros será el generado por el programa en su última iteración (15), mientras que el valor en el último terminal será una lista ordenada de todos los números generados en cada iteración (11, 12, 13, 14 y 15).


Figura 2-11. Ejemplo de túnel, shift register y autoindexing.

2.4. FOR

El bucle FOR es muy parecido al WHILE. También repite el código de su interior un número de veces pero, a diferencia del anterior, este número es fijado a priori y no puede cambiarse una vez empiece a ejecutarse.

Consta de dos terminales numéricos:

• El terminal de iteración se sitúa igual que en el bucle WHILE, está en el interior de la estructura y se va incrementando en una unidad por cada iteración empezando desde cero.

• El terminal de cuenta está colocado en la esquina superior izquierda de la estructura simbolizado con una «N». En él se conectará un valor numérico que será el que fije el número de repeticiones del bucle.


Figura 2-12. Estructura FOR.

Además, desde la versión 8.5 de LabVIEW se puede añadir un tercer terminal a través del menú contextual. Este terminal es el de condición que también tiene el WHILE para detener el bucle. Su utilización sería equivalente al empleo de un BREAK en el interior de un FOR. Hay que advertir que el rendimiento del bucle FOR puede disminuir al emplear el terminal de condición.

Todo lo dicho en la explicación del bucle WHILE respecto a los shift register, los Feedback Nodes y la salida indexada también es válido para el FOR. Tanto en el menú contextual del WHILE como del FOR existe la opción de sustituir uno por el otro.

Los túneles indexados son la opción por defecto en los bucles FOR. Cuando se cablea un array de forma indexada como entrada, puede obviarse el terminal de cuenta porque se toma el tamaño del array como el número de veces que se ha de repetir el bucle; es equivalente a un FOREACH.

Si por cualquier motivo, como cablear un cero al terminal de iteraciones o una entrada indexada con un array vacío, provocara que el bucle FOR no tuviera ninguna iteración, a la salida aparecerán los valores por defecto.

2.5. EVENT

La estructura EVENT fue introducida por primera vez en la versión 6.1 de LabVIEW. Es una estructura muy útil en VI con los que interactúa el usuario porque mejora la eficiencia del programa.

Al igual que la estructura CASE, los EVENT tienen varios subdiagramas y un menú en la parte superior para cambiar el que se muestra. En este menú también se indica una condición que hace que el código del subdiagrama correspondiente se ejecute. La diferencia con CASE es que EVENT detiene la ejecución del hilo del programa hasta que se da esa condición, es decir, congela el programa hasta que ocurre un evento.

En la esquina superior izquierda hay un terminal llamado Event Timeout que se usa en el evento por defecto: el Timeout. El código del diagrama para el evento timeout se ejecutará cuando pase el número de milisegundos indicados en el terminal Event Timeout.


Figura 2-13. Estructura EVENT

Para añadir más subdiagramas hay que proceder de igual manera que con CASE, es decir, a través del menú contextual. Cada diagrama debe tener asociados uno o varios eventos, que se configuran desde la ventana Edit Events, como puede verse en la figura 2-14.

En esta ventana primero se muestra el número del diagrama (Events Case) y, a continuación, la lista de los eventos que pueden dar lugar a la ejecución del diagrama (Event Specifiers). Para añadir o eliminar eventos se usan los botones de abajo (Add Event y Remove). Para definir un evento primero hay que especificar su fuente en Event Sources, los eventos de las secciones Application y This VI están predefinidos y son acciones típicas como cerrar la ventana, presionar una tecla, etc. La fuente de eventos Dynamic sólo está disponible cuando se activa Show Dynamic Event Terminals y se cablean convenientemente. Para más información puede consultarse el tema dedicado a programación multihilo en la sección 10.5; Panes se activa cuando el Panel Frontal se divide en varias partes, Splitters sólo se activa cuando hay uno de estos elementos en el Panel Frontal y, finalmente, Controls muestra una lista de todos los controles que hay en el VI. En Events se podrá elegir el evento concreto asociado a la fuente seleccionada.

En este ejemplo se ejecutará el diagrama únicamente cuando el control llamado Boolean cambie de valor.


Figura 2-14. Ventana de configuración de eventos.

En la parte izquierda de cada subdiagrama de la estructura EVENT se sitúa Event Data Node que aporta información sobre la fuente del evento, por ejemplo una referencia al control, su valor actual, el valor anterior al evento, etc.

Si se observa con detalle la figura 2-14 puede verse que hay eventos marcados con una flecha roja que acaban en una interrogación y otros con el mismo nombre pero con una flecha verde y sin interrogación. Los que tienen la flecha roja e interrogación se llaman filtros. Los filtros se ejecutan antes que la acción asociada al evento, pudiendo, entre otras cosas, desactivarlo. Cuando se selecciona un filter event aparecerá automáticamente un nodo en la parte derecha del diagrama llamado Event Filter Node.

El ejemplo de la figura 2-15 muestra el uso de los filter events para descartar un evento; en este caso sirve para impedir que el usuario cierre el Panel Frontal del VI.


Figura 2-15. Ejemplo de uso de filtros en un evento.

Finalmente hay que citar algunas recomendaciones que hace National Instruments sobre el uso de la estructura EVENT, como no usar un EVENT dentro de otro ni dos EVENT dentro del mismo bucle.

Si un EVENT se inserta dentro de un WHILE y éste acaba mediante un botón (un botón de stop del tipo latch when released/pressed) con un evento asociado a dicho botón, se debe insertar el botón dentro del subdiagrama asociado al cambio de valor de dicho botón. La razón es asegurarse que la evaluación de la condición del bucle se realice después de la ejecución del EVENT. En caso de que el botón de stop se conectara directamente al terminal de condición del WHILE, quedaría en paralelo con el EVENT; el problema es que el EVENT congelará su ejecución hasta que se produzca un evento (por ejemplo un cambio de valor en el botón de stop); mientras esa parte del programa está congelada se evaluaría la condición de terminar el código del evento con el valor del botón de stop en reposo, es decir, se evalúa la condición antes de presionar el botón y, por tanto, antes de ejecutar el EVENT; el efecto es que serían necesarias dos pulsaciones del botón para detenerse.

Los cambios de valor en controles de forma programada no generan eventos (en eventos registrados de forma estática), sólo se generan cuando se realiza el cambio de valor mediante la interfaz de usuario. Una excepción es cuando se escribe empleando la propiedad Value (signaling).

En esta sección se han estudiado únicamente los eventos estáticos, pero hay dos tipos más: eventos dinámicos y definidos por el usuario. Éstos se explicarán en el tema sobre programación multihilo en la sección 10.5.

2.6. TIMED LOOP y TIMED SEQUENCE

TIMED LOOP es una estructura que apareció en la versión 7.1. Su funcionamiento consiste en repetir el código de su interior con unas determinadas especificaciones de tiempo o hasta que se cumpla cierta condición, por lo que es muy usado en aplicaciones de tiempo real.

Su dibujo recuerda a un WHILE circundado por un halo azul, aunque a diferencia de éste, no es necesario establecer una condición de parada o continuación. También presenta varios nodos, que son (de izquierda a derecha):

Input: permite configurar el funcionamiento del bucle por medio de un asistente o cableando los datos. Este nodo puede extenderse para cablear más datos de los mostrados.

Left Data: proporciona información sobre la iteración anterior, por ejemplo el tiempo que ha tardado (Iteration Duration), si le ha dado tiempo a acabar la tarea antes de que empiece una nueva ejecución (Finished Late? [i-1]) y otras.

Right Data: se trata de una configuración dinámica que permite modificar los parámetros de configuración de la estructura para la siguiente iteración. Los parámetros son prácticamente los mismos que en el Input Node, excepto el nombre del bucle y el origen del reloj.

Output: al igual que el Left Data Node, el Output Node proporciona información, pero en este caso la información se genera después de que el bucle se haya detenido.


Figura 2-16. Estructura TIMED LOOP.

La configuración puede realizarse cableando los valores adecuados en el Input Node o mediante los parámetros que aparecen haciendo doble clic sobre el primer y tercer nodo.


Figura 2-17. Ventana de configuración del TIMED LOOP.

A continuación se verán algunas de las principales partes mostradas en la figura 2-17.

Loop Timing Source es la fuente de reloj. Cuando el programa se ejecute sobre un PC, la fuente será un reloj de 1 kHz del sistema operativo; si la ejecución se realiza en otros dispositivos, puede haber otras fuentes disponibles. Para seleccionar la fuente desde el programa puede usarse el VI Create Timing Source, en la paleta Programming > Structures > Timed Structures. Los otros VI de esta paleta permiten abortar programadamente un TIMED LOOP (Stop Timed Structure), sincronizarlos (Syncronize Timed Structure Starts), controlar el comienzo de cada uno (Build Timing Source Hierarchy) y crear, disparar y borrar una origen de reloj.


Figura 2-18. Estructuras temporizadas.

El periodo será el tiempo que pase entre el comienzo de dos iteraciones, y el offset el tiempo que el bucle espera antes de empezar la primera ejecución. Deadline es el tiempo máximo con que cuenta el bucle para ejecutar el código en su interior; en caso de que la ejecución lleve más tiempo se avisará con Finished Late? [i-1]. Los modos de Action on Late Iterations configuran la forma en que el bucle responde cuando la ejecución lleva más tiempo del especificado; las opciones son autoexplicativas, básicamente sirven para ‘alinear’ el comienzo de las ejecuciones y ‘saltar’ iteraciones. En sistemas multinúcleo también se puede elegir con qué procesador se ejecutará el bucle con Processor Assignment.

Para ejecutarse, cada TIMED LOOP crea su propio sistema de ejecución que contiene un único hilo (ver capítulo 10 dedicado a multihilo). La prioridad se refiere a la preferencia de ejecución entre un bucle y los demás. Los valores más altos corresponderán a TIMED LOOP con mayor prioridad. Cuando dos o más bucles vayan a empezar su ejecución en un instante determinado, la prioridad de cada bucle determinará el orden. En caso de que haya varios con el mismo nivel de prioridad, el orden será comenzando por el de menor tiempo invertido en la ejecución.

También se pueden añadir frames a un TIMED LOOP mediante el menú contextual del mismo modo que se hacía con un SEQUENCE, con lo que un TIMED LOOP podrá ejecutar varios subdiagramas secuencialmente, cada uno con sus propias especificaciones temporales.


Figura 2-19. TIMED LOOP con frames.

En este caso el Right Data Node de un frame cambiará los parámetros del siguiente subdiagrama a ejecutarse en lugar de la siguiente iteración y el Left Data Node proporcionará información sobre el subdiagrama anterior.

El Left Node del primer subdiagrama y el Right Node del último proporcionan más parámetros que el resto debido a que pueden afectar al siguiente/anterior subdiagrama o también a toda la estructura.

En el mismo menú, junto a TIMED LOOP se encuentra una estructura parecida llamada TIMED SEQUENCE. La principal diferencia con el TIMED LOOP es que en este caso no se repite la ejecución de los subdiagramas, por lo tanto no se podrá especificar un periodo, pero aun así sí puede tener otras características temporales como offset, deadline, etc.


Figura 2-20. Estructura TIMED SEQUENCE.

2.7. DISABLE STRUCTURE

Estas estructuras también han aparecido recientemente, pues se usaron por primera vez en la versión 8.0. Sirven para comentar el código, por lo que son muy útiles en la depuración de programas. Hay dos tipos de estructuras de deshabilitación: la incondicional y la condicional.

La incondicional es una estructura que, como CASE, STACKED SEQUENCE o EVENT, se compone de varios subdiagramas. Uno de ellos estará habilitado y el resto estarán deshabilitados; lógicamente el habilitado será el único que se ejecute, y el resto no llegarán a compilarse. Para cambiar el subdiagrama habilitado hay que hacer uso del menú contextual de la estructura.


Figura 2-21. Estructura DISABLE.

La condicional funciona de la misma forma que la anterior, excepto que el diagrama habilitado se selecciona de forma automática dependiendo del valor de unos símbolos asociados al proyecto. Algunos símbolos predefinidos son OS y CPU.

También se pueden definir nuevos símbolos en la ventana de propiedades del proyecto correspondiente, como muestra la figura 2-22.


Figura 2-22. Ventana para crear símbolos.

Una vez creados los símbolos pertinentes, hay que editar la condición de la estructura con Edit Condition For This Subdiagram… del menú contextual. Se puede añadir más de una condición con el botón «+».


Figura 2-23. Configuración de la condición DISABLE.

2.8. FORMULA NODE

La estructura FORMULA NODE puede encontrarse tanto en el menú Programming > Structures como en Mathematics > Scripts & Formulas.

A diferencia de las anteriores, FORMULA NODE no controla el flujo de ejecución, sino que evalúa una expresión matemática escrita como texto con una sintaxis parecida al lenguaje C.

El texto consistirá en una serie de sentencias finalizadas por el símbolo «;». Las sentencias normalmente son asignaciones que usan operadores o funciones, aunque también pueden ser declaraciones de variables, bucles o sentencias de condición. También pueden insertarse comentarios de la misma manera que en C.

Casi todas las funciones que se pueden usar dentro de un FORMULA NODE tienen su equivalente como VI. Éstas son: abs(x), acos(x), acosh(x), asin(x), asinh(x), atan(x), atan2(x,y), atanh(x), ceil(x), ci(x), cos(x), cosh(x), cot(x), csc(x), exp(x), expm1(x), floor(x), getexp(x), gamma(x), getman(x), int(x), intrz(x), ln(x), lnp1(x), log(x), log2(x), max(x,y), min(x,y), mod(x,y), pow(x,y), rand( ), rem(x,y), si(x), sec(x), sign(x), sin(x), sinc(x), sinh(x), sizeOfDim(array,dim), spike(x), sqrt(x), step(x), tan(x), tanh(x).

Los operadores son:

** exponenciación.

+, -, ++, - -, !, ~ suma y resta, pre-post incremento-decremento, negación lógica y complemento de bits.

*, /, %, +, - operaciones básicas.

≫, ≪ desplazamientos.

<, >, >=, <=,!=, = = comparaciones

&, ^, |, &&, || operaciones lógicas.

…?...:… evaluación condicional.

= asignación.

• Las estructuras son:

If (condición) sentencias1 else sentencias2

do sentencias while (condición)

while (condición) sentencias

for (asignación; condición; sentencia) sentencias

switch (condición) lista de casos

Otras palabras reservadas son: break, case, continue, default, pi. Para más información puede consultarse la ayuda.

Presionando el botón secundario en los bordes de la estructura pueden crearse variables de entrada o de salida.

En el ejemplo dado en la figura 2-24 se tiene la fórmula de Herón para calcular la superficie de un triángulo a partir de sus lados; en ella hay tres entradas (a, b, c) que corresponden a la longitud de los lados del triángulo, un resultado intermedio que se trata como una salida más (p) y una salida que es la superficie (S).


Figura 2-24. Fórmula de Herón.

2.9. Scripts

Al igual que el nodo FORMULA NODE, hay otras estructuras que también aceptan textos, de forma que se puede combinar la programación propia de LabVIEW con la más tradicional programación textual.

El MathScript Node es otra de las nuevas estructuras de LabVIEW 8.0 y mejorada en 8.20. A diferencia de otras, MathScript Node no llama a otros programas, y su código es compilado junto con el resto del VI.

En Tools > MathScript Window… puede abrirse una ventana (Figura 2-25) para ayudar a depurar el código. En el campo Command Window pueden escribirse los comandos, también puede ejecutarse un script completo desde la pestaña Script y los resultados pueden verse en la pestaña Variables.


Figura 2-25. Depuración de código con MATH SCRIPT.

La sintaxis que se usa en la estructura MathScript Node es en gran medida compatible con la de MATLAB. Dispone de cientos de funciones que pueden consultarse en la ayuda.

Por otra parte, MATLAB Node también se encuentra únicamente en el menú Mathematics > Scripts & Formulas > Script Nodes. Esta estructura llama al servicio «Matlab Server» a través de ActiveX para ejecutar los comandos (sólo en Windows).

En versiones antiguas también existía XMath Script Node, que utilizaba el programa MATRIXx de National Instruments, similar a MATLAB, pero en las versiones actuales ya no se mantiene.

Al igual que con FORMULA NODE, se deben crear variables de entrada y salida, pero en este caso hay que asignar explícitamente el tipo de datos, tal y como puede verse en la figura 2-26.


Figura 2-26. MathScript Node y MATLAB Script Node.

En la figura 2-27 pueden verse estas dos estructuras. El código de ambas es equivalente: se trata del diseño de un filtro y su aplicación a una señal aleatoria. Obsérvese el parecido de los códigos.


Figura 2-27. Implementación de un filtro utilizando las dos estructuras.

2.10. Ejemplos

2.10.1. Ejemplo I: Filtro promediador

2.10.1.1. Explicación teórica

Los filtros integradores o promediadores son un tipo de filtro paso bajo. Se emplean para reducir el ruido de una señal suponiendo que éste es de frecuencia mucho más alta que la propia señal. También se aplica en la edición de imágenes para añadir difuminados.

El funcionamiento es sencillo: calcular el promedio de una señal en un intervalo determinado.

En un promediador móvil existe lo que se llama ‘ventana’, que indica el tamaño del intervalo a promediar. En un sistema discreto este tamaño serán las muestras de la secuencia de entrada que se promediarán. Una ventana móvil avanza en cada paso del algoritmo una posición en la secuencia de entrada para realizar el promediado como se muestra en la figura 2-28.


Figura 2-28. Ventana móvil para realizar el promediado.

Matemáticamente puede expresarse la ecuación en diferencias del sistema como:


Donde:

y(n): valor calculado.

N: tamaño de la ventana.

x: señal de entrada.

k: índice para recorrer los valores a promediar.

Aplicando la propiedad del desplazamiento temporal, se puede hallar la transformada Z del sistema definido por la ecuación anterior y, a su vez, de esta nueva expresión se obtendrá la estructura del filtro.


La estructura de la figura 2-29 se ha particularizado para un orden igual a tres.


Figura 2-29. Filtro de orden tres.

2.10.1.2. Código

Para realizar este ejemplo se implementará un filtro promediador móvil de orden tres que añada un difuminado a una fotografía.

La entrada será una imagen en blanco y negro con formato BMP que consiste en una lista (array) de valores de cada uno de los componentes de la imagen. Estos componentes son RGB (Red, Green, Blue), por lo que para cada punto habrá tres valores. La siguiente lista representa cómo sería este array para una imagen con cuatro puntos:

R1, G1, B1, R2, G2, B2, R3, G3, B3, R4, G4, B4...

Como la imagen sólo contiene grises, los valores de los tres componentes son iguales, por lo tanto bastará con aplicar el filtro a sólo uno de los componentes o lo que es lo mismo, a uno de cada tres valores.

El programa empezará leyendo el fichero y desglosando su información; entre esta información se obtendrá un array RGB como el anterior. Por simplicidad se trabajará en una dimensión; una mejora sería actuar por filas y luego otra vez por columnas.

La función Decimate 1D Array con un tamaño de tres elementos obtendrá en la primera de sus salidas un array cuyos elementos sean los de las posiciones 1, 4, 7, 10, 13… del array de entrada, es decir, obtendrá todos los valores del componente R de cada punto.

Después los valores R de cada punto son promediados: el promediador sumará el último valor leído y los tres anteriores; el resultado se dividirá por cuatro. La ‘ventana’ se implementará mediante shift registers y el resultado se irá indexando en el lateral del FOR.

Para reconstruir la imagen basta con hacer el proceso inverso al de decimar: interpolar. Finalmente, se dibujan las imágenes antes y después de aplicar el filtro.

Mediante la señal selector se podrá elegir el tipo de estructura implementada. Si el selector está activado el filtro será como en la figura 2-29, y si no está activado habrá realimentación de las salidas a la entrada (IIR).


Figura 2-30. VI que implementa un filtro promediador móvil de orden 4.

2.10.1.3. Resultado

Para explicar el efecto del filtro se puede decir que proporciona cierta resistencia o inercia al cambio en la imagen. En la figura 2-31 puede verse el resultado del filtro sobre una fotografía.


Figura 2-31. Resultado del filtro sobre una fotografía.

La figura 3-32 es un detalle del principio de la fotografía después de aplicar el filtro usando realimentación de salidas a la entrada para magnificar este efecto. En ella se ve cómo hay una serie de puntos negros en la esquina que en la original no estaban, esto se debe a la condición inicial del filtro (hay que esperar a que todos los shift registers tengan datos que provengan de la foto).


Figura 2-32. Detalle de los primeros puntos que pasan por el filtro y del borde izquierdo.

Otro efecto que se puede apreciar es que la parte derecha de la imagen parece extenderse en la izquierda; es decir, la línea del horizonte de la parte derecha se puede ver que se extiente por la parte izquierda también. Esto se produce porque se ha considerado la imagen como un único array; para solucionar esto se podría aplicar el filtro a cada una de las filas.

Es aconsejable ejecutar este ejemplo con la opción Highlight Execution activada, pues de esta manera se puede ver un ejemplo práctico del uso de los shift registers y la salida indexada en los bucles.

Como puede intuirse, este método se puede generalizar para otros tipos de filtros FIR e IIR.

2.10.2. Ejemplo II: Generación de números primos

2.10.2.1. Explicación teórica

Como todo el mundo sabe, un número primo es el número natural que sólo es divisible por él mismo y por la unidad.

Desde los tiempos de Euclides (300 a. de C.) se sabe que existen infinitos números primos. Los primeros algoritmos para encontrar números primos también proceden de la época de los antiguos griegos, como la «criba de Eratóstenes». Desde entonces ha pasado mucho tiempo, pero aún se sigue investigando en este campo. Por ejemplo, en el año 2004 se creó otro algoritmo que mejora el anterior llamado «criba de Atkin».

La generación de números primos es una herramienta muy interesante en campos como la criptografía, donde algunos algoritmos como el RSA usan números primos de valores altos como base para realizar el cifrado.

2.10.2.2. Código

En este ejemplo se optará por un algoritmo que sea lo más sencillo posible, consistirá en hacer un barrido de números empezando por el dos hasta el límite indicado por el usuario, es decir, se recorrerán los números 2, 3, 4, 5, 6, 7, 8… Para cada número se volverá a hacer un barrido hasta encontrar un número que sea divisor del primero. Si estos dos números son iguales significa que el único divisor de este número es él mismo, por lo tanto es un número primo. En la figura 2-33 puede verse un diagrama de flujo de este algoritmo. Nótese que en este algoritmo, al igual que en muchos otros, se obvia el número uno.

El código para implementar este algoritmo con el nodo FORMULA es exactamente igual que en lenguaje C. En primer lugar, se declararán las variables y, a continuación, se usan dos bucles FOR, uno para cada barrido.

Para determinar si un número es divisor del otro, se comprobará si el resto de la división entre ambos es igual a cero. Finalmente, los números primos encontrados se almacenan en un array.


Figura 2-33. Algoritmo para la obtención de números primos.


Figura 2-34. Implementación del algoritmo en un VI.

2.10.2.3. Resultado

La figura 2-35 muestra un array con los números primos que hay entre dos y quince como resultado de la ejecución del programa anterior.


Figura 2-35. Números primos obtenidos.

En este ejemplo se han aprendido dos cosas: en primer lugar a usar el FORMULA NODE, y en segundo lugar a darse cuenta de que, a pesar de su nombre, esta estructura no sólo sirve para introducir fórmulas matemáticas sino que también se pueden emplear en ella elementos de un lenguaje de programación como bucles, funciones, etc.

2.10.3. Ejemplo III: Bingo

2.10.3.1. Explicación teórica

En esta ocasión se desea realizar un programa que genere un cartón para jugar al bingo. Para esto se necesita crear una matriz o array de dos dimensiones que contenga valores aleatorios entre 1 y 100. Habrá diez columnas (una para cada decena) y cuatro filas.

2.10.3.2. Código

El programa principal tiene la típica estructura de un WHILE y un EVENT.

El evento que se muestra en la figura 2-36 corresponde al cambio de valor de un botón llamado cartón. Este botón tiene por acción mecánica Latch When

Released (se verá en el próximo capítulo). Cuando se presiona el botón se ejecutará el subdiagrama.

Para generar un cartón se necesitan dos bucles; el primero recorrerá cada una de las decenas. Como la cantidad de ejecuciones es conocida, se usará un FOR. Dentro de este bucle habrá otro; este segundo bucle será un WHILE, y en él se generarán números de forma aleatoria entre dos límites, límites que sirven para acotar los números dentro de la decena correspondiente.

La función Random Number (en Programming > Number) devuelve números entre el 0 y el 1. Para generar números enteros se multiplicará por 10 y se sumará el límite inferior; el valor resultante se aproxima al entero superior. En caso de que el número ya haya sido generado antes se descartará; si no había sido generado, se almacenará en un array. Para detectar si el número ya se había generado, se busca dentro del array si algún valor coincide con el nuevo mediante Search 1D Array (en Programming > Array). Si no había ninguno, la salida de esta función contendrá el valor «-1» y se ejecutará el diagrama mostrado en el CASE. Para cualquier otro número se ejecutará el caso por defecto que simplemente deja pasar el array de la entrada a la salida.

Cuando se han generado cuatro números, se detiene el WHILE, se ordenan los números del array con Sort 1D Array y se pasa a la siguiente decena.

Después de recorrer todas las decenas, en la salida indexada del FOR se habrá generado un array de dos dimensiones con los números de un nuevo cartón.

La estructura EVENT contiene otro subdiagrama para cuando se presione el botón Stop. Este subdiagrama únicamente contiene una constante booleana con el valor TRUE cableada a la condición del WHILE principal. Puede pensarse que sería más sencillo dejar vacío este caso y cablear directamente el botón de Stop a la condición, pero no es recomendable porque, como se ha explicado en la sección 2.5, esto provocaría que la condición del bucle se evaluara antes de presionar el botón, con lo que el valor que tiene en ese instante obligaría a ejecutar otra iteración más.


Figura 2-36. VI para la generación de cartones para jugar al bingo.

2.10.3.3. Resultado

En la figura 2-37 puede verse un cartón de bingo generado con el programa de este ejemplo. Cada vez que se presione el botón Cartón, se generará un nuevo cartón.


Figura 2-37. Cartón obtenido de la ejecución del VI.

Este ejemplo muestra el uso de las estructuras EVENT, WHILE, FOR y CASE. Por una parte, se ha visto cuándo se debe usar FOR (con un número de ejecuciones conocido) y cuándo WHILE (cuando no hay un número determinado de ejecuciones); también se ha visto el uso de la estructura EVENT y el típico problema del botón de Stop y, por último, se ha utilizado un CASE dejando un caso por defecto. En el próximo capítulo se explicarán más detalladamente los arrays.

2.11. Ejercicios

1. ¿A qué es equivalente el siguiente programa?


Figura 2-38. Ejercicio 1.

2. Realizar un programa que calcule el factorial de un número. Hacerlo primero con estructuras de repetición y luego con FORMULA NODE.

3. Realizar un programa que genere un array con el primer millón de números enteros mediante un bucle WHILE y un FOR. ¿Cuál es más eficiente? ¿Por qué?

4. Modificar el programa del ejemplo I para que también acepte imágenes a color.

5. Realizar mediante programación visual el mismo algoritmo que en el ejemplo II. Comparar ambos programas en términos de velocidad y espacio.

6. Añadir al evento del ejemplo III otro caso cuando se presione un botón llamado bola. El subdiagrama debe simular el funcionamiento de un bombo para jugar al bingo, debiéndose tener en cuenta no repetir las bolas que ya habían sido sacadas. Opcionalmente también puede añadirse algún mecanismo para detectar si la bola que ha salido estaba en el cartón. (Pista: ver en el siguiente capítulo las propiedades de los controles.)

2.12. Bibliografía

J. Kodosky, E. Pérez, AN 039: Linear System in LabVIEW, National Instruments, 1993.

National Instruments, AN 200: Using the Timed Loop to Write Multirate Applications in LabVIEW, 2004.

National Instruments, LabVIEW Basics I Course Manual, 2000.

National Instruments, LabVIEW User Manual, 2001.

Rick Bitter et al. LabVIEW Advanced Programming Techniques, CRC Press LLC, 2001.

LabVIEW: Entorno gráfico de programación

Подняться наверх