This page is also available in English
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.
Cuesta un poco entender qué es la placa. Si se empieza por lo más general, tenemos que:
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ú.
Para que un programa nuestro funcione en la placa, hay que hacer estos pasos:
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.
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).
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.
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:
archivo.c
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
.
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, ...).
En los ejemplos se incluyen (#include
) dos ficheros de cabecera:
ezregs.h
dice en qué dirección está cada registro de la placa,
tanto los del microcontrolador interno (8051) como los que le
ha añadido Cypress.
ezusb.h
declara las estructuras y las funciones propias de la
placa EZ-USB (EZUSB_InitI2C, etc)
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:
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.
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).
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.
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.
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.
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
.
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>
.
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.
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;
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;
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.
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:
.globl
(o con etiqueta::
).
Entonces no hace falta el -g
al compilar.
.include
equ
) se hacen con NOMBRE = valor
.area CSEG (CODE)
antes del código.
0xVALOR
, y los binarios
con 0bVALOR
. En A51 llevan la letra al final.
end
al final de cada código.
EZUSB_Delay1ms
, por ejemplo, en el ensamblador
sólo está definido el símbolo EZUSB_DELAY1MS
.
_
(significa
símbolo externo). sdcc lo añade automáticamente al compilar C.
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.
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).
hex
y cargarlos en la placa.
sdcc
.
ezusb.lib
que se puede usar con sdcc
.
ezusb.lib
con sdcc.
ezusb.lib
,
usar otro compilador que acepte bibliotecas en el formato de Keil.
Los hay, pero no he encontrado ninguno
gratis y para Linux.
Costaría encontrar otro mejor que sdcc.
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,