sábado, septiembre 05, 2009

Paso 29: Diseño e implementación de la aplicación de software

1.1.1. Características del sistema

Nuestro sistema de control consiste en una PC x86 estándar, con procesador Pentium III de 700 MHz y 128 MB de memoria RAM. El OS GNU/Linux, está basado en una distribución Debian con kernel 2.6.17 y parche de tiempo real RTAI 3.4.

La PC incluye, además, una placa adquisidora ISA, de 8 canales analógicos diferenciales de 12 bits de resolución con 10 μseg de conversión, 4 entradas y 7 salidas discretas y un temporizador de 16 bits. Tanto la PC como la placa adquisidora fueron provistos por la carrera de Ingeniería en Automatización y Control Industrial de la Universidad Nacional de Quilmes.

El controlador de los motores e interfaz de entradas/salidas (E/S) discretas están concentrados en un microcontrolador Atmega16 de 8 MHz, como se explica en la sección . Los ciclos de trabajo de las señales PWM de cada motor, se transmiten desde la PC al microcontrolador mediante una línea de comunicación RS-232, con un período de 20 mseg.

En la figura 1.1 se muestra un esquema de la disposición de los componentes de la planta.




Figura 1.1: Esquema de conexión entre la PC y los sensores y actuadores de la planta.




1.1.2. Diseño y arquitectura del software

La aplicación del software, desarrollada para cubrir los propósitos del proyecto, está dividida en un proceso para la interfaz de usuario y un módulo del núcleo destinado a las tareas de control, comunicación y adquisición de datos, como puede verse en la figura 1.2.

El proceso del espacio de usuario cuenta con las funcionalidades necesarias para ofrecer interacción entre el sistema y el usuario. Por medio de una simple interfaz de texto, en donde se presentan las variables más significativas de la planta, se reciben los comandos de actuación desde el teclado y se almacenan los datos en archivos. El corazón de la aplicación reside en el espacio del núcleo, desde donde se controlan las variables del sistema y se establece la comunicación, tanto con la placa adquisidora, como con la interfaz serie RS-232.




Figura 1.2: Diseño conceptual del software.



El código fuente de la aplicación se encuentra organizado según se trate del módulo o del proceso, con las características que se enumeran a continuación:

  • Módulo del núcleo

    • Código fuente

      • BORA_rt_task.c: Posee las funciones principales del módulo y se encarga de
        administrar las actividades de la aplicación.

      • BORAcom.c: Provee la interfaz de comunicación entre el uC y la PC por el puerto serie.

      • MR590.c: Contiene las funciones necesarias para manipular los motores de la planta.

      • kControl.c: Destinado para ejecutar las rutinas de control del sistema.

      • kEncoder.c: Posee la tarea de adquisición de pulsos provenientes de los encoders.




    • Archivos de cabecera

      • BORAglobal.h: Común al proceso y al módulo. Contiene las definiciones de tiempo, FIFOs, puerto serie, modos de operación, frecuencias de PWM y estructuras de datos y comandos.

      • serialP.h: Librería con las funciones y definiciones del driver de tiempo real del
        puerto serie.



  • Proceso usuario

    • Código fuente

      • gui.c: Contiene las funciones elementales para el manejo de la interfaz de texto y
        archivos.

      • uShow.c: Interfaz para presentar los datos en pantalla.

      • uOperacion.c: Provee las funciones de interfaz del teclado, menú y motores.


    • Archivos de cabecera

      • uConfig.h: Librería con las definiciones y estructuras de datos empleadas en el
        proceso usuario.


El entorno de desarrollo empleado es un editor de texto simple para KDE denominado Kate [3], que reconoce la sintaxis del lenguaje C/C++, entre otros. Mientras que el compilador es el GNU Compiler Collection (GCC) 4.2.1 [2].

1.1.2.1. Sistema de comunicación IPC
El mecanismo de comunicación entre el proceso y el módulo se establece por medio de dos buffers FIFO, uno de lectura y otro de escritura. El algoritmo ?? muestra la forma en que los buffers han sido implementados con las estructuras de datos asociadas. F_DATOS es, desde el punto de vista del módulo, un buffer de escritura encargado de suministrar todas las variables de importancia que representan al sistema. El lazo de comunicación se cierra con el buffer de lectura F_COMANDO, por el que se reciben los comandos ingresados desde el proceso usuario.

La creación de los FIFOs sucede durante la inicialización del módulo, con la llamada a init_module(). Además, en la misma función, se asocia la rutina de servicio de atención del FIFO de lectura con los datos provenientes desde el proceso, denominada rtf_create_handler().

Las funciones, despModoLibre() y despModoControl(), responden al proceso usuario con los datos actualizados de actuación, según se encuentre en el modo libre o control.


BORA_rt_task.c


1int init_module(void)
2{
3 ...
4 rtf_create(F_DATOS, 8000); /* FIFO de escritura */
5 rtf_create(F_COMANDO, 8000); /* FIFO de lectura */
6 ...
7 res = rtf_create_handler(F_COMANDO, (void*)fifo_handler_comando);
8 ...
9 return OK;
10}
11
12static int fifo_handler_comando(unsigned char fifo)
13{
14 int numbyte;
15 do
16 {
17 numbyte = rtf_get(F_COMANDO, &kMR590, sizeof(kMR590));
18 }
19 while (numbyte != 0);
20 return 0;
21}
22
23static int despModoLibre(void)
24{
25 ...
26 /* Cálculo de referencias en X e Y */
27 /* Cálculo del sentido de giro de los motores en X e Y */
28 /* Actualización de la actuación en modo libre */
29
30 /* Envío de los datos al proceso usuario por medio de F_DATOS */
31 retfifo = rtf_put(F_DATOS, &datos_user, sizeof(datos_user));
32 return OK;
33}
34
35static int despModoControl(void)
36{
37 ...
38 /* Cálculo de referencias en X e Y */
39 /* Cálculo del sentido de giro de los motores en X e Y */
40 /* Actualización de las variables de estado controladas */
41 /* Actualización de las variables de estado observadas */
42 /* Actualización de la acción de control */
43
44 /* Envío de los datos al proceso usuario por medio de F_DATOS */
45 retfifo = rtf_put(F_DATOS, &datos_user, sizeof(datos_user));
46 return OK;
47}


BORAglobal.h

1typedef struct
2{
3 unsigned int niter; /* Num de iteraciones para generar Ref */
4 float refx; /* vector Referencia X */
5 float refy[3]; /* vector Referencia Y */
6 float x1;
7 float x2;
8 float x3;
9 float x4;
10 float u;
11 int pulsoy;
12} STRUCT_DATOS;
13
14typedef struct
15{
16 int comandox;
17 int comandoy;
18 int frec;
19 int modo;
20} STRUCT_COMANDO;

En el algoritmo ?? figuran los datos enviados al proceso, que están asignados en la estructura STRUCT_DATOS, donde se encuentran las variables de estado del sistema, las referencias a seguir, el número de iteraciones o pasos, la acción de control, los pulsos de los encoders y ángulos de oscilación de la carga transportada. En cambio, la estructura STRUCT_COMANDO, contiene la información asociada con los comandos enviados para el desplazamiento en el eje x e y, la frecuencia de operación del PWM de ambos motores y el modo de funcionamiento del despachador.

La relación, vista desde el proceso, se establece por medio de las funciones que se detallan en el
algoritmo ??. En éste caso, el FIFO se puede leer y escribir como si se tratase de un dispositivo de caracteres, de la misma manera que un archivo o un puerto de comunicaciones. Esto significa que no se necesita una API adicional, por lo que se gana en portabilidad de la aplicación.

La función de lectura en el proceso está a cargo de getKernelDatos(), que espera la llegada de los datos desde el módulo del núcleo. Cuando los datos están disponibles, se imprime en la pantalla las variables de interés. Los comandos se comunican al módulo mediante la función setKernelCmd(). De ésta manera, el servicio de comunicación FIFO establece un mecanismo eficiente para el envío y recepción de datos entre el proceso usuario y el módulo del núcleo. La figura 1.3 da un ejemplo del caso.


1static STRUCT_DATOS getKernelDatos(fd_set read_fd)
2{
3 ...
4 /* Espera que el FIFO esté disponible */
5 FD_ZERO(&read_fd);
6 FD_SET(fiford, &read_fd);
7 stat = select (FD_SETSIZE, &read_fd, (fd_set*)NULL, (fd_set*)NULL, &timeout);
8 stat = read(fiford, &kernel_datos, sizeof(kernel_datos));
9 if (stat == ERROR)
10 {
11 /* Imprime datos recibidos en pantalla */
12 }
13 return (kernel_datos);
14}
15
16int setKernelCmd(STRUCT_COMANDO opMR590)
17{
18 if (sizeof(opMR590) != write(fifowr, &opMR590, sizeof(opMR590)))
19 {
20 return ERROR;
21 }
22 return OK;
23}





Figura 1.3: Comunicación entre el proceso y del módulo del núcleo.



1.1.2.2. Tarea Despachador
El módulo BORA_rt_task.c, descripto en el algoritmo ??, consiste en dos tareas, donde Despachador() es la rutina principal, que centraliza la actividad y modo de operación del sistema y es, por lo tanto, la de mayor prioridad. Luego, la tarea suplementaria Encoder() limita su función a la lectura de las entradas discretas de la placa ADQ12-B, para la adquisición de las pulsos de los encoders correspondientes al puente y montacargas. Ambas tareas se ejecutan en forma periódica y se crean y lanzan desde init_module(). También, durante el inicio del módulo, se crean los relojes de tiempo real, los FIFOs, se inicializan los datos del controlador y se abre el puerto serie para la comunicación con el microcontrolador. Cuando el módulo es descargado, se liberan todos los recursos ocupados por las tareas, FIFOs y otras funciones adicionales, en el punto de salida cleanup_module().


1int init_module(void)
2{
3 ...
4 /* Configuración del reloj de tiempo real en modo periódico */
5 rt_set_periodic_mode();
6
7 /* Inicio de las tareas */
8 rt_task_init(&rt_despacha, (void*)Despachador, 1, STACK_SIZE, TASK_PRIORITY, 1, 0);
9 rt_task_init(&rt_encoder, (void*)encoder, 1, STACK_SIZE, TASK_PRIORITY+10, 0, 0);
10
11 /* Creación e inicio de FIFOs F_DATOS y F_COMANDO */
12
13 /* Inicia los estados del controlador. */
14 iniciaEstados();
15
16 /* Inicia timers y tareas con base de tiempo de 2 mseg */
17 tick_period = start_rt_timer(nano2count(MS02));
18 rt_task_make_periodic(&rt_encoder, rt_get_time() + tick_period, tick_period);
19 rt_task_make_periodic(&rt_despacha, rt_get_time() + 10*tick_period, 10*tick_period);
20
21 /* Apertura del COM1 */
22 res = rt_spopen(COM1, BAUD, NUMBITS, STOPBITS,...
23 RT_SP_PARITY_NONE, RT_SP_NO_HAND_SHAKE, RT_SP_FIFO_DISABLE);
24 switch(res)
25 {
26 /* Verifica errores en el COM1 */
27 }
28 /* Limpia buffers Tx y Rx del COM1 */
29
30 return OK;
31}
32
33void cleanup_module(void)
34{
35 stop_rt_timer();
36 rtf_destroy(F_DATOS);
37 rtf_destroy(F_COMANDO);
38 rt_task_delete(&rt_despacha);
39 rt_task_delete(&rt_encoder);
40 rt_spclose(COM1);
41 return;
42}

La tarea Despachador puede entenderse como una máquina de estados, que cambiará de estado o modo de operación según lo determine el usuario. Se puede ver en el diagrama de la figura 1.4 que inicialmente la tarea envía un caracter de inicio de comunicación con el uC y queda en espera de la respuesta. Este mecanismo es de utilidad, ya que es en el uC donde se accionan los dos motores del puente grúa, y tanto el sentido de giro como el ciclo de trabajo de cada PWM son enviados por el puerto serie.



Figura 1.4: Diagrama de flujo del Despachador.


Una vez establecida la comunicación entre el driver de los motores y la PC, el modo de operación

determinará el funcionamiento de la aplicación. El Despachador cuenta con cuatro modos posibles, Libre, Control, Espera y Salida:

Libre:
Es el modo de funcionamiento del sistema a lazo abierto. Esto significa que no se controlarán las variables del sistema como posición y oscilación de la carga.

Control:
Se diferencia del anterior en que la planta se encuentra a lazo cerrado, controlando la
posición y oscilación de la carga de acuerdo con el algoritmo de control hallado en XXXXXXX.

Espera:
Aguarda el cambio del modo de operación sin realizar actividades de comunicación y control.

Salida:
Es un modo adicional que permite cerrar la aplicación y descargar el módulo, sin perder
la actividad del uC. El objetivo de éste modo de operación es lograr que al reiniciar la
aplicación, el uC se encuentre activo y reanude su funcionamiento en lugar de aplicar un ciclo de apagado-encendido para volverlo activo.

1.1.2.3. Comunicación serie de tiempo real
La comunicación con el uC se establece mediante un conjunto limitado de funciones, contenidas en BORAcom.c , para la transmisión y recepción de datos. La función receivedata() se encarga de recibir la respuesta desde el uC cuando se inicia el diálogo con la PC. Una vez que la comunicación ha sido establecida entre ambos dispositivos, la función senddata() transmite un byte por única vez, con la frecuencia del PWM que utilizará el uC para manipular a los motores. La tabla 1.1 muestra las frecuencias de PWM disponibles, según el divisor de reloj seleccionado en el microcontrolador.


Selección

Frecuencia










PWM_DIV_1

31,250 kHz

PWM_DIV_8

3,906 kHz

PWM_DIV_64

488,280 Hz

PWM_DIV_256

122,070 Hz

PWM_DIV_1024

30,520 Hz


Cuadro 1.1: Frecuencias del PWM posibles.



Finalmente, la función setComData(), asociada a los modos Libre y Control del Despachador, transmite tres bytes periódicamente con la información del ciclo de trabajo y el sentido de giro de los dos motores, tal como se describe en la figura 1.5.





Figura 1.5: Paquete de actuación.


Los parámetros de configuración del puerto serie son: 38400 bps, 8 bits de datos, sin paridad y 1 bit de stop (38400,8,N,1). Los dos primeros bytes del paquete de actuación contienen el ciclo de trabajo de los PWM, cuyo rango de uso se encuentra entre 0 y 255, correspondiente a una escala del 0 - 100 %. En el byte 3 se enmascaran el modo de operación del driver de los motores y los sentidos de giro para los ejes X e Y.
1.1.2.4. Generador de referencias
El esquema de control propuesto en XXXX cuenta con un generador de referencias para facilitar la operación de los motores durante el arranque y la parada. Su propósito consiste en producir las curvas óptimas de posición que debe seguir el puente grúa, en función de una secuencia de aceleraciones, cuya duración e intensidad dependen de las características inherentes al sistema. Este método, desarrollado por [1], está basado en la estrategia de los operadores humanos, que permite conseguir la máxima velocidad de traslado, con mínima oscilación de la carga. La figura 1.6 describe la secuencia del movimiento durante el arranque y la parada.



Figura 1.6: Secuencia de movimiento con el generador de trayectorias.



Desde el punto de vista funcional, getMR590ref() se encarga de generar la secuencia de pulsos de
aceleración que resulta en la curva de referencia de posición de la figura 1.7 a). De acuerdo con el parámetro recibido por la función, el generador producirá pulsos positivos, negativos o nulos, para los movimientos de avance, retroceso y parada, respectivamente.



Figura 1.7: a) Referencia de posición. b) Referencia de velocidad. c) Referencia de aceleración.


La función getMR590dir() sirve de complemento al generador de referencias ya que, debido a las

características del driver del motor, no es suficiente contar con el parámetro de referencias, sino que debemos calcular el sentido de giro con que deben actuar los motores. El algoritmo ?? da un ejemplo de la aplicación, tal como se encuentra en el archivo MR590.c.


MR590.c


1unsigned char getMR590ref(unsigned char cmd)
2{
3 unsigned char t_on;
4 int signo = 1;
5
6 if ((cmd == AVANZAR) || (cmd == RETROCEDER))
7 {
8 signo = POSITIVO;
9 }
10 else if (cmd == DETENER)
11 {
12 signo = NEGATIVO;
13 }
14 else if((cmd == RESET) || (cmd == SALIR))
15 {
16 signo = NULO;
17 niter = NULO;
18 }
19
20 if ((Posicion > LIMITE_MIN) && (Posicion < LIMITE_MAX))
21 {
22 refx[0] += signo * PENDIENTE;
23 if (refx[0] > 1) refx[0] = T_ON_MAX;
24 if (refx[0] < 0) refx[0] = T_ON_MIN;
25 }
26
27 /* Escalar refx[0] para DC de la salida PWM del uC */
28 t_on = (unsigned char)(refx[0] * 255);
29
30 return (t_on);
31}
32
33unsigned char getMR590dir(unsigned char cmd)
34{
35 if(cmd == AVANZAR)
36 girox = SENTIDO_AHORARIO;
37 else if(cmd == RETROCEDER)
38 girox = SENTIDO_HORARIO;
39 return (girox);
40}

1.1.2.5. Controlador
El controlador es el corazón y causa que justifica la aplicación. Su objetivo consiste en lograr que el puente grúa y montacargas sean ubicados de acuerdo con las referencias de posición deseadas, con el menor error posible. Además, la planta controlada debe ser capaz de alcanzar su posición con un ángulo de oscilación de la carga despreciable, facilitando la manipulación de los objetos

transportados.

En el esquema implementado, figura 1.4, el controlador es uno de los modos de operación del módulo del nucleo, definido por la función despModoControl(). Esta rutina, ejecuta en forma secuencial las cinco fases que comprenden al algoritmo de control, provistas por el módulo kControl.c, como se puede ver en la figura 1.8.



Figura 1.8: Secuencia del algoritmo de control.


En primer lugar, despModoControl() recibe el comando de operación con el que generará la referencia de posición. Luego, toma los valores de cada estado medible (ej.: posición lineal del puente y posición angular de la carga) y, con dicha información, estima los estados no medibles (ej.: velocidad lineal y angular). Con los estados estimados y medidos, se calculan las actuaciones para los dos motores y se actualizan los estados del sistema. Se prepara la trama, definida en 1.1.2.3, con el paquete de actuación, y se transmiten los datos de ambos motores al driver. Finalmente, se muestra la información en pantalla. El diagrama de la figura 1.9 resume su funcionamiento.




Figura 1.9: Diagrama de flujo del modo Control.


Las funciones principales se detallan en los fragmentos de algoritmo ??, ?? y ??.


1static void calculaEstados(void)
2{
3 sysve.x1[0] = sysve.x1[1]*A11 + sysve.x2[1]*A12 + sysve.x3[1]*A13 + sysve.u[1]*B1;
4 sysve.x2[0] = sysve.x2[1]*A22 + sysve.x3[1]*A23 + sysve.x4[1]*A24 - sysve.u[1]*B2;
5 sysve.x3[0] = sysve.x2[1]*A32 + sysve.x3[1]*A33 + sysve.x4[1]*A34 + sysve.u[1]*B3;
6 sysve.x4[0] = sysve.x2[1]*A42 + sysve.x3[1]*A43 + sysve.x4[1]*A44 - sysve.u[1]*B4;
7}


1static void observaEstados(void)
2{
3 obsve.x1_est[0] = obsve.L0[0][0]*sysve.x1[1] + obsve.L0[0][1]*sysve.x2[1] + (obsve.ALCBK[0][0]*obsve.x1_est[1] + obsve.ALCBK[0][1]*obsve.x2_est[1] + obsve.ALCBK[0][2]*obsve.x3_est[1] + obsve.ALCBK[0][3]*obsve.x4_est[1] ) + sysve.Bd[0]*sysve.u[1];
4
5 obsve.x2_est[0] = obsve.L0[1][0]*sysve.x1[1] + obsve.L0[1][1]*sysve.x2[1] + (obsve.ALCBK[1][0]*obsve.x1_est[1] + obsve.ALCBK[1][1]*obsve.x2_est[1] + obsve.ALCBK[1][2]*obsve.x3_est[1] + obsve.ALCBK[1][3]*obsve.x4_est[1] ) + sysve.Bd[1]*sysve.u[1];
6
7 obsve.x3_est[0] = obsve.L0[2][0]*sysve.x1[1] + obsve.L0[2][1]*sysve.x2[1] + (obsve.ALCBK[2][0]*obsve.x1_est[1] + obsve.ALCBK[2][1]*obsve.x2_est[1] + obsve.ALCBK[2][2]*obsve.x3_est[1] + obsve.ALCBK[2][3]*obsve.x4_est[1] ) + sysve.Bd[2]*sysve.u[1];
8
9 obsve.x4_est[0] = obsve.L0[3][0]*sysve.x1[1] + obsve.L0[3][1]*sysve.x2[1] + (obsve.ALCBK[3][0]*obsve.x1_est[1] + obsve.ALCBK[3][1]*obsve.x2_est[1] + obsve.ALCBK[3][2]*obsve.x3_est[1] + obsve.ALCBK[3][3]*obsve.x4_est[1] ) + sysve.Bd[3]*sysve.u[1];
10}


1static unsigned char Control(void)
2{
3 /* Normaliza el valor absoluto de actuación en 8 bits sysve.u[0] */
4 unsigned char res;
5
6 /* Actuación */
7 sysve.u[0] = sysve.ref[0]*k1 - obsve.acK[0]*sysve.x1[1] - obsve.acK[1]*obsve.x2_est[1] - obsve.acK[2]*obsve.x3_est[1] - obsve.acK[3]*obsve.x4_est[1];
8
9 /* Saturador */
10 if (sysve.u[0] > 1) sysve.u[0] = 1;
11 else if (sysve.u[0] < -1) sysve.u[0] = -1;
12
13 if ( sysve.u[0] >= 0) res = (unsigned char)(sysve.u[0]*255);
14 /* Si la actuación es negativa, toma el valor absoluto */
15 if ( sysve.u[0] < 0) res = ~(char)(sysve.u[0]*255)+1;
16
17 return (res);
18}
1.1.2.6. Interfaz con el usuario
El archivo gui.c contiene las funciones que permiten al proceso usuario establecer la interacción entre el operador y el sistema. Para ello se empleó la librería de programación ncurses1 , para el desarrollo de interfaces de texto que se ejecutan bajo un emulador de terminal de video [4].

De ésta manera, la interfaz con el usuario presenta un sencillo menú de configuracion, comandos y visualizacion de datos (posicion, ángulo, velocidad, estado de la comunicación, etc). Inicialmente el usuario elige elmodo de trabajo deseado (Libre, Control, Calibración o Salida), luego la frecuencia de los PWM de una lista de opciones que dispone el uC y, finalmente, el menú de comandos. Por medio de éste último es posible desplazar el puente grúa y montacargas. La figura 1.10 muestra un esquema de los diferentes menúes disponibles para la operación de la planta.




Figura 1.10: Esquema de los distintos menúes disponibles en la interfaz con el usuario.



Una vez que se ha establecido el modo de operación y la frecuencia de trabajo de los PWM, el menú de comandos provee las opciones posibles para desplazar el puente hacia adelante y atrás, y al montacarga hacia un lateral u otro. En la figura 1.11 se representan las teclas disponibles para efectuar los movimientos necesarios de la planta.





Figura 1.11: Teclas de operación del sistema.



En cuanto al funcionamiento del proceso, la interfaz presenta una secuencia que consta de tres estados posibles:

RUN:
Es el estado por defecto del proceso, una vez que se haya activado y exista comunicación

con el microcontrolador.


ERROR:
En caso de detectar un error durante la ejecución de la rutina, el estado de error se

encargará de eliminar el proceso y liberar los recursos asociados.


STOP:
Al finalizar la operación de la planta, el estado stop permite salir de la aplicación sin

problemas, eliminando el proceso y liberando los recursos asociados.


Inicialmente, el proceso crea todos lo recursos necesarios para correr la aplicación, desde el archivo de registro de variables, hasta los FIFOs de comunicación con la tarea del módulo del núcleo. Establece la configuración de la pantalla, donde se asignan regiones para mostrar los datos del sistema, la introducción de comandos y visualización de la posición del puente y montacargas. Luego, el sistema comienza a ejecutarse normalmente en el estado RUN. Bajo éste modo de funcionamiento, el proceso ejecuta dos rutinas básicas: una para la lectura de los comandos ingresados por el operario y posterior envío a la tarea del núcleo, y la otra para recibir los datos de la planta que serán mostrados en pantalla al operador y almacenados en un archivo de
registro de datos. Finalmente, en alguno de los dos modos restantes, ERROR o STOP, se eliminan
los recursos asociados y el proceso termina. La figura 1.12 expone un diagrama de flujo de su
comportamiento.




Figura 1.12: Diagrama de flujo de la interfaz con usuario.



La forma de interacción entre el usuario y la planta se describe en la figura 1.13, donde se puede ver la secuencia de ejecución de comandos descripta anteriormente.




Figura 1.13: Secuencia de la interacción entre el usuario y el sistema.



Bibliografía

[1] Fernando di Sciascio, Control borroso de sistemas dinámicos, Universidad Nacional de San Juan, INAUT, 1994.

[2] GCC, http://gcc.gnu.org/.

[3]
Kate, http://www.kate-editor.org/.

[4]
Wikipedia, Ncurses, http://en.wikipedia.org/wiki/Ncurses, Agosto 2009.

No hay comentarios.: