Ponencia Antonio Luque SISTEMA DE SIMULACIÓN PARA UN MICROCONTROLADOR ELECTRÓNICO BASADO EN LAS HERRAMIENTAS DE DESARROLLO GNU J. M. Benítez, A. Luque Departamento de Ingeniería Electrónica, Universidad de Sevilla, Spain aluque@gte.esi.us.es En este artículo se presenta un entorno modular de simulación de sistemas electrónicos construidos alrededor de un microcontrolador. A partir de unas herramientas que propociona el depurador GDB, se han desarrollado simuladores de periféricos y de dispositivos electrónicos simples que pueden conectarse a un microcontrolador. Se describe la arquitectura general del sistema, que consta de: simuladores de periféricos, simuladores de módulos externos, interfaz de usuario, y sistema de comunicaciones entre las partes. Palabras clave: microcontrolador, simulación, GDB, periférico. 1. Introducción Los microcontroladores son las piezas fundamentales de los sistemas empotrados, que en los últimos tiempos se han convertido en casi ubicuos. Un microcontrolador es un dispositivo electrónico parecido a un microprocesador, pero más completo que éste en muchos aspectos. Al igual que un microprocesador, un microcontrolador (uC) posee una unidad aritméticológica (ALU), y una serie de registros internos. Estos elementos forman la unidad central de proceso (CPU). Un microprocesador no contiene nada más (salvo quizá memoria de almacenamiento), pero un microcontrolador integra en el mismo circuito integrado una serie de periféricos, que lo dotan de funcionalidad completa. Estos periféricos son, por ejemplo, contadores, puertos serie, temporizadores, memorias, puertos de E/S, etc. Un microcontrolador no necesita de más componentes electrónicos para funcionar, mientras que un microprocesador sí. Es por esto, que los microcontroladores son el componente más utilizado para construir sistemas empotrados, en los que el tamaño y el coste son factores determinantes (VanSickle2001). Normalmente, estos sistemas empotrados carecen de los interfaces que presentan los ordenadores de propósito general, como teclado o monitor, por lo que la programación de los microcontroladores que los integran no puede hacerse directamente. Lo usual es escribir y compilar el programa en un ordenador personal y luego descargar una imagen binaria del mismo a la memoria interna del microcontrolador. La depuración de los programas es extremadamente difícil en un sistema empotrado, al no poseer éste los mecanismos de interfaz con el exterior antes descritos, siendo muy habitual el proceso de prueba y error para depurar un programa. Conscientes de este problema, muchos fabricantes de microcontroladores han desarrollado emuladores, dispositivos que, situados en el sistema electrónico en el mismo lugar en que estaría el microcontrolador, y conectados a su vez a un PC, se comportan desde el punto de vista eléctrico de la misma forma que lo haría el microcontrolador que se pretende emular. Estos emuladores suelen tener un precio prohibitivo, que los hace inviables para muchas aplicaciones. El proceso de depuración usando el hardware real es caro y tedioso. Se comprende la necesidad de contar con un simulador que se ejecute enteramente en un PC y que sea capaz de leer e interpretar un programa compilado para el microcontrolador, y proporcionar las mismas salidas que proporcionaría este. Usando simuladores, el ciclo de desarrollo se puede acortar sensiblemente y el coste fijo asociado al proyecto es bastante menor. En este artículo se describe un sistema de simulación desarrollado por los autores para los microcontroladores de la familia M.CORE de Motorola (MMC2000). El sistema está basado en las herramientas GNU, como GCC y GDB que han sido aportadas por el fabricante del microcontrolador, a las que los autores han añadido las rutinas suficientes para simular el microcontrolador completo. La familia M.CORE consta de varios microcontroladores diferentes construidos alrededor de una CPU RISC (Reduced Instruction Set Code). Su característica más destacada es el bajo consumo, lo que los hace ideales para aplicaciones móviles o que necesiten baterías. Cada miembro de la familia es ligeramente diferente en los periféricos que incorpora, pero en general se cuenta con puertos serie síncronos (SPI) y asíncronos (SCI), memoria Flash, memoria RAM estática (SRAM), temporizadores, convertidores analógico-digitales, y puertos digitales de entrada/salida, entre otros. 2. Diseño general del sistema La compañía Motorola, fabricante del microcontrolador, proporciona un simulador de la CPU del mismo para ser embebido en el depurador GDB. Por otra parte, algunos empleados de la misma han contribuido a la comunidad de software libre una versión del compilador GCC y del resto de herramientas de desarrollo GNU adaptadas para producir código M.CORE. El simulador aportado por Motorola se limita exclusivamente a la CPU del microcontrolador, por lo que es apropiado únicamente para calcular los tiempos de ejecución de un determinado programa. En este aspecto es muy preciso, como se puede comprobar en la Tabla 1, en la que se comparan los ciclos de reloj que toma la ejecución de varios programas en el simulador del que tratamos con los que toma según las pruebas internas de Motorola. La compañia afirma que la precisión del simulador para cualquier programa está en torno al 0.5%. ciclos reales ciclos sim Diferencia Programa 1 366101 366102 0.00% Programa 2 4461476 4462938 0.03% Programa 3 255551 255547 0.00% Tabla 1. Comparación de ciclos de reloj usados por varios programas, en la realidad y en el simulador de Motorola. Los programas son: algoritmos de ordenación, resolución de ecuaciones y compresión de datos. Fuente: Motorola, Inc. El concepto central del sistema descrito en este artículo es la construcción, en torno al simulador de Motorola, un sistema que sea capaz de simular los periféricos del microcontrolador, y al que se puedan conectar dispositivos externos, de la misma forma que se haría con un sistema electrónico real. El usuario del simulador es capaz de especificar qué dispositivos están conectados en qué pines del microcontrolador, y el simulador se encarga de que estos dispositivos reflejen el estado actual del sistema. En la sección 5 se incluye un ejemplo simple. Para lograr el objetivo descrito, son necesarias dos simulaciones adicionales a la de la CPU. Por una parte, hay que simular todos los periféricos internos al microcontrolador, como contadores, puertos, etc. Y por otra, hay que añadir un conjunto de módulos que se puedan conectar externamente al simulador del microcontrolador completo. Estos módulos pueden ser LEDs, displays de 7 segmentos, terminales serie, motores paso a paso, o cualquier cosa que se pueda inventar. Estas dos nuevas simulaciones se describirán con detalle en las secciones 3 y 4. Con el objeto de no limitar arbitrariamente el sistema, la prioridad en el diseño del mismo ha sido la modularidad. Los autores han desarrollado únicamente algunos módulos que se pueden conectar al microcontrolador, pero han dotado al sistema de la capacidad de poder ser extendido simplemente escribiendo el código de nuevos módulos. Para esto se ha diseñado una interfaz de comunicación entre el simulador y los módulos externos, de forma que sea muy fácil añadir nuevos módulos en cualquier momento. En la Fig. 1 se muestra la arquitectura general del sistema. Las partes sombreadas han sido desarrolladas por los autores dentro de este proyecto, mientras que las que tienen fondo blanco se encuentran disponibles bajo licencia libre. Figura 1. Estructura general del proyecto 3. Simulación de periféricos La principal característica de la simulación de los periféricos, al igual que la del resto del sistema, es su modularidad. No todos los microcontroladores de esta familia presentan los mismos dispositivos periféricos, de manera que el diseño de estos dispositivos sigue un patrón modular. Los elementos periféricos se representan como módulos independientes al microcontrolador, para conseguir así una estructura flexible que puede simular cualquier elemento microcontrolador de la familia. De esta forma, además, se facilita la incorporación de nuevos periféricos al simulador. Para que la flexibilidad no derive en problemas de incompatibilidad entre los distintos elementos que forman el simulador se ha definido una interfaz de acceso a los dispositivos periféricos. La estandarización del acceso a estos periféricos permite que la adición de un nuevo elemento que se ajuste a esta interfaz definida no implique cambios adicionales en el resto de la estructura del simulador. La combinación del diseño modular en la simulación de los periféricos junto con la definición de un acceso estándar a ellos consigue que el microprocesador vea a los periféricos como cajas negras, sin que tenga que conocer nada acerca de cuál es su estructura interna o cómo se ha implementado su funcionalidad. Lo único que conoce el microprocesador es la manera de acceder a esas cajas negras. La directriz que suele seguir Motorola en la arquitectura de sus microcontroladores es mapear todos los periféricos en memoria, de manera que no hay diferencia entre acceder a una posición de memoria y acceder a un dispositivo externo, del mismo modo que no hay diferencia entre acceder a un periférico y acceder a otro. Esta arquitectura viene a redundar en la idea de que el microprocesador considere a los periféricos como cajas negras que se distinguen entre sí tan sólo por la dirección mediante la cual son accedidas. Éste ha sido, pues, el diseño que se ha seguido en la definición de la interfaz de acceso a los periféricos, por ser la que habitualmente emplea Motorola, pero podría cambiarse fácilmente a otras arquitecturas usadas por otros fabricantes, como puede ser Intel, que diferencia entre accesos a memoria y accesos a periféricos. Cada uno de los periféricos desarrollados en el proyecto posee su propio código, que trata de imitar la lógica propia del componente hardware real. Así por ejemplo, un contador posee un código que decrementa continuamente a intervalos regulares un cierto registro y activa un bit en otro cuando la cuenta ha llegado a cero. El programa que se ejecuta en el simulador puede leer este bit para saber si la cuenta ha acabado, o acceder en cualquier momento de forma transparente al valor actual de la cuenta. 4. Interfaz de usuario El uso de interfaces de usuario se hace especialmente útil en el desarrollo de sistemas empotrados, pues éstos carecen de mecanismos de interfaz con el exterior como ocurre con los sistemas para computadores. De esta manera, el poder visualizar de algún modo cuáles son las salidas que proporcionan los programas que ejecuta el microcontrolador se convierte en una ayuda inestimable para el desarrollo de tales sistemas. La interfaz de usuario desarrollada refleja la doble vertiente de la naturaleza del desarrollo de sistemas empotrados. Por un lado, dispone de todos los elementos habituales de un sistema de depuración convencional para programas que se ejecuten en computadores, como puede ser la ejecución controlada o la posibilidad de comprobar el valor de las variables que conforman el programa. Por otro lado, la incorporación al simulador de los dispositivos periféricos que forman el microcontrolador hace necesario que la interfaz de usuario no sólo trabaje con el programa que ejecuta el microprocesador, sino que también muestre el estado de los distintos periféricos. De esta manera, podemos comprobar, por ejemplo, el estado del puerto serie o de los distintos registros de salida. Finalmente, y para conseguir que el simulador se ajuste lo más posible a la realidad, del mismo que podemos conectar físicamente distintos elementos a la salida del microcontrolador para comprobar su estado, la interfaz de usuario permite hacer lo mismo con el simulador, mediante una serie de módulos independientes que representan elementos externos al microcontrolador. Toda esta estructura se ha diseñado teniendo presente la necesidad de que el conjunto sea fácilmente extensible, de forma que se puedan incorporar nuevos módulos si el usuario de la interfaz lo cree necesario. La interfaz de usuario es la capa más externa en la estructura del sistema desarrollado por los autores. Se trata de una sencilla aplicación gráfica que pone a disposición del usuario toda la potencia y capacidad que ofrece la API de comunicación con GDB. Dicha aplicación está implementada con la completa librería de componentes Qt que ofrece Trolltech (Kalle2002). Se ha elegido esta librería por la enorme flexibilidad y funcionalidad que provee el mecanismo de signals y slots para la comunicación entre objetos. Hay que hacer notar que la aplicación gráfica está perfectamente separada de la API, por lo que no es necesario utilizar aquélla si queremos usar esta última. La API está presentada mediante una serie de llamadas que obtienen toda la funcionalidad que ofrece GDB, por lo que es posible desarrollar cualquier otro tipo de aplicación gráfica que haga uso de dicha interfaz de programación. La aplicación desarrollada por los autores, aun siendo una interfaz de usuario completa y totalmente funcional, puede servir de muestra a otras futuras aplicaciones y a lo que éstas pueden conseguir si se utiliza la API. La aplicación gráfica tiene un diseño convencional compuesto por un menú, una barra de herramientas y una serie de vistas y panales. En la vista principal se puede ver el código del programa que se quiere simular. Mediante el menú o directamente en la barra de herramientas, el usuario de la aplicación gráfica puede controlar cómo se ejecuta el programa, mediante la inserción y eliminación de puntos de ruptura y mediante los comandos de control de flujo de ejecución. Se dispone también de una serie de ventanas adicionales en las que se puede ir siguiendo la evolución de varios componentes del programa. Así, se dispone de vistas para obtener el valor de variables, de la pila de llamadas, de los registros internos del microprocesador y de cualquier dirección de memoria del microcontrolador. Es esta serie de vistas la que da sentido y utilidad al uso del simulador, puesto que se puede comprobar que todos estos parámetros toman los valores que quiere el programador a medida que ejecuta su programa. Por último y para hacer más útil el uso de esta herramienta, se ha provisto a la interfaz un modo gráfico en el que el usuario puede añadir a la salida del microcontrolador elementos externos como los que puede conectar en la realidad. De esta forma, se pretende conseguir que el simulador refleje lo más fielmente posible el comportamiento real del microcontrolador, comprobando el funcionamiento de elementos externos en lugar de una serie de valores numéricos. Toda esta funcionalidad se obtiene a través de la API de comunicación con GDB. Esta interfaz de programación ofrece toda una serie de llamadas que ponen a disposición de los programadores todo lo que GDB ofrece mediante su interfaz de comandos. En el sistema desarollado por los autores se ha puesto especial hincapié en los aspectos más relevantes del diseño de sistemas empotrados y que podemos apreciar en la interfaz de usuario, como puede ser la obtención del estado de los registros internos del microprocesador o la obtención del valor almacenado en una posición cualquiera de memoria. Esta interfaz de programación no está ligada al microcontrolador M.CORE, es decir, es independiente del microprocesador que se utilice, pudiéndose utilizar con cualquier otro simulador que provea GDB. El funcionamiento de la librería se basa en la creación de dos procesos que se comunican mediante tuberías (pipes). Uno de estos procesos, el hijo, ejecuta el depurador GDB, mientras que el otro, el proceso padre, se encarga de realizar las peticiones y presentarlas de manera adecuada al exterior. También se utiliza otro mecanismo de comunicación entre procesos, como son las señales, que se emplean para informar a la API de que han sucedido eventos significativos, como puede ser el cambio de estado de los periféricos. Para comunicarse con GDB se utiliza la interfaz GDB/MI, que es una interfaz orientada al uso de GDB como una parte de un conjunto mayor (Stallman1994). 5. Ejemplo de aplicación En esta sección se pretende ilustrar con un ejemplo las capacidades del sistema. Se va a conectar un módulo externo en algunos pines de salida del microcontrolador simulado y se va a proceder a simular un programa que acceda a dichas salidas. Uno de los módulos desarrollados en el proyecto es el conjunto de LEDs. Se trata de un número de diodos emisores de luz que se pueden conectar a cualquier pin de salida del microcontrolador. Cuando el programa que se está ejecutando establece un valor lógico alto en uno de esos pines, el LED correspondiente se ilumina en pantalla, y cuando el valor lógico es bajo, permanece apagado. En este ejemplo, el conjunto de LEDs se conecta al puerto A del microcontrolador (MMC2000). Una vez se ha hecho esto, se compila el siguiente programa, que hace parpadear uno de los LEDs del conjunto (se han omitido algunas inicializaciones de los registros del microcontrolador, que no aportan nada al ejemplo). int main(void) { set_led(OFF); for(i=0;i<=10;++i) { set_led(ON); delay1(); set_led(OFF); delay1(); } } En el programa anterior, la función set_led() únicamente pone un valor lógico alto o bajo en un registro interno del microcontrolador, en función de su único parámetro, como se muestra en el código siguiente. void set_led(unsigned char v) { if(v) reg_PORTA.reg|=0x01; else reg_PORTA.reg&=0xFE; } Este registro en un microcontrolador real se redirige inmediatamente a un conjunto de pines de salida (el puerto A). En este sistema, es el simulador del periférico correspondiente al puerto A el que informa al módulo de los LEDs de que el valor del registro ha cambiado. El módulo entonces actualiza la imagen en pantalla, dando al usuario la impresión de que un LED se enciende o apaga. Por supuesto, son posibles simulaciones más complejas. Por ejemplo, los autores han desarrollado también un módulo externo que simula un terminal serie, que se puede conectar al puerto serie asíncrono del microcontrolador simulado. De esta forma, es posible la visualización en pantalla de los caracteres transmitidos por el programa del microcontrolador. 6. Conclusiones Se ha presentado un sistema apropiado para la simulación de sistemas electrónicos sencillos basados en microcontroladores. El sistema descrito está orientado a la simulación de un microcontrolador concreto, pero es fácilmente portable a otros para los que existan simuladores de sus CPUs (GDB proporciona varios). La gran ventaja del sistema descrito es la modularidad, que permite escribir nuevos módulos que conectar al microcontrolador simulado con gran facilidad. Las líneas de desarrollo futuro del proyecto se centran en la creación de una mayor cantidad de módulos externos para ampliar el rango de sistemas electrónicos simulables. Otra característica que los autores están estudiando es la posibilidad de conectar dos o más microcontroladores simulados a través de sus puertos serie (síncronos o asíncronos) con el objeto de simular un sistema de comunicaciones completo. Referencias (Kalle2002) Matthias Kalle Dalheimer, Programming with Qt, 2nd ed., O'Reilly, 2002. (MMC2000) MMC2107 Technical Data, Motorola Inc., 2000. (Stallman1994) Richard M. Stallman and Roland H. Pesch, Debugging with GDB, Free Software Foundation, 1994. (VanSickle2001) Ted Van Sickle, Programming microcontrollers in C, 2nd ed., LLH Publishing, 2001.