EZ-USB Firmware Kit - Tutorial
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:
- Sicherstellen, dass die Systemvoraussetzungen erfüllt sind, insbesondere dass javac bzw. javac.exe -- der Java-Kompiler gefunden werden kann (per PATH Umgebungsvariable)
- Ein Terminal (Linux) öffnen oder MSys (Windows) starten
- 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. - 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:
- Nur Windows: Den Treiber libusb-win32 bei Bedarf installieren, d.h. libusb-win32 dem Gerät wie folgt zuweisen:
- Das Gerät anstecken
- 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.
- 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'