Informe - Escuela de Ingeniería Eléctrica

Anuncio
Universidad de Costa Rica
Facultad de Ingeniería
Escuela de Ingeniería Eléctrica
IE – 0502 Proyecto Eléctrico
Construcción de una plataforma móvil para procesamiento de datos, medición y actuación basada en sistemas de computación embebida y tecnología celular
Por:
Juan Diego Acuña Castillo
Ciudad Universitaria Rodrigo Facio
Marzo del 2010
Construcción de una plataforma móvil para procesamiento de datos, medición y actuación basada en sistemas de computación embebida y tecnología celular
Por:
Juan Diego Acuña Castillo
Sometido a la Escuela de Ingeniería Eléctrica
de la Facultad de Ingeniería
de la Universidad de Costa Rica
como requisito parcial para optar por el grado de:
BACHILLER EN INGENIERÍA ELÉCTRICA
Aprobado por el Tribunal:
_________________________________
Ing. Rodrigo García
Profesor Guía
_________________________________
Ing. Lochi Yu Profesor lector
_________________________________
Ing. Andrés Díaz
Profesor lector
ii
DEDICATORIA
A mis padres, por todo su apoyo, paciencia y amor.
iii
ÍNDICE GENERAL
ÍNDICE DE FIGURAS..................................................................................................vii
ÍNDICE DE TABLAS.....................................................................................................ix
NOMENCLATURA.........................................................................................................x
RESUMEN......................................................................................................................xii
CAPÍTULO 1: Introducción...........................................................................................1
1.1 Objetivos....................................................................................................................................1
1.1.1 Objetivo general....................................................................................................................1
1.1.2 Objetivos específicos.............................................................................................................1
1.2 Justificación...............................................................................................................................2
1.3 Presentación del problema.........................................................................................................3
1.4 Metodología...............................................................................................................................5
CAPÍTULO 2: Marco Teórico........................................................................................9
2.1 Los sistemas embebidos, el GadgetPC y el AT90USB162........................................................9
2.1.1 Los sistemas embebidos.......................................................................................................9
2.1.2 La GadgetPC y el AT90USB162 desde la perspectiva de la computación embebida........11
AT90USB162............................................................................................................................12
La GadgetPC.............................................................................................................................13
2.2 El desarrollo de aplicaciones para sistemas embebidos..........................................................15
2.2.1 Desarrollo para la familia AVR..........................................................................................15
2.2.2 Desarrollo para la GadgetPC.............................................................................................20
2.3 Fundamentos de la tecnología USB........................................................................................25
2.3.1 Estándares USB.................................................................................................................26
2.3.2 Componentes y topología USB.........................................................................................27
El host USB...............................................................................................................................27
Dispositivos USB (“devices”)...................................................................................................27
2.3.3 La comunicación entre un host y un dispositivo................................................................29
Endpoints (Extremos o terminaciones).....................................................................................30
“Pipes” (tuberías)......................................................................................................................31
Configuraciones e Interfaces....................................................................................................32
2.4 “Datacards” y su uso en Linux................................................................................................34
CAPÍTULO 3: Comunicación sobre USB con el AT90USB162, LUFA y Linux......37
iv
3.1 LUFA.......................................................................................................................................37
3.1.1 Funciones relevantes del manejador de LUFA para dispositivo CDC...............................42
CAPÍTULO 4: Implementación del hardware y firmware del sistema.....................44
4.1 Aplicaciones en los microcontroladores: est_at90.c y est_atmega.c.......................................45
4.2 El manejador del hardware de la estación: est_ctrl.c...............................................................47
4.2.1 Implementación de est_ctrl................................................................................................48
4.2.2 El nombre del dispositivo serial virtual.................................................................................49
CAPÍTULO 5: Implementación de la Interfaz con el usuario: estacion.php...........51
5.1 Aplicaciones basadas en tecnología Web y PHP.....................................................................51
5.2 Estructura e implementación de la aplicación estacion.php.....................................................52
5.2.1 Descripción general............................................................................................................52
5.2.1.1 estacion.php..................................................................................................................52
5.2.1.2 estacionMenu.php.......................................................................................................52
5.2.1.3 estacionHead.html y estacion.css.................................................................................53
5.2.1.4 lecturaPuertos.php y escrituraPuertos.php ..................................................................54
5.2.1.5 ESdigital.php (Entrada/Salida digital)..........................................................................54
5.2.1.6 pwm.php.......................................................................................................................55
5.2.1.7 cad.php..........................................................................................................................55
5.2.2 Detalles de implementación...............................................................................................58
5.2.2.1 Arquitectura de las aplicaciones....................................................................................58
5.2.2.2 Operación interna de las aplicaciones...........................................................................59
CAPÍTULO 6: Conectividad y acceso a Internet desde la GadgetPC utilizando el módem USB Huawei E1556...........................................................................................63
6.1 Instalación del hardware..........................................................................................................63
6.1.1 Problemas de instalación y USB_ModeSwitch..................................................................63
6.2 Establecimiento de una conexión a Internet con el módem USB Huawei.............................67
6.2.1 pppd y chat: configuración y uso.......................................................................................67
6.2.2 Estableciendo y terminando una conexión........................................................................72
6.3 Haciendo a la GadgetPC visible desde Internet......................................................................73
6.3.1 Redireccionamiento de puertos con ssh.............................................................................74
CAPÍTULO 7. Conclusiones y Recomendaciones.......................................................80
7.1 Conclusiones............................................................................................................................80
7.2 Recomendaciones....................................................................................................................82
BIBLIOGRAFÍA............................................................................................................83
APÉNDICE A. Código fuente de aplicaciones desarrolladas....................................86
est_at90.c (AT90USB162).................................................................................................................86
estacion.h (AT90USB162)................................................................................................................88
est_atmega.c (ATMEGA16)..............................................................................................................91
v
cad.h (ATMEGA16)..........................................................................................................................92
est_common.h (AT90USB162 y ATMEGA16).................................................................................93
uart_ucr.h (AT90USB162 y ATMEGA16)........................................................................................99
uart_ucr.c (AT90USB162 y ATMEGA16).......................................................................................99
estacion.php (GadgetPC )................................................................................................................101
est_ctrl.c (GadgetPC )......................................................................................................................102
ESdigital.php (GadgetPC )..............................................................................................................103
pwm.php (GadgetPC ).....................................................................................................................106
cad.php (GadgetPC )........................................................................................................................111
lecturaPuertos.php (GadgetPC )......................................................................................................112
escrituraPuertos.php (GadgetPC )...................................................................................................113
estacionMenu.php (GadgetPC ).......................................................................................................114
estacionHead.html (GadgetPC )......................................................................................................115
estacion.css (GadgetPC ).................................................................................................................115
vi
ÍNDICE DE FIGURAS
Figura 1.1 GadgetPC..................................................................................................................................3
Figura 1.2 Modem USB 3G, sólo y conectado a un host...........................................................................4
Figura 1.3 AT90USB162 en su tarjeta para prototipos..............................................................................4
Figura 2­1. Dos dispositivos con sistemas de computación embebidos, .................................................10
con requerimientos bastante distintos......................................................................................................10
Figura 2­2. El ambiente de desarrollo típico para sistemas embebidos...................................................13
..................................................................................................................................................................21
Figura 2­3. El logo del Proyecto GNU. ...................................................................................................21
Figura 2­4. Los logos oficiales del USBIF que certifican compatibilidad con USB 2.0. .......................26
..................................................................................................................................................................28
Figura 2­5. Vista física y lógica del USB................................................................................................28
Figura 2­6. El modelo de comunicación entre un host y un dispositivo en USB.....................................29
Figura 4­1 Vista general del sistema implementado................................................................................45
Figura 4­2 Contenido del archivo de reglas 96­estacion.rules.................................................................50
Figura 5.1 Página de inicio de la aplicación.............................................................................................53
Figura 5.3 Control de Entrada/Salida y estado de los pines.....................................................................56
Figura 5.4 Control de Salidas PWM........................................................................................................57
Figura 5.5 Control de Entradas Analógicas.............................................................................................57
Figura 5.5 Detalle de una función que genera contenido con base en la configuración .........................59
y estado actual del sistema.......................................................................................................................59
Figura 5.6 Función para generar un elemento de formulario...................................................................59
con nombre parametrizado y contenido dinámico..................................................................................59
Figura 5.7 Uso de exec( ) para invocar a est_ctrl.....................................................................................62
Figura 6.1 Comportamiento del sistema ante la conexión de la datacard................................................64
Figura 6.2 Respuesta del sistema ante la conexión de la datacard,..........................................................66
vii
con usb_modeswitch instalado................................................................................................................66
Figura 6.3 Detección del dispositivo y creación de los puertos,..............................................................67
al cargar el módulo usbserial....................................................................................................................67
Figura 6.4 Archivo de opciones para establecer la conexión al servicio de Internet del ICE..................69
Figura 6.5 Script de chat para inicializar el módem................................................................................70
Figura 6.6 Creación de la interfaz ppp0 como resultado de una conexión exitosa..................................72
Figura 6.7 Redireccionamiento de puerto para hacer a la GadgetPC visible desde Internet,...................75
con ayuda de un servidor público intermediario (Jack)...........................................................................75
Figura 6.8 Archivo /root/.ssh/config en la GadgetPC: configuraciones para accesar a Jack...................76
Figura 6.9 Establecimiento de una sesión/túnel ssh desde la GadgetPC a Jack.......................................78
Figura 6.10 Acceso desde un host remoto al servidor web de la GadgetPC............................................79
a través del puerto redirigido en Jack......................................................................................................79
viii
ÍNDICE DE TABLAS
Tabla 2­1 Servicios de datos sobre tecnología celular.............................................................................34
Tabla 4.1 Comandos primitivos para control de la estación.....................................................................46
ix
NOMENCLATURA
Apache El servidor web de la Apache Software Foundation.
API “Application Programming Interface”
ATMEGA16, Modelos de microcontrolador de 8 bits de arquitectura AVR de Atmel.
AT90USB162 (AT90)
ARM “Advanced RISC Machine” (la arquitectura de microprocesador)
AVR La arquitectura base de los microcontroladores Atmel de 8 bits.
C El lenguaje de programación C
CAD Convertidor Analógico­Digital
CDC/ACM
“Communications Device Class” / “Abstract Control Model” la clase y la sub­
clase de dispositivos USB
CSS
“Cascading Style Sheet”
Debian
La distribución de Linux
DRAM “Dynamic RAM”
EEPROM “Electrically Erasable EPROM”
FLIP
GPC
“Flexible In­System Programmer”
GadgetPC
GCC
“Gnu Compiler Collection” o “Gnu C Compiler”
GNU
“GNU's Not Unix”
GNU GPL
“GNU General Public License”
GUI
“Graphical User Interface”
HTML
“HyperText Markup Language”
HTTP
“HyperText Transport Protocol”
LUFA
“Lightweight USB Framework for AVR”
MMU “Memory Management Unit”
x
RAM “Random Access Memory”
ROM “Read­Only Memory”
PC
“Personal Computer”
PHP
“PHP Hypertext Processor”, el lenguaje de programación y scripting
PPP
“Point to Point Protocol”, el protocolo de red punto­a­punto
pppd
“Point to Point Protocol Daemon”, el programa que implementa PPP.
PWM
“Pulse Width Modulation” (Modulación de Ancho de Pulso).
SSH
“Secure SHell”, el protocolo de red para acceso remoto seguro.
ssh,sshd
Programas cliente y servidor para el protocolo SSH.
URL
“Universal Resource Locator”.
USART
“Universal Synchronous/Asynchronous Receiver­Transmitter” (Receptor­
Transmisor Síncrono/Asíncrono Universal).
USB
“Universal Serial Bus”.
xi
RESUMEN
En el presente proyecto el objetivo principal fue construir una plataforma para medición y actuación remota, integrando varios componentes de hardware comercialmente disponible y herramientas de software, en su mayoría libres. Para construir esta plataforma se utilizó una arquitectura modular tanto a nivel de hardware como de software. A nivel de hardware, el sistema está compuesto principalmente por dos microcontroladores AVR de Atmel (un AT90USB162 y un ATMEGA16), una microcomputadora llamada GadgetPC, del fabricante Bipom Electronics, basada en la arquitectura ARM y el sistema operativo Linux, y un módem USB (“datacard”) Huawei E1556, comercializado por el ICE bajo los recientemente introducidos planes de datos sobre la red celular 3G. La GadgetPC solo posee puertos USB, por lo que la comunicación con todos los dispositivos externos, incluyendo el conjunto formado por los microcontroladores, se realiza sobre este medio.
En cuanto a software, los componentes principales usados fueron la distribución Debian­
Armel, (principalmente por su amplia disponibilidad de paquetes), el servidor web Apache y el lenguaje PHP, para crear el sitio que forma la interfaz con los usuarios remotos de la plataforma, y el “daemon” pppd, para realizar la conexión a Internet utilizando el módem.
Se halló que la plataforma sí es viable, y que el uso de la datacard brinda buena cobertura y suficiente ancho de banda, si bien la naturaleza de la conexión ofrecida por el servicio de datos sobre celular presenta algunos inconvenientes que hacen menos directo el acceso desde Internet hacia la plataforma. Aún así estos obstáculos se pueden sortear por diferentes métodos, dos de lo cuáles son los túneles ssh y las Redes Privadas Virtuales. En este proyecto se implementó la primera solución por cuestiones de tiempo.
Se encontró que los sistemas embebidos basados en Linux son realmente muy poderosos y versátiles, y no son muy diferentes de utilizar respecto a las distribuciones típicas. Las capacidades de la plataforma podrían ser fácilmente expandidas para brindar muchas más funciones que las actualmente ofrecidas.
xii
CAPÍTULO 1: Introducción
1.1 Objetivos
1.1.1 Objetivo general
Construir un sistema que permita la comunicación entre un host conectado a Internet, y un computador embebido remoto conectado directamente a un microcontrolador, para actuación y medición de señales, y a un módem 3G para la comunicación inalámbrica.
1.1.2 Objetivos específicos
1. Montar el entorno de desarrollo de hardware y software necesario para la programación y pruebas de:
1. El computador embebido basado en Linux y producido por Bipom Electronics comercializado bajo el nombre de GadgetPC (GPC).
2. Un microcontrolador Atmel AT90USB162.
3. El host que provee la interfaz para el usuario.
2. Estudiar los conceptos y herramientas relativas a USB que se requieran para manejar el hardware y software asociado a esta tecnología.
3. Investigar sobre la tecnología celular 3G y los dispositivos disponibles comercialmente para utilizarla en Costa Rica.
4. Establecer comunicación entre el microcontrolador y el GPC utilizando el hardware de USB disponible en ambos, para lograr envío y recepción de caracteres.
5. Establecer el acceso a Internet del GPC mediante la instalación de un módem 3G.
6. Diseñar e implementar un sistema de software que abarque los tres componentes principales ( host remoto, GPC, y microcontrolador ) y que permita al usuario final accesar al microcontrolador para generar diversas salidas en sus pines, leer datos y ejecutar funciones, de manera remota.
1
2
1.2 Justificación
La idea inicial de este proyecto surgió de un TCU en el que se realizó la inspección del sistema eléctrico de un edificio mediano. La dificultad para desplazarse por el cielo raso y llegar a puntos lejanos de éste, llevaron a pensar que sería bueno tener un vehículo manejado remotamente, con una cámara de vídeo y brazos mecánicos simples, que pudiera llegar a los puntos difíciles y mover cables u otros objetos con el fin de inspeccionarlos visual y físicamente.
Se pensó que realmente, la transmisión de señales de control y de datos es un problema en sí mismo, aparte del vehículo, por lo que se puede diseñar de manera independiente. Sería bueno, por ejemplo, tener ese sistema encapsulado y listo para uso con diversos vehículos, robots, estaciones meteorológicas o cualquier otro dispositivo compatible con una interfaz previamente definida. En el futuro, sistemas más complejos que la Escuela quisiera desarrollar podrían basarse en esta plataforma genérica.
Por otro lado, existe una segunda justificación, menos pragmática, inspirada en el artículo de Chris Anderson, titulado “In the Next Industrial Revolution, Atoms are the New Bits” (“En La Próxima Revolución Industrial, los Átomos son los Nuevos Bits”). Una de las ideas propuestas por este artículo, es que la caída en los precios de la tecnología y la mayor difusión del conocimiento, así como el crecimiento de las tecnologías “Open Source”, han redemocratizado la producción en gran medida. La Primera Revolución Industrial concentró la producción en pocas manos porque sólo grandes compañías podían adquirir los medios necesarios, debido a su costo exorbitante. Hoy en día surge y crece el potencial para revertir esto. Así, uno de los objetivos no técnicos del proyecto, es demostrar que es posible diseñar y construir un sistema completo, robusto, funcional, y de bajo costo, partiendo de componentes disponibles comercialmente, hardware y software abierto, y el valor agregado del conocimiento.
3
1.3 Presentación del problema
El problema central consistió en desarrollar un sistema que permita el acceso remoto a los periféricos y pines del microcontrolador, utilizando una computadora personal típica, siendo los únicos requisitos que posea conexión a Internet y un navegador Web moderno.
La computadora embebida GadgetPC es un dispositivo basado en un microcontrolador ARM de 32 bits producido por Atmel. Está diseñada para funcionar con Linux y actualmente soporta dos distribuciones: Arm9­Linux y Debian­Armel. La GPC cuenta con cinco puertos USB: uno para conectarse a un host principal y actuar como un periférico de éste, para labores de programación y depuración principalmente, y cuatro puertos USB tipo host para conectarle dispositivos periféricos. Además, se tiene disponible una interfaz serial básica, compuesta por un par de pines RXD y TXD (con niveles de voltaje de 0 y 3.3V) para aplicaciones que requieran comunicación serial. La Gadget PC se muestra en la Figura 1.1, en tamaño natural.
Figura 1.1 GadgetPC
La GPC es un dispositivo de gran poder y pequeño
tamaño, aquí se representa en escala 1:1
Respecto a las telecomunicaciones, se decidió usar la tecnología celular 3G, con el fin de maximizar la cobertura geográfica así como el ancho de banda disponible, previendo un eventual soporte para aplicaciones con sonido, vídeo, o procesamiento intensivo de datos. Se usará un módem 4
3G como el que se muestra en la figura 1.2, vendido por el Instituto Costarricense de Electricidad como parte de uno de los planes de la nueva marca Kolbi.
Figura 1.2 Modem USB 3G, sólo y conectado a un host.
Además, se definió que se utilizaría USB para la comunicación entre la GadgetPC y el microcontrolador, porque, aunque ambos podrían utilizar una interfaz serial típica (protocolo tipo RS­
232, aunque con niveles de voltajes compatibles con TTL), USB es una tecnología actual, de gran capacidad, y en desarrollo. Esto fue lo que llevó a seleccionar el microcontrolador AT90USB162 como unidad final. Este microcontrolador también es fabricado por Atmel pero es un dispositivo de 8 bits, mucho más pequeño y simple que su gran familiar presente en la GadgetPC. La tarjeta de prototipo que aloja al microcontrolador se muestra en la figura 1.3, y el pequeño dispositivo se puede apreciar cerca de su centro.
Figura 1.3 AT90USB162 en su tarjeta para prototipos
La tarjeta incluye un botón, un led, un puerto mini­USB, el conector
para el programador, y el conector para suministrar energía a la tarjeta. 5
Por otro lado, se buscó usar, en lo posible, aplicaciones y herramientas abiertas como Linux, Debian, el servidor web Apache, y otras, para que la plataforma fuera lo más libre que se pueda y el diseño quedara disponible a futuros estudiantes o desarrolladores.
El reto consistió principalmente, entonces, en integrar todos los componentes de software y hardware, y elaborar las aplicaciones e interfaces faltantes, para obtener el sistema deseado.
1.4 Metodología
La naturaleza modular de los componentes y el software utilizados para construir el sistema se prestó para hacer una división bastante clara y ordenada del procedimiento requerido para realizarlo.
La metodología seguida en este proyecto se inspiró en tres principios que han pasado la prueba del tiempo en los campos relacionados con desarrollo de hardware y software.
1. Lectura y estudio de documentación técnica y teoría. Esto incluyes hojas del fabricante, manuales, tutoriales, libros de texto y otros documentos.
2. Divide y vencerás: a nivel del sistema, los distintos programas y componentes deben siempre tener fines específicos y tareas concretas, y articularse entre sí para crear el sistema final. Es más fácil hacer un sistema a partir de componentes modulares, con requerimientos y propósitos claros e interfaces bien definidas entre ellos, que un solo sistema grande y monolítico. Este principio se puede aplicar recursivamente, aplicándolo de nuevo a cada uno de los programas que componen el sistema para lograr programas bien estructurados, comprensibles, flexibles y mantenibles.
3. Desarrollo iterativo, incremental y evolutivo, acompañado de pruebas a lo largo de todo el proceso.
Utilizando estos principios como una guía general, se elaboró un procedimiento específico, que se explica seguidamente:
1. La etapa inicial del proyecto consistió en la recopilación y estudio de bibliografía básica para el desarrollo de éste. Aunque la consulta de documentación y otros textos fue una constante durante todo el Proyecto, inicialmente se plantearon las siguientes fuentes de información:
1. Fichas técnicas, hojas del fabricante, notas de aplicación, guías y tutoriales sobre los tres principales componentes de Hardware: GadgetPC, AT90USB162 y módem.
6
2. Documentación sobre el estándar USB y la manera en que se implementa y utiliza en sistemas Linux.
3. Documentación general sobre la tecnología celular 3G. En realidad no se profundizó mucho en los detalles de esta tecnología pues se buscaba ser, más que todo, usuario de ella.
2. La segunda etapa del Proyecto cubrió el montaje inicial de todo el ambiente de desarrollo. En esta etapa, la finalidad era familiarizarse con el manejo y programación de los componentes. Comprendió los siguientes pasos:
1. Instalación en el equipo para desarrollo, de las herramientas de hardware y software necesarias para utilizar la GadgetPC y programar aplicaciones que se ejecuten en ella. Se instalaron las herramientas básicas de desarrollo que permitieron la programación del programa elemental “Hola Mundo!” en la GadgetPC.
2. Instalación del entorno de desarrollo para el microcontrolador AT90. Estos microcontroladores se programan con un método distinto que otros microcontroladores Atmel de 8 bits más viejos, que usan el ISP (“In­System Programming”) tradicional a través de la interfaz SPI. Así, fue necesario investigar la metodología de programación y las herramientas necesarias. Finalmente, igual que con la GadgetPC en el punto anterior, se programó una primera aplicación ( un “Hola Mundo” simbólico, con leds) en el AT90.
3. Instalación del módem USB 3G en algunas PC típica cuyo sistema operativo fuera Debian o alguna de las distribuciones de Linux descendientes de Debian, como Ubuntu. La instalación se realizó totalmente con comandos introducidos en la línea de comandos del sistema, y no de forma gráfica, para lograr así reproducir más fielmente las condiciones que se encontrarían si la instalación fuera en la GadgetPC. Con este paso, se buscaba comprender más a fondo el proceso de instalación y las posibles dificultades que se podrían encontrar en él, así como verificar el correcto funcionamiento del módem, todo ello evitando las complicaciones que podrían surgir por el hecho de usar un sistema relativamente desconocido y menos “típico” como lo es la GPC. Esta etapa buscaba producir dos resultados concretos: acceso a Internet para el host que usa el módem, y buena documentación del proceso de instalación, para usarla como referencia posteriormente cuando se hiciera la instalación en la GPC.
7
3. En una tercera Etapa se inició el desarrollo de partes básicas para el Sistema pero que eran bastante independientes de éste y entre sí, específicamente:
1. El establecimiento de comunicación sobre USB entre la GPC y el AT90. Se consultó la documentación del periférico USB del AT90 en su hoja de fabricante y la documentación sobre USB en general. También se revisó la documentación y código de la librería LUFA, una librería desarrollada específicamente para los microcontroladores AVR con USB. Se logró primero el envío y recepción de caracteres partiendo de una “demo” básica de la librería, y posteriormente se modificó para obtener flujos más grandes y continuos de comandos y respuestas, entre la GPC y el AT90.
2. La instalación del módem 3G en la GPC, tomando como referencia su instalación en la PC típica y realizando ajustes o cambios en el procedimiento donde fuera necesario. La GPC logró una conectividad confiable y de buena velocidad a Internet. Se hicieron pruebas básicas como ping a servidores conocidos (como google.com), traceroute y wget, entre otras.
3. La instalación del servidor web Apache en la GPC. Se montó el servidor Apache y el intérprete de PHP siguiendo procedimientos comunes de instalación de paquetes en Linux. Se hizo inicialmente una pequeña página (“Hola Mundo” otra vez, pero en HTML/PHP) que se podía ver desde una PC conectada a la misma LAN mediante un adaptador de WiFi USB.
4. En la cuarta etapa se llevó a cabo la primera parte de la integración de aplicaciones y componentes. Se desarrolló la primera versión de la página web central y la primera versión de la aplicación principal del microcontrolador secundario, así como programas o scripts de utilería y soporte que fueron necesarios. La primera versión de la aplicación web en esta etapa fue muy simple: permitía enviar un carácter al microcontrolador y éste lo desplegaba en leds en uno de sus puertos. También, la aplicación web permitía leer un carácter de un puerto del microcontrolador y desplegarlo en la página web.
5. En un quinta etapa se elaboró sobre las primeras versiones de la página web y la aplicación del microcontrolador para mejorar su estilo, presentación y funcionalidad y finalmente permitir a un usuario en Internet:
8
1. Configurar, leer o escribir los puertos del microcontrolador.
2. Leer digitalmente cualquier pin.
3. Leer analógicamente los pines con capacidad analógica.
4. Activar y detener salidas PWM, definiendo su período y ciclo de trabajo.
CAPÍTULO 2: Marco Teórico
El desarrollo de aplicaciones basadas en sistemas embebidos comprende un conjunto amplio de conceptos, que abarcan su estudio y análisis, su diseño, y su implementación final en hardware y software. Los temas principales que se cubren en el presente marco teórico son:
1. Las características generales de la computación embebida, y su ejemplificación con el GadgetPC y al AT90USB162.
2. Los conceptos básicos del desarrollo de aplicaciones para sistemas embebidos y las herramientas de desarrollo para los microcontroladores AVR, y el GadgetPC (cuyo núcleo es un microcontrolador ARM9)
3. Los fundamentos de la tecnología USB.
4. Conceptos introductorios sobre los servicios de datos basados en telefonía celular 3G y el uso de módems de este tipo en Linux.
2.1 Los sistemas embebidos, el GadgetPC y el AT90USB162
En esta sección primero se describen las características generales de los sistemas de computación embebidos y posteriormente se explica cuáles de estas características ­y cómo­ se presentan en la GadgetPC y el AT90USB162.
2.1.1 Los sistemas embebidos
El concepto de sistema embebido no tiene una definición estricta, aunque en general se entiende como un sistema de computación que está diseñado y programado para realizar un conjunto de tareas específicas, que cumplen una función particular dentro de un sistema o dispositivo mayor. Esto contrasta con un sistema de cómputo de propósito general como una PC, en la que el objetivo es servir para la amplia gama de tareas que un usuario podría querer realizar. El diseño y dimensionamiento de los recursos de software y hardware de los que dispondrá un sistema embebido se hace para cubrir los requerimientos específicos de las tareas para las que se pensó el dispositivo y no para otras. Por último, es común que haya algunos requerimientos de temporización o de operación en tiempo real para las aplicaciones embebidas.
Cuando se habla de sistemas embebidos, se debe tener en cuenta de que pueden ser muy 9
10
grandes y poderosos, como los routers del “backbone” de Internet, o pequeños, como un enrutador inalámbrico para el hogar. Así, es difícil brindar una lista de características completamente generales para todos los sistemas, más allá de la definición del párrafo anterior. En este proyecto, el énfasis es en los sistemas embebidos considerados como pequeños, donde la definición de “pequeño” es, necesariamente, ambigua. El espectro de tecnologías actualmente disponibles no permite trazar líneas divisorias nítidas entre estas categorías.
Figura 2­1. Dos dispositivos con sistemas de computación embebidos, con requerimientos bastante distintos.
Teniendo lo anterior en mente, muchas veces se hace referencia a estos sistemas embebidos (los pequeños, utilizados en este proyecto) desde la perspectiva de los “recursos limitados”: en muchos computadoras de este tipo, los recursos como energía, CPU, memoria, almacenamiento permanente, entrada y salida, e interfaz con el usuario son relativamente pequeños y limitados, o algunos pueden estar del todo ausentes. Esto acota el tamaño y velocidad de las aplicaciones que se pueden ejecutar en uno de estos sistemas. Por ejemplo, no se puede esperar que un microcontrolador de 8 bits como un PIC o AVR pueda correr un compilador (grande) como GCC, o que un pequeño dispositivo soporte una interfaz gráfica, con ventanas e íconos.
En esta misma línea de pensamiento, también se hace referencia a una limitada expansibilidad del hardware y el software: mientras que en una computadora de escritorio típica es normal la frecuente remoción o adición de periféricos (internos o externos) y programas, con procedimientos más o menos estandarizados, en un computador embebido normalmente no se espera una gran variación de la configuración de hardware y software, o al menos no como parte de la operación normal del dispositivo. Inclusive muchas veces se requieren procedimientos, programas y equipos especiales para actualizar su hardware o software, como los programadores (hardware especial) STK500 y sus drivers 11
asociados (software especial) que se pueden usar para programar muchos microcontroladores de la familia AVR de Atmel.
Se conoce como firmware al programa o conjunto de programas que se carga en un sistema embebido para gobernar su comportamiento. Éste se almacena en algún tipo de memoria no volátil como memoria flash, usando un procedimiento y protocolo específico para el componente. Por ejemplo, el “In­System Programming” (ISP) de Atmel es una metodología de programación que define la interfaz de hardware y software mediante la cuál se pueden programar muchos microcontroladores de Atmel, como los de la familia del AVR (a la que pertenecen el Atmega y el AT90). La palabra firmware remite al hecho de que es algo que está “grabado”, implicando que su modificación no es algo muy común.
En cuanto a la unidad central de procesamiento, muchos sistemas embebidos pequeños utilizan microcontroladores como componentes principales, ya que los microcontroladores integran muchos periféricos útiles como temporizadores y unidades para comunicación con el mundo exterior, como USARTS, lo cual permite minimizar el costo y la complejidad del diseño. En sistema más complejos, por ejemplo para vídeo de alta definición, se utilizan chips de alto desempeño, optimizados para la labor, como Procesadores Digitales de Señales (DSP). Los microprocesadores de propósito general, como los que se pueden encontrar en una PC (Pentium, Dual Core, etc), no son tan comunes en las aplicaciones embebidas, ya que muchos de ellos requieren del soporte de chips externos, consumen más energía de la necesaria y no están nada optimizados para alguna tarea específica.
Finalmente, un rasgo común de los sistemas embebidos es que su interfaz con el usuario es muchas veces muy simple, por ejemplo con botones y leds, o necesita soporte externo para ser visible: una consola serial, un acceso mediante la Web a través de un navegador, o una conexión sobre una red utilizando un protocolo como SSH o Telnet, son varias de las formas comunes en que se accesa a los dispositivos.
2.1.2 La GadgetPC y el AT90USB162 desde la perspectiva de la computación embebida.
Tanto la GadgetPC como el AT90USB162 son sistemas de cómputo completos por sí solos, ambos tienen una unidad central de procesamiento, memoria, almacenamiento permanente (de programa(s) y datos), e interfaces para comunicarse con otros sistemas.
12
AT90USB162
El AT90USB162 es el más pequeño de ambos sistemas. En él se identifican claramente las características del concepto tradicional de sistema embebido pequeño (aunque lo más probable es que se le use como un componente en una función mayor y no como el centro de la aplicación).
Su palabra de datos (“data word”) es de 1 Byte y su memoria RAM es de sólo 512 Bytes. Su palabra de instrucción (“instruction word”) es de 16 o 32 bits, y su memoria flash de programa es de 16 KB. Por lo tanto, en el mejor de los casos puede soportar un programa de 8K instrucciones y 512 datos de un byte. Si se piensa que son instrucciones de ensamblador, se estará de acuerdo en que no es mucho. En cuanto a almacenamiento permanente, también es pequeño: posee una EPROM de 512 Bytes, que se puede usar para almacenar datos de forma no volátil bajo control de programa, o con un programador externo. Se puede usar la memoria flash de programa para almacenar datos (con instrucciones especiales), pero entonces se sacrifica el tamaño del programa. En resumen: no se pueden ignorar las limitantes de espacio al desarrollar para el dispositivo. Además de los recursos de memoria Flash, RAM y EPROM, el dispositivo brinda como periféricos integrados, un temporizador de 8 bits (con dos canales para PWM), un temporizador de 16 bits (con tres canales para PWM), un temporizador guarda (“Watchdog timer”), y un comparador analógico. En todos los casos, las capacidades, tamaños y cantidades de estos recursos y periféricos están definidas y no son expansibles o ajustables: esto refleja la usual rigidez del hardware en un sistema empotrado.
Un AT90USB162 no posee, por defecto, ningún tipo de interfaz con el usuario, toda interfaz debe ser programada por el mismo desarrollador, para la aplicación específica que vaya a cumplir el microcontrolador. Sí posee, sin embargo, soporte en hardware para varios protocolos que le permiten comunicarse con dispositivos externos, y que se podrían usar, entre otras cosas, para establecer comunicación con un equipo mayor, siendo este último el encargado de desplegar la interfaz. Los protocolos soportados por el hardware del AT90USB162 incluyen PS/2, SPI, diversas configuraciones para comunicación serial asíncrona o síncrona (con una USART altamente flexible), y el más importante para este proyecto, USB.
Respecto a su programación, el microcontrolador se puede programar de varias maneras (más adelante se detallan), pero en todos los casos se necesita de un host externo para desarrollar la aplicación y para cargar el programa en el dispositivo. Este arreglo (host para desarrollo, dispositivo 13
por programar, hardware y software para programación) es típico del desarrollo de aplicaciones empotradas, y se repite en el caso del desarrollo para la GadgetPC. Se ilustra en la figura 2­2.
Figura 2­2. El ambiente de desarrollo típico para sistemas embebidos.
La GadgetPC
La GadgetPC es realmente una PC casi completa pero sin periféricos (externos), y representa otro nivel de computación embebida, más allá de lo que se puede construir con un AT90USB162. En un sistema centrado en un microcontrolador de 8 bits como el AT90 el paradigma es relativamente simple: la aplicación que se cargue en el microcontrolador es la que gobierna la operación de todo el sistema, no existe un sistema operativo. La arquitectura simple de los microcontroladores AVR (de Atmel) de 8 bits no soporta un S.O. moderno. La GadgetPC es diferente en varios sentidos. En primer lugar, aunque sí está basada en un microcontrolador, éste es un microcontrolador AT91SAM9260, basado en la arquitectura ARM9. Esta arquitectura es de 32 bits, y posee la capacidad de expandir su memoria principal mediante DRAM externa, lo que permite mayor capacidad para programas y datos.
14
Pero la característica clave que diferencia a la arquitectura ARM9 (del AT91SAM9260) de la arquitectura AVR (del AT90USB162), más allá de su mayor tamaño en casi todo, es que la primera cuenta con una Unidad de Manejo de Memoria (MMU) y la segunda no. La MMU permite implementar un sistema de memoria virtual. La memoria virtual permite, a su vez, implementar mecanismos de protección de memoria (para que por ejemplo, una aplicación no pueda entrar en el espacio de memoria de otra) y de traducción de direcciones virtuales a direcciones físicas (requerida para por ejemplo, la relocalización de código en la memoria física). Los “kernels” modernos de Linux, y otros sistemas operativos actuales, requieren las capacidades de memoria virtual y por lo tanto sólo pueden pueden ser ejecutados en una CPU que tenga MMU.
Así, en la GadgetPC realmente se puede tener un sistema operativo dentro del que se ejecutan las aplicaciones, con todos los beneficios y restricciones que involucra esto. El S.O. con que viene de fábrica es una distribución de Linux llamada “ARM9 Linux” aunque es posible también instalar un Debian especialmente compilado para ARM9.
En la GadgetPC también se observan las características de un sistema embebido pequeño típico: su memoria RAM externa, de 32 MB, no es expansible (está integrada a la tarjeta base de la GagdetPC); tampoco lo es su DataFlash externa, de 8 MB. Utiliza como unidad central un microcontrolador para minimizar la cantidad de componentes externos, ahorrando costo, espacio y energía. Además, requiere de una consola conectada a su único puerto serial para desplegar la interfaz con el usuario (un shell de Linux), o bien alguna conexión por red para accesarla remotamente con ftp, SAMBA, telnet, o ssh. De otro modo no es posible para un usuario interactuar con ella.
Entonces, el camino que toma la GadgetPC para lograr una alta flexibilidad es aprovechar la tecnología USB junto con las capacidades de propósito general de Linux. La GPC aprovecha el hardware de host­USB del AT91SAM9260 para brindar cuatro puertos a los que se pueden conectar dispositivos USB. Gracias a las propiedades del USB (más adelante se discute USB con más detalle) la cantidad de periféricos puede ser un número muy superior a cuatro, si se utilizan “hubs” USB. Un hub USB es un dispositivo que expande el bus USB agregando más puntos de conexión a partir de un puerto. Así, la GPC se puede expandir para realizar cualquier función, mediante un dispositivo USB apropiado, siempre que se cuente con soporte para él en el sistema operativo. La utilización más básica de esta capacidad es la instalación de un gran almacenamiento permanente con una llave USB o un 15
disco duro externo con interfaz USB. De hecho, la imagen de Linux que corre la GPC debe mantenerse en una llave USB porque no cabe en la computadora misma (en ninguna de sus memorias no volátiles internas, ni en la DataFlash presente en la tarjeta).
Debe pensarse entonces en la GPC como un sistema embebido en sí mismo y a la vez una plataforma general, sobre la que se pueden montar aplicaciones embebidas específicas. Debe notarse que en el contexto de la GPC, hablar de “firmware” (entendido como el programa que se carga en un computador embebido para regir su operación) puede prestarse a confusión: por un lado el microcontrolador subyacente sí tiene memorias que contienen firmware, entendido en el sentido tradicional. Pero si se desarrolla una aplicación para el Linux instalado en la GPC, se copia esa aplicación a la GPC (por ejemplo con un protocolo para transferencia de archivos como FTP), y se ejecuta, entonces sería más bien como un programa más en una computadora típica, es decir, software, y no firmware en el sentido tradicional.
2.2 El desarrollo de aplicaciones para sistemas embebidos
En esta sección se exploran algunos de los conceptos básicos y las herramientas involucradas para el desarrollo basado en la GPC y los microcontroladores AVR.
2.2.1 Desarrollo para la familia AVR
Hay varios conjuntos de programas y hardware que se pueden utilizar para desarrollar aplicaciones para esta familia de microcontroladores, desde entornos integrados como AVR Studio hasta scripts de shell. Muchas veces los entornos integrados y los scripts complejos hacen que se pierda la perspectiva paso a paso del proceso, por lo que seguidamente se discuten brevemente las herramientas involucradas y el papel que juegan dentro de éste.
avr­binutils
Es la versión para AVR del binutils de GNU. binutils es un paquete de aplicaciones para la construcción y manipulación de archivos objeto (estos son archivos “binarios” que contienen código compilado). Normalmente un programador no ve ni invoca directamente a estas herramientas, sino que otras, de más alto nivel, las invocan. Por ejemplo, avr­gcc no solo compila sino que, por debajo, llama al ensamblador(avr­as), y al “linker”(avr­ld), que a su vez hacen uso de otros programas de aún más bajo nivel como avr­objcopy. Sin embargo vale la pena discutir estas aplicaciones para comprender más 16
a fondo el proceso. Entre ellas se tienen:
•
avr­as: el ensamblador para AVR. Su tarea consiste en traducir del lenguaje nemónico del conjunto de instrucciones para el núcleo AVR, a código objeto para esa arquitectura de CPU. Este código ensamblador puede provenir de avr­gcc o ser generado por un programador manualmente. •
avr­ld: el “linker” (enlazador) para avr. Se utiliza para combinar y unir archivos de código objeto y librerías, en nuevos archivos objeto, librerías o ejecutables.
•
avr­ar y avr­ranlib: se utilizan para crear librerías.
•
avr­objcopy: es un programa que permite copiar archivos objeto o extraer secciones de éstos, a otros archivos objeto. También puede traducir entre varios formatos de archivos objeto. Su función principal, desde la perspectiva de un usuario. es traducir el código compilado a un archivo .hex, en formato hexadecimal, que se puede cargar a alguna de las memorias (flash o eeprom) del microcontrolador.
•
avr­objdump: es una aplicación para obtener información acerca de archivos objeto. Se utiliza, por ejemplo, para desensamblar código objeto. Se puede usar para ver, en formato legible para un humano, el contenido de archivos .elf, discutidos más a fondo posteriormente.
•
avr­strip: se utiliza para remover información simbólica de archivos objeto, por ejemplo para quitar símbolos de “debugging” u otros símbolos que ya no son necesarios.
avr­gcc
Es la versión de GCC (“GNU Compiler Collection”) para los microcontroladores AVR. avr­
gcc y las aplicaciones anteriores se ejecutan en la equipo para desarrollo, y no en el dispositivo en que funcionará el ejecutable. Por esta razón es un compilador cruzado (“cross­compiler”).
avr­gcc brinda excelente soporte para el lenguaje C, y para parte de C++. Aunque se le llama a avr­gcc, o bien a gcc mismo, “el compilador”, en realidad este programa no solo compila código, sino que además contiene al preprocesador, y por defecto invoca al ensamblador y al enlazador (“linker”) en las etapas adecuadas, silenciosamente, para finalmente producir un ejecutable.
El compilador avr­gcc, al ser una versión de gcc, tiene las mismas opciones generales y un conjunto de opciones específicas para la familia AVR. Por ejemplo, el siguiente comando invoca al 17
compilador con la opción ­mmcu para especificar que el dispositivo objetivo es un Atmega32, mientras que todas las demás opciones son conocidas del gcc normal (para que genere advertencias e información para depuración, optimice el tamaño del programa y genere como salida un archivo objeto llamado ledblink.o):
avr­gcc ­Wall ­g ­Os ­mmcu=atmega32 ledblink.c ­o ledblink.o
avr­libc
Es una versión reducida de la librería estándar de C para los microcontroladores AVR. Además de muchas funciones que facilitan la utilización de periféricos, el acceso a registros dentro del microcontrolador, y la realización de tareas de cómputo comunes (como cálculos matemáticos), avr­libc incluye código que se encarga de la inicialización básica del microcontrolador y provee una interfaz fácil de usar para manejo de interrupciones.
Así por ejemplo, al incluir estas librerías en el código fuente, se obtienen macros que permiten manipular los registros por su nombre de manera transparente al programador o declarar subrutinas de atención a interrupciones (ISRs) como si fueran funciones, entre otras facilidades de gran conveniencia.
Programación del dispositivo: los archivos .elf y .hex
Normalmente las aplicaciones para los microcontroladores avr se escriben en C, ensamblador, o una combinación de ambos. A partir del código fuente, y utilizando las herramientas discutidas hasta el momento (avr­binutils, avr­gcc, avr­libc) se obtiene un archivo binario en formato .elf. El formato .elf (“Executable and Linkable Format”) es un formato binario estandarizado, de uso ampliamente difundido en sistemas Unix y Linux, y comúnmente utilizado para contener ejecutables, código objeto, librerías compartidas, y “core dumps” (un “core dump” es el estado grabado de la imagen en memoria y otra información de estado de un programa, generada usualmente cuando ocurre un error fatal). Un archivo .elf contiene un conjunto de secciones que pueden contener diversos tipos de información binaria, ya sea para la ejecución del archivo, o para su enlazamiento (“linking”) con otros archivos. Algunas de las secciones comúnmente encontradas en un archivo .elf son:
•
.data: contiene datos inicializados que se cargarán como parte de la imagen en memoria del programa. Por ejemplo, en un programa en C, una variable global que tenga un valor asignado 18
como parte de su declaración, estará en esta sección.
•
.bss: contiene datos no inicializados. •
.text: esta sección contiene las instrucciones ejecutables de un programa.
Adicionalmente, en el caso de los archivos objeto o ejecutables para AVR, se utilizan secciones especiales como:
•
.eeprom: es una sección de datos especial que define el contenido de la EEPROM del microcontrolador. La EEPROM es independiente de la flash de programa, y por eso utiliza su propia sección. Se puede utilizar avr­objcopy para extraer esta sección y generar un archivo .hex (discutido más adelante) para programar la EEPROM.
•
.bootloader: el bootloader es una sección de código especial que los AVR pueden utilizar. Esta sección de código ocupa una porción de la memoria flash cuyo tamaño está definido por los “fuses” (fusibles) BOOTSZ, y se ejecuta al iniciar la operación del microcontrolador. Su uso principal es para cargar código en la memoria de programa del microcontrolador. Así, el bootloader toma el control inicialmente y puede leer el código de la aplicación desde una fuente externa, utilizando cualquiera de las interfaces que tenga el microcontrolador (puertos de E/S , puerto serial, SPI, USB, etc), cargarlo en la flash del micro, y transferir el control a esa aplicación recién cargada para empezar su operación normal. Esto incrementa la flexibilidad de los microcontroladores.
•
.fuse: esta sección contiene datos que definen el estado de los fusibles (“fuses”) del microcontrolador, bits especiales que definen aspectos como qué fuente de reloj utilizará el micro (externa, oscilador interno, etc) o el tamaño del bootloader. Estos bits, al igual que los bits de protección, se programan por aparte de la flash y la EEPROM.
•
.lock: contiene el valor de los bits de protección del microcontrolador. Estos bits definen qué memorias del microcontrolador pueden ser modificadas, y por quién. Por ejemplo, los bits de protección se pueden configurar para que las instrucciones de la sección de aplicación del programa (aquélla sección que no es el bootloader) no puedan realizar escrituras sobre la sección del bootloader, con el fin de evitar su corrupción.
•
.signature: es una sección que contiene el número identificador del modelo de microcontrolador.
19
La siguiente secuencia de comandos permite obtener un archivo .elf para el AT90USB162, a partir de un código fuente en C llamado avr­usb­162_Led.c:
#avr­gcc ­mmcu=at90usb162 ­Wall ­g ­O0 ­o avr­usb­162_Led.o ­c avr­
usb­162_Led.c #avr­gcc ­mmcu=at90usb162 avr­usb­162_Led.o ­o avr­usb­162_Led.elf
El archivo obtenido contiene código objeto para la arquitectura del AT90USB162, pero aún falta cargarlo en la memoria de programa del microcontrolador. Como en el microcontrolador no se cuenta con un ambiente de ejecución que pueda interpretar y cargar el .elf, éste aún debe ser traducido a una imagen adecuada para la memoria flash del dispositivo. Para esto se transforma el archivo .elf en un archivo .hex, un archivo de texto compuesto de líneas de dígitos hexadecimales, que define cuál va a ser el contenido de la memoria flash del dispositivo. El archivo .hex, en el caso de los AVR, sigue el formato hexadecimal de Intel, llamado ihex. Para generar este archivo .hex se hace uso de avr­objcopy, una de las herramientas incluidas en avr­binutils, y el archivo .elf que contiene el ejecutable de interés:
#avr­objcopy ­O ihex ­R .eeprom ­R .fuse ­R .lock ­R .signature avr­
usb­162_Led.elf avr­usb­162_Led.hex El programa avr­objcopy producirá una archivo hexadecimal en formato Intel (­O ihex), que se llamará avr­usb­162_Led.hex, a partir del archivo ejecutable avr­usb­162_Led.elf. La opción ­R argumento es para remover la sección argumento, es decir, evita que se incluya, en el archivo de salida (.hex), la sección llamada arg del archivo de entrada (.elf). En este caso se remueven varias secciones que no tienen por qué escribirse en la flash de programa del microcontrolador: la sección de .eeprom, los “fuses” y los “locks” son independientes de ella, y la sección .signature no es necesaria pues no contiene instrucciones.
Programación del dispositivo: transferencia de la aplicación al microcontrolador
Una vez obtenido el archivo .hex con el programa, falta aún transferir sus datos a la memoria flash del microcontrolador, utilizando software y hardware para programación. Para los AVR hay bastantes opciones de hardware para programación, desde programadores comerciales como el STK500 o al JTAG ICE mkII, diseñados por Atmel, hasta soluciones de bajo costo basadas en interfaces con el 20
puerto paralelo o serial.
En cuanto a software, las opciones típicas en ambientes Linux son uisp y avrdude, siendo este último una de las opciones más usadas actualmente. Sin embargo, los dispositivos de la familia AT90USB son relativamente nuevos (salieron al mercado en el 2008) por lo que aún no existe soporte en uisp o avrdude para ellos, si no es en conjunción con alguno de los programadores de hardware más avanzados (y costosos) como el JTAGICE mkII o el AVRISP mkII. Por suerte, Atmel brinda una alternativa de bajo costo y poco hardware: los dispositivos AT90USB162 vienen preprogramados con un bootloader que pone al dispositivo en modo de programación. Para iniciar la ejecución del bootloader, se debe mantener el pin HWB en 0 mientras se resetea el dispositivo (poniendo un 0 en su pin de RESET) . Cuando se entra en el modo de programación, el código del bootloader activa y configura el hardware de USB del microcontrolador, de modo que si, estando en este modo, se conecta el microcontrolador a un puerto USB del host para desarrollo, el dispositivo será visto como un periférico.
En el lado del equipo para desarrollo, una aplicación provista por el fabricante, llamado FLIP, “Flexible In­System Programmer”, permite instalar el manejador adecuado y así se puede reconocer el dispositivo como un microcontrolador programable, para finalmente escribir archivos .hex a su flash o a su EEPROM.
Utilizando el bootloader precargado, y FLIP, es posible programar el AT90USB162 con solo un cable USB entre él y el host, sin ningún hardware adicional. El código del bootloader es capaz de utilizar el hardware USB del microcontrolador. Una vez descargados los archivos .hex en el microcontrolador, basta con resetearlo, con su pin de RESET o con un comando de FLIP, para empezar a ejecutar la aplicación. (Se debe tener cuidado de no mantener en bajo el pin HWB al resetear, para evitar que se ejecute el bootloader y se entre al modo de programación nuevamente).
2.2.2 Desarrollo para la GadgetPC
El desarrollo de aplicaciones para la GadgetPC utiliza, convenientemente, un kit de herramientas muy similar al que se utiliza para la familia AVR, pero en sus versiones para la arquitectura ARM. Así, se cuenta con versiones para ARM de binutils, gcc y la librería estándar de C. Efectivamente, gracias a que binutils y gcc son proyectos abiertos y libres, y a que la librería de C es un estándar, ha sido posible utilizarlos para muchas arquitecturas, siendo ARM y AVR sólo un par: estas 21
herramientas también se han utilizado exitosamente para las arquitecturas Intel (IA32 e IA64), Motorola 68K, PowerPC, Sparc, MIPS, entre muchas otras.
Figura 2­3. El logo del Proyecto GNU. Los kits de herramientas para AVR, ARM y muchas otras arquitecturas se basan en buena parte en herramientas de GNU.
Por otro lado, en la GadgetPC se tiene un sistema operativo (Linux) dentro del cual se ejecutan los programas, es decir, que éstos no existen de manera independiente, sino que deben ejecutarse dentro de un ambiente preexistente. Así, al igual que en una computadora de escritorio típica, una aplicación para la GadgetPC no es (en general) simplemente código para el procesador o microcontrolador ARM que conforma su núcleo, sino que la aplicación interactúa con el sistema mediante una interfaz compuesta ya sea por un conjunto bien definido de llamadas directas al sistema (“system calls”) o mediante funciones de librería (muchas veces de alguna librería estándar para el lenguaje) que en algún punto recurren a servicios del sistema, internamente.
Un caso típico es la entrada y salida: en C, en aplicaciones de escritorio, normalmente se utilizan las funciones de stdio.h (perteneciente a la librería estándar) como printf y scanf, para estas tareas. Típicamente los ejecutables no contienen las instrucciones máquina para estas funciones, aún después de ser compilados, sino que, en el momento de ser ejecutados, el Sistema los enlaza dinámicamente con sus librerías para tiempo de ejecución. Este mecanismo brinda varias ventajas:
1. Reduce drásticamente el tamaño de las aplicaciones.
2. Permite que el S.O. mantenga el control de recursos, como E/S y memoria.
3. Algunas funciones pueden ser manejadas mejor en tiempo de ejecución en comparación con su manejo en tiempo de compilación, como el manejo de excepciones.
Pero, para poder implementar el enlazamiento dinámico se requiere entonces, que el sistema operativo de la plataforma que hospeda a la aplicación brinde dos recursos:
1. Las librerías para tiempo de ejecución utilizadas por la aplicación. En una computadora con Linux es posible encontrar gran cantidad de estas librerías, por lo general reciben nombres que 22
empiezan en lib y terminan en .so, terminación que indica que contienen código objeto compartido (“shared object”): código que puede enlazarse y ser utilizado por otros programas. En directorios como /lib y /usr/lib se pueden hallar múltiples ejemplos.
2. El enlazador (“linker”) dinámico. Éste es el programa que, cuando se lanza la aplicación, enlaza el código de ésta con el código de las librerías para tiempo de ejecución presentes en el sistema, y permite finalmente ejecutar la aplicación. Esta pieza tan importante de software, muchas veces pasada por alto, normalmente también se puede encontrar en /lib. Por ejemplo, en la PC en que se editó el presente trabajo (Ubuntu 9.10, Kernel 2.16.31, CPU AMD64), el enlazador está en /lib/ld­2.10.1.so (aunque existe además un vínculo simbólico llamado /lib/ld­linux.so.2, que apunta a /lib/ld­2.10.1.so)
En conjunto, se le llama sysroot al sistema de archivos que contiene las librerías para tiempo de ejecución y el enlazador dinámico (y otro programas y archivos auxiliares). El sysroot se debe montar en algún punto del sistema de archivos del sistema objetivo. La raíz del sistema de archivos (“/”) es una posibilidad comúnmente usada, aunque pueden usarse otros puntos de montaje. Cuando se compila una aplicación en el host para desarrollo, las opciones de compilación deben indicar donde está el sysroot, las librerías compartidas y el enlazador, en el sistema objetivo. De éste modo, el sistema objetivo podrá cargar la aplicación correctamente cuando se intente ejecutarla en él.
Otra posibilidad es no especificar el sysroot en tiempo de compilación, sino invocar directa y explícitamente (en el sistema objetivo) al enlazador dinámico adecuado, indicándole el ejecutable que debe cargar y el directorio donde están las librerías compartidas necesarias.
Por defecto, si no se especifica un sysroot de alguna manera en tiempo de compilación (explícitamente o implícitamente con alguna opción al invocarlo), el compilador supondrá un sysroot de acuerdo a sus opciones de configuración, lo que muchas veces supone un sysroot radicado en /.
Soucery G++ Lite y la GadgetPC
Soucery G++ Lite es un paquete que contiene la cadena de herramientas GNU, compilada para un host de desarrollo basado en IA32 y sistemas objetivo basados en ARM y Linux. El paquete incluye las versiones para ARM/Linux de:
•
Binutils
•
Gcc
23
•
Glibc: la librería estándar de C de GNU
•
G++: el compilador para C++ de GNU
•
libstdc++: la librería para tiempo de ejecución estándar para C++ de GNU
•
gdb, gdbserver: el “debugger”(depurador) de GNU, principalmente utilizado para debugging de aplicaciones (no del kernel). El gdbserver permite debugging remoto.
•
CodeSourcery Debug Sprite para ARM: un programa desarrollado por Code Sourcery, para depurar sistemas basados en ARM que no tienen Sistema Operativo, o para depurar al Sistema Operativo (el kernel de Linux) mismo. Sourcery G++ Lite, un producto (no comercial) de la empresa Code Sourcery, es el paquete de herramientas básico sugerido por el fabricante de GadgetPC (BiPOM Electronics). Por su lado, el GadgetPC ya trae instalado un sysroot adecuado para correr aplicaciones compiladas con las configuraciones por defecto de las herramientas de Sourcery G++ Lite, por lo que no es necesario utilizar opciones especiales de compilación.
Así, un programa simple como “Hola Mundo” en C, compatible con la GadgetPC, se puede compilar en el host para desarrollo con solo invocar al gcc para ARM incluído en Sourcery G++ Lite:
juan@juan­laptop:~/arm­tests$ ls hello* hello.c juan@juan­laptop:~/arm­tests$ cat hello.c #include <stdio.h> int main(){ printf("Hello World!"); return 0;
} juan@juan­laptop:~/arm­tests$ arm­none­linux­gnueabi­gcc ­o hello hello.c juan@juan­laptop:~/arm­tests$ ls hello* hello hello.c juan@juan­laptop:~/arm­tests$ file hello hello: ELF 32­bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.16, not stripped 24
El último comando muestra que el archivo obtenido es un ejecutable para ARM.
Transferencia del programa al dispositivo
El archivo objeto obtenido al compilar sí es directamente ejecutable en la GadgetPC (suponiendo que el sysroot está presente, como debería ser). Esto es diferente del caso para el AT90USB162, en el que el ejecutable debía aún ser limpiado de secciones innecesarias y transformado a formato ihex para “quemarlo” en la memoria del microcontrolador. En el caso de la GadgetPC basta con transferir el archivo a su almacenamiento secundario y ejecutarlo como se haría con cualquier otro programa que se pueda ejecutar desde la consola.
Las opciones son varias, algunas de ellas son:
1. Copiar el archivo a una llave USB y conectarla a uno de los puertos de la GadgetPC, montarla en donde se desee, y ejecutar el programa.
2. Accesar remotamente a la GPC mediante ssh (La GPC puede ser un servidor SSH) y copiar el archivo a la GPC con scp.
3. Desde la consola de la GPC, obtener el archivo de algún servidor FTP externo, mediante el comando de busybox, “ftpget”. Busybox es una aplicación multifuncional que contiene muchos de los programas de utilería básicos de los sistemas Linux en un solo ejecutable, para ahorrar espacio. Es común en Linux embebido y forma parte de Arm9­Linux.
4. Configurar la GPC como un servidor FTP y subir el archivo a ella, utilizando un cliente FTP en el host para desarrollo.
La flexibilidad de la GadgetPC resulta evidente. Debe considerarse que, además de contar con la posibilidad de escribir aplicaciones, un programador también tiene a su disposición el shell del sistema para hacer scripts. Este shell es compatible con sh, y es una de las aplicaciones contenidas en el busybox presente en el ARM9­Linux para GadgetPC. Si se usa Debian, se dispone de varias opciones para shells (sh, bash, ksh, …).
25
2.3 Fundamentos de la tecnología USB
El “Universal Serial Bus” (en español, Bus Serial Universal :) o USB, es una tecnología de comunicación estandarizada, orientada a comunicaciones entre una computadora central (tradicionalmente una PC) y sus periféricos. Una de las principales metas de USB fue simplificar la conexión, instalación y configuración de periféricos en computadoras personales. Antes de USB, habían surgido numerosas interfaces cuyo problema principal era que habían sido concebidas para ciertas funciones en particular, lo que las hacía rígidas o poco útiles para otras aplicaciones. Por ejemplo, los puertos PS/2 eran para teclados y ratones, el puerto paralelo más que todo para impresoras, el puerto serial para teclados, ratones o módems, el puerto “de juegos” para “joysticks” e instrumentos musicales en algunos casos, y así para muchas otras interfaces más. La necesidad de una mejor solución era apremiante.
USB toma un enfoque más moderno para las comunicaciones, separando más limpiamente la interfaz (física o lógica) de la aplicación particular. USB fue diseñado con un modelo en capas, como los utilizados para el estudio de las redes de computadoras (e.g. Modelo OSI, Modelo TCP/IP).
USB se adapta a distintos requerimientos de ancho de banda (los bits/segundo disponibles para un dispositivo conectado) y garantías de latencia (el tiempo máximo que se debe esperar entre transmisiones) y brinda control de flujo y corrección de errores en hardware, si se desea. Por lo tanto es útil en un rango amplio de aplicaciones, como dispositivos para interfaz humana (ratones, teclados), audio y vídeo en tiempo real, almacenamiento masivo, comunicaciones (módems), entre muchas otras. Adicionalmente, la velocidad (en bits por segundo, bps) de USB ha venido subiendo sorprendentemente en los últimos años: el nuevo estándar USB 3.0 define la “Super­Velocidad” (“Super­Speed”, 4.8 Gbps), un gran salto sobre la máxima velocidad de USB 2.0, la “high­speed”, de 480Mbps.
USB permite tener mucho periféricos en un mismo bus, utilizando hubs USB. Asimismo USB define clases estándar de dispositivos, una clase tiene una interfaz lógica estandarizada. Muchos sistemas operativos ya tienen manejadores (drivers) para comunicarse con dispositivos de las clases estándar, por lo que es fácil desarrollar un dispositivo USB que utilice la interfaz de la clase para implementar una nueva función, implementando su funcionalidad específica sobre la funcionalidad básica de cierta clase. Por todas estas razones, USB es una tecnología de gran relevancia actualmente y seguramente 26
lo será en los próximos años. En esta sección se explicará más a fondo su terminología y funcionamiento.
2.3.1 Estándares USB
USB es promovido, desarrollado y estandarizado por el Foro de Implementadores (“USB Implementer's Forum”), un consorcio de empresas del área de tecnologías de la información que incluye a compañías como HP, Intel, Microsoft, Phillips, NEC y otras.
La versión vigente del estándar USB es USB 2.0, que fue precedido por USB 1.0 y USB 1.1. USB 2.0 está ampliamente difundido en los equipos de escritorio, y soporta tres velocidades de transferencia: velocidad baja (“low speed”, 1,5 Mbps), velocidad completa (“full speed”, 12 Mbps) y velocidad alta (“hi­speed”, 480 Mbps). USB 3.0 es una extensión del estándar pero no viene a invalidar a USB 2.0, sino que introduce modificaciones al hardware y al protocolo para soportar, entre otras cosas, mayores tasas de transferencia (hasta 4.8 Gbps, con la “Super­speed”) así como una nueva modalidad de USB llamada USB “On­the­go” (USB “para llevar”), que le permite a un dispositivo USB mostrar capacidades limitadas de host usb, para conectarse directamente a otros dispositivos. En el USB tradicional (2.0) un equipo dado es siempre un anfitrión(“host”) o un dispositivo (“device”) y sus roles son estáticos, y no está contemplada la comunicación entre dos dispositivos o entre dos hosts, solamente entre un host y los dispositivos conectados a su bus.
En el presente proyecto, tanto la GadgetPC como el AT90USB162 cuentan con interfaces USB 2.0 cuya velocidad máxima es “full­speed”. Por lo tanto, lo que se discute de aquí en adelante no considera las nuevas capacidades y conceptos introducidos por USB 3.0. Además, debe tenerse en cuenta que la presentación del tema es, necesariamente, abreviada, pues USB es un tema amplio. En capítulos posteriores se profundizará más en aspectos conceptuales y de implementación , conforme lo ameriten los objetivos específicos de cada parte.
Figura 2­4. Los logos oficiales del USBIF que certifican compatibilidad con USB 2.0. El logo de la izquierda es para productos que usan velocidad “low” o “full”, el de la derecha es para productos que implementan alta velocidad.
27
2.3.2 Componentes y topología USB
El host USB
Un bus USB cuenta con un único host. El host es el ente central que se encarga de:
1. Detectar nuevos dispositivos: el host detecta (con ayuda de los hubs) los nuevos dispositivos que se conectan al bus. En un proceso denominado enumeración el host le asigna una dirección única al nuevo dispositivo y lo incluye en su vista de la topología del bus. Asimismo cuando un dispositivo se desconecta el host debe detectar esta situación, liberar los recursos del bus, y notificar al S.O. como corresponda.
2. Manejar el flujo de datos y de información de control en el bus: el host es quien divide el tiempo del bus entre los distintos dispositivos y el que determina quién y cuándo puede transmitir en el bus. El host inicia siempre las transferencias, sean de control o de datos.
3. Suministrar energía a los dispositivos del bus: un dispositivo puede extraer una cantidad limitada de corriente (500 mA máximo) de las líneas de alimentación del bus, que tienen un voltaje nominal de 5 V. Los dispositivos pueden tener otros suministros propios de energía, independientes del bus.
4. Recibir solicitudes del sistema para intercambiar datos o comandos con los dispositivos conectados. Estas solicitudes podrían provenir del driver específico para un dispositivo, o ser una solicitud estándar para la clase de dispositivo.
Dispositivos USB (“devices”)
Dado un host que controle el bus, pueden haber varios dispositivos conectados al bus. Un dispositivo puede ser de uno de dos tipos:
1. un hub: es un tipo especial de dispositivo que multiplica el número de puertos disponibles. Todo host usb tiene al menos un hub: el hub raíz (“root hub”). Un hub tiene un puerto “hacia arriba” que va hacia el host y uno o más puertos “hacia abajo” que conectan con funciones o con otros hub.
2. Una función: un dispositivo que provee alguna capacidad o funcionalidad al sistema, por ejemplo un mouse o unos parlantes. Las funciones se conectan a un puerto de un hub para 28
conectarse al bus.
Desde el punto de vista físico, un bus USB tiene una topología de estrella escalonada, donde los hubs y funciones están en un nivel dado (del 2 al 7) y el controlador usb del host, junto con el hub raíz, están en el nivel 1. Sin embargo, realmente la presencia de los hubs y los otros dispositivos en el bus son transparentes a los dispositivos y al software en el host, por lo que realmente a la hora de desarrollar aplicaciones se puede pensar que existe un canal dedicado entre el host y cada una de las funciones. La figura 2­5 muestra ambas vistas del USB:
Figura 2­5. Vista física y lógica del USB
Las tareas básicas de un dispositivo son:
1. Detectar las comunicaciones en el bus que van dirigidas a él (observando la dirección a la que van dirigidas las tramas que se ven en el bus). El dispositivo debe estar preparado para almacenar información recibida, enviar información solicitada, o un código de estatus que indique su situación, en el momento en que observa una transmisión dirigida a su dirección. La dirección de un dispositivo es asignada por el host durante la enumeración. Antes de que ocurra la enumeración, justo después de conectarse (y después de recibir el reseteo inicial proveniente del bus) un dispositivo usa la dirección por defecto que establece el estándar.
2. Recibir y responder un conjunto de solicitudes estándar genéricas que el host puede hacerle, por ejemplo para solicitarle información al dispositivo sobre su clase y sus capacidades, su estado actual, para escoger una configuración, o para resetear el dispositivo. Estas solicitudes son utilizados tanto en el proceso de conexión como en la operación normal del bus. Un dispositivo USB debe ser capaz de proveer cierta información básica que lo identifique ante el sistema 29
(discutida más adelante).
3. Recibir y ejecutar solicitudes provenientes del software en el host, en conjunto con drivers adecuados instalados en el host, para cumplir la función específica del dispositivo.
2.3.3 La comunicación entre un host y un dispositivo
La figura 2­6. muestra una visión general de la comunicación USB. En USB es posible pensar en un protocolo de varias capas. La capa más baja es la física, donde está el cable USB y la transmisión por medios electrónicos. Sobre esa capa se encuentra la capa de dispositivos lógicos, en la que el software de USB del Sistema (en el host) puede comunicarse de manera genérica con los dispositivos para labores administrativas y de configuración y control. Finalmente la capa da función (análoga a la capa de aplicación en OSI y TCP/IP) permite que un software cliente, como una aplicación, acceda a las funcionalidades específicas de un dispositivo, de acuerdo con la interfaz que éste provea.
Figura 2­6. El modelo de comunicación entre un host y un dispositivo en USB.
Basado en el modelo de la Especificación USB 2.0
30
Endpoints (Extremos o terminaciones)
En USB, todo el tráfico de datos ocurre entre software en el host y endpoints (extremos o terminaciones) en los dispositivos. Un endpoint es básicamente un buffer en un dispositvo, que puede contener datos listos para ser enviados o puede recibir datos provenientes del host. Todos los dispositivos deben poseer al menos un endpoint: el endpoint 0, o endpoint de control, que se utiliza para comunicaciones de control como las usadas al agregar un dispositivo al bus para asignarle una dirección al dispositivo, configurarlo o resetearlo, entre otras. Cuando un dispositivo se conecta por primera vez al bus, el endpoint 0 es el único que está activo por defecto y tiene un conjunto estándar de características.
Un endpoint cuenta con varias características asociadas que lo describen:
1. Un número que lo identifica, del 1 al 15 en dispositivos con “full­speed” o “hi­speed”.
2. Un sentido (principal) o direccionalidad para el flujo de datos en él: IN (hacia adentro) o OUT (hacia afuera). Estos sentidos toman como referencia al host: un endpoint IN es para enviar datos que entran al host, uno OUT es para recibir datos que salen del host. En conjunto, un número de endpoint y un sentido, conforman una dirección de endpoint (“endpoint address”), que identifica al endpoint dentro de ese dispositivo. Usando una dirección de dispositivo (asignada por el host durante la enumeración) en conjunto con una dirección de endpoint (de las que el dispositivo tenga definidas y configuradas en un momento dado), se puede identificar exactamente de donde proviene o a quien va dirigida una trama en el bus.
3. Un requerimiento de latencia máxima.
4. Un requerimiento de ancho de banda mínimo.
5. Requerimientos de control de errores.
6. Un tamaño de paquete máximo que puede manejar.
7. El tipo de transferencia utilizada por el endpoint. USB soporta cuatro modos o tipos de transferencias: control, isócronas, en masa (“bulk”), y por interrupción.
1. De control: orientadas a labores administrativas del host sobre los dispositivos, son principalmente intercambios solicitud/respuesta iniciados por el host para llevar a cabo comandos de configuración sobre el dispositivo y/o leer su estado actual. Son confiables 31
(en el sentido de que hay detección y corrección de errores automática). El endpoint 0 siempre usa transferencias de control y rara vez se utilizan otros endpoints de control en un dispositivo. Es posible también utilizar tramas de control para transmitir datos específicos a la implementación de dispositivo que se está realizando.
2. Isócronas: son transferencias en las que los datos se deben producir y consumir en tiempo real. Son periódicas y continuas. En una transmisión isócrona, la información sobre temporización está implícita en la tasa con que se envían y reciben datos. Debido a los requerimientos de temporización, las transferencias iśocronas no implementan detección de errores ni corrección automática. Son apropiadas para aplicaciones de audio y vídeo como micrófonos, parlantes, cámaras web y telefonía.
3. “Bulk transfers” (literalmente, en masa o en volumen): son transmisiones de datos que, aunque pueden ser grandes, realmente no tienen necesidades de latencia o ancho de banda apremiantes. Son confiables. En un bus con poca actividad, estas transferencias son las más rápidas, pero en un bus congestionado o con muchos periféricos, pueden esperar. Son utilizadas comúnmente con dispositivos de almacenamiento masivo, escáneres, impresoras, o cámaras fotográficas.
4. De interrupción: son transmisiones que tienen una latencia acotada y un ancho de banda mínimo garantizado, pero son confiables. Son frecuentemente utilizadas para dispositivos de interfaz humana como teclados, donde hay un tiempo de respuesta aceptable máximo y los datos deben ser entregados sin errores.
“Pipes” (tuberías)
Los pipes o tuberías son asociaciones abstractas entre el endpoint de un dispositivo y software en el host. Representan un canal para transferir datos entre software en el host (que utiliza un buffer en memoria) y un endpoint de un dispositivo. El software del host debe crear pipes, para comunicarse con cada endpoint que desee. Cada pipe tiene asociado ciertos recursos de bus, y un tipo de transferencia determinado al momento de crearlo, que refleja las características del endpoint al que se conecta. El endpoint 0 tiene asociado siempre, desde que el dispositivo inicia su operación, un pipe conocido como el pipe de control por defecto, sobre el cual se hacen las transferencias de control. Este pipe permite configurar el dispositivo, e incluso habilitar nuevos endpoints y pipes si el dispositivo los 32
soporta.
Asociado con el endpoint 0 y el pipe por defecto, existe un conjunto de información que describe al dispositivo USB y que el host puede obtener. Hay cuatro categorías de información que se pueden hallar en un dispositivo:
1. Información estándar: es información cuya definición aplica para todos los dispositivos USB. Incluye campos como el número identificador del fabricante, la clase a la que pertenece el dispositivo, sus capacidades de manejo de energía, etc. Se utilizan estructuras estandarizadas para describir la información del dispositivo, las configuraciones que ofrece, las interfaces que provee, y las características de sus endpoints.
2. Información específica para la clase del dispositivo.
3. Información propia del fabricante, cuyo formato está definido por él.
4. Información sobre el estado actual del dispositivo, para control. Los estados en los que puede estar un dispositivo están determinados por el estándar USB.
Configuraciones e Interfaces
Antes de que la función de un dispositivo USB pueda ser utilizada, el dispositivo debe ser configurado para escoger esa función. El host aprende sobre las capacidades del dispositivo por los descriptores de configuración. Dependiendo del uso que se le vaya a dar al dispositivo, un número de configuración específico es seleccionado por el host mediante una solicitud “setConfiguration( )”. Por ejemplo (un caso hipotético), un mouse USB podría tener dos posibles configuraciones: una para funcionar como un mouse para diestros y otra para zurdos. Los dispositivos, al ser conectados al bus, empiezan en un estado “no configurado”. Es responsabilidad del host escoger la configuración adecuada a partir de un catálogo de una o más que el dispositivo le envía.
Dentro de una configuración particular, un dispositivo puede soportar una o más interfaces. Una interfaz es un conjunto de endpoints relacionados (con sus características asociadas) que se usan juntos para implementar una única característica o función que el dispositivo provee al host. El protocolo que se usa para comunicarse con este conjunto de endpoints, y la interpretación del contenido de cada uno dentro de la interfaz, por lo general se define en las Especificaciones de Clase USB, que son documentos oficiales del USBIF, o en la documentación propia de un fabricante.
Un host USB debe escoger una interfaz particular de una configuración, para poder usar el 33
dispositivo. Las interfaces que ofrece un dispositivo se describen en descriptores de interfaz. El host escoge qué interfaz usar dentro de la configuración.
Una vez que un dispositivo USB ha sido conectado, enumerado, y configurado, está listo para ser usado, mediante las solicitudes que la interfaz seleccionada pone a disposición del software en el host.
34
2.4 “Datacards” y su uso en Linux
Los datacards o módems inalámbricos son dispositivos que utilizan uno o más estándares para comunicación celular para brindar a un usuario una conexión a Internet. La mayoría de estos módems son capaces de funcionar con varios servicios de comunicaciones y en varias bandas de frecuencia, dependiendo de la disponibilidad de estos servicios en un momento y posición geográfica dada. Aunque se han comercializado como parte de los nuevos servicios “3G” del ICE, realmente pueden usar protocolos pertenecientes a “2G” (GSM: GPRS o EDGE), “3G” (UMTS), y “3.5G” (HSPDA). HSDPA es uno de los estándares más nuevos y el que puede lograr la mayor velocidad, y la red del ICE lo soporta.
La tabla 2­1 resume los nombres y velocidades de los principales servicios de comunicación celular. Las velocidades mostradas son velocidades máximas típicas para darse una idea de como se comparan entre sí los servicios, pero no debería esperarse siempre tener esa velocidad a la hora de usar el servicio.
Tabla 2­1 Servicios de datos sobre tecnología celular
Servicio
HSDPA
High Speed Downlink Packet Access
UMTS
Universal Mobile Telecommunications System
EDGE
Enhanced Data Rates for GSM Evolution
GPRS
General Packet Radio Service
Máxima Velocidad Máxima Velocidad de Bajada
de Subida
Comentarios
3.5G, es una evolución de UMTS.
3.6 Mb/s
384 kb/s
Servicio 3G
384 kb/s
384 kb/s
247.5 kb/s
123.7 kb/s
16 a 48 kb/s
8 a 48 kb/s
También llamado EGPRS (Enhanced GPRS), se considera 3G.
Muy limitado, se considera 2.5G por ser una expansión de GSM
Realmente, aunque los datacards sean dispositivos relativamente nuevos y soporten estándares de comunicación avanzados como HSDPA, la interfaz que presentan a la hora de instalarlos y 35
utilizarlos en una computadora de escritorio con Linux es bastante conocida y ampliamente soportada por las herramientas del sistema y varios paquetes conocidos.
Las datacards en general son reconocidas por Linux como dispositivos seriales al tener los módulos usbserial o el módulo option instalados en el kernel. Normalmente al ser conectados al sistema, son instalados automáticamente como dispositivos seriales (virtuales) en /dev/ttyUSB0, o el primer ttyUSBx que no esté ocupado.
En ocasiones los dispositivos se identifican a sí mismo primero como unidades de CD virtuales, para forzar que se ejecute su “autorun” y así instalar sus propios drivers en el host. Esto es útil en máquinas Windows pero no tanto en Linux (donde la presencia del autorun es detectada pero de todas maneras no es ejecutable). Si esto sucede y por ello no se instalan los dispositivos seriales virtuales, para algunos dispositivos una vez desmontado el CD­ROM virtual el dispositivo se reinicia y ya se identifica adecuadamente la interfaz serial. Utilizando reglas de udev se puede evitar del todo que aparezca el CD­ROM virtual, para evitar molestias o pasos extra, y que simplemente se instale el dispositivo serial de una vez.
Lo importante es que, una vez que el dispositivo está instalado como un puerto serial, se comporta como un módem serial analógico típico, que se controla mediante comandos del conjunto de comandos Hayes, llamados típicamente comandos “AT” (porque la mayoría empiezan con las letras AT) básicos y subconjuntos de comandos específicos a la comunicación sobre red celular. Estos comandos son cadenas cortas de caracteres que instruyen al módem a realizar acciones específicas. El comando “ATZ”, por ejemplo, inicializa el módem en su configuración por defecto, el comando “ATH” cuelga la llamada actual, “ATD” es para marcar un número, etc. Con un software como minicom estos comandos se pueden enviar manualmente al módem, aunque lo normal es que algún otro programa (como pppd, discutido más adelante) se encargue del control del módem.
Para establecer una conexión a Internet, hay que utilizar el protocolo Point­to­Point (PPP), que es básicamente un protocolo (de uso muy difundido) para encapsular tráfico IP (o de otros protocolos de capa de red, como IPX) sobre enlaces seriales como el que se establece, mediante el módem, entre la computadora y el proveedor del servicio.
En Linux, el daemon pppd es el programa principal encargado de implementar el protocolo PPP para establecer y mantener conexiones a Internet sobre enlaces seriales como los ofrecidos por 36
módems “dialup” (como en este caso), módems ADSL, u otros. pppconfig es un programa auxiliar a pppd, interactivo, para ayudar a los usuarios a crear la configuración y los scripts auxiliares que le permiten a pppd establecer, mantener y terminar conexiones. pon y poff son aplicaciones asociadas a pppd para iniciar y terminar (respectivamente) conexiones previamente configuradas, creando o eliminando una interfaz de red bajo el nombre de pppX (donde X es 0 si solo se usa una interfaz ppp). La interfaz de red ppp0, una vez activa, se puede examinar con ifconfig para verificar su estado, y se puede usar para acceder a Internet. Realmente pppd es la aplicación central y las otras son principalmente formas convenientes de trabajar con él.
Algunas aplicaciones llamadas “dialers”, como wvdial pueden facilitar el trabajo de instalación y uso de interfaces ppp sobre módems seriales ya que automatizan mucho del proceso de configuración al contener alguna “inteligencia” extra, si se le compara con usar pppd directamente, que es de bastante bajo nivel pues permite control total sobre el protocolo, cosa que rara vez es necesaria.
En distribuciones como Ubuntu y Debian para PCs, el NetworkManager permite instalar las datacards mediante un asistente gráfico muy intuitivo y fácil de usar, pero que es de poca utilidad si no se tiene una GUI.
CAPÍTULO 3: Comunicación sobre USB con el AT90USB162, LUFA y Linux.
El hardware USB de los microcontroladores Atmel es complejo de utilizar directamente pues USB es un protocolo amplio, lo que se se refleja en una gran cantidad de registros y lógica para su uso. Desarrollar firmware propio (en el dispositivo) y manejadores (“drivers”) correspondientes (en el host), desde cero, es un proceso no trivial.
La opción más razonable consiste en usar recursos probados y listos, como librerías para AVR y software del sistema que ya esté presente. Así, los componentes principales que se utilizaron para lograr comunicación sobre USB son:
1. El marco de trabajo (“framework”) LUFA.
2. Los manejadores básicos incluidos en el kernel de Linux para manejar dispositivos USB de clases estándar.
3. La interfaz de programación para puertos seriales de Linux en C, basada principalmente en termios.h y unistd.h.
3.1 LUFA
LUFA (Lighweight USB Framework for AVR) es un framework de código abierto desarrollado para los microcontroladores AVR con hardware USB. Fue escrito por Dean Camera, un desarrollador independiente. Este framework contiene la librería que implementa la pila del protocolo USB, y varias demos (proyectos demostrativos) básicos que se pueden utilizar como punto de partida para implementar nuevos sistemas.
LUFA incluye desde funciones de bajo nivel que se usan para implementar el protocolo USB sobre el hardware de los microcontroladores, hasta implementaciones de drivers (manejadores) (terminología de LUFA) para clases estándar, como la clase HID (“Human Interface Device”) usada comúnmente para mouses y teclados, la clase CDC (“Communications Device Class”) muy usada por dispositivos para telecomunicaciones en general, o la clase Audio, entre otras.
En el contexto de LUFA se denomina “driver” (manejador) a una interfaz de programación y su implementación subyacente, usada en el firmware, para lograr cierta funcionalidad específica o 37
38
implementar una clase de dispositivo USB. No debe confundirse con el manejador que existe en el host para manejar la comunicación con el dispositivo. De hecho, LUFA no contiene manejadores para instalar en el host ni facilidades para desarrollarlos, pero esto no es realmente necesario: en la mayoría de los sistemas operativos modernos ya existen drivers genéricos para los dispositivos de las clases estándar más comunes.
Para este proyecto se partió de la demo VirtualSerial.c. Esta demo realiza la implementación genérica de un dispositivo que presenta una interfaz serial virtual, utilizando la clase CDC y asociadas. La clase CDC es la clase general para equipos de comunicaciones. Su objetivo es brindar una plataforma común sobre la que se puedan realizar comunicaciones en distintos estándares y/o protocolos, como ISDN, Ethernet, módems PSTN, telefonía, etc.
Para implementar un puerto serial virtual se utilizan dos interfaces trabajando en conjunto:
1. Una interfaz de la clase ACM (“Abstract Control model”, una subclase de la CDC). Esta subclase está orientada al control y configuración de la comunicación serial con dispositivos, usualmente, pero no exclusivamente, módems PSTN. Por ejemplo, las solicitudes estándar (“requests”) SendEncapsulatedCommand y GetEncapsulatedResponse se pueden utilizar para intercambiar comandos con un módem o algún equipo genérico. Estos comandos podrían ser comandos “AT” como los que usan la mayoría de los módems para realizar llamadas y configurar la conexión. Otro ejemplo importante es la solicitud SetLineCoding, que permite al host especificar los parámetros de la conexión serial(virtual): baud­rate, bits de parada, bits de paridad y bits de datos.
2. Una interfaz de la clase Data Class: esta es una interfaz bastante genérica que básicamente representa un canal para enviar y recibir bytes de datos. Se puede usar para datos en bruto (“raw”), no asociados a ningún protocolo de comunicación en particular. En los módems viejos, además, los comandos se envían como secuencias especiales de caracteres a través de esta interfaz (ya que originalmente, con el puerto serial “real”, solo había un canal), por lo que datos y comandos comparten el canal. En la aplicación de puerto serial virtual, esta es la interfaz principal para envío y recepción de datos.
La estructura de la aplicación VirtualSerial.c básica se presenta en la figura 3.1. En esta figura se esquematizan los archivos involucrados y sus dependencias (el árbol de “#includes”). Los 39
archivos .c (si existen) tienen nombres correspondientes y se ubican en los mismos directorios que sus .h asociados, pero la interfaz documentada está definida por los .h: los .c contienen las implementaciones de las funciones, pero no son directamente de interés para un usuario de la librería, aunque sí son de gran ayuda si se desea estudiar a fondo el funcionamiento interno de ésta.
En la aplicación básica se pueden identificar cuatro grupos de archivos:
1. El grupo que implementa el protocolo USB estándar básico para un dispositivo. La mayor parte de los archivos son para este fin, en la figura son los que están en color negro. Las labores básicas de inicialización y administración de la interfaz USB, como el manejo de las solicitudes y descriptores estándar para el proceso de enumeración, configuración y control por parte del host, son manejadas por este conjunto.
2. El que implementa la clase CDC de alto nivel. Este grupo se marca encerrándolo con una línea punteada azul. Este conjunto agrega la definición de los descriptores de configuración propios de CDC y subclases que permiten al host identificar la función del dispositivo, además de una interfaz de programación conveniente que está a disposición de la aplicación en el microcontrolador para enviar y recibir datos, y cuyas partes relevantes se discuten brevemente más adelante.
3. Los manejadores para la tarjeta de desarrollo, para manejar LEDs y botones (LEDs.h, Joystick.h), representados en azul. Estos deben ser escritos por los usuarios de la librería (si se desea) respetando una interfaz especificada por LUFA, y se usan por ejemplo para desplegar el estado del dispositivo USB o leer botones. No son parte de USB.
4. Librerías de utilería de avrlibc, para distintos fines, representadas en gris en la figura.
Realmente aunque la librería de LUFA sea grande (y eso que solo se está usando en modo “device”, pues hay dispositivos AVR que pueden ser hosts también), simplifica el trabajo del programador en gran medida. Si se usa el manejador de alto nivel para CDC, como se ha hecho en este proyecto, entonces las tareas por las que el programador debe responsabilizarse consisten en:
1. Crear una variable de tipo USB_ClassInfo_CDC_Device_t usada para almacenar los parámetros y el estado de una instancia de interfaz CDC. Al momento de crear la variable se establecen parámetros como cuáles son los endpoints del microcontrolador asociados a esta interfaz y sus tamaños. El estado de la interfaz consta de dos estructuras de información: el 40
estado de las líneas de control (virtuales, que representan las líneas reales de un puerto serial: CTS, RTS, DTR, DCD, DSR, etc) y la configuración de parámetros de puerto serial: baud­rate, paridad, bits de datos y codificación de los caracteres.
2. Definir y llenar el descriptor de configuración que identifica al dispositivo y permite al host determinar sus capacidades y configurarlo para usarlo como un dispositivo CDC/ACM.
3. Llamar al inicio de la ejecución a la función USB_Init(), que inicializa el hardware USB del microcontrolador e inicia el proceso de conexión con el host.
4. Invocar frecuentemente, por ejemplo al final del lazo principal del programa, a la función CDC_Device_USBTask( USB_ClassInfo_CDC_Device_t* ), que
realiza labores administrativas generales sobre la interfaz como liberar endpoints OUT (de host a device) cuyos datos ya han sido leídos por la aplicación del microcontrolador, para que puedan seguir recibiendo nuevos datos, entre otras.
5. Invocar frecuentemente (en este caso se define un lapso máximo de 30ms entre invocaciones) a la función USB_USBTask(), que realiza labores administrativas generales de USB, básicamente, recibir y procesar solicitudes (“requests”) que vienen en paquetes SETUP (los paquetes para control de USB) desde el host, a través del pipe de control (que desemboca en el endpoint 0 en el dispositivo).
6. Finalmente (y esta es la razón de ser de todo el firmware para USB y CDC discutido hasta el momento), el programador deberá hacer uso de la API expuesta por el manejador para dispositivo CDC de LUFA, para intercambiar datos con el host. Esta API se discute a continuación.
41
42
3.1.1 Funciones relevantes del manejador de LUFA para dispositivo CDC
El manejador de alto nivel pone a disposición del programador funciones convenientes para enviar y recibir datos. Estas funciones se declaran en LUFA/Drivers/USB/HighLevel/Device/CDC.h. Las más importantes para la presente aplicación son: uint16_t CDC_Device_BytesReceived(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo);
Devuelve el número de caracteres nuevos que han sido recibidos en la interfaz y están esperando ser leídos por la aplicación. El argumento CDCInterfaceInfo, en esta y otras funciones de esta API, es una variable del tipo USB_ClassInfo_CDC_Device_t que se discutió antes. Realmente en la aplicación de puerto serial virtual solo se usa una instancia de clase CDC, por lo que el argumento siempre será la misma variable global, pero la librería es más general y permite varias instancias independientes, por lo que es necesario especificarle una como parámetro.
uint8_t CDC_Device_ReceiveByte(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo);
Devuelve el siguiente byte del flujo serial de bytes, o 0 si no hay datos por leer. Como 0 podría confundirse con un dato válido, antes se debe invocar a CDC_Device_BytesReceived para asegurarse de que realmente hay algo qué leer.
uint8_t CDC_Device_SendByte(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, const uint8_t Data);
Recibe un byte para enviarlo por la interfaz dada. Devuelve un código que permite determinar si se pudo enviar correctamente o hubo un error que lo impidió, por ejemplo que el dispositivo no está conectado al bus o el endpoint por el que se enviaría está detenido (“stalled”).
uint8_t CDC_Device_SendString(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, char* const Data, const uint16_t Length);
43
Similar a SendByte() pero para cadenas de caracteres. Se especifica una interfaz, la dirección del primer carácter, y la cantidad de caracteres por enviar. También devuelve un código de error/éxito.
void CDC_Device_CreateStream(USB_ClassInfo_CDC_Device_t* CDCInterfaceInfo, FILE* Stream)
Incrementando aún más la abstracción, la librería ofrece la posibilidad de crear un “stream” estándar (de C) a partir de la interfaz. El “archivo” creado se puede usar con funciones de <stdio.h> de avrlibc, como fgetc, fputc, fputs, entre otras.
Debe notarse que la librería evita las funciones que leen cadenas “completas” (delimitadas ya sea con un terminador específico como 0 o por una cuenta de caracteres dada) como fgets o la inexistente CDC_Device_GetString() porque en muchos casos, si las lecturas han de ser no­
bloqueantes, esto llevaría a que en algún punto durante la recepción, el endpoint no estuviera listo para ser leído, lo que causaría la cancelación de la transferencia y la obtención de una cadena no realmente completa. Si lo requiere, la aplicación debe implementar mecanismos propios para obtener líneas o cadenas completas a partir de las funciones básicas ofrecidas, esta labor no es muy compleja. Así pues, el uso del framework LUFA facilita enormemente el desarrollo de aplicaciones basadas en USB con AVR. Utilizando la API que pone a disposición el manejador CDC, se obtiene una funcionalidad lo suficientemente general ­y a la vez sencilla­ para implementar una aplicación final arbitraria más especializada. Es sobre esta base que se construye la aplicación para la unidad de adquisición de datos y actuación que es uno de los objetivos del presente proyecto.
CAPÍTULO 4: Implementación del hardware y firmware del sistema
El sistema diseñado en este proyecto es una aplicación distribuida puesto que tiene componentes que se ejecutan en distintas plataformas y entre los cuales debe haber comunicación y coordinación. A grandes rasgos, existen cuatro componentes de software y firmware que fueron desarrollados como parte de la estación de medición y actuación.
1. La aplicación est_at90.c que se ejecuta en el microcontrolador principal, el AT90USB162, y que se encarga de la funcionalidad básica de la estación y de la comunicación USB con el host (la GadgetPC).
2. La aplicación est_atmega.c que se ejecuta en un microcontrolador de soporte, un ATMEGA16, para las funciones de conversión analógico­digital (y eventual expansión de la plataforma si fuera necesario).
3. El programa est_ctrl.c, ejecutado en la GadgetPC y que sirve como manejador del hardware para otras aplicaciones en la GadgetPC que desean accesar a la estación.
4. La aplicación estacion.php, que presenta la interfaz con el usuario y se puede accesar desde un navegador web.
En el nivel más bajo (también es el más lejano, desde la perspectiva de un usuario), está la aplicación que se ejecuta en el microcontrolador principal, est_at90, que se encarga de ejecutar las funciones más primitivas como escribir y leer a los registros del microcontrolador e implementar la funcionalidad de dispositivo serial virtual sobre USB. En el nivel más alto está la aplicación web, estacion.php, que básicamente expone una interfaz intuitiva y de alto nivel para los usuarios.
Ahora bien, es posible, desde PHP, acceder directamente al puerto serial mediante las funciones integradas para manejo de archivos como fopen, fgets, fwrite, fclose, etc, y la ayuda de programas del sistema (invocados desde PHP) como stty. De modo que el programa en PHP podría comunicarse directamente con el microcontrolador, pero esto lo haría más grande e innecesariamente complejo. Desde el punto de vista de diseño de software es una mejor solución delegar la comunicación con el microcontrolador en un programa dedicado a ese fin. Esta es la función de est_ctrl, que existe en una capa intermedia entre la aplicación en php y el programa del microcontrolador AT90. La figura 4­1 44
45
muestra la relación entre los distintos componentes del sistema.
Figura 4­1 Vista general del sistema implementado
4.1 Aplicaciones en los microcontroladores: est_at90.c y est_atmega.c
El microcontrolador principal es el AT90USB162. Las aplicaciones en el host se comunican con este dispositivo, quien interpreta los comandos recibidos, los ejecuta y devuelve una respuesta apropiada. El ATMEGA16 cumple una labor secundaria, de apoyo, pero su existencia es transparente al host.
El AT90USB162 escucha en la línea USB solicitudes (comandos) provenientes del host. Utilizando las funciones primitivas puestas a disposición por el firmware de dispositivo CDC/ACM (discutidas en el capítulo anterior), lee los caracteres provenientes del host y forma comandos. Un carácter de nueva línea, '\n', marca el fin de un comando. Básicamente, el AT90 implementa un pequeño protocolo de solicitud­respuesta, cómo el utilizado para configurar y examinar un módem serial. De hecho, si se tiene acceso al hardware de la estación (en lugar de accesarla por medio de est_ctrl o estacion.php), es posible utilizarla al nivel más bajo de abstracción abriendo el puerto serial virtual (/dev/ttyACM0) con una aplicación como minicom o gtkterm y tecleando los comandos directamente. La especificación del protocolo implementado está en la tabla 4.1
46
Tabla 4.1 Comandos primitivos para control de la estación
Protocolo de bajo nivel para control de la estación
Parámetros
Sintaxis
1
2
3
Descripción
Leer de PIN de puerto
r[puerto]
'b' | 'c' | 'd'
Respuesta
OK r[valor de PIN]
Escribir a PORT de puerto w[puerto][valor de PORT]
'b' | 'c' | 'd'
“XX”
OK
Leer de PORT de puerto
w[puerto]G
'b' | 'c' | 'd'
G
OK r[valor de PORT]
Configurar DDR de pines
d[puerto][valor de DDR]
'b' | 'c' | 'd'
“XX”
OK
Leer DDR de pines
d[puerto]G
'b' | 'c' | 'd'
G
OK r[valor de DDR]
Configurar T de PWM
T[timer][prescala][tope]
'1' | '2'
'0' ­ '5'
Leer T de PWM
T[timer]G
'1' | '2'
G
Configurar D de PWM
D[timer][salida][D]
'1' | '2'
'a' | 'b' | 'c'
“XXXX” OK
Leer D de PWM
D[timer][salida]G
'1' | '2'
'a' | 'b' | 'c'
G
OK [D]
Configurar Estado PWM
p[timer][salida][estadoPWM] '1' | '2'
'a' | 'b' | 'c'
'0' | '1'
OK
Leer Estado PWM
p[timer][salida]G
'1' | '2'
'a' | 'b' | 'c'
G
OK [estadoPWM]
Leer de CAD
c[canal]
'0' ­ '7'
Comando desconocido
n/a
ERR 1
Comando incorrecto
n/a
ERR 2
Timeout
n/a
ERR TOUT
“XXXX” OK
OK [prescala] [tope]
OK [valor CAD]
Notas: (1) 'X' es cualquier dígito hex (2) Para [estadoPWM] y [estadoCAD], '0'­>OFF, '1'­>ON (3) G es la letra 'G'
El microcontrolador rechaza cualquier comando desconocido o cualquier comando conocido pero que tenga valores de parámetros no explícitamente permitidos según la tabla 4.1, con los mensajes de error “ERR 1” o “ERR 2”, respectivamente. Otro posible mensaje de error es TOUT (de “time­out”) que se explica más adelante.
En el caso de un comando relacionado con una conversión analógico/digital, el AT90 lo reenvía tal cual al ATMEGA, quien a su vez ejecuta una versión similar pero disminuida del protocolo (que solo sirve para los comando relacionados con CAD). La comunicación entre ambos microcontroladores se lleva a cabo utilizando la UART disponible en cada uno. La UART del AT90 es esencialmente el mismo hardware que la del ATMEGA, excepto por pequeñas variaciones en los nombres de los registros y los bits de configuración. Por esta razón para este proyecto, se partió del código utilizado en el curso IE­0107 Laboratorio de Microcontroladores, uart_ucr.c y uart_ucr.h. Esta pequeña librería se modificó para portarla al AT90. Así, con el mismo código fuente se puede escoger para cuál microcontrolador se quiere usar defininiendo una de dos macros USART_ATMEGA16 o USART_AT90USB162. Adicionalmente, se revisó para limpiar un poco el código y mejorar su 47
funcionalidad. Concretamente, se reescribieron los encabezados de las subrutinas de atención a interrupciones para que estén conformes con el avr­libc vigente, se sustituyeron muchas macros y tipos definidos en el código por definiciones y tipos estándar (por ejemplo, los tipos definidos por el usuario como bool o u08 ya están, o hay algunos equivalentes, en headers estándar como stdbool.h y stdint.h) y se corrigió la función uart_getchar() para que se pueda recibir el valor 0 como un dato válido.
Para hacer más robusto al sistema ante fallas en la comunicación entre los microcontroladores, se utilizó el Timer0 del AT90USB162 como un temporizador de “timeout”. Cuando se hace una solicitud al ATMEGA, solo se espera aproximadamente 5ms a que éste responda (esto es un poco más del tiempo necesario para realizar la conversión y responder por la interfaz serial, con un baudrate de 19200bps). Si en ese tiempo el ATMEGA no respondió, significa que se ha dañado él o la línea serial, por lo que el AT90 continúa con su trabajo y responde al host con un mensaje de “TOUT”. Por esta razón el Timer0 no está disponible a la aplicación para generar salidas PWM, pero a cambio sería relativamente fácil expandir el sistema haciendo un mayor uso de los recursos del ATMEGA que actualmente no se aprovechan.
4.2 El manejador del hardware de la estación: est_ctrl.c
En general es una buena práctica de diseño de software delegar acciones y tareas en programas o funciones con un propósito específico, bien definido, y una interfaz clara. También es una buena práctica separar la implementación subyacente de la interfaz mostrada. Tener un manejador dedicado para la tarea de comunicación con el microcontrolador refuerza estas ideas y simplifica el diseño del sistema. Al existir est_ctrl otras aplicaciones pueden hacer uso del hardware sin saber los detalles de bajo nivel de la comunicación, basta con que respeten el protocolo de la tabla 4.1 y conozcan como invocar a est_ctrl y qué salida genera. Si en el futuro se decidiera cambiar algo de la implementación, por ejemplo usar WUSB (“Wireless USB”) en lugar de USB normal, esto no tendría por qué afectar a las aplicaciones siempre que est_ctrl mantenga su interfaz.
est_ctrl recibe como parámetros (desde la línea de comandos) un conjunto de comandos dirigidos al microcontrolador. La sintaxis de invocación es la siguiente:
$est_ctrl comando1 comando2 …
48
Cada comandoN debe ser uno de los comandos especificados en la tabla 4.1. Por ejemplo el comando para escribir el valor 0xF1 al puerto D sería “wdf1”, mientras que el comando para leer el puerto B sería “rb”. La invocación del programa est_ctrl, para ejecutar estos dos comandos en secuencia sería entonces:
$est_ctrl wdf1 rb
El programa procesa los comandos y lleva a cabo la comunicación con el AT90. Los comandos se envían al AT90 en el orden en que se pasan en la línea de comandos. La salida de est_ctrl consiste en imprimir, a la salida estándar, la respuesta a cada uno de los comandos ejecutados, una respuesta por línea. El programa est_ctrl no valida los comandos, pues esta tarea ya es realizada en el microcontrolador, que responderá con un mensaje de error apropiado que eventualmente será canalizado a la salida estándar.
En caso de que ocurra un error grave de acceso al microcontrolador, que impida la comunicación con él, est_ctrl aborta la ejecución de comandos e imprime el mensaje “ERROR FATAL: no se pudo abrir el puerto de acceso al microcontrolador” y sale con un valor de retorno de 1. Si no ocurre un error grave el programa terminará de imprimir respuestas y retornará con un valor de 0.
4.2.1 Implementación de est_ctrl
Debido a que el firmware de USB en el microcontrolador utiliza la clase CDC­ACM, el microcontrolador es visto en el sistema operativo Linux como un dispositivo serial virtual. El módulo cdc­acm.ko es el que brinda soporte para dispositivos de esta clase. Cuando el dispositivo se conecta a la GadgetPC el manejador lo detecta y crea un nodo de dispositivo en el directorio /dev. El nombre asignado al dispositivo es /ttyACMx, donde 'x' depende de si existen otros dispositivos ttyACM antes de conectar el microcontrolador. (Más adelante se explica por qué esto puede ser un inconveniente y cómo evitarlo).
En vista de que el dispositivo se comporta como un puerto serial virtual, están a disposición del programador las API para manejar dispositivos seriales. En este caso, las librerías más relevantes son termios.h, unistd.h, fcntl.h. termios.h cuenta con las funciones para establecer parámetros del puerto serial. Para esta 49
aplicación en particular, debido a que el dispositivo en realidad no es un puerto serial, parámetros como la tasa de baudios, los bits de datos, paridad y parada realmente no son relevantes para la comunicación pues el bus USB ya implementa sus propios mecanismos de control de velocidad (para dispositivos como la clase CDC las transferencias de datos son del tipo “bulk”, usan el ancho de banda según esté disponible en el bus) y de detección y corrección de errores (USB garantiza entrega libre de errores para todos los tipos de transferencia excepto las isócronas). Así, el dispositivo no está atado a una velocidad particular, como los dispositivos seriales normales, sino que tiene una mayor flexibilidad.
Aún así otros parámetros y funciones sí son relevantes, como el preprocesamiento de la entrada/salida y el tipo de entrada. En este caso se usa la entrada canónica, esta es una entrada orientada a líneas de texto. Cuando se realiza una operación de lectura con read() esta lee caracteres hasta el siguiente carácter de nueva línea. Esto es adecuado para comunicarse con el microcontrolador porque como parte del protocolo, éste siempre termina las respuestas con un '\n'. Para la salida se usa salida en modo “raw” (crudo) para asegurar que los caracteres se manden si ningún tipo de preprocesamiento al microcontrolador. Esto es para evitar la introducción (automatizada) de caracteres no deseados como los que se usarían para formar secuencias de escape apropiadas para comunicación con un dispositivo serial típico como un módem analógico. Al usar salida raw se tiene control estricto sobre la cantidad de bytes (y sus valores) que finalmente salen de la computadora hacia el microcontrolador.
Las otras dos librerías, unistd.h y fcntl.h son relevantes porque como la mayoría de las cosas en Linux (o en Unix, en general), el dispositivo se ve finalmente como un archivo que puede ser escrito o leído. Funciones como open(), read(), write() y close() trabajan sobre el puerto serial (virtual), o mas específicamente, sobre el descriptor de archivo asociado al dispositivo, como lo harían sobre otros dispositivos o archivos y forman la base para el acceso al dispositivo y el intercambio de datos con éste.
4.2.2 El nombre del dispositivo serial virtual
Como se mencionó anteriormente, la presencia previa de otros dispositivos ttyACM podría alterar el nombre que el sistema finalmente asigna al dispositivo. Esto es inconveniente para est_ctrl porque implica que éste debería determinar, siempre que es invocado, cuál es el dispositivo ttyACM en el que se encuentra realmente el microcontrolador. Para evitar la potencial variabilidad del nombre es posible utilizar udev para darle un nombre persistente. udev es el sistema de Linux para manejar 50
eventos de conexión y desconexión de dispositivos en general. Mediante la definición de reglas en los directorios /etc/udev/rules.d o /lib/udev/rules.d es posible establecer criterios que permitan identificar dispositivos específicos y ejecutar ciertas acciones cuando se detecta su conexión o desconexión.
En particular, todos los dispositivos USB cuentan con atributos que incluyen el código de fabricante y el código de producto. En este caso estos códigos están determinados por el firmware de la librería LUFA e identifican unívocamente al microcontrolador. Estos atributos se pueden usar en una regla udev para identificarlo y, ante su conexión a la computadora, crear un enlace simbólico, con un nombre bien definido, que enlace al nodo real (cuyo nombre sí podría variar) en el que se encuentra el dispositivo. En el programa est_ctrl la conexión se hace abriendo el link simbólico, cuyo nombre es fijo y puede incluso establecerse en tiempo de compilación, evitando la complicación de tener que buscar el ttyACM adecuado.
La regla necesaria se coloca en un archivo con un nombre escogido por el desarrollador (terminado en .rules), en el directorio /etc/udev/rules.d. En este caso el archivo 96­estacion.rules contiene la regla necesaria para crear un link llamado ttyEST.
#Regla para asignarle un nombre persistente a la estacion del AT90USB162 SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2044", SYMLINK+="ttyEST"
Figura 4­2 Contenido del archivo de reglas 96­estacion.rules
(Aunque en la figura no se aprecia, toda la regla debe estar en una misma línea)
CAPÍTULO 5: Implementación de la Interfaz con el usuario: estacion.php
5.1 Aplicaciones basadas en tecnología Web y PHP.
La aplicación con que interactúa el usuario final es un conjunto de programas en PHP que se ejecutan sobre un servidor web Apache corriendo sobre Debian en la GadgetPC.
El uso de un servicio web en un dispositivo de propósito específico, para labores de configuración y monitoreo, es un concepto que se está utilizando mucho actualmente en la industria debido a que ofrece múltiples ventajas en términos económicos y de conveniencia para usuarios y diseñadores. Entre las ventajas principales de este modelo de interfaz con el usuario están las siguientes:
1. Existen varias opciones de servidores web disponibles en el mercado, tanto propietarios como libres. Las herramientas libres, como Apache, son muy fuertes en este campo y cuentan con amplio uso y abundante documentación. En general los servidores web tienen requisitos bastante moderados de procesamiento, memoria y espacio de almacenamiento.
2. Los lenguajes de programación y scripting comúnmente utilizados, como Php, Perl, Python, Javascript, entre otros, así como los lenguajes de marcado (HTML, XML, etc) también son de uso ampliamente difundido y son versátiles, poderosos y de aprendizaje relativamente rápido (para lograr un nivel básico, al menos).
3. Brinda la posibilidad de crear interfaces gráficamente agradables, poderosas e intuitivas con los recursos del cliente, por lo que esta carga de procesamiento no recae en el dispositivo­
servidor. Simultáneamente, vuelve prescindibles en gran medida la incorporación de “displays” y botones en los dispositivos (y los controladores asociados), lo que puede significar un ahorro importante para el fabricante.
4. Se logra naturalmente una conectividad a Internet sin la necesidad de desarrollar aplicaciones específicas que se deban instalar en los clientes, o protocolos propios para la comunicación sobre red, pues se utilizan tecnologías existentes y probadas (HTTP y navegadores web).
Por su lado, PHP es un lenguaje de scripting ligado estrechamente a la creación de páginas 51
52
web dinámicas, si bien ha evolucionado para servir propósitos más generales. Aunque se puede usar como un lenguaje independiente, su presentación y uso más común es como un módulo que se agrega a un servidor web preexistente, en este caso Apache. Ante la solicitud de una página .php por parte de un cliente, un servidor web debidamente configurado invocará al intérprete de php para que ejecute el programa (que es interpretado, no previamente compilado). La salida generada por el programa será el documento con que el servidor responderá al cliente solicitante.
Una página en php básica es un documento html con código php incrustado mediante el uso de las etiquetas especiales <?php ?>. PHP interpreta lo delimitado entre sus etiquetas de acuerdo a las reglas del lenguaje y lo ejecuta, pero además imprime el html puro que está fuera de ellas (siempre que el flujo lógico de ejecución del programa pase por donde está ese contenido). De este modo PHP otorga una gran capacidad para el control de la funcionalidad y presentación de la página web.
5.2 Estructura e implementación de la aplicación estacion.php
5.2.1 Descripción general
La aplicación estacion.php consta de varias páginas independientes que presentan una interfaz similar (en cuanto a presentación y estilo) para el usuario. Las páginas dinámicas que componen la aplicación se discuten a continuación.
5.2.1.1 estacion.php
Es la página principal que recibe al usuario. Brinda información general sobre el estado y forma de uso del sistema. La figura 5.1 muestra la página de inicio (en estas capturas la GadgetPC estaba funcionando en una LAN privada por lo que los URL no coinciden con los que se usarían en el sistema en condiciones normales).
5.2.1.2 estacionMenu.php
Es una página parcial que incluye elementos globales utilizados por las demás páginas. estacionMenu.php es incluído por todas las otras páginas mediante la función include( ) de php. En ella se define el menú de páginas de la aplicación y su (pequeña) lógica de despliegue, usado para que todas las páginas presenten un menú consistente y que está centralizado en un solo documento, para facilitar su administración. El menú enlaza con todas las demás páginas y le indica al usuario en cuál se 53
encuentra actualmente.
En estacionMenu.php también se definen algunas funciones de utilería usadas por las otras páginas y se define la variable global $EST_CTRL, que contiene el comando básico para invocar a est_ctrl, así como otras variables como mensajes de error comunes.
Figura 5.1 Página de inicio de la aplicación
5.2.1.3 estacionHead.html y estacion.css
estacionHead.html especifica la parte común a todas las páginas de la aplicación, del encabezado en la sección <head>...</head>. Especifica la codificación de caracteres de los documentos y establece el uso de estacion.css como hoja de estilo. Por su parte, estacion.css especifica las caracterísiticas generales de presentación visual del sitio. El uso de hojas de estilo facilita enormemente el diseño, rediseño y modificación visual de los sitios y es realmente un requisito de cualquier aplicación web moderna, pues la práctica de especificar estilos con atributos en los elementos html se considera obsoleta hoy en día.
54
5.2.1.4 lecturaPuertos.php y escrituraPuertos.php Son páginas sencillas para leer o escribir rápidamente el valor de un puerto especificado. Se mantienen porque podrían resultar convenientes, aunque posiblemente se eliminen de la aplicación definitiva.
Figura 5­2 Lectura y escritura simple de puertos
5.2.1.5 ESdigital.php (Entrada/Salida digital)
Esta página constituye la aplicación principal para el control de las entradas y salidas digitales del microcontrolador. En ella se pueden observar y configurar el estado de los tres registros relacionados con E/S de cada puerto del microcontrolador de una manera conveniente, por pin y puerto, o ver las funciones especiales asignadas actualmente a cada pin en caso de que no estén disponibles al usuario, por ejemplo porque una salida ya está siendo usada por el generador de señal PWM.
En los microcontroladores AVR cada puerto tiene tres registros asociados: PINx, PORTx, y 55
DDRx. PIN es de solo lectura y representa el valor que está presente físicamente en los pines del dispositivo. PORT es el buffer de salida: los valores de los bits de PORT aparecen en los pines físicos si el pin está configurado como salida. Si un pin es una entrada, PORT controla la activación(1)/desactivación(0) de la resistencia de pullup asociada. DDR controla si cada pin es una entrada o una salida. La página ESdigital.php permite la lectura y control de los valores de PORT y DDR, y la lectura de los valores de PIN, para todos aquellos pines que no tengan una función especial asignada en un momento dado. Para estos últimos los controles no se despliegan sino que en su lugar se muestra la función asignada, y los espacios respectivos se muestran en gris y no son editables. Esto puede variar dinámicamente según se activen o desactiven funciones especiales durante el uso del sistema. (Hay algunos pines que del todo no existen en el microcontrolador. Estos tienen un – en su campo de función asignada).
La figura 5.3 muestra una captura de la página ESdigital.php.
5.2.1.6 pwm.php
Mediante esta página se puede ejercer control sobre cada una de las salidas PWM de las que el sistema dispone. El usuario puede especificar tanto el período como el ciclo de trabajo y activar o desactivar cada salida. El periodo se especifica en ms y puede ir de 0.01ms a 16500ms. Internamente el programa convierte este valor en los valores de registros y prescala adecuados para lograr el valor deseado intentando mantener la mejor resolución posible. El ciclo de trabajo se puede introducir como un valor en ms o un porcentaje. El programa valida todos los valores de período y ciclo de trabajo para evitar la ejecución de comandos erróneos como aquéllos con D>100%, D>T, o con números fuera de rango o con problemas de formato, en cuyo caso no realiza acciones sobre el microcontrolador y despliega un mensaje de error apropiado. La figura 5.4 muestra la página pwm.php
5.2.1.7 cad.php
La página cad.php brinda acceso a las funciones de conversión analógico­digital. El usuario puede solicitar una conversión en cualquiera de los ocho canales disponibles o un muestreo de todos los canales. En este último caso el muestreo se realiza secuencialmente pues el convertidor del que se dispone no puede realizar conversiones simultáneas. El usuario también cuenta con la posibilidad de desplegar los resultados como números enteros decimales con precisión de 10 bits (de 0 a 1023) o como valores en punto flotante en el rango de 0.00 a Vcc (el voltaje de alimentación del sistema, que 56
constituye la referencia del convertidor) que en este caso es de 5.0 V pues los microcontroladores toman su alimentación del USB. La figura 5.5 muestra la página cad.php
Figura 5.3 Control de Entrada/Salida y estado de los pines
57
Figura 5.4 Control de Salidas PWM
Figura 5.5 Control de Entradas Analógicas
58
5.2.2 Detalles de implementación
5.2.2.1 Arquitectura de las aplicaciones
Las páginas de la aplicación están constituidas como formularios html. PHP es conveniente para este tipo de aplicaciones por dos aspectos en particular: el lenguaje tiene integrado el soporte para los métodos comunes de envío de información al servidor (get, post) mediante variables “superglobales” predefinidas llamadas $_GET[ ] y $_POST[ ], y su integración con html hace fácil la creación de campos con nombres, valores y estilos altamente parametrizables.
Dado un formulario que utilice alguno de los métodos citados, los valores contenidos en sus elementos se pueden recuperar en el programa php con solo accesar el arreglo correspondiente al método, utilizando el nombre del campo como índice. Por ejemplo si en un documento html se define el siguiente elemento de entrada tipo texto:
<form action=”procesador.php” method=”get”>
...
Edad en años cumplidos: <input type=”text” name=”Edad” size=”4” value=”0”>
... </form>
Entonces en el programa php encargado de procesar el formulario (procesador.php en este caso) el valor se puede recuperar accesando $_GET[“Edad”].
Por otro lado, la capacidad de crear el contenido dinámicamente permite una generación eficiente de la página mediante la invocación de funciones, cuando hay muchos elementos de los formularios que son similares pero que aplican para entidades distintas. Así por ejemplo en ESdigital.php, elementos como el control de cada puerto (y dentro de estos, el control de cada pin) no tienen que escribirse individualmente en el html sino que se puede escribir una función que reciba parámetros adecuados y genere código html para lograr elementos de formulario con nombres parametrizados (y únicos) y contenidos apropiados para el elemento, suponiendo que existen estructuras de datos o funciones disponibles para recuperar valores según los parámetros pasados a la función generadora de código. La figura 5.5 muestra a modo de ejemplo la función imprimirControlPin() de este programa. En esta se observa la generación dinámica del control de un pin individual en términos del puerto y número de pin en cuestión. 59
Figura 5.5 Detalle de una función que genera contenido con base en la configuración y estado actual del sistema.
La figura 5.6 muestra en detalle la función imprimirCheckboxEscrituraPin(), en la que se ve como el nombre del elemento se genera con base en los parámetros, creando así nombres con una estructura regular que pueden ser referenciados fácilmente en las funciones que procesan el formulario.
Figura 5.6 Función para generar un elemento de formulario
con nombre parametrizado y contenido dinámico.
Este tipo de construcciones brindan más flexibilidad al sistema pues si en el futuro se habilitaran más puertos (por ejemplo haciendo uso de los del ATMEGA) o cambiara el número de pines por puerto (si se cambiara a una arquitectura de más bits) entonces no sería necesario cambiar el código que genera los formularios.
5.2.2.2 Operación interna de las aplicaciones
En todas las aplicaciones implementadas el propio programa que contiene el formulario es el 60
encargado de procesarlo. Las aplicaciones inician su operación revisando la variable “submit” del arreglo global $_GET. Si esta variable no existe es porque la página está siendo accesada por primera vez y aún no se ha presionado ningún botón, en cuyo caso se continúa. En caso de que exista, se revisa su fuente (por ejemplo en ESdigital hay muchos botones de “submit”) y se ejecuta el comando apropiado.
Después de esta primera etapa se procede a cargar los datos relevantes para la aplicación desde el microcontrolador, para colocarlos en las estructuras de datos y variables del programa, esta es la razón por la que la mayoría de los comandos primitivos poseen una contraparte para leer de vuelta los registros o variables que modifican en el microcontrolador. Estos datos eventualmente serán usados para generar el contenido de campos que dependen del estado del microcontrolador, como la mayoría de los campos en ESDigital.php o pwm.php. De este modo se mantiene la consistencia entre lo mostrado y el estado real del microcontrolador. Así si se abandona el sitio web y posteriormente se regresa, las páginas reflejarán el estado del AT90 en el momento del reingreso. Si por el contrario se utilizaran “cookies”, sesiones de php o archivos en el servidor, para intentar recordar los datos, se incrementaría significativamente el riesgo de inconsistencias (y sería conceptualmente incorrecto porque realmente el estado del sistema está asociado al microcontrolador y no a los usuarios).
No todos los campos son dependientes del estado del microcontrolador. Por ejemplo el selector de unidades (ms o %) de pwm.php es un elemento que no está almacenado en el microcontrolador sino que simplemente es un artificio de la interfaz. El estado de estos elementos se recupera de las variables del método de envío del formulario, en este caso $_GET, y se redibuja el elemento con ese valor, preservándolo entre “submits”, para conveniencia del usuario.
Una vez procesados los comandos y cargados los datos de estado y configuración relevantes desde el microcontrolador, se procede a generar el código html que describe la página que finalmente es mostrada. Si ocurrió un error de acceso al microcontrolador, este habrá sido detectado al inicio de la ejecución y la página se desplegará, cargada con valores por defecto (que no tienen realmente significado) y un mensaje de error en la parte superior.
Ejecución de los comandos de bajo nivel
En el fondo todas las aplicaciones finalmente recurren a los servicios de est_ctrl para enviar los comandos al microcontrolador y recibir las respuestas. Asimismo est_ctrl es capaz de generar un 61
mensaje de error y un valor de retorno distinto de 0 en caso de que el microcontrolador sea inaccesible (porque no se pudo abrir el dispositivo, por ejemplo porque no está presente en el sistema o ha fallado). php ofrece varias facilidades para invocar programas del sistema, con distintos grados de control sobre el proceso. Una de las funciones más versátiles es proc_open(), usada para ejecutar un programa y a la vez ejercer un control detallado sobre su ejecución, permitiendo controlar aspectos como los descriptores de archivos que utilizará (por ejemplo para que su entrada y salida estándar se redirijan a “pipes” que sean leídos y escritos por el script php) o las variables de ambiente que estarán a su disposición, entre otras capacidades.
Para los efectos de este trabajo, sin embargo, se optó por la función exec() que aunque no es tan poderosa como proc_open() es adecuada para la aplicación. La función exec() sirve para ejecutar programas externos y tiene la siguiente estructura:
string exec(string $command [,array &$output [,int &$return_var ]] )
exec ejecuta el programa especificado en $command, y coloca la salida estándar del programa en el arreglo $output (una línea por elemento), además de almacenar el valor de retorno del programa en $return_var. La función en sí retorna la última línea de salida del programa, lo cual es redundante si se usó el arreglo $output (que es opcional) pero puede ser conveniente para invocaciones sencillas.
En las aplicaciones implementadas, la variable global $EST_CTRL contiene la ruta completa del programa est_ctrl. Cada aplicación concatena esta variable con los comandos primitivos que necesita y luego invoca a exec() . La figura 5.7 muestra dos ejemplos de invocación, correspondientes a cad.php y ESdigital.php. 62
a) Uso en la lectura de resultados de conversiones en cad.php
b) Uso en la carga de valores desde el microcontrolador en ESDigital.php
Figura 5.7 Uso de exec( ) para invocar a est_ctrl
CAPÍTULO 6: Conectividad y acceso a Internet desde la GadgetPC utilizando el módem USB Huawei E1556
Uno de los objetivos principales de este Proyecto es el uso de un módem USB en la GadgetPC para darle conexión a Internet sobre la red celular, con el fin de que la plataforma final fuera accesible remotamente sin la necesidad de una red LAN en las cercanías del sistema. Aunque, como se explica en este capítulo, el uso de una conexión mediante el servicio de datos sobre 3G plantea algunos inconvenientes, en general sí es una alternativa viable. A grandes rasgos el problema de la instalación y uso de la “datacard” como medio de conexión se puede separar en tres partes:
1. Instalación física del dispositivo, a nivel de USB y los drivers del kernel necesarios para su correcto reconocimiento y control.
2. Uso del dispositivo para establecer una conexión desde el sistema a Internet.
3. Visibilidad y accesibilidad desde Internet hacia el dispositivo.
6.1 Instalación del hardware
Los módems USB realmente brindan una interfaz muy similar a los módems analógicos, básicamente en lugar de ser accesados en uno de los puertos seriales estándar (ttyS0 a ttyS3) aparecen como puertos seriales USB (ttyUSB0, ttyUSB1, …). El módulo del kernel llamado usbserial es el que brinda el soporte para que las aplicaciones puedan accesar a estos puertos virtuales USB como si fueran puertos normales (de manera transparente). Un módulo más actual, llamado option, busca dar soporte más específicamente para módems celulares, que por el avance de la tecnología requieren más ancho de banda que los módems más viejos. Sin embargo el Debian distribuido para la GadgetPC no contaba con option a menos de que se hubiera recompilado el kernel. Como a la larga el módulo usbserial presentó un buen desempeño, y la compilación (cruzada) del kernel para una plataforma tan particular como la GadgetPC no es una tarea trivial, se prefirió usar éste.
6.1.1 Problemas de instalación y USB_ModeSwitch
El problema principal que se encontró en la instalación del módem no fue en relación con los módulos citados anteriormente sino con la forma en que el sistema veía el dispositivo. Estas datacards 63
64
(y al parecer muchas otras en el mercado, de otros fabricantes también) están diseñadas de modo que se presenten al sistema inicialmente como una unidad de almacenamiento masivo, específicamente un CD­ROM virtual. El objetivo de este comportamiento es que el sistema anfitrión ejecute el “autorun” del dispositivo para facilitar al usuario final la instalación de los drivers necesarios. Una vez instalados los drivers, estos pueden detectar la presencia del dispositivo y ejecutar sobre él un comando (específico para el modelo, fabricante y firmware del aparato) para cambiarlo de modo, para que el dispositivo muestre sus capacidades de módem, en particular, los puertos seriales virtuales. En algunos dispositivos basta con desmontar (o eyectar) el CD­ROM para que cambie de modo, pero este no era el caso con la tarjeta Huawei. La figura 6.1 muestra el comportamiento típico del sistema al conectar la datacard. En ella se puede observar la aparición del CD­ROM virtual y otro dispositivo que corresponde a la memoria interna de la datacard. Además en el comando lsusb se ve que la identificación es 12d1:1446 (que no es la identificación que se observa cuando el dispositivo está funcionando como módem).
Figura 6.1 Comportamiento del sistema ante la conexión de la datacard
Lo común (e inconveniente) de este problema en muchos dispositivos comerciales llevó a que surgiera USB_ModeSwitch, una aplicación para, como su nombre lo dice, cambiar el modo de operación de este tipo de dispositivos multifuncionales. USB_ModeSwitch es desarrollado por Josua Dietze y otros colaboradores, y es software libre liberado bajo la licencia GPL. El programa aún no 65
forma parte del “mainstream” de alguna distribución o del kernel, por lo que se distribuye en forma de código fuente. Para su compilación se requiere la librería libusb­dev. Con esta librería y el compilador para ARM fue posible compilar exitosamente la aplicación.
El paquete en que viene USB_ModeSwitch no es solo el programa central (usb_modeswitch) sino que además incluye: 1. Los archivos de configuración necesarios para cada dispositivo conocido (nombrados según el “vendorID” y el “productID”) que contienen las opciones de invocación necesarias para realizar el cambio de modo para ese dispositivo particular. El dispositivo Huawei E1556 (modelo comercializado por el ICE) no aparece como tal, pero este nombre de modelo no es realmente significativo, pues internamente el dispositivo sí es uno de los modelos conocidos (12d1:1446 cuando está en modo de dispositivo de almacenamiento, 12d1:1001 cuando está en modo módem).
2. Reglas de udev para desencadenar la llamada al script (no el programa) usb_modeswitch al detectar la conexión de un dispositivo conocido.
3. Un script (/lib/udev/usb_modeswitch) que “envuelve” al programa (/usr/bin/usb_modeswitch) y que está hecho para ser invocado desde udev. El script realiza algunas actividades de verificación de identidad del dispositivo para finalmente invocar a usb_modeswitch con el archivo de configuración que corresponde.
El script está escrito en tclsh por lo que se requiere tener esta consola/intérprete instalado en el sistema para contar con los beneficios de usb_modeswitch automatizado. Si del todo no se dispusiera de tclsh igualmente se podría invocar al programa usb_modeswitch manualmente, pero como tclsh estaba disponible como un paquete en Debian (instalable con apt­get) no fue necesario el uso manual.
Una vez instalado usb_modeswitch y tclsh, el sistema es capaz de identificar al dispositivo cuando es conectado y realiza el cambio de modo. La figura 6.2 muestra el momento en que el módem se desconeta y reconecta a la GadgetPC, pero esta vez con usb_modeswitch habilitado. Inicialmente se identifica como un par de dispositivos de almacenamiento. Inmediatamente (debido a udev) usb_modeswitch se ejecuta, provocando la desaparición de los dispositivos de almacenamiento. El comando lsusb revela una nueva identificación: 12d1:1001. Esta es la que corresponde a la configuración de módem.
66
Figura 6.2 Respuesta del sistema ante la conexión de la datacard,
con usb_modeswitch instalado.
Una vez que el dispositivo está en configuración módem, se carga el módulo usbserial en el kernel utilizando el comando modprobe. Para que el módulo se asocie al módem, se pasan los números de identificación del dispositivo como parámetros para el módulo. En la figura 6.3 se muestra la carga del módulo al kernel, y cómo esto ocasiona el reconocimiento de los puertos seriales virtuales. El dispositivo presenta varios puertos aunque realmente sólo el primero sirve para configurar el módem y conectarse a Internet. La documentación respecto a la utilidad específica de los otros puertos es un poco confusa, pero en general se pueden usar para monitorear aspectos de la operación del módem como la fuerza de la señal recibida y las tasas de transferencia logradas, el tipo de red que llega y se está usando (HSDPA, EDGE, GPRS) entre otros parámetros. También es posible que el servicio de SMS (mensajes de texto) utilice alguno de estos puertos. En todo caso son prescindibles para efectos de la conexión a Internet. En este punto el dispositivo está listo para ser configurado y usado para establecer una conexión.
67
Figura 6.3 Detección del dispositivo y creación de los puertos,
al cargar el módulo usbserial
6.2 Establecimiento de una conexión a Internet con el módem USB Huawei.
Para utilizar el módem, más allá del soporte para el hardware, realmente solo se necesita pppd, el “daemon” de PPP, encargado de establecer y mantener la conexión sobre PPP y poner a disposición del sistema la interfaz de red ppp0. pppd sí requiere, sin embargo, que algún programa o ente externo prepare la línea serial sobre la que se superpondrá PPP. Este el papel del programa chat, que realiza labores de inicialización del módem y marca el número telefónico adecuado, antes de que pppd establezca la comunicación PPP sobre la línea. En esencia, la conexión a Internet establecida con la “datacard” no es muy distinta, desde el punto de vista lógico, de una conexión de marcado (“dial­
up”) sobre un módem serial convencional.
6.2.1 pppd y chat: configuración y uso
pppd requiere de dos componentes base: soporte a nivel del kernel para ppp (kernel compilado para soportarlo y módulos adecuados (ppp_async, ppp_generic, slhc, crc_ccitt) y el “daemon” pppd propiamente. En general la mayoría de las distribuciones de Linux modernas incluyen ambos componentes, y en el caso de la GadgetPC tanto el Arm9­Linux como el Debian­Armel contaban con 68
ellas (a éste último fue necesario copiarle los módulos y el pppd desde el Arm9­Linux, pero su kernel sí estaba compilado para soportarlos).
En PPP, debido a que es un protocolo punto­a­punto, la conexión se establece con un solo equipo remoto, conocido como el igual (o “peer”). Esto difiere de otras arquitecturas de red como en el caso de una red Ethernet, en que un grupo de posiblemente varios hosts comparte un segmento de red. En este caso el “peer” es el proveedor de servicios de Internet (el ICE), e interesa establecer una conexión con él para lograr una salida a Internet. En otras aplicaciones, por ejemplo, PPP se podría utilizar para crear un vínculo WAN, sobre una línea telefónica, entre dos redes LAN.
pppd permite definir el conjunto de opciones que se usarán para conectarse a un “peer” dado en un archivo de opciones que se coloca en /etc/ppp/peers/, o en la línea de comandos al momento de invocarlo (o ambas). Para este proyecto el archivo options_ice contiene las opciones para conectarse a la red del ICE. Este archivo se presenta en la figura 6.4, y las distintas opciones se explican en los comentarios en él. Para iniciar la conexión simplemente se invoca pppd con la opción call y el nombre del archivo de opciones que debe usar (suponiendo que éste existe en /etc/ppp/peers/):
#/ppp/bin/pppd call options_ice
Antes de iniciar las actividades de PPP, pppd ejecutará el comando dado como argumento de la opción connect (una de las opciones pasadas mediante el archivo de opciones) para inicializar el módem. En este caso se puede ver que el comando dentro del “connect” es la invocación del programa chat para que ejecute el archivo /ppp/peers/chat_ice. Este último archivo no es un archivo de opciones para ppp sino un “chatscript” para el programa chat. Un “chatscript” consiste de un conjunto de mensajes­por­enviar / respuestas­esperadas que chat intercambiará con el módem, para configurarlo y realizar la llamada telefónica, antes de ceder el control de la comunicación a pppd. La figura 6.5 muestra el contenido del archivo chat_ice, utilizado para inicializar la tarjeta Huawei.
69
##### options_ice: Opciones para PPP para el ICE #####
#No se le exige al equipo remoto autenticarse
noauth
#Dispositivo serial al que esta conectado el modem
/dev/ttyUSB0
#Velocidad para la comunicacion con el dispositivo serial
460800
#Script por usar para inicializar el modem
connect '/ppp/bin/chat ­V ­f /ppp/peers/chat_ice'
#Script por utilizar para finalizar la operacion del modem
disconnect '/ppp/bin/chat ­V ­f /ppp/peers/chat_ice_disconnect'
#Instalar la direccion IP del peer como default gateway,
#si la negociacion de IPCP se completa exitosamente.
#Interesa hacerlo porque en este caso el peer es nuestro ISP.
defaultroute #No utilizar una direccion IP local para este equipo
#El peer (ICE) nos debera dar una direccion IP como
#parte del proceso de negociacion de IPCP
noipdefault
#Obtener direcciones de servidores DNS del peer (ICE)
#Los dos DNS se pasan como variables de ambiente a los scripts
#ip­up y ademas se colocan en el archivo /etc/ppp/resolv.conf
usepeerdns
#No desconectarse de la consola inicial. Si no se usa esta opcion
#pppd se convierte en un proceso en el background.
#Util para debugging.
#nodetach
#Usuario y password (aunque el ice realmente no los usa para nada)
user user
password pass
Figura 6.4 Archivo de opciones para establecer la conexión al servicio de Internet del ICE.
70
s####### chat_ice: chatscript para inicializar
####### la datacard Huawei para conexion a Internet
## Si en algun momento se recibe del modem alguno de
## los mensajes especificados despues de ABORT,
## chat aborta el proceso de conexion
ABORT BUSY
ABORT "NO CARRIER"
ABORT "NO DIALTONE"
## Inicialmente se espera "" (es decir, nada)
## antes de enviar el primer mensaje al modem
"" ## ATZ resetea el modem a su configuracion default
## Se espera a que el modem responda "OK"
ATZ OK
## AT+CPIN desbloquea la tarjeta SIM enviandole el PIN.
## Esto podria generar un ERROR si ya estaba desbloqueada
## en cuyo caso no habria problema, solo se prosigue (­­)
AT+CPIN=2651 TIMEOUT 6 ERROR­­
## AT+CGDCONT define el Contexto PDP. El Contexto PDP
## define los parametros de la conexion para servicio de datos.
AT+CGDCONT=1,"IP","kolbi3g","0.0.0.0",0,0 OK
## ATDT marca el numero telefonico para la conexion
ATDT*99#
Figura 6.5 Script de chat para inicializar el módem
En este caso los pasos que toma el chatscript son:
1. Resetear el módem El comando ATZ realiza esta acción, para asegurarse que no haya conflictos debido a configuraciones o usos previos.
2. Desbloquear la tarjeta SIM del módem Se debe enviar el comando AT+CPIN=#### donde #### es el PIN de la tarjeta. Como el módem podría estar bloqueado o desbloqueado previamente, la respuesta a este comando podría ser OK (si estaba bloqueado y fue desbloqueado exitosamente) o ERROR (si ya antes estaba desbloqueado). Así, el comando mostrado espera la respuesta ERROR, y si no ocurre es porque ocurrió OK, entonces después de 6 segundos se ejecuta un comando nulo (­­) y se prosigue (el módem estará desbloqueado después de todo esto, independientemente de si al principio lo estaba o no). La tarjeta está inicialmente 71
bloqueada cuando es conectada a la computadora, después de un desbloqueo permanecerá en ese estado hasta que sea desconectada y reconectada. Resulta conveniente tener un solo script que pueda manejar ambas condiciones.
3. Escoger el Contexto PDP (Packet Data Protocol)
Esto un conjunto de parámetros que definen la conexión de datos. Vale la pena aclarar el significado de los distintos campos. El comando utilizado es el siguiente:
AT+CGDCONT=1,"IP","kolbi3g","0.0.0.0",0,0
El 1 inicial es un número de identificación para el contexto PDP, pues en un mismo dispositivo se podrían definir varios contextos diferentes dependiendo del proveedor, el tipo de cobertura, etc. El segundo parámetro, “IP”, determina el protocolo que se utilizará para este contexto PDP. El tercer parámetro, “kolbi3g”, define el Nombre del Punto de Acceso (“Access Point Name”) que es esencialmente el nombre de la red que brinda el servicio de datos. El cuarto parámetro “0.0.0.0” es la dirección IP solicitada, pero el valor 0.0.0.0 tiene el significado especial de que la dirección IP se asignará dinámicamente. Los dos últimos parámetros tienen que ver con la compresión de datos y encabezados, respectivamente, están desactivados por defecto.
4. Marcar el número del servicio de datos
El número definido por el ICE es *99#. Una vez realizada una llamada a ese número, se puede utilizar para que pppd inicie el proceso de negociación de PPP.
Si en algún caso chat recibe una respuesta que no es la esperada (y suponiendo que no haya un curso de acción alternativo definido), o bien si recibe en algún momento una de las respuestas marcadas con la palabra clave “ABORT”, chat terminará su ejecución con un valor de retorno de error que pppd reconocerá, y la conexión no se realizará. Con las configuraciones mostradas siempre se lograron conexiones exitosas y se obtuvo una operación satisfactoria, por lo que los errores de conexión solo podrían darse si ocurren errores de hardware o de proveedor o cambios en la configuración del servicio por parte del ICE.
72
6.2.2 Estableciendo y terminando una conexión
Cuando una conexión se establece exitosamente, ocurren varios eventos importantes en el sistema. El más significativo es la aparición de una nueva interfaz de red, ppp0. Figura 6.6 Creación de la interfaz ppp0 como resultado de una conexión exitosa
Adicionalmente otros eventos de interés ocurren:
1. Se crea un archivo con los nombres de los servidores DNS obtenidos desde el peer, en /etc/ppp/resolv.conf. Este se puede usar para leer de él y actualizar /etc/resolv.conf, que es el utilizado por el servicio de resolución de nombres del sistema.
2. Si se usó la opción defaultroute (como es el caso), y anteriormente no había una ruta por defecto definida en el sistema), la dirección del “peer” se agrega a la tabla de enrutamiento del sistema como ruta por defecto. Si ya había una ruta por defecto antes, entonces su 73
cambio se deberá manejar desde scripts pues pppd no lo hace automáticamente, aún con la opción defaultroute.
3. Se coloca el número de identificación del proceso (“process id”, pid) pppd encargado de la conexión en el archivo /var/run/ppp0.pid.
4. Se ejecuta el script /etc/ppp/if­up. Este script puede ser modificado por el administrador del sistema para realizar tareas de apoyo arbitrarias cuando se establece una conexión, de manera automatizada. Por ejemplo, modificaciones complejas de la tabla de enrutamiento o el archivo /etc/resolv.conf. El establecimiento del túnel ssh hacia el servidor público (como se explica en la sección 6.3) se puede ubicar en este script para que una vez establecida la conexión a Internet inmediatamente se lleve a cabo, de manera automatizada, este proceso. De momento esta funcionalidad no se ha implementado pero está planeada.
Para terminar la conexión se hace uso del comando kill para enviarle una señal al proceso pppd correspondiente. pppd responde a la señal HUP (“Hangup”) terminando el enlace, restaurando la configuración previa del puerto serial, y cerrándolo. Para enviar esta señal el comando adecuado es el siguiente:
#/bin/kill ­HUP `cat /var/run/ppp0.pid`
El comando cat entre ` ` se ejecutará, generando como salida el pid del pppd, que se le pasa como argumento a kill. Al terminar pppd se terminará la conexión, desaparecerá la interfaz ppp0, se ejecutará el script ip­down, y se eliminará el archivo ppp0.pid.
6.3 Haciendo a la GadgetPC visible desde Internet
Una inspección de la figura 6.6 (en la sección 6.2) muestra un hecho que complica el uso de la conexión establecida para brindar servicios en Internet desde la GadgetPC (o cualquier host conectado a Internet mediante “datacard”): la dirección IP asignada por el proveedor del servicio es una dirección privada, perteneciente al rango 10.0.0.0/8. Adicionalmente la dirección es variable (dentro de este rango) entre conexiones hechas en distintos momentos.
Para poder correr el servidor Web en la GadgetPC y que pudiera ser accesado desde cualquier 74
punto, se requiere de una dirección pública y conocida. Se realizó la consulta al ICE sobre la posibilidad de obtener una IP pública asociada a la datacard pero como era razonable esperar, se informó de que el servicio de IP pública no se ofrece para este tipo de conexión.
Para resolver el problema finalmente se utilizó una capacidad incorporada en ssh: la creación de túneles ssh para redirigir el tráfico TCP de otros protocolos entre el cliente ssh y el servidor ssh. Se optó por esta solución principalmente por su simplicidad. Aunque el uso de una Red Privada Virtual (VPN) es una solución más robusta e integral, no se disponía del tiempo necesaria para implementarla pues era un tema ajeno a los objetivos iniciales de la investigación, pero realmente valdría la pena implementarlo así.
6.3.1 Redireccionamiento de puertos con ssh
ssh permite encapsular, dentro del protocolo SSH, el tráfico de otros protocolos que utilicen TCP. Por ejemplo, se puede encapsular el protocolo de correo SMTP o el protocolo web HTTP. El objetivo primordial de esta característica es hacer que protocolos que de otro modo son “inseguros” viajen de manera segura por Internet. A esta capacidad se le conoce como redireccionamiento de puertos (“port forwarding”).
En toda comunicación sobre SSH, existe un cliente ssh, que ejecuta ssh o scp, y un servidor ssh, que ejecuta sshd. Además para otros servicios TCP existe tanto un cliente de aplicación (como un navegador web, e.g. Firefox) como un servidor de aplicación (como un servidor Web, e.g. Apache)
En un redireccionamiento local (“local forwarding”) el cliente ssh abre un socket en la máquina local que puede ser usado por clientes de aplicación (existentes en esa máquina local, o con conectividad hacia esa máquina) para accesar un servidor de aplicación que está en el servidor ssh remoto. En un redireccionamiento remoto ocurre lo contrario: el cliente ssh abre un socket en la máquina remota (el servidor ssh) al que aplicaciones cliente en esa máquina remota (o con conectividad hacia ella) pueden conectarse para accesar los servicios de un servidor de aplicación que existe en el cliente ssh.
Justamente es el redireccionamiento remoto el apropiado para el problema a mano: se requiere abrir un socket en un servidor público visible desde Internet, que permita llegar al servidor Web oculto en la GadgetPC. La GadgetPC es la que ejecuta el cliente ssh y el servidor web. Ella sí puede (y es la que debe) establecer una conexión como cliente ssh hacia el servidor público porque éste sí es 75
alcanzable desde Internet. No puede ser a la inversa (un redireccionamiento local desde el servidor público a la GadgetPC) porque el servidor no sabe cómo llegar a la GadgetPC en primer lugar. La implementación de la solución consiste en configurar el cliente ssh para que cree el túnel al conectarse al servidor público, y al servidor ssh para que acepte conexiones desde el mundo exterior al puerto redirigido. Actualmente el servidor público es jack.desocupadolector.org. La figura 6.7 muestra pictóricamente la situación.
En la figura 6.7 el túnel ssh se establece entre el puerto 2080 de Jack y el 80 de la GadgetPC. El extremo en Jack se denomina extremo que escucha porque está abierto a conexiones provenientes de clientes de aplicación, mientras que el extremo en la GadgetPC se denomina el extremo que conecta porque realiza la conexión con el servidor de aplicación final.
Figura 6.7 Redireccionamiento de puerto para hacer a la GadgetPC visible desde Internet,
con ayuda de un servidor público intermediario (Jack)
Configuración en el lado del cliente ssh (GadgetPC)
76
Para establecer el redireccionamiento remoto se creó una cuenta para el usuario estacion en Jack, de modo que ssh pudiera accesar al servidor desde la GadgetPC. En la GadgetPC el usuario encargado de establecer el túnel es root. En el archivo de configuración del cliente ssh para el usuario root (/root/.ssh/config) se coloca la configuración para host remoto jack_tunnel. Así es posible iniciar la sesión con el comando sencillo “ssh jack_tunnel” desde la GadgetPC. El contenido del archivo config de root se muestra en la figura 6.8.
#Archivo de configuracion para el cliente ssh
#para conectarse a Jack desde la GadgetPC
# Opciones comunes a cualquier conexion con Jack
# desde la GadgetPC
Host jack_*
# Direccion del servidor
HostName jack.desocupadolector.org
# Nombre de usuario de la cuenta en Jack
user estacion
# Control de los mensajes periodicos hacia # el servidor para mantener viva la conexion.
# De otro modo el ICE la destruye despues de # un minuto de inactividad.
ServerAliveInterval 50
ServerAliveCountMax 5
# Opciones propias de la sesion para fines administrativos
Host jack_session
# Opciones para sesion para establecimiento de tunel
Host jack_tunnel
RemoteForward *:2080 localhost:80
ExitOnForwardFailure yes
Figura 6.8 Archivo /root/.ssh/config en la GadgetPC: configuraciones para accesar a Jack
De las opciones utilizadas, la más relevante para la discusión es la opción RemoteForward. Esta opción establece que en el host remoto (Jack), el tráfico proveniente de cualquier interfaz (indicado por el *) que vaya al puerto 2080 (escogido arbitrariamente, para recordar que a la larga habrá un servidor web ahí) se debe redirigir al puerto 80 del localhost (la GadgetPC).
Otro par de opciones importantes son las opciones “ServerAliveInterval 50” y “ServerAliveCountMax 5”. Cuando se realizaron las pruebas de conexión entre la GadgetPC y Jack, se descubrió que estando dentro de una sesión de ssh, después de (casi exactamente) un minuto de 77
inactividad, es decir, de no introducir comandos de ningún tipo en la consola, la conexión se perdía y el cliente ssh se quedaba atascado. Se descubrió que este tipo de comportamientos de rompimiento de la conexión en protocolos orientados a sesión (como SSH, y Telnet) normalmente se debe a procesos de NAT (“Network Address Translation”) o cortafuegos en el medio de la sesión, siendo la causa más probable el NAT o algo similar que de seguro el ICE necesita para permitir que la red privada de “datacards” accese a Internet. Sí resulta, sin embargo, excesivamente corto el tiempo de expiración de 60 segundos.
En cualquier caso, la opción ServerAliveInterval define cada cuánto tiempo, en segundos, el cliente ssh enviará un pequeño paquete para determinar si la conexión hasta el servidor está aún activa. Este paquete es suficiente para mostrar actividad y así evitar que la conexión de ssh sea destruida por agentes externos. Para este fin se elige un intervalo un tanto menor que el tiempo de expiración. La otra opción, ServerAliveCountMax establece la cuenta máxima de estos paquetes que se enviarán al servidor aún cuando no se reciba una respuesta. Si después de ServerAliveCountMax paquetes enviados al servidor ssh, éste aún no responde, el cliente asumirá que se perdió la conectividad y terminará la conexión.
Configuración en el lado del servidor ssh (Jack)
Además de la cuenta para el usuario estacion se debe habilitar al servidor ssh para que permita el redireccionamiento de puertos en general y además para que permita que otros hosts externos a él hagan uso de los puertos redirigidos existentes en él. Normalmente, cuando se hace un redireccionamiento con ssh, solo la máquina en la que está el extremo “que escucha” del túnel ssh tiene acceso a ese puerto. Es decir, por ejemplo, que por defecto solo un navegador Web que se ejecute en Jack podría accesar al puerto 2080 de Jack. Esto ocurre porque ssh por defecto solo liga la interfaz localhost:2080 al túnel, es decir que el túnel está asociado a 127.0.0.1:2080 y no realmente a las otras interfaces de jack: por ejemplo, no está implícitamente asociado a jack.desocupadolector.org:2080.
Para habilitar la redireccion de puertos en general y el acceso de terceros a los puertos redirigidos, se deben agregar las opciones “AllowTcpForwarding Yes” y “GatewayPorts Yes”, respectivamente, en el archivo de configuración del servidor, /etc/ssh/sshd_config/.
Una vez completada la configuración del cliente y servidor ssh, es posible crear el túnel 78
mediante el comando “ssh jack_tunnel” en la GadgetPC. Una vez establecido el túnel el servidor web de la GadgetPC estará disponible en la dirección http://jack.desocupadolector.org:2080/. La GadgetPC debe estar encendida y conectada a Jack para que la página sea accesible, si no, no se encontrará nada en esa dirección.
En la figura 6.9 se observa cómo se establece esta sesión se ssh con redireccionamiento. Una vez en Jack, es posible observar las conexiones de red con netstat. Existe un servicio escuchando en todas las interfaces (*) en el puerto 2080. Éste es el puerto redireccionado hacia la GadgetPC. Figura 6.9 Establecimiento de una sesión/túnel ssh desde la GadgetPC a Jack.
79
En este caso el establecimiento del túnel ha obligado a iniciar una sesión de consola interactiva en Jack, pero también es posible iniciar ssh desde la GadgetPC de modo que se ejecute como un proceso en el fondo, agregando la opción ­f a la invocación. OpenSSH requiere, si se va a enviar al fondo (background) que se le pase algún comando para ejecutar, a menos que se le indique que no es necesario que ejecute nada mediante la opción adicional ­N. De modo que si solo se quisiera establecer el túnel, sin entrar a Jack ni tener que ejecutar algún comando de relleno, la invocación sería la siguiente:
#ssh jack_tunnel ­f ­N
En la figura 6.10 se muestra un acceso a la GadgetPC a través de Jack. Como lo revela la página web desplegada y la barra de URL, el acceso al sitio (público) en el puerto redirigido, lleva al servidor en la GadgetPC. Figura 6.10 Acceso desde un host remoto al servidor web de la GadgetPC
a través del puerto redirigido en Jack.
CAPÍTULO 7. Conclusiones y Recomendaciones
En este trabajo se implementó una plataforma básica para medición y actuación remota a partir de una plataforma basada en Linux embebido como entidad central, microcontroladores como periféricos, un módem USB 3G para comunicación inalámbrica a larga distancia, y tecnología web para su interfaz con el usuario.
La plataforma resultante posee una funcionalidad básica pero aún así demuestra los aspectos básicos y herramientas clave que pueden utilizarse para implementar este tipo de sistemas, por ejemplo para labores de control remoto de hogares o edifcios. Existen muchos aspectos que podrían ser optimizados o ampliados. En este capítulo se enuncian y describen los principales logros, obstáculos, carencias y sugerencias nacidas de la elaboración del proyecto.
7.1 Conclusiones
1. Se logró trabajar exitosamente con dos ejemplos de sistemas computacionales que representan dos formas de sistemas embebidos importantes en la actualidad: microcontroladores de 8 bits (AT90USB162 y ATMEGA16) y microcomputadoras basadas en Linux (GadgetPC). Ambos tipos de sistemas difieren considerablemente en cuánto a poder de procesamiento, flexibilidad, nichos de aplicación y paradigma de desarrollo de aplicaciones (interfaz con el desarrollador). Sin embargo en el fondo comparten elementos fundamentales como el kit de herramientas básico para compilación y construcción de aplicaciones, y muchas librerías como la librería estándar de C, lo que facilita mucho su aprendizaje y uso.
2. La GadgetPC es un dispositivo realmente poderoso para su tamaño y especificaciones. Si se piensa en que para servir las páginas de la aplicación debe ejecutar simultáneamente Linux, Apache, PHP, pppd, est_ctrl, y otras aplicaciones, se puede apreciar el potencial de dispositivos de éste tipo, para una gran cantidad de aplicaciones. Linux embebido es una poderosa opción para sistemas de ésta naturaleza, y además tiene un costo relativamente bajo. Desarrollar para un sistema como éste realmente no difiere mucho de desarrollar para otro Linux, en especial cuando se utiliza Debian: cualquier usuario de Debian o Ubuntu 80
81
podría adaptarse rápidamente.
3. Se estudió la teoría del protocolo USB 2.0. USB es un protocolo mucho más complejo que el utilizado por los dispositivos seriales típicos, pero a cambio, ofrece una versatilidad inigualable frente a protocolos más viejos. Los dispositivos de varios modos de operación (como la datacard, con su modo de almacenamiento y su modo para comunicaciones) o que incluyen muchos dispositivos en uno (como los múltiples puertos virtuales expuestos por el módem en su modo de comunicación) son una muestra de esta flexibilidad. 4. Fue posible utilizar el protocolo USB en un nivel básico para establecer comunicación entre host y un microcontrolador como dispositivo periférico para que éste último cumpliera una nueva función. Sin embargo el uso que se le dio a USB es aún rudimentario en comparación con sus capacidades reales, que no se han aprovechado. No se realizó una implementación original del firmware USB base o la clase CDC, sino que se utilizó la librería LUFA, la cuál es un excelente punto de partida. Desarrollar una librería para implementar una clase de dispositivo estándar o nueva, desde cero, podría ser un proyecto completo en sí mismo.
5. Se consiguió una correcta y confiable instalación de la tarjeta de datos Huawei en la GadgetPC utilizando el programa usb_modeswitch, el módulo del kernel usbserial, y pppd con sus programas y módulos de soporte. En general todos estos componentes están fácilmente alcanzables o incluídos para muchas distribuciones de Linux por lo que se podría esperar que estos módems y otros similares hallen, con relativa facilidad, buen soporte en este Sistema Operativo, aún cuando se carezca de drivers propietarios.
6. La conexión a Internet mediante “datacard” es estable y tiene buena cobertura y velocidad. Sin embargo está fuertemente orientada a dar servicio a usuarios finales que básicamente desean navegar por la web. Al utilizar una red privada que sale a Internet mediante NAT (o similar) con un agresivo temporizador de expiración para conexiones orientadas a sesión “inactivas”, se pueden causar inconvenientes para otro tipo de usuarios: por ejemplo, un administrador de red que suela usar ssh para administrar sus equipos remotos deberá estar al tanto de este hecho y tomar las medidas pertinentes.
7. Sí se pudo resolver el problema de hacer a la GadgetPC visible desde Internet para acceso al servidor web en ella. Aún así, la solución dista de ser óptima porque la conexión de ssh solo expone el servicio http pero no otros: por ejemplo, igualmente no es posible administrar la 82
GadgetPC desde un equipo remoto a nivel de consola. Tampoco es una solución escalable.
8. Se implementó una interfaz con el usuario usando PHP para generación de páginas dinámicas. PHP, y su estrecha integración con HTML, permitió el desarrollo rápido del sitio, aunque no se contaba con experiencia previa en el lenguaje.
7.2 Recomendaciones
El sistema podría ser optimizado en múltiples aspectos. Algunos de las ideas que valdría la pena considerar y eventualmente implementar son:
1. Instalar un software de VPN en la GadgetPC para enlazarla con red en la cual pueda ser accesada de manera más confiable y generalizada, no sólo el servicio http.
2. Aumentar la funcionalidad del sitio web mediante la adición de javascript para algunas tareas que es mejor realizar en el lado del cliente, como el despliegue condicional de partes de las formas o la validación de campos de entrada. Esto sería particularmente útil porque escondería parte de la latencia relativamente alta que caracteriza a la conexión por 3G.
3. Agregar funciones para configurar e iniciar procesos de recolección de datos en forma periódica durante intervalos de tiempo establecidos por el usuario, y que se almacenen en bases de datos remotas.
4. Falta automatizar los procesos de establecimiento y mantenimiento permanente de la conexión a Internet y el túnel que comunica al servidor público. Esto se puede lograr con scripts o procesos que se ejecuten al inicio de la operación del sistema y luego observen continuamente el estado de las conexiones, intentando mantenerlas arriba siempre que sea posible, para una máxima disponibilidad y mínima intervención humana directa sobre la GadgetPC
BIBLIOGRAFÍA
1. Anderson, Chris. “In the Next Industrial Revolution, Atoms Are The New Bits”. Wired Magazine.
Vol
18
Nª
2.
2010.
Disponible
en: http://www.wired.com/magazine/2010/01/ff_newrevolution
2. Atmel Corporation. “Application Note AVR282 USB firmware upgrade for AT90USB” 2008.
3. Atmel Corporation. “Hoja del fabricante del AT90USB162/AT90USB82 (Documento 7707E­AVR­11/08)” 2008.
4. Atmel Corporation. “Hoja del fabricante del AT91SAM9260 (Documento 6221I­ATARM­
17­Jul­09)” 2009.
5. avr­libc.
AVR Libc 1.6.5 User Manual. http://www.nongnu.org/avr­libc/user­
manual/index.html
6. Axelson, Jan. “USB Complete: The Developer's Guide”. 4ta Edición. Publicado por Lakeview Research. EEUU. 2009
7. Barret, D.J. Silverman, R.E. Byrnes, R.G. “SSH: The Secure Shell”. 2da Edición. Editorial O'Reilly. 2005
8. Bipom Electronics. “GadgetPC and MINI­MAX/ARM9 Single Board Computer: C Programming Guide for Linux” v1.02. 3 de febrero, 2010
9. Bipom Electronics. “GadgetPC Single Board Computer: Debian Installation Guide.” v1.03. 20 Octubre, 2009.
10. Bipom Electronics. “GadgetPC Single Board Computer: Quick Start Guide” V1.09. 12 de marzo, 2010.
11. Bothwick, Neil. “Get mobile broadband on your Linux laptop” techradar.com http://www.techradar.com/news/mobile­computing/get­mobile­broadband­on­your­linux­laptop­
486813?artc_pg=3
12. Camera, Dean. LUFA (2010) Australia, 2010. http://www.fourwalledcubicle.com/LUFA.php
83
84
13. Camera, Dean. Documentación interna de la librería LUFA generada automáticamente con Doxygen.
14. Camera, Dean. Código fuente de la libería LUFA y la demo VirtualSerial.
15. CodeSourcery, Inc. “Sourcery G++ Lite ARM GNU/Linux Sourcery G++ Lite 2010q1­202 Getting Started”. 2010.
16. Deegan, Patrick. “Developing for the Atmel AVR Microcontroller on Linux”. Linux Journal. EEUU. Edición #130. Enero, 2005.
17. Dietze, Josua. “USB_ModeSwitch ­ Activating Switchable USB Devices on Linux”. 2010. http://www.draisberghof.de/usb_modeswitch/
18. Documentación integrada de Linux (Ubuntu 9.10). Manpage: pppd, chat, ssh, ssh_config, sshd_config.
19. Documentación integrada de Linux (Ubuntu 9.10). Manpages: as, ld, ar, ranlib, objcopy, objdump, strip, gcc
20. Kirch, Olaf. Dawson, Terry. “Linux Network Administrator's Guide” 2da Edición. Editorial O'Reilly. EEUU. 2000.
21. Linux.co.uk. “How to get online with 3G broadband” http://linux.co.uk//docs/center/how­
to/how­to­get­online­with­3g­broadband
22. Matthew, N. Stones, R. “Beginning Linux Programming”. 4ta Edición. Wiley Publishing. EEUU. 2008.
23. Olson, Philip et. al. “PHP Manual” The PHP Documentation Group. 2010. http://www.php.net/manual/en/index.php
24. Patterson & Hennessy. “Computer Architecture: A quantitative approach”. 4ta Edición. Morgan Kauffman Publishers. EEUU, 2007.
25. Tlab.org “Huawei E220 3G Datacard and Linux” http://tlab.org/index.php?page=huawei­
e220­3g­usb­data­card­with­tele2­in­ubuntu­linux
26. USB Implementer's Forum. “Universal Serial Bus Specification” Revisión 2.0. USBIF. 27 de Abril, 2000.
27. USB Implementer's Forum. “Universal Serial Bus Class Definitions for Communications 85
Devices” Revisión 1.2. USBIF. 16 de noviembre de 2007
28. USB Implementer's Forum. “Universal Serial Bus Communication Class Subclass Specificacition for PSTN Devices” Revisión 1.2. USBIF. 9 de febrero de 2007.
29. w3schools.com “HTML Tutorial” http://www.w3schools.com/html/default.asp
30. w3schools.com “CSS Tutorial” http://www.w3schools.com/css/default.asp
31. w3school.com “PHP Tutorial” http://www.w3schools.com/php/default.asp
32. Wesley PA4WDH.
“The Linux GPRS Howto”. v0.8. Diciembre, 2009. http://www.xs4all.nl/~ernstagn/GPRS­HOWTO/
33. Wikipedia,
The
Free
Encyclopedia.
“Embedded
System” http://en.wikipedia.org/wiki/Embedded_system
34. Wikipedia,
The
Free
Encyclopedia.
http://en.wikipedia.org/wiki/Universal_Serial_Bus
“Universal
Serial
Bus”. APÉNDICE A. Código fuente de aplicaciones desarrolladas
est_at90.c (AT90USB162)
/***************************************************************************
est_at90.c: aplicacion principal para el microcontrolador AT90USB162
para implementar una estación a control remoto controlada por USB
Juan Diego Acuña Castillo, 2010
[email protected]
Basada en la demo "VirtualSerial" de la librería LUFA.
*****************************************************************************/
/*
LUFA Library
Copyright (C) Dean Camera, 2010.
dean [at] fourwalledcubicle [dot] com
www.fourwalledcubicle.com
*/
#include "VirtualSerial.h"
#include "estacion.h"
/* Información de configuración y estado de la interfaz de clase CDC. Esta estructura
se le pasa a todas las funciones del driver para clase CDC, así es posible tener varias
instancias de una misma clase en un mismo dispositivo, aunque en este caso
solo se usa una
*/
USB_ClassInfo_CDC_Device_t VirtualSerial_CDC_Interface =
{
.Config =
{
.ControlInterfaceNumber = 0,
.DataINEndpointNumber = CDC_TX_EPNUM,
.DataINEndpointSize = CDC_TXRX_EPSIZE,
.DataINEndpointDoubleBank = false,
.DataOUTEndpointNumber = CDC_RX_EPNUM,
.DataOUTEndpointSize = CDC_TXRX_EPSIZE,
.DataOUTEndpointDoubleBank = false,
.NotificationEndpointNumber = CDC_NOTIFICATION_EPNUM,
.NotificationEndpointSize = CDC_NOTIFICATION_EPSIZE,
.NotificationEndpointDoubleBank = false,
};
},
/* Un "archivo" estándar que se asociará con la interfaz CDC, de modo que el puerto serial virtual
pueda ser accesado como un flujo de caracters normal de C. ( por ejemplo con fputs )
*/
static FILE USBSerialStream;
/* Programa principal */
int main(void)
{
SetupHardware();
//Crear un stream de caracteres normal a partir de la interfaz, para poder usar las funciones de stdio.h con ella
CDC_Device_CreateStream(&VirtualSerial_CDC_Interface, &USBSerialStream);
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
//Variables para almacenar un comando en un buffer.
uint8_t
byteRecibido = 0;
char
buferDeComando[MAX_LONG_COMANDO+2];
int
indiceBuferComando=0;
bool finDeComandoRecibido=false;
bool
overflowDeBufer=false;
/*****************************************************************
Lazo Principal
******************************************************************/
for (;;)
{
// Revisar si se han recibido caracteres desde el Host
while (CDC_Device_BytesReceived(&VirtualSerial_CDC_Interface))
{
//Recibir el nuevo byte
byteRecibido = CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface);
//Si hay espacio en el buffer de comando se almacena ahí
86
87
if( indiceBuferComando < MAX_LONG_COMANDO && !finDeComandoRecibido ){
if(byteRecibido==EST_FIN_DE_COMANDO1 || byteRecibido==EST_FIN_DE_COMANDO2){
buferDeComando[indiceBuferComando++]=0;
finDeComandoRecibido=true;
}
else{
buferDeComando[indiceBuferComando++]=byteRecibido;
}
}
else if(indiceBuferComando==MAX_LONG_COMANDO){
overflowDeBufer=true;
}
}//end while
//Si se ha recibido un "finDeComando" desde el host, se procesa
if(finDeComandoRecibido){
//Procesar comando
procesarComando(buferDeComando);
//Despues de cualquier procesamiento la respuesta esta en la
//variable global respuestaComando
fputs(respuestaComando, &USBSerialStream);
//Resetear buffer
finDeComandoRecibido = false;
indiceBuferComando = 0;
}
if(overflowDeBufer){
fputs(RESP_BUF_OVF, &USBSerialStream);
//Resetear buffer
finDeComandoRecibido = false;
overflowDeBufer = false;
indiceBuferComando = 0;
}
//De LUFA/Drivers/USB/Class/Device/CDC.h
CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
//De LUFA/Drivers/USB/Class/HighLevel/USBTask.h
USB_USBTask();
}//end for(;;) principal
}
/* Inicializar el hardware del sistem */
void SetupHardware(void)
{
/* Deshabilitar el watchdog timer */
MCUSR &= ~(1 << WDRF);
wdt_disable();
/* Disable clock division */
clock_prescale_set(clock_div_1);
/* Hardware Initialization */
initPuertos();
init_PWM();
uart_init();
USB_Init();
}
/* Manejador de evento para cuando ocurre el evento de Conexión USB */
void EVENT_USB_Device_Connect(void)
{
LEDs_SetAllLEDs(LEDMASK_USB_ENUMERATING);
}
/* Manejador de evento para cuando ocurre el evento de Desconexión USB */
void EVENT_USB_Device_Disconnect(void)
{
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
}
/* Manejador de evento para cuando ocurre un evento de Cambio de Configuración USB */
void EVENT_USB_Device_ConfigurationChanged(void)
{
LEDs_SetAllLEDs(LEDMASK_USB_READY);
if (!(CDC_Device_ConfigureEndpoints(&VirtualSerial_CDC_Interface)))
LEDs_SetAllLEDs(LEDMASK_USB_ERROR);
}
/* Manejador para el evento de Request de Control no­Manejado */
void EVENT_USB_Device_UnhandledControlRequest(void)
{
CDC_Device_ProcessControlRequest(&VirtualSerial_CDC_Interface);
}
88
estacion.h (AT90USB162)
/***************************************************************************
estacion.h: funciones propias de la estacion para el AT90USB162.
Realiza el procesamiento de comandos y ejecución de primitivas
Juan Diego Acuña Castillo, 2010
[email protected]
*****************************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "uart_ucr.h"
#define EST_AT90
#include "../est_common/estCommon.h"
/***************************************************
Declaracion de funciones y variables
****************************************************/
/* *********************************************
****** Interfaz Privada
********
********************************************* */
/* Funciones para ejecución de comandos primitivos */
/* Son invocadas por procesarComando() segun el codigo de comando */
void
void
void
void
void
exec_escribirAPuerto(char *bufCmd);
exec_leerDePuerto(char *bufCmd);
exec_configurarDireccionesPines(char *bufCmd);
exec_reenviarComandoAMicroAuxiliar(char *bufCmd);
exec_configurarEstadoCAD(char *bufCmd);
void
void /* Inicializa/Detiene el Timer0 para que cuente el timeout de la respuesta
del atmega (CAD)*/
iniciarTimerTimeout(void);
detenerTimerDeTimeout(void);
/***************************************************
Implementacion de funciones
****************************************************/
void procesarComando(char* bufCmd){
switch(bufCmd[PARAM_OPCODE_0]){
}
//El codigo de operacion esta en bufCmd[PARAM_OPCODE_0]
case 'w': //Escribir PORT de puerto
exec_escribirAPuerto(bufCmd);
break;
case 'r': //Leer PIN de puerto
exec_leerDePuerto(bufCmd);
break;
case 'd': //Escribir DDR de puerto
exec_configurarDireccionesPines(bufCmd);
break;
case 'T':
exec_configurarPeriodoPWM(bufCmd);
break;
case 'D':
exec_configurarCicloPWM(bufCmd);
break;
case 'p':
exec_configurarEstadoPWM(bufCmd);
break;
case 'c':
exec_reenviarComandoAMicroAuxiliar(bufCmd);
break;
case 'a':
exec_configurarEstadoCAD(bufCmd);
break;
default:
setRespuesta(RESP_ERR_CMD);
}
void
exec_escribirAPuerto(char *bufCmd){
//Validar parametro "puerto"
if( esPuertoValido(bufCmd[PARAM_PUERTO]) ){
89
if( bufCmd[PARAM_VALOR]=='G' ){
//Consulta de una valor previamente escrito
uint8_t valor=0;
switch(bufCmd[PARAM_PUERTO]){
case 'b':
valor = PORTB;
break;
case 'c':
valor = PORTC;
break;
case 'd':
valor = PORTD;
break;
}
sprintf(respuestaComando,"%sr%02x\n",RESP_OK_PARCIAL, valor);
}else{
//Escribir un valor
uint8_t valor;
parsearByte(&bufCmd[PARAM_VALOR], &valor);
switch(bufCmd[PARAM_PUERTO]){
case 'b':
PORTB = valor;
break;
case 'c':
PORTC = valor;
break;
case 'd':
PORTD = valor;
break;
}
setRespuesta(RESP_OK);
}
}else{
//Se ha pasado un parametro de puerto no valido
setRespuesta(RESP_ERR_PARAM);
}
return;
}
void
exec_leerDePuerto(char *bufCmd){
//Validar parametro "puerto"
if( esPuertoValido(bufCmd[PARAM_PUERTO]) ){
uint8_t valor=0;
switch(bufCmd[PARAM_PUERTO]){
case 'b':
valor = PINB;
break;
case 'c':
valor = PINC;
break;
case 'd':
valor = PIND;
break;
}
sprintf(respuestaComando,"%sr%02x\n",RESP_OK_PARCIAL, valor);
}
else{
}
void
}
return;
//Se ha pasado un parametro de puerto no valido
setRespuesta(RESP_ERR_PARAM);
exec_configurarDireccionesPines(char *bufCmd){
//Validar parametro "puerto"
if( esPuertoValido(bufCmd[PARAM_PUERTO]) ){
uint8_t valorDDR;
if(bufCmd[PARAM_VALOR]=='G'){
//Consulta de un valor previamente escrito
switch(bufCmd[PARAM_PUERTO]){
case 'b':
valorDDR = DDRB;
break;
case 'c':
valorDDR = DDRC;
break;
case 'd':
valorDDR = DDRD;
break;
90
}
sprintf(respuestaComando,"%sr%02x\n",RESP_OK_PARCIAL, valorDDR);
}else{
//Escribir un valor nuevo
parsearByte(&bufCmd[PARAM_VALOR], &valorDDR);
switch(bufCmd[PARAM_PUERTO]){
case 'b':
DDRB = valorDDR;
break;
case 'c':
//Algunos pines de PORTC nunca deberian ser salidas...
//e.g. RESET!
DDRC = 0xf4 & valorDDR;
break;
case 'd':
DDRD = valorDDR;
break;
}
setRespuesta(RESP_OK);
}
}
else{
//Se ha pasado un parametro de puerto no valido
setRespuesta(RESP_ERR_PARAM);
}
return;
}
#define microAuxTimeout() ( TIFR0 & (1<<OCF0A) )
uint8_t c_aux=0;
#define flushInBuf() while(uart_getchar(&c_aux));
void
exec_reenviarComandoAMicroAuxiliar(char *bufCmd){
flushInBuf();
//Enviar el comando al microcontrolador auxiliar
strcat(bufCmd,"\n");
uart_putstr(bufCmd);
uint8_t
char*
c=0;
ptrRespuesta = respuestaComando;
bool respuestaRecibida=false;
iniciarTimerTimeout();
//Timer0 da aprox 6ms para que el micro auxiliar responda
while( !respuestaRecibida && !microAuxTimeout() ){
if( uart_getchar(&c) ){
if(ptrRespuesta < respuestaComando + MAX_LONG_RESPUESTA ­ 1){
*ptrRespuesta++ = c;
if(c=='\n'){
respuestaRecibida=true;
*ptrRespuesta=0;
}
}else{//Ha ocurrido un error
//Flushear el buffer de recepcion
flushInBuf();
setRespuesta(RESP_ERR_CAD);
respuestaRecibida=true;
}
}
}//end while
if( microAuxTimeout() ){
//Flushear el buffer de recepcion
flushInBuf();
setRespuesta(RESP_ERR_TOUT);
}
detenerTimerDeTimeout();
}
void
exec_configurarEstadoCAD(char *bufCmd){
setRespuesta(RESP_NOP);
}
void iniciarTimerTimeout(){
TCNT0 = 0;
OCR0A = 255;
TIFR0 |= (1 << OCF0A); //Limpiar interrupcion, si esta pendiente
TCCR0A = (1 << WGM01);
//Modo CTC, TOP=OCR0A
91
TCCR0B = (4 << CS00); //Prescala 256
}
void detenerTimerDeTimeout(){
TCCR0B &= (0xF8);
//Prescala 0 (Apagado)
TCNT0 = 0;
//Resetear cuenta
TIFR0 |= (1 << OCF0A); //Limpiar interrupcion, si esta pendiente
}
est_atmega.c (ATMEGA16)
/***************************************************************************
est_atmega.c: aplicacion principal para el microcontrolador ATMEGA16
para implementar una estación a control remoto controlada por USB.
El AT90USB162 es el microcontrolador principal y le envía comandos
a su esclavo, el ATMEGA16, a través de la UART.
Juan Diego Acuña Castillo, 2010
[email protected]
*****************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdbool.h>
#include <util/delay_basic.h>
#include "uart_ucr.h"
#include "cad.h"
/*
est_atmega.c:
programa para usar el atmega16 como un voltímetro de varios canales.
El microcontrolador escucha en su puerto serial por mensajes de la forma "cN\n", donde
N es un caracter de '0' a '7' que indica el canal del CAD que se desea muestrear.
Si la solicitud es válida, el micro realiza la conversión y devuelve un mensaje con
el formato "Nxxx\n", donde N es el canal del CAD al que corresponde la conversion
y xxx son tres dígitos hexadecimales con el resultado de la conversión, de 000 a 3ff
*/
#define esTerminadorDeComando(c) (c==EST_FIN_DE_COMANDO1||c==EST_FIN_DE_COMANDO2)
#define bufferDeComandoLleno() (ptrBufferDeComando > bufferDeComando + MAX_LONG_COMANDO ­ 2)
int main(void){
//Indica que se ha recibido un terminador de comando
bool comandoRecibido=false;
//Indica que se han recibido demasiados caracteres para la capacidad del
//buffer
bool
overflowDeBufferDeComando=false;
//Buffer para almacenar un comando recibido
char
bufferDeComando[MAX_LONG_COMANDO];
//puntero auxiliar para recorrer el buffer
char* ptrBufferDeComando = &bufferDeComando[0];
//caracter auxiliar para leer de uart y pasar a buffer
unsigned char caracterRecibido=0;
//Inicializar el hardware del microcontrolador excepto la uart
initHardware();
//Esperar 20ms antes de activar la UART, para evitar
//recibir basura causada porque el otro micro aún no haya
//inicializado la suya
_delay_loop_2(40000);
//Inicializar la uart
uart_init();
uart_putstr("OK\n");
while(true){
//Loopear hasta que se tenga un nuevo comando
//o se desborde el buffer de comando.
while( !comandoRecibido && !overflowDeBufferDeComando ){
//uart_getchar devuelve false si no se ha recibido nada
//El caracter recibido se copia en el argumento
if( uart_getchar(&caracterRecibido) ){
if( !bufferDeComandoLleno() ){
if( !esTerminadorDeComando(caracterRecibido) ){
92
*ptrBufferDeComando++ = caracterRecibido;
}else{
}
}else{
*ptrBufferDeComando++ = 0;
comandoRecibido=true;
overflowDeBufferDeComando=true;
}
}
}
//Si se recibio un comando, procesarlo
if( comandoRecibido ){
comandoRecibido=false;
ptrBufferDeComando = bufferDeComando;
procesarComando(bufferDeComando);
}
uart_putstr(respuestaComando);
if(overflowDeBufferDeComando){
comandoRecibido=false;
overflowDeBufferDeComando=false;
ptrBufferDeComando = bufferDeComando;
}
uart_putstr(RESP_BUF_OVF);
}
return 0;
}
cad.h (ATMEGA16)
/***************************************************************************
cad.h: funciones para el ATMEGA16 para dar el "servicio" de conversión
analógico­digital.
Juan Diego Acuña Castillo, 2010
[email protected]
*****************************************************************************/
#include <string.h>
#include <stdio.h>
#include <avr/io.h>
#define EST_ATMEGA
#include "../est_common/estCommon.h"
/***************************************************
Declaracion de funciones y variables
****************************************************/
//Inicializa el hardware del microcontrolador
void initHardware(void);
//Inicializa el convertidor analógico digital
void init_CAD(void);
//Configura los timer para empezar a funcionar en PWM a futuro
void init_PWM(void);
void exec_procesarSolicitudConversion(char *bufCmd);
//Iniciar una conversion en uno de los canales
int realizarConversion(char codigoCanal);
/***************************************************
Implementacion de funciones
****************************************************/
void initHardware(void){
init_CAD();
init_PWM();
}
void procesarComando(char* bufCmd){
switch(bufCmd[PARAM_OPCODE_0]){
case 'T':
93
exec_configurarPeriodoPWM(bufCmd);
break;
case 'D':
exec_configurarCicloPWM(bufCmd);
break;
case 'p':
exec_configurarEstadoPWM(bufCmd);
break;
case 'c':
exec_procesarSolicitudConversion(bufCmd);
break;
default:
strcpy(respuestaComando,RESP_ERR_CMD);
}
}
void exec_procesarSolicitudConversion(char *bufCmd){
if( esCanalValido(bufCmd[PARAM_CANAL]) ){
int resultado = realizarConversion(bufCmd[PARAM_CANAL]);
sprintf(respuestaComando, "%s%04d\n",RESP_OK_PARCIAL,resultado);
}
else{
strcpy(respuestaComando,RESP_ERR_PARAM);
}
}
/***************************************************
Capa de acceso al hardware
****************************************************/
void init_CAD(void){
//REFS[1:0]=01 : AVCC como referencia
//ADLAR=0 : Resultado justificado a la derecha
//ADMUX=0 : Canal 0, inicialmente, se varia despues.
ADMUX = (1 << REFS0);
//ADEN=1 : Habilitar convertidor
//ADPS=111 : CLK_ADC = 8 000 000 / 128 = 62 500 Hz
ADCSRA = (1 << ADEN) | ( 7 << ADPS0 );
}
//Configura el PORTA como entradas y sin pullups
PORTA = 0;
DDRA = 0;
int realizarConversion(char codigoCanal){
uint8_t canal = codigoCanal ­ '0';
int resultado = 0;
//Escoger el nuevo canal con ADMUX
ADMUX = (ADMUX & 0xe0) | canal;
//Iniciar la conversion
ADCSRA |= (1 << ADSC);
//Esperar a que termine la conversion.
//El bit ADSC se pone en 0 automaticamente cuando esta termina.
while( ADCSRA & (1 << ADSC) )
;
resultado = ADC;
return resultado;
}
est_common.h (AT90USB162 y ATMEGA16)
/***************************************************************************
est_common.h: funciones de la estacion comunes al AT90USB162 y el ATMEGA16.
Realiza el procesamiento de comandos específicos presentes en ambos
y la respectiva ejecución de primitivas
Definir la macro EST_AT90 o EST_ATMEGA al compilar con bandera
" ­Dmi_macro" de gcc
Juan Diego Acuña Castillo, 2010
[email protected]
*****************************************************************************/
#ifndef ESTCOMMON_H_INCLUDED
#define ESTCOMMON_H_INCLUDED
94
//La maxima longitud de un comando recibido desde el host
#define MAX_LONG_COMANDO 20
#define MAX_LONG_RESPUESTA MAX_LONG_COMANDO
#define EST_FIN_DE_COMANDO1 '\r'
#define EST_FIN_DE_COMANDO2 '\n'
//Offsets de los parametros en los comandos
//Ver tabla "Comandos primitivos para control de la estación"
enum offsetsParametros {
PARAM_OPCODE_0 = 0,
PARAM_PUERTO = 1,
PARAM_VALOR = 2,
PARAM_VALOR_DDR = 2,
PARAM_TIMER = 1,
PARAM_PRESCALA = 2,
PARAM_TOPE = 3,
PARAM_SALIDA = 2,
PARAM_D = 3,
PARAM_ESTADO_PWM = 3,
PARAM_CANAL = 1,
PARAM_ESTADO_CAD = 1
};
char*
RESP_BUF_OVF="OVF\n"; /*Respuesta ante una condicion de Overflow del buffer de comando*/
char*
RESP_ERR_CMD="ERR 1\n"; /*Respuesta ante un codigo de comando invalido*/
char*
RESP_ERR_PARAM="ERR 2\n"; /*Respuesta ante un comando con parametros invalidos*/
char* RESP_ERR_CAD="ERR 3\n"; /*Respuesta ante un error en la linea que comunica con el micro de CA/D*/
char* RESP_ERR_TOUT="ERR TOUT\n"; /*Respuesta ante un error de timeout del CAD */
char*
RESP_OK="OK\n"; /*Respuesta para comando exitoso que no devuelve nada */
char*
RESP_OK_PARCIAL="OK "; /*Parte de la respuesta para un comando exitoso que devuelve algo*/
char*
RESP_NOP="NOP\n"; /*Respuesta para un comando aun no implementado :) */
char
/*Contiene la respuesta generada al ejecutar el ultimo comando*/
respuestaComando[MAX_LONG_RESPUESTA];
/* Carga una respuesta apropiada en la variable global respuestaComando */
#define setRespuesta(x) ( strcpy(respuestaComando,x) )
//*********************************************
//Funciones y macros para validar parametros
//*********************************************
#ifdef EST_AT90
#define esPuertoValido(x) ( x=='b' || x=='c' || x=='d' )
#define esPrescalaValida(x) ( x >= '0' && x <= '5' )
#define esTimerValido(x) ( x=='0' || x=='1' || x=='2' )
#define esSalidaPWMValida(s) ( s=='a' || s=='b' || s=='c' )
#define esEstadoPWMValido(e) ( e=='0' || e=='1' )
#else
#ifdef EST_ATMEGA
#define esPrescalaValida(x) ( x >= '0' && x <= '5' )
#define esTimerValido(x) ( x=='1')
#define esSalidaPWMValida(s) ( s=='a' || s=='b' )
#define esEstadoPWMValido(e) ( e=='0' || e=='1' )
#define esCanalValido(c) ( c>='0' && c<='7')
#endif
#endif
/***************************************************
Prototipos de funciones comunes a ambos micros
****************************************************/
/*
procesarComando():
Inicia el proceso de decodificación y ejecución de un comando
recibido del host
Cada micro define su propia implementacion de este metodo
porque implementan un subconjunto distinto de comandos.
Parametros:
bufCmd: puntero al inicio del comando
Salida:
La respuesta a la ejecución del comando, en la variable global
respuestaComando
*/
void procesarComando(char* bufCmd);
void
/* Configuracion basica inicial del PWM */
init_PWM(void);
/* Funciones que ejecutan control primitivo de PWM */
void
exec_configurarPeriodoPWM(char *bufCmd);
void
exec_configurarCicloPWM(char *bufCmd);
void
exec_configurarEstadoPWM(char *bufCmd);
95
/* Fija el valor de prescala (CS1[2:0]) y TOP (ICR1) */
setPeriodoTimer1(uint8_t prescala, uint16_t tope);
/* Configura el estado (on/off) de una salida PWM */
bool
setEstadoPWM(char codigoTimer, char codigoSalida, char nuevoEstado);
/* Obtiene el estado (on/off) de una salida PWM */
bool getEstadoPWM(char codigoTimer,char codigoSalida);
void
/*Interpreta los dos siguientes caracteres hexadecimales de una cadena
de caracteres como los dos digitos hex consecutivos que forman un byte.
Parametros:
char* ptrCaracter: apunta a la cadena por parsear
uint8_t* ptrByte: un puntero a un byte en que se almacenará lo leído
Salida:
retorna : numero de caracteres validos leidos
en *ptrByte : el valor del byte (0 si no se leyo nada o se leyo 0 )*/
uint8_t
parsearByte(char *ptrCaracter, uint8_t *ptrByte);
uint16_t parsearWord(char *ptrCaracter, uint16_t *ptrByte);
/* Transforma un digito(caracter) de 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
a su correspondiente valor numerico. */
uint8_t digitCharToInt(char caracter);
/***************************************************
Implementacion de funciones
****************************************************/
uint8_t parsearByte(char *ptrCaracter, uint8_t *ptrByte){
uint8_t nuevoByte=0;
int i=0;
int numCharsLeidos=0;
for(i=0; i<2; i++){
if(*ptrCaracter){
nuevoByte <<= 4;
nuevoByte |= digitCharToInt(*ptrCaracter++);
numCharsLeidos++;
}//end if(*ptrCaracter)
}//end for
*ptrByte = nuevoByte;
return numCharsLeidos;
}
uint16_t parsearWord(char *ptrCaracter, uint16_t *ptrWord){
uint16_t nuevoWord=0;
int i=0;
int numCharsLeidos=0;
for(i=0; i<4; i++){
if(*ptrCaracter){
nuevoWord <<= 4;
nuevoWord |= digitCharToInt(*ptrCaracter++);
numCharsLeidos++;
}//end if(*ptrCaracter)
}//end for
*ptrWord = nuevoWord;
}
return numCharsLeidos;
uint8_t digitCharToInt(char caracter){
if(caracter >= '0' && caracter <= '9')
return caracter­'0';
else
return caracter­'a'+10;
}
void
exec_configurarPeriodoPWM(char *bufCmd){
if( esTimerValido(bufCmd[PARAM_TIMER]) ){
#ifdef EST_AT90
//Enrutar el comando hacia el micro auxiliar
if(bufCmd[PARAM_TIMER]=='2'){
//Para el atmega, realmente es el timer1
bufCmd[PARAM_TIMER]='1';
extern void
exec_reenviarComandoAMicroAuxiliar(char *cmd);
exec_reenviarComandoAMicroAuxiliar(bufCmd);
return;
}
#endif
if(bufCmd[PARAM_PRESCALA]=='G'){
//Se ha solicitado una lectura
if(bufCmd[PARAM_TIMER]=='0'){
96
//Al final el Timer0 se usó para otras cosas entonces no está
//realmente implementado para PWM
setRespuesta(RESP_NOP);
}else if(bufCmd[PARAM_TIMER]=='1'){
}else{
}
//Responder con OK, luego el codigo de prescala actual,
//luego el valor de ICR1 como 4 dígitos hex
sprintf(respuestaComando,"OK %d %04x\n", (TCCR1B & 0x07) ,ICR1);
setRespuesta(RESP_ERR_PARAM);
}
else if( esPrescalaValida(bufCmd[PARAM_PRESCALA]) ){
//Leer el valor de prescala
uint8_t prescala=digitCharToInt(bufCmd[PARAM_PRESCALA]);
//Aun no se ha implementado el PWM para timer0
if(bufCmd[PARAM_TIMER]=='0'){
setRespuesta(RESP_NOP);
return;
}else if(bufCmd[PARAM_TIMER]=='1'){
//Leer el valor de tope
uint16_t tope=0;
parsearWord(&bufCmd[PARAM_TOPE],&tope);
//Fijar el periodo del Timer
setPeriodoTimer1(prescala,tope);
setRespuesta(RESP_OK);
}
}else{
setRespuesta(RESP_ERR_PARAM);
}
}else{
}
setRespuesta(RESP_ERR_PARAM);
}
void init_PWM(void){
//Seleccionar modo PWM phase & frequency correct con TOP=ICR1
TCCR1A &= ~(0x03);
TCCR1B |= (1<<WGM13);
TCCR1B &= ~(1<<WGM12);
}
void
exec_configurarCicloPWM(char *bufCmd){
if( esTimerValido(bufCmd[PARAM_TIMER]) && esSalidaPWMValida(bufCmd[PARAM_SALIDA]) ){
#ifdef EST_AT90
//Enrutar el comando hacia el micro auxiliar
if(bufCmd[PARAM_TIMER]=='2'){
//Para el atmega, realmente es el timer1
bufCmd[PARAM_TIMER]='1';
extern void
exec_reenviarComandoAMicroAuxiliar(char *cmd);
exec_reenviarComandoAMicroAuxiliar(bufCmd);
return;
}
#endif
if(bufCmd[PARAM_D]=='G'){
//Se ha solicitado una lectura del Ciclo de Trabajo
if(bufCmd[PARAM_TIMER]=='0'){
setRespuesta(RESP_NOP);
return;
}else if(bufCmd[PARAM_TIMER]=='1'){
uint16_t valorCiclo=0;
if(bufCmd[PARAM_SALIDA]=='a')
valorCiclo=OCR1A;
else if(bufCmd[PARAM_SALIDA]=='b')
valorCiclo=OCR1B;
#ifdef EST_AT90
else if(bufCmd[PARAM_SALIDA]=='c')
valorCiclo=OCR1C;
#endif
else{
setRespuesta(RESP_ERR_PARAM);
return;
}
sprintf(respuestaComando,"%s%04x\n",RESP_OK_PARCIAL,valorCiclo);
}else{
setRespuesta(RESP_ERR_PARAM);
}
97
}else{
//Se ha solicitado una escritura del Ciclo de Trabajo
//Aun no se ha implementado el PWM para timer0
if(bufCmd[PARAM_TIMER]=='0'){
setRespuesta(RESP_NOP);
return;
}else if(bufCmd[PARAM_TIMER]=='1'){
//Leer el valor de cicloTrabajoD
uint16_t cicloTrabajoD = 0;
parsearWord(&bufCmd[PARAM_D],&cicloTrabajoD);
//Fijar el Ciclo de trabajo del Timer
if(bufCmd[PARAM_SALIDA]=='a')
OCR1A = cicloTrabajoD;
else if( bufCmd[PARAM_SALIDA]=='b' )
OCR1B = cicloTrabajoD;
#ifdef EST_AT90
else if( bufCmd[PARAM_SALIDA]=='c' )
OCR1C = cicloTrabajoD;
#endif
setRespuesta(RESP_OK);
}else{
setRespuesta(RESP_ERR_PARAM);
}
}
}else{
setRespuesta(RESP_ERR_PARAM);
}
return;
}
void
exec_configurarEstadoPWM(char *bufCmd){
if(
{
esTimerValido( bufCmd[PARAM_TIMER] )
&& esSalidaPWMValida( bufCmd[PARAM_SALIDA] ) )
#ifdef EST_AT90
//Enrutar el comando hacia el micro auxiliar
if(bufCmd[PARAM_TIMER]=='2'){
//Para el atmega, realmente es el timer1
bufCmd[PARAM_TIMER]='1';
extern void
exec_reenviarComandoAMicroAuxiliar(char *cmd);
exec_reenviarComandoAMicroAuxiliar(bufCmd);
return;
}
#endif
if(bufCmd[PARAM_ESTADO_PWM]=='G'){
//Se ha solicitado una lectura del estadoPWM
if( bufCmd[PARAM_TIMER]=='0' ){
setRespuesta(RESP_NOP);
return;
}
else{
bool estado = getEstadoPWM(bufCmd[PARAM_TIMER],bufCmd[PARAM_SALIDA]);
sprintf(respuestaComando,"%s%d\n",RESP_OK_PARCIAL,estado);
return;
}
}else if( esEstadoPWMValido( bufCmd[PARAM_ESTADO_PWM ] ) ){
//Se ha solicitado una escritura del estadoPWM
if( bufCmd[PARAM_TIMER]=='0' ){
setRespuesta(RESP_NOP);
return;
}
else{
setEstadoPWM(bufCmd[PARAM_TIMER],bufCmd[PARAM_SALIDA],bufCmd[PARAM_ESTADO_PWM]);
setRespuesta(RESP_OK);
}
}else{
setRespuesta(RESP_ERR_PARAM);
}
}
else
{
setRespuesta(RESP_ERR_PARAM);
}
return;
}
void
setPeriodoTimer1(uint8_t prescala, uint16_t tope){
//Fijar el valor de prescala para cuando se active el PWM
//prescalaTimer1 = prescala;
98
uint8_t aux = TCCR1B;
//Obtener la configuracion actual de TCCR1B
aux &= 0xf8;
//Borrar el valor actual de prescala, TCCR1B[2:0]
aux |= prescala;
//Setear la nueva prescala
TCCR1B = aux;
//Actualizar TCCR1B
}
bool
//Fijar el valor de tope
ICR1 = tope;
setEstadoPWM(char codigoTimer, char codigoSalida, char nuevoEstado){
if( nuevoEstado == '0' ){
//OFF, desactivar salida
if( codigoTimer == '0' ){
//NOP
}else{
//if(codigoTimer == '1')
if(codigoSalida == 'a'){
TCCR1A &= ~(1 << COM1A1); //Modo: pin desconectado del PWM
#ifdef EST_AT90
DDRC &= ~(1 << PC6);
//Dejar el pin como entrada
#else
DDRD &= ~(1 << PD5);
#endif
}else if (codigoSalida == 'b'){
TCCR1A &= ~(1 << COM1B1);
#ifdef EST_AT90
DDRC &= ~(1 << PC5);
#else
DDRD &= ~(1 << PD4);
#endif
}
#ifdef EST_AT90
else if (codigoSalida == 'c'){
TCCR1A &= ~(1 << COM1C1);
//DDRB &= ~(1 << PB7);
}
#endif
}
}else{
//ON, activar salida
if( codigoTimer == '0' ){
//NOP
}else{
//if(codigoTimer == '1')
if(codigoSalida == 'a'){
TCCR1A |= (1 << COM1A1); //Modo: PWM no invertido
TCCR1A &= ~(1 << COM1A0);
#ifdef EST_AT90
DDRC |= (1 << PC6); //Dejar el pin como salida
#else
DDRD |= (1 << PD5);
#endif
}else if (codigoSalida == 'b'){
TCCR1A |= (1 << COM1B1);
TCCR1A &= ~(1 << COM1B0);
#ifdef EST_AT90
DDRC |= (1 << PC5);
#else
DDRD |= (1 << PD4);
#endif
}
#ifdef EST_AT90
else if (codigoSalida == 'c'){
}
}
return true;
}
#endif
TCCR1A |= (1 << COM1C1);
TCCR1A &= ~(1 << COM1C0);
DDRB |= (1 << PB7);
}
bool getEstadoPWM(char codigoTimer, char codigoSalida){
99
bool estado=false;
if(codigoTimer=='1'){
switch(codigoSalida){
case 'a':
estado = (TCCR1A & (3<<COM1A0) )==(2<<COM1A0); //Modo: PWM no invertido
break;
case 'b':
estado = (TCCR1A & (3<<COM1B0) )==(2<<COM1B0);
break;
#ifdef EST_AT90
case 'c':
estado = (TCCR1A & (3<<COM1C0) )==(2<<COM1C0);
break;
#endif
}
}
return estado;
}
#endif // ESTCOMMON_H_INCLUDED
uart_ucr.h (AT90USB162 y ATMEGA16)
/****************************************************************************
uart_ucr.h: funciones para uso de la UART, para microcontroladores AVR
Basadas en el código de
1) Volker Oth "UART library"
Date: 5/1999
http://8bit.at/avr
2) Escuela de Ing. Eléctrica, UCR
****************************************************************************/
#include <stdbool.h>
#ifndef UART_ucr_H
#define UART_ucr_H
/* set baud rate here */
#define UART_BAUD_RATE 19200
#define ESC 0x1b
#define UART_BUF_SIZE 25
#ifndef F_CPU
#define F_CPU 8000000
#endif
/* automatically calculate baud register value */
#define UART_BAUD_SELECT (F_CPU/(UART_BAUD_RATE*16l)­1)
/* prototypes */
void uart_init(void);
void uart_clr(void);
void uart_nl(void);
bool uart_putchar(uint8_t c);
bool uart_getchar(uint8_t* c);
bool uart_putstr(char s[]);
#endif
uart_ucr.c (AT90USB162 y ATMEGA16)
/****************************************************************************
uart_ucr.c: funciones para uso de la UART, para el AT90USB162 o ATMEGA16.
Definir la macro USART_AT90USB162 o USART_ATMEGA16 al compilar con bandera
" ­Dmi_macro" de gcc
1) Volker Oth "UART library"
Date: 5/1999
http://8bit.at/avr
2) Escuela de Ing. Eléctrica, UCR
****************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
100
#include "uart_ucr.h"
/* uart globals */
volatile uint8_t uart_txd_buf_cnt;
volatile uint8_t uart_rxd_buf_cnt;
uint8_t *uart_txd_in_ptr, *uart_txd_out_ptr;
uint8_t *uart_rxd_in_ptr, *uart_rxd_out_ptr;
uint8_t UART_CLR[] = {ESC, '[','H', ESC, '[', '2', 'J',0};
uint8_t UART_NL[] = {0x0d,0x0a,0};
uint8_t uart_txd_buffer[UART_BUF_SIZE];
uint8_t uart_rxd_buffer[UART_BUF_SIZE];
static unsigned int paso=0;
#ifndef USART_AT90USB162
#define USART_ATMEGA16
#endif
void uart_init(void)
/* initialize uart */
{
/* enable RxD/TxD, receive complete int */
//Registro UCSRB: Usart Control and Status Register B
//RXCIE: RX Complete Interrupt Enable
//RXEN: Receiver Enable
//TXEN: Transmitter Enable
#if defined USART_ATMEGA16
UCSRB=(1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
#elif defined USART_AT90USB162
UCSR1B=(1<<RXCIE1)|(1<<RXEN1)|(1<<TXEN1);
#endif
/* set baud rate */
//Registro UCSRA: Usart Control and Status Register A
#if defined USART_ATMEGA16
UCSRA=0;
UBRRH= (uint8_t) (UART_BAUD_SELECT >> 8);
UBRRL= (uint8_t) UART_BAUD_SELECT;
#elif defined USART_AT90USB162
UCSR1A=0;
UBRR1= (uint16_t) UART_BAUD_SELECT;
#endif
uart_txd_in_ptr = uart_txd_out_ptr = uart_txd_buffer;
uart_rxd_in_ptr = uart_rxd_out_ptr = uart_rxd_buffer;
uart_txd_buf_cnt = 0;
uart_rxd_buf_cnt = 0;
paso=1;
}
/* Interrupt handler for Uart Data Buffer Empty Interrupt */
#if defined USART_ATMEGA16
ISR(USART_UDRE_vect)
#elif defined USART_AT90USB162
ISR(USART1_UDRE_vect)
#endif
{
if (uart_txd_buf_cnt > 0) {
#if defined USART_ATMEGA16
UDR=*uart_txd_out_ptr; /* write byte to data buffer */
#elif defined USART_AT90USB162
UDR1=*uart_txd_out_ptr;
#endif
if (++uart_txd_out_ptr >= uart_txd_buffer + UART_BUF_SIZE) /* Pointer wrapping */
uart_txd_out_ptr = uart_txd_buffer;
if(­­uart_txd_buf_cnt == 0) /* if buffer is empty: */
{
/* disable UDRIE int */
#if defined USART_ATMEGA16
UCSRB &= ~(1<<UDRIE);
#elif defined USART_AT90USB162
UCSR1B &= ~(1<<UDRIE1);
#endif
}
}
}
#if defined USART_ATMEGA16
ISR(USART_RXC_vect)
#elif defined USART_AT90USB162
ISR(USART1_RX_vect)
#endif
/* signal handler for receive complete interrupt */
{
#if defined USART_ATMEGA16
*uart_rxd_in_ptr = UDR; /* read byte from receive register */
#elif defined USART_AT90USB162
101
*uart_rxd_in_ptr = UDR1;
#endif
uart_rxd_buf_cnt++;
if (++uart_rxd_in_ptr >= uart_rxd_buffer + UART_BUF_SIZE) /* Pointer wrapping */
uart_rxd_in_ptr = uart_rxd_buffer;
}
bool uart_getchar(uint8_t* c)
{
if (uart_rxd_buf_cnt>0) {
cli();
uart_rxd_buf_cnt­­;
*c = *uart_rxd_out_ptr; /* get character from buffer */
if (++uart_rxd_out_ptr >= uart_rxd_buffer + UART_BUF_SIZE) /* pointer wrapping */
uart_rxd_out_ptr = uart_rxd_buffer;
sei();
return true;
}
else
return false; /* buffer is empty */
}
bool uart_putchar(uint8_t c)
{
if (!paso) return 0;
while (!(uart_txd_buf_cnt<UART_BUF_SIZE))
;
cli();
uart_txd_buf_cnt++;
*uart_txd_in_ptr = c; /* put character into buffer */
if (++uart_txd_in_ptr >= uart_txd_buffer + UART_BUF_SIZE) /* pointer wrapping */
uart_txd_in_ptr = uart_txd_buffer;
#if defined USART_ATMEGA16
UCSRB |= (1<<UDRIE); /* enable UDRIE int */
#elif defined USART_AT90USB162
UCSR1B |= (1<<UDRIE1);
#endif
sei();
return 1;
}
bool uart_putstr(char s[])
{
char *c = s;
while (*c)
if (uart_putchar(*c))
c++;
else return 0;
return 1;
}
void uart_clr(void)
/* Send a 'clear screen' to a VT100 terminal */
{
uart_putstr( (char*) UART_CLR);
}
void uart_nl(void)
/* Send a 'new line' */
{
uart_putstr( (char*) UART_NL);
}
estacion.php (GadgetPC )
<!­­ ***************************************************************** ********************************************************************** estacion.php: página inicial de la aplicación. Brinda alguna información general sobre el sistema. ********************************************************************** ****************************************************************** ­­> <html> <head> <?php include("estacionHead.html") ?> <title>Estación Remota</title> 102
</head> <body> <?php
$GLOBALS["NOMBRE_EN_MENU"]="Inicio"; include("estacionMenu.php") ?> <h2>Bienvenido a la Estación Remota</h2> <p>Este sitio está hospedado en la GadgetPC y permite el control de alto nivel del hardware de sensado y actuación implementado con el microcontrolador AT90USB162.
</p> <p>Actualmente están habilitadas las funciones de lectura y escritura digital, configuración de entradas y salidas, control de PWM y conversión analógico digital. Puede navegar por los distintos modos de uso utilizando el menú en la parte superior de esta página. </p> <table> <tr> </tr> <tr> </tr> <?php /* */ ?> </body> </html> <?php ?>
<th>Algunos datos...</th> <td>PATH de <b>est_ctrl</b>:</td> <td><?php /*echo $EST_CTRL;*/ echo $EST_CTRL; ?></td> echo "<tr><td>Estado del Microcontrolador</td><td>"; if( microcontroladorOK() ){ echo "<p style=\"color:blue\">EN LÍNEA</p>"; } else{ echo "<p style=\"color:red\">DESCONECTADO</p>"; }
echo "</td></tr>"; $whoami=array(); $groups=array(); exec("whoami",$whoami); exec("groups",$groups); echo "<tr><td>whoami</td><td>$whoami[0]</td></tr>"; echo "<tr><td>groups</td><td>$groups[0]</td></tr>"; </table> function microcontroladorOK(){ global $EST_CTRL; exec($EST_CTRL." rb",$respuesta,$valorDeRetorno); if($valorDeRetorno != 0){ return false; } else{ return true; } } est_ctrl.c (GadgetPC )
/******************************************************************************
est_ctrl.c: "driver" simple para intercambiar mensajes con el microcontrolador
usando el protocolo de bajo nivel del Cap. 4
******************************************************************************/
#include <termios.h>
//Funciones para configurar el puerto serial
#include <stdio.h> //Funciones basicas de I/O
#include <stdlib.h> //Funciones miscelaneas comunes
#include <string.h> //Manipulacion de cadenas de caracteres
#include <unistd.h> //Funciones estandar de unix, en particular read() y write()
#include <fcntl.h> //Funciones para control de archivo (open(),fcntl())
#define PUERTOSERIAL "/dev/ttyEST"
int main(int argc, char* argv[])
{
int fd,longitudRespuesta;
//termios: estructura que especifica los parametros
//del puerto serial
struct termios paramsPuerto = {0};
char bufIn[255],bufOut[255];
103
//...
//open(), de unistd.h, abre un archivo en un modo especifico,
//en este caso lectura/escritura
fd = open(PUERTOSERIAL, O_RDWR);
if (fd < 0)
{
printf("ERROR FATAL: no se pudo abrir el puerto serial virtual\n");
exit(1); //exit(), de stdlib.h, termina el programa y devuelve un valor
//al ambiente, en este caso un codigo de error
}
//...
/*
BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
CS8 : 8n1 (8bit,no parity,1 stopbit)
CLOCAL : local connection, no modem contol
CREAD : enable receiving characters
*/
paramsPuerto.c_cflag = B115200 | CS8 | CREAD | CLOCAL;
/*
IGNPAR : ignore bytes with parity errors
ICRNL : map CR to NL (otherwise a CR input on the other computer
will not terminate input)
otherwise make device raw (no other input processing)
*/
paramsPuerto.c_iflag = IGNPAR | ICRNL;
/* Salida "raw" (sin ningun procesamiento adicional) */
paramsPuerto.c_oflag = 0;
/*
ICANON : entrada canonica (orientada a lineas)
disable all echo functionality, and don't send signals to calling program
*/
paramsPuerto.c_lflag = ICANON;
/* "Limpiar" (flush) la linea y cargar los nuevos parametros */
tcflush(fd, TCIOFLUSH);
//de termios.h
tcsetattr(fd,TCSANOW,&paramsPuerto); //de termios.h
/* Configuracion del puerto concluida. Procesar comandos y generar salidas. */
int i;
for(i=1; i<argc; i++){
}
}
size_t longitudArg = strlen(argv[i]);
strcpy(bufOut, argv[i]);
bufOut[longitudArg]='\n';
bufOut[longitudArg+1]=0;
write(fd, bufOut, strlen(bufOut));
//de unistd.h
longitudRespuesta = read(fd,bufIn,20); //de unistd.h
bufIn[longitudRespuesta]=0;
printf("%s",bufIn);
close(fd);
//de unistd.h
exit(0); //de stdlib.h
ESdigital.php (GadgetPC )
<!­­ ***************************************************************** ********************************************************************** ESDigital.php: lectura y escritura digital de puertos, y configuración de entradas/salidas. Se pueden leer los valores de PINx, y leer/escribir los valores de PORTx y DDRx de los puertos del micro AT90USB162 ********************************************************************** ****************************************************************** ­­> <html> <head> <?php include("estacionHead.html")?> <title>Estación Remota</title> </head> <body> <?php $GLOBALS["NOMBRE_EN_MENU"]="E/S Digital"; include("estacionMenu.php"); /******************************************************************** Funciones para presentacion de la forma *********************************************************************/ define(NUM_BITS,8); 104
$funcionesPines = array( "B" => array("IO","IO","IO","IO","IO","IO","IO","IO"), "C" => array("­","­","IO","­","IO","IO","IO","IO"), "D" => array("IO","IO","CAD RX","CAD TX","IO","IO","IO","IO") ); $valoresPIN = array( "B" => array(0,0,0,0,0,0,0,0), "C" => array(0,0,0,0,0,0,0,0), "D" => array(0,0,0,0,0,0,0,0) ); $valoresPORT = array( "B" => array(0,0,0,0,0,0,0,0), "C" => array(0,0,0,0,0,0,0,0), "D" => array(0,0,0,0,0,0,0,0) ); $valoresDDR = array( "B" => array(0,0,0,0,0,0,0,0), "C" => array(0,0,0,0,0,0,0,0), "D" => array(0,0,0,0,0,0,0,0) ); function imprimirContenidosFormaESDigital(){ global $ERROR_FATAL_MICROCONTROLADOR; $micro_OK = cargarValoresDesdeMicro(); if( !$micro_OK ){ echo "[Error] $ERROR_FATAL_MICROCONTROLADOR"; } if($_GET["submit"]){ list($comando,$puerto) = explode(" ",$_GET["submit"]); switch($comando){ case "Leer": ejecutarLectura($puerto); break; case "Escribir": ejecutarEscritura($puerto); break; case "Configurar": ejecutarConfiguracion($puerto); break; } $micro_OK = cargarValoresDesdeMicro(); if( !$micro_OK ){ echo "[Error] $ERROR_FATAL_MICROCONTROLADOR"; }
} echo "<table class=\"centered\">"; foreach(array("B","C","D") as $puerto){ echo "<tr><th> Puerto $puerto </th></tr>"; echo "<tr><td>",imprimirControlesES($puerto),"</td></tr>"; } echo "</table>"; } function imprimirControlesES($puerto){ echo "<table><tr>"; for($i=NUM_BITS­1; $i>=0; $i­­){ echo "<td>",imprimirControlPin($puerto,$i),"</td>"; } echo "<td>",imprimirBotonesSubmitPin($puerto),"</td>"; echo "</tr></table>\n"; } function imprimirControlPin($puerto,$pin){ global $funcionesPines; global $valoresPIN; $esIODigital = ($funcionesPines[$puerto][$pin] == "IO");
echo "<table> <tr><td class=\"centeredText",$esIODigital?'"':"_grayedOut\"", "style=\"width:6em\" style=\"height:30px\">", $valoresPIN[$puerto][$pin],"</td></tr> <tr><td class=\"centeredText",$esIODigital?'"':"_grayedOut\"", "style=\"height:30px\">", $esIODigital?imprimirCheckboxEscrituraPin($puerto,$pin):"­","</td></tr> <tr><td class=\"centeredText",$esIODigital?'"':"_grayedOut\"", "style=\"height:30px\">", $esIODigital?imprimirRadioButtonES($puerto,$pin):$funcionesPines[$puerto][$pin], "</td></tr></table>"; } 105
function imprimirBotonesSubmitPin($puerto){ echo "<table> <tr><td><input class=\"submitInput\" style=\"width:9em\" type=\"submit\" name=\"submit\" value=\"","Leer $puerto","\"/></td></tr> <tr><td><input class=\"submitInput\" style=\"width:9em\" type=\"submit\" name=\"submit\" value=\"","Escribir $puerto","\" /></td></tr> <tr><td><input class=\"submitInput\" style=\"width:9em\" type=\"submit\" name=\"submit\" value=\"","Configurar $puerto","\" /></td></tr> </table>";
} function imprimirCheckboxEscrituraPin($puerto,$pin){ $__NOMBRE_ELEMENTO__ = "p".$puerto.$pin; global $valoresPORT; echo "<input type=\"checkbox\" name=\"$__NOMBRE_ELEMENTO__\" value=\"1\"", ($valoresPORT[$puerto][$pin]==1)?'checked="checked"':'' ,">"; } function imprimirRadioButtonES($puerto,$pin){ $__NOMBRE_ELEMENTO__ = "d".$puerto.$pin; global $valoresDDR; echo "<input type=\"radio\" name=\"$__NOMBRE_ELEMENTO__\" value=\"0\" ", ($valoresDDR[$puerto][$pin]==0)?('checked="checked"'):('') ,">E <input type=\"radio\" name=\"$__NOMBRE_ELEMENTO__\" value=\"1\" ", ($valoresDDR[$puerto][$pin]==1)?('checked="checked"'):('') ,">S"; }
function esIODigital($port,$pin){ return $funcionesPines[$port][$pin]=="IO"; }
/******************************************************************** Funciones para acceso al microcontrolador *********************************************************************/ function cargarValoresDesdeMicro(){ global $puertosMicro; global $valoresPIN; global $valoresPORT; global $valoresDDR; global $funcionesPines; global $EST_CTRL; $cmd = $EST_CTRL." rb rc rd wbG wcG wdG dbG dcG ddG p1aG p1bG p1cG"; exec($cmd,$respuestasMicro,$returnVal); if( $returnVal != 0 ){ return false;//Ocurrio un error grave con el acceso al microcontrolador } //Cargar los valores para los registros de interés de c/u de los 3 puertos for($i=0; $i<=2; $i++){ //Cargar los valores de PINx $valorBinario = binaryStr( substr($respuestasMicro[$i],4,2), 8); for($j=7; $j>=0; $j­­){ $valoresPIN[$puertosMicro[$i]][$j]=$valorBinario[7­$j]; } //Cargar los valores de PORTx $valorBinario = binaryStr( substr($respuestasMicro[$i+3],4,2), 8); for($j=7; $j>=0; $j­­){ $valoresPORT[$puertosMicro[$i]][$j]=$valorBinario[7­$j]; } } //Cargar los valores de DDRx $valorBinario = binaryStr( substr($respuestasMicro[$i+6],4,2), 8); for($j=7; $j>=0; $j­­){ $valoresDDR[$puertosMicro[$i]][$j]=$valorBinario[7­$j]; } //Cargar los valores de funciones especiales asignadas a puertos $funcionesPines["C"][6]= (substr($respuestasMicro[9],3,1)=="1")?"PWM 1A":"IO"; $funcionesPines["C"][5]= (substr($respuestasMicro[10],3,1)=="1")?"PWM 1B":"IO"; $funcionesPines["B"][7]= (substr($respuestasMicro[11],3,1)=="1")?"PWM 1C":"IO";
return true; } function ejecutarLectura($puerto){ global $EST_CTRL; global $valoresPIN; $cmd = $EST_CTRL." r".strtolower($puerto); //echo "execing: ",$cmd,"</br>"; exec( $cmd,$respuestasMicro,$returnVal); } function ejecutarEscritura($puerto){ global $EST_CTRL; global $funcionesPines; //Obtener el nuevo valor del puerto a partir de la forma 106
} $nuevoValor = 0; for($i=0; $i<NUM_BITS; $i++){ $bit = $_GET["p$puerto$i"]; if($bit) $nuevoValor |= ($bit << $i); } $valorHex = (($nuevoValor<=15)?'0':'').dechex($nuevoValor); $letraPuerto=strtolower($puerto); $cmd = $EST_CTRL." w$letraPuerto$valorHex"; //echo "execing: ",$cmd,"</br>"; exec( $cmd, $respuestasMicro, $returnVal); function ejecutarConfiguracion($puerto){ global $EST_CTRL; global $funcionesPines; } //Obtener el nuevo valor del puerto a partir de la forma $nuevoValor = 0; for($i=0; $i<NUM_BITS; $i++){ //Si el puerto es una IO comun, se lee el nuevo estado de la forma if($funcionesPines[$puerto][$i]=="IO"){ $bit = $_GET["d$puerto$i"]; if($bit) $nuevoValor |= ($bit << $i); //Si el puerto es una salida PWM, entonces debe quedar como salida }else if( substr($funcionesPines[$puerto][$i],0,3)=="PWM"){ $nuevoValor |= (1 << $i); } } $valorHex = (($nuevoValor<=15)?'0':'').dechex($nuevoValor); $letraPuerto=strtolower($puerto); $cmd = $EST_CTRL." d$letraPuerto$valorHex"; //echo "execing: ",$cmd,"</br>"; exec( $cmd, $respuestasMicro, $returnVal); ?> <fieldset> <legend>E/S Digital</legend> <form action="" name="formaPWM" method="get"> <?php imprimirContenidosFormaESDigital() ?> </form> </fieldset> </body> </html>
pwm.php (GadgetPC )
<!­­ ***************************************************************** ********************************************************************** pwm.php: control de los periodos y ciclos de trabajo de los timers y sus respectivas salidas PWM Escribe el valor de prescala, periodo y ciclo de trabajo ********************************************************************** ****************************************************************** ­­> <html> <head> <?php include("estacionHead.html") ?> <title>Estación Remota</title> </head> <body> <?php
$GLOBALS["NOMBRE_EN_MENU"]="PWM";
include("estacionMenu.php"); $salidasPWM1 = array("A","B","C"); define(ON,"1"); define(OFF,"0"); class Timer { public $id; public $periodo; public $salidasPWM; public $valoresD; public $valoresActivacion; function __construct($timerId,$nombresSalidasPWM){ $this­>id=$timerId; 107
$this­>salidasPWM = $nombresSalidasPWM; foreach($this­>salidasPWM as $salida){ $this­>valoresD[$salida]=50; } }//end __construct function imprimirTimer(){ echo "<table class=\"centered\"> <tr><td> <table style=\"width:100%\"> <tr> <th colspan=\"3\"><h2>Timer ",$this­>id,"</h2></th> </tr>
<tr>
<td>   Periodo</td> <td style=\"text­align:right\">", $this­>imprimirCampoPeriodo($id), "</td> <td class=\"rightAlignedText\">ms (0.01­16500)</td> </tr> </table> </td></tr> <tr><td> <table align=\"center\"> <tr>"; foreach($this­>salidasPWM as $salida){ echo "<td>",$this­>imprimirControlSalida($salida),"</td>"; } echo "</tr> </table> </td></tr> <tr><td style=\"text­align:center\">
<input type=\"submit\" name=\"submit\" value=\"Ajustar PWM ",$this­>id,"\"/> </td></tr>
</table>";
}//end function imprimirTimer() function imprimirControlSalida($nombreSalida){ $__NOMBRE__ = $this­>id.$nombreSalida; echo "<table> <tr> <th>Salida $nombreSalida</th> </tr> <tr> <td><table><tr> <td>D</td> <td><input style=\"text­align:right\" type=\"text\" size=\"4\" name=\"D$__NOMBRE__\" value=\"", $this­>valoresD[$nombreSalida], "\" /></td> <td> <select name=\"unidades$__NOMBRE__\"> <option ", ($_GET["unidades$__NOMBRE__"]=="%")?"selected=\"selected\"":"" ,">%</option> <option ", ($_GET["unidades$__NOMBRE__"]=="ms")?"selected=\"selected\"":"" ,">ms</option> </select>
</td> </tr></table> </td> </tr> <tr> <td>
<table width=\"100%\"> <tr>
<td>Activar</td> <td><input type=\"checkbox\" name=\"activar$__NOMBRE__\" value=\"1\" ", ($this­>valoresActivacion[$nombreSalida]=="1")?"checked=\"checked\"":"",
" /></td> </tr> </table> </td> </tr> </table>"; }//end function imprimirControlSalida function imprimirCampoPeriodo() { echo "<input style=\"text­align:right\" type=\"text\" name=\"periodoPWM",$this­>id,"\" value=\"",$this­>periodo,"\" size=\"10\" />"; }//end function imprimirCampoPeriodo() function ejecutarAjustarPeriodo($salidaPWM, $periodo_en_ms){ global $EST_CTRL; //Obtener los parametros para el microcontrolador convertirPeriodo($periodo_en_ms, $valorRegistroPeriodo, $codigoPrescala); 108
$cmd = $EST_CTRL . " T$this­>id".$codigoPrescala.hexStr($valorRegistroPeriodo); exec( $cmd, $respuesta); }//end function ejecutarAjustarPeriodo function ejecutarAjustarCicloDeTrabajo($salidaPWM, $periodo_en_ms, $cicloDeTrabajo, $unidadesCicloDeTrabajo){ global $valoresPS; global $EST_CTRL; convertirPeriodo($periodo_en_ms, $valorRegistroPeriodo, $codigoPrescala); if($unidadesCicloDeTrabajo=="ms"){ $valorRegistroCiclo = (int)(4000*$cicloDeTrabajo/$valoresPS[$codigoPrescala]); }else if($unidadesCicloDeTrabajo=="%"){ $valorRegistroCiclo = (int)($cicloDeTrabajo/100*$valorRegistroPeriodo); } $cmd = $EST_CTRL . " D$this­>id".strtolower($salidaPWM).hexStr($valorRegistroCiclo); exec( $cmd, $respuesta); //echo "ejecutarAjustarCicloDeTrabajo: $cmd</br> $respuesta[0] </br>"; }//end function ejecutarAjustarCicloDeTrabajo function ejecutarSetEstadoSalidaPWM($letraSalida,$estado){ global $EST_CTRL;
$cmd = "$EST_CTRL p$this­>id".strtolower($letraSalida).$estado; exec( $cmd, $respuesta); //echo "ejecutarSetEstadoSalidaPWM: $cmd</br> $respuesta[0] </br>"; }//end function ejecutarSetEstadoSalidaPWM }//end Class Timer //Posibles valores de Prescala $valoresPS=array(0=>1,1=>1,8,64,256,1024); //Maximos periodos que se pueden lograr con las distintas prescalas $maxPeriodoSegunPrescala = array(1=>16.38,131.0,1048.0,4194.0,16500.0); //Minimo periodo (no nulo) $minPeriodo=0.01; //Maximo periodo permitido $maxPeriodo=$maxPeriodoSegunPrescala[5]; //Valores de periodo, por timer $valoresT = array(1=>0); //Valores de ciclo de trabajo, por timer y por salida $valoresD = array(1 => array("A"=>50, "B"=>50, "C"=>50)); //Valores de estado (on,off), por timer y por salida //Estos son solo valores default para la forma, realmente se leen del micro //durante el funcionamiento normal del sistema $valoresActivacion = array(1 => array("A"=>1, "B"=>0, "C"=>1)); //Declarar las variables para cada timer $timers = array(
"1"=>new Timer("1",array("A","B","C")), "2"=>new Timer("2",array("A","B"))
); ?> <fieldset> <legend>Salidas PWM</legend> <form action="" name="formaPWM" method="get"> <?php imprimirContenidosFormaPWM() ?> </form> </fieldset> </body> </html> <?php function imprimirContenidosFormaPWM(){ global $timers; global $puertosMicro; global $salidasPWM1; global $ERROR_FATAL_MICROCONTROLADOR; //Cargar configuraciones actuales de períodos, ciclos de trabajo //y estado de activación de las salidas $microcontroladorOK = cargarConfigPWM(); if( !$microcontroladorOK ){ echo "[Error] $ERROR_FATAL_MICROCONTROLADOR"; } foreach($timers as $timer){ /*************/ if($_GET["submit"]=="Ajustar PWM $timer­>id" && $microcontroladorOK){ //echo "Ajustando Timer $timer­>id"; //Leer el nuevo valor de periodo desde la forma $valorPeriodo = $_GET["periodoPWM$timer­>id"]; if( !validarPeriodo($valorPeriodo,$err) ){ echo "[Validacion del Periodo $timer­>id] $err</br>"; 109
continue; } foreach($timer­>salidasPWM as $salidaPWM){ if($_GET["activar$timer­>id$salidaPWM"]=="1"){ //Leer el valor del ciclo de trabajo nuevo para la salida $valorD = $_GET["D$timer­>id$salidaPWM"]; //Obtener unidades: ms o % $unidadesD = $_GET["unidades$timer­>id$salidaPWM"]; if( !validarCicloDeTrabajo($valorD,$unidadesD,$valorPeriodo,$err) ){ echo "[Validacion del Ciclo de Trabajo $timer­>id$salidaPWM] $err</br>"; continue; } $timer­>ejecutarAjustarPeriodo($salidaPWM,$valorPeriodo); $timer­>ejecutarAjustarCicloDeTrabajo($salidaPWM,$valorPeriodo,$valorD,$unidadesD); $timer­>ejecutarSetEstadoSalidaPWM($salidaPWM,ON); }else{ $timer­>ejecutarSetEstadoSalidaPWM($salidaPWM,OFF); }//end if $_GET["activar...//end foreach($timer­>salidas } }//end if $_GET["submit.. /*************/ }//end foreach $timer cargarConfigPWM(); imprimirTimers(); }//end function function imprimirTimers(){ global $timers; echo "<table class=\"centered\"> <tr>"; foreach($timers as $timer){ echo "<td>",$timer­>imprimirTimer(),"</td>\n"; } echo "</tr></table>"; } function validarPeriodo($periodo_ms,&$errores){ global $maxPeriodoSegunPrescala; global $minPeriodo; global $maxPeriodo; if( $periodo_ms < $minPeriodo){ $errores="ERROR: valor menor de $minPeriodo";
return false; }else if($periodo_ms > $maxPeriodo){ $errores="ERROR: valor mayor de $maxPeriodo";
return false; } return true; } function convertirPeriodo($periodo_ms,&$ICR,&$codigoDePrescala){ global $valoresPS; global $maxPeriodo; global $minPeriodo; global $maxPeriodoSegunPrescala; //Hallar la prescala adecuada, la más pequeña que permita lograr //el periodo buscado, para obtener el maximo valor del registro ICR posible, //y así lograr la resolución más buena. $codigoDePrescala = 1; while( $periodo_ms > $maxPeriodoSegunPrescala[$codigoDePrescala] && $periodo_ms <= $maxPeriodo ) { $codigoDePrescala++; } //Ahora la prescala minima necesaria esta en codigoDePrescala // La ecuacion del periodo es T = (2 * valor_de_Prescala * ICR ) / f_clk // o bien: // ICR = f_clk*T / (2*valor_de_Prescala) $ICR = (int) ( 8000000/(2 * $valoresPS[$codigoDePrescala]) * ($periodo_ms/1000) ); return true; } function validarCicloDeTrabajo($valorD,$unidadesD,$valorPeriodo,&$errores){ $D = filter_var($valorD,FILTER_VALIDATE_FLOAT); if($D===false){ $errores="No es un numero decimal"; return false; } if( $D < 0){ $errores="No se admiten valores negativos para D"; 110
} return false; } if( $unidadesD == "%" ){ if($D > 100){ $errores="No se admite D>100%"; return false; } }else if( $unidadesD == "ms"){ if($D > $valorPeriodo){ $errores="No se admite D>T"; return false; } } return true; function cargarConfigPWM(){ global
$valoresPS; global $EST_CTRL; global
$timers; $cmd = "$EST_CTRL "; foreach($timers as $timerKey=>$timerVal){ $cmd .= "T{$timerVal­>id}G "; foreach($timerVal­>salidasPWM as $salida){ $cmd .= "D{$timerVal­>id}".strtolower($salida)."G p{$timerVal­>id}".strtolower($salida)."G "; } } //echo $cmd; //return; exec($cmd, $respuestas, $valorDeRetorno); if($valorDeRetorno!=0){ return false; } $i=0;//Variable para contar la respuesta que se está procesando foreach($timers as $timerKey=>$timerVal){ //Obtener el periodo y prescala del timer list($ok_o_err,$codigoPrescala,$valorHexDeT) = explode(" ",$respuestas[$i++]); $valorRegistroT = hexdec($valorHexDeT); if($codigoPrescala=="0"){ $timerVal­>periodo = 0.01; }else{ $timerVal­>periodo = hexdec($valorHexDeT)*($valoresPS[$codigoPrescala])/4000; }
//Obtener el ciclo de trabajo y prescala de cada salida foreach($timerVal­>salidasPWM as $salida){ $valorHexDeD = substr($respuestas[$i++],3); $valorActivacion = substr($respuestas[$i++],3); } return true; } ?> $valorRegistroD = hexdec($valorHexDeD); if($_GET["unidades$timerVal­>id$salida"]=="ms"){ $valorDeD = ($valorRegistroD)*($valoresPS[$codigoPrescala])/4000; }else{ if($valorRegistroT!=0) $valorDeD = ($valorRegistroD)/($valorRegistroT)*100; else $valorDeD = 0; } $timerVal­>valoresD[$salida] = round($valorDeD,2); $timerVal­>valoresActivacion[$salida] = $valorActivacion; } 111
cad.php (GadgetPC )
<!­­ ***************************************************************** ********************************************************************** cad.php: lectura de los canales analógicos presentes en la estacion (en el ATMEGA) ********************************************************************** ****************************************************************** ­­> <html> <head> <?php include("estacionHead.html") ?> <title>Estación Remota</title> </head> <body> <?php
$GLOBALS["NOMBRE_EN_MENU"]="CA/D";
include("estacionMenu.php"); $opcionesCanales = array("0","1","2","3","4","5","6","7","Todos"); ?> <fieldset> <legend>Lectura Analogica</legend> <form> <table> <tr> <td>Formato:</td> <td class="centeredText" colspan="2"> <?php imprimirSelectorFormato(); ?></td>
</tr> <tr> <td>Canal CAD:</td> <td><?php imprimirSelectorLecturaSimple(); ?></td> <td><input type="submit" name="submitCAD" value="Tomar Lectura"/></td> </tr>
</table> <?php generarRespuestaCAD() ?> </body> </html> </form> </fieldset> <?php function imprimirSelectorLecturaSimple(){ global $opcionesCanales;
echo "<select name=\"canalCAD\">\n"; foreach($opcionesCanales as $opcion){ echo '<option ', ($_GET["canalCAD"]==$opcion)?('selected="selected"'):(''), ">$opcion</option>\n"; } echo "</select>\n";
} function imprimirSelectorFormato(){ echo "<input type=\"radio\" name=\"formato\" value=\"entero\" ", ($_GET["formato"]!="decimal")?"checked=\"checked\"":'', "/>Entero&nbsp&nbsp", "<input type=\"radio\" name=\"formato\" value=\"decimal\"", ($_GET["formato"]=="decimal")?"checked=\"checked\"":'',"/>Con decimales"; } function generarRespuestaCAD(){ global $ERROR_FATAL_MICROCONTROLADOR; global $EST_CTRL; if($_GET["submitCAD"]=="Tomar Lectura"){ echo "<hr/>\n"; //Sintetizar el comando para est_ctrl $canal = $_GET["canalCAD"]; $cmd = ""; if($canal=="Todos"){ for($i=0; $i<=7; $i++){ $cmd .= "c$i "; } }else{ $cmd = "c$canal"; 112
} //Llamar a est_ctrl exec($EST_CTRL." $cmd",$respuestas,$valorRetorno); if($valorRetorno!=0){ $respuesta=$ERROR_FATAL_MICROCONTROLADOR; }else{ foreach($respuestas as &$respuesta){ if( substr($respuesta,0,3)=="OK " ){ $respuesta=ltrim(substr($respuesta,3),'0'); if(!$respuesta) $respuesta='0'; } } } //Generar salidas echo "<table>\n"; if($canal=="Todos"){ for($i=0; $i<=7; $i++){ echo "<tr> <th> Canal $i </th> <td class=\"cadResponse\">",aplicarFormato($respuestas[$i]),"</td> </tr>\n"; } }else echo "<tr> <th> Canal $canal </th> <td class=\"cadResponse\">",aplicarFormato($respuestas[0]),"</td> </tr>\n"; echo "</table>\n"; } } ?> function aplicarFormato($valor){ if($_GET["formato"]=="decimal"){ return sprintf("%.3f",$valor/1023*5.00); }else return $valor; } lecturaPuertos.php (GadgetPC )
<!­­ ***************************************************************** ********************************************************************** lecturaPuertos.php: lectura de un puerto (PINx) ********************************************************************** ****************************************************************** ­­> <html> <head> <?php include("estacionHead.html") ?> <title>Estación Remota</title> </head> <body> <?php
$GLOBALS["NOMBRE_EN_MENU"]="Lectura de Puertos"; include("estacionMenu.php") ?> <fieldset> <legend>Lectura de Puertos</legend> <?php imprimirContenidoForma(); ?> </fieldset> </body> </html> <?php function imprimirContenidoForma(){ ?> <form action="" name="formaLecturaPuertos" method="get"> <table> <tr> <td>Puerto:</td> <td><select name="puertoPorLeer"> <option <?php markIfSelected("B")?> >B</option> <option <?php markIfSelected("C")?> >C</option> <option <?php markIfSelected("D")?> >D</option> </select></td> <!­­Submit­­> <td><input type="submit" name="submitLeerPuerto" value="Leer"/></td> </tr> <?php imprimirRespuesta(); ?> </table> </form> <?php } function markIfSelected($portID){ if( strcmp( $_GET["puertoPorLeer"], $portID) == 0 ){ echo "selected=\"selected\""; } } 113
function imprimirRespuesta(){ if( $_GET["submitLeerPuerto"] ){ echo '<tr><td>Lectura: </td> <td>'. leerPuerto($_GET["puertoPorLeer"]) .'</td></tr>'; } } function leerPuerto($puerto){ //El comando basico para llamar al programa externo est_ctrl global $EST_CTRL; //Prepara el comando particular para est_ctrl $cmd="r".strtolower($puerto); $resp=array(); //Llamar a est_ctrl. Su salida queda en resp exec($EST_CTRL . " $cmd", $resp); //Si el micro respondio OK la respuesta esta en el 4to y 5to caracter, en hex if( strncmp($resp[0], "OK", 2) == 0){ return "0x".strtoupper(substr($resp[0],4,2)); }else { return $resp[0]; } ?> } escrituraPuertos.php (GadgetPC )
<!­­ ***************************************************************** ********************************************************************** escrituraPuertos.php: escritura a un puerto (PORTx) ********************************************************************** ****************************************************************** ­­> <html> <head> <?php include("estacionHead.html") ?> <title>Estación Remota</title> </head> <body> <?php
$GLOBALS["NOMBRE_EN_MENU"]="Escritura a Puertos"; include("estacionMenu.php") ?> <fieldset> <legend>Escritura a Puertos</legend> <?php imprimirContenidoForma(); ?> </fieldset> </body> </html> <?php function imprimirContenidoForma(){ ?> <form action="" name="formaEscrituraPuertos" method="get"> <table> <tr> <td>Puerto:</td> <td> <select name="puertoPorEscribir"> <option <?php markIfSelected("B")?> >B</option> <option <?php markIfSelected("C")?> >C</option> <option <?php markIfSelected("D")?> >D</option> </select> </td> </tr> <tr> <td>Nuevo Valor:</td> <td><input type="text" size="10" name="valorPorEscribir" value="0"/> </td> <td> <input type="submit" name="submitEscribirPuerto" value="Escribir Puerto"/> </td> <!­­Submit­­> </tr> <?php generarRespuesta(); ?> </form> </table> <?php } function markIfSelected($portID){ if( strcmp( $_GET["puertoPorEscribir"], $portID) == 0 ){ echo "selected=\"selected\""; 114
} } function generarRespuesta(){ if( $_GET["submitEscribirPuerto"] ){ echo '<tr><td>Respuesta</td><td>'; $nuevoValor = trim($_GET["valorPorEscribir"]); if( preg_match("/^[\da­fA­F]{1,2}$/",$nuevoValor) ){ echo escribirPuerto($_GET["puertoPorEscribir"],$nuevoValor); } else{ echo 'Error de formato'; } echo '</td></tr>'; } } function escribirPuerto($puerto, $valor){ global $EST_CTRL; $cmd = "w".strtolower($puerto).strtolower($valor); exec($EST_CTRL." $cmd",$respuesta); return trim($respuesta[0]); } ?>
estacionMenu.php (GadgetPC )
<!­­ ***************************************************************** ********************************************************************** estacionMenu.php: menu común a todas las páginas de la aplicación La página en que está el usuario se muestra encerrada en brackets [] ********************************************************************** ****************************************************************** ­­> <h1>Control de la Estación Remota</h1><hr/>  <a class="coolAnchor" href="estacion.php"><?php enBracket("Inicio"); ?></a>    <a class="coolAnchor" href="lecturaPuertos.php"><?php enBracket("Lectura de Puertos"); ?></a>    <a class="coolAnchor" href="escrituraPuertos.php"><?php enBracket("Escritura a Puertos"); ?></a>    <a class="coolAnchor" href="ESdigital.php"><?php enBracket("E/S Digital"); ?></a>    <a class="coolAnchor" href="pwm.php"><?php enBracket("PWM"); ?></a>    <a class="coolAnchor" href="cad.php"><?php enBracket("CA/D"); ?></a><hr/> <?php $EST_CTRL="/var/www/estacion/est_ctrl"; $ERROR_FATAL_MICROCONTROLADOR="ERROR FATAL: no se pudo establecer la comunicacion con el microcontrolador</br>\n"; $puertosMicro = array("B","C","D"); //Transforma una cadena de caracteres hex en una cadena de caracteres binarios //de al menos $numBits digitos function binaryStr($hexStr, $numBits=8){ $str = decbin( hexdec($hexStr) ); $zerosToAdd = $numBits­strlen($str); $leadingZeroes = "";
while($zerosToAdd­­) $leadingZeroes.="0"; return $leadingZeroes.$str; } //Transforma un valor en su representacion HEX de al menos $digits digitos function hexStr($value,$digits=4){ return sprintf("%0$digits"."x",$value); } function enBracket($nombreElementoMenu){ //Cada pagina que incluya a estacionMenu debe indicar su nombre de menu //definiendo la variable $GLOBALS["NOMBRE_EN_MENU"] según le corresponda. if( $GLOBALS["NOMBRE_EN_MENU"] == $nombreElementoMenu){ echo "[","$nombreElementoMenu","]"; }else{ echo " ",$nombreElementoMenu," "; } }
115
estacionHead.html (GadgetPC )
<!­­ ***************************************************************** ********************************************************************** estacionHead.html: encabezado común de todas las páginas ********************************************************************** ****************************************************************** ­­> <meta http­equiv="Content­Type" content="text/html; charset=utf­8"> <link rel="stylesheet" type="text/css" href="estacion.css" />
estacion.css (GadgetPC )
/* */ ****************************************************** estacion.css: hoja de estilo común a todas las páginas de control de estación. ****************************************************** body{ } background­color:#FFFFFF; font­size:0.8em; font­family:sans­serif; table{ } /*border:1px solid black;*/ table.centered{ margin­left:auto; margin­right:auto; } td{ font­size:0.7em; } td.rightAlignedText{ text­align:right; } td.cadResponse{ text­align:right; width:4em; } td.centeredText{ text­align:center; } td.centeredText_grayedOut{ text­align:center; background­color:#E6E6E6; }
th{ font­size:1em; background­color:#005C9A; color:#FFFFFF; } input{ } font­size:0.9em; select{ } font­size:0.9em; .coolAnchor{ font­weight:bold; /*font­size:1.6em;*/ /*font­family:monospace;*/ text­decoration:none; color:#005C7A; } 116
15 16 22 25 38 40 41 53 57 58 65 66 68 69 71 74 76 78 79 84 87 90 91
Descargar