HowTo FT232H

Installer le driver et les librairies C de chez FTDI sur Linux

  1. Télécharger le driver D2XX Linux x64 sur le site de FTDI link

  2. Suivre les indications du fichier README.txt présent dans l’archive

    Comme mentionné dans le README.txt, afin de rendre possible l’utilisation du driver D2XX, il faut veiller à désactiver le module ftdi_sio qui est chargé automatiquement lorsqu’on connecte un FT232H à la machine.

    Une des méthodes consiste à décharger les modules ftdi_sio et usbserial avec la commande rmmod. Cependant, une méthode plus élégante est présentée dans Résolution automatique du conflit avec le driver ftdi_sio

  3. S’assurer que le driver fonctionne en compilant et exécutant l’exemple situé dans /release/examples/Simple/

    Le programme doit être exécuté avec sudo

  4. Télécharger la librairie MPSSE link

  5. Suivre le indications du fichier README.txt

Accès au FT232H depuis l’espace utilisateur (i.e. sans sudo) & résolution automatique du conflit avec le driver ftdi_sio

Ces 2 fonctionnalités peuvent être obtenues à l’aide de règles udev.

Une règle udev est un ensemble d’instructions qui permet au système Linux de gérer automatiquement les périphériques connectés (ex. : définir les permissions, exécuter des scripts lors de la (dé)connexion d’appareils…​).

Les règles udev sont stockées dans des fichiers avec l’extension .rules, principalement dans trois répertoires :

  1. /etc/udev/rules.d/ (règles définies par l’utilisateur)

  2. /run/udev/rules.d/ (règles volatiles)

  3. /lib/udev/rules.d/ (règles fournies par la distribution)

Accès au FT232H depuis l’espace utilisateur

La règle va consister à attribuer les droits rw au groupe plugdev sur le périphérique associé au FT232H.

Le groupe plugdev est un groupe système spécial utilisé dans certaines distributions Linux, notamment Ubuntu et Debian, pour gérer les permissions d’accès aux périphériques amovibles

Procédure pour créer et appliquer la règle :

  1. Créer un fichier /etc/udev/rules.d/10-ft232h.rules avec le contenu suivant :

    /etc/udev/rules.d/10-ft232h.rules
    SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6014", GROUP="plugdev", MODE="0660"

    La règle cible le périphérique usb dont les PID/VID sont 0x6014/0x0403 pour lui appliquer les droits 0660 pour le groupe plugdev

  2. Appliquer la règle :

    sudo udevadm control --reload-rules && sudo udevadm trigger
  3. Vérifier que l’utilisateur est bien dans le groupe plugdev

    $ groups
    jdoe adm cdrom sudo dip plugdev lpadmin sambashare
  4. Décharger les modules ftdi_sio et usbserial

    sudo rmmod ftdi_sio & sudo rmmod usbserial
  5. Exécuter — sans sudo — le programme de test inclus dans l’archive de la librairie libmpsse (→ LibMPSSE_1.0.5/Linux/release/test/test.c) pour vérifier la bonne application de la règle udev :

    $ ./test (1)
    
    Version Check
    libmpsse: 00010005
    libftd2xx: 00010427
    
    Test case 1 - I2C_GetNumChannels
                    I2C_GetNumChannels returned 0; channels=1
    
    Test case 2 - I2C_GetChannelInfo
                    I2C_GetChannelInfo returned 0 for channel =0
                    Flags=0x2
                    Type=0x8
                    ID=0x4036014
                    LocId=0x205
                    SerialNumber=FT9H67OF
                    Description=USB <-> Serial Converter
                    ftHandle=(nil) (should be zero)
    [...]
    1 Ce programme de test met en œuvre l’interface I2C du FT232H et non l’interface SPI utilisée dans le projet mais l’important ici est de vérifier que le programme fonctionne sans sudo.

Résolution automatique du conflit avec le driver ftdi_sio


🕮 Sources :


Comme indiqué plus haut, le fait de brancher le FT232H va automatiquement provoquer le chargement des modules ftdi_sio et usbserial.

Les modules ftdi_sio et usbserial vont faire en sorte de considérer le FT232H comme un adaptateur USB vers liaison série. Or, le FT232H est un adaptateur USB plus complet qui permet une interface avec des GPIOS, un bus I2C et un bus SPI.

De façon à pouvoir utiliser le driver D2XX de FTDI qui permet l’accès à ces fonctionnalités, il faut empêcher l’utilisation du driver ftdi_sio avec le FT232H.

Ceci peut se faire en déchargeant systématiquement les modules avec la commande sudo rmmod ftdi_sio & sudo rmmod usbserial mais cela empêche alors l’utilisation d’éventuels autres adaptateurs USB/série FTDI branchés au système.

Une solution plus élégante consiste à créer une règle udev qui permettra de “casser” l’association entre le FT232H et le driver ftdi_sio après son branchement sur le système.

Procédure pour créer et appliquer la règle :

  1. Débrancher et rebrancher le FT232H. Ceci provoquera non seulement le chargement automatique des modules ftdi_sio et usbserial mais également l’association de ces drivers au FT232H.
    Ceci peut se vérifier avec la commande suivante :

    $ lsusb -tv
    /:  Bus 001.Port 001: Dev 001, Class=root_hub, Driver=ohci-pci/12p, 12M
        ID 1d6b:0001 Linux Foundation 1.1 root hub
        | Port 001: Dev 002, If 0, Class=Human Interface Device, Driver=usbhid, 12M
            ID 80ee:0021 VirtualBox USB Tablet
    /:  Bus 002.Port 001: Dev 001, Class=root_hub, Driver=ehci-pci/12p, 480M
        ID 1d6b:0002 Linux Foundation 2.0 root hub
        | Port 001: Dev 011, If 0, Class=Vendor Specific Class, Driver=ftdi_sio, 480M (1)
            ID 0403:6014 Future Technology Devices International, Ltd FT232H Single HS USB-UART/FIFO IC
    1 le driver ftdi_sio a été associé au FT232H
  2. Ajouter au fichier /etc/udev/rules.d/10-ft232h.rules créé au paragraphe précédent la règle suivante :

    /etc/udev/rules.d/10-ft232h.rules
    # [...]
    SUBSYSTEM=="usb", DRIVER=="ftdi_sio", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", RUN+="/bin/sh -c 'echo $kernel > /sys/bus/usb/drivers/ftdi_sio/unbind"
  3. Appliquer la règle :

    sudo udevadm control --reload-rules && sudo udevadm trigger
  4. Débrancher et rebrancher le FT232H

  5. Vérifier que le driver ftdi_sio n’est plus associé au FT232H

    ~$ lsusb -tv
    /:  Bus 001.Port 001: Dev 001, Class=root_hub, Driver=ohci-pci/12p, 12M
        ID 1d6b:0001 Linux Foundation 1.1 root hub
        | Port 001: Dev 002, If 0, Class=Human Interface Device, Driver=usbhid, 12M
            ID 80ee:0021 VirtualBox USB Tablet
    /:  Bus 002.Port 001: Dev 001, Class=root_hub, Driver=ehci-pci/12p, 480M
        ID 1d6b:0002 Linux Foundation 2.0 root hub
        | Port 001: Dev 010, If 0, Class=Vendor Specific Class, Driver=[none], 480M (1)
            ID 0403:6014 Future Technology Devices International, Ltd FT232H Single HS USB-UART/FIFO IC
    1 le driver ftdi_sio n’est plus associé au FT232H

Mise en œuvre avec un rhéostat numérique MCP4142

Ci-dessous un exemple de mise en œuvre du module FT232H pour dialoguer avec un rhéostat numérique Microchip réf. MCP4142 link.

Schéma :

ft232h mcp4142 bb

Programme :

mcp4142-cli.c
/**
 * Programme de test qui met en oeuvre la librairie libmpsse et le driver 
 * D2XX de chez FTDI pour piloter un rheostat numérique de chez 
 * Microship (MCP4142-10k) à travers un FT232H.
 * 
 * Le programme fixe d’abord la valeur du rheostat à sa valeur médiane
 * (0x40 : ≈5 kΩ) en appelant la fonction 'Write Data' puis
 * l’incrémente toutes les secondes jusqu’à sa valeur maximale (0x80)
 * avec la commande 'Increment Wiper'. A chaque incrément, on lit la valeur
 * effective du rheostat avec la commande 'Read Data'.
 * Dès que la valeur atteint la valeur max. du rhéostat (0x80 : ≈10kΩ),
 * le programme quite.
 * Pendant l’incrémentation, l’ohmètre doit vérifier l’augmentation de la résistance
 * de ≈5kΩ à ≈10kΩ entre les broches 5 et 6.
 * 
 * Ce programme met en oeuvre les fonctions suivantes de la libmpsse :
 * - Init_libMPSSE()
 * - SPI_GetNumChannels()
 * - SPI_GetChannelInfo()
 * - SPI_OpenChannel()
 * - SPI_InitChannel()
 * - SPI_Write()
 * - SPI_ReadWrite()
 * - SPI_CloseChannel()
 * - Cleanup_libMPSSE()
 * 
 * compilation : gcc -o mcp4142-cli mcp4142-cli.c -lmpsse
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <ftd2xx.h>
#include <WinTypes.h>
#include <libmpsse_spi.h>

int main() {
   FT_STATUS status;

   // Initialiser la librairie
   Init_libMPSSE();

   // Récupérer le nombre de canaux disponibles
   uint32_t numChannels;
   status = SPI_GetNumChannels(&numChannels);

   if(status != FT_OK) {
      printf("Impossible de déterminer le nombre de canaux MPSSE.\nBye!");
      Cleanup_libMPSSE();
      exit(-1);
   } else {
      printf("Nombre de canaux SPI trouvés : %d\n", numChannels);
   }

   // Afficher le détail des canaux trouvés
   FT_DEVICE_LIST_INFO_NODE channelInfo;
   uint32_t ft232hChannel = -1;
   for(int i = 0; i < numChannels; i++) {
      status = SPI_GetChannelInfo(i, &channelInfo);
      if(status != FT_OK) {
         printf("Impossible d’obtenir les détails du canal MPSSE n°%d.\nBye!", i);
         Cleanup_libMPSSE();
         exit(-1);
      } else {
         printf("Canal n° %d\n", i);
         printf(". Description\t: %s\n", channelInfo.Description);
         printf(". N° de série\t: %s\n", channelInfo.SerialNumber);
         printf(". ID\t\t: 0x%08x\n", channelInfo.ID);
         printf(". Flags\t\t: 0x%08x\n", channelInfo.Flags); /* Voir 
            https://ftdichip.com/wp-content/uploads/2023/09/D2XX_Programmers_Guide.pdf
            pour la définition des flags :
            "The flag value is a 4-byte bit map containing miscellaneous data as defined in Appendix A – Type
            Definitions. Bit 0 (least significant bit) of this number indicates if the port is open (1) or closed (0). Bit 1
            indicates if the device is enumerated as a high-speed USB device (2) or a full-speed USB device (0). The
            remaining bits (2 - 31) are reserved."
            */
         printf(". Type\t\t: %d\n", channelInfo.Type); /* Voir 
            https://docs.rs/libftd2xx/latest/libftd2xx/enum.DeviceType.html
            pour la liste des types (8=FT232H, 5=FT232R …​)
            */
         if(channelInfo.Type == 8) {
            ft232hChannel = i;
         }
      }
   }
   
   // Ouvrir le canal
   FT_HANDLE handle;
   status = SPI_OpenChannel(ft232hChannel, &handle);
   if(status != FT_OK) {
      printf("Impossible d’ouvrir le canal MPSSE n°%d associé au FT232H.\nBye!", ft232hChannel);
      Cleanup_libMPSSE();
      exit(-1);
   }  
   
   // Initialiser le canal
   ChannelConfig channelConf;
   channelConf.ClockRate = 1000000; // 1 MHz
   channelConf.LatencyTimer = 16; /* Voir
      https://ftdichip.com/wp-content/uploads/2020/08/AN232B-04_DataLatencyFlow.pdf
      pour une explication du latency timer.
      Ce délai n’a de sens que pour la lecture.
      La valeur 16ms est la valeur par défaut.
      :TODO: tenter de comprendre la signification réelle de ce paramètre
      */
   channelConf.configOptions = SPI_CONFIG_OPTION_MODE0 // SPI Mode 0
      | SPI_CONFIG_OPTION_CS_DBUS3 // xDBUS3 est utilisé comme Chip Select (CS)
      | SPI_CONFIG_OPTION_CS_ACTIVELOW // Chip Select actif à l’état bas
      ;
   channelConf.Pin = 0x00000000; /* Directions et valeurs des broches après 
      l’appel à SPI_InitChannel et SPI_CloseChannel.
      BIT7-BIT0 : Direction des broches après SPI_InitChannel (0=Input, 1=Output)
      BIT15-BIT8 : Valeurs des broches après SPI_InitChannel (0=Low, 1=High)
      BIT23-BIT16 : Direction des broches après SPI_CloseChannel (0=Input, 1=Output)
      BIT31-BIT24 : Valeurs des broches après SPI_CloseChannel (0=Low, 1=High)
      */;
   status = SPI_InitChannel(handle,&channelConf);
   if (status != FT_OK)
   {
      printf("Echec de l’initialisation du canal MPSSE n°%d associé au FT232H.\nBye!\n", ft232hChannel);
      // Fermer le canal & la librairie
      status = SPI_CloseChannel(handle);
      if(status != FT_OK) {
         printf("Erreur lors de la fermeture du canal MPSSE n°%d associé au FT232H.\n", ft232hChannel);
      }  
      Cleanup_libMPSSE();
      exit(-1);
   }   
   
   // Fixer la valeur du rheostat du MCP4142 à sa valeur médiane (0x40 ⇒ ~5kOhms)
   uint8_t inBuffer[2];
   uint8_t outBuffer[2] = {0x00, 0x40};
   uint32_t sizeToTransfer = 2;
   uint32_t sizeTransferred;
   uint32_t transferOptions = SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES // BIT0 : taille donnée en octets
      | SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE // BIT1 : CS activé avant le transfert
      | SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE // BIT2 : CS désactivé après le transfert
      ;
   status = SPI_Write(handle, outBuffer, sizeToTransfer, &sizeTransferred, transferOptions);
   if (status != FT_OK)
   {
      printf("Echec d’écriture du rheostat sur le canal MPSSE n°%d associé au FT232H.\nBye!\n", ft232hChannel);
      // Fermer le canal & la librairie
      status = SPI_CloseChannel(handle);
      if(status != FT_OK) {
         printf("Erreur lors de la fermeture du canal MPSSE n°%d associé au FT232H.\n", ft232hChannel);
      }  
      Cleanup_libMPSSE();
      exit(-1);
   } else {
      printf("valeur écrite avec succès\n");
   }
   
   int quit = 0;
   while(!quit) {
      
      // Incrémenter la valeur du rheostat
      outBuffer[0] = 0x04;
      sizeToTransfer = 1;
      status = SPI_Write(handle, outBuffer, sizeToTransfer, &sizeTransferred, transferOptions);
      if (status != FT_OK)
      {
         printf("Echec d’incrémentation de la valeur du rheostat sur le canal MPSSE n°%d associé au FT232H.\nBye!\n", ft232hChannel);
         // Fermer le canal & la librairie
         status = SPI_CloseChannel(handle);
         if(status != FT_OK) {
            printf("Erreur lors de la fermeture du canal MPSSE n°%d associé au FT232H.\n", ft232hChannel);
         }  
         Cleanup_libMPSSE();
         exit(-1);
      } else {
         printf("Valeur incrémentée avec succès !\n");
      }
      
      // Lire la valeur du rhéostat du MCP4142
      outBuffer[0] = 0x0C;
      outBuffer[1] = 0x00;
      sizeToTransfer = 2;
      status = SPI_ReadWrite(handle, inBuffer, outBuffer, sizeToTransfer, &sizeTransferred, transferOptions);
      if (status != FT_OK)
      {
         printf("Echec de lecture du rheostat sur le canal MPSSE n°%d associé au FT232H.\nBye!\n", ft232hChannel);
         // Fermer le canal & la librairie
         status = SPI_CloseChannel(handle);
         if(status != FT_OK) {
            printf("Erreur lors de la fermeture du canal MPSSE n°%d associé au FT232H.\n", ft232hChannel);
         }  
         Cleanup_libMPSSE();
         exit(-1);
      } else {
         uint16_t rValue = (uint16_t)(inBuffer[0] << 8) | inBuffer[1];
         printf("valeur lue : 0x%04x\n", rValue);
         if(rValue == 0xFE80) {
            break;
         }
      }
      
      sleep(1);
   }
   
   // Fermer le canal & la librairie
   status = SPI_CloseChannel(handle);
   if(status != FT_OK) {
      printf("Erreur lors de la fermeture du canal MPSSE n°%d associé au FT232H.\n", ft232hChannel);
   }  
   Cleanup_libMPSSE();
   printf("Programme terminé.\nBye !");
   
   return 0;
}

Ressources

🞄  🞄  🞄