martes, 9 de noviembre de 2010

Estructura general de un programa en C para sistemas embebidos

En un programa para computador común, éste puede iniciar, realizar su tarea en la función main() y cuando termina la tarea se sale de la función y retorna al sistema operativo. En los programas diseñados para microcontroladores generalmente no hay un sistema operativo al cual retornar cuando se ejecuta la función main(), y por tanto, el programa se debe diseñar para estar siempre dentro de esta función (exceptuando cuando se usa un sistema operativo en tiempo real, en donde cambia la filosofía de programación). Así, es posible el uso de ciclos que aseguren la ejecución continua del programa; interrupciones que manejen el flujo de acuerdo a la sincronización de timers o eventos externos; o sistemas más complejos que manejen sistemas operativos en tiempo real (RTOS).

Por otra parte, se debe tener en cuenta el tiempo de reacción del sistema, es decir, el tiempo que tarda en dar una salida correspondiente a una entrada determinada. Dependiendo de la aplicación, este tiempo puede ser crítico, sobre todo en sistemas que realizan varias tareas que tienen prioridades diferentes. De esta forma, de acuerdo a las necesidades de la aplicación, es posible implementar programas usando diferentes estructuras de ejecución.

Polling

Esta es la opción más simple, y se usa para implementar aplicaciones sencillas con recursos limitados de timers y puertos de interrupción. En esta aproximación, se implementa un ciclo en en cual se leen las entradas, se ejecutan los procesos correspondientes a las entradas, y finalmente se generan las salidas.

Programa en polling
La principal desventaja de esta estructura de ejecución radica en que no se tiene un control preciso de la sincronización de eventos, es decir, es difícil controlar el período en el cual se repite el ciclo principal. Para solucionar este problema, es posible insertar una instrucción de retraso que detiene la ejecución por un tiempo fijo, luego de este tiempo, procede a ejecutar el ciclo siguiente.

Otro inconveniente que se tiene al trabajar usando polling es que todos los procesos se ejecutan estrictamente de forma secuencial, y no es posible asignar prioridades. Esto es necesario si se tienen procesos que se deben ejecutar de forma urgente cada cierto tiempo o luego de una entrada determinada; y al mismo tiempo se tienen procesos que se ejecutan de una forma más lenta. Cuando se tiene una situación de este tipo en un programa estructurado para realizar polling, los procesos más lentos retrasarán la ejecución de los que se deben ejecutar de una forma más urgente, provocando un tiempo de reacción que puede ser inaceptable para la aplicación.

Interrupciones

Mediante el uso de interrupciones es posible asignar prioridades de ejecución a determinadas tareas, lo cual es una gran ventaja en el momento de implementar programas que tienen operaciones lentas y de baja prioridad y también operaciones que se deben ejecutar de forma prioritaria sobre las otras, independientemente si estas últimas ya terminaron o no.

Programa con estructura de interrupciones
Como se puede observar en la Figura anterior, se tiene un ciclo principal de fondo, el cual puede correr libremente sin realizar acciones, o puede realizar las acciones de baja prioridad en las que no se requiere un control estricto de ejecución. Las rutinas prioritarias, por su parte, se implementan en una o varias funciones de servicio de interrupción, que pueden ser llamadas por un evento externo, como el cambio de estado en un puerto; o por un evento interno, como el desbordamiento de un contador o timer.

El esquema basado en interrupciones es útil cuando se tienen sistemas de mediana complejidad. Es muy importante tener en cuenta que el intervalo en el cual se llaman las rutinas de interrupción debe ser mayor al tiempo que tardan en ejecutarse los procesos de dichas rutinas, para así asegurar que las funciones críticas se ejecuten correctamente a la frecuencia de deseada. De lo contrario, el sistema puede saltar de interrupción en interrupción evitando que se ejecuten las otras tareas.

RTOS - Real Time Operating System

En sistemas grandes y complejos en tiempo real, con muchas tareas que se deben ejecutar de forma concurrente, se hace necesario el uso de un sistema operativo en tiempo real (RTOS). Mediante el RTOS se implementa un planificador de tareas, basado en prioridad, para la ejecución de procesos múltiples.

El uso de RTOS hace que el diseño de software de microcontroladores para sistemas complejos sea una tarea mucho más sencilla. El modelo de RTOS se muestra en la Figura siguiente. Las conexiones entre las tareas representan señales de sincronización, intercambio de datos o notificaciones de agotamiento de tiempo de espera.

Programa con sistema operativo en tiempo real
Un RTOS es un programa de fondo que controla y organiza la ejecución y comunicación de tareas que se deben ejecutar de forma paralela, programa el uso compartido de recursos, y distribuye las prioridades de las tareas. Existe un gran numero de RTOS comerciales, y hoy en día su uso es extendido en aplicaciones embebidas de alta complejidad.

martes, 2 de noviembre de 2010

Desarrollo de firmware sobre lenguaje de alto nivel

Un microprocesador es un elemento que se encarga de interpretar determinadas instrucciones y de dar las órdenes para ejecutar dichas instrucciones en conjunto con otros elementos con los cuales debe funcionar, como memorias, buses, puertos I/O, etc. Por su parte, los microcontroladores son sistemas más complejos, los cuales contienen un microprocesador interno y todos los elementos necesarios para que pueda trabajar de forma independiente. Así, un microcontrolador contiene puertos I/O, un reloj o base de tiempo, memoria de datos, memoria del programa, una ALU (Unidad Lógica y Aritmética) que permite realizar procesos lógicos y aritméticos, entre otras características adicionales.

Los microprocesadores trabajan con un conjunto de instrucciones, las cuales consisten en códigos binarios que le dicen al microprocesador la función que debe realizar. Diferentes microprocesadores tienen diferentes conjuntos de instrucciones, por lo tanto, para trabajar desde el nivel de instrucciones, es necesario tener un profundo conocimiento de todo el conjunto de instrucciones del microprocesador, con el fin de diseñar programas que aprovechen todas las capacidades de éste.

El lenguaje ensamblador asigna nombres mnemónicos a los códigos de las instrucciones del microprocesador; así, para realizar una operación, no es necesario aprenderse el código correspondiente a dicha operación, sino que basta con usar el nombre del comando, el cual se asigna de tal forma que recuerde la operación que hace. Por ejemplo, la instrucción MOV le indica al microprocesador que debe mover ciertos datos de un lugar a otro, dependiendo de los parámetros. Cada microprocesador tiene un conjunto específico de instrucciones, las cuales le brinda el fabricante al desarrollador junto con toda la información del microprocesador que le pueda ser útil en el momento de desarrollar aplicaciones.

Realizando la transición de lenguaje de programación ensamblador a un lenguaje de mayor nivel, como lo es C, encontramos que C es un lenguaje multipropósito que puede ser implementado sobre cualquier hardware, a diferencia del lenguaje ensamblador, el cual varía dependiendo del microprocesador que se esté usando. Adicionalmente, el lenguaje C es más sencillo e intuitivo, y tiene herramientas que pueden simplificar notablemente la tarea del desarrollador, como la implementación sencilla de operaciones matemáticas, inclusión de librerías estándar que contienen las funciones más usadas, entre otras. El archivo en lenguaje C se compila usando un compilador específico para el procesador sobre el que se realizará la implementación. La salida del compilador es código ensamblador, el cual se ensambla, generando un archivo de código objeto que posteriormente se enlaza con librerías del compilador a través del linker. Luego del enlace, se obtiene finalmente código de máquina que puede ser cargado directamente a la memoria de programa.

En la siguiente figura se muestra el proceso de compilación de un programa en C:

Estructura de compilación de un programa de alto nivel

La principal ventaja de trabajar en lenguaje C con respecto a ensamblador es que requiere de un tiempo de desarrollo menor, y también un tamaño del programa menor (escrito por el usuario), pues el lenguaje C incluye operadores y funciones comúnmente utilizadas, para las cuales la implementación en lenguaje ensamblador requiere de mayor tiempo de desarrollo. Como ejemplo sencillo para demostrar esta tesis, tómese el manejo de variables de punto flotante. Mientras que en lenguaje C basta con declarar la variable como flotante y utilizar los operadores y funciones aritméticas (el compilador se encarga de llevar la variable flotante a enteros que puede manejar el hardware del microcontrolador); el lenguaje ensamblador no admite el manejo de este tipo de variables y es necesario realizar más procedimientos para el manejo y operación aritmética de las mismas.

A continuación se muestra una tabla con las principales ventajas y desventajas de trabajar en lenguaje de alto nivel para microcontroladores: