Usb4all

De Proyecto Butiá
Revisión del 15:37 17 oct 2011 de Andres (Discusión | contribuciones) (7. Detalles de implementación en el firmware)

Saltar a: navegación, buscar

1. Motivación

La motivación de este proyecto se centra en lograr de una manera sencilla, la comunicación entre un sistema computador y un conjunto de dispositivos electrónicos no necesariamente pensados para interactuar con una computadora. Durante muchos años la única forma de interactuar con dispositivos externos desde a un computador personal (Personal Computer) (PC) fueron los puertos seriales y paralelos, esto llevo a que se utilizaran ampliamente en multiplicidad de dispositivos. Sus principales carácteristicas son su simplicidad de manejo vía software y facilidad de inclusión en distintos dispositivos. En la última década han aparecido nuevos medios o canales de comunicación, tales como Bluetooth, Fidelidad Inalámbrica (Wireless Fidelity) (WiFi), FireWire, Bus Universal en Serie (Universal Serial Bus) (USB), etc. Estos permitieron mejorar las velocidades de comunicación y calidad de datos y lentamente tornaron a los puertos paralelos y seriales en obsoletos, hasta llegar al día de hoy en que se obtienen sólo de manera opcional en los nuevos PC. Dentro de todas estas nuevas tecnologías que aparecieron la que tuvo mayor aceptación y difusión entre los usuarios fue el USB, debido a su facilidad y versatilidad de escenarios de uso. Esta simplicidad desde el punto de vista del usuario de los dispositivos USB tiene como contrapartida una mayor complejidad para los desarrolladores de software y hardware, lo cual es un gran problema al momento de interactuar con dispositivos electrónicos. Frente a estas adversidades que presenta el entorno actual aparece la necesidad de buscar una forma de reducir el grado de complejidad y conocimientos necesarios para poder desarrollar software que utilice la tecnología USB. Al mismo tiempo, se requiere una plataforma base que incorpore componentes de hardware reutilizables y que en conjunto formen una solución genérica para la comunicación con dispositivos electrónicos diversos. En el contexto de lo antedicho aparecen un conjunto de desafíos como son el desarrollo de controladores (drivers) y construcción de piezas de hardware que brinde una solución genérica reutilizable, desplazando el manejo de los casos particulares a componentes específicos. Esto proporciona el beneficio de evitar que cada vez que se necesite conectar un nuevo dispositivo, se comience desde cero. A su vez permite recuperar y potenciar la característica de facilidad de manejo que poseían los puertos seriales y paralelos, explotando todas las capacidades brindadas por USB.

2. Arquitectura (Firmware/Hardware)

La arquitectura esta compuesta por diferentes componentes, los cuales van a permitir el funcionamiento del sistema desde lo concerniente al hardware como las abstracciones necesarias para manejarlo mediante software. Los componentes principales son:

2.1 USB4all Baseboard

Fig.2.1.1: USB4all Baseboard

Es una placa de entrada/salida configurable que se conecta por USB con un sistema computador. El proyecto USB4all es mucho más que una placa de E/S, en sí es una forma de modelar sistemas embebidos, siendo la placa de E/S un componente que fue necesario construir para desarrollar la solución completa. Para la construcción de ésta placa se utilizó un microcontrolador PIC18F4550 de Microchip. Entre las características más destacadas en lo que concierne al proyecto se encuentra el soporte completo del estándar USB, pudiéndose utilizar varias de sus características, como ser diferentes tipos de transferencias y varios canales de comunicación (endpoints).

2.2 USB4all base firmware

Es el firmware más estático, brinda servicios a los usermodules para que puedan utilizar los recursos presentes en la baseboard (timmers, puerto USB, conversores A/D, etc) además de brindar las primitivas para el intercambio de mensajes entre el computador y el usermodule y realiza la gestión de los usermodules. Establece las bases para que los usermodules sean independientes del protocolo de comunicación computador/placa y de los detalles de hardware del microcontrolador utilizado. A su vez oferce un entorno de ejecución concurrente que permite instanciar de forma dinámica varios usermodules.

2.3 USB4all usermodule Son los componentes intercambiables del sistema que permiten encapsular la lógica de un dispositivo especifico y su protocolo de comunicación con las aplicaciones de usuario. Permite al usuario dar rápidamente soporte a un nuevo dispositivo de forma genérica, expandiendo de ésta manera las funcionalidades del USB4all basefirmware. Los user modules son los bloques principales sobre los que se construye la arquitectura USB4all. Exponen una API uniforme que es utilizada a modo de callbacks por el base firmware y también exponen los servicios que brindan los dispositivos. Es recomendable modelar cada uno de los dispositivos electrónicos conectados a la baseboard como un usermodule donde los servicios a exponer se mapean con las características del mismo, como ejemplo en el caso de un motor, sería esperable que expusiera servicios para moverse, cambiar la velocidad y el sentido.

Del lado del sistema computador se dispone de diferentes formas de interacción: Todas ellas implementan el protocolo USB4all, su objetivo es abstraer al usuario del mismo brindando una forma sencilla de utilizar el sistema, existiendo soporte para diferentes lenguajes de programación. El objetivo principal de las bibliotecas utilizadas en el computador son las de permitir utilizar los servicios de los usermodules. Esta forma de trabajo permite desarrollar la lógica de interacción entre los diferentes dispositivos electrónicos dentro del sistema computador, con lenguajes de mayor abstracción, mejores herramientas de desarrollo, permitiendo generar un código con un nivel de mantenibilidad y abstracción mayor al que se lograría si todo estuviera embebido en el microcontrolador.

Uno de los componentes más usados actualmente es el bobot que persigue un enfoque genérico al igual que el proyecto USB4all y permite acompañar la extensibilidad de la placa USB4all mediante el uso de ciertos componentes propios de la arquitectura llamados drivers.

3. Protocolo de Comunicación

El objetivo final del protocolo de comunicación es direccionar los mensajes provenientes desde la aplicación que ejecuta en el computador al usermodule correspondiente ejecutando en la placa USB4all. El protocolo está formado por un stack de protocolos.

Fig. 3.2 Protocolo USB4all

4 Entorno de desarrollo

Fig. 4.1:Entorno de desarrollor con Piklab en GNU/Linux
Fig. 4.2: Entorno de desarrollor con MPLab en GNU/Linux

El proyecto USB4all está desarrollado para el compilador C18 de Microchip, hoy día se dispone de un buen soporte del mismo para GNU/Linux puediendose descargar de [1] también se dispone de un entorno de desarrollo llamado MPLab el cual también se encuentra disponible para GNU/Linux en la misma dirección. Hace un tiempo atrás no existía compatibilidad para GNU/Linux de estas herramientas clásicas para los ambientes Windows en GNU/Linux. Debiendose recurrir a alternativas para trabajar en un ambiente GNU/Linux, una opción era Piklab, Piklab es un entorno de desarrollo para trabajar con microcontroladores PIC similar a lo que es MPLab. Era muy común utilizar la versión de C18 de windows emulada mediante wine en GNU/Linux debiendose configurar en las opciones de toolchain de PIKLab correspondientes a C18 para que apunten correctamente a los directorios donde se encuentra el compilador C18. En el repositorio puede encontrarse un proyecto piklab configurado de esta manera. Más información sobre la configuración de c18 con piklab en linux (deprecated) ver: [2]
Luego de compilar, cargar el binario (.hex) en la placa mediante el uso del bootloader que se incluye en el firmware, utilizando la herramienta fsusb como se describe en la sección Grabando el Firmware.

5. Grabando el Firmware

Para grabar el firmware se necesita disponer de un programador, como el picdem o el pickit, este último tiene buen soporte dentro de GNU/Linux. Microchip disrtibuye un bootloader para la familia de microcontroladores 18F, el cual permite grabar el firmware sin la necesidad de disponer de un programador, para realizar esto es necesario dejar prescionado el botón de programación (botón más cercano al led) mientras se resetea la placa con el botón de reset. Una vez que el dispositivo se encuentra en modo programación su identificador de producto cambia de 000c a 000b, esto puede verificarse utilizando el comando lsusb cuya salida se adjunta para los dos casos a continuación:

Id dispositivo USB4all

   Bus 002 Device 002: ID 04d8:000c Microchip Technology, Inc. 

id dispositivo Bootloader

   Bus 002 Device 003: ID 04d8:000b Microchip Technology, Inc.

Dado que Microchip no distribuye microcontroladores con el bootlader previamente grabado, es necesario utilizar un programador por hardware para grabar el bootloader. USB4all acutalmente utiliza un bootloader modificado que permite actualizar el firmware programáticamente sin necesidad de prescionar los botones, útil a la hora de realizar actualizaciones remotas, pero responde a los mismos comandos que el bootloader de Microchip, lo cual permite utilizar el software ya desarrollado para interactuar con el bootloader. Los fuentes del bootloader pueden encontrarse en el repositorio en el directorio bootloader del firmware, es un proyecto separado y una ves compilado su grabación se realiza mediante un programador por hardware como picdem o pickit. Una vez grabado el bootloader ya no es necesario utilizar programadores por hardware y puede realizarse la grabación del firmware mediante varios programas que implementan el protocolo de grabación de firmware de microchip. Una de esas utilidades es el programa fsusb el cual puede encontrarse en [3] y es de código libre. Su uso se realiza de la siguiente forma:

   ./fsusb --program usb4all2.hex

Donde usb4all2.hex es el binario generado como resultado de compilar el proyecto. Recordar que el hardware debe de estar en modo bootloader para poder recibir el firmware.

6. Escribiendo un usermodule

Un usermodule es el bloque sobre el cual se representa una realidad y se modela utilizando la arquitectura USB4all, en esta sección se presentan sus características, estructura necesaria para especificar los servicios que brinda y de que forma se anexa al firmwarebase

Fig. 6.1: Tabla de referencias a user modules

6.1 Estructura de un usermodule:
Un usermodule debe cumplir con una API determinada, de esta manera es posible agregar las funcionalidades que el módulo expone, dentro del basefirmware. Las operaciones principales que se deben implementar son las encargadas de atender los eventos de:

  • inicialización
  • liberación de recursos
  • configuración

Estas operaciones se ejecutan en determinados momentos del ciclo de vida de una aplicación USB4all, siendo la operación registrada al evento de inicialización invocada al abrir desde la aplicación el usermodule y la de liberación de recursos al cerrarlo, esto permite utilizar los recursos del microcontrolador solo cuando es necesario. La operación de configuración puede invocarse en cualquier momento para cambiar en caliente algún aspecto de configuración del hardware. A comenzar un usermodule se debe declarar cuales van a ser las funciones que van a atender estos eventos y el nombre del módulo para ser identificado desde el computador al listar los módulos presentes en la placa.

   #pragma romdata user
   uTab userBuzzerModuleTable = {&UserBuzzerInit,&UserBuzzerRelease,&UserBuzzerConfigure,"buzzer"};
   #pragma code

Donde user es el nombre de la sección y uTab es una estructura para almacenar las posiciones de memoria de las operaciones que exporta el módulo y su nombre.

Como argumento a la operación init se recibe el número de handler asignado al módulo, este número es utilizado como argumento en otras operaciones como ser setHandlerReceiveFunction la cual recibe por parámetro el handler y un puntero a la función encargada de manejar la recepción de datos. Otra operación similar es getSharedBuffer que recibe por parámetro el handlerID del módulo y retorna un buffer por el cual el módulo puede enviar datos a la aplicación que ejecuta en el computador. Un buen lugar para obtener éste buffer y registrar la función de recepción de datos es la operación encargada del evento de inicialización

 void UserBuzzerInit(byte i) {
    usrBuzzerHandler = i;
    // add my receive function to the handler module, to be called automatically when the pc sends data to the user module
    setHandlerReceiveFunction(usrBuzzerHandler,&UserBuzzerReceived);
    // initialize the send buffer, used to send data to the PC
    sendBufferUsrBuzzer = getSharedBuffer(usrBuzzerHandler);
}//end UserBuzzerInit

La función de recepción de datos es la encargada de implementar el protocolo de comunicación entre el usermodule y la aplicación, típicamente es un case en el que se espera por los diferentes comandos que pueden enviarse. La forma de codificar el pedido de servicios es mediante mensajes del tipo < COMANDO [argumento1] [argumento2]...[argumentoN] >. La respuesta debe ser del tipo < COMANDO [resultado1] [resultado2]..[resultadoN] >. El buffer para enviar datos al computador es obtenido como argumento de la función y el buffer para enviar datos fue obtenido previamente en la función de inicialización.

 void UserBuzzerReceived(byte* recBuffPtr, byte len){
      byte index;
      byte j;  
      byte userBuzzerCounter = 0;
      byte tiempo;

      switch(((BUZZER_DATA_PACKET*)recBuffPtr)->CMD){
        case READ_VERSION:
              //dataPacket._byte[1] is len
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[0] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[0]; 
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[1] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[1]; 
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[2] = BUZZER_MINOR_VERSION;
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[3] = BUZZER_MAJOR_VERSION;
              userBuzzerCounter = 0x04;
              break;  
              
        case PRENDER:
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[0] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[0]; 
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[1] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[1]; 
              buzzer_on();
              userBuzzerCounter = 0x02;
              break;  
        
        case APAGAR:
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[0] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[0]; 
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[1] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[1]; 
              buzzer_off(); 
              userBuzzerCounter = 0x02;
              break;  

        case BUZZER_TRIPLE:
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[0] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[0]; 
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[1] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[1]; 
              cantTicks1 = (byte)(((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[1]);
              cantTicks2 = (byte)(((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[2]);  
              cantTicks3 = (byte)(((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[3]);
              boubleBeep(cantTicks1, cantTicks2, cantTicks3);
              userBuzzerCounter = 0x02;
              break;        

        case BUZZER_CORTO:
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[0] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[0]; 
              ((BUZZER_DATA_PACKET*)sendBufferUsrBuzzer)->_byte[1] = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[1]; 
              cantTicks3 = ((BUZZER_DATA_PACKET*)recBuffPtr)->_byte[1];
              timeOutTicksBuzz = 1; // lo seteo timmer para que venza inmediantamente
              buzzerState = DELAY;  
              registerT0event(0, &buzzEvent);
              userBuzzerCounter = 0x02;
              break;    

        case RESET:
              Reset();
              break;
     
         default:
              break;
      }//end switch(s)
      if(userBuzzerCounter != 0){
            j = 255;
            while(mUSBGenTxIsBusy() && j-->0); // pruebo un máximo de 255 veces
                if(!mUSBGenTxIsBusy())
                    USBGenWrite2(usrBuzzerHandler, userBuzzerCounter);
      }//end if            
}//end UserBuzzerReceived

En este ejemplo se puede apreciar como se reciben los pedidos de comandos desde la aplicación que ejecuta en el computador y como se obtienen los parámetros necesarios para implementar los comandos y se devuelven los datos (en caso de ser requeridos) a la aplicación.

6.2 Entrada/Salida en user modules:
Una de las tareas más importantes de los usermodules es la de realizar entrada/salida con diferentes periféricos electrónicos. Para realizar ésta tarea existen dos paradigmas:

  • Polling
  • Interrupciones


Dentro de la arquitectura USB4all existen formas de que los módulos implementen entrada/salida utilizando estos paradigmas, para el caso de pooling existe un servicio que permite registrar una función para ser ejecutada cuando el procesador este disponible.

addPollingFunction(&UserBuzzerProcessIO);

Existe soporte para interrupciones mediante el módulo DynamicISR el cual permite que otros módulos se registren para ser notificados cuando ocurren interrupciones.

addISRFunction(&timmerISRFunction);

Una forma que mantiene alta la cohesión del sistema es registrar módulos que se encarguen de esperar un evento en particular y brinde una interfaz para que los usermodules se registren para ser notificados de eventos generados por el componente de hardware que esta siendo modelado. Hoy día se dispone del módulo T0service que se encarga de brindar servicio de timmers a los usermodules. Al igual que el resto de los componentes funciona registrando funciones que son invocadas por el basefirmware

También existen funciones para desregistrar los usermodules de las notificaciones, es conveniente realizar la desregistración en la función que implementa el evento de liberación de recursos del módulo.

Por ahora el único componente de hardware del microcontrolador modelado de esta forma es el timmer, pero puede escalarse a otros que requieran ser compartidos por más de un módulo. Hay que recordar que la concurrencia implementada es puramente colaborativa entre usermodules por lo cual se debe tener especial cuidado al programar las funciones que atienden estos eventos ya que si se queda "colgada" va a degradar la disponibilidad de todo el sistema.

Para agregar el usermodule a la placa de entrada salida usb4all baseboard se debe agregar al proyecto en piklab el módulo y realizar la compilación del proyecto

7. Detalles de implementación en el firmware

El firmware está organizado en diferentes área que se mapean en secciones definidas dentro de la memoria ROM correspondiente a la memoria de programa del microcontrolador, para implementar esto se necesita especificar en un archivo llamado linker script dónde están alojadas físicamente(en memoria) estas secciones. La forma de realizar este mapeo es editando el archivo lrk asociado al proyecto. Existe la directiva CODEPAGE que es utilizada para trabajar con las secciones de programa, permite inicializar datos, constantes, y referencias externas. Tiene el siguiente formato

   CODEPAGE NAME=memName START=addr END=addr [PROTECTED] [FILL=fillvalue]

donde: memName es un string ASCII utilizado para identificar una CODEPAGE.

La directiva #pragma El estándar ANSI C provee a cada implementación de un método para definir constructores únicos, según sea requerido por la arquitectura del procesador a utilizar. Esto es hecho utilizando la directiva #pragma. La directiva #pragma más común en el compilador utilizado (MPLAB C18) identifica la sección de memoria para ser utilizada en el PIC18XXXX. Por ejemplo #pragma code le indica al compilador MPLAB 18 que compile el código C en la sección de programa de la memoria de programa. La sección de código está definida en el linker script asociado para cada dispositivo PIC18XXXX, especificando en que áreas de la memoria de programa pueden ser ejecutadas instrucciones. Esta directiva puede ser insertada directamente o puede ser seguida de la dirección en el área de código del procesador destino, permitiendo tener un control total del a memoria de código. En el contexto del USB4all esto es muy utilizado, debido a que se separan varias areas de memoria

When allocating memory for variables, the other most common #pragma directive is

  1. pragma udata

Uninitialized variables defined after this declaration will use the General Purpose Registers for storage. This differs from writing a C program on a device where variables and instructions exist within the same memory space. On a PIC18XXXX, program memory is very different from file register memory, and as a result, memory areas for data and program memory must be identified explicitly.

     Directive                                               Use
                      Program memory instructions. Compile all subsequent instructions into

code

                      the program memory section of the target PIC18XXXX.
                      Data stored in program memory. Compile the subsequent static data

romdata

                      into the program memory section of the target PIC18XXXX.
                      Uninitialized data. Use the file register (data) space of the PIC18XXXX

udata

                      for the uninitialized static variables required in the following source
                      code. The values for these locations are uninitialized. For more
                      information, see the section on “Start-up Code” in the MPLAB® C18
                      C Compiler User’s Guide.
                      Initialized data. Use the file register (data) space of the PIC18XXXX for

idata

                      the uninitialized variables required in the following source code. Unlike
                      udata, however, these locations will be set to values defined in the
                      source code. Note that this implies that these values will be placed
                      somewhere in program memory, then moved by the compiler
                      inititialization code into the file registers before the application begins
                      execution.
                      Define the state of the PIC18XXXX Configuration bits. These will be

config

                      generated in the .HEX file output by the linker and will be programmed
                      into the device along with the application firmware.
                      Compile the code from the named C function as a high priority Interrupt

interrupt

                      Service Routine. See the MPLAB® C18 C Compiler User’s Guide
                      section on “Interrupt Service Routines”.
                      Compile the code from the named C function as a low priority Interrupt

interruptlow

                      Service Routine. See the MPLAB® C18 C Compiler User’s Guide
                      section on “Interrupt Service Routines”.
                      Specify where variables will be located so the compiler won’t generate

varlocate

                      extraneous instructions to set the bank when accessing the variables.
                      See the MPLAB® C18 C Compiler User’s Guide section on “#pragma
                      varlocate”.

For full information on these #pragma directives and others, refer to the MPLAB® C18 C Compiler User’s Guide.


SECTIONS

      As described above, sections are the various areas in PIC18XXXX memory, including
      program memory, file register (data) memory, EEDATA nonvolatile memory and data
      stack memory, among others.
      Usually sections are needed for program memory and data memory. As the design
      becomes more sophisticated, other section types may be required.
      Sections are defined in the linker scripts. Here is the linker script for the PIC18F452:
      EXAMPLE 6-1:           PIC18F452 SAMPLE LINKER SCRIPT
         // Sample linker script for the PIC18F452 processor
         LIBPATH .
         FILES c018i.o
         FILES clib.lib
         FILES p18f452.lib
         CODEPAGE   NAME=vectors    START=0x0            END=0x29           PROTECTED
         CODEPAGE   NAME=page       START=0x2A           END=0x7FFF
         CODEPAGE   NAME=idlocs     START=0x200000       END=0x200007       PROTECTED
         CODEPAGE   NAME=config     START=0x300000       END=0x30000D       PROTECTED
         CODEPAGE   NAME=devid      START=0x3FFFFE       END=0x3FFFFF       PROTECTED
         CODEPAGE   NAME=eedata     START=0xF00000       END=0xF000FF       PROTECTED
         ACCESSBANK NAME=accessram  START=0x0            END=0x7F
         DATABANK   NAME=gpr0       START=0x80           END=0xFF
         DATABANK   NAME=gpr1       START=0x100          END=0x1FF
         DATABANK   NAME=gpr2       START=0x200          END=0x2FF
         DATABANK   NAME=gpr3       START=0x300          END=0x3FF
         DATABANK   NAME=gpr4       START=0x400          END=0x4FF
         DATABANK   NAME=gpr5       START=0x500          END=0x5FF
         ACCESSBANK NAME=accesssfr  START=0xF80          END=0xFFF          PROTECTED
         SECTION    NAME=CONFIG     ROM=config
         STACK SIZE=0x100 RAM=gpr5
      This linker script defines the main program memory with the name page extending
      from address 0x002A to 0x7FFF. When a #pragma code directive is encountered,
      the compiler will generate machine code instructions to be placed in this area.
      Data memory is defined for the six file register banks (gpr = General Purpose Register
      bank) of the PIC18F452. Due to the nature of banked memory on the PIC18XXXX,
      these six regions are defined as separate sections. When #pragma udata and
      #pragma idata directives are encountered, the compiler will reserve areas in these
      file register banks for storage of the variables subsequently defined.
      The accessram and accesssfr sections define the Access RAM areas in data
      memory.
      Note that some areas are marked “PROTECTED”. This means that the linker will not put
      code or data into those areas unless specifically directed. To put code or data into a
      protected area, use the #pragma directive as shown here:
      #pragma code page
      This will cause the subsequent instructions to be compiled in the page section, usually
      the main program memory area as defined in the linker script.


The ANSI C standard provides each C implementation a method for defining unique

       constructs, as required by the architecture of the target processor. This is done using
       the #pragma directive. The most common #pragma directive in the MPLAB C18
       compiler identifies the section of memory to be used in the PIC18XXXX. For instance,
       #pragma code
       tells MPLAB 18 to compile the C language code following this directive into the “code”
       section of program memory. The code section is defined in the associated linker script
       for each PIC18XXXX device, specifying the program memory areas where instructions
       can be executed.

8. Bobot

Fig. 8.1 bobot logo
Fig. 8.2 Arquitectura del bobot
Fig. 8.3 Interacción con dispositivo USB4all de forma web

bobot-server (version 2) es un servicio que permite acceder a aplicaciones y usuarios interactuar con dispositivos USB4all. Consiste en un agente altamente portable y liviano, que exporta la funcionalidad de los dispositivos USB4all presentes de una forma fácil de usar. Ofrece dos métodos de acceso, uno optimizado para aplicaciones, basado en un socket y un protocolo fácilmente parseable, y otro optimizado para ser usado por humanos, mediante un sitio web hosteado en el propio agente, el cual atiende en el puerto 2010. Bobot introduce el concepto de driver, en el cual se codifica el protocolo que utilizan los usermodules de esta forma se exponen al usuario del sistema bobot los servicios que implementan los usermodules ocultando los detalles relacionados con el protocolo de intercambio de mensajes. Para desarrollar compatibilidad con un nuevo dispositivo electronico en la plataforma USB4all utilizando bobot para su control, se debe primero desarrollar el usermodule necesario como fue descripto en la sección: Escribiendo un usermodule luego se debe escribir el driver correspondiente a ese usermodule. El driver se escribe en Lua, este debe seguir un formato y se utiliza el recurso de tablas que brinda Lua para implementarlo en conjunto con la característica de de este lenguaje en el que las funciones son miembros de primer orden, esto permite (entre otras cosas) que una función pueda ser un tipo de datos válido, pasar funciones como argumento de funciones o como retorno de las mismas y almacenarla en una tabla como es el caso de los drivers bobot. Por este motivo dentro de la tabla se pueden almacenar funciones donde en la misma se especifican las operaciones que el driver expone, y los tipos de parámetros que recibe.
Mucha de ésta información es utilizada por el bobot para generar el sitio web mencionado anteriormente, en el cual se describe al módulo de usuario y se puede usar como documentación y forma de prueba del mismo. En el driver también se especifica otra información, como el método de la función que es utilizada para resolver de forma genérica las invocaciones a los servicios expuestos por el driver.
A continuación se muestra un ejemplo de driver para el módulo de usuario presentado en la sección: Escribiendo un usermodule. En este driver podemos ver que se exportan los siguientes servicios:

  • read_version
  • prender
  • apagar
  • buzzer_corto
  • buzzer_triple
 
local device = _G
local RD_VERSION = string.char(0x00)
local PRENDER = string.char(0x01)
local APAGAR = string.char(0x02)
local BUZZER_CORTO = string.char(0x03)
local BUZZER_TRIPLE = string.char(0x04)

api={}
api.read_version = {}
api.read_version.parameters = {} --no parameters
api.read_version.returns = {[1]={rname="version", rtype="number"}} --one return
api.read_version.call = function ()
        local get_read_version = RD_VERSION 
        device:send(get_read_version)
        local version_response = device:read(2) 
        local raw_val = string.byte(version_response, 2) 
        --print("rawval, deg_temp: ", raw_val, deg_temp)
        return raw_val
end

api.prender = {}
api.prender.parameters = {} --no parameters
api.prender.returns = {} --no return
api.prender.call = function ()
    local write_res, err = device:send(PRENDER)
    if write_res then return true else return false end
end

api.apagar = {}
api.apagar.parameters = {} --no parameters
api.apagar.returns = {} --no return
api.apagar.call = function ()
    local write_res, err = device:send(APAGAR)
    if write_res then return true else return false end
end

api.buzzer_corto = {}
api.buzzer_corto.parameters = {[1]={rname="num", rtype="number"}} 
api.buzzer_corto.returns = {} --no return
api.buzzer_corto.call = function (num)
    local write_res, err = device:send(BUZZER_CORTO .. string.char(num))
    if write_res then return true else return false end
end

api.buzzer_triple = {}
api.buzzer_triple.parameters = {[1]={rname="tiempo1", rtype="number"}, [2]={rname="tiempo2", rtype="number"}, [3]={rname="tiempo3", rtype="number"}} 
api.buzzer_triple.returns = {} --no return
api.buzzer_triple.call = function (tiempo1, tiempo2, tiempo3)
    local write_res, err = device:send(BUZZER_TRIPLE .. string.char(tiempo1) .. string.char(tiempo2) .. string.char(tiempo3))
    if write_res then return true else return false end
end

En el ejemplo puede notarse al comparar con el usermodule presentado de ejemplo, como se realiza el pasaje de parámetros entre el driver y el usermodule, esto corresponde con el protocolo definido por el usuario para codificar los comandos del usermodule y el orden de los parámetros, este protocolo es llamado en la arquitectura USB4all como user protocol y responde al formato de < COMANDO [argumento1] [argumento2]...[argumentoN] > para el pedido y mensajes del tipo < COMANDO [resultado1] [resultado2]..[resultadoN] > para la respuesta. Entre las características del bobot se encuentra la capacidad de abstraer al usuario del tipo de hardware de placa de E/S está conectada pudiendo ser placas que utilizan comunicación serial rs232c o USB u otra tecnología como también la cantidad de las mismas. En la figura 8.2 puede verse un caso posible, donde se encuentran diferentes usermodules cargados en una baseboard y ciertos drivers cargados en el bobot que van a permitir consumir los servicios expuestos por los usermodules.

Como se mencionó al comienzo de la sección, bobot expone un protocolo que puede utilizarse mediante conexiones TCP/IP, este protocolo se describe de esta forma:

   LIST
   DESCRIBE moduleName
   CALL moduleName operation param1, param2, ...,paramN
   OPEN moduleName
   CLOSEALL

d


Puede descargarse bobot-server desde el git del proyecto butiá en sourceforge.

Arquitectura de Software/Firmware


El proyecto USB4all se encuentra disponible bajo licencia GNU/GPL v2 en el repositorio sorceforge
Este proyecto surge originalmente como un trabajo de tesis de grado en Ingeniería en Computación de Aguirre, Fernandez y Grossy.