EZ-USB board on Linux

Using a Cypress development board with sdcc and fxload
3-2005 ... 13-05-2006. Daniel Clemente

Esta página también está en español


In the subject called PI -Periferals and Interfaces- at the FIB (UPC, Public University of Catalonia) we used a development board at the practices. You connect it to a computer and transfer the programs, which can control the buttons, write on the LCD screen, or communicate with the computer or other devices.

We used proprietary programs which only worked on Windows, so I looked for a way to do the same but on any operating system, and with free (as in freedom) and gratis programs, so that anyone could study them.

This isn't finished, but it's a starting point to anyone who were looking for this. At least you won't need to start from zero.

Description of the board

It's a little tricky to understand what the board is. Starting from the most general, we have: The famous development board used at PI

The board has been designed and set up by the teachers of the subject (PI); they bought some pieces at electronic shops and they put them together. Among these pieces are: dot matrix, buttons, memory (64 Kb), serial and USB connectors, and the μC AN2131QC, and furthermore some LEDs, resistors, transistors, and others.

The main component at the board is the microcontroller AN2131QC, from the company Cypress (which bought Anchor Chips, the original producer). The AN2131Q is a product of the EZ-USB family from Cypress. Other microcontrollers (AN2122T, AN2135S, etc) have somewhat different characteristics (more USB endpoints, or faster ports, for instance).

This microcontroller, AN2131QC, has several important components, such as a 8 Kb memory, a UART (it has two), and of course the processor, which is an improved 8051. The original 8051 was made by Intel at 1980, bit now is produced by other companies, like Atmel. It's an 8 bits CPU with 256 bytes of RAM, 2 or 3 timers, 5 or 6 interrupts, 4 ports (of 8 bits), and serial port. The improvements made by Cypress with their EZ-USB is that it runs faster, operates at 3.3V, has a third counter (16 bits), and more.

The good thing is that the assembler code of the 8051 CPU still works on the microcontroller AN2131, so the board used in PI can also be programmed with code compatible with the 8051. In addition, some extra functions must be used to access everything related to just the EZ-USB.

There exist other development boards which use the AN2131QC from Cypress, for example: MF3001, USB I2C/IO, USBSIMM; and if you don't like them, make one yourself.

Original behaviour (and in Windows)

To make our program run in the board, we had to follow these steps:

  1. Write the high-level code. We used a sort of somewhat unclean C (it seems that everything we were taught about modular and structured programming doesn't apply here). Beware of the code: a=5; doesn't mean that a gets set the value 5, but it might be a call to a hardware function. Sometimes just changing the order of assignments (b=0;a=0; instead of a=0;b=0;) solved the problems.
  2. Compile it. You need a cross compiler since the desired processor is the 8051, not x86. We used Keil μVision 2. After compiling, the object file is linked with a library provided by Cypress, called EZUSB.LIB and present in all programs. The result of the compilation is a file with extension HEX which follows the specification from Intel.
  3. Load the code in the board. There's a program, Cypress Control Panel, to transfer (download) this HEX file to the board via USB.
  4. Look at the board to see if it works. Just after the transfer, it starts running and we have to test (by using the buttons and LCD screen) whether it does what it was intended. If something doesn't work, change the code and repeat the process until you run out of patience. You can also use debuggers and simulators, but we were not taught that at PI.

So the two tools used are: Keil (compiler) and the loader from Cypress. I haven't exhaustively studied them as they only run on Windows, but what I have tried is to find a way to do the same, but using only crossplattform, no cost, and hopefully free software (which we can study). And not a demo, like Keil (at some subjects, like SDMI students could not compile large projects since the demo was limited in size).

Loading an hex in the board (fxload)

I explain this first because is the easiest part. We have an .hex (already compiled) and want the board to run it.

In kernel 2.6, the ability to transfer to the EZ-USB board is already incorporated in the kernel. You just need the program fxload, and a command like fxload -I DosPunts.hex is sufficient to make the board start running the just transferred code. In kernel 2.4 you must use this driver.

So that you can test, here there is an example program: DosPunts.hex. It shines two points at the dot matrix, and does nothing else.

Compiling C code to create the .hex (sdcc)

This needs a cross-compiler for the specific language we want. For C or C dialects, there are several 8051 compilers. Among them there's C51 (the one found in Keil, expensive and for MS-DOS) and sdcc (free software, gratis, crossplatform, and including a lot of other tools).

You can compile code written in several other languages, both high and low level. For ASM (assembler), the known ones are A51 (from Keil), asx8051 (from sdcc), and others.

The one I have decided to use in Linux is sdcc (it seems it's the most complete, and in addition it's free software). To create the hex you have to do so:

  1. Write code in that pseudo-C, in a file like file.c
  2. Run sdcc file.c, possibly passing it some options like -I to specify where are the includes. This creates a lot of files: .asm, .ihx, .lnk, .lst, .map, .mem, .rel, .rst, .sym. If what you were compiling was assembler, it's done with asx8051 -o file.asm and after that, sdcc file.rel.
  3. Convert the ihx to hex with packihx file.ihx >file.hex.

To compile projects which have several files, compile each one individually with flag -c and then link them together, putting the main.c first. SDCC manual explains this better.

And something else: due to the architecture of the PI board, sdcc will need some parameters like --code-loc 0x0080 --xram-loc 0x1000 --iram-size 256 --model-small (well, default model is small anyway). I found these data at the Keil configuration at the laboratories.

All this works; the problem is that at the EZ-USB board, most of the functions needed to access the hardware are implemented in an extern library, called EZUSB.LIB. For instance, in this program, DosPunts.c, it can be seen that almost the whole code are custom functions (EZUSB_WriteI2C, EZUSB_Delay, ...).

ezregs.h, ezusb.h and others

At the examples we included (#include) two header files:

These files, and others found at the directory include of the program, also need modification to be able to compile without problems. The originals are here: include-orig.tar.gz, and the fixed version is in this folder: include-fix/ (also in this tar.gz). I hope I don't disturb anyone by distributing them.

The changes I made to them are:

Lowercase names

On Windows, each program referenced the files in a different manner. I have renamed them all to lowercase; for instance ezregs.h (formerly EZRegs.h) so that it is easy to work with them. And by the way, I have indented them and removed the TABs.

Register definitions for the 8051

At ezregs.h we have the memory address by which each SFR from the board can be accessed. sdcc already provides a similar file, at /usr/share/sdcc/include/8051.h; the addresses of both do agree, but the problem is that our micro is an extended 8051 (not to be confused with the 8052), and there are definitions which are missing in the file from sdcc. For instance, in the original there's just SBUF, but the one from Cypress has SBUF0 and SBUF1.

Anyway, the sdcc file is useful to learn the syntax which the register definitions have to follow. It should be something like: sfr at 0x8D TH1; but the Keil format is sfr TH1 = 0x8D;. Both use sfr for register names and sbit for names of bits inside registers. I have corrected this (by using regular expressions it's done very fast).

Don't abuse the preprocessor

At ezregs.h there's also the definition of registers found at the EZ-USB board (not at the 8051), such as the ones we need to use the USB. They are xdata registers, so they have to be accessed as if they were at the external RAM.

The bad point is that they are doing a filthy trick with preprocessor:

  #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;

And like that, for all registers. The intention is the change _AT_ to ;// to ignore the right half of several lines.

This doesn't please sdcc (warning: pasting "/" and "/" does not give a valid preprocessing token); and neither to me (in fact, gcc also joins our complaining, with normal C). I have changed that to something more elegant:

  #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 */

With that, ezregs.h is fixed; fx2regs.h and fx.h also need the same change.

The .inc

These 6 files are included when you program in assembler:

They use the Keil syntax, but they are so seldom used that I preferred not to change them. I will talk of them again.

EZUSB.LIB library

The functions to work with the components added by Cypress are in the library file EZUSB.LIB, and they are more or less these. When we worked with Keil (Windows), at each project we had to include the file EZUSB.LIB so that it got linked with the compiled code; the problem is that the original EZUSB.LIB is in the format which understands Keil, but that's not the same of the one used in sdcc libraries.

Fortunately and not hoping it, I found a diretory with all the source code of the EZUSB library in a Keil installation. But it's prepared to compile only with the environment and syntax of Keil (using C51 and A51). I hope I don't disturb anyone if I distribute this here.

After a lot of work, I ported all the C and ASM code of this library to a format which sdcc could understand. The result is in ezusb-fix/ (also in this tar.gz); will Cypress get angry if I publish fixes for their code?.

Despite the codes were simple, I have fixed several errors. Now I will explain the changes that were needed.

Creation of a library

According to Cypress, EZUSB.LIB is created using C51 to compile each .c separately and A51 for each .a51, also separately. Then you must create an empty library, with LIB51, and add all of the generated object files except the ones for 2200jmpt.a51, FXJmpTb.a51, renum.a51, USBJmpTb.a51.

According to sdcc, a .lib is just a list of .rel files, which are the object code, result of the individual compilation of each .c or .asm (by using -c). It's so easy: just a list with the file names, one per line.

In addition, a .lib from sdcc may also contain all the code (so that it ends up being in just one file), and the format is equally simple: by using sdcclib, the content of each rel (which is plain text) is included, and they are classified by using a tags system similar to HTML. On the other hand, the Keil format is binary, and not easy to understand (at least by me).

Then, my goal is to fix the 13 files 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, compile them separately, and join them in a librray to make my own ezusb.lib.

Lowercase names

To compile the library on Linux, the #include have to reference the correct file (agreeing even with the upper and lower case). I have changed in all files things like #include "..\inc\Fx.h" to #include <fx.h>.

stdlib.h instead of stdio.h

get_cnfg.c, get_dscr.c, get_infc.c, get_strd.c needed the symbol NULL, which is in stdlib.h, not stdio.h. i2c.c directly didn't need it.

Conversion to xdata* at get_infc.c

It says this when compiling:

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

The relevant part of code is (the 16 is the return):

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

The function should return a xdata* ("pointer to the RAM external to the 8051"), but in the small model we are using in compilation, local variables are placed by default in internal RAM (storage type data).

I don't think that USB descriptors are located in data (the 128/256 bytes of internal RAM), so declaring it xdata* it will solve it:

  INTRFCDSCR      xdata *intrfc_dscr;

volatile at I2CPckt

The error compiling i2c.c:

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

Line to blame: I2CPCKT volatile I2CPckt;. But at ezusb.h it says that extern I2CPCKT I2CPckt;, so that doesn't match.

There are two options: add volatile wherever needed, or remove it where it appears. I think that if they put it, there must be some reason, so I add it too to ezusb.h and fx2.h:

  extern I2CPCKT volatile I2CPckt;

function cannot return 'bit'

At i2c.c and i2c_rw.c there are some functions which return 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)

According to ezusb.h, it's said that typedef bit BOOL;, but a function returning a single bit is not allowed. In fact, the ones from i2c.c return TRUE and FALSE, which are defined (#define) at ezusb.h to 1 and 0 respectively, so they can be normal integers. The strange part is that the ones from i2c_rw.c return constants like I2C_OK, I2C_NACK, I2C_BERROR, with values 8, 7 and 6 respectively. I can't understand the meaning of that transformation to bit; sdcc is doing well by complaining because of that.

I have changed the return type to BYTE, changing the headers at i2c.c, i2c_rw.c and ezusb.h and fx2.h. In theory a byte can function in the same way as a bit: 0 means false, and different to 0, true. But this change may make some older programs (which wanted to get a BOOL) stop compiling now.

Transforming the Keil assembler to sdcc

To complete EZUSB.LIB there still are 3 assembler files: delayms.a51, susp.a51, usbirqcl.a51, which include (in the sense of #include) to ezregs.inc, even when there are 6 includes: ezbits.inc ezbits.inc ezregs.inc fx2regs.inc fx.inc macros.inc reg320.inc .

The syntax used by asx8051 (the compiler found in sdcc) is very different to the one from A51 (the assembler compiler from Keil). It's worth studying the .asm which sdcc generates when it compiles C.

In asx8051 there are some differences respect from A51:

Instead of fixing the 3 .a51, I have created 3 .asm files with the code written in the syntax of asx8051, which -in my opinion- is clearer than A51's. Since data from the include was scarcely used (just to obtain the address of the SFR USBCS, PCON and EXIF), I have repeated the declaration of those registers where needed, and I haven't used includes.

I hope to have converted the files correctly.

Testing the library

Once the 13 files (10 C and 3 ASM) have been compiled, there have been no problems creating ezusb.lib from the 13 .rel with sdcclib.

Using the corrected header files (include-fix/) and the corrected library (ezusb-fix/) I can now compile DosPunts.c with:

  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

I tried simulating the hex with s51 (it comes with sdcc): s51 DosPunts.hex. It's not very appropriate since the PI board is not an 8051 (for instance, it has 256 bytes of RAM instead of 128). In the simulation, s51 said:

  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

That's logical, since at the 8051 (which is what it's simulating), there are just 128 (0x7f) bytes of RAM, and beyond 0x80 there live the SFRs. You can blame the initialization code which sdcc adds to empty the RAM:

  ; starting with r0=0 and a=0
  00005$: mov     @r0,a
          djnz    r0,00005$
  ;       _mcs51_genRAMCLEAR() end

This is like i=0; do { mem[i]=0; i--; } while (i!=0);. That's effective, but the simulator complains. You may add --no-xinit-opt when compiling to stop generating this initialization code and avoid warnings (but face the consequences), or ask s51 to perform as an 8052, which has the full 256 bytes: s51 -t 8052 (even when our board is not an 8052).

Summary of what I have done

What still has to be done

Other solutions

Disclaimer, and information about this document

I started writing this with absolutely no knowledge about microcontrollers; if in the theory classes at PI they had taught us something about the practices, I could have gathered all this information faster.

I have read things like this 8051 tutorial, but I am still a novice on microcontrollers. With this document I just intend to help a bit the new students so that they don't start their investigations from zero. I recommend to study the source code of the free software projects (like sdcc); it's a wonderful way of learning which it's worth profiting.

And, by the way, let me say that the subject PI was a general joke (even when everyone in our school, FIB, knows that). We made the practices trying things randomly or copying them from somewhere else, since at our group there were no explanation or notes to take, and the chip specification (340 pages, in English) didn't help very much at the beginning.

We got to the point where we were reading from a barcode scanner without having the scanner, and without even knowing how do these readers work. A teacher said that we could try to simulate it with Hyperterminal, but that he could not make it work, so there wasn't very much we could try.

And that's the way it was... at the end, the hardest thing to do was not to pass the subject, but to learn something useful.

Oh, and everything I have written has license do what you want, since I distribute some code which I don't really know if I have the right to modify. I have no relation with Cypress or with my university; I am responsible of everything told here. Write me if something needs to be changed. n142857 -at-g-m-a-i-l--dot-com

March-July 2005 (updated on 13-05-2006), Daniel Clemente Laboreo,