Activité : intégration d'un contrôleur de bus I2C

Le bus I2C

Un bus I2C (Inter-IC Bus) est un bus série synchrone souvent utilisé pour échanger des données entre un microcontrôleur et un ou plusieurs périphériques. Dans le cadre de ce projet, il permettra à notre application de dialoguer avec un module capteur de température,

Maître et esclave I2C

Historiquement, les électroniciens utilisent les termes « maître » et « esclave » (ou « master » et « slave ») pour désigner les rôles joués par les composants d’un système lorsqu’ils communiquent ensemble. Il s’agit d’une analogie qui vise à faciliter la compréhension : dans un ordinateur, on dira que le processeur se comporte comme un maître, et ses périphériques comme des esclaves.

Il ne s’agit pas de faire la promotion de l’esclavage, ni d’en minimiser la gravité.

Cependant, depuis plusieurs années, cette terminologie est remise en question. La communauté des informaticiens et électroniciens remplace progressivement les mots « maître » et « esclave » par des termes plus neutres. À notre connaissance, il n’existe pas encore de consensus sur le choix de ces nouveaux termes. Pour cette raison, cette page continue à utiliser les mots « maître » et « esclave » qui restent très présents dans les documentations techniques des constructeurs et dans les spécifications de protocoles.

Dans une communication I2C, le maître est le composant qui a l’initiative des communications. L’esclave répond aux demandes du maître.

Physiquement, un bus I2C est composé de deux signaux :

C’est toujours le maître qui produit le signal d’horloge. Comme il n’y a qu’une ligne de données, le maître et l’esclave prennent alternativement le contrôle de SDA pour envoyer des données ou acquitter la réception d’un octet.

Le protocole I2C

Contrairement au bus SPI, un bus I2C peut interconnecter plusieurs maîtres et plusieurs esclaves. Chaque esclave possède une adresse sur 7 bits qui permet de l’identifier.

Au démarrage d’une trame I2C, le maître prend le contrôle des lignes SCL et SDA :

Ensuite, il peut utiliser SDA pour transmettre une séquence d’octets, ou libérer SDA pour laisser l’esclave lui transmettre des données. À la fin de chaque octet, le destinataire envoie un bit d’acquittement sur SDA.

Le protocole de communication est assez complexe à mettre en œuvre, d’abord à cause de la présence d’une seule ligne de données SDA qui a plusieurs rôles (signaler le démarrage ou l’arrêt d’une communication, envoyer ou recevoir des bits d’adresse ou de données, acquitter la réception d’un octet), mais aussi à cause de la présence éventuelle de plusieurs maîtres qui peuvent tenter de démarrer une communication simultanément.

Nous ne détaillerons pas le protocole dans cette page. Si vous le souhaitez, vous pouvez consulter la spécification officielle du bus I2C.

Compléter le projet Vivado

Si ce n’est pas déjà fait, ouvrez votre projet Computer dans Vivado :

cd $HOME/CoCiNum
./scripts/vivado vivado/Computer/Computer.xpr

Dans le panneau, Flow Navigator, exécutez l’action Add Sources, choisissez Add or create design sources et pressez le bouton Next.

Ajoutez les fichiers source VHDL suivants à votre projet. Tous ces fichiers sont situés dans des sous-dossiers de CoCiNum/src/vhdl.

Sous-dossier Fichier Rôle
I2C I2CMaster-precompiled.vhd Un périphérique contrôleur de bus I2C.

Nous ne fournissons pas le code source du contrôleur I2C.

Le fichier I2CMaster-precompiled.vhd contient du code VHDL qui a fait l’objet d’une première étape de synthèse logique. Il n’est pas destiné à être lu par un être humain.

Choisissez sur quel connecteur d’extension Pmod de la carte Basys3 vous allez brancher votre périphérique I2C. Sur la sérigraphie de la carte, ils portent les noms JA (en haut à gauche), JB (en haut à droite), JC (en bas à droite), et JXADC (en bas à gauche).

Dans le panneau, Flow Navigator, exécutez à nouveau l’action Add Sources, choisissez Add or create constraints et pressez le bouton Next.

Ajoutez au projet Vivado le fichier de contraintes correspondant à votre choix :

Sous-dossier Fichier Rôle
Basys3 Basys3_PmodA.xdc Fichier de contraintes pour Vivado, brochage du connecteur JA.
Basys3 Basys3_PmodB.xdc Fichier de contraintes pour Vivado, brochage du connecteur JB.
Basys3 Basys3_PmodC.xdc Fichier de contraintes pour Vivado, brochage du connecteur JC.
Basys3 Basys3_PmodXADC.xdc Fichier de contraintes pour Vivado, brochage du connecteur JD.

L’entité I2CMaster

L’entité I2CMaster est conçue comme un périphérique pour le processeur Virgule. Elle possède deux paramètres génériques :

Port Type Rôle
CLK_FREQUENCY_HZ Entier La fréquence du signal d’horloge clk_i, en Hz
I2C_FREQUENCY_HZ Entier La fréquence du signal d’horloge série scl_io, en Hz. Par défaut 100 kHz.

Et voici la liste de ses ports :

Port Direction Type Rôle
clk_i Entrée Logique Le signal d’horloge global
reset_i Entrée Logique La commande de réinitialisation
valid_i Entrée Logique Demande de transfert de donnée
ready_o Sortie Logique Indicateur de fin d’une lecture ou d’une écriture
write_i Entrée Vecteur de 4 bits Sélection des octets à écrire
address_i Entrée Logique Le bus d’adresses
wdata_i Entrée Vecteur de 32 bits Le bus de données en écriture
rdata_o Sortie Vecteur de 32 bits Le bus de données en lecture
evt_o Sortie Logique Indique la fin d’un échange de données
error_o Sortie Logique Indique une erreur au cours d’un échange de données
scl_io Entrée-sortie Logique La ligne d’horloge I2C
sda_io Entrée-sortie Logique La ligne de données I2C

Dans une situation simple avec un seul maître et un seul esclave, la sortie error_o ne sera pas utilisée.

En VHDL, les ports scl_io et sda_io sont déclarés avec le mode inout car ils jouent alternativement les rôles d’entrées ou de sorties.

L’entrée address_i permet d’accéder à deux registres de 32 bits. À l’adresse '1', le registre de contrôle se décompose en plusieurs champs :

address_i Bits Registre Rôle
'0' 31 à 0 data_reg Les données envoyées ou reçues.
'1' 14 à 12 send_len_reg Le nombre d’octets à envoyer (entre 0 et 4)
'1' 10 à 8 receive_len_reg Le nombre d’octets à recevoir (entre 0 et 4)
'1' 6 à 0 slave_address_reg L’adresse de l’esclave

L’entité I2CMaster implémente un scénario d’utilisation du bus I2C dans lequel une trame se décompose en :

  1. L’envoi d’une séquence contenant au maximum 4 octets.
  2. La réception d’une séquence contenant au maximum 4 octets, en provenance du même esclave.

Pour réaliser un tel échange, il faut :

  1. Régler send_len_reg, receive_len_reg et slave_address_reg.
  2. Écrire dans data_reg pour démarrer la communication. S’il n’y a rien à envoyer (send_len_reg = 0), on peut écrire n’importe quoi dans data_reg.
  3. Attendre que evt_o passe à '1'.
  4. Lire les octets reçus dans data_reg.

La sortie evt_o sera typiquement reliée au contrôleur d’interruptions.

Modification du système

Paquetage Computer_pkg

Dans le fichier Computer_pkg.vhd, ajoutez des constantes pour définir les caractéristiques des nouveaux périphériques du système :

Constante Type Valeur Rôle
I2C_ADDRESS Octet 84hex Les bits 31 à 24 de l’adresse pour accéder au contrôleur SPI.
INTC_EVENTS_I2C Entier 3 Pour le contrôleur d’interruptions, le numéro de l’événement indiquant la fin d’une communication SPI.

Les informations contenues dans la colonne Valeur sont des exemples. Vous pouvez les modifier à condition que chaque périphérique de votre architecture ait une adresse et un numéro d’événement différents des autres périphériques.

Entité Computer

Dans le fichier Computer.vhd, complétez l’entité Computer en déclarant les ports du connecteur d’extension que vous avez choisi. Le tableau ci-dessous donne la liste des ports disponibles et leur rôle. Ils sont tous de type std_logic.

Rôle JA JB JC JXADC Mode
Serial Clock pmod_a3 pmod_b3 pmod_c3 pmod_xadc3 inout
Serial Data pmod_a4 pmod_b4 pmod_c4 pmod_xadc4 inout

Architecture Structural

Dans le fichier Computer.vhd, complétez l’architecture pour intégrer un contrôleur I2C en utilisant les constantes que vous avez ajoutées au paquetage Computer_pkg.

Inspirez-vous des instances déjà présentes dans l’architecture.

Représentez sous la forme d’un schéma la nouvelle structure du système.

Fichier de contraintes

Le bus I2C nécessite la présence de résistances de tirage pull-up sur les lignes SCL et SDA. Nous allons profiter du fait que les entrées-sorties du FPGA Artix-7 possèdent des résistances internes dont l’utilisation est configurable.

Si vous avez choisi le connecteur JA, par exemple, ouvrez le fichier CoCiNum/src/vhdl/Basys3/Basys3_PmodA.xdc et ajoutez les lignes suivantes :

set_property PULLUP true [get_ports pmod_a3]
set_property PULLUP true [get_ports pmod_a4]

Vous pouvez procéder de la même manière si vous avez choisi un autre connecteur.

Générer le bitstream et configurer le FPGA

Dans Vivado, générez le fichier binaire à charger dans le FPGA : Flow NavigatorProgram and DebugGenerate Bitstream.

Si ce n’est pas déjà fait, reliez le connecteur micro-USB de la carte à un port USB de votre PC et mettez la carte sous tension.

Connectez Vivado à votre carte Basys3 : Flow NavigatorProgram and DebugOpen Hardware ManagerOpen TargetAuto-connect.

Configurez le FPGA : Flow NavigatorProgram and DebugOpen Hardware ManagerProgram Device.

Si vous n’avez pas d’autre périphérique à ajouter, vous pouvez passer à la section Développement logiciel embarqué.