Diseño Software

La presente sección de este trabajo trata sobre el software desarrollado para el control del Display. Como se ha comentado al principio de este proyecto, el control se va a realizar cumpliendo unas determinadas restricciones temporales y por tanto diseñando un sistema de tiempo real. La complejidad del software no está en su tamaño, sino en la implementación de las restricciones temporales.

Introducción

El software que se va a desarrollar no pretende realizar un control completo de todas las posibilidades que se pueden realizar sobre el Display, tan sólo se realizará la denominada Práctica 0, que consiste en ser capaz de mostrar un mensaje estático en el Display generado por refresco. La frecuencia de refresco implica el cumplimiento de unas determinadas restricciones temporales.

Teniendo en cuenta la característica principal del hardware diseñado, en un instante los 7 leds como máximo de una columna pueden estar encendidos, las posibilidades parecen en principio escasas. Sin embargo podemos aprovechar el fenómeno de la persistencia de visión del ojo humano para incrementar las posibilidades de desarrollo.

Los frames son la imagen final que se quiere sea vista en el display por un observador. Para conseguir que el observador no perciba la discontinuidad de la imagen, los frames se hacen pasar rápidamente. La velocidad a la que se hacen pasar se le llama frecuencia de refresco. La frecuencia a la que el observador no nota ningún parpadeo de la imagen está entorno a 50-60Hz. Por tanto se tiene que la persistencia de la visión es un fenómenos visual humano que permite ver imágenes de vídeo sin parpadeo.

En este caso, se considera que un frame corresponde a una columna, y el conjunto de frames a las 40 columnas. Aquí, el hacer pasar los frames consiste en activar la correspondiente columna y filas consecutivamente de forma que se genere la imagen final.

Por tanto el software desarrollado consistirá en explotar este fenómeno, en este caso, únicamente se pide que sea capaz de mostrar un mensaje en el display y que este aparezca estático en él. El conseguir esto implica que se cumplan unos determinados requisitos temporales, y aquí es donde entra el diseño y programación de un sistema de tiempo real.

Todos los requisitos finales del software se pueden ver aquí, donde está la Especificación de Requisitos Software. El resto de características del sistema software se pueden observar en las siguientes secciones de este página.

Arriba

Diseño del sistema

Aunque estrictamente este hardware no requeriría un control en tiempo real, se va a diseñar de este modo, ya que como se comentó, este dispositivo servirá para la realización de prácticas de sistemas de tiempo real. También se puede tener en cuenta que este dispositivo pueda formar parte de un sistema mayor que sí requiera estrictamente de restricciones temporales.

Partiendo entonces, de que se va a diseñar un sistema de tiempo real, se debe encontrar una metodología de desarrollo que se adapte perfectamente a este tipo de sistemas. Una de estas metodologías es HRT-HOOD (Hard Real-Time Hierarchical Object-Oriented Design) y es la que se usará.

HRT-HOOD

HRT-HOOD es un método de diseño estructurado, basado en objetos, para sistemas de tiempo real en general y estricto en particular. Este método va dirigido u orientado hacia sistema de tiempo real complejos.

Con esta metodología el sistema se diseña como una jerarquía de objetos abstractos, donde, un objeto se caracteriza por sus operaciones y su comportamiento, y cada objeto se puede descomponer en otros de más bajo nivel.

Esta metodología se basa en un proceso de desarrollo iterativo centrado en la etapa de diseño. Consiste en validar todos los aspectos que se pueda en la etapa de diseño, prestando especial atención a la validación del comportamiento temporal.

En este fichero se encuentra una descripción completa de la metodología y en éste una introducción a la misma.

Diseño del sistema con la metodología HRT-HOOD

Esta metodología se centra principalmente en las restricciones temporales, pero el sistema incluye otras características. Estas características o propiedades aparecen por ejemplo en la especificación de requisitos, pero no aparecerán usando esta metodología, de forma que se comentarán insertando anotaciones cuando sea preciso. No se usará otra metodología para describirlas dado su sencillez y su escasa influencia en la parte principal del sistema.

A continuación se describe el sistema fase a fase, siguiendo el ciclo de vida de HRT-HOOD.

Definición de Requisitos

Una descripción de lo que debe hacer el sistema se encuentra en la Especificación de Requisitos Software. Como complemento se incluyen aquí los Casos de Uso para aclarar todas las funcionalidades del sistema, incluidas las que no son de tiempo real.

Diseño de la arquitectura lógica

Una vez se conocen los requisitos funcionales y temporales del sistema se procede a la identificación de objetos o clases.

En un primer nivel de descomposición, se trata de encontrar las clases de objetos apropiadas en las cuales el sistema se puede construir. Los requisitos sugieren dividir en dos subsistemas como se puede ver en la siguiente figura.

Diagrama de Objetos de Nivel 1

El objeto Consola de operaciones es un objeto activo que no tiene ninguna operación que requiera tiempo real, y por tanto no lo llama ningún otro objeto salvo el sistema, que hace uso de él para capturar los mandatos del usuario. No se comentará más de este objeto pues su comportamiento no se puede analizar al ser activo.

El objeto Mostrar mensaje estático es un objeto esporádico con dos operaciones, la típica y normal, COMENZAR, que provoca su ejecución y otra, PARAR, que provoca que termine de ejecutarse. Ambas operaciones son del tipo ASER (Asynchronous Execution Request), petición de ejecución asíncrona. Ambas operaciones están provocadas por el Usuario cuando éste pulsa el teclado.

Este objeto se descompone en otros dos objetos, un objeto que se encarga de pintar el mensaje en el display y otro objeto que controla la ejecución del anterior como puede verse en la siguiente figura . El objeto Pintar es cíclico, la única operación que tiene es PINTAR del tipo ASATC (Asynchornous Transfer of Control Request), transferencia asíncrona de petición de control. Ésto significa que el control pasa al objeto pintar cuando se llama a su función. Este objeto se sincroniza con su padre por medio del objeto Control de Parada. El objeto Control de Parada es protegido y permite la sincronización entre los objetos Pintar y Mostrar mensaje estático. Tiene dos operaciones ESCRIBIR y LEER. Ambas son del tipo PSER (Protected Synchronous Execution Request), petición protegida de ejecución síncrona. Son así porque bloquean al llamador hasta que no devuelven el resultado.

Diagrama de Objetos a Nivel 2

Comprobando la descomposición realizada con las reglas de uso y descomposición se ve que será posible realizar el análisis del comportamiento temporal.

Diseño de la arquitectura física y análisis temporal

Una vez se ha realizado la arquitectura lógica, el primer paso consiste en asociar atributos temporales a los objetos teniendo en cuenta los tiempos de ejecución de las operaciones y de las tareas.

Se establecen las prioridades suponiendo un sólo procesador, planificación con prioridades fijas y acceso a los objetos protegidos mediante el protocolo del techo inmediato de prioridad.

En este caso se estudian todos los objetos menos el activo, quedando los objetos con los siguientes atributos según se puede ver en el siguiente cuadro.

Objeto Tipo T D P
Mostrar mensaje estático S A A 9
Pintar C Tsc Tsc 8
Control de Parada Pr 10

El valor de Tsc viene definido en la Especificación de Requisitos Software y en este caso tiene una valor de 400000ns, mientras el valor de A que representa el periodo mínimo de dos activaciones sucesivas no viene especificado. Se toma la decisión de establecer un valor de 10ms.

El valor para las prioridades se ha establecido así porque el objeto Mostrar mensaje estático es el encargado de activar y desactivar al objeto Pintar por lo que tiene mayor prioridad que éste. Ambos se sincronizan con el objeto protegido Control de parada por lo que éste tiene la mayor prioridad.

Diseño detallado

En esta fase se define la estructura modular del sistema. En esta parte se incluye también los módulos auxiliares del sistema que no requieren de restricciones temporales. En la siguiente figura puede verse el diagrama modular de nivel 1.

Diagrama modular del sistema RT_DISPLAY

En este primer diagrama se ve puede ver el módulo parserxml que es el encargado de leer la información de un fichero XML sobre los símbolos representables en el Display. Este módulo hace uso de la librería libxml para la lectura de ficheros XML y se ha usado este tutorial para conocer su funcionamiento. El módulo lxrt_efects, implementa la funcionalidad de mostrar un mensaje estático en el Display como una tarea de tiempo real a nivel de usuario, mientras el módulo rt_efects, implementa la misma funcionalidad pero a nivel de kernel. Éstos dos últimos módulos hacen uso de los servicios de RTAI para implementar las restricciones temporales y de COMEDI para escribir sobre el Display por medio de la tarjeta PCI-9111HR. En esta página se hace una descripción de RTAI y de LXRT. El módulo rt_display o sistema se encarga del resto de funcionalidades que se deben cumplir.

En la siguiente figura se puede ver la descomposición del módulo rt_efects en dos, por un lado en lo que sería el objeto cíclico Pintar en el display y en el objeto protegido de sincronización Control de parada. Esta misma división se hace para el módulo lxrt_efects.

Descomposición del módulo rt_efects

Codificación y cálculo de tiempos de ejecución

A la hora de realizar la implementación se tenía la restricción de hacer uso del sistema operativo de tiempo real RTAI, el cual proporciona interfaz para los lenguajes C y C++. En este caso se decidió hacer uso del lenguaje C para la codificación del diseño.

En esta sección no se va a incluir código fuente, tan sólo se comentarán detalles de la codificación.

Los detalles más importante del sistema son relativos a la parte de control en tiempo real, y en este caso tanto a nivel de usuario como a nivel de kernel. La diferencia está en como se declara la tarea que contiene el algoritmo de control, ya que éste es muy simple y se reproduce a continuación.

while(!stop)
{
for(j=0;j menor que 5;j++)
{
selectOutputDecoder(device,subdevice,MAIN,j);
for(k=0;k menor que 8;k++)
{
selectOutputDecoder(device,subdevice,SECONDARY,k);
writeColumn(device,subdevice,msg,column);

column++;
if (column == 40)
column = 0;

rt_task_wait_period();
}
}
}

La funcionalidad de este algoritmo es ir activando sucesivamente cada columna y en cada activación de una columna encender los leds adecuados según el mensaje que se tenga que mostrar. Es en este algoritmo donde se deben cumplir las restricciones temporales. En primer lugar se observa la función rt_task_wait_period(), esta función lo que hace es dormir la tarea hasta que se produzca el siguiente periodo. El valor de este periodo es el correspondiente al objeto Pintar, Tsc. En cada periodo se activa una columna. La otra restricción temporal que venía marcada en la Especificación de Requisitos Software era el tiempo que permanecía encendido un led durante la activación de su columna. En este caso está implementado dentro de la función writeColumn(..) y se hace mediante la función rt_sleep(Ton) siendo Ton el tiempo que debe permanecer encendido. Se ha realizado así porque RTAI no permite la espera para varios periodos.

En primer lugar se comenta el código de control a nivel de usuario. Para poder implementar las restricciones temporales en modo usuario se hace uso de la funcionalidad LXRT de RTAI

Básicamente lo que se hace es crear un función para que sea ejecutada por un thread. En este caso a la función/thread se la da un nombre (STATIC), se define su periodo y se fuerza su planificación en el planificador de GNU/Linux con política FIFO. Para poder planificar el thread se hace uso de la función rt_allow_non_root() y para que el código de control se realice en tiempo real estricto desde el nivel de usuario, antes del algoritmo anterior se llama a la función rt_make_hard_real_time() y cuando termina, a la función rt_make_soft_real_time(). De esta forma se consigue que el thread se convierta en tarea de tiempo real y se planifique en el planificador de RTAI/LXRT durante la ejecución del algoritmo, en vez de en el de GNU/Linux como inicialmente estaba.

En cambio cuando se trabaja a nivel de kernel, se define una tarea de tiempo real directamente, con su periodo y con el mismo código de control. La diferencia con el anterior es que esta tarea se planifica directamente en el planificador de RTAI, pero en este caso hace falta asignarla una prioridad.

Otra de las diferencias con respecto al nivel de usuario es que la comunicación entre la aplicación de usuario que recoge el mensaje a mostrar en el Display y la tarea de nivel de kernel debe realizarse por medio del mecanismo de comunicación FIFO. En este caso se ha optado por usar dos FIFOs, uno para enviar la información del mensaje e iniciar la muestra en el Display y otro para parar. Ambos son de escritura desde la aplicación de usuario y de lectura desde el módulo del kernel. En la siguiente figura se puede apreciar la arquitectura completa.

Arquitectura del sistema RT_DISPLAY

Pruebas y medidas de tiempos

Para la realización de pruebas sobre este dispositivo de acuerdo a las funcionalidades que se debían implementar se debe tener en cuenta que son de tipo visual principalmente, si bien para comprobar si se cumplen estrictamente las restricciones temporales es necesario realizar la medida de tiempos, en este caso realizadas vía software también.

En primer lugar se realizaron las pruebas de control individual sobre todos los canales y que no requerían cumplir restricciones temporales. Para realizarlas se tuvo en cuenta el interfaz hardware y con ello se pudo realizar el control individual de cada led de forma correcta.

A continuación se realizó la prueba sobre la funcionalidad de presentar los simbolos disponibles. En este caso se muestra un listado con los símbolos disponibles junto con el tamaño que ocupa en número de columnas. Esta prueba fue correcta y la siguiente que se realizó tenía que ver con ésta y era comprobar que leds de cada columna se enciendían con cada símbolo. Se hizo vía software escribiendo por pantalla un 0 o un 1 si el led debía estar apagado o encendido respectivamente.

Por último, las siguientes pruebas son relativas a la funcionalidad principal del sistema, mostrar un mensaje estático. En este caso la prueba consistía en visualizar el mensaje sin apreciar ningún tipo de discontinuidad en la imagen. Tras la primera prueba se observó que el brillo de los leds era escaso, lo que provocó que se cambiara el diseño eléctrico del Display como se comentó en la sección de diseño hardware.

Tras las siguientes pruebas se pudo comprobar que el mensaje mostrado en el Display, usando la funcionalidad de tiempo real desde el nivel de usuario, mostraba algunas irregularidades en la continuidad de la imagen, como que algun led brillará más que otros. Este efecto se producía cuando el sistema estaba con alta carga o cuando se movía rápidamente el ratón. Por contra, cuando la prueba se hacía a nivel de kernel no se observaba ninguna irregularidad.

Para la medida de tiempos se he hecho uso de la función de RTAI rt_get_time_ns() que proporciona el tiempo en nanosegundos. Los tiempos que se han medido tanto a nivel de usuario como a nivel de kernel son el tiempo que se dormía una tarea para que los leds permanecieran encendidos, así como el periodo de cada tarea. Para esta ultima medición se ha hecho uso del interfaz que proporciona RTAI en el sistema de ficheros /proc.

En el caso de la funcionalidad a nivel de usuario se ha recopilado la siguiente información. Ejecutando el comando cat /proc/rtai/scheduler se obtiene:

RTAI LXRT Real Time Task Scheduler.
Calibrated CPU Frequency: 450993000 Hz
Calibrated 8254 interrupt to scheduler latency: 2689 ns
Calibrated one shot setup time: 2008 ns
Number of RT CPUs in system: 1
Priority Period(ns) FPU Sig State CPU Task HD/SF PID RT_TASK * TIME
-------------------------------------------------------------------
1000000001 400000 No No 0x0 0:1 1 0 6145 c7581040 0
TIMED
READY

Ésto indica, aparte de información del sistema, que existe una tarea en el planificador de RTAI/LXRT de periodo 400000ns. Ejecutando ahora el comando cat /proc/rtai/RTAI\names se sabe que esta tarea se llama STATIC (como se llamó en la implementación).

RTAI LXRT Information.
MAX_SLOTS = 100
Slot Name ID Type RT_Handle Pointer Tsk_PID MEM_Sz USG Cnt
-------------------------------------------------------------------
1 STATIC 0xa0225276 TASK 0xc7581040 0x00000000 0 6145 1

Haciendo ahora uso de la función rt_get_time_ns() se obtiene la siguiente traza durante una de las pruebas:

[LXRT_EFECTS] --> Turned on time (After - Before) = 335065
[LXRT_EFECTS] --> Turned on time (After - Before) = 330741
[LXRT_EFECTS] --> Turned on time (After - Before) = 331624
[LXRT_EFECTS] --> Turned on time (After - Before) = 328854
[LXRT_EFECTS] --> Turned on time (After - Before) = 329768
[LXRT_EFECTS] --> Turned on time (After - Before) = 327251
[LXRT_EFECTS] --> Turned on time (After - Before) = 331172
[LXRT_EFECTS] --> Turned on time (After - Before) = 330662
[LXRT_EFECTS] --> Turned on time (After - Before) = 327877
[LXRT_EFECTS] --> Turned on time (After - Before) = 327704
[LXRT_EFECTS] --> Turned on time (After - Before) = 326720
[LXRT_EFECTS] --> Turned on time (After - Before) = 351238
[LXRT_EFECTS] --> Turned on time (After - Before) = 326222
[LXRT_EFECTS] --> Turned on time (After - Before) = 326124
[LXRT_EFECTS] --> Turned on time (After - Before) = 326165
[LXRT_EFECTS] --> Turned on time (After - Before) = 327183
[LXRT_EFECTS] --> Turned on time (After - Before) = 324688
[LXRT_EFECTS] --> Turned on time (After - Before) = 326932
[LXRT_EFECTS] --> Turned on time (After - Before) = 326533
[LXRT_EFECTS] --> Turned on time (After - Before) = 326644
[LXRT_EFECTS] --> Turned on time (After - Before) = 327414
[LXRT_EFECTS] --> Turned on time (After - Before) = 323939
[LXRT_EFECTS] --> Turned on time (After - Before) = 345313
[LXRT_EFECTS] --> Turned on time (After - Before) = 327360
[LXRT_EFECTS] --> Turned on time (After - Before) = 326695
[LXRT_EFECTS] --> Turned on time (After - Before) = 326105
[LXRT_EFECTS] --> Turned on time (After - Before) = 329074
[LXRT_EFECTS] --> Turned on time (After - Before) = 324121
[LXRT_EFECTS] --> Turned on time (After - Before) = 326078
[LXRT_EFECTS] --> Turned on time (After - Before) = 326344

El valor estricto que debería obtenerse tendría que ser de 300000ns, sin embargo aquí el valor oscila entre los 320000ns y los 352000ns.

Para el caso de la misma funcionalidad implementada como módulo de kernel se obtiene para el comando cat /proc/rtai/scheduler:

RTAI Uniprocessor Real Time Task Scheduler.
Calibrated CPU Frequency: 450993000 Hz
Calibrated timer interrupt to scheduler latency: 2689 ns
Calibrated one shot setup time: 2008 ns
Number of RT CPUs in system: 1
Priority Period(ns) FPU Sig State Task RT_TASK * TIME
-----------------------------------------------------
1 400000 Yes No 0x5 1 cd0d4a80 0
TIMED
> cd0d4a80
READY

En este caso se muestra información del planificador de RTAI, como es que hay una tarea con prioridad 1 y de periodo 400000ns. Es la que se ha implementado dentro del módulo. Gracias también a este sistema de ficheros se puede obtener más información, por ejemplo de los FIFOs que se estan usando. Ejecutando cat /proc/rtai/fifos se obtiene:

RTAI Real Time fifos status.
Maximum number of FIFOS 64.
fifo No Open Cnt Buff Size handler malloc type Name
---------------------------------------------------------
0 1 4096 cd08e040 kmalloc
1 1 4096 cd0d3160 kmalloc

Con esta información se puede comprobar que el manejador para el FIFO número 0 es la tarea de tiempo real que está definida en el módulo (tiene el mismo puntero), sin embargo para el FIFO número 1 no es la tarea de tiempo real como se comentó en el punto anterior. Ésto es así porque para este FIFO se declaró un manejador para saber cuando se escribía en el por parte de la aplicación de usuario.

Por otra parte y al igual que antes, en el módulo también se hizo uso de la función rt_get_time_ns() y los resultados fueron:

[RT_EFECTS] --> Turned on time (After - Before) = 307956
[RT_EFECTS] --> Turned on time (After - Before) = 307557
[RT_EFECTS] --> Turned on time (After - Before) = 307899
[RT_EFECTS] --> Turned on time (After - Before) = 307251
[RT_EFECTS] --> Turned on time (After - Before) = 308031
[RT_EFECTS] --> Turned on time (After - Before) = 308279
[RT_EFECTS] --> Turned on time (After - Before) = 308375
[RT_EFECTS] --> Turned on time (After - Before) = 308632
[RT_EFECTS] --> Turned on time (After - Before) = 308300
[RT_EFECTS] --> Turned on time (After - Before) = 308071
[RT_EFECTS] --> Turned on time (After - Before) = 307981
[RT_EFECTS] --> Turned on time (After - Before) = 309268
[RT_EFECTS] --> Turned on time (After - Before) = 307610
[RT_EFECTS] --> Turned on time (After - Before) = 307315
[RT_EFECTS] --> Turned on time (After - Before) = 307628
[RT_EFECTS] --> Turned on time (After - Before) = 308259
[RT_EFECTS] --> Turned on time (After - Before) = 307646
[RT_EFECTS] --> Turned on time (After - Before) = 309324

Como se puede ver, comparados con los anteriores aqui el resultado es mucho más proximo al esperado, lo que demuestra que los cambios de contexto a nivel de usuario producen más retardos que a nivel de kernel debido al uso de la función rt_sleep(Ton).

La conclusión que se puede obtener de las pruebas visuales y medida de tiempos es que la implementación más correcta para sistemas de tiempo real estricto mediante RTAI debe hacerse como módulo de kernel y no con la opción de LXRT.

Arriba