Laden Sie die Konfiguration über USB auf das FPGA herunter oder zerlegen Sie FTDI MPSSE



Im Leben jeder Schublade kommt eine Zeit, in der Sie Ihren eigenen Loader der Konfigurationsdatei in das FPGA schreiben möchten. Ich musste an der Entwicklung eines Ausbildungsstandes für die Abteilung einer technischen Universität teilnehmen. Der Stand ist für die Untersuchung der digitalen Signalverarbeitung konzipiert, obwohl dies im Rahmen dieses Artikels nicht von besonderer Bedeutung ist. Und die Bedeutung ist, dass das FPGA (Altera Cyclone IV) das Herzstück des Standes ist, auf dem die Schüler alle Arten von DSP-Schemata sammeln, wie sie vom Autor des Standes konzipiert wurden. Der Ständer ist über USB mit dem Computer verbunden. Sie müssen das FPGA über USB vom Computer herunterladen.

Es wurde beschlossen, mithilfe von FTDI in seiner zweikanaligen Inkarnation - FT2232H - eine Verbindung zu einem PC herzustellen. Ein Kanal wird für die FPGA-Konfiguration verwendet, der andere für den Hochgeschwindigkeits-FIFO-Austausch.


FTDI verfügt über eine MORPH-IC-II- Debug - Karte, auf der das Cyclone II-FPGA über USB geflasht wird. Konzepte im öffentlichen Bereich. Der Quellcode des Bootloaders ist teilweise geöffnet: Der Bootloader selbst ist verfügbar. Die gesamte Logik der Arbeit mit FTDI wird jedoch in eine private Bibliothek verschoben und kann nicht geändert werden. In Wahrheit hatte ich ursprünglich vor, diesen Bootloader in meinem Projekt zu verwenden oder in extremen Fällen meine Shell basierend auf ihrer DLL zu erstellen. Die Firmware wird im passiven seriellen Modus (passives serielles PS) in das FPGA geladen, FTDI arbeitet im MPSSE-Modus. Auf dem Steckbrett wurde die Leistung der MORPH-IC-II-Lösung vollständig bestätigt, aber das Problem kam, wie es häufig vorkommt, nicht von wo. Es stellte sich heraus, dass während des Betriebs der DLL MORPH-IC-II alle angeschlossenen FTDI-Geräte blockiert sind und im Rahmen des Trainingskomplexes zwei weitere Geräte mit ähnlichen Wandlern vorhanden sind: ein Generator und ein Signalanalysator. Gleichzeitiges Arbeiten mit ihnen ist nicht möglich. Verdammt komisch und nervig.


Ein ähnlicher Fall wurde von den Jungs vom Marsrover implementiert: dem USB JTAG-Programmierer MBFTDI . FTDI wird dort auch im MPSSE-Modus verwendet, aber im Gegensatz zu MORPH-IC-II werden FPGA-Operationen im JTAG-Modus ausgeführt. Quellen sind frei verfügbar, aber ich habe keinen eindeutigen Hinweis auf ihren Status (Lizenz) gefunden. Um sie in einem kommerziellen Projekt zu verwenden, hob sich meine Hand nicht.


Ich werde einen solchen Fehler korrigieren. Alles, was im Rahmen dieses Artikels vorgestellt wird, wird in einem offenen Repository unter der BSD-Lizenz veröffentlicht.


Konfigurationsdatei auf FPGA-Chip herunterladen


Zunächst sollten Sie sich mit dem FPGA-Startmodus befassen. Für diejenigen, die gerade erst anfangen, sich mit dem Thema vertraut zu machen, werde ich einen kleinen Ausflug machen. Obwohl Altera (Intel) FPGAs der Cyclone IV E-Familie auf meinem Board installiert sind, sind die Lademethoden für die gesamte Cyclone FPGA-Gruppe ähnlich, und es besteht der Verdacht, dass sie in der einen oder anderen Form für viele andere Familien geeignet sind.


Diese Art von FPGA verwendet flüchtiges SRAM zum Speichern von Konfigurationsdaten. Diese Konfigurationsdaten bestimmen die Funktionalität des resultierenden Geräts. Im Fachjargon werden diese Daten häufig als "Firmware" bezeichnet. Daher wird die Firmware in einem speziellen RAM gespeichert und muss bei jedem Einschalten des Geräts in den FPGA-Chip geladen werden. Es gibt verschiedene Möglichkeiten (Konfigurationsschemata), mit denen die Firmware in den SRAM geladen werden kann (die Liste ist für Cyclone IV E relevant):


  1. Aktive serielle (AS).
  2. Aktive Parallele (AP)
  3. Passive Seriennummer (PS)
  4. Schnelle passive Parallele (FPP).
  5. JTAG.

Die Auswahl eines bestimmten Startmodus erfolgt über die externen Terminals des FPGA (MSEL-Gruppe). Der JTAG-Modus ist immer verfügbar. Der aktive Modus bedeutet, dass das FPGA bei eingeschalteter Stromversorgung unabhängig Daten aus dem externen Speicher (seriell oder parallel) liest. Im passiven Modus wartet das FPGA darauf, dass ein externes Medium proaktiv Konfigurationsdaten an dieses überträgt. Diese Schemata passen gut in das Konzept von Master (Master) - Slave (Slave). Im aktiven Modus fungiert das FPGA als Master und im passiven Modus als Slave.


Bei diesem Problem handelt es sich nicht um das FPGA, sondern der Benutzer muss entscheiden, wann die Firmware aktualisiert werden soll, sodass der Startmodus passiv sein sollte. Und um die Beine des Chips zu schonen, wählen wir eine serielle Schnittstelle. Passive Serial (PS) -Modus und JTAG sind hier geeignet. Die Logik des JTAG ist etwas komplizierter. Konzentrieren wir uns also auf die erste Option.
Die folgende Abbildung zeigt das Verbindungsschema des FPGA zu einem externen Controller zum Herunterladen im PS-Modus.



Um die Konfiguration zu starten, muss der externe Master einen Übergang von niedrig nach hoch auf der nCONFIG- Leitung erzeugen . Sobald das FPGA bereit ist, Daten zu empfangen, bildet es auf der nSTATUS- Leitung einen hohen Pegel. Danach kann der Master mit der Übertragung von Daten auf der DATA- Leitung [0] und den entsprechenden Taktimpulsen auf der DCLK- Leitung beginnen. Daten müssen an das Zielgerät übertragen werden, bis auf der Leitung CONF_DONE ein hoher Pegel hergestellt ist (oder die Daten nicht enden) und das FPGA in den Initialisierungszustand wechselt. Es ist zu beachten, dass nach dem Setzen von CONF_DONE auf eins zwei weitere Taktimpulse angelegt werden müssen, damit die FPGA-Initialisierung beginnt.


Die Daten werden durch das niedrigstwertige Bit ( LSB ) vorwärts übertragen, dh wenn die Konfigurationsdatei die Sequenz 02 1B EE 01 FA enthält (nehmen Sie das Beispiel wie aus dem Handbuch), sollte die Sequenz auf der Datenleitung gebildet werden:


0100-0000 1101-1000 0111-0111 1000-0000 0101-1111 

Somit werden nur fünf Leitungen verwendet: DATA- Leitungen [0] und DCLK für die serielle Übertragung, nCONFIG- , nSTATUS- , CONF_DONE-Leitungen für die Steuerung.
Im Kern ist der PS-Modus nichts anderes als SPI mit zusätzlicher Flag-Manipulation.
Die Datenübertragungsrate sollte niedriger sein als die in der Dokumentation angegebene maximale Frequenz. Für die im Projekt verwendete Cyclone IV E-Serie beträgt sie 66 MHz.


Die minimale Übertragungsfrequenz existiert nicht, theoretisch ist es möglich, die Konfiguration auf unbestimmte Zeit auszusetzen. Dies bietet hervorragende Möglichkeiten für ein schrittweises Debuggen unter Beteiligung eines Oszilloskops, das wir sicherlich verwenden werden.


Die folgende Abbildung zeigt das Zeitdiagramm der Schnittstelle mit den wichtigsten Zeitabläufen.



Sly Beast MPSSE


Betrachten Sie den Betrieb von FTDI im MPSSE-Modus. Der MPSSE-Modus (Multi-Protocol Synchronous Serial Engine) ist meiner Meinung nach ein mehr oder weniger erfolgreicher Versuch, einen bestimmten Designer für serielle Schnittstellen zu erstellen, um dem Entwickler die Möglichkeit zu geben, weit verbreitete Datenübertragungsprotokolle wie SPI, I2C, JTAG, 1-Wire und viele zu implementieren andere basieren auf ihnen.


Derzeit ist der Modus für Mikroschaltungen verfügbar: FT232H, FT2232D, FT2232H, FT4232H. In meinem Projekt verwende ich FT2232H, daher sprechen wir in größerem Umfang darüber. Für den MPSSE-Modus werden 16 Abschnitte zugewiesen, die in zwei Bytes unterteilt sind: das untere L und das höchste H. Jedes Byte kann gelesen oder gesetzt werden. Die vier Unterschenkel des Bytes L haben spezielle Funktionen - über sie kann eine serielle Datenübertragung erfolgen. Jeder Zweig kann als Eingabe oder Ausgabe konfiguriert werden, ein Standardwert für die Ausgabe kann festgelegt werden. Für die sequentielle Übertragung, die Reihenfolge der Bits ( MSB / LSB ), die Länge des übertragenen Wortes, die Frequenz der Taktimpulse, die Synchronisation vorne - vorne (steigend) oder hinten (fallend) können Sie wählen, ob Sie nur Taktimpulse ohne Daten senden oder die 3-Phasen-Taktung auswählen möchten (relevant für I2C) und vieles mehr.


Fahren Sie nahtlos mit der Programmierung fort. Es gibt zwei alternative Möglichkeiten der Softwareinteraktion mit FTDI-Chips: Die erste, nennen wir es klassisch. In diesem Fall wird der Chip im System, wenn er an einen USB-Anschluss angeschlossen ist, als virtueller serieller Anschluss (COM) definiert. Das Betriebssystem verwendet den VCP-Treiber (Virtual COM Port). Alle weiteren Programmierungen unterscheiden sich nicht von der Programmierung des klassischen COM-Ports: geöffnet - übertragen / gezählt - geschlossen. Dies gilt für verschiedene Betriebssysteme, einschließlich Linux und Mac OS. Mit diesem Ansatz können jedoch nicht alle Funktionen des FTDI-Controllers realisiert werden - der Chip funktioniert als USB-UART-Adapter. Die zweite Methode wird von der proprietären FTD2XX-Bibliothek bereitgestellt. Diese Schnittstelle bietet spezielle Funktionen, die in der Standard-COM-Port-API nicht verfügbar sind. Insbesondere können spezielle Betriebsmodi wie MPSSE, 245 FIFO und Bit-Bang konfiguriert und verwendet werden. Die FTD2XX-API-Bibliothek ist im D2XX-Programmierhandbuch für die Entwicklung von Softwareanwendungen gut dokumentiert, das in engen Kreisen seit langem bekannt ist. Und ja, FTD2XX ist auch für verschiedene Betriebssysteme verfügbar.


Die FTDI-Entwickler standen vor der Aufgabe, das relativ neue MPSSE in das bestehende Interaktionsmodell der D2XX-Software zu integrieren. Und es gelang ihnen, für die Arbeit im MPSSE-Modus werden dieselben Funktionen verwendet wie für andere "klassische" Modi, es wird dieselbe FTD2XX-Bibliotheks-API verwendet.


Kurz gesagt kann der Algorithmus für den Betrieb im MPSSE-Modus wie folgt beschrieben werden:


  1. Suchen Sie das Gerät im System und öffnen Sie es.
  2. Initialisieren Sie den Chip und versetzen Sie ihn in den MPSSE-Modus.
  3. Stellen Sie den Betriebsmodus von MPSEE ein.
  4. Direkte Arbeit mit Daten: GPIO senden, empfangen, verwalten - wir implementieren das Zielaustauschprotokoll.
  5. Schließen Sie das Gerät.

Einen Bootloader schreiben


Kommen wir zum praktischen Teil. In meinen Experimenten werde ich die Eclipse-Version von Oxygen.3a Release (4.7.3a) als IDE und mingw32-gcc (6.3.0) als Compiler verwenden. Win7 Betriebssystem.


Von der FTDI- Website laden wir die neueste aktuelle Version des Treibers für unser Betriebssystem herunter. Im Archiv finden wir die Header-Datei ftd2xx.h mit einer Beschreibung aller API-Funktionen. Die API selbst ist als ftd2xx.dll implementiert, aber wir werden den dynamischen Import für später belassen und den statischen Link verwenden: Wir benötigen die Bibliotheksdatei ftd2xx.lib. In meinem Fall befindet sich ftd2xx.lib im i386-Verzeichnis.


Erstellen Sie in Eclipse ein neues C-Projekt. Das Erstellen eines Makefiles kann mit einer IDE als vertrauenswürdig eingestuft werden. Geben Sie in den Linker-Einstellungen den Pfad und den Namen der ftd2xx-Bibliothek an (ich habe die erforderlichen Dateien in das Projektverzeichnis im Ordner ftdi übertragen). Ich werde mich nicht auf die Funktionen zum Einrichten eines Projekts für Eclipse konzentrieren, da ich vermute, dass die meisten von ihnen andere Umgebungen und Compiler für die Win-Programmierung verwenden.


Punkt eins. Suchen Sie ein Gerät und öffnen Sie es


Mit der FTD2XX-API können Sie den Chip anhand der einen oder anderen bekannten Informationen öffnen. Dies kann die Seriennummer im System sein: Der erste angeschlossene FTDI-Chip erhält die Nummer 0, die nächste 1 usw. Die Anzahl im System wird durch die Reihenfolge bestimmt, in der die Mikroschaltungen angeschlossen sind, gelinde gesagt, dies ist nicht immer praktisch. Um den Chip nach Nummer zu öffnen, wird die Funktion FT_Open . Sie können den Chip anhand seiner Seriennummer ( FT_OPEN_BY_SERIAL_NUMBER ), Beschreibung ( FT_OPEN_BY_DESCRIPTION ) oder nach Standort ( FT_OPEN_BY_LOCATION ) FT_OpenEx . FT_OpenEx Funktion FT_OpenEx . Die Seriennummer und Beschreibung werden im internen Speicher des Chips gespeichert und können dort während der Herstellung des Geräts mit installiertem FTDI aufgezeichnet werden. Die Beschreibung kennzeichnet in der Regel den Gerätetyp oder die Familie, und die Seriennummer muss für jedes Produkt eindeutig sein. Daher ist die Beschreibung die bequemste Methode, um Geräte zu identifizieren, die von dem zu entwickelnden Programm unterstützt werden. Wir werden den FTDI-Chip gemäß der Beschreibung (Deskriptor) öffnen. Wenn wir die Chip-Deskriptor-Zeichenfolge anfangs kannten, müssen wir nicht nach dem Gerät im System suchen. Als Experiment werden jedoch alle Geräte angezeigt, die mit FTDI an den Computer angeschlossen sind. Mit der Funktion FT_CreateDeviceInfoList erstellen FT_CreateDeviceInfoList eine detaillierte Liste der verbundenen Chips, und mit der Funktion FT_GetDeviceInfoList berücksichtigt.


Liste der angeschlossenen Geräte. Listing:
 ftStatus = FT_CreateDeviceInfoList(&numDevs); if (ftStatus == FT_OK) { printf("Number of devices is %d\n",numDevs); } if (numDevs == 0) return -1; // allocate storage for list based on numDevs devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs); ftStatus = FT_GetDeviceInfoList(devInfo,&numDevs); if (ftStatus == FT_OK) for (int i = 0; i < numDevs; i++) { printf("Dev %d:\n",i); printf(" Flags=0x%x\n",devInfo[i].Flags); printf(" Type=0x%x\n",devInfo[i].Type); printf(" ID=0x%x\n",devInfo[i].ID); printf(" LocId=0x%x\n",devInfo[i].LocId); printf(" SerialNumber=%s\n",devInfo[i].SerialNumber); printf(" Description=%s\n",devInfo[i].Description); } 

Begrüße meinen Zoo
 D:\workspace\ftdi-mpsse-ps\Debug>ftdi-mpsse-ps.exe Number of devices is 4 Dev 0: Flags = 0x0 Type = 0x5 ID = 0x4036001 LocId = 0x214 SerialNumber = AI043NNV Description = FT232R USB UART Dev 1: Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x2121 SerialNumber = L731T70OA Description = LESO7 A Dev 2: Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x2122 SerialNumber = L731T70OB Description = LESO7 B Dev 3: Flags = 0x2 Type = 0x8 ID = 0x4036014 LocId = 0x213 SerialNumber = FTYZ92L6 Description = LESO4.1_ER 

An meinen PC sind drei Geräte mit FTDI-Chips angeschlossen: FT232RL (Typ 0x5), FT2232H (Typ 0x6) und FT232H (Tepe 0x8). Der FT2232H-Chip im System wurde als zwei unabhängige Geräte angezeigt (Dev 1 und Dev 2). Die FPGA-PS-Schnittstelle ist mit Dev 2 verbunden, ihr Deskriptor lautet „LESO7 B“. Öffne es:


 //Open a device with device description "LESO7 B" ftStatus = FT_OpenEx("LESO7 B", FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { printf ("pen failure\r\n"); return -1; } 

Die meisten API-Funktionen geben den Status ihres Aufrufs vom Typ FT_STATUS . Alle möglichen Werte werden in der Header-Datei als enum beschrieben. Es gibt viele davon, aber es reicht zu wissen, dass der FT_OK Wert das Fehlen eines Fehlers ist. Alle anderen Werte sind Fehlercodes. Ein guter Programmierstil besteht darin, den Statuswert nach jedem Aufruf der API-Funktion zu überprüfen.


Wenn das Gerät erfolgreich geöffnet wurde, wird in der Variablen ftHandle anderer Wert als Null ftHandle , ein äquivalenter Dateideskriptor, der beim Arbeiten mit Dateien verwendet wird. Das resultierende Handle stellt eine Verbindung mit der Hardwareschnittstelle her und sollte verwendet werden, wenn alle Bibliotheksfunktionen aufgerufen werden, die Zugriff auf den Chip erfordern.
Um die Funktionsfähigkeit des Systems für die aktuelle Phase in der Praxis zu bestätigen, sollten wir sofort mit Schritt 5 unseres Algorithmus fortfahren.


Nachdem Sie mit dem Chip fertig sind, müssen Sie ihn schließen. Verwenden Sie dazu die Funktion FT_Close :


 FT_Close(ftHandle); 

Punkt 2. Initialisieren Sie den Chip und schalten Sie die MPSSE ein


Die Einstellung ist typisch für die meisten Modi und wird in der Dokumentation zu AN_135 FTDI MPSSE Basics ausführlich beschrieben .


  1. Wir führen einen Reset (Rezet) des Chips durch. FT_ResetDevice Funktion.
  2. Falls sich im Empfangspuffer Müll befindet, löschen wir ihn. FT_Purge Funktion.
  3. Passen Sie die Größe der Puffer zum Lesen und Schreiben an. Funktion FT_SetUSBParameters .
  4. Schalten Sie die Parität aus. FT_SetChars .
  5. Wir legen Zeitüberschreitungen für das Lesen und Schreiben fest. Standardmäßig sind Zeitüberschreitungen deaktiviert. Aktivieren Sie die Übertragungszeitüberschreitung. FT_SetTimeouts .
  6. Wir konfigurieren die Wartezeit für das Senden eines Pakets vom Chip an den Host. Standardmäßig 16 ms, beschleunigen auf 1 ms. FT_SetLatencyTimer .
  7. Aktivieren Sie die Flusskontrolle, um eingehende Anforderungen zu synchronisieren. FT_SetFlowControl .
  8. Alles ist bereit, um den MPSSE-Modus zu aktivieren. Setzen Sie den MPSSE-Controller zurück. Wir verwenden die Funktion FT_SetBitMode und setzen den Modus auf 0 (Modus = 0, Maske = 0).
  9. Schalten Sie den MPSSE-Modus ein. Funktion FT_SetBitMode - Modus = 2, Maske = 0.

Wir vereinen und konfigurieren den Chip in der Funktion MPSSE_open . Als Parameter übergeben wir eine Zeile mit dem Handle des zu öffnenden Geräts:


Listing MPSSE_open
 static FT_STATUS MPSSE_open (char *description) { FT_STATUS ftStatus; ftStatus = FT_OpenEx(description, FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { printf ("open failure\r\n"); return FT_DEVICE_NOT_OPENED; } printf ("open OK, %d\r\n", ftHandle); printf("\nConfiguring port for MPSSE use...\n"); ftStatus |= FT_ResetDevice(ftHandle); //Purge USB receive buffer first by reading out all old data from FT2232H receive buff: ftStatus |= FT_Purge(ftHandle, FT_PURGE_RX); //Set USB request transfer sizes to 64K: ftStatus |= FT_SetUSBParameters(ftHandle, 65536, 65536); //Disable event and error characters: ftStatus |= FT_SetChars(ftHandle, 0, 0, 0, 0); //Sets the read and write timeouts in milliseconds: ftStatus |= FT_SetTimeouts(ftHandle, 0, 5000); //Set the latency timer to 1mS (default is 16mS): ftStatus |= FT_SetLatencyTimer(ftHandle, 1); //Turn on flow control to synchronize IN requests: ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_RTS_CTS, 0x00, 0x00); //Reset controller: ftStatus |= FT_SetBitMode(ftHandle, 0x0, FT_BITMODE_RESET); //Enable MPSSE mode: ftStatus |= FT_SetBitMode(ftHandle, 0x0, FT_BITMODE_MPSSE); if (ftStatus != FT_OK) { printf("Error in initializing the MPSSE %d\n", ftStatus); return FT_OTHER_ERROR; } Sleep(50); // Wait for all the USB stuff to complete and work return FT_OK; } 

Punkt 3. Konfigurieren Sie den MPSEE-Betriebsmodus


Tatsächlich ist zu diesem Zeitpunkt der MPSSE-Prozessor aktiviert und bereit, Befehle zu empfangen. Befehle sind Bytefolgen, deren erstes Byte "Op-Code" ist, gefolgt von Befehlsparametern. Der Befehl hat möglicherweise keine Parameter und besteht aus einem "Op-Code". Befehle werden mit der Funktion FT_Write . Eine Antwort vom MPSSE-Prozessor kann mit der Funktion FT_Read abgerufen werden.


Nach jedem Senden eines Befehls ist es hilfreich, die Prozessorantwort zu lesen, da die Antwort im Falle eines falschen Befehls eine Fehlermeldung enthalten kann - das 0xFA-Zeichen. Der Mechanismus "Bad Command - 0xFA Response" kann verwendet werden, um das Anwendungsprogramm mit dem MPSSE-Prozessor zu synchronisieren. Wenn alles in Ordnung ist, gibt der Chip das 0xFA-Zeichen bei einem absichtlich fehlerhaften Befehl zurück. Operationscodes werden im Befehlsprozessor für den MPSSE- und MCU-Hostbus-Emulationsmodus beschrieben .
Bei der Konfiguration von MPSSE müssen Datenrate, Richtung und Anfangszustände der E / A-Leitungen festgelegt werden.
Stellen Sie die Datenrate des MPSSE-Prozessors ein. Die Einstellungen für Chips, die nur den Vollgeschwindigkeitsmodus (FT2232 D ) unterstützen, und für Chips mit Hochgeschwindigkeitsmodus (FT2232 H , FT232H, FT4232H) sind etwas unterschiedlich. Der ältere FT2232D verwendet einen 12-MHz-Takt, während moderne 60-MHz-Takt verwenden. Daher die Formel zur Berechnung der Datenübertragungsrate:


D.ataS.peedA. A.A. A.= fracfcore(1+D.ichvichsor) cdot2


wobei f core die FTDI- Kernfrequenz ist, ist Divisor ein Zwei-Byte-Teiler, der tatsächlich die Datentaktfrequenz festlegt.
Wenn der Teiler gleich Null ist, beträgt die maximale Datenübertragungsrate 30 Mbit / s, und die minimale Datenübertragungsrate liegt bei einem Teiler zwischen 65535 und 458 Bit / s.
Wir werden die Berechnung des Teilers dem Präprozessor anvertrauen. Das Makro gibt den Divisor zurück:


 #define FCORE 60000000ul #define MPSSE_DATA_SPEED_DIV(data_speed) ((FCORE/(2*data_speed)) -1) 

Und diese beiden Makros geben die hohen bzw. niedrigen Bytes des Teilers zurück:


 #define MPSSE_DATA_SPEED_DIV_H(data_speed) ((MPSSE_DATA_SPEED_DIV(data_speed)) >> 8) #define MPSSE_DATA_SPEED_DIV_L(data_speed) \ (MPSSE_DATA_SPEED_DIV(data_speed) - (MPSSE_DATA_SPEED_DIV_H(data_speed)<< 8)) 

Darüber hinaus ist zu beachten, dass in modernen Chips aus Gründen der Kompatibilität mit dem alten FT2232D ein zusätzlicher 5-Teiler vorhanden ist, der 60 MHz in 12 MHz umwandelt. Dieser Teiler ist standardmäßig aktiviert, in unserem Fall sollte er ausgeschaltet sein.
Wir finden den entsprechenden Op-Code (0x8A) und den Helmbefehl an den Prozessor:


Auflistung der Teameinreichungen
 BYTE byOutputBuffer[8], byInputBuffer[8]; DWORD dwNumBytesToRead, dwNumBytesSent = 0, dwNumBytesRead = 0; byOutputBuffer[0] = 0x8A; ftStatus = FT_Write(ftHandle, byOutputBuffer, 1, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); if (ftStatus != FT_OK) { printf("Error\r\n"); return FT_OTHER_ERROR; } else if (dwNumBytesToRead > 0) { printf("dwNumBytesToRead = %d:", dwNumBytesToRead); for ( int i = 0; i < dwNumBytesToRead; i++) printf (" %02Xh", byInputBuffer[i]); printf("\r\n"); return FT_INVALID_PARAMETER; } return FT_OK; 

Als Experiment senden wir anstelle des eigentlichen Befehls 0x8A den Wert 0xFE, der keinem Op-Code entspricht, die Konsolenausgabe:


 dwNumBytesToRead = 2: FAh FEh 

Der Prozessor hat zwei Bytes zurückgegeben, das fehlerhafte Befehlsbyte ist 0xFA und der Wert dieses fehlerhaften Befehls. Indem wir mehrere Befehle gleichzeitig senden, können wir nicht nur die Tatsache des Fehlers selbst verfolgen, sondern auch verstehen, in welchem ​​Team dieser Fehler aufgetreten ist.
Um in Zukunft nicht mit "magischen Zahlen" umzugehen, werden wir alle Op-Codes in Form von Konstanten formatieren und in einer separaten Header-Datei ablegen.
Um den Modus vollständig zu konfigurieren, müssen Sie die Richtung der E / A-Leitungen und ihren Standardwert angeben. Wenden wir uns dem Anschlussplan zu. Um einen bereits aufgeblähten Artikel nicht zu überladen, habe ich ein interessantes Fragment des Schemas gezeichnet:



Die Zeilen DCLK , DATA [0] , nCONFIG müssen als Ausgänge konfiguriert sein, die Zeilen nSTATUS , CONF_DONE als Eingänge. Anhand des Diagramms bestimmen wir, welche Anfangszustände die Linien haben sollen. Aus Gründen der Übersichtlichkeit ist die Pinbelegung der Schaltung in der Tabelle zusammengefasst:


FPGA-PinPin NamePinMPSSERichtungStandard
DCLKBDBUS038TCK / SKRaus0
DATEN [0]BDBUS139TDI / DORaus1
nKONFIGBDBUS240TDO / DIRaus1
nSTATUSBDBUS341TMS / CSIn1
CONF_DONEBDBUS443GPIOL0In1

Alle verwendeten Leitungen befinden sich im Low-Byte des MPSSE-Ports. Verwenden Sie den Op-Code 0x80, um den Wert festzulegen. Dieser Befehl setzt zwei Argumente voraus: Das erste Byte nach dem Operationscode ist der bitweise Wert und das zweite die Richtung (eins ist der Ausgangsport, Null ist der Eingangsport).
Im Rahmen des Kampfes gegen die "magische Zahl" werden alle Seriennummern und ihre Standardwerte als Konstanten formatiert:


Ports definieren
 #define PORT_DIRECTION (0x07) #define DCLK (0) #define DATA0 (1) #define N_CONFIG (2) #define N_STATUS (3) #define CONF_DONE (4) // initial states of the MPSSE interface #define DCLK_DEF (1) #define DATA0_DEF (0) #define N_CONFIG_DEF (1) #define N_STATUS_DEF (1) #define CONF_DONE_DEF (1) 

Es muss nur noch sichergestellt werden, dass die TDI-TDO-Schleife deaktiviert ist (kann zum Testen aktiviert werden) und in eine separate Funktion gestellt werden:


Auflisten der MPSSE_setup-Funktion
 static FT_STATUS MPSSE_setup () { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8], byInputBuffer[8]; FT_STATUS ftStatus; // Multple commands can be sent to the MPSSE with one FT_Write dwNumBytesToSend = 0; // Start with a fresh index byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_DIVIDER_5; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_ADAPTIVE_CLK; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_3PHASE_CLOCKING; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Reset output buffer pointer // Set TCK frequency // Command to set clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_TCK_DIVISION; // Set ValueL of clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_DATA_SPEED_DIV_L(DATA_SPEED); // Set 0xValueH of clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_DATA_SPEED_DIV_H(DATA_SPEED); ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Reset output buffer pointer // Set initial states of the MPSSE interface // - low byte, both pin directions and output values /* | FPGA pin | Pin Name | Pin | MPSSE | Dir | def | | --------- | -------- | --- | ------ | --- | --- | | DCLK | BDBUS0 | 38 | TCK/SK | Out | 0 | | DATA[0] | BDBUS1 | 39 | TDI/DO | Out | 1 | | nCONFIG | BDBUS2 | 40 | TDO/DI | Out | 1 | | nSTATUS | BDBUS3 | 41 | TMS/CS | In | 1 | | CONF_DONE | BDBUS4 | 43 | GPIOL0 | In | 1 | */ // Configure data bits low-byte of MPSSE port: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_LOWBYTE; // Initial state config above: byOutputBuffer[dwNumBytesToSend++] = (DCLK_DEF << DCLK) | (DATA0_DEF << DATA0) | (N_CONFIG_DEF << N_CONFIG) | (N_STATUS_DEF << N_STATUS) | (CONF_DONE_DEF << CONF_DONE); // Direction config above: byOutputBuffer[dwNumBytesToSend++] = PORT_DIRECTION; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Send off the low GPIO config commands dwNumBytesToSend = 0; // Reset output buffer pointer // Set initial states of the MPSSE interface // - high byte, all input, Initial State -- 0. // Send off the high GPIO config commands: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_HIGHBYTE; byOutputBuffer[dwNumBytesToSend++] = 0x00; byOutputBuffer[dwNumBytesToSend++] = 0x00; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Disable loopback: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_LOOP_TDI_TDO; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); if (ftStatus != FT_OK) { printf("Unknown error in initializing the MPSSE\r\n"); return FT_OTHER_ERROR; } else if (dwNumBytesToRead > 0) { printf("Error in initializing the MPSSE, bad code:\r\n"); for ( int i = 0; i < dwNumBytesToRead; i++) printf (" %02Xh", byInputBuffer[i]); printf("\r\n"); return FT_INVALID_PARAMETER; } return FT_OK; } 

Punkt 4. Wir implementieren das Ladeprotokoll


Alles scheint bereit für praktische Experimente. Überprüfen Sie zunächst, ob die Initialisierung korrekt durchgeführt wurde. Rufen MPSSE_open() im Hauptteil des Programms MPSSE_open() und MPSSE_setup() . Bevor Sie das Gerät schließen ( FT_Close ), setzen Sie ein leeres getchar() . Führen Sie das Programm aus und verwenden Sie das Oszilloskop, um sicherzustellen, dass alle PS-Leitungen auf die Standardpegel eingestellt sind. Wenn Sie den Wert dieser Ebenen bei der Initialisierung ändern (mit dem FPGA passiert nichts Schlimmes), stellen wir sicher, dass der MPSSE-Prozessor das gewünschte Ergebnis als gültig ausgibt - alles funktioniert ordnungsgemäß und Sie können mit der Datenübertragung fortfahren.
Das sequentielle Senden und Empfangen von Daten erfolgt im Befehlsmodus mit demselben Operationscode. Das erste Byte des Befehls ist ein Op-Code, der die Art der Operation bestimmt, gefolgt von der Länge der gesendeten oder empfangenen Sequenz und, falls es sich um eine Übertragung handelt, den tatsächlichen Daten. Der MPSSE-Prozessor kann Daten senden und empfangen, auch gleichzeitig. Die Übertragung kann entweder das niedrigstwertige Bit Forward (LSB) oder das höchstwertige (MSB) sein. Die Datenübertragung kann entweder an der Vorder- oder der Hinterflanke von Taktimpulsen erfolgen. Jede Kombination von Optionen hat ihren eigenen Op-Code, jedes Op-Code-Bit beschreibt den Betriebsmodus:


BitFunktion
0Front-Write-Synchronisation: 0 - positiv, 1 - negativ
11 - Arbeit mit Bytes, 0 - Arbeit mit Bits
2Vorderkante zum Lesen: 0 - positiv, 1 - negativ
3Übertragungsmodus: 1 - LSB, 0 - MSB zuerst
4TDI-Datenübertragung
5Lesen von Daten von einer TDO-Leitung
6TMS-Datenübertragung
7Muss 0 sein, sonst ist dies eine andere Gruppe von Befehlen

Bei der Konfiguration von FPGAs gemäß dem PS-Schema werden Daten im LSB-Modus an der Vorderkante übertragen.Es ist für uns bequemer, mit Bytes anstatt mit Bits zu arbeiten. In diesem Fall nimmt der Op-Code den Wert 0001_1000b oder 0x18 in hexadezimaler Darstellung an. Die Argumente des Befehls sind die Länge der übertragenen Sequenz (zwei Bytes, beginnend mit der niedrigstwertigen) und die Datensequenz selbst. Ein kleines Merkmal sollte berücksichtigt werden: Die Länge wird minus eins codiert. Das heißt, wenn wir ein Byte senden möchten, dann ist die Länge 0, wenn wir 65536 senden möchten, müssen wir die Länge von 65535 angeben. Ich denke, es ist klar, warum dies getan wird. Senden wir den Datenblock als Funktion aus MPSSE_send.


Auflisten der MPSSE_send-Funktion
 static BYTE byBuffer[65536 + 3]; static FT_STATUS MPSSE_send(BYTE * buff, DWORD dwBytesToWrite) { DWORD dwNumBytesToSend = 0, dwNumBytesSent, bytes; FT_STATUS ftStatus; // Output on rising clock, no input // MSB first, clock a number of bytes out byBuffer[dwNumBytesToSend++] = MPSSE_CMD_LSB_DATA_OUT_BYTES_POS_EDGE; // 0x18 bytes = dwBytesToWrite -1; byBuffer[dwNumBytesToSend++] = (bytes) & 0xFF; // Length L byBuffer[dwNumBytesToSend++] = (bytes >> 8) & 0xFF; // Length H memcpy(&byBuffer[dwNumBytesToSend], buff, dwBytesToWrite); dwNumBytesToSend += dwBytesToWrite; ftStatus = FT_Write(ftHandle, byBuffer, dwNumBytesToSend, &dwNumBytesSent); if (ftStatus != FT_OK ) { printf ("ERROR send data\r\n"); return ftStatus; } else if (dwNumBytesSent != dwNumBytesToSend) { printf ("ERROR send data, %d %d\r\n", dwNumBytesSent, dwNumBytesToSend); } return FT_OK; } 

— 65 , - , op-code . byBuffer , buff , , op-code . , , .
, "" , 25 , , , 1 ( , #define DATA_SPEED 1000000ul ). :


 BYTE byOutputBuffer[] = {0x02, 0x1B, 0xEE, 0x01, 0xFA}; MPSSE_send(byOutputBuffer, sizeof(byOutputBuffer)); 

( ):


DATA[0] , — DCLK . . , , .


, SPI ( ). , PS, . nCONFIG , nSTATUS , CONF_DONE . — , , — , .


MPSSE_get_lbyte , , .


MPSSE_get_lbyte
 static FT_STATUS MPSSE_get_lbyte(BYTE *lbyte) { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8]; FT_STATUS ftStatus; dwNumBytesToSend = 0; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_GET_DATA_BITS_LOWBYTE; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, lbyte, dwNumBytesToRead, &dwNumBytesRead); if ((ftStatus != FT_OK) & (dwNumBytesToRead != 1)) { printf("Error read Lbyte\r\n"); return FT_OTHER_ERROR; // Exit with error } return FT_OK; } 

, op-code , . , - , , . , . MPSSE_set_lbyte :


MPSSE_set_lbyte
 static FT_STATUS MPSSE_set_lbyte(BYTE lb, BYTE mask) { DWORD dwNumBytesToSend, dwNumBytesSent; BYTE byOutputBuffer[8], lbyte; FT_STATUS ftStatus; ftStatus = MPSSE_get_lbyte(&lbyte); if ( ftStatus != FT_OK) return ftStatus; // Set to zero the bits selected by the mask: lbyte &= ~mask; // Setting zero is not selected by the mask bits: lb &= mask; lbyte |= lb; dwNumBytesToSend = 0; // Set data bits low-byte of MPSSE port: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_LOWBYTE; byOutputBuffer[dwNumBytesToSend++] = lbyte; byOutputBuffer[dwNumBytesToSend++] = PORT_DIRECTION; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); if ((ftStatus != FT_OK) & (dwNumBytesSent != 1)) { printf("Error set Lbyte\r\n"); return FT_OTHER_ERROR; } return FT_OK; } 

, . : FTDI; MPSSE; rbf- , nCONFIG , N_STATUS ; rbf- ; , , CONF_DONE . , MPSSE FTDI . , nCONFIG "" , , , .


main
 int main(int argc, char *argv[]) { FT_STATUS ftStatus; BYTE lowByte; DWORD numDevs; // create the device information list if ( argv[1] == NULL) { printf ("NO file\r\n"); return -1; } frbf = fopen(argv[1],"rb"); if (frbf == NULL) { printf ("Error open rbf\r\n"); return -1; } ftStatus = FT_CreateDeviceInfoList(&numDevs); if ((numDevs == 0) || (ftStatus != FT_OK)) { printf("Error. FTDI devices not found in the system\r\n"); return -1; } ftStatus = MPSSE_open ("LESO7 B"); if (ftStatus != FT_OK) { printf("Error in MPSSE_open %d\n", ftStatus); EXIT(-1); } MPSSE_setup(); if (ftStatus != FT_OK) { printf("Error in MPSSE_setup %d\n", ftStatus); EXIT(-1); } printf ("nConfig -> 0\r\n"); MPSSE_set_lbyte(0, 1 << N_CONFIG); printf ("nConfig -> 1\r\n"); MPSSE_set_lbyte(1 << N_CONFIG, 1 << N_CONFIG); if (MPSSE_get_lbyte(&lowByte) != FT_OK) { EXIT(-1); } if (((lowByte >> N_STATUS) & 1) == 0) { printf("Error. FPGA is not responding\r\n"); EXIT(-1); } int i = 0; size_t readBytes = 0; // Send the configuration file: do { readBytes = fread(buff, 1, MPSSE_PCK_SEND_SIZE, frbf); if (MPSSE_send(buff, readBytes) != FT_OK) EXIT(-1); putchar('*'); if (!((++i)%16)) printf("\r\n"); } while (readBytes == MPSSE_PCK_SEND_SIZE); printf("\r\n"); memset(buff, 0x00, sizeof(buff)); MPSSE_send(buff, 1); //        ? printf("Load complete\r\n"); // wait CONF_DONE set // A low-to-high transition on the CONF_DONE pin indicates that the configuration is // complete and initialization of the device can begin. i = 0; do { if (MPSSE_get_lbyte(&lowByte) != FT_OK) { printf ("Error read CONF_DONE\r\n"); EXIT(-1); } if (i++ > TIMEOUT_CONF_DONE) { printf ("Error CONF_DONE\r\n"); EXIT(-1); } Sleep(2); } while (((lowByte >> CONF_DONE) & 1) == 0); printf("Configuration complete\r\n"); FT_Close(ftHandle); fclose(frbf); } 

Beispiel für das Starten eines Programms:


 pen "LESO7 B" OK nConfig -> 0 nConfig -> 1 ** Load complete Configuration complete 

rbf- . . 30 / .
, - JTAG.


Verwandte Materialien


  1. FTDI-MPSSE-Altera PS . .
  2. . . .
  3. Software Application Development D2XX Programmer's Guide . FTDI. API D2XX.
  4. FTDI MPSSE Basics. Application Note AN_135 . . FTDI MPSSE. .
  5. Command Processor for MPSSE and MCU Host Bus Emulation Modes. Application Note AN_108 . op-code. .
  6. D2XX Drivers . FTDI.

Source: https://habr.com/ru/post/de426131/


All Articles