EZ-USB Firmware Kit - Tutorial

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Einleitung

ZTEX stellt ein Firmware-Kit mit zugehörigem Treiber-API zur Verfügung, welches ursprünglich für ZTEX USB-Boards entworfen wurde, aber auch mit anderer EZ-USB basierter Hardware zusammen arbeitet.

Das Paket ist quelloffen und wird wird unter GPLv3 vertrieben.

Das Paket läuft unter Linux und Windows. (Die Portierung auf andere Betriebssysteme sollte problemlos möglich sein). Die Java-Treiber-API ermöglicht die Entwicklung von plattformübergreifenden Geräte-Treibern.

Eine Einführung in das Firmware-Kit und eine Beschreibung der Vorzüge befindet sich bereits im Artikel ZTEX USB- und USB-FPGA-Module und wird hier nicht wiederholt.

Wichtigste Eigenschaften des Pakets

  • Firmware-Kit (für EZ-USB Mikrocontroller) in C geschrieben (erfordert SDCC-Compiler).
  • Zusammenbau der Firmware erfolgt mittels Makro-Prozessor. Dieser ermöglicht mit wenigen Makros alle erforderlichen Einstellungen festzulegen. Die benötigten USB-Deskriptoren und die dazugehörigen Routinen werden automatisch generiert.
  • Treiber-API ist in Java geschrieben und ermöglicht Plattform-unabhängige Geräte-Treiber.
  • Open-Source unter GPLv3 lizenziert
  • Hauptfunktionen des Firmware-Kits / Treiber-API's:
    • Hochladen der Firmware direkt in den EZ-USB Mikrocontroller
    • EEPROM-Interface zum Lesen / Scheiben von Daten in den / aus dem EEPROM-Speicher
    • Hochladen der Firmware in den EEPROM
    • Hochladen des Bitstreams in das FPGA (für ZTEX FPGA Boards)

Systemvoraussetzungen

Die Systemvoraussetzungen für das Kompilieren der Firmware und der Treiber sind

  • Java JDK 6 oder später
  • SDCC
  • MSys (nur für Windows)

Die Systemvoraussetzungen für das Ausführen der Beispiele bzw. der selbst erstellten Software sind

  • Java JRE 6 oder später

Download / Homepage / weitere Informationen

Beispiel

Das folgende Beispiel für ZTEX USB-FPGA-Module 2.13c demonstriert den verwendeten Makro-Ansatz des Firmware-Kits. Das Beispiel ist ein Bestandteil des Firmware-Kits.

Die Firmware (definiert in ucecho.c) legt die Endpoints 2 und 4 fest (beide 512 Bytes, doppel-gepuffert, Bulk-Transfer, zu Interface 0 gehörig). Alle zum Endpoint 4 geschriebenen Daten werden vom FPGA in Großbuchstaben konvertiert und können von Endpoint 2 zurückgelesen werden.

Der Bitstream für die FPGA-Konfiguration wird in ucecho.vhd definiert. Das FPGA liest pro Taktzyklus ein Byte von Port PC, konvertiert es in Großbuchstaben und schreibt es nach Port PB zurück.

Der Treiber (definiert in UCEcho.java) lädt bei Bedarf die Firmware in dem EZ-USB-Mikrocontroller und den Bitstream in das FPGA hoch, sendet Benutzer-Strings zum Gerät und liest diese zurück.

Das Hochladen der Firmware in den EEPROM wird ebenfalls unterstützt (z. B. mittels des zum Paket gehörenden Werkzeugs FWLoader).

ucecho.c

Das ist der C-Quellcode für die Firmware:

#include[ztex-conf.h]   // Loads the configuration macros, see ztex-conf.h for the available macros
#include[ztex-utils.h]  // include basic functions

// define endpoints 2 and 4, both belong to interface 0 (in/out are from the point of view of the host)
EP_CONFIG(2,0,BULK,IN,512,2);    
EP_CONFIG(4,0,BULK,OUT,512,2);   

// identify as ZTEX USB FPGA Module 2.13  (Important for FPGA configuration)
IDENTITY_UFM_2_13(10.17.0.0,0);	 

// give them a nice name
// this product string is also used for identification by the host software
#define[PRODUCT_STRING]["ucecho example for UFM 2.13"]

// enables high speed FPGA configuration via EP4
ENABLE_HS_FPGA_CONF(4);

// enables flash  and loading bitstream from flash
ENABLE_FLASH;
ENABLE_FLASH_BITSTREAM;

__xdata BYTE run;

#define[PRE_FPGA_RESET][PRE_FPGA_RESET
    run = 0;
]
// this is called automatically after FPGA configuration
#define[POST_FPGA_CONFIG][POST_FPGA_CONFIG
    IFCONFIG = bmBIT7;	        // internel 30MHz clock, drive IFCLK ouput, slave FIFO interface
    SYNCDELAY; 
    EP2FIFOCFG = 0;
    SYNCDELAY;
    EP4FIFOCFG = 0;
    SYNCDELAY;

    REVCTL = 0x0;	// reset 
    SYNCDELAY; 
    EP2CS &= ~bmBIT0;	// stall = 0
    SYNCDELAY; 
    EP4CS &= ~bmBIT0;	// stall = 0

    SYNCDELAY;		// first two packages are waste
    EP4BCL = 0x80;	// skip package, (re)arm EP4
    SYNCDELAY;
    EP4BCL = 0x80;	// skip package, (re)arm EP4

    FIFORESET = 0x80;	// reset FIFO
    SYNCDELAY;
    FIFORESET = 0x82;
    SYNCDELAY;
    FIFORESET = 0x00;
    SYNCDELAY;

    OED = 255;
    run = 1;
]

// include the main part of the firmware kit, define the descriptors, ...
#include[ztex.h]

void main(void)	
{
    WORD i,size;
    
// init everything
    init_USB();

    while (1) {	
	if ( run && !(EP4CS & bmBIT2) ) {	// EP4 is not empty
	    size = (EP4BCH << 8) | EP4BCL;
	    if ( size>0 && size<=512 && !(EP2CS & bmBIT3)) {	// EP2 is not full
		for ( i=0; i<size; i++ ) {
		    IOD = EP4FIFOBUF[i];	// data from EP4 is converted to uppercase by the FPGA ...
		    EP2FIFOBUF[i] = IOB;	// ... and written back to EP2 buffer
		} 
		EP2BCH = size >> 8;
		SYNCDELAY; 
		EP2BCL = size & 255;		// arm EP2
	    }
	    SYNCDELAY; 
	    EP4BCL = 0x80;			// skip package, (re)arm EP4
	}
    }
}

ucecho.vhd

Das ist der VHDL-Quellcode für die FPGA-Konfiguration:

library ieee;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
Library UNISIM;
use UNISIM.vcomponents.all;

entity ucecho is
   port(
      pd        : in unsigned(7 downto 0);
      pb        : out unsigned(7 downto 0);
      fxclk     : in std_logic
   );
end ucecho;


architecture RTL of ucecho is

--signal declaration
signal pb_buf : unsigned(7 downto 0);

begin
    pb <= pb_buf;

    dpUCECHO: process(fxclk)
    begin
         if fxclk' event and fxclk = '1' then
	    if ( pd >= 97 ) and ( pd <= 122)
	    then
		pb_buf <= pd - 32;
	    else
		pb_buf <= pd;
	    end if;
	end if;
    end process dpUCECHO;
    
end RTL;

UCEcho.java

Das ist der Java-Quellcode für die PC-seitige Software / für den Treiber:

import java.io.*;
import java.util.*;

import ch.ntb.usb.*;

import ztex.*;

// *****************************************************************************
// ******* ParameterException **************************************************
// *****************************************************************************
// Exception the prints a help message
class ParameterException extends Exception {
    public final static String helpMsg = new String (
		"Parameters:\n"+
		"    -d <number>       Device Number (default: 0)\n" +
		"    -f 	       Force uploads\n" +
		"    -e                Use the encrypted bitstream\n" +
		"    -p                Print bus info\n" +
		"    -h                This help" );
    
    public ParameterException (String msg) {
	super( msg + "\n" + helpMsg );
    }
}

// *****************************************************************************
// ******* Test0 ***************************************************************
// *****************************************************************************
class UCEcho extends Ztex1v1 {

// ******* UCEcho **************************************************************
// constructor
    public UCEcho ( ZtexDevice1 pDev ) throws UsbException {
	super ( pDev );
    }

// ******* echo ****************************************************************
// writes a string to Endpoint 4, reads it back from Endpoint 2 and writes the output to System.out
    public void echo ( String input ) throws UsbException {
	byte buf[] = input.getBytes(); 
	int i = LibusbJava.usb_bulk_write(handle(), 0x04, buf, buf.length, 1000);
	if ( i<0 )
	    throw new UsbException("Error sending data: " + LibusbJava.usb_strerror());
	System.out.println("Send "+i+" bytes: `"+input+"'" );

	try {
    	    Thread.sleep( 10 );
	}
	    catch ( InterruptedException e ) {
	}

	buf = new byte[1024];
	i = LibusbJava.usb_bulk_read(handle(), 0x82, buf, 1024, 1000);
	if ( i<0 )
	    throw new UsbException("Error receiving data: " + LibusbJava.usb_strerror());
	System.out.println("Read "+i+" bytes: `"+new String(buf,0,i)+"'" );  
    }
    
// ******* main ****************************************************************
    public static void main (String args[]) {
    
	int devNum = 0;
	boolean force = false;
	boolean encrypted = false;
	
	try {
// init USB stuff
	    LibusbJava.usb_init();

// scan the USB bus
	    ZtexScanBus1 bus = new ZtexScanBus1( ZtexDevice1.ztexVendorId, ZtexDevice1.ztexProductId, true, false, 1);
	    if ( bus.numberOfDevices() <= 0) {
		System.err.println("No devices found");
	        System.exit(0);
	    }
	    
// scan the command line arguments
    	    for (int i=0; i<args.length; i++ ) {
	        if ( args[i].equals("-d") ) {
	    	    i++;
		    try {
			if (i>=args.length) throw new Exception();
    			devNum = Integer.parseInt( args[i] );
		    } 
		    catch (Exception e) {
		        throw new ParameterException("Device number expected after -d");
		    }
		}
		else if ( args[i].equals("-f") ) {
		    force = true;
		}
		else if ( args[i].equals("-e") ) {
		    encrypted = true;
		}
		else if ( args[i].equals("-p") ) {
	    	    bus.printBus(System.out);
		    System.exit(0);
		}
		else if ( args[i].equals("-p") ) {
	    	    bus.printBus(System.out);
		    System.exit(0);
		}
		else if ( args[i].equals("-h") ) {
		        System.err.println(ParameterException.helpMsg);
	    	        System.exit(0);
		}
		else throw new ParameterException("Invalid Parameter: "+args[i]);
	    }
	    

// create the main class	    
	    UCEcho ztex = new UCEcho ( bus.device(devNum) );
	    
// upload the firmware if necessary
	    if ( force || ! ztex.valid() || ! ztex.dev().productString().equals("ucecho example for UFM 2.13")  ) {
		System.out.println("Firmware upload time: " + ztex.uploadFirmware( "ucecho.ihx", force ) + " ms");
	    }
	    
// upload the bitstream if necessary
	    if ( force || ! ztex.getFpgaConfiguration() ) {
		if ( encrypted ) System.out.println("FPGA configuration time: " + ztex.configureFpgaHS( "fpga/ucecho.runs/impl_2/ucecho.bit" , force, -1 ) + " ms");
		else System.out.println("FPGA configuration time: " + ztex.configureFpga( "fpga/ucecho.runs/impl_1/ucecho.bit" , force, -1 ) + " ms");
	    } 

// claim interface 0
	    ztex.trySetConfiguration ( 1 );
	    ztex.claimInterface ( 0 );
	    
// read string from stdin and write it to USB device
	    String str = "";
	    BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) );
	    while ( ! str.equals("quit") ) {
		System.out.print("Enter a string or `quit' to exit the program: ");
		str = reader.readLine();
		if ( ! str.equals("") )
		    ztex.echo(str);
	        System.out.println("");
	    }
	    
// release interface 0
	    ztex.releaseInterface( 0 );	
	    
	}
        catch ( BitstreamUploadException e ) {
	    System.out.println("Error: "+e.getLocalizedMessage() );
	    if ( encrypted ) System.out.println("Make sure that the encryption key from key file fpga/ucecho.nky is loaded to battery-backed RAM");
        }
	catch (Exception e) {
	    System.out.println("Error: "+e.getLocalizedMessage() );
	} 
   } 
   
}

Installation des Pakets

Das Firmware-Kit ist als tar.bz2- und als zip-Archiv verfügbar und kann z. B. mittels der Kommandos

 bunzip2 -c ztex-<release number>.tar.bz2 | tar -x

oder

 unzip ztex-<release number>.zip,

entpackt werden.

Kompilieren des Beispiels

Die Regeln zum Zusammenbauen der Binaries werden mittels Makefile's festgelegt, d.h. eine GNU-Umgebung ist erforderlich. Das Kompilieren geschieht in folgenden Schritten:

  1. Sicherstellen, dass die Systemvoraussetzungen erfüllt sind, insbesondere dass javac bzw. javac.exe -- der Java-Kompiler gefunden werden kann (per PATH Umgebungsvariable)
  2. Ein Terminal (Linux) öffnen oder MSys (Windows) starten
  3. Angenommen das Firmware-Kit wurde im Verzeichnis $HOME/ztex (Linux) oder im Verzeichniss c:\ztex (Windows) installiert. (Das aufgelistete Beispiel befindet sich im Unterverzeichnis examples/usb-fpga-2.13/2.13c/ucecho/.) In dieses Verzeichnis kann mittels der Befehle

    cd $HOME/ztex/examples/usb-fpga-2.13/2.13c/ucecho/

    unter Linux, bzw.

    cd /c/ztex/examples/usb-fpga-2.13/2.13c/ucecho/

    unter Windows+MSys gewechselt werden.
  4. Das Kommando

    make

    baut alles zusammen (einschließlich der Firmware) und packt es in ein .jar-Archiv.

Ausführen des Beispiels

Zum Ausführen des Beispiels wird nur die .jar-Datei benötigt, welche sowohl unter Linux als auch unter Windows läuft. (Unter Windows muss der Treiber libusb-win32 installiert sein.) Diese .jar-Datei enthält den Java-Bytecode, die Firmware für den EZ-USB Microcontroller, den Bitstream für das FPGA und die Betriebssystem-abhängigen Bibliotheken. Das Ausführen geschieht in folgenden Schritten:

  1. Nur Windows: Den Treiber libusb-win32 bei Bedarf installieren, d.h. libusb-win32 dem Gerät wie folgt zuweisen:
    1. Das Gerät anstecken
    2. Wenn Windows nach dem Treiber fragt, die Datei cypress.inf aus dem Unterverzeichnis libusb-win32 des Firmware-Kits auswählen. (Diese Datei ist gültig, falls die Cypress Hersteller- und Produkt-ID verwendet wird, was nur zu experimentellen Zwecken bei nichtöffentlicher Verwendung gestattet ist. Im Fall einer anderen Hersteller- und Produkt-ID kann mittels des Werkzeuges inf-wizard.exe im Unterverzeichnis libusb-win32eine neue .inf Datei erstellt werden.
  2. Das Beispiel mittels des Kommandos

    java -cp UCEcho.jar UCEcho

    ausführen. Da die .jar-Datei alles enthält, was zum Ausführen des Beispiels notwendig ist, kann die Datei an jede Stelle kopiert / von jeder Stelle aus ausgeführt werden.

Ein Beispielaufruf sieht wie folgt aus:

stefan@ws2:/drv_a2/usb-fpga/ztex-1.1/examples/usb-fpga-2.13/2.13c/ucecho$ java -cp UCEcho.jar UCEcho
FPGA configuration time: 44 ms
Enter a string or `quit' to exit the program: Hello world!
Send 12 bytes: `Hello world!'
Read 12 bytes: `HELLO WORLD!'

Enter a string or `quit' to exit the program: quit
Send 4 bytes: `quit'
Read 4 bytes: `QUIT'