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.
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.
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}
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.
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.
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.
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.
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.
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.
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.
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 ncurses , 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.
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.
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.
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.
Bibliografía
[1] Fernando di Sciascio,
Control borroso de sistemas dinámicos, Universidad Nacional de San Juan, INAUT, 1994.