Sistemas Informáticos de Tiempo Real
Módulo de simulación de un ascensor
Esta documentación describe el
funcionamiento
del módulo de software que simula un ascensor, las funciones
de programación disponibles para controlarlo, los argumentos
pasados
y las restricciones al comportamiento tras cada función.
El enunciado de la práctica de construcción de un sistema
de control para dicho ascensor se puede encontrar aquí.
Descarga del código
El codigo del simulador en versión texto
se encuentra en los ficheros
simusor.c
y simusor.h.
El código del simulador con interfaz
gráfico se encuentra en simusor.tgz.
Además, hay un esquema de un gestor de
eventos para hacer el simulador basado en procesamiento de eventos en gestor_eventos.c.
Indice
Forma de integrar el
código
del simulador de texto
El código del simulador consta de varios
threads
de simulación que corren en paralelo a nuestro programa y de
varias
funciones de programación que podremos invocar en nuestros
threads
para controlar la maquinaria del ascensor. La forma de integrar este
código
en nuestra aplicación consta de dos partes:
- Declarar en nuestro código las
funciones del
simulador añadiendo la línea de declaración:
- #include
"simusor.h"
- En nuestra función main hay que
poner en marcha
la simulación del ascensor invocando la función
main_ascensor():
- int
main()
- {
-
main_ascensor();
-
...
continua
nuestro programa...
- }
- Al compilador el programa hay que
añadir el
módulo simusor.c
y las librerías libm.a y libpthread.a.
La opción -Wall hace que el compilador sea más estricto a
la hora de verificar posibles errores en nuestro programa por lo que
puede
dar una lista más larga de warnings (avisos). Es conveniente
verificar
la razón por la que da cada aviso ya que aunque no sea un error
de compilación puede indicar que el código no se va a
comportar
como inicialmente pensábamos.
- $ cc
main.c simusor.c-lm
-lpthread -Wall
Una vez que se ponen en marcha los threads de
simulación
del ascensor, nos comunicamos con el sistema simulado mediantes el
conjunto
de funciones que se describe más adelante. Los threads de
simulación
son un programa sin fin que continua hasta que terminemos con el
proceso
que los ha creado (el programa que contiene la función main).
Forma de
integrar el
código del simulador gráfico
El código es totalmente compatible con un
programa que use el simulador de texto. De hecho, los ficheros
simusor.c
y simusor.h son los mismos, excepto por el símbolo GFX_ASCENSOR,
que está definido al comienzo de simusor.h. Para usarlo debemos
preparar el programa de control (main.c) siguiendo el esquema del
apartado
anterior
- Descomprimir el código de simulador
gráfico
ejecutando:
- $ tar
xvfz
simusor.tgz
- ascensor/
ascensor/ascensor.glade
ascensor/pixmaps/
ascensor/pixmaps/lock.xpm
... etc ...
- Cambiarnos al directorio ascensor
recien creado
y ejecutar el comando:
- $
./autogen.sh
- Si el comando termina con algún
mensaje de
error debemos revisar nuestra instalación del Linux con el
programa
"kpackage" desde los CD-ROMs del RedHat 7.2. Tenemos que tener
instalado
los paquetes gtk+-devel,
gnome-core-devel,
y gnome-libs-devel.
Lo más probable es que todos estos paquetes ya estén
instalados
automáticamente.
- Cambiarnos al directorio src
detro
del directorio ascensor y copiar aquí nuestro fichero
main.c
(sobreescribiendo
el que viene de ejemplo).
- Compilar con el comando make.
No es
posible
compilarlo directamente con el comando cc ya que incorpora numerosos
ficheros
y librerías.
- El ejecutable queda con el nombre ascensor.
Hay que ejecutarlo desde este mismo directorio para que encuentre los
iconos
del programa.
Una vez tengamos en marcha el simulador podemos
seguir
usando los comandos de texto en el terminal de texto donde vemos los
mensajes
o usar los botones correspondientes a estos mismos comandos en el
interfaz
gráfico.
Parámetros del sistema
El fichero simusor.h define una serie de constantes que especifican los
parámetros de funcionamiento y dimensiones del sistema. Al usar
estos parámetros es conveniente usar la constante dada en lugar
de poner su valor, de forma que podamos cambiar la simulación
con
solo editar los valores de este fichero. Los símbolos definidos
son los siguientes (su valor actual está entre
paréntesis):
VELOCIDAD: Velocidad máxima en m/s (1,0
m/s).
TIME_STEP: Paso de tiempo de la simulación
del ascensor
en
seg (0,100 s).
N_PISOS: Numero de plantas (10).
H_PISO: Altura de cada planta en metros (3,0
m).
H_INI: Altura de la planta baja en metros (0
m).
H_FIN: Altura de la ultima planta en metros
(H_INI+N_PISOS*H_PISO).
H_MIN: Limite inferior del recorrido del ascensor
en
metros
(H_INI-0,5 m).
H_MAX: Limite superior del recorrido del ascensor
en
metros
(H_FIN+0,5 m).
TOLERANCIA_SENSOR: Radio alrededor de la planta en
que el
sensor
de planta está activado (0,1 m).
RETRASO_PUERTA: Tiempo que tarda la puerta en
abrirse o en
cerrarse
completamente. (2 s).
H_FRENADA: Distancia a recorrer a velocidad lenta
tras dejar
una
planta y antes de llegar a la de destino en metros (0,3 m).
Comandos de las botoneras
El simulador está leyendo el teclado continuamente a la espera
de
que entremos comandos que equivalen a pulsar un botón de la
botonera.
Los comandos disponibles son:
- i<n> <ENTER>: Donde <n> es el
número de una planta.
Equivale a pulsar un boton del interior.
- e<n> <ENTER>: Donde <n> es el
número de una planta.
Equivale a pulsar un boton del exterior.
- o<ENTER>: Pone un obstáculo en medio de
la
puerta.
- no<ENTER>: Quita el obstáculo de la
puerta.
Funciones disponibles
Actuación sobre el sistema
- void
main_ascensor
();
Esta función inicializa las estructuras
de control del ascensor y lanza los threads que simulan las distintas
partes.
Es necesario llamarla antes de comenzar a interaccionar con el ascensor
mediante cualquier otra función.
- void
cmd_motor (int
dirección, int velocidad);
Esta función cambia el estado del motor
del ascensor. Los argumentos son la dirección y velocidad del
movimiento.
Ambos argumentos pueden especificarse usando los siguientes
símbolos
definidos en simusor.h:
Dirección:
CMDMOTOR_ARRIBA
CMDMOTOR_STOP
CMDMOTOR_ABAJO
Velocidad:
VMOTOR_STOP
VMOTOR_LENTO
VMOTOR_RAPIDO
El motor permanece parado tanto si se
especifica
CMDMOTOR_STOP como si se pone la velocidad a VMOTOR_STOP. La velocidad
rápida corresponde a la constante predefinida VELOCIDAD,
mientras
que la velocidad lenta es la mitad de dicha constante.
El tiempo de respuesta del sistema es
aparentemente
instantáneo, aunque los detalles de implementación
resultan
en retardos más o menos aleatorios del orden de
centésimas
de segundo que introducen una imprecisión que equivale a las
variaciones
en el tiempo de frenada del sistema debidas a las condiciones en cada
momento
(peso transportado, etc).
Si dejamos el ascensor en movimiento hasta que
llegue al final del recorrido en cualquiera de los dos sentidos, el
motor
hará una parada automática en el límite
especificado
activando el sensor de fin de carrera correspondiente. Estos sensores
no
mandan ninguna interrupción, por lo que se han de consultar
mediante
las funciones correspondientes.
El ascensor dispone de un codificador que
genera
un pulso por milímetro de recorrido. El contador de pulsos suma
los pulsos tanto en la subida como en la bajada acumulando hasta 3000
pulsos
en cada planta recorrida. El contador se resetea cada vez que pasa por
una planta. Dicho codificador está programado para enviar una
interrupción
a nuestro sistema cuando rebasa los 2700 pulsos.
En la simulación, la interrupción
la envía en forma de signal al thread que nosotros indiquemos
(ver
la función activa_encoder()
).
int
cmd_puerta (int
comando);
Esta función manipula la puerta del
ascensor.
Dicha puerta solo puede abrirse cuando el motor está parado y la
cabina detenida en una planta (se permite un error en la parada
correspondiente
a la tolerancia del sensor de planta). Cuando se alcanza la planta, el
mecanismo de la puerta queda desbloqueado y puede manipularse con esta
rutina.
Si se usa como argumento la constante
CMDPUERTA_ABRIR,
comienza la apertura de la puerta hasta que lo esté del todo.
El argumento CMDPUERTA_CERRAR comienza a cerrar
la puerta hasta que cierre del todo. Si encuentra un obstáculo,
el mecanismo intenta seguir con el cierre. El software de control es
responsable
de parar el proceso de cerrado examinando el sensor
fotoeléctrico
y dando de nuevo el comando de apertura.
El motor no acepta nuevos comandos hasta que
la puerta esté totalmente cerrada.
int
activa_encoder
(pthread_t *th, int signal);
Esta función programa el encoder para
que envíe el signal indicado al thread cuya estructura de
control
se referencia cada vez que se sobrepase el límite de 2700 pulsos
en el contador.
Esta función solo se ha de llamar una
vez después de la inicialización del simulador
(después
de llamar a main_ascensor) cuando ya
tengamos creado el thread
que
se va a encargar de procesar el signal correspondiente. int main()
{
pthread_t
th;
main_ascensor();
/*
Creamos
thread de control */
pthread_create(&th,
NULL, rutina_control, NULL);
/*
Indicamos
al encoder qué signal usar con nuestro thread */
activa_encoder(&th,
SIGUSR1);
...
continua
nuestro programa...
}
No hay ningún signal asignado por
defecto.
Se puede elegir libremente dentro de los signals que pueden ser usados
por los usuarios (ver sección de signals).
El thread que va a recibir el signal debe
programar
una rutina de atención al mismo usando la llamada signal()
en el código de la función del thread: signal(SIGUSR1,
rutina_de_atención);
int
activa_botonera
(pthread_t *th, int signal);
Es función es similar a activa_encoder.
Programa qué signal se tiene que enviar a qué thread
cuando
se pulse un nuevo botón en la botonera. Se usa un solo signal
para
avisar de cambios tanto en la botonera interna como en la externa. Solo
se avisa cuando se pulsa un botón por primera vez. No se avisa
ni
cuando se apaga el botón ni cuando se pulsa uno ya activado (ver
comportamiento de las botoneras interna y externa).
Al igual que antes, esta función solo
se ha de llamar una vez después de la inicialización del
simulador (después de llamar a main_ascensor) cuando ya
tengamos
creado el thread que se va a encargar de procesar el signal
correspondiente. int main()
{
pthread_t
th;
main_ascensor();
/*
Creamos
thread de control */
pthread_create(&th,
NULL, rutina_control, NULL);
/*
Indicamos
al encoder qué signal usar con nuestro thread */
activa_encoder(&th,
SIGUSR1);
...
continua
nuestro programa...
}
No hay ningún signal asignado por
defecto.
Se puede elegir libremente dentro de los signals que pueden ser usados
por los usuarios (ver sección de signals).
El thread que va a recibir el signal debe
programar
una rutina de atención al mismo usando la llamada signal()
en el código de la función del thread:
signal(SIGUSR1,
rutina_de_atención);
Consulta del estado del sistema
- double
lee_altura
();
Esta función retorna la altura en metros
en el instante en que se llama de la cabina del ascensor dentro del
hueco
como un número real. Esta
función
solo está disponible para poder verificar que el sistema de
control
funciona correctamente, comparando la altura que predice el control con
la altura real. No debe usarse para implementar la estrategia de
control
en si.
- int
lee_codificador
();
Esta función lee el valor actual del
contador
del codificador devolviendo el número de pulsos contado. Esta
función solo está disponible para poder verificar que el
sistema de control funciona correctamente si se quiere analizar el
valor
del encoder. No debe usarse para implementar la estrategia de control
en
si.
- int
lee_planta ();
Esta función retorna el número
del sensor de planta actualmente activado. Un sensor de planta solo se
activa si la cabina está situada a una altura dentro del
intervalo
[H-TOLERANCIA_SENSOR,H+TOLERANCIA_SENSOR], donde H es la altura de la
planta.
Esto quiere decir que solo se devuelve el número de un sensor si
el ascensor está parado o en marcha a la altura de una planta.
No existe ninguna interrupción
asociada que avise de que se haya activado nigún sensor.
En caso de no haber ningún sensor activo
en el momento de la llamada, la función devuelve -1.
- int
lee_sensor_bajo
();
Retorna el estado del sensor de fin de carrera
inferior. Este sensor se activa cuando la cabina alcanza el tope
inferior
del recorrido, tras lo cual el motor frena automáticamente. No
existe ninguna interrupción asociada que avise de que el
sensor
se ha activado.
Retorna 1 si el sensor está activado y
0 si no lo está.
- int
lee_sensor_alto
();
Retorna el estado del sensor de fin de carrera
superior. Este sensor se activa cuando la cabina alcanza el tope
superior
del recorrido, tras lo cual el motor frena automáticamente. No
existe ninguna interrupción asociada que avise de que el
sensor
se ha activado.
Retorna 1 si el sensor está activado y
0 si no lo está.
- int
lee_estado_puerta
();
Retorna el estado del mecanismo de la puerta.
El estado es:
- 0 si está cerrada completamente
- 1 si se está cerrando
- 2 si se está abriendo y
- 3 si está abierta del todo.
No hay interrupción asociada que indique
cambios
en el estado de la puerta.
- int
lee_celula ();
Retorna el estado del sensor
fotoeléctrico
de la puerta. El estado es 0 si no hay ningún obstáculo y
1 si lo hay. Este sensor no controla automáticamente el
motor
de la puerta.
- int
lee_botint ();
Retorna un número entero donde cada bit
corresponde al estado de un botón de la botonera interna de la
cabina.
El bit de menor peso representa el botón de la planta 0. La
botonera
puede tener un máximo de 32 botones dado el tamaño de los
enteros en C.
Cada bit toma el valor 1 si el botón
está
pulsado y 0 en caso contrario. Cuando un botón es pulsado, el
bit
correspondiente pasa a 1. Si se vuelve a pulsar antes de volver a cero
no cambia de estado.
Los botones se mantienen pulsados hasta que la
cabina se detiene en la planta correspondiente y se da la orden de
abrir
la puerta (tan solo basta comenzar la apertura, no es necesario que la
puerta llegue a abrir del todo). En este caso, el bit que representa al
botón de la planta actual se pone a cero automáticamente.
Cuando se pulsa un botón, se
envía
una interrupción al sistema indicando un cambio en el estado de
la botonera. Si se pulsa un botón ya activo no se envía
ninguna
interrupción.
En la simulación, la interrupción
la envía en forma de signal al thread que nosotros indiquemos
(ver
la función activa_botonera()
).
- int
lee_botext ();
Esta función trabaja de forma
idéntica
a la función lee_botint(), excepto en que se refiere a
los
botones situados en la puerta de cada planta (solo hay un botón
de llamada por planta). El comportamiento del registro de estado de
esta
botonera es idéntico al de la botonera interna. Los bits de
botón
de planta de esta botonera también se ponen a cero
automáticamente
cuando la cabina para y se abre la puerta en una planta.
Cuando se pulsa un botón, se
envía
una interrupción al sistema indicando un cambio en el estado de
la botonera. Si se pulsa un botón ya activo no se envía
ninguna
interrupción.
En la simulación, la interrupción
la envía en forma de signal al thread que nosotros indiquemos
(ver
la función activa_botonera()
).
Manipulación de timeval
Estas funciones permiten operar comodamente con
intervalos
de tiempo o referencias a instantes absolutos expresados en formato
timeval.
- void
suma_timeval(struct
timeval *inst, double dt);
Esta función suma la diferencia dt
expresada como un número de segundos con decimales al instante tv
expresado
en formato timeval. Al ser tv una variable pasada por
referencia,
el resultado es la modificación de la propia estructura tv.
- double
resta_timeval(struct
timeval *t1, struct timeval *t2);
Esta función calcula la diferencia t1-t2
devolviendo un número real de segundos con decimales.
- void
genera_timeval(struct
timeval *tv, double dt);
Esta función pasa el número de
segundos con decimales dt a formato timeval y lo devuelve en la
estructura pasada por referencia como primer argumento.
Signals
Aunque existen 32 signals distintos, no es
posible
usar todos ellos para nuestros propios fines de programación ya
que muchos de ellos están asociados con eventos del propio
sistema
operativo, de modo que podría ocurrir que se dispare uno de
estos
signals por un envento del sistema mientras estamos usándolo
como
mecanismo de comunicación. Por esta razón, hay que estar
seguro de qué signals podemos usar para nuestro propio programa.
Los signals sugeridos para usar en el programa son:
- SIGUSR1: Reservado para el usuario. Se
puede
usar
sin riesgo.
- SIGUSR2: Reservado para el usuario. Se
puede
usar
sin riesgo.
- SIGHUP: Sirve para que los procesos
terminen
cuando
se cierra la ventana o terminal en que se ejecutan. Se puede
reprogramar,
pero el programa no terminará automáticamente si se
cierra
la consola de texto sin matarlo antes con Control-C.
- SIGPIPE: Sirve para controlar la
escritura en
un
mecanismo de comunicación denominada PIPE. Si no se usan (poco
probable
en esta práctica) es posible usarlo sin problemas.
- SIGURG: Relacionado con las
comunicaciones
TCP/IP
y los sockets. Si no se usan comunicaciones de red se puede usar sin
problemas.
© 2003 Guillermo Pérez Trabado
Departamento de Arquitectura de Computadores
Universidad de Málaga