Placa EZ-USB en Linux

Usando una placa de desarrollo de Cypress con sdcc y fxload
3-2005 ... 13-05-2006. Daniel Clemente

This page is also available in English


Introducción

En la asignatura PI -Periféricos e Interfícies- de la FIB (UPC) nos hacían usar una placa de desarrollo en las prácticas. Se conecta al ordenador y se le ponen programas, que controlan los botones, escriben por la pantallita, o se comunican con el ordenador u otros aparatos.

Se usaban programas propietarios que sólo iban en Windows, así que busqué una forma de hacer lo mismo en cualquier sistema operativo y con programas gratis y libres que sí que pudieran estudiar los alumnos.

No está acabado, pero es una guía para que cualquiera que se ponga a buscar no empiece desde cero.


Descripción de la placa

Cuesta un poco entender qué es la placa. Si se empieza por lo más general, tenemos que: La famosa placa de desarrollo de PI

La placa la han diseñado y montado los profesores de PI, comprando las piezas por separado en tiendas de electrónica y juntándolas. Estas piezas incluyen: dot matrix, botones, memoria (64 Kb), conectores serie y USB, y el μC AN2131QC, además de LEDs, resistencias, condensadores, transistores y otros.

El componente central de la placa es el microcontrolador AN2131QC, de la empresa Cypress (que compró a Anchor Chips, el fabricante original). El AN2131Q es un producto de la familia EZ-USB de Cypress. Otros microcontroladores (AN2122T, AN2135S, etc) tienen características algo distintas (más endpoints USB, o puertos más rápidos, por ejemplo).

Este microcontrolador, el AN2131QC, tiene varias piezas importantes, como una memoria de 8 Kb, la UART (tiene dos), y el procesador, claro, que es un 8051 mejorado. El 8051 original lo hizo Intel en 1980, pero ahora lo fabrican otras compañías, como Atmel. Es una CPU de 8 bits con 256 bytes de RAM, 2 ó 3 contadores, 5 ó 6 interrupciones, 4 puertos de 8 bits, y puerto serie. Las mejoras que le ha hecho Cypress en su EZ-USB es que va más rápido, opera a 3.3V, tiene un tercer contador (de 16 bits), y otras cosas.

Lo bueno es que el código ensamblador de la CPU 8051 sigue funcionando en el microcontrolador AN2131, por lo tanto la placa de PI también se puede programar con código compatible con 8051. Además hay que usar funciones extra para acceder a todo lo propio de EZ-USB.

Hay otras placas de desarrollo que usan el AN2131QC de Cypress, por ejemplo: MF3001, USB I2C/IO, USBSIMM; y si no te gustan, hazte una tú.

Funcionamiento original (y en Windows)

Para que un programa nuestro funcione en la placa, hay que hacer estos pasos:

  1. Escribir el código en alto nivel. Nosotros usábamos una especie de C muy chapucero (parece que todo lo que nos enseñan sobre programación modular y estructurada no se aplica en este campo). Mucho cuidado con el código: a=5; no quiere decir que a tome por valor 5, sino que puede ser una llamada a una función de hardware. A veces con cambiar de orden las asignaciones (b=0;a=0; en vez de a=0;b=0;) se arreglaban los problemas.
  2. Compilarlo. Hace un falta un compilador cruzado ya que el procesador destino es el 8051, no el x86. Nosotros usábamos el Keil μVision 2. Después de compilar, el fichero objeto se enlaza con una librería que da Cypress, llamada EZUSB.LIB y que está presente en todos los programas. El resultado de la compilación es un fichero de extensión HEX que sigue la especificación de Intel.
  3. Cargar el código en la placa. Hay un programa, Cypress Control Panel, para transferir (download) este fichero HEX a la placa por el cable USB.
  4. Mirar la placa para ver si funciona. Nada más transferir el programa, empieza a funcionar y hay que comprobar (mediante los botones y la pantallita) que haga lo que se esperaba. Si algo no va, cambiar un poco el código y repetir el proceso hasta que se acabe la paciencia. También se pueden usar depuradores y simuladores, pero eso no nos lo explicaron en PI.

Las dos herramientas usadas son: el Keil (compilador) y el cargador de Cypress. No los he podido probar a fondo porque sólo van en Windows, pero lo que he intentado es buscar alguna forma de hacer lo mismo con herramientas multiplataforma, gratis, y si puede ser, software libre (que podamos estudiar). Y que no sea una demo, como el Keil (en algunas asignaturas como SDMI los alumnos no podían compilar proyectos grandes porque estaban limitados por la demo).

Cargar un hex en la placa (fxload)

Lo explico antes porque es lo más fácil. Tenemos un .hex (ya compilado) y queremos que la placa lo ejecute.

En el kernel 2.6, el soporte para transferir a la placa EZ-USB viene integrado en el kernel. Sólo hace falta el programa fxload, y un comando como fxload -I DosPunts.hex es suficiente para que la placa empiece a ejecutar el código recién transferido. En el kernel 2.4 hay que usar este driver.

Para hacer pruebas, aquí pongo un programa de ejemplo: DosPunts.hex. Esto enciende dos puntitos en el dot matrix, y no hace nada más.

Compilar código C para crear un .hex (sdcc)

Para esto hace falta un compilador cruzado (cross-compiler) del lenguaje en cuestión. Para C o dialectos de C, hay muchos compiladores de 8051. Entre ellos está el C51 (el de Keil, caro y de MS-DOS) y el sdcc (libre, gratis, multiplafatorma, y que incluye muchas otras herramientas).

Se puede compilar código en muchos otros lenguajes, de alto y bajo nivel. Para ASM (ensamblador), los conocidos son el A51 (el de Keil), el asx8051 (el de sdcc), y otros.

El que he elegido para usar en Linux es sdcc (parece que es el más completo, y además es software libre). Para generar el hex hay que seguir estos pasos:

  1. Escribir código en el pseudo-C, en un archivo.c
  2. Ejecutar sdcc archivo.c, posiblemente pasándole opciones como el -I para decir dónde están los includes. Esto genera muchos archivos: .asm, .ihx, .lnk, .lst, .map, .mem, .rel, .rst, .sym. Si lo que se compilaba era un ensamblador, se hace con asx8051 -o archivo.asm y luego sdcc archivo.rel.
  3. Convertir el ihx a hex con packihx archivo.ihx >archivo.hex.

Para compilar proyectos de muchos archivos, se compilan individualmente con -c y luego se enlazan todos juntos, con el main.c al principio. El manual de SDCC lo explica bien.

Y otra cosa: debido a la arquitectura de la placa de PI, a sdcc habría que añadirle los parámetros --code-loc 0x0080 --xram-loc 0x1000 --iram-size 256 --model-small (bueno, el modelo predeterminado ya es el small). Los datos los he sacado de la configuración que tenía el Keil en los laboratorios.

Todo esto funciona; el problema es que en la placa EZ-USB, la mayoría de funciones para acceder al hardware están implementadas en una biblioteca (library) externa, llamada EZUSB.LIB. Por ejemplo, en este programa, DosPunts.c, se ve que casi todo el código son funciones propias (EZUSB_WriteI2C, EZUSB_Delay, ...).

ezregs.h, ezusb.h y otros

En los ejemplos se incluyen (#include) dos ficheros de cabecera:

Estos archivos, y los demás que hay en el directorio include del programa, también requieren modificación para que compilen sin problemas. Los originales están aquí: include-orig.tar.gz, y la versión corregida en este directorio: include-fix/ (también en este tar.gz). Espero no molestar a nadie distribuyéndolo.

Los cambios que les he hecho son:

Nombres en minúsculas

En Windows, cada programa referenciaba a los archivos de una forma distinta. Les he llamado a todos en minúsculas; por ejemplo ezregs.h (antes era EZRegs.h) para que sea más fácil trabajar con ellos. Ya de paso, los he indentado y he quitado los TABs.

Definición de registros del 8051

En ezregs.h se dice la posición de memoria en la que está cada SFR de la placa. sdcc ya proporciona un archivo parecido, en /usr/share/sdcc/include/8051.h; las direcciones de cada uno coinciden, pero el problema es que el micro de la placa es un 8051 ampliado (no confundir con el 8052), y hay definiciones que faltan en el fichero de sdcc. Por ejemplo, en el original sólo está SBUF, pero el de Cypress tiene SBUF0 y SBUF1.

De todas formas, el archivo de sdcc sirve para ver qué sintaxis han de tener las definiciones de registros. Debería ser algo como: sfr at 0x8D TH1; pero el formato de Keil es sfr TH1 = 0x8D;. Ambos usan sfr para nombres de registros y sbit para nombres de bits dentro de registros. Lo he cambiado (usando expresiones regulares se hace muy rápido).

No abusar del preprocesador

En ezregs.h también están las definiciones de registros de la placa EZ-USB (no del 8051), como los necesarios para usar el USB. Son registros xdata, o sea, que se accede a ellos como si estuvieran en la memoria RAM externa.

Lo malo es que hacen una guarrería con el preprocesador:

  #ifdef ALLOCATE_EXTERN
  #define EXTERN
  #define _AT_ _at_
  #else                           /*  */
  #define EXTERN extern
  #define _AT_ ;/ ## /
  #endif                          /*  */
  
  /* Register Assignments 3/18/99 TPM */
  EXTERN xdata volatile BYTE OUT7BUF[64] _AT_ 0x7B40;
  EXTERN xdata volatile BYTE IN7BUF[64] _AT_ 0x7B80;
  EXTERN xdata volatile BYTE OUT6BUF[64] _AT_ 0x7BC0;

Y así con todos los registros. La intención es cambiar el _AT_ por ;// para ignorar la mitad derecha de varias líneas.

Esto no le gusta a sdcc (warning: pasting "/" and "/" does not give a valid preprocessing token); y a mí tampoco (de hecho gcc también se une a las quejas, con C normal). Lo he cambiado a algo más elegante:

  #ifdef ALLOCATE_EXTERN
  #define NEWEZREG(_name,_where,_size) volatile xdata at _where _size _name
  #else                           /*  */
  #define NEWEZREG(_name,_where,_size) extern volatile xdata _size _name
  #endif                          /*  */
  
  /* Register Assignments 3/18/99 TPM */
  NEWEZREG(OUT7BUF[64],0x7B40,BYTE);
  NEWEZREG(IN7BUF[64],0x7B80,BYTE);
  NEWEZREG(OUT6BUF[64],0x7BC0,BYTE);

Con esto ezregs.h queda arreglado; fx2regs.h y fx.h también necesitan el mismo cambio.

Los .inc

Estos 6 ficheros se incluyen cuando se programa en ensamblador:

Usan la sintaxis de Keil, pero se usan tan poco que he preferido no hacerles cambios. Más adelante vuelvo a hablar de ellos.

Biblioteca EZUSB.LIB

Las funciones para trabajar con las piezas añadidas por Cypress están en el archivo de biblioteca EZUSB.LIB, y son más o menos éstas. Cuando trabajábamos con Keil (en Windows), en cada proyecto teníamos que agregar el fichero EZUSB.LIB para que lo enlazara con el código compilado; el problema es que el EZUSB.LIB original está en el formato que entiende Keil, que no es el mismo que el de las bibliotecas de sdcc.

Por casualidad, en una instalación de Keil encontré un directorio con el código fuente de toda la biblioteca EZUSB pero preparado para compilar con el entorno y la sintaxis de Keil (usando C51 y A51). Espero no molestar a nadie si lo distribuyo aquí.

Después de mucho trabajo, porté todo el código C y ASM de esta biblioteca a un formato normal que entienda sdcc. El resultado está en ezusb-fix/ (también en este tar.gz); espero que Cypress no se enfade por publicar mejoras a su código.

Aunque los códigos eran sencillos, he corregido muchos errores. A continuación explico los cambios que he tenido que hacer.

Creación de una biblioteca

Según Cypress, EZUSB.LIB se crea usando C51 para compilar cada .c por separado y A51 para cada .a51, por separado. Luego toca crear una biblioteca vacía, con LIB51, y añadir todos los archivos objetos generados excepto los de 2200jmpt.a51, FXJmpTb.a51, renum.a51, USBJmpTb.a51.

Según sdcc, un .lib es sólo una lista de archivos .rel, que son el código objeto resultado de compilar cada .c o .asm de manera independiente (-c). Así de sencillo; es sólo una lista con los nombres de los archivos, uno por línea.

Aunque un .lib de sdcc también puede tener dentro el código (así se queda en un solo archivo), y el formato es igual de sencillo: mediante el programa sdcclib se incluye el contenido de cada archivo rel (que es texto plano), clasificados usando un lenguaje de etiquetas al estilo HTML. En cambio el formato de Keil es binario, e incomprensible (para mí).

Entonces, mi objetivo es arreglar los 13 ficheros delay.c, delayms.a51, discon.c, EZRegs.c, get_cnfg.c, get_dscr.c, get_infc.c, get_strd.c, i2c.c, i2c_rw.c, resume.c, susp.a51, usbirqcl.a51, compilarlos individualmente, y juntarlos en una biblioteca para tener mi propio ezusb.lib.

Nombres en minúsculas

Para compilar la librería en Linux, los #include tienen que hacer referencia al archivo correcto (con mayúsculas y minúsculas bien puestas). He cambiado en todos los ficheros cosas como #include "..\inc\Fx.h" a #include <fx.h>.

stdlib.h en vez de stdio.h

get_cnfg.c, get_dscr.c, get_infc.c, get_strd.c necesitaban el símbolo NULL, que está en stdlib.h, no en stdio.h. i2c.c directamente no lo necesitaba.

Conversión a xdata* en get_infc.c

Da este mensaje al compilar:

  get_infc.c:16: warning: function return value mismatch
  from type 'struct __00010003 generic* '
  to type 'struct __00010003 xdata-xdata* '

La parte del código relevante es (el 16 es el return):

  INTRFCDSCR xdata*       EZUSB_GetIntrfcDscr(BYTE ConfigIdx, BYTE IntrfcIdx, BYTE AltSetting)
  {
  ...
  INTRFCDSCR      *intrfc_dscr;
  ...
  intrfc_dscr = (INTRFCDSCR xdata *)((WORD)config_dscr + config_dscr->length);
  return(intrfc_dscr);
  ...

La función debería devolver un xdata* ("puntero a RAM externa del 8051"), pero en el modelo small que usamos al compilar, la variable local se coloca por defecto en memoria RAM interna (tipo de almacenamiento data).

No creo que los descriptores USB estén en data (los 128/256 bytes de RAM interna), así declarándola xdata* se soluciona:

  INTRFCDSCR      xdata *intrfc_dscr;

volatile en I2CPckt

El error compilando i2c.c:

  i2c.c:5: error: extern definition for 'I2CPckt' mismatches with declaration.
  from type 'struct __00010007'
  to type 'volatile-struct __00010007'

Línea culpable: I2CPCKT volatile I2CPckt;. Pero en ezusb.h ya ponía que extern I2CPCKT I2CPckt;, por tanto no concuerda.

Hay dos opciones: poner volatile donde falta, o quitarlo donde aparece. Supongo que si lo han puesto será por algo, así que lo añado en ezusb.h y fx2.h:

  extern I2CPCKT volatile I2CPckt;

function cannot return 'bit'

En i2c.c y i2c_rw.c hay varias funciones que devuelven BOOL:

  /* en i2c.c */
  BOOL EZUSB_WriteI2C_(BYTE addr, BYTE length, BYTE xdata *dat);
  BOOL EZUSB_ReadI2C_(BYTE addr, BYTE length, BYTE xdata *dat);
  /* en i2c_rw.c */
  BOOL EZUSB_ReadI2C(BYTE addr, BYTE length, BYTE xdata *dat)
  BOOL EZUSB_WriteI2C(BYTE addr, BYTE length, BYTE xdata *dat)

Según ezusb.h, se dice que typedef bit BOOL;, pero que una función devuelva un solo bit no está permitido. En realidad las de i2c.c devuelven TRUE y FALSE, que están definidas (#define) en ezusb.h a 1 y 0 respectivamente, por tanto pueden ser enteros normales. Lo raro es que las de i2c_rw.c devuelven constantes como I2C_OK, I2C_NACK, I2C_BERROR, con valores 8, 7 y 6 respectivamente. No sé qué sentido tiene esa transformación a bit; ya hace bien el sdcc quejándose.

He cambiado el tipo devuelto a BYTE, cambiando las cabeceras en i2c.c, i2c_rw.c y ezusb.h y fx2.h. En teoría un byte cumple las funciones de un bit: 0 indica falso, y distinto de 0, cierto. Pero este cambio puede hacer que programas anteriores (que esperaban recibir BOOL) ya no compilen.

Transformando el ensamblador de Keil a sdcc

Para completar EZUSB.LIB quedan 3 archivos en ensamblador: delayms.a51, susp.a51, usbirqcl.a51, que incluyen (en el sentido de #include) a ezregs.inc, aunque de includes hay 6: ezbits.inc ezbits.inc ezregs.inc fx2regs.inc fx.inc macros.inc reg320.inc .

La sintaxis que usa asx8051 (el compilador de sdcc) es muy distinta a la de A51 (el compilador ASM de Keil). Vale la pena estudiar los .asm que sdcc genera al compilar C.

En asx8051 hay algunas diferencias respecto al A51:

En vez de arreglar los 3 .a51, he creado 3 ficheros .asm con el código escrito usando la sintaxis de asx8051, que -en mi opinión- es más clara que la de A51. Como se usaban muy pocos datos provenientes del include (sólo para obtener la dirección de los SFR USBCS, PCON y EXIF), he repetido las declaraciones de estos registros donde hacían falta y no he usado includes.

Espero haber hecho bien la conversión.

Probando la biblioteca

Una vez compilados los 13 ficheros (10 C y 3 ASM), no ha habido problemas al crear ezusb.lib a partir de los 13 .rel mediante sdcclib.

Usando los ficheros de cabecera corregidos (include-fix/) y la biblioteca corregida (ezusb-fix/) ya se puede compilar DosPunts.c con:

  echo OPCS="--code-loc 0x0080 --xram-loc 0x1000 --iram-size 256 --model-small"
  sdcc -I ../include-fix -L ../ezusb-fix -l ezusb $OPCS DosPunts.c
  packihx DosPunts.ihx >DosPunts.hex

Probé a simular el hex con s51 (viene con sdcc): s51 DosPunts.hex. No es muy apropiado porque la placa de PI no es un 8051 (por ejemplo, tiene 256 bytes de RAM en vez de 128). Al simular, s51 decía:

  ...
  Error: invalid address 0x82 in memory iram.
     0x04ad f6       MOV   @R0,A
  Error: invalid address 0x81 in memory iram.
     0x04ad f6       MOV   @R0,A
  Error: invalid address 0x80 in memory iram.
     0x04ad f6       MOV   @R0,A

Es lógico que dé error, ya que en el 8051 (que es lo que simula), hay 128 (0x7f) bytes de RAM, y a partir de 0x80 son SFRs. La culpa es del código de inicialización que pone sdcc para vaciar la RAM:

  ...
  ; empezando con r0=0 y a=0
  00005$: mov     @r0,a
          djnz    r0,00005$
  ;       _mcs51_genRAMCLEAR() end

Equivale a i=0; do { mem[i]=0; i--; } while (i!=0);. Esto es efectivo pero el simulador se queja. Se puede añadir --no-xinit-opt al compilar para no generar este código de inicialización y evitar warnings (cuidado con las consecuencias), o pedirle a s51 que se comporte como un 8052, que tiene los 256 bytes: s51 -t 8052 (aunque nuestra placa no es un 8052).

Resumen de lo que he hecho

Lo que queda por hacer

Otras soluciones

Disculpas, e información sobre este documento

Empecé a escribir esto sin saber absolutamente nada de microcontroladores; si en clase de teoría de PI nos hubieran explicado algo sobre las prácticas me habría costado menos recopilar esta información.

Me he leído cosas como este tutorial de 8051, pero sigo siendo un novato en microcontroladores. Con este documento sólo pretendo ayudar un poco a los alumnos nuevos para que no empiecen desde cero sus investigaciones. Recomiendo estudiar el código de proyectos de software libre (como sdcc); es una magnífica forma de aprendizaje que vale la pena aprovechar.

Y aprovecho para decir que la asignatura PI fue un cachondeo general (aunque esto ya lo saben todos en la FIB). Las prácticas las hacíamos probando cosas al azar o copiándolas de otro grupo, ya que en el nuestro no había explicaciones ni apuntes, y la especificación del chip (340 páginas, en inglés) no ayudaba mucho al principio.

Llegamos al punto de estar leyendo desde una pistola de códigos de barras sin tener la pistola, y además sin conocer cómo funcionan estos lectores. Un profesor nos dijo que quizás la podíamos simular con el Hyperterminal, pero que a él no le había funcionado, así que no valía la pena esforzarse mucho.

Así iba todo... al final lo complicado de la asignatura no era aprobar, sino aprender algo útil.

Ah, todo esto que he escrito tiene licencia haz lo que quieras, ya que distribuyo código del que no tengo muy claro si tengo permiso para modificarlo. No tengo relación con Cypress ni con mi universidad; todo lo que he hecho es responsabilidad mía. Escríbeme si hace falta cambiar algo. n142857 -arroba-g-m-a-i-l--punto-com


Marzo-Julio 2005 (actualizado el 13-05-2006), Daniel Clemente Laboreo,