Téléchargez la configuration sur FPGA via USB ou démontez FTDI MPSSE



Dans la vie de chaque tiroir, il arrive un moment oĂč vous voulez Ă©crire votre propre chargeur du fichier de configuration dans le FPGA. J'ai dĂ» participer Ă  l'Ă©laboration d'un stand de formation pour le dĂ©partement d'une universitĂ© technique. Le stand est conçu pour Ă©tudier le traitement numĂ©rique du signal, bien que cela ne soit pas particuliĂšrement important dans le cadre de cet article. Et la signification est que le FPGA (Altera Cyclone IV) est Ă  la base du stand, sur lequel les Ă©tudiants collectent toutes sortes de schĂ©mas DSP, tels que conçus par l'auteur du stand. Le support est connectĂ© Ă  l'ordinateur via USB. Vous devez tĂ©lĂ©charger le FPGA depuis l'ordinateur via USB.

Il a Ă©tĂ© dĂ©cidĂ© de se connecter Ă  un PC utilisant FTDI dans son incarnation Ă  deux canaux - FT2232H. Un canal sera utilisĂ© pour la configuration FPGA, l'autre peut ĂȘtre utilisĂ© pour l'Ă©change FIFO haute vitesse.


FTDI a une carte de dĂ©bogage MORPH-IC-II , oĂč le FPGA Cyclone II est flashĂ© via USB. Concepts dans le domaine public. Le code source du chargeur de dĂ©marrage est partiellement ouvert: le chargeur de dĂ©marrage lui-mĂȘme est disponible, cependant, toute la logique de travail avec FTDI est dĂ©placĂ©e vers une bibliothĂšque privĂ©e et ne peut pas ĂȘtre modifiĂ©e. En vĂ©ritĂ©, j'avais initialement prĂ©vu d'utiliser ce chargeur de dĂ©marrage dans mon projet, ou, dans des cas extrĂȘmes, de crĂ©er mon shell en fonction de leur DLL. Le firmware est chargĂ© dans le FPGA en mode sĂ©rie passif (passif sĂ©rie - PS), FTDI fonctionne en mode MPSSE. Sur la maquette, les performances de la solution MORPH-IC-II ont Ă©tĂ© pleinement confirmĂ©es, mais le problĂšme, comme cela arrive souvent, ne vient pas d'oĂč. Il s'est avĂ©rĂ© que pendant le fonctionnement de la dll MORPH-IC-II, tous les appareils FTDI connectĂ©s sont bloquĂ©s, et dans le cadre du complexe de formation, il y a deux autres appareils avec des convertisseurs similaires: un gĂ©nĂ©rateur et un analyseur de signal. Un travail simultanĂ© avec eux n'est pas possible. Merde bizarre et ennuyeux.


Un cas similaire a été implémenté par les gars du rover Mars: le programmeur USB JTAG MBFTDI . FTDI y est également utilisé en mode MPSSE, mais contrairement au MORPH-IC-II, les opérations FPGA sont effectuées en mode JTAG. Les sources sont disponibles gratuitement, mais je n'ai pas trouvé d'indication claire sur leur statut (licence). Par conséquent, pour les utiliser dans un projet commercial, ma main ne s'est pas levée.


Je corrigerai une telle erreur, tout ce qui sera présenté dans le cadre de cet article est posté dans un référentiel ouvert sous licence BSD.


Télécharger le fichier de configuration sur la puce FPGA


Tout d'abord, vous devriez vous occuper du mode de démarrage FPGA. Pour ceux qui commencent tout juste à se familiariser avec le sujet, je ferai une petite excursion. Bien que des FPGA Altera (Intel) de la famille Cyclone IV E soient installés sur ma carte, les méthodes de chargement sont similaires pour l'ensemble du groupe FPGA Cyclone, et on soupçonne que sous une forme ou une autre, elles conviennent à de nombreuses autres familles.


Ce type de FPGA utilise une SRAM volatile pour stocker les donnĂ©es de configuration. Ces donnĂ©es de configuration dĂ©terminent la fonctionnalitĂ© du pĂ©riphĂ©rique rĂ©sultant. Dans le jargon professionnel, ces donnĂ©es sont souvent appelĂ©es «firmware». Ainsi, le firmware est stockĂ© dans une RAM spĂ©ciale et chaque fois que l'appareil est allumĂ©, il doit ĂȘtre chargĂ© dans la puce FPGA. Il existe plusieurs façons (schĂ©mas de configuration) par lesquelles le firmware peut ĂȘtre chargĂ© dans SRAM (la liste est pertinente pour Cyclone IV E):


  1. Série active (AS).
  2. ParallĂšle actif (AP)
  3. Série passive (PS)
  4. ParallĂšle passif rapide (FPP).
  5. JTAG.

Le choix d'un mode de démarrage spécifique s'effectue à l'aide de broches FPGA externes (groupe MSEL). Le mode JTAG est toujours disponible. Le mode actif implique que lorsque l'alimentation est appliquée, le FPGA lit indépendamment les données de la mémoire externe (série ou parallÚle). En mode passif, le FPGA attend qu'un support externe lui transfÚre de maniÚre proactive les données de configuration. Ces schémas s'intÚgrent bien dans le concept de maßtre (maßtre) - esclave (esclave). Dans les modes actifs, le FPGA agit comme maßtre et dans les modes passifs comme esclave.


Dans ce problĂšme, ce n'est pas le FPGA, mais l'utilisateur doit dĂ©cider quand le firmware doit ĂȘtre mis Ă  jour, donc le mode de dĂ©marrage doit ĂȘtre passif. Et pour sauver les jambes de la puce, nous choisissons une interface sĂ©rie. Le mode sĂ©rie passif (PS) et JTAG conviennent ici. La logique du JTAG est un peu plus compliquĂ©e, alors concentrons-nous sur la premiĂšre option.
La figure ci-dessous montre le schéma de connexion du FPGA à un contrÎleur externe pour le téléchargement en mode PS.



Pour dĂ©marrer la configuration, le maĂźtre externe doit gĂ©nĂ©rer une transition basse Ă  haute sur la ligne nCONFIG . DĂšs que le FPGA est prĂȘt Ă  recevoir des donnĂ©es, il formera un niveau Ă©levĂ© sur la ligne nSTATUS . AprĂšs cela, le maĂźtre peut commencer Ă  transmettre des donnĂ©es sur la ligne DATA [0] et les impulsions d'horloge correspondantes sur la ligne DCLK . Les donnĂ©es doivent ĂȘtre transmises au pĂ©riphĂ©rique cible jusqu'Ă  ce qu'un niveau Ă©levĂ© soit Ă©tabli sur la ligne CONF_DONE (ou que les donnĂ©es ne se terminent pas) et que le FPGA passe Ă  l'Ă©tat d'initialisation. Il convient de noter qu'aprĂšs que CONF_DONE est dĂ©fini sur un, deux impulsions d'horloge supplĂ©mentaires doivent ĂȘtre appliquĂ©es pour que l'initialisation FPGA commence.


Les donnĂ©es sont transmises par le bit le moins significatif ( LSB ) vers l'avant, c'est-Ă -dire que si le fichier de configuration contient la sĂ©quence 02 1B EE 01 FA (prenez l'exemple tel qu'il est tirĂ© du manuel), la sĂ©quence doit ĂȘtre formĂ©e sur la ligne de donnĂ©es:


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

Ainsi, seules cinq lignes sont utilisées: les lignes DATA [0] et DCLK pour la transmission série, les lignes nCONFIG , nSTATUS , CONF_DONE pour le contrÎle.
À la base, le mode PS n'est rien de plus que SPI avec une manipulation supplĂ©mentaire des drapeaux.
Le taux de transfert de donnĂ©es doit ĂȘtre infĂ©rieur Ă  la frĂ©quence maximale indiquĂ©e dans la documentation; pour la sĂ©rie Cyclone IV E utilisĂ©e dans le projet, elle est de 66 MHz.


La fréquence de transmission minimale n'existe pas, il est théoriquement possible de suspendre la configuration pour une durée indéterminée. Cela donne d'excellentes opportunités de débogage étape par étape avec la participation d'un oscilloscope, que nous utiliserons certainement.


La figure ci-dessous montre le chronogramme de l'interface avec les temporisations les plus significatives.



Sly Beast MPSSE


ConsidĂ©rez le fonctionnement de FTDI en mode MPSSE. Le mode MPSSE (Multi-Protocol Synchronous Serial Engine), Ă  ​​mon avis, est une tentative plus ou moins rĂ©ussie de crĂ©er un certain concepteur d'interface sĂ©rie, pour donner au dĂ©veloppeur la possibilitĂ© d'implĂ©menter des protocoles de transfert de donnĂ©es rĂ©pandus, tels que SPI, I2C, JTAG, 1-wire et bien d'autres d'autres basĂ©s sur eux.


Actuellement, le mode est disponible pour les microcircuits: FT232H, FT2232D, FT2232H, FT4232H. Dans mon projet, j'utilise le FT2232H, nous en parlons donc plus largement. Pour le mode MPSSE, 16 segments sont allouĂ©s, divisĂ©s en deux octets: le L infĂ©rieur et le H. le plus Ă©levĂ©. Chaque octet peut ĂȘtre lu ou dĂ©fini. Les quatre branches infĂ©rieures de l'octet L ont des fonctions spĂ©ciales - la transmission de donnĂ©es en sĂ©rie peut se produire Ă  travers elles. Chaque branche peut ĂȘtre configurĂ©e comme entrĂ©e ou sortie, une valeur par dĂ©faut peut ĂȘtre dĂ©finie pour la sortie. Pour la transmission sĂ©quentielle, l'ordre des bits ( MSB / LSB ), la longueur du mot transmis, la frĂ©quence des impulsions d'horloge, la synchronisation avant - avant (Rising) ou arriĂšre (Falling), vous pouvez choisir de transmettre uniquement des impulsions d'horloge sans donnĂ©es, ou sĂ©lectionner une horloge triphasĂ©e (pertinent pour I2C) et bien plus encore.


Passez sans problÚme à la programmation. Il existe deux façons alternatives d'interaction logicielle avec les puces FTDI: la premiÚre, appelons-la classique, dans ce cas, lorsqu'elle est connectée à un port USB, la puce du systÚme est définie comme un port série virtuel (COM), le systÚme d'exploitation utilise le pilote VCP (Virtual COM Port). Toutes les autres programmations ne diffÚrent pas de la programmation du port COM classique: ouvert - transmis / compté - fermé. Et cela est vrai pour divers systÚmes d'exploitation, y compris Linux et Mac OS. Cependant, avec cette approche, il ne sera pas possible de réaliser toutes les fonctionnalités du contrÎleur FTDI - la puce fonctionnera comme un adaptateur USB-UART. La deuxiÚme méthode est fournie par la bibliothÚque propriétaire FTD2XX, cette interface fournit des fonctions spéciales qui ne sont pas disponibles dans l'API de port COM standard, en particulier, il est possible de configurer et d'utiliser des modes de fonctionnement spéciaux, tels que MPSSE, 245 FIFO, Bit-bang. La bibliothÚque de l'API FTD2XX est bien documentée par le Guide du programmeur du développement d'applications logicielles D2XX , largement connu depuis longtemps dans les cercles étroits. Et oui, FTD2XX est également disponible pour différents systÚmes d'exploitation.


Les dĂ©veloppeurs de FTDI ont Ă©tĂ© confrontĂ©s Ă  la tĂąche d'intĂ©grer la relativement nouvelle MPSSE dans le modĂšle d'interaction logiciel D2XX existant. Et ils ont rĂ©ussi, pour travailler en mode MPSSE le mĂȘme ensemble de fonctions est utilisĂ© que pour les autres modes "classiques", la mĂȘme bibliothĂšque FTD2XX API est utilisĂ©e.


En bref, l'algorithme pour fonctionner en mode MPSSE peut ĂȘtre dĂ©crit comme suit:


  1. Recherchez l'appareil dans le systĂšme et ouvrez-le.
  2. Initialisez la puce et mettez-la en mode MPSSE.
  3. Définissez le mode de fonctionnement de MPSEE.
  4. Travail direct avec les données: transmettre, recevoir, gérer GPIO - nous implémentons le protocole d'échange cible.
  5. Fermez l'appareil.

Écrire un chargeur de dĂ©marrage


Passons à la partie pratique. Dans mes expériences, j'utiliserai la version Eclipse d'Oxygen.3a Release (4.7.3a) comme IDE, et mingw32-gcc (6.3.0) comme compilateur. SystÚme d'exploitation Win7.


Sur le site Web FTDI, nous tĂ©lĂ©chargeons la derniĂšre version actuelle du pilote pour notre systĂšme d'exploitation. Dans l'archive, nous trouvons le fichier d'en-tĂȘte ftd2xx.h avec une description de toutes les fonctions de l'API. L'API elle-mĂȘme est implĂ©mentĂ©e en tant que ftd2xx.dll, mais nous laisserons l'importation dynamique pour plus tard et utiliserons le lien statique: nous avons besoin du fichier de bibliothĂšque ftd2xx.lib. Pour mon cas, ftd2xx.lib se trouve dans le rĂ©pertoire i386.


Dans Eclipse, crĂ©ez un nouveau projet C. La crĂ©ation d'un makefile peut ĂȘtre approuvĂ©e par un IDE. Dans les paramĂštres de l'Ă©diteur de liens, spĂ©cifiez le chemin et le nom de la bibliothĂšque ftd2xx (j'ai transfĂ©rĂ© les fichiers requis dans le rĂ©pertoire du projet dans le dossier ftdi). Je ne me concentrerai pas sur les fonctionnalitĂ©s de configuration d'un projet pour Eclipse, car je soupçonne que la plupart de Win utilise d'autres environnements et compilateurs pour la programmation sous Win.


Premier point. Trouver un appareil et l'ouvrir


L'API FTD2XX vous permet d'ouvrir la puce en utilisant l'une ou l'autre des informations connues Ă  son sujet. Il peut s'agir de son numĂ©ro de sĂ©rie dans le systĂšme: la premiĂšre puce FTDI connectĂ©e prendra le numĂ©ro 0, la suivante 1 et ainsi de suite. Le nombre dans le systĂšme est dĂ©terminĂ© par l'ordre dans lequel les microcircuits sont connectĂ©s, pour le moins, ce n'est pas toujours pratique. Pour ouvrir la puce par numĂ©ro, la fonction FT_Open est FT_Open . Vous pouvez ouvrir la puce par son numĂ©ro de sĂ©rie ( FT_OPEN_BY_SERIAL_NUMBER ), sa description ( FT_OPEN_BY_DESCRIPTION ) ou par emplacement ( FT_OPEN_BY_LOCATION ), pour cela, la fonction FT_OpenEx est FT_OpenEx . Le numĂ©ro de sĂ©rie et la description sont stockĂ©s dans la mĂ©moire interne de la puce et peuvent y ĂȘtre enregistrĂ©s lors de la fabrication de l'appareil avec FTDI installĂ©. En rĂšgle gĂ©nĂ©rale, la description caractĂ©rise le type d'appareil ou de famille et le numĂ©ro de sĂ©rie doit ĂȘtre unique pour chaque produit. Par consĂ©quent, le moyen le plus pratique d'identifier les pĂ©riphĂ©riques pris en charge par le programme en cours de dĂ©veloppement est sa description. Nous ouvrirons la puce FTDI selon la description (descripteur). En fait, si nous connaissions initialement la ligne de descripteur de puce, nous n'avons pas besoin de rechercher le pĂ©riphĂ©rique dans le systĂšme, cependant, Ă  titre expĂ©rimental, nous afficherons tous les pĂ©riphĂ©riques connectĂ©s Ă  l'ordinateur avec FTDI. En utilisant la fonction FT_CreateDeviceInfoList , FT_CreateDeviceInfoList crĂ©erons une liste dĂ©taillĂ©e des puces connectĂ©es, et en utilisant la fonction FT_GetDeviceInfoList , FT_GetDeviceInfoList considĂ©rerons.


Liste des appareils connectés. Annonce:
 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); } 

Bienvenue mon 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 

Trois appareils avec puces FTDI sont connectés à mon PC: FT232RL (type 0x5), FT2232H (type 0x6) et FT232H (tepe 0x8). La puce FT2232H du systÚme était affichée sous la forme de deux périphériques indépendants (Dev 1 et Dev 2). L'interface FPGA PS est connectée à Dev 2, son descripteur est «LESO7 B». Ouvrez-le:


 //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; } 

La plupart des fonctions API renvoient le statut de leur appel de type FT_STATUS , toutes les valeurs possibles sont dĂ©crites comme enum dans le fichier d'en-tĂȘte. Il y en a beaucoup, mais il suffit de savoir que la valeur FT_OK est l'absence d'erreur, toutes les autres valeurs sont des codes d'erreur. Un bon style de programmation consiste Ă  vĂ©rifier la valeur d'Ă©tat aprĂšs chaque appel Ă  la fonction API.


Si le pĂ©riphĂ©rique a Ă©tĂ© ouvert avec succĂšs, alors dans la variable ftHandle apparaĂźt une valeur autre que zĂ©ro, un descripteur de fichier Ă©quivalent, qui est utilisĂ© lorsque vous travaillez avec des fichiers. La poignĂ©e rĂ©sultante Ă©tablit une connexion avec l'interface matĂ©rielle et doit ĂȘtre utilisĂ©e lors de l'appel de toutes les fonctions de bibliothĂšque qui nĂ©cessitent l'accĂšs Ă  la puce.
Afin de confirmer dans la pratique l'opérabilité du systÚme pour l'étape actuelle, nous devons passer immédiatement à l'étape cinq de notre algorithme.


AprĂšs avoir fini de travailler avec la puce, vous devez la fermer. Pour ce faire, utilisez la fonction FT_Close :


 FT_Close(ftHandle); 

Point 2. Initialisez la puce et allumez le MPSSE


Le paramÚtre est typique de la plupart des modes et est bien décrit dans la documentation de base AN_135 FTDI MPSSE .


  1. Nous effectuons une réinitialisation (rezet) de la puce. Fonction FT_ResetDevice .
  2. Dans le cas oĂč il y a des ordures dans le tampon de rĂ©ception, nous les effaçons. Fonction FT_Purge .
  3. Ajustez la taille des tampons pour la lecture et l'écriture. Fonction FT_SetUSBParameters .
  4. Désactivez la parité. FT_SetChars .
  5. Nous fixons des délais pour la lecture et l'écriture. Par défaut, les délais d'attente sont désactivés, activez le délai de transmission. FT_SetTimeouts .
  6. Nous configurons le temps d'attente pour l'envoi d'un paquet de la puce à l'hÎte. Par défaut, 16 ms, accélérez à 1 ms. FT_SetLatencyTimer .
  7. Activez le contrĂŽle de flux pour synchroniser les demandes entrantes. FT_SetFlowControl .
  8. Tout est prĂȘt pour activer le mode MPSSE. RĂ©initialisez le contrĂŽleur MPSSE. Nous utilisons la fonction FT_SetBitMode , dĂ©finissons le mode sur 0 (mode = 0, masque = 0).
  9. Activez le mode MPSSE. Fonction FT_SetBitMode - mode = 2, masque = 0.

Nous unissons et configurons la puce dans la fonction MPSSE_open , en paramÚtre nous passons une ligne avec la poignée de l'appareil à ouvrir:


Liste 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; } 

Point 3. Configurer le mode de fonctionnement MPSEE


En fait, Ă  ce stade, le processeur MPSSE est activĂ© et prĂȘt Ă  recevoir des commandes. Les commandes sont des sĂ©quences d'octets, dont le premier est "op-code", suivies des paramĂštres de commande. La commande peut ne pas avoir de paramĂštres et se composer d'un "code d'opĂ©ration". Les commandes sont transmises Ă  l'aide de la fonction FT_Write , une rĂ©ponse du processeur MPSSE peut ĂȘtre obtenue Ă  l'aide de la fonction FT_Read .


AprĂšs chaque envoi de commande, il est utile de lire la rĂ©ponse du processeur, car en cas de commande incorrecte, la rĂ©ponse peut contenir un message d'erreur - le caractĂšre 0xFA. Le mĂ©canisme "mauvaise commande - rĂ©ponse 0xFA" peut ĂȘtre utilisĂ© pour synchroniser le programme d'application avec le processeur MPSSE. Si tout va bien, la puce renverra le caractĂšre 0xFA sur une commande dĂ©libĂ©rĂ©ment erronĂ©e. Les codes d'opĂ©ration sont dĂ©crits dans Processeur de commande pour le mode d'Ă©mulation de bus hĂŽte MPSSE et MCU .
La configuration de MPSSE revient à définir le débit de données, la direction et les états initiaux des lignes d'E / S.
Pensez Ă  dĂ©finir le dĂ©bit de donnĂ©es du processeur MPSSE. Les paramĂštres des puces prenant en charge uniquement le mode pleine vitesse (FT2232 D ) et des puces Ă  haute vitesse (FT2232 H , FT232H, FT4232H) sont quelque peu diffĂ©rents. Le FT2232D hĂ©ritĂ© utilise une horloge de 12 MHz, tandis que les modĂšles modernes utilisent 60 MHz. D'oĂč la formule de calcul du taux de transfert de donnĂ©es:


DataSpeed​​= fracfcore(1+diviseur) cdot2


oĂč f core est la frĂ©quence du coeur FTDI, Divisor est un diviseur Ă  deux octets, qui, en fait, rĂšgle la frĂ©quence d'horloge des donnĂ©es.
Par conséquent, si le diviseur est égal à zéro, le taux de transfert de données maximal sera de 30 Mbps et le taux de transfert de données minimal sera au niveau d'un diviseur 65535 - 458 bit / s.
Nous confierons le calcul du diviseur au préprocesseur. La macro renvoie le diviseur:


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

Et ces deux macros renvoient respectivement les octets haut et bas du diviseur:


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

De plus, il convient de noter que dans les puces modernes pour la compatibilitĂ© avec l'ancien FT2232D, il existe un diviseur supplĂ©mentaire de 5, qui transforme 60 MHz en 12 MHz. Ce sĂ©parateur est activĂ© par dĂ©faut, dans notre cas il doit ĂȘtre dĂ©sactivĂ©.
On retrouve l'op-code correspondant (0x8A) et la commande casque au processeur:


Liste de soumission d'équipe
 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; 

À titre expĂ©rimental, au lieu de la commande rĂ©elle 0x8A, nous enverrons la valeur 0xFE, qui ne correspond Ă  aucun op-code, la sortie de la console:


 dwNumBytesToRead = 2: FAh FEh 

Le processeur a renvoyĂ© deux octets, le mauvais octet de commande est 0xFA et la valeur de cette mauvaise commande. Ainsi, en envoyant plusieurs commandes Ă  la fois, nous pouvons non seulement suivre le fait de l'erreur elle-mĂȘme, mais Ă©galement comprendre sur quelle Ă©quipe cette erreur s'est produite.
Afin de ne plus traiter les "nombres magiques" Ă  l'avenir, nous formaterons tous les codes opĂ©rationnels sous forme de constantes et les placerons dans un fichier d'en-tĂȘte sĂ©parĂ©.
Pour configurer complÚtement le mode, vous devez spécifier la direction des lignes d'E / S et leur valeur par défaut. Passons maintenant au schéma de connexion. Afin de ne pas encombrer un article déjà gonflé, j'ai dessiné un fragment intéressant du schéma:



Les lignes DCLK , DATA [0] , nCONFIG doivent ĂȘtre configurĂ©es comme sorties, les lignes nSTATUS , CONF_DONE comme entrĂ©es. En utilisant le diagramme, nous dĂ©terminons quels Ă©tats initiaux les lignes devraient avoir. Pour plus de clartĂ©, le brochage du circuit est rĂ©sumĂ© dans le tableau:


Broche FPGANom de brochePinMPSSEDirectiondéfaut
DCLKBDBUS038TCK / SKOut0
DONNÉES [0]BDBUS139TDI / DOOut1
nCONFIGBDBUS240TDO / DIOut1
nSTATUSBDBUS341TMS / CSDans1
CONF_DONEBDBUS443GPIOL0Dans1

Toutes les lignes utilisées se trouvent sur l'octet bas du port MPSSE. Pour définir la valeur, utilisez le code op 0x80. Cette commande suppose deux arguments: le premier octet suivant le code op est la valeur bit par bit, et le second est la direction (un est le port de sortie, zéro est le port d'entrée).
Dans le cadre de la lutte contre le "numéro magique", tous les numéros de ligne série et leurs valeurs par défaut seront formatés sous forme de constantes:


Définir les ports
 #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) 

Il ne reste plus qu'Ă  s'assurer que la boucle TDI - TDO est dĂ©sactivĂ©e (peut ĂȘtre activĂ©e pour les tests) et la mettre dans une fonction distincte:


Liste de la fonction MPSSE_setup
 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; } 

Point 4. Nous implémentons le protocole de chargement


Tout semble prĂȘt pour des expĂ©riences pratiques. Tout d'abord, vĂ©rifiez que l'initialisation est effectuĂ©e correctement, dans le corps principal du programme, appelez MPSSE_open() et MPSSE_setup() , et avant de fermer le pĂ©riphĂ©rique ( FT_Close ), nous mettons un getchar() vide. ExĂ©cutez le programme et utilisez l'oscilloscope pour vous assurer que toutes les lignes PS sont dĂ©finies aux niveaux par dĂ©faut. En modifiant la valeur de ces niveaux lors de l'initialisation (rien de mauvais ne se produira avec le FPGA), nous nous assurons que le processeur MPSSE donne le rĂ©sultat souhaitĂ© comme valide - tout fonctionne correctement et vous pouvez procĂ©der au transfert de donnĂ©es.
L'envoi et la rĂ©ception sĂ©quentiels de donnĂ©es sont effectuĂ©s en mode commande en utilisant le mĂȘme code d'opĂ©ration. Le premier octet de la commande est l'op-code, qui dĂ©termine le type d'opĂ©ration, suivi de la longueur de la sĂ©quence transmise ou reçue et, s'il s'agit d'une transmission, des donnĂ©es rĂ©elles. Le processeur MPSSE peut envoyer et recevoir des donnĂ©es, Ă©galement en mĂȘme temps. La transmission peut ĂȘtre soit le bit le moins significatif en avant (LSB), soit le plus significatif (MSB). La transmission de donnĂ©es peut se produire sur les fronts avant ou arriĂšre des impulsions d'horloge. Chaque combinaison d'options a son propre code d'opĂ©ration, chaque bit de code d'opĂ©ration dĂ©crit le mode de fonctionnement:


BitFonction
0Synchronisation en écriture avant: 0 - positif, 1 - négatif
11 - travailler avec des octets, 0 - travailler avec des bits
2Bord avant pour la lecture: 0 - positif, 1 - négatif
3Mode de transmission: 1 - LSB, 0 - MSB en premier
4Transmission de données TDI
5Lecture des données d'une ligne TDO
6Transmission de données TMS
7Doit ĂȘtre 0, sinon c'est un autre groupe de commandes

Lors de la configuration des FPGA selon le schĂ©ma PS, les donnĂ©es sont transmises sur le front montant en mode LSB.Il est plus pratique pour nous d’utiliser des octets plutĂŽt que des bits, auquel cas le code d’opĂ©ration prendra la valeur 0001_1000b ou 0x18 en reprĂ©sentation hexadĂ©cimale. Les arguments de la commande seront la longueur de la sĂ©quence transmise (deux octets, en commençant par le moins significatif) et la sĂ©quence de donnĂ©es elle-mĂȘme. Une petite caractĂ©ristique doit ĂȘtre prise en compte: la longueur est codĂ©e moins un. Autrement dit, si nous voulons envoyer un octet, alors la longueur sera 0, si nous voulons envoyer 65536, alors nous devons spĂ©cifier la longueur de 65535. Je pense qu'il est clair pourquoi cela est fait. Envoyons le bloc de donnĂ©es en tant que fonction MPSSE_send.


Liste de la fonction MPSSE_send
 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); } 

Exemple de démarrage d'un programme:


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

rbf- . . 30 / .
, - JTAG.



  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/fr426131/


All Articles