Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/Utiliser des shields Arduino avec les FPGA

Début de la boite de navigation du travail pratique

Comme son titre l'indique, ce chapitre est destiné à étudier les liaisons dangereuses (ou pas) entre les shields destinés aux Arduinos (5 V) et les FPGA (3,3 V). Ici, le mot shield sera pris dans un sens très large : tout ce qui se connecte à l'Arduino rapidement, y compris avec des fils et des plaques à essais.

Utiliser des shields Arduino avec les FPGA
Image logo représentative de la faculté
T.P. no 10
Leçon : Very High Speed Integrated Circuit Hardware Description Language

TP de niveau 15.

Précédent :TPs ATTiny861 avec Altera
Suivant :Sommaire
En raison de limitations techniques, la typographie souhaitable du titre, « Travail pratique : Utiliser des shields Arduino avec les FPGA
Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/Utiliser des shields Arduino avec les FPGA
 », n'a pu être restituée correctement ci-dessus.


La très grande majorité des shields Arduino sont prévus pour être alimenté en 5 V. L'utilisation de ces shields avec des FPGA qui fonctionnent en 3,3 V nécessite donc un certain nombre de précautions. Celui qui craint le plus dans l'histoire est naturellement le FPGA.

Il existe des cartes FPGA avec un connecteur de type Arduino, donc capable de recevoir un shield :

  • la DE0 nano Soc de chez Terasic en est un premier exemple. Elle est équipée d'un cyclone V de chez Altera(Euh... pardon, de chez Intel) . Le connecteur Arduino de cette carte fournit du 5 V à l'endroit normal du 5 V des cartes Arduino. Cela veut dire que si vous mettez un shield sur cette carte, vous risquez d'endommager le FPGA. Il suffit d'avoir 3 boutons en pull-up et vous avez automatiquement du 5 V sur quelques entrées du FPGA !
  • la DE10-lite du même constructeur possède aussi un connecteur Arduino.
  • La carte Arty en est un autre exemple de chez Digilent. Nous ne l'avons pas encore exploré (car nous n'en possédons pas) et ne savons donc pas comment est géré le 5 V. Une phrase dans la documentation nous laisse penser qu'on a le même type de problème qu'avec la carte précédente : The Arty is not compatible with shields that output 5 V digital or analog signals. Driving pins on the Arty shield connector above 5 V may cause damage to the FPGA.

Pour cela nous avons décidé de faire nos propres shields en gardant quelques bonnes idées des shields commercialisés. Il est difficile cependant de se mettre un bandeau sur les yeux et de ne pas regarder vers l'orient. Même si construire est une idée raisonnable pour un établissement qui enseigne l'électronique et donc la fabrication de cartes, les bas prix des shields venant de Chine la met à mal pour les hobbyistes et même les enseignants qui ont des budgets de plus en plus serrés. Nous avons en effet trouvé un shield avec quelques boutons et un afficheur LCD de deux lignes de 16 caractères pour un peu moins de 4  et un shield multifonction pour un peu plus de 5 . Tous les deux ont été testés et fonctionnent parfaitement avec des Arduino. Nous allons donc explorer leur utilisation dans ce chapitre en remplaçant l'Arduino par un FPGA. Et nous en profiterons pour nous intéresser à bien d'autres shields et capteurs...

Pour les cartes Xilinx de Digilent, pensez à relire les connecteurs PMod pour comprendre cette connectique.

Étude du shield multi fonction modifier

Le shield multifonction est décrit par exemple ici.

Cette carte est prévue pour un fonctionnement en 5 V. Les 4 digits d'affichage sept segments sont pilotés par deux registres à décalage qu'il nous faudra étudier pour bien comprendre ce que l'on cherche à réaliser. Le point important à ce stade est qu'ils ont été choisis en CMOS (74HC595) et que par conséquent le 3,3 V ne leur fait pas peur. L'autre problème éventuel est de savoir si les afficheurs fonctionnent en 3,3 V. La réponse est OUI.

Début d’un principe
Fin du principe


Présentation modifier

Prenez le code d'exemple présenté ici. Nous vous conseillons fortement son utilisation avec une carte Arduino pour une prise de contact rapide. On reproduit ce code ici

/* Define shift register pins used for seven segment display */
#define LATCH_DIO 4
#define CLK_DIO 7
#define DATA_DIO 8
 
/* Segment byte maps for numbers 0 to 9 */
const byte SEGMENT_MAP[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};
/* Byte maps to select digit 1 to 4 */
const byte SEGMENT_SELECT[] = {0xF1,0xF2,0xF4,0xF8};
 
void setup ()
{
/* Set DIO pins to outputs */
pinMode(LATCH_DIO,OUTPUT);
pinMode(CLK_DIO,OUTPUT);
pinMode(DATA_DIO,OUTPUT);
}
 
/* Main program */
void loop()
{
 
/* Update the display with the current counter value */
WriteNumberToSegment(0 , 0);
WriteNumberToSegment(1 , 1);
WriteNumberToSegment(2 , 2);
WriteNumberToSegment(3 , 3);
}
 
/* Write a decimal number between 0 and 9 to one of the 4 digits of the display */
void WriteNumberToSegment(byte Segment, byte Value)
{
digitalWrite(LATCH_DIO,LOW);
shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_MAP[Value]);
shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_SELECT[Segment] );
digitalWrite(LATCH_DIO,HIGH);
}

Ce programme est sensé afficher 0123 sur les 4 digits. Il montre aussi quelques détails importants sur le câblage :

  • le transfert des données se fait à l'aide de trois fils (seulement) et c'est ce qui en fait tout son intérêt. Ces trois fils sont décrit maintenant
  • le fil de donnée DATA_DIO est câblé en broche 8 Arduino
  • le fil d'horloge de transfert CLK_DIO est câblé en broche 7 Arduino
  • le fil de mémorisation est câblé en broche 4

Ces informations sont vitales pour nous.

Revenons un peu sur cette histoire des trois fils. Un afficheur sept segments 4 digits comporte au moins 12 fils :

  • 7 fils pour chacun des segments
  • 1 fil pour le point
  • 4 fils pour la sélection de chacun des digits.

Réussir à n'utiliser que trois sorties pour l'utiliser est donc une idée intéressante. Elle est réalisable en remplaçant l'écriture parallèle par une écriture série. Ce qui rend la mise en œuvre délicate est le multiplexage des afficheurs : il faut mettre à jour sans arrêt l'affichage pour faire croire au lecteur qu'il est simultané sur les quatre afficheurs alors qu'il n'en n'est rien. En programmation, ceci peut être réalisé à l'aide d'une interruption qui s'occupe des transferts dans les deux 74HC595. En VHDL, le matériel doit s'occuper de tout cela.

Notre montage d'essai modifier

 
Connecter un shield multifonction avec une nexys 3

Nous n'avons pas beaucoup de fils à brancher à cause de l'utilisation des registres à décalage. 5 fils en tout (alimentation comprise). Le connecteur PMod des cartes Digilent est déjà décrit dans un autre chapitre et permet de brancher 6 fils (y compris alimentation). Un simple connecteur PMod est donc suffisant et donc facile à trouver sur les cartes Digilent.

  • le Vcc 3,3 V de PMod est amené au 5 V du shield
  • La masse de PMod est amenée à la masse du shield
  • le suivant, fil de donnée, est amené à la broche 8 du shield
  • le suivant, fil d'horloge, est amené à la broche 7 du shield
  • le suivant, horloge de mémorisation, est amené à la broche 4 du shield.

Tous les numéros apparaissant dans cette description correspondent à la sériegraphie du shield.

Évidemment, une connexion aussi simple ne permet pas d'avoir accès à toutes les fonctionnalités du shield. Pour cet exemple seuls les 4 digits des afficheurs sept segments sont gérés par le FPGA. Si vous voulez utiliser l'information des boutons poussoirs il vous faut savoir qu'ils sont montés avec une résistance de tirage vers le haut (pull-up) et sont connectés aux broches serigraphiées A1, A2 et A3 du shield. Donc trois fils en plus. Les 4 leds sont reliées à 13, 12, 11 et 10 encore 4 fils. Le buzzer est relié à 3 avec donc un seul fil.

Du VHDL pour afficher une donnée sur 4 digits modifier

Résoudre ce problème est pédagogiquement très intéressant pour l'étude des registres à décalages et aussi pour la gestion d'un séquencement à l'aide d'un compteur.

Dans la suite nous serons amenés à réaliser deux types de signaux :

  • un tick : signal à "un" pendant une seule période d'horloge globale du FPGA (nous utiliserons cette dénomination anglo-saxonne dans la suite de ce chapitre)
  • une horloge : signal qui reste à 1 sur plusieurs fronts et à 0 sur plusieurs fronts de l'horloge globale du FPGA

Le tick ne servira qu'à l'intérieur du FPGA pour réaliser des montages synchrones tandis que l'horloge pilotera des circuits externes incapables de fonctionner à la fréquence du FPGA. Tout ceci va très vite devenir plus clair avec les mises en pratiques dans les exercices qui suivent.


Introduction : Exercice 1 modifier

 
Diminuer la fréquence de comptage d'un compteur 16 bits

Nous allons commencer par réaliser un compteur 16 bits. Nous allons afficher son comptage sur les Leds disponibles : 8 sur la Nexys 3 (Digilent) et 16 sur la DE2-115 (Terasic). Il est donc impossible d'utiliser l'horloge 100MHz de la carte Nexys 3 et espérer voir quelque chose à l’œil. Il faut donc utiliser un autre compteur pour diminuer cette fréquence. Ceci a été présenté plusieurs fois dans ce livre et ailleurs :

Mais à chaque fois il s'agissait de prendre le bit de poids fort du compteur et de l'utiliser comme horloge de l'étage suivant. Cette façon de faire est assez facile à comprendre mais déconseillée pour une utilisation dans un FPGA. On doit lui préférer la méthode synchrone : toutes les horloges de tous les composants sont reliées à une horloge commune : l'horloge globale.

Nous allons donc réaliser l'ensemble demandé dans les règles de l'art : les deux compteurs utiliseront la même horloge. On vous demande de réaliser l'ensemble présenté par le schéma ci-dessus, en commençant par trouver l'équation combinatoire demandée. Il s'agit de réaliser un tick sur ENO, c'est-à-dire un signal qui ne vaut un que pendant une seule période d'horloge : l'horloge globale.

Début d’un principe
Fin du principe


Le multiplexeur et le transcodeur : Exercice 2 modifier

 
Schéma complet à réaliser

On vous donne le schéma complet de ce que l'on cherche à faire dans la figure ci-contre. On vous demande de réaliser le multiplexeur et le transcodeur (à droite du schéma).

Le multiplexeur ne présente aucune difficulté de réalisation.

Le transcodeur non plus. Il vous faut seulement savoir qu'il faut un '0' pour allumer un segment et que le segment g est poids faible.

En fin de l'exercice 2 vous aurez donc le compteur 25 bits, le compteur 16 bits, le multiplexeur commandé par 4 interrupteurs et le transcodeur.

Cet ensemble peut être testé sur les afficheurs de votre carte FPGA. Vous prendrez quatre interrupteurs pour la sélection même si le séquenceur de l'exercice 3 n'en délivrera que deux. Cela vous permettra de savoir comment on sélectionne une seul afficheur à la fois et surtout ce qui se passe lorsque vous en sélectionnez deux.

Le séquenceur : Exercice 3 modifier

 
Table de séquencement partielle

Comme d'habitude, l'affichage consiste à réaliser des choses visibles et d'autres non visibles. Par exemple, faire défiler des nombres est intéressant si cela est visible. Mais la commutation des afficheurs, elle, reste invisible. Ceci nécessite la réalisation de plusieurs fréquences :

  • une d'environ 2,98 Hz (= 100MHz / 2**25) nécessite donc un compteur de 25 bits sera utilisée pour faire défiler un compteur de 16 bits qui sera affiché
  • une d'environ 1,56 MHz (= 100MHz / 2**6) servira à envoyer les données au shield (servira d'horloge de données)
  • une d'environ 1525 Hz (= 100MHz / 2**16) servira à commuter les afficheurs

Nous allons essayer de respecter les règles matérielles des FPGA pour les horloges en question. Cela veut dire qu'en fait ce ne seront pas des horloges mais des signaux de validation. Nous n'avons pas assez utilisé cette règle dans ce livre et essayerons de résoudre ce problème plus tard.

Réaliser le séquenceur. La difficulté, bien sûr est de réaliser une partie matérielle qui est en fait du combinatoire. Nous allons vous aider en vous présentant un extrait de la table de vérité (figure ci-contre). Une bonne compréhension de celle-ci est nécessaire pour résoudre ce problème.

Une bonne exploration de la table de séquencement vous montre que :

  • les bits Q17 et Q16 serviront à la sélection du digit en cours d'affichage : il y en a 4 donc deux bits suffisent (cela n'apparaît pas sur la table de séquencement)
  • les bits Q24 à Q18 ne sont pas utiles au séquencement : les équations logiques ne les feront donc pas apparaître
  • il y a deux types de sortie :
    • tick ne sont définis que pour une période d'horloge : Eno et Load. Ils seront utiles au fonctionnement interne, c'est-à-dire au FPGA mais pas au shield directement.
    • horloges utiles à l'extérieure : Done. On ne peut pas prendre un tick pour cela car cela serait bien trop rapide pour les circuits 74HC595D du shield.
  • le 0->1->0 qui apparaît en dernière colonne a pour signification que l'on doit réaliser une période d'horloge sur une ligne. Le seul moyen de faire cela est d'utiliser le bit de poids immédiatement plus petit, à savoir Q5.

1°) Exprimer Eno et Load en fonction de Q(15 downto 0)

2°) Exprimer Done en fonction de Q(15 downto 6)

3°) On vous donne la réalisation de clk1MHz

clk1MHz <= cmpt(5) when cmpt(15 downto 6) >= "0000000001" 
           and cmpt(15 downto 6) < "0000010000" else
             '0';

N'aviez-vous pas remarqué que cette "horloge" ne fonctionne pas tout le temps, seulement après un Load ?

4°) Complétez ce qui est nécessaire pour réaliser le séquenceur complet

5°) A l'aide d'un code un peu similaire à celui ci-dessous, on vous demande d'essayer encore une fois votre code sur votre carte FPGA (sans shield)

with sel select
    s <= "0001" when "00",
         "0010" when "01",
         "0100" when "10",
         "1000" when others;

Le composant "Load_And_Send" : Exercice 4 modifier

 
Schéma complet à réaliser

On redonne ci-contre, la figure que l'on cherche à réaliser. Il ne reste plus qu'à réaliser le composant du bas (à droite) que l'on a appelé load_and_send.

  • Une des entrées de ce composant est "sel" mais sur deux bits. Un code comme celui donné dans l'exercice 3 (5° question) sera nécessaire pour le transformer en sélection de 1 parmi 3.
  • L'autre est naturellement la donnée qui sort du transcodeur.

Ce que l'on doit faire avec ce composant est de former un signal de 16 bits avec les deux données qui n'en font que 11, et ceci lors d'un LOad synchrone. On vous rappelle que ceci peut se faire avec le code VHDL :

if rising_edge(clk) then
   if load = '1' then
        s16bits <= s & "0000" & dataIn & '1';  
   end if;
end if;

où l'opérateur '&' en VHDL est l'opérateur de concaténation. Le '1' complètement à droite est là pour éteindre le point.

  • sa troisième fonctionnalité est de réaliser un décalage vers la droite quand son entrée 'en' est à un.

Quand tout cela sera réalisé, il sera grand temps de réaliser l'ensemble complet.

Le code complet corrigé modifier

Voici le code complet correspondant à la résolution de notre problème.

Avec ce code source, seule la gestion du point n'est pas correcte : pas de point pour le digit des unités comme nous le désirons , mais un point sur les trois autres digits !

Comme la photo l'a montré, nous utilisons le connecteur JD d'une carte Nexyx 3. Nous donnons donc le fichier ucf pour cette carte :

## This file is a general .ucf for Nexys3 rev B board
## To use it in a project:
## - remove or comment the lines corresponding to unused pins
## - rename the used signals according to the project

## Clock signal
NET "clk_100MHz"            LOC = "V10" | IOSTANDARD = "LVCMOS33";   #Bank = 2, pin name = IO_L30N_GCLK0_USERCCLK,   Sch name = GCLK
Net "clk_100MHz" TNM_NET = sys_clk_pin;
TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 100000 kHz;

##JD, LX16 Die only
#NET "JD<0>"  LOC = "G11" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L40P,   Sch name = JD1
#NET "JD<1>"  LOC = "F10" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L40N,   Sch name = JD2
#NET "JD<2>"  LOC = "F11" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L42P,   Sch name = JD3
#NET "JD<3>"  LOC = "E11" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L42N, 
NET "LatchClk"  LOC = "F10" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L40N,  Sch name = JD2
NET "ShiftClk"  LOC = "F11" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L42P,  Sch name = JD3
NET "DataOut"   LOC = "E11" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L42N,  Sch name = JD4
#NET "JD<4>"  LOC = "D12" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L47P,    Sch name = JD7
#NET "JD<5>"  LOC = "C12" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L47N,    Sch name = JD8
#NET "JD<6>"  LOC = "F12" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L51P,    Sch name = JD9
#NET "JD<7>"  LOC = "E12" | IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L51N,    Sch name = JD10

Comptage décimal : Exercice 5 modifier

 
Cascader des compteurs décimaux pour 4 digits

Le compatge précédent affiche en hexadécimal. Les informaticiens savent le lire mais pas le commun des mortels. On vous demande donc de remplacer le compteur sur 16 bits par quatre compteur BCD cascadés.

Le schéma de principe est présenté dans la figure ci-contre. L'idée générale est de remplacer le compteur 16 bits de la section précédente par quatre compteurs cascadés : un par digit.

Nous n'avons pas nommé les fils internes dans la figure. C'est la première étape de ce que vous ferez si vous ne voulez pas vous perdre dans le câblage des composants.

Indication : Vous disposez d'un compteur décimal BCD cascadable complet:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity CounterBCD is
   port( EN: in std_logic;
 	 Clock: in std_logic;
 	 Reset: in std_logic;
	 ENO : out std_logic;
 	 Output: out std_logic_vector(3 downto 0));
end CounterBCD;
 
architecture Behavioral of CounterBCD is
   signal cmpt: std_logic_vector(3 downto 0);
	signal s_en_cmpt: std_logic_vector(4 downto 0);
begin   process(Clock,Reset)
   begin
      if(rising_edge(Clock)) then
		  if Reset='1' then
         cmpt <= "0000";
 	      elsif EN='1' then
	         if cmpt="1001" then
	            cmpt<="0000";
	         else
	           cmpt <= cmpt + 1;
	         end if;
         end if;
      end if;
   end process;
   Output <= cmpt;
	s_en_cmpt <= en & cmpt;
   with s_en_cmpt select
     ENO <= '1' when "11001",
            '0' when others;
end Behavioral;

Voici la solution correspondante.

Réalisation d'un réveil : Exercice 6 modifier

 
Réaliser un compteur des heures minutes

Transformer l'ensemble des quatre compteurs décimaux en un ensemble capable de compter des heures et minutes.

La résolution de ce problème nécessitera d'utiliser

  • le reset pour les compteurs dizaine et unité des heures : détection de 23 et du en sur les unités passe à 00 à l'aide du reset (bouclage synchrone par du combinatoire, synchrone car le reset est synchrone)
  • pour les minutes, le comptage des dizaine nécessitera la réalisation d'un compteur modulo 6. Il s'agit de partir du compteur BCD de la section précédente et d'exécuter une simple modification.

L'ensemble du travail à réaliser vous est présenté dans la figure ci-contre. Cet ensemble remplacera le compteur 16 bits qui attaquait le multiplexeur. Nous gardons donc la fréquence de 3 Hz pour ne pas attendre trop longtemps le passage de 23:59 à 00:00 qui est la difficulté de cet exercice.

Et si l'on mettait un processeur pour piloter tout cela modifier

Maintenant que nous avons réalisé un périphérique fonctionnel, il est grand temps de le piloter par un processeur. Ce travail va consister donc à remplacer le compteur 16 bits de l'exercice 4 par deux ports d'un processeur. Nous allons utiliser pour cela l'ATMega16 déjà décrit dans ce livre.

Exercice 7 : Réalisation simple avec deux PORTs modifier

 
Réalisation d'un réveil à l'aide d'un processeur sur notre shield multifonction

L'objectif de cet exercice est d'arriver au même point que l'exercice 6 mais avec un processeur. Il s'agit donc de faire défiler des chiffres en format hh:mm, deux digits pour les heures et deux pour les minutes. Le transcodage matériel est gardé, cela implique que les données seront sur 16 bits. Pour simplifier le dessin de la figure, un certain nombre de détails n'ont pas été présentés :

  • le fichier io.vhd n'est pas dessiné mais une partie de son contenu seulement (le process iowr qui est le seul qui nous intéresse)
  • le fichier complet du processeur qui est le fichier complet contenu dans le FPGA n'est que très partiellement présenté

Malgré cette représentation partielle par le dessin, nous espérons que le travail à réaliser vous paraîtra clair :

  • retirer le compteur 16 bits de l'exercice 6 et de le remplacer par une entrée sur 16 bits
  • réaliser l'entrée 16 bits avec deux PORTs du processeur. On prendra PORTB pour les 8 bits de poids faible, donc les minutes et PORTC pour les 8 bits de poids fort, donc les heures
  • le composant top de l'exercice 6 amputé de son compteur 16 bits, sera ensuite changé en composant de "io.vhd" et câblé correctement. Ses trois sorties seront amenées jusqu'aux sorties du processeur
Début d’un principe
Fin du principe

Réaliser ensuite le programme qui compte comme un réveil. 0n vous conseille cependant d'utiliser l'incrémentation ++, habituelle en C, et de la faire suivre d'une série de tests.

Exercice 8 : Réalisation d'un réveil complet modifier

Nous allons utiliser les ressources complètes du shield multifonction pour réaliser un réveil complet. Il s'agit d'utiliser en plus des afficheurs, les trois boutons et le buzzer. Il doit être possible de régler l'heure de réveil et l'armement de ce dernier. L'heure courante sera gardée assez rapide pour les tests.

Le cahier des charges est le suivant :

  • le bouton de gauche est utilisé pour l'armement du réveil et allume une LED.
  • le bouton du milieu est utilisé pour passer en mode réglage (incrémentation) de l'heure de réveil. Un nouvel appui passe en mode décrémentation. Enfin un nouvel appui quitte le mode réglage
  • le bouton de droite incrémente l'heure de réveil. Un bon fonctionnement serait d'augmenter la vitesse de défilement de ces heures en fonction du temps d'appui

La sérigraphie du shield vous donne :

  • bouton gauche en A1, bouton milieu en A2 et bouton droite en A3
  • LED D1 en 13. En fait les 4 LEDs font face à leur connecteurs
  • Le buzzer est en (-3)

Ces informations sont importantes pour câbler votre shield au FPGA.

Voir aussi modifier

Adapter des shields 5V aux cartes FPGA 3,3 V modifier

Nous avons déjà eu l'occasion de le répéter dans ce livre, les cartes Digilent sont conçues de plus en plus pour n'avoir comme connecteur d'extension que des PMod. C'est pratique si l'on achète des extensions PMod. Nous en utilisons quelques unes avec succès (voir chapitre sur la robotique mobile) mais nous les trouvons bien trop chères. Un sonar se retrouve à 40  alors que le module HC SR04 peut être trouvé aux alentours de 2 . Un PMod double afficheur 7 segments coûte 16  tandis qu'on a utilisé des quadruples afficheurs pour moins de 4 . Bien sûr le branchement n'est alors pas immédiat. Nous ne prétendons pas que les qualités sont les mêmes mais pour les utiliser avec des étudiants la basse qualité suffit bien souvent. L'autre problème important est qu'il n'y a pas de 5V sur le PMod !

Digilent va garder ses PMod certainement longtemps... mais commence à faire des cartes avec des connecteurs de type Arduino comme la carte Arty. Mais là c'est le prix de la carte elle-même qui pose problème ! Elle est à 99,00$ aux US, mais se retrouve à 174  chez nous !

Tous ces discours n'amènent qu'à une question : comment faire pour utiliser les shields 5V avec les cartes FPGA ?

Les réponses sont encore chinoises :

  • vous pouvez trouver un module d'alimentation 5V et 3,3 V pour plaque à essais pour un peu plus de 2 . Voici un lien qui montre de quoi nous parlons.
  • si vous avez besoin d'adapter les tensions, des modules convertisseur de tension logique bidirectionnel peuvent être trouvés aux alentours d'1 .

Soyons un peu plus précis. Dans le cas général, au-delà des problèmes de PMod, vous allez vous trouver dans deux situations différentes :

  • l'i2c, abordé dans la section suivante. C'est un problème à part car ce qui caractérise l'i2c c'est qu'il nécessite des résistances de tirage (pull-up). L'i2c 5V aura donc des résistances de tirages reliées à 5V. Si votre maître est en i2c 3,3 V vous aurez votre maître qui sera tiré à 5V dès qu'il recevra une réponse du périphérique... et il ne supporte pas forcément. Il vous faut alors les adaptations bidirectionnelles évoquées.
  • tous les autres protocoles comme le SPI, la rs232 et d'autres ne nécessitent pas forcément une adaptation bidirectionnelle. Si un périphérique 5V est commandé par un signal 3,3 V cela ne pose aucun problème en général. Si ce périphérique 5V envoi un 1 logique au maître 3,3 V, il faut adapter. Cela peut se faire avec une simple paire de résistance 1k, 2k ou même 10k, 20k.

Une remarque pour terminer. Quand nous disons que le 3,3 V peut commander sans problème du 5V, ceci est vrai pour du TTL. Nous allons rencontrer dans la suite de ce chapitre deux circuits qui ne vérifient pas les conditions :

  • TM1637 pour commander 4 digits de 7 segments alimenté en 5V mais qui nécessite 0,7*Vcc soit 3,5 V pour fonctionner correctement
  • L298N pour commander deux moteurs à courant continu qui nécessite une adaptation de tension au moins pour une question de sûreté de fonctionnement

De l'I2C pour accéder à d'autres capteurs modifier

Nous avons déjà eu l'occasion d'aborder l'i2c dans ce livre, mais dans un contexte précis : Etude de la manette Nunchuk. Nous avions réalisé un périphérique complètement adapté à la manette. Il a cependant été réalisé avec une grosse machine d'états et il est temps pour nous de réaliser un périphérique à peu près compatible avec les périphériques i2c que l'on trouve dans les AVR. L'intérêt est alors sa facilité de mise en œuvre par programme.

Conception du périphérique i2c modifier

La conception d'un périphérique i2c est trop ardue pour en faire un TP de niveau 15. Nous avons décidé de la déplacer dans la partie cours de ce livre, dans le chapitre Améliorer l'ATMega8 avec l'ATMega16 et l'ATMega32. Ce cours a été rédigé sous forme d'exercices et nous invitons les architectes matériels en herbe à les réaliser, voir à les améliorer.

Nous allons garder dans ce chapitre de TP les utilisations pratiques de ce périphérique.

Périphérique Tiny RTC modifier

C'est un composant dont le prix varie beaucoup : nous l'avons acheté pour environ de 1,1  et peut être encore trouvé à ce prix. De toute façon, pas de quoi faire un crédit sur 20 ans.

Ce composant est décrit dans son propre WIKI. Vous y trouverez une librairie et un programme d'exemple suffisant pour se lancer.

Le problème pour ce composant avec un FPGA est encore une fois électrique. Le circuit central, le DS1307 possède deux entrées de tension :

  • une entrée batterie   avec une tension entre 2,0 V et 3,5 V (peut laisser présager une compatibilité avec le FPGA mais ...)
  • une entrée   avec une tension entre 4,5 V et 5,5 V pour l'interface i2c, et là c'est le drame...

Il est donc impossible de brancher le Tiny RTC directement sur un FPGA : une adaptation en tension est nécessaire.

Périphérique MPU6050 sur carte Nexys 3 modifier

Il s'agit d'un accéléromètre doublé d'un gyroscope. Il peut être trouvé aux environs de 2  et quand on sait qu'il possède un processeur 32 bits spécialisé pour les mouvements (Digital Motion Processor : DMP), il est facile de se laisser tenter. Signalons aussi qu'un chapitre de TP est consacré à ce composant dans le livre sur les AVR et qu'un peu de documentation peut être trouvée plus loin dans ce chapitre.

Utilisation en 3,3 V avec un microcontrôleur modifier

 
Relier le launchPad tm4c123 au MPU6050

Comme nous en avons pris l'habitude dans ce chapitre, nous commençons par essayer les shields avec un processeur, en général 32 bits, qui a l'avantage de fonctionner en 3,3 V. Nous ne dérogerons pas à ce rituel ici.

Pour ne pas utiliser toujours un MSP432, nous avons décidé d'utiliser une carte launchpad tm4c123 de chez Texas Instrument. Elle sera essayée avec l'environnement de programmation Energia tellement proche de l'Arduino qu'un Copier-Coller du code est suffisant.

Voici donc notre code de départ. Il se cantonne à relever les données de l'accéléromètre, pas celles du gyroscope et de les envoyer à travers la liaison série.

// MPU-6050 Short Example Sketch
// By Arduino User JohnChi
// August 17, 2014
// Public Domain
#include<Wire.h>
const int MPU=0x68; // I2C address of the MPU-6050
int16_t AcX,AcY,AcZ;

void setup(){
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B); // PWR_MGMT_1 register
  Wire.write(0); // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  Serial.begin(9600);
}

void loop(){
  Wire.beginTransmission(MPU);
  Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU,6,true); // request a total of 6 registers
  AcX=Wire.read()<<8|Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  AcY=Wire.read()<<8|Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ=Wire.read()<<8|Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Serial.print("AcX = "); Serial.print(AcX);
  Serial.print(" | AcY = "); Serial.print(AcY);
  Serial.print(" | AcZ = "); Serial.println(AcZ);
  delay(333);
}

Qui fonctionne correctement à part quelque fois des petits problèmes avec la liaison série qui n'ont donc rien à voir avec ce que l'on cherche à résoudre. Le MPU6050 fonctionne donc parfaitement lorsqu'il est alimenté en 3,3 V ou en 5V.

Utilisation avec un FPGA modifier

 
Relier la carte Nexys 3 au MPU6050

Le périphérique pour la Nunchuk dans un autre chapitre est utilisable tel quel. Par contre le programme doit être légèrement modifié. La raison en est que le prorocole i2c utilisé est différent. Je ne parle pas du protocole i2c physique, qui bien sûr, reste identique pour les deux périphériques... mais de la façon d'interroger le périphérique pour lui demander des données.

L'adresse du périphérique MPU6050 peut être 0x68 ou 0x69 en fonction de la valeur sur l'entrée AD0 du circuit. Nous prendrons AD0 relié à la masse ce qui fixe l'adresse à 0x68.

Le code commence bien sûr par une initialisation de l'i2c physique puis se poursuit l'envoi de 0x6B suivi de 0x00. Ceci termine la partie setup

Ensuite vient la boucle infinie qui réinitialise la lecture par l'envoi de 0x3B puis d'une lecture des six données. Comme d'habitude en i2c la fin de lecture se termine par un non acquittement suivi d'un stop.

Voici donc ce code complet. Nous y avons laissé les sous-programmes pour la liaison série même si nous ne les utilisons pas.

Vous pouvez remarquer le "PORTC=t[0] qui permet de sortir la première valeur lue sur les leds. C'est le poids fort de l'accélération suivant l'axe des x. Un bon fonctionnement doit donc se traduire par des leds qui changent quand l'orientation de l'accéléromètre varie car celui-ci est sensible à l'accélération de pesanteur.

Périphérique MPU6050 sur carte Altera DE2-115 modifier

A priori le portage n'est qu'électrique : on débranche la nexys 3 et on branche la DE2-115. Mais, comme toujours, il faut se méfier des à priori...

Rappelons pour ceux qui arrivent ici sans avoir lu grand chose des autres parties de ce livre, que nous n'avons pas encore porté notre processeur préféré (ATMega16) sur les cyclones d'Altera mais que nous devons nous contenter du modeste ATTiny861. C'est mieux que rien et cela se programme en C.

Pour commander notre coeur i2c, nous avons décidé d'utiliser les registres du Tiny861 et non ceux des ATMega : ceux des ATMegas ont un nom en TWXX tandis que ceux du Tiny861 ont un nom en USIXX. Pourquoi procéder comme cela ?

  • avantage : on évite les redéfinitions des noms de registres dans la partie entête du programme c
  • inconvénient : on s'éloigne du cœur i2c du vrai Tiny861 de chez Atmel car si les noms ont changés, il en est de même des fonctionalités

L'inconvénient est facile à lever : il suffit d'écrire une librairie pour l'i2c... que voici

 

La librairie fournie ci-dessus ne fonctionne pas dans un ATTiny861 du commerce.

Réalisation matérielle modifier

Nous vous proposons sans détailler la ressource complète sur mon site personnel. Comme d'habitude le sous-répertoire soft contient le programme c et sa compilation.

Voir aussi modifier

Module d'affichage LED avec affichage de l'horloge pour Arduino modifier

Ce module n'est pas à proprement parler un shield. Si vous voulez voir son aspect tapez le titre de cette section dans Google ou autre. Ce qui nous intéresse est son prix (3,84  au moment de l'écriture de cette section 2017) et que sa conception est complètement différente du shield multi fonctions de la section précédente. Une des conséquences est qu'il nous faudra modifier notre VHDL pour l'utiliser. Sa mise en œuvre nécessite de résoudre deux problèmes :

  • est-il compatible avec le 3,3 V des FPGA ?
  • maîtriser le circuit TM1637 de Titan Micro electronics qui équipe ce module d'affichage

Compatibilité électrique modifier

La documentation du TM1637 affirme que ce composant peut être alimenté entre 4,5 V et 5,5 V. Sachant que VIH est à 0,7 VCC, il ne pourra pas être confortablement utilisé en 3,3 V. Nous l'avons appris à nos dépens.

Programmation du TM1637 modifier

Du code pour Arduino est facilement trouvable dans GitHub. Pour entrer dans le monde du 3,3 V, nous allons utiliser ce code avec Energia avec comme cible, une carte MSP432. Pour information Energia est l'environnement compatible Arduino pour les processeurs Texas Instrument. Nous utilisons une carte MSP432 parce que le 5V nécessaire au bon fonctionnement du module d'affichage y est présent.

Decompactez la librairie précédente dans le répertoire libraries de l'installation d'Energia. Nous n'avons pas pris le risque de garder le tiret dans le nom de celle-ci et l'avons remplacé par un caractère de soulignement. Ce travail se fait donc exactement de la même manière que pour l'environnement Arduino.

Vous avez alors à disposition un exemple que nous reproduisons :

qui fonctionne parfaitement avec une alimentation en 5V.

Ceci n'est pas conforme à la documentation. Une autre manière de dire les choses c'est que ce jour d'essai, nous avons eu de la chance car la documentation indique clairement qu'il faut utiliser une adaptation de tension : VIH= 0,7 x Vcc vaut 3,5 V.

Réalisation matérielle modifier

La communication se faisant avec un protocole de type i2c (ce n'est pas de l'i2c car il n'y a pas d'adresse) les données sont bidirectionnelles. Ceci est réalisé traditionnellement par des résistances de pull-up. Celles que l'on peut utiliser avec les contraintes dans les FPGA sont en général considérées comme trop grandes. Nous avons eu l'occasion de les utiliser avec la manette Nunchuk et un FPGA Xilinx sans problème réel de fonctionnement. D'autre part, nous avons fait fonctionner de l'i2c sans contrainte de pull-up dans les FPGA Altera.

En somme, il semble qu'il n'y a pas lieu de déclarer des entrées/sorties bidirectionnelles comme pull-up. Pour ceux qui désirent le faire sur Altera, Voir comment utiliser le "Pin Planer" ou l' "Assignement Editor" pour cela.

Programme d'essai modifier


#define TM1637_I2C_COMM1    0x40
#define TM1637_I2C_COMM2    0xC0
#define TM1637_I2C_COMM3    0x80
const uint8_t digitToSegment[] = {
 // XGFEDCBA
  0b00111111,    // 0
  0b00000110,    // 1
  0b01011011,    // 2
  0b01001111,    // 3
  0b01100110,    // 4
  0b01101101,    // 5
  0b01111101,    // 6
  0b00000111,    // 7
  0b01111111,    // 8
  0b01101111,    // 9
  0b01110111,    // A
  0b01111100,    // b
  0b00111001,    // C
  0b01011110,    // d
  0b01111001,    // E
  0b01110001     // F
  };
int main() {
  uint8_t i, t[4];
  uint16_t cmpt=0;
// init
  TWIInit();
  _delay_ms(10);
// loop
  while(1) {
    t[0] = digitToSegment[cmpt & 0x000F];
    t[1] = digitToSegment[(cmpt & 0x00F0)>>4];
    t[2] = digitToSegment[(cmpt & 0x0F00)>>8];
    t[3] = digitToSegment[(cmpt & 0xF000)>>12];
    // reinitialisation pour ecriture
    TWIStartWriteStop(TM1637_I2C_COMM1);
    TWIStartWrite(TM1637_I2C_COMM2);
    for (i=0;i<3;i++)
      TWIWrite(t[i]);
    TWIWriteStop(t[3]);
    TWIStartWriteStop(TM1637_I2C_COMM3 | 0x0F);
    PORTC=cmpt;
    _delay_ms(500);
    cmpt++;
  }
  return 0;
}

Les sous-programmes manquants sont présentés dans la partie i2c de ce chapitre.

Plutôt que de rendre ce programme complètement fonctionnel, nous préférons nous intéresser à une réalisation purement VHDL intégrée dans notre processeur.

Autre réalisation matérielle modifier

Nous avons décidé de nous intéresser à ce qui a été fait comme réalisation matérielle par d'autres auteurs. github tm1637 pour FPGA (VHDL) semble un bon point de départ.

Nous redonnons le code ici car il a été très légèrement modifié :

Nous allons tout simplement maintenant fournir les données 16 bits nécessaire au bon fonctionnement de l'afficheur à l'aide du processeur. Bien sûr il nous faudra deux registres pour cela. Nous avons choisi ADCH et ADCL pour cela. Leur câblage peut sembles un peu étrange mais est lié à l'ordre d'affichage des 4 digits qui est fixé pas le circuit TM1637.

Il ne vous reste qu'à sortir les SDA et SCL de ce module jusqu'au processeur et à réaliser un programme de fonctionnement. En voici un exemple :

#include <avr/io.h>
//#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
int main() {
  uint16_t cmpt=0;
// init
  _delay_ms(10);
// loop
  while(1) {
    PORTC=cmpt;
    _delay_ms(100);
    cmpt++;
// on donne à manger au TM1737 :
    ADCL = cmpt;
    ADCH = (cmpt>>8);
  }
  return 0;
}

Voir aussi modifier

Étude du Shield LCD keypad modifier

Ce shield est décrit ICI. Il peur être trouvé pour environ 3,70  sur Internet (au moment de l'écriture de ces lignes).

  • Sa première particularité est qu'il doit être alimenté en 5V
  • Sa deuxième particularité est que les 5 boutons poussoirs sont reliés à une seule entrée qui devient donc analogique. La lecture de ces boutons nécessite donc un convertisseur analogique numérique. ATTENTION la tension analogique en question dépasse 3,3 V !!!

La question est donc de vérifier que malgré une alimentation à 5V, l'écran LCD fonctionne correctement. Pour ce faire, nous avons câblé le shield avec une carte MSP432 qui permet une alimentation du shield en 5V mais qui possède un processeur de type ARM qui fonctionne en 3,3 V. L'essai ayant été concluant, nous sommes prêt à tenter notre chance avec un FPGA. Nous allons utiliser la carte DE2-115 qui possède un connecteur d'extension sur lequel le 5V est disponible, ce qui n'est pas le cas pour les cartes de Digilent (les connecteurs PMod n'utilisent que du 3,3 V).

Le code utilisé a nécessité l'installation de la librairie LiquidCrystal avec Energia :

#include <LiquidCrystal.h>

LiquidCrystal lcd(6, 7, 2, 3, 4, 5);        // select the pins used on the LCD panel
// dans l'ordre P1.5 , P4.3, P4.1, P3.3, P3.2 P6.0
// reliés à :    9,    8,    7,    -6 ,  -5,  4 (repérés sur Arduino)
unsigned long tepTimer ;    

void setup(){ 
    lcd.begin(16, 2);                       // start the library
}

void loop(){ 
    lcd.setCursor(0, 0);                   // set the LCD cursor   position 
    int val;                               // variable to store the value coming from the analog pin 
    double data;                           // variable to store the temperature value coming from the conversion formula
    val=analogRead(A1);                     // read the analog in value:
    data = (double) val * (5/10.24);       // temperature conversion formula
    
    if(millis() - tepTimer > 500){         // output a temperature value per 500ms 
             tepTimer = millis();

             // print the results to the lcd
             lcd.print("T: ");               
             lcd.print(data);             
             lcd.print("C");              
     } 
}

Les paramètres de lcd sont les noms Energia de la carte MSP432.

Module VHDL seul modifier

 
Comment connecter une carte DE2-115 à un shield LCD

Nous allons essayer, dans cette section, d'utiliser un module VHDL touvé chez Opencores. Il se trouve dans Projets -> Other -> 16x2 LCD controller et a été réalisé par Daniel Drescher.

Nous rappelons que nous avons décidé d'utiliser la carte DE2-115 de Terasic équipée d'un Cyclone IV de chez Intel/Altera. Ceux qui connaissent la carte savent qu'elle est déjà équipée d'un affichage LCD. Mais nous allons en rajouter quand même un deuxième pour comprendre les problèmes à résoudre dans ce cas. En fait il n'y a aucun problème, le cœur trouvé sur Opencores.org fonctionne immédiatement. Reste quand même à comprendre un peu son fonctionnement pour aller plus loin.

Voici en résumé les connexions que l'on a réalisé :

LCD en rs d(7) d(6) d(5) d(4)
Arduino Pin 9 8 7 6 5 4
DE2-115 GPIO[13] GPIO[11] GPIO[7] GPIO[5] GPIO[3] GPIO[1]

Les numéros des broches avec la convention Arduino sont données ici pour se repérer pour les broches de Shield. Pour celui qui n'a jamais utilisé d'Arduino, espérons que le schéma suffira.

Le ficher de contraintes correspondant à l'image ci-contre est donné au format CSV :

To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved
CLK,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL,
lcd_e,Output,PIN_AF15,4,B4_N2,3.3-V LVTTL,
lcd_rs,Output,PIN_AF16,4,B4_N2,3.3-V LVTTL,
lcd_db[7],Output,PIN_AE16,4,B4_N2,3.3-V LVTTL,
lcd_db[6],Output,PIN_Y16,4,B4_N0,3.3-V LVTTL,
lcd_db[5],Output,PIN_Y17,4,B4_N0,3.3-V LVTTL,
lcd_db[4],Output,PIN_AC15,4,B4_N2,3.3-V LVTTL,

Adapter ceci pour une autre carte est relativement simple. Il suffit de changer le fichier de contraintes.

Reste cependant à répondre à une question : peut-on directement mettre ce shield sur la carte DE0-nano SOC qui possède un connecteur pour Shield de type Arduino ? La réponse est : affaire à suivre. Le problème est lié à la façon dont sont câblés les boutons poussoirs : quand aucun bouton poussoir n'est appuyé, vous vous trouvez avec 5V sur la broche A0 de l'Arduino, qui dans le cas de la carte en question est relié à un convertisseur analogique numérique extérieur au FPGA, mais qui semble ne supporter que 4,096 V (tension de référence par défaut). Le circuit utilisé pour la conversion analogique numérique est un LTC2308. Le parcourt rapide de sa documentation (du LTC2308) m'a permis de déterminer que son alimentation est en 5V, mais pas si ces 5V peuvent être utilisés en tension de référence. Donc personnellement je n'utiliserai pas ce shield directement sur la DE0-nano SOC avant d'avoir lu une documentation plus complète. Mais vous pouvez utiliser ce shield avec de simples fils, un peu comme sur la figure, en laissant tomber les boutons pour le moment.

Exercice 1 modifier

On vous demande de changer l'affichage réalisé par le module de chez Opencores.org. Vous pourrez y afficher ce que vous désirez, le but étant de vous montrer comment les choses se passent.

Câbler le shield directement sur un PORT modifier

En général l'utilisation d'un afficheur LCD avec un microcontrôleur se fait naturellement de cette manière. Dans un FPGA, il est possible de faire autrement mais il est bon de commencer par le plus utilisé. Le processeur enfoui que l'on utilisera sera un ATTiny861.

Si vous avez réalisé l'exercice 1, vous avez compris que la gestion des deux lignes de 16 caractères nécessite 32 octets, soit 32 PORTs... et ceci est un problème car c'est ce que possède le Tiny... et si on les utilise tous ici, on ne pourra plus faire grand chose d'autre ! Il nous faudra trouver donc une autre solution.

Dans ce qui suit nous allons montrer encore une fois comment on peut libérer des ressources de code en réalisant un périphérique, d'abord très simplifié, puis général. Mais pour commencer, nous allons montrer d'abord qu'un simple affichage peut être réalisé en interfaçant directement l'afficheur LCD sans passer par la partie matérielle de l'exercice 1.

Exercice 2 modifier

Réaliser une interface directe de l'afficheur à un Tiny861 et montrer qu'un texte général peut être affiché.

Indication : on adaptera le code C ci-dessous au matériel.

//      lcd.c
//      
//      Copyright 2014 Michel Doussot <michel@mustafar>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.


#include <avr/io.h>
#define RS 		0x04
#define E		0x02
#define RW 		0x08
#define DATAS  	0xF0

// unite approximative 2us
void delai(unsigned long int delai) {
	volatile long int i=0;
	for(i=0;i<delai;i+=1);
} 

// portB :
// b7 b6 b5 b4 b3 b2 b1 b0
// D3 D2 D1 D0 RW RS  E CE0
void rs_haut(void) {
   PORTB = PORTB | RS;
}

void rs_bas(void) {
   PORTB = PORTB & ~RS; 
}

void rw_haut(void) {
   PORTB = PORTB | RW;
}

void rw_bas(void) {
   PORTB = PORTB & ~RW; 
}

void e_haut(void) {
   PORTB = PORTB | E;
   delai(8);
}

void e_bas(void) {
    PORTB = PORTB & ~E; 
    delai(8);
}

void e_puls(void) {
   e_haut();
   e_bas();
}

void ecris_4(unsigned char valeur) {
	unsigned char v;
	v = (valeur << 4) & DATAS;
	PORTB = PORTB & ~DATAS ;
	PORTB = PORTB | v ;
    e_puls();
}

void ecris_8(unsigned char valeur) {
	unsigned char v;
	v = valeur & DATAS;
	PORTB = PORTB & ~DATAS ;
	PORTB = PORTB | v ;
    e_puls();
	v = (valeur << 4) & DATAS;
	PORTB = PORTB & ~DATAS ;
	PORTB = PORTB | v ;
    e_puls();    
}


void setup() {

   PORTB = 0;
   delai(6000);
   ecris_4(0x03);
   delai(1600);
   ecris_4(0x03);
   delai(800);
   ecris_4(0x03);
   delai(800);
   ecris_4(0x02);
   delai(40);
   ecris_4(0x02);
   ecris_4(0x08);
   delai(40);
   ecris_4(0x00);
   ecris_4(0x06);
   delai(40);
   ecris_4(0x00);
   ecris_4(0x0C);
   delai(40);
   ecris_4(0x00);
   ecris_4(0x01);
   delai(800);   
}

void writecar(char car) {
	rs_haut();
	ecris_8((unsigned char)car);
}

void writestr(char *chaine) {
	rs_haut();
	while (*chaine) {
		ecris_8((unsigned char)*chaine++);
	}
}

Le point important de ce code par rapport à tout ce qui a été vu jusqu'ici est l'apparition de lignes comme PORTB = PORTB | ... qui montrent que PORTB doit être en entrée et en sortie.

Il est possible de continuer à faire évoluer la librairie d'affichage à l'infini pour une utilisation plus simple. Mais nous allons maintenant vraiment nous intéresser à la réalisation d'un périphérique capable d'afficher. Pour commencer nous allons abandonner le caractère généraliste de l'afficheur pour nous cantonner à un affichage très spécialisé.

Exercice 3 modifier

Pouvez-vous imaginer et réaliser une interface (même partielle) entre le fichier « lcd16x2_ctrl.vhd » et notre Tiny861 pour au moins gérer un affichage des deux chiffres d'un compteur BCD. Évidemment on laissera tomber le fichier « lcd16x2_ctrl_demo.vhd » qui n'est là que pour montrer comment l'ensemble fonctionne.

Indications :

  • le Tiny861 sur carte Altera possède déjà son chapitre de Travaux Pratiques.
  • le compteur BCD est réalisé en C dans le processeur... Il est sur 8 bits et les 4 bits de poids faibles représentent les unités tandis que les 4 bits de poids forts représentent les dizaines. Ce que l'on veut donc c'est qu'une instruction de type PORTX=0x23 affiche directement 23 quelque part sur votre afficheur LCD.

Exercice 4 modifier

Nous avons comme objectif d'utiliser deux FIFO : un par ligne à afficher. L'avantage est une utilisation de deux PORTs seulement, un par FIFO. L'inconvénient est qu'il va falloir bâtir un séquenceur capable de vider les FIFO pour réaliser l'affichage des lignes.

Réflexion :

  • l'utilisation de deux PORTs comme indiqué dans l'énoncé peut n'être qu'une assertion de principe ! On peut utiliser un troisième PORT pour communiquer avec le séquenceur puisqu'il en faut un. Par exemple, utiliser le bit B0 de poids faible d'un troisième registre est possible : on le met à 1 pour dire au périphérique : ça y est les deux FIFO sont prêts à être utilisés pour être affichés, à toi de te débrouiller. On admet bien sûr que le processeur et son périphérique se tutoient.
  • on peut aussi n'utiliser que deux PORTs et faire partir le séquenceur systématiquement quand les deux FIFO sont pleins

1°) Avant de mettre au point l'utilisation de deux lignes, nous allons nous contenter de la première ligne. Nous allons aussi utiliser un seul PORT qui écrira dans le FIFO. C'est donc le signal FIFO plein qui fera partir le séquenceur.

Indications :

 
Ecriture dans un FIFO par l'intermédiaire d'un PORT
  • La partie donnée dans la figure ci-contre concerne l'écriture dans un FIFO. On rappelle qu'un FIFO est un registre dans lequel on écrit des données les unes à la suite des autres. La première donnée écrite sera la première donnée pouvant être lue. Si vous réalisez ce qui est en rouge dans la figure, vous aurez un FIFO complètement fonctionnel en écriture. Vous faites un programme qui écrit deux valeurs dans PORTB et elles partiront toutes les deux dans le FIFO. Si vous n'avez que cette écriture de disponible, vous ne pourrez jamais rien faire des valeurs qui sont dans le FIFO. Remarquez aussi que l'on a gardé la partie de l'exercice 3 qui affiche deux chiffres décimaux en deuxième ligne. Il faut compléter cette figure par un séquenceur destiné à lire les données disponibles dans le FIFO. Avant de vous attaquer à cette autre partie du problème, posez-vous un peu et essayez de bien comprendre ce qui est fait : c'est la partie la plus simple du problème que vous avez sous les yeux.
  • Le séquenceur qui lit les 16 données du FIFO comporte 32 états. Il faut deux états pour lire une donnée car un état permet d'écrire dans line1_buffer tandis que l'autre dit au FIFO qu'une de ses données est lue. Pour ce séquenceur, seul le démarrage est conditionné à "full" qui signifie que le FIFO est rempli. Pour tous les autres états, il n'y a aucune condition de passage à l'état suivant, ..., puis à la fin on revient à l'état initial. Ce séquenceur est donc facile à écrire mais long (toutes les 32 transitions sont à réaliser dans un grand case when).
 
Description d'un séquenceur pour lire le FIFO
  • Si le séquenceur est réalisé avec un compteur, cela permet de remplacer le grand case when par une simple incrémentation. Nous pouvons choisir un compteur de 5 bits : les 4 bits de poids fort feront le compteur jusqu'à 16 pour vider le FIFO tandis que le bit de poids faible fera le signal de lecture du FIFO. En effet après lecture d'un FIFO, il faut lui dire que sa donnée a été lue pour qu'il prépare la suivante.
  • Le problème du signal FIFO plein est qu'il cesse d'être à 1 dès la première lecture. Vous allez utiliser ce signal et le mémoriser pendant tout le temps nécessaire à vider ce FIFO. C'est probablement là que réside la plus grande difficulté de conception de notre module. Prenez donc le temps de méditer cette figure. Nous avons dessiné des rectangles en pointillés parce qu'ils représentent des process (pour éviter les PORT MAP). Le process de comptage n'a rien de difficile. Le process qui maintien s_en_d à 1 tant que la lecture du FIFO n'est pas terminée n'est pas simple (enfin tout est relatif). Votre réflexion doit se faire en vous posant les questions :
    • quelle condition fait passer s_en_d à 1 ? Réponse le passage de full à 1
    • quelle condition fait passer s_en_d à 0 ? Réponse quand le compteur est arrivé au bout du comptage
    • que se passe-t-il en dehors de ces conditions ? Réponse un simple maintien (mémorisation).
  • Si les indications ci-dessus vous semblent insuffisantes, une autre version de la question 1°) plus conventionnelle est donnée un peu plus bas : question 1°-bis)
  • Le FIFO est donné ci-dessous

Et maintenant voici la solution complète :

 
Autre description d'un séquenceur pour lire le FIFO

1°-bis) La façon de faire de la question 1°) est intéressante mais assez difficile pour des étudiants non spécialistes. La réalisation d'un séquenceur avec un compteur demande une réflexion importante et assez inhabituelle : les séquenceurs sont plutôt réalisés par des machines d'états. Lorsque nous avons décidé nous-même de donner cet exercice en examen à l'UTT, nous avons choisi de réaliser le séquenceur de manière plus classique comme le montre le schéma ci-contre.

2°) Réaliser l'interface complète des deux lignes.

3°) La façon de faire des deux questions précédentes consiste à prendre un module tout fait et à l'adapter à notre processeur en réalisant le moins de modifications possibles. Il serait plus confortable de réaliser une partie matérielle capable de reprendre les principales actions réalisées par la librairie C et de les implanter dans le matériel. Cela permettrait aux utilisateurs qui ont l'habitude d'utiliser une telle librairie de retrouver un peu leurs marques. Mais ce travail consiste alors, à ne prendre dans le module "lcd16x2_ctrl.vhd" que les parties intéressantes. Ceci nécessite donc beaucoup de temps : Avis aux amateurs.

Indications : Pour mieux comprendre le texte de la question donnons un peu plus de détails.

  • en ce qui concerne l'initialisation, faut-il la rendre accessible par le processeur ou la laisser complètement indépendante ?
  • réaliser le transfert 8 bits dans l'afficheur
  • distinguer l'envoi d'une commande de l'envoi d'une donnée. Ceci peut être fait à l'aide de deux registres/PORTs :
    • l'envoi d'une donnée se fait dans le PORTA (par exemple)
    • l'envoi d'une commande se fait par le PORTB (par exemple)
  • cette façon de faire ne nécessite pas obligatoirement de FIFO mais probablement un bit en lecture pour dire que l'envoi de donnée ou la commande est terminée. Certaines commandes sont relativement longues, comme l'effacement de l'écran.

4°) Encore plus difficile : réaliser un coprocesseur qui est capable d'exécuter certaines routines. Il faut alors prévoir un mécanisme de communication entre le Tiny861 et le coprocesseur pour que le premier puisse demander au deuxième de réaliser une routine précise.

Voir aussi modifier

Tourelle pan/tilt et sonar modifier

Petit rappel avant de commencer :

  • le mot anglais pan signifie faire un panoramique horizontal
  • le mot anglais tilt signifie incliner

Ce que nous allons traiter dans cette section n'est pas à proprement parler un shield. Mais nous avions prévenu : nous traitons dans ce chapitre de shields au sens large, c'est-à-dire tout ce qui peut être connecté à un Arduino en quelques secondes.

Ce sujet a été donné comme projet à des étudiants en 2016/2017. Il a consisté à réaliser une carte d'extension de la carte DE2-115 sur laquelle se trouvent une tourelle pan/tilt commandée par deux servomoteurs et un sonar HC-SR04. L'idée est d'explorer un peu le problème pour une éventuelle future utilisation en robotique mobile.

Présentation de la carte d'extension modifier

Nous avons imposé aussi une carte MSP432 sur l'ensemble ainsi qu'un connecteur pour la manette Nunchuk déjà évoquée plusieurs fois dans ce livre.

Pour vous faire une idée plus précise, le compte-rendu de réalisation de deux binômes est disponible sur Internet dans le Wiki de l'IUT de troyes :

Commande des servomoteurs modifier

La commande de plusieurs servomoteurs n'est pas un problème dans un FPGA. Vous trouverez facilement du code VHDL pour cela. Nous n'avons pas choisi le code le plus compact mais l'essentiel est qu'il fonctionne :

Exercice 1

Réaliser un code VHDL qui utilise le fichier "servo.vhd" du ZIP ci-dessus ainsi que tous ses sous ensembles (servoclock.vhd, servotiming.vhd et servounit.vhd). On n'utilisera aucun processeur mais seulement un compteur 31 bits en lui prenant les 8 bits de poids fort pour réaliser la commande de servo. Autrement dit on fait un balayage horizontal en une quarantaine de secondes.

Lecture du sonar modifier

Nous utilisons le célèbre sonar HC-SR04 qui peut être trouvé pour moins de 2  assez facilement. Nous allons partir du code VHDL de Mike Field (Ce lien n'est plus disponible !!! Mais une partie du code peut être trouvée dans le compte-rendu cité ci-dessus). Ce lien semble indiquer qu'il fait fonctionner le HR-SR04 en 3,3 V puisqu'il n'y a pas de 5V sur les PMods ! Nous avons essayé en 3,3 V absolument sans succès, ce qui est parfaitement conforme à la documentation (alimentation entre 4,5 V et 5,5 V).

Nous l'avons fait fonctionner cependant en 5V d'alimentation et sans convertisseur de niveaux logiques, puis avec, pour des essais plus longs. En effet, la broche "echo" délivre du 5V ce qui n'est pas conseillé pour un FPGA. Ces essais montrent qu'il est possible de commander "Trig" en 3,3 V. La boche "echo" étant une entrée (pour le FPGA) un simple diviseur de tension avec des résistances 20k/10k est suffisant.

Tout ceci nous amène à envisager une utilisation avec les cartes terasic qui ont du 5V sur leurs connecteurs.

Exercice 2 : adaptation du code pour une de2-115 modifier

1°) Le code de Mike Field devra être changé :

  • utilisation en 50 MHz au lieu des 100 MHz de la Basys 3
  • les afficheurs de la de2-115 ne sont pas multiplexés contrairement à ceux de la Basys 3
  • remplacement des types "unsigned" par des "std_logic_vector" et changer les librairies en conséquence

Ce dernier point n'est pas obligatoire mais nos étudiants de niveau 15 ne connaissent que les "std_logic_vector" et nous n'avons pas l'intention de leur enseigner plus sur le sujet.

Pour faire ces modifications vous pouvez repérer les deux process qui gèrent l'affichage et les commenter et les remplacer par un câblage du composant de l'exercice 2 du Corrigé du TP1. Ce composant nécessite bien sûr lui-même, le corrigé de l'exercice 1.

Avec ces petites modifications le code de Mike Field est parfaitement fonctionnel. Nous ne mettons pas la correction à disposition pour le moment car il faut bien laisser chercher nos étudiants.

Les contraintes pour la carte de TO/Baczkowski sont :

To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved
MCLK,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL,
Unit7segs[6],Output,PIN_H22,6,B6_N0,2.5 V,
Unit7segs[5],Output,PIN_J22,6,B6_N0,2.5 V,
Unit7segs[4],Output,PIN_L25,6,B6_N1,2.5 V,
Unit7segs[3],Output,PIN_L26,6,B6_N1,2.5 V,
Unit7segs[2],Output,PIN_E17,7,B7_N2,2.5 V,
Unit7segs[1],Output,PIN_F22,7,B7_N0,2.5 V,
Unit7segs[0],Output,PIN_G18,7,B7_N2,2.5 V,
Diz7segs[6],Output,PIN_U24,5,B5_N0,2.5 V,
Diz7segs[5],Output,PIN_U23,5,B5_N1,2.5 V,
Diz7segs[4],Output,PIN_W25,5,B5_N1,2.5 V,
Diz7segs[3],Output,PIN_W22,5,B5_N0,2.5 V,
Diz7segs[2],Output,PIN_W21,5,B5_N1,2.5 V,
Diz7segs[1],Output,PIN_Y22,5,B5_N0,2.5 V,
Diz7segs[0],Output,PIN_M24,6,B6_N2,2.5 V,
sonar_trig,Output,PIN_AE16,4,B4_N2,3.3-V LVTTL,
sonar_echo,Input,PIN_Y16,4,B4_N0,3.3-V LVTTL,

2°) Le code de Mike Field affiche incorrectement après 99. Pouvez-vous le modifier pour avoir 3 digits d'affichage en utilisant des compteurs BCD cascadés.

Utiliser un processeur Tiny861 modifier

Pour coordonner l'ensemble du travail de balayage de la tourelle pan/tilt et la lecture du sonar, nous avons décidé d'utiliser un processeur. Comme d'habitude, ce processeur sera enfoui dans le FPGA.

Nous avions comme objectif initial de réaliser un affichage sur écran VGA en niveaux de gris de profondeur à partir des données du sonar. Mais, les étudiants comme le tuteur ayant pris du retard nous nous rabattons sur un affichage de chacune des lignes horizontales balayées sur un afficheur lcd (sous forme de barregraphe). Cet afficheur est présent sur la carte DE2-115 que nous utilisons. Si vous n'en possédez pas, nous avons déjà présenté dans ce chapitre comment en utiliser un externe et très bon marché.

Schéma du montage réalisé modifier

Si l'on veut connaître comment programmer notre Tiny, il nous faut savoir comment les périphériques sont connectés au processeur. C'est l'objet de la figure ci-contre (à venir si elle n'est pas encore là) où l'on discerne :

  • l'utilisation du PORTB pour la gestion de l'afficheur lcd
    • b7 .. b4 seront les données sur 4 bits
    • b3 sera RW
    • b2 sera RS
    • b1 sera E
  • l'utilisation du PORTA pour sortir sur des LEDs
  • l'utilisation de DDRA pour commander la position horizontale de la tourelle (pan)
  • l'utilisation de DDRB pour commander la position verticale de la tourelle (tilt)
  • l'utilisation de PINB en lecture pour lire la distance en cm
  • l'utilisation de DDRB en lecture pour avoir en poids faible le status du sonar

Ressource modifier

Nous vous donnons la ressource de départ sur notre site :

Tout y est réalisé pour être conforme au schéma que l'on vient de présenter.

  L'hébergement de mon site perso se termine le 5 septembre 2023 ! A ce jour je n'ai pas encore décidé comment je vais gérer ce problème dont je ne suis pas à l'origine. Il en résulte que le lien panTiltSonarTiny.zip et l'ensemble des corrections qui utilisent mon site perso seront indisponibles à partir de cette date pour tout ce chapitre. SergeMoutou (discuter)



Code de départ modifier

Le code de départ est donné pour que vous n'ayez pas tout à faire. Ce code vous montre comment créer un caractère spécifique pour l'afficheur LCD et aussi comment l'utiliser.

Tout le monde aura reconnu un cœur comme caractère spécial qui semble indiquer notre préférence entre Linux et Windows.

Code final modifier

L'objectif de ce code est de gérer :

  • le balayage horizontal
  • le balayage vertical
  • de synchroniser les deux balayages
  • de gérer le sonar
  • de créer les nouveaux caractères du barregraphe
  • d'afficher les résultats sur l'écran LCD

Utilisation d'un capteur de couleur TCS3200 modifier

 
Le capteur de lumière TCS3200 sur une carte

Le composant TCS3200 est un capteur de couleur qui se trouve facilement tout monté sur une petite carte avec 4 leds pour moins de 2  (en 2018). Nous avons décidé d'examiner la possibilité d'utilisation de ce type de capteurs pour un suivi de lignes en robotique.

Ce capteur est composé de 8x8 photodiodes :

  • 16 sont précédées par un filtre qui enlève le bleu
  • 16 sont précédées par un filtre qui enlève le rouge
  • 16 sont précédées par un filtre qui enlève le vert
  • 16 ne sont pas filtrées

Il sort de ces photodiodes un courant qui est converti intérieurement en fréquence. En clair ce que nous avons à mesurer est une fréquence ou une période.

Fonctionnement S0, S1
S0 S1 Echelle de sortie de fréquence
0 0 Éteint
0 1 2%
1 0 20%
1 1 100%

Nous avons décidé d'utiliser le mode 100% pour nos premiers essais. S0 et S1 seront donc positionnés à 1 par l'Arduino ou le FPGA.

Fonctionnement S2, S3
S2 S3 Type de filtre lumineux
0 0 Rouge
0 1 Bleu
1 0 Pas de filtre
1 1 Vert

Nous avons décidé d'utiliser le mode sans filtre de couleur pour nos premiers essais.

Première utilisation avec un Arduino modifier

Ce capteur supporte des tensions d'alimentation allant de 2,5 V à 5,5 V. Il peut donc être utilisé avec un Arduino sans problème. Nous avons facilement trouvé un petit programme sur Internet que nous avons modifié

#define S0 4
#define S1 5
#define S2 6
#define S3 7
#define sensorOut 8
int frequency = 0;
void setup() {
  pinMode(S0, OUTPUT);
  pinMode(S1, OUTPUT);
  pinMode(S2, OUTPUT);
  pinMode(S3, OUTPUT);
  pinMode(sensorOut, INPUT);
  
  digitalWrite(S0,HIGH);
  //digitalWrite(S1,LOW);
  digitalWrite(S1,HIGH);
  
  Serial.begin(9600);
}
void loop() { 
  //digitalWrite(S2,LOW);
  digitalWrite(S2,HIGH);
  digitalWrite(S3,LOW);
  frequency = pulseIn(sensorOut, LOW);
  Serial.print("T= ");
  Serial.print(frequency);
  Serial.println("  ");
  delay(1000);
}

La lecture du code source montre comment tout cela est connecté.

Portage sur FPGA modifier

Nous avons utilisé une carte Nexys3 avec un processeur enfoui ATMega16 largement utilisé dans ce cours.

La partie matérielle est réalisée par le code suivant :

 
Connecter une carte TCS3200 à une Nexys 3 par le double PMod JA

Comme vous pouvez le remarquer dans ce code VHDL, nous avons décidé d'utiliser un petit filtrage sur les signaux envoyés par le capteur. (Pour tout vous dire nous n'avons pas essayé sans). C'est le module filter qui s'occupe de cela : il sort un 1 quand il y a au moins 8 1 de reçu consécutivement. Une fois le 1 sorti il faut huit 0 de suite pour le passer à 0.

L'autre module est destiné à la détection de fronts montants. Il sort deux signaux :

  • tick : qui sert à la gestion de la mise à zéro du timer et à la sauvegarde des valeurs intéressantes dans un registre 8 bits.
  • en_timer qui sert à autoriser le comptage du timer

Le module complet met en série ces deux modules et gère un timer.

Ce module est donc connecté au SOC ATMega18 dans lequel tourne le programme suivant :

#include <avr/io.h>
#undef F_CPU
#define F_CPU 50000000UL
#include "util/delay.h"
int main() {
  uint8_t period;
// init
// _____________________________________
// | X | X | OE | S0 | S1 | X | S2 | S3 |
// _____________________________________
// | O | O | 0  | 1  | 1  | 1 | 1  | 0  | = 0x1E
// --------------------------------------
   PORTB = 0x1E;
  _delay_ms(10);
// loop
  while(1) {
    period = PINB;
    PORTC = period;
    _delay_ms(1000);
  }
  return 0;
}

Voici la connexion correspondante permettant d'expliquer le code (information identique à la figure ci-dessus) :

PORTB de ATMega16 X X OE S0 S1 X S2 S3
FPGA Pin (connecteur JA) NC NC JA<5> JA<4> JA<3> JA<2> JA<1> JA<0>
TCS3200 - - OE S0 S1 OUT S2 S3
  • X signifie que les deux bits de poids fort du PORTB n'agissent pas sur le capteur
  • NC désigne : Non Connecté
  • OUT est une sortie du capteur, donc une entrée du FPGA. Il est donc normal qu'elle ne soit pas présente sur le PORTB de l'ATMega16. C'est une entrée spécifique qui est utilisée directement dans le module pulsin.vhd
  • les - sont là car le capteur n'a que 6 broches pour être commandé et lu (plus les deux VCC et GND)

Voir aussi modifier

Utilisation d'une caméra OV7670 modifier

Les caméras OV7670 sont des caméras très bon marché : entre 5  et 8  sur Internet au moment de l'écriture de ces lignes. Elles sont configurables par un protocole très proche de l'i2c mais l'on doit récupérer soi-même les données du capteur par paquets de 8 bits.

Ce capteur vidéo n'est pas à proprement parler un shield. Il est d'autre part difficile à utiliser avec un Arduino dans sa version simple (sans mémoire externe). Il n'empêche qu'il est facile de trouver des vidéos et du code qui met en œuvre cette caméra OV7670avec des Arduino.

Une première utilisation modifier

Nous avons pris l'habitude dans ce chapitre de tester les shields avec des processeurs en 3,3 V. Nous allons déroger à la règle ici car ces processeurs ne sont pas très adaptés à des essais de caméras. Cela ne veut pas dire que ce n'est pas possible puisqu'il est facile de trouver sur internet des exemples avec un processeur 32 bits.

Nous allons plutôt essayer un code VHDL tout fait pour commencer : il s'agit de saisir une image et de l'envoyer directement sur un écran vidéo.

Un code de ce genre peut être trouvé sous le titre "Digital Camera: OV7670 CMOS camera + DE2-115 FPGA board + LandTiger 2.0 board". En bas de la page en question se trouvent les liens pour télécharger les diverses versions du projet. Nous avons testé la première version. Pour cela, nous avons bien sûr passé plus de temps à câbler la caméra qu'à essayer ce code qui est absolument fonctionnel tout de suite. En effet nous disposons d'une carte DE2-115.

Aucun autre commentaire à part que la caméra se branche évidemment sur le connecteur d'extension. Pour savoir comment, il vous faut comparer la documentation du connecteur avec les contraintes pour les diverses entrées sorties de la caméra. C'est long mais pas difficile.

Le premier auteur qui semble avoir réalisé un code VHDL pour cette caméra bon marché semble être Mike Field. Son site a changé ou disparu depuis l'écriture de ces lignes mais il est assez facile de retrouver son code repris par d'autres : dans ce Github par exemple.

Une application simple modifier

Le code de la section précédente est gourmand en mémoire interne au FPGA. Pour un cyclone IV 115 ce n'est pas un problème, mais nous avons l'intention d'utiliser des FPGA plus petits pour résoudre d'ailleurs des problèmes simples. Notre objectif est simplement de réaliser un suivi de lignes avec un Robot à l'aide de cette caméra.

!!!Doc en vrac à ne pas lire!!! modifier

Un problème de résolution de la caméra par rapport à la mémoire FPGA utilisée dans un autre projet a été réglé par le mail ci-dessous. La documentation de la caméra est tellement opaque que nous laissons ce mail ici en VRAC pour le moment :

Je ne sais pas si tu es toujours avec ta caméra ?

Peux-tu essayer de passer de registre d'adresse 3E à 0x19 pour changer la résolution pour être en accord avec ta mémoire. Autrement, pour faire encore plus petit, met-le à 0x1A
Cela se fait avec :

when x"05" => sreg <= x"3E19"; -- QVGA YUV tout petit x2
ou
when x"05" => sreg <= x"3E1A"; -- QQVGA YUV tout petit x4

dans le case address du fichier ov7670_registers.vhd

Le problème est qu'avec ces valeurs tu passes en mode YUV ce qui en principe n'est pas gênant pour faire du noir et blanc, mais peut te surprendre.

Autrement dans ov7670_capture.vhd je fabrique actuellement mon dout avec :

-- version 4 bits de niveaux de gris :
  dout <= d_latch(15 downto 12) & d_latch(10 downto 7) & d_latch(4 downto 1);
  -- version (8 bits ?) de niveaux de gris :
  --   dout <= "0000" & d_latch(10 downto 3);

Tu prends ce qui n'est pas commenté. Il me semble que les commentaires sont faux.

Pour faire ton seuillage, je pense que le mieux est de travailler dans RGB.vhd. Moi actuellement j'ai :
begin
-->> original :
--  R <= Din(11 downto & Din(11 downto when Nblank='1' else "00000000";
--  G <= Din(7 downto 4)  & Din(7 downto 4)  when Nblank='1' else "00000000";
--  B <= Din(3 downto 0)  & Din(3 downto 0)  when Nblank='1' else "00000000";
--<<
--> version 4 bits de niveaux de gris :
--  R <= Din(7 downto 4) & "0000" when Nblank='1' else "00000000";
--  G <= Din(7 downto 4)  & "0000"  when Nblank='1' else "00000000";
--  B <= Din(7 downto 4)  & "0000"  when Nblank='1' else "00000000";
--<<
--> version (8 bits ?) de niveaux de gris :
  R <= Din(7 downto 0) when Nblank='1' else "00000000";
  G <= Din(7 downto 0) when Nblank='1' else "00000000";
  B <= Din(7 downto 0) when Nblank='1' else "00000000";
--<<
end Behavioral;

A mon avis, pour le seuillage, il te faudra faire un truc du genre :

R <= Din(7) OR Din(6) OR Din(5) when Nblank='1' else '0';

avec un Din(4) en plus dans le OU, ou seulement Din(7) OR Din(6), c'est là que tu interviens ! et même chose pour G et B...

Bon WE
A+

Capteur de débit optique modifier

Les capteurs de débits optiques sont utilisés principalement dans les souris optiques. Ils sont donc devenus bon marché. Leur domaine d'application s'est récemment élargi aux Drones. On n'imagine pas, bien sûr, une optique à ras du sol pour le drone qui évolue en altitude : l'optique n'est donc certainement pas une optique de souris. On a donc développé des optiques autour des composants utilisés dans les souris. Voila ce que nous désirons étudier aujourd'hui.

Présentation de notre capteur modifier

 
Capteur ADNS 3080

Nous allons utiliser un capteur architecturé autour du circuit ADNS 3080. Ce circuit est capable de prendre une image de 30x30 pixels et de la comparer à l'image précédente pour en déduire le déplacement. Ce calcul est réalisé avec un processeur embarqué de 32 bits.

La photo ci-contre présente le capteur seul. Mais si vous voulez voir à quoi ressemble le capteur complet cliquez ici. Ce composant associé à une optique peut être trouvé aux alentours de 7  (Oct. 2018).


Comme d'habitude nous allons commencer par faire fonctionner tout cela dans des microcontrôleurs 3,3 V. Nous allons utiliser la carte Tiva C series de Texas instrument.

Code de démarrage modifier

Nous avons trouvé sur Internet du code destiné à un Arduino. Il ne nous a pas été très difficile de le porter pour une utilisation avec Energia et notre ARM 32 bits.

TIVA C Pin GND NC 3,3 V PB5 PB6 PB7 PB4 PB3 NC NC
CJMCU GND 5V 3V NCS MISO MOSI SCLK RST NPD LED

Portage dans un FPGA modifier

 
Capteur de débit optique ADNS 3080 et carte FPGA

Le FPGA choisi est un FPGA spartan6 LX9 monté sur une carte achetée en Chine pour environ 20 . Elle nécessite naturellement d'avoir à disposition un programmateur Xilinx

Partie matérielle modifier

L'interaction avec ce type de capteur est en SPI. Nous avons déjà utilisé le SPI mais plutôt en esclave. Le travail pour réaliser un maître SPI est déjà réalisé mais sera présenté un peu plus loin.

La gestion du SPI sera donc réalisée comme d'habitude par notre SOC ATMega16.

La seule chose à faire est donc de porter une librairie quelconque qui fonctionne avec le SPI, ce que nous avons utilisé avec le microcontrôleur d'essai utilisé dans la section précédente.

Partie logicielle modifier

Le code donné ici est complet pour capturer une image et l'envoyer à la vitesse de 19200 bauds (avec quartz 50MHz) par la liaison série. Ce que vous obtenez dans votre hyperterminal est assez difficile à interpréter mais vous voyez des changements avec la lumière.

La capture du déplacement est aussi fonctionnelle (rappelons que ce capteur est destiné aux souris informatiques). Elle nous servira certainement plus tard aux calculs de déplacement d'un robot mobile.

Voici une librairie en C :

Et voici maintenant un programme principal en C :



Module de communication radio nRF2401 modifier

Les modules radio nRF2401 peuvent être trouvés entre 1 et 2  pour les versions de base qui ont une antenne gravée sur le circuit. Cette antenne gravée limite bien sûr la portée, mais celle-ci est suffisante pour réaliser par exemple une télécommande de Robot mobile.

Nous allons partir d'un code VHDL sur Internet pour réaliser une communication à distance entre un FPGA et un microcontrôleur.

Essai sur microcontrôleur modifier

 
Module NRFl01 de Nordic Semiconductor

La carte NRF24L01 s'alimente obligatoirement en 3,3V. Pour ceux qui veulent utiliser des cartes Arduino, c'est naturellement possible à condition que ces cartes possèdent une sortie régulée en 3,3V. Ce n'est pas le cas de toutes les cartes Arduino.

Une fois trouvée cette alimentation en 3,3V, une bonne nouvelle est que les entrées acceptent le 5V de l'Arduino. En clair vous n'aurez aucun problème à utiliser ce module avec les Arduino 8/32 bits.

Essai avec un FPGA modifier

Aucun code fonctionnel n'est présenté pour le moment.

Voir aussi modifier

Robot auto équilibré (ou auto balancé) modifier

Nous avions annoncé dès le départ que le mot shield serait pris au sens large dans ce chapitre. Et bien là, vous ne serez pas déçu. Nous allons en effet terminer ce chapitre par un shield Aruino bien plus complexe que ceux qui précèdent puisqu'il s'agit d'un robot complet. Pourquoi le classer dans ce chapitre ? Tout simplement parceque pour le réaliser, nous sommes parti d'un kit pour Arduino et avons remplacé l'Arduino par un FPGA.

Présentation du kit modifier

Le sainsmart balancing robot kit est un kit dont le prix varie fortement. Nous l'avions acheté pour 100  il y a deux ans et fini par le rendre rendre stable avec une troisième roue (roue castor). Pour tenter notre chance une deuxième fois avec l'auto-équilibre, nous en avons acheté un nouveau à 80  chez le même fournisseur et pour une version un peu améliorée par rapport à l'ancienne.

Du kit, nous n'utiliserons absolument pas la partie commande architecturée autour d'une carte Arduino UNO puisque nous avons l'intention de la remplacer par une carte FPGA que nous allons présenter maintenant.

FPGA : une carte Mojo pour tout commander modifier

La carte FPGA MOJO est suffisamment particulière pour être présentée brièvement. Elle est architecturée autour d'un FPGA Spartan6 XC6SLX9 et d'un microcontrôleur ATMega32U4. Ce microcontrôleur 32U4 est celui qui se trouve dans la carte Arduino Leonardo. Le rôle du microcontrôleur est de :

  • charger les programmes (*.bit) dans le FPGA à l'aide d'un outil spécifique sur le PC (MOJO Loader) écrit en Java. La destination peut être soit dans le FPGA directement, soit dans une Flash qui sera alors automatiquement chargée dans le FPGA lors d'une mise sous tension.
  • gérer le transfert de données du FPGA vers le 32U4 par une liaison série (TTL 3,3 V) et le transformer en USB, ce qui était en général réalisé par les circuits spécialisés FTDI. Nous parlons au passé car les platines Arduino Leonardo utilisent maintenant le 32U4 pour ce travail.
  • gérer le transfert de données des convertisseurs analogiques numériques vers le FPGA en utilisant le protocole SPI.

Mais pour interfacer cette carte qui fonctionne en 3,3 V avec la puissance qui demande 5V pour les signaux de commande ainsi que le capteur, il nous faut réaliser une carte de commande que nous allons présenter maintenant.

Processeur embarqué dans le FPGA et périphériques modifier

L'utilisation d'un accéléromètre MPU6050 qui fonctionne en I2C nous impose d'office à choisir une version améliorée de l'ATMega16, celle qui est capable de gérer I2C (présentée dans un autre chapitre de ce livre).

Nous avons d'autre part rencontré beaucoup de problèmes difficilement résolubles à l'aide de nos 8 LEDs. Il nous faut forcément développer une interface série pour pouvoir suivre ce qui se passe entre le capteur et la commande de moteur. Deux possibilités s'offrent à nous :

  • utiliser un convertisseur RS232 TTL 5V/3,3 V vers USB (disponible pour quelques Euros)
  • utiliser l'ATMega 32U4 présent sur la carte MOJO pour faire ce travail

Nous avons acheté un convertisseur mais pendant les quelques jours d'attente pour sa réception, nous avons réalisé une version de notre ATMega16 qui communique parfaitement avec l'ATMega32U4. C'est ce que nous allons présenter maintenant.

État des lieux pour la liaison série modifier

Le processeur embarqué ATMega16 possède depuis le début une liaison série. Celle-ci peut être utilisée sans problème avec le fameux convertisseur dont nous venons de parler. Quelques essais nous ont obligé cependant à renoncer à son utilisation pour communiquer avec l'ATMega32U4. En effet cette communication est un peu particulière ... et pas trop documentée. Mais il existe un programme VHDL qui réalise un écho sur cette liaison et qui est fonctionnel. Partir d'un programme fonctionnel pour l'interfacer à notre processeur embarqué c'est toujours plus simple que de modifier un programme VHDL qui ne fonctionne pas. Nous avons donc décidé de faire une deuxième liaison série pour la transmission de données du processeur embarqué vers le processeur 32U4.

Réalisation d'une deuxième transmission série modifier

Comme promis, nous allons partir d'une partir d'une version VHDL qu'il est possible de trouver dans Mojo-Base-VHDL-1.1.zip. Essayez-la et vous verrez que la partie gestion de la liaison série fonctionne parfaitement en écho. Il est naturellement possible d'envisager d'interfacer tout ce code au processeur embarqué mais nous préférerons n'utiliser que le module "serial_tx.vhd". Ce module est suffisamment court pour être donné complètement maintenant :

Maintenant, il nous faut interfacer ce code au processeur. Comme d'habitude tout se passe dans le fichier "ioi2c.vhd" qui est donné maintenant :

Réalisation d'une commande des moteurs modifier

La partie puissance de commande des moteurs est réalisée par une carte bon marché architecturée autour du L298. Pour réaliser ce type de commande dans un autre projet, nous avions utilisé les modules PMOD HB5 de chez Digilent ainsi que le code VHDL associé. Ce VHDL n'est pas tout à fait approprié car il n'est pas capable de gérer le mode freinage des moteurs lors d'un changement de direction. Par paresse, nous l'avons quand même utilisé ce qui a abouti à la destruction de deux modules L298N !

Dans ce cas des décisions énergiques s'imposent : nous avons donc décidé d'écrire nos propres modules de commande. Nous avons gardé la bonne idée de l'ancien code : surveiller les changements de directions des moteurs et réaliser un freinage (matériel). Cela permettra d'utiliser cette carte avec des étudiants sans leur demander de gérer ces problèmes par programmation.

Voici donc le code correspondant :

Utilisation du MPU6050 modifier

Le MPU6050 est un accéléromètre gyroscope (6 axes) relativement bon marché : il peut être trouvé entre 1  et 2  assez facilement.

Il existe une bibliothèque complète de gestion du MPU6050 pour Arduino. Elle est composée, entre autre, de deux fichiers principaux :

  • MPU6050.h
  • MPU6050.cpp

qui ont été développés par Jeff Rowberg <jeff@rowberg.net>. Des exemples d'utilisation supplémentaires peuvent aussi être trouvés.

Nous allons nous contenter dans cette section d'une bibliothèque beaucoup plus simple.

Initialisation modifier

La communication entre le processeur embarqué et le MPU6050 utilise l'i2c. C'est d'ailleurs une raison pour laquelle on n'utilise pas la librairie de Jeff Rowberg car notre processeur embarqué n'est pas compatible à 100% avec le périphérique i2c de l'ATMega328p.

  // initialisation i2c
  TWIInit();
  _delay_us(500);
  TWIStartWrite(MPU<<1);
  TWIWrite(0x6B);
  TWIWriteStop(0x00);

Il s'agit donc d'envoyer dans le registre d'adresse 0x6B la valeur 0x00. En effet le registre 0x6B s'appelle PWR_MGMT_1 et possède un bit particulier (b6) appelé SLEEP qui est initialisé à 1 à la mise sous tension et que l'on doit passer à 0 pour mettre le MPU6050 en marche en le sortant de son mode de veille.

Reste maintenant à configurer les sensibilités nécessaires à votre cahier des charges.

Accéléromètre modifier

Il existe plusieurs sensibilités de mesures de l'accélération. Pour la choisir il faut écrire dans le registre d'adresse 0x1C (appelé ACCEL_CONFIG) les valeurs suivantes dans les bits ACCEL_CONFIG[4:3] appelés AFS_SEL[1:0]

Table de configuration
AFS_SEL[1:0] Valeur pleine échelle
0 +/- 2g
1 +/- 4g
2 +/- 8g
3 +/- 16g

Dans le cas d'un choix à 0, la mesure sera donnée avec 16384/g. Autrement dit, une mesure d'un g donnera 16384 tandis que deux g donnera 32768. Ceci montre que les données sont sur 16 bits et elles sont signées. Le type C pour les récupérer est donc soit "int" soit "int16_t".

Voici donc le code associé à l'initialisation de l'accéléromètre :

// setting the accelerometer to +/- 2g
  TWIStartWrite(MPU<<1);
  TWIWrite(0x1C);
  TWIWriteStop(0x00);

Gyroscope modifier

Il existe aussi plusieurs calibres (sensibilités) de mesures de la vitesse de rotation. Pour le choisir il faut écrire dans le registre d'adresse 0x1B (appelé GYRO_CONFIG) les valeurs suivantes dans les bits GYRO_CONFIG[4:3] appelés FS_SEL[1:0]

Table de configuration
FS_SEL[1:0] Valeur pleine échelle
0 +/- 250 degrés/s
1 +/- 500 degrés/s
2 +/- 1000 degrés/s
3 +/- 2000 degrés/s

Pour FS_SEL=0, les valeurs retournées sont de 131 par °/s. En clair, la valeur maximale de 250°/s donnera 131*250 = 32750.

Voici donc notre code d'initialisation :

// setting the gyro to full scale +/- 250 °/s
  TWIStartWrite(MPU<<1);
  TWIWrite(0x1B);
  TWIWriteStop(0x00);

Lecture des données modifier

Voici les registres qui vont vous donner les valeurs de l'accélération :

Les données d'accélération
Registre(HEX) Registre(DEC) Données
3B 59 ACCEL_XOUT[15:8]
3C 60 ACCEL_XOUT[7:0]
3D 61 ACCEL_YOUT[15:8]
3E 62 ACCEL_YOUT[7:0]
3F 63 ACCEL_ZOUT[15:8]
40 64 ACCEL_ZOUT[7:0]

Voici les registres qui vont vous donner les valeurs de la température :

Les valeurs de la température
Registre(HEX) Registre(DEC) Données
41 65 TEMP_OUT[15:8]
42 66 TEMP_OUT[7:0]

Bien sûr il n'est pas nécessaire de lire la température mais les ingénieurs ont décidé que celle-ci serait située juste entre les valeurs de l'accéléromètre et les valeurs du gyroscope. Si vous voulez lire les données en une seule opération i2c, vous vous trouverez donc avec les données de température.

Voici donc où sont les données de la vitesse angulaire :

Les données du gyroscope
Registre(HEX) Registre(DEC) Données
43 67 GYRO_XOUT[15:8]
44 68 GYRO_XOUT[7:0]
45 69 GYRO_YOUT[15:8]
46 70 GYRO_YOUT[7:0]
47 71 GYRO_ZOUT[15:8]
48 72 GYRO_ZOUT[7:0]

Voici comment il est possible de rassembler toutes ces données dans un tableau au fur et à mesure de la lecture :

// reinitialisation pour lecture
    TWIStartWrite(MPU<<1);
    TWIWriteStop(0x3B);
    _delay_ms(1);
    TWIStartWrite((MPU<<1)|1);
    for (i=0;i<13;i++)
      t[i] = TWIReadACK();
    t[13] = TWIReadNACKStop();

Reste donc à transformer ces données en rassemblant 2 cases successives 8 bits pour en faire un nombre 16 bits signé.

Utilisation du DMP du MPU6050 modifier

Notre aventure avec nos modules L298N de puissance déjà présentée est aussi liée à l'utilisation de l'accéléromètre MPU6050 en tout cas de la manière où il a été utilisé jusqu'ici. Cette façon est la plus simple : elle nécessite un processeur avec une interface i2c et c'est tout ! Le problème est qu'alors aucun filtrage n'est effectué : si le robot tombe d'un côté, le moteur va l'accélérer pour le redresser ce qui provoquera une accélération qui sera lue par l'accéléromètre au point de perturber la mesure et faire croire que l'on tombe de l'autre côté. Il en résulte un tremblement intempestif du robot qui de toute façon ne tiendra pas debout. Pour éviter cela, il nous faudrait faire de la fusion de données avec le gyroscope.

Il existe une autre façon de procéder : utiliser une fusion de données interne au MPU6050 réalisée avec un Digital Movement Processor (DMP). Il s'agit d'un processeur 32 bits spécialisé pour ce type de calcul.

Pourquoi n'avons-nous pas commencé par cela ? La façon d'accéder aux données traitées par le DMP n'est pas très simple. Elle nécessite la bibliothèque spéciale de Jeff Rowberg (déjà citée) et en particulier d'un de ses exemples "MPU6050_DMP6.ino". Il fonctionne avec le fichier "MPU6050_6Axis_MotionApps20.h" : lisez donc ce fichier pour vous convaincre que porter cette librairie demandera un certain travail !

A ce point nous en savons assez pour conclure que l'utilisation du DMP nécessitera un Arduino intermédiaire entre le MPU6050 et le FPGA. La raison profonde à cela est que nous avons découvert que le portage de la librairie sur notre processeur embarqué dans notre FPGA ne pourra pas se faire :


Utilisation d'un STM32 avec le MPU6050 modifier

 
Connexion entre un STM32 et un MPU6050

Nous avons renoncé à porter la librairie de Jeff Rowberg pour l'utilisation du MPU6050 en mode DMP (voir section précédente) dans notre SOC ATMega16. Il nous faut donc rajouter un processeur entre l'accéléromètre/gyroscope et le FPGA.

Nous avons décidé de faire fonctionner le MPU6050 avec un STM32. Les raisons ?

  1. le STM32 Blue Pill peut se trouver à 1,50  en Chine (en 2018)
  2. la carte STM32 Blue Pill est de petite taille
  3. le STM32 fonctionne en 3,3 V et n'amène donc pas de problème de tension avec le FPGA
  4. le STM32 Blue Pill se programme sous l'environnement Arduino (avec quelques petits ajustements quand même).
  5. nous avons trouvé une version de la librairie DMP de Jeff Rowberg sur Internet fonctionnant avec les 32 bits (ARM Cortex M3 ici)
 
Schéma de principe reliant accéléromètre, STM32 et MOJO

Cette architecture finale est plus que discutable techniquement :

  1. les 32 bits sont plus gourmands en énergie que les 8 bits. Un arduino Mini suffirait donc mais il est vendu plus cher que le STM32
  2. Le 32 bits que nous utilisons ici est largement suffisant pour être tout seul aux commandes du robot. Le FPGA est donc inutile. Nous le gardons seulement parce que nous sommes dans un livre sur les FPGA. Les décisions des commandes (régulation proportionnelle pour le moment) seront donc toujours réalisées par le FPGA tandis que le capteur de position verticale sera composé d'un MPU6050 et d'un STM32.

Il nous faut maintenant choisir un protocole de communication entre le STM32 et le FPGA.

Nous avons décidé de réaliser deux communications différentes entre le STM32 et le FPGA :

  • la liaison série originale : nous employons le mot original pour signifier que l'exemple de départ utilisait cette liaison série pour communiquer avec un PC
  • une liaison SPI déjà étudiée dans ce livre. Elle consiste à réaliser un esclave SPI qui sera piloté par un microcontrôleur STM32.

Il nous faut d'abord trouver les broches de la platine Blue Pill qui sont utilisées par défaut pour le SPI lorsque la librairie "Arduino" est utilisée. Pour cela nous avons repris un exemple déjà évoqué dans un autre chapitre. Il s'agissait d'utiliser une carte Nexys3 comme esclave SPI. Maintenant, le processeur sera un STM32. Le code STM32 est effectivement très simple :

#include <SPI.h>

#define SPI1_NSS_PIN PA4    //SPI_1 Chip Select pin is PA4.
// SPI_1 SCK is PA5
// SPI_1 MISO is PA6
// SPI_1 MOSI is PA7
void setup() {
  // put your setup code here, to run once:
   // Setup SPI 1
  SPI.begin(); //Initialize the SPI_1 port.
  SPI.setBitOrder(MSBFIRST); // Set the SPI_1 bit order
  SPI.setDataMode(SPI_MODE0); //Set the  SPI_1 data mode 0
  SPI.setClockDivider(SPI_CLOCK_DIV16);      // Slow speed (72 / 16 = 4.5 MHz SPI_1 speed)
  pinMode(SPI1_NSS_PIN, OUTPUT);

}

void loop() {
  byte data;
  // put your main code here, to run repeatedly:
  digitalWrite(SPI1_NSS_PIN, LOW); // manually take CSN low for SPI_1 transmission
  data = SPI.transfer(0x55); //Send the HEX data 0x55 over SPI-1 port
  digitalWrite(SPI1_NSS_PIN, HIGH); // manually take CSN high between spi transmissions
  delay(500);
  digitalWrite(SPI1_NSS_PIN, LOW); // manually take CSN low for SPI_1 transmission
  data = SPI.transfer(0xAA); //Send the HEX data 0xAA over SPI-1 port
  digitalWrite(SPI1_NSS_PIN, HIGH); // manually take CSN high between spi transmissions
  delay(500);  
}

Ce code fonctionne parfaitement avec un esclave SPI en VHDL déjà étudié dans ce livre.

La carte d'adaptation modifier

Commençons par résumer le fonctionnement que l'on vient de décrire pour bien cibler la carte à réaliser.

Répartition des tâches entre MPU6050, STM32 et FPGA modifier

Pour résumer ce que nous avons déjà expliqué :

  • un accéléromètre MPU6050 est utilisé avec son processeur de calcul intégré, le DMP
  • les données du DMP du MPU6050 sont recueillies par un STM32
  • le STM32 récupère les données du DMP et s'intéresse particulièrement au calcul de l'angle par rapport à la verticale
  • une fois cet angle obtenu il est systématiquement envoyé par SPI au FPGA qui le transforme en entrée pour le processeur (du FPGA)
  • le processeur du FPGA calcule le rapport cyclique qu'il faut envoyer aux deux moteurs
  • le FPGA réalise le rapport cyclique des moteurs, adapté au composant de puissance utilisé L298N
  • ... et le robot tiendra debout peut-être ...

Présentation de la carte d'extension modifier

La carte d'adaptation est destinée à se placer sur la carte FPGA MOJO. C'est donc une sorte de bouclier (shield). Son cahier des charges est le suivant :

  • adapter les tensions de commande pour le composant de puissance L298N (monté sur une carte spécifique). La documentation du composant L298N laisse entendre en effet une compatibilité TTL donc commandable par du 3,3 V mais que nous n'avons jamais réussi à réaliser.
  • réaliser des modifications sur des paramètres importants du régulateur (ou autre). Nous avons choisi pour cela un bouton codeur incrémental.
  • recevoir la carte accéléromètre
  • aider au débogage avec un afficheur 4 digits 7 segments.

Nous avons réalisé une première carte mais celle-ci présente trois défauts :

  1. les 8 LEDs de la carte MOJO sont aussi présentes sur le connecteur d'extension et nous en avons utilisé une par mégarde pour la commande de puissance. Ceci peut sembler anecdotique mais c'est le seul moyen de débogage dont on dispose très facilement !
  2. l'afficheur 4 digits de 7 segments aurait pu être utile lui aussi pour le débogage mais nous l'avons choisi pour son prix réduit et comme le montre la section qui lui est consacré dans ce chapitre, nous ne maîtrisons pas encore son utilisation.
  3. pas de possibilité de lire le processeur DMP

Une deuxième carte a donc vu le jour pour répondre à tous les problèmes présentés auparavant. Nous avons retirés les afficheurs 7 segments et le bouton codeur incrémental mais naturellement ajouté le STM32 déjà évoqué.

Ressources modifier

Le code présenté dans cette section est disponible sur mon site personnel. Il n'est pas encore capable de gérer le maintien du robot en auto-équilibre mais cela viendra un jour... Pour le moment, seule la correction Proportionnelle est effective avec une consigne d'angle. Or quand l'angle est de 0 (la consigne) le robot n'est pas en équilibre : il a donc tendance à se déplacer pour réaliser la consigne à 0, c'est-à-dire l'auto équilibre !

Écran 2.8" SPI modifier

Nous avons acheté un 2.8 TFT SPI 240x320 V1.1 pour environ 6  (2018) mais pour ce prix, le composant responsable de la détection tactile n'est en général pas soudé sur la carte (et pas livré). Cela tombe bien, nous n'avons pas l'intention de nous intéresser au côté tactile pour le moment. Comme vous l'avez remarqué dans le titre de cette section, il s'agit d'un composant SPI donc avec une vitesse de transfert faible. Vous pouvez trouver des écrans avec un transfert de 16 bits à la fois, mais ils sont un peu plus chers (environ 12  en 2019).

Comme d'habitude, nous allons commencer par étudier le composant à l'aide d'un microcontrôleur. Nous choisissons un microcontrôleur 3,3 V pour éviter les problèmes de tensions. L'écran que nous utilisons peut être alimenté en 3,3 V et est architecturé autour du composant ILI9341.


Étude avec un microcontrôleur 32 bits modifier

Nous allons utiliser la carte TivaC LaunchPad de chez Texas Instrument avec l'environnement compatible Arduino : Energia.

Préparation de l'environnement Energia modifier

Vous devez télécharger la librairie de gestion du composant central ILI9341 qui a été adaptée à l'environnement Energia : Librairie ILI9341 pour Energia.

Cette librairie doit être installée correctement.


Câblage avec la carte Tiva modifier

Le câblage est lié au matériel et au logiciel. Côté matériel, la carte TivaC LaunchPad est utilisée. Côté logiciel, le programme impose un certain nombre de broches :

Tft.begin(PE_1,PE_2,PE_3,PE_4);                // CS,DC,BL,RESET pin

impose par exemple CS, DC, BL et RESET. Le SPI utilisé par défaut dans Energia impose le reste. Voici donc le câblage complet :

2.8 TFT SDO/MISO LED SCK SDI/MOSI DC RESET CS GND VCC
TivaC LaunchPad PB6 PE3 PB4 PB7 PE2 PE4 PE1 GND 3.3V

Essai modifier

Il vous faut choisir dans les exemples par défaut de la librairie. Remarquez quand même que les cartes bon marché ne sont pas équipée du composant destiné à gérer le côté tactile de l'écran. Pour ce type d'écran (le mien) évitez donc l'utilisation de l'exemple paint.


Tous les problèmes associés à la carte MSP432 sont maintenant résolus : le code ci-dessous est compilable et parfaitement fonctionnel depuis le 28/01/2020.

Étude avec un FPGA modifier

Il n'est évidemment pas très difficile de réaliser un périphérique SPI en VHDL. Si l'on n'est pas courageux, Internet nous fournit un certain nombre de cœurs SPI. Ces cœurs ne sont pas orientés processeur mais plutôt destinés à un fonctionnement quasi autonome. Par exemple, ils gèrent :

  • un adressage des périphériques SPI
  • un slave select automatique (SSEL)
  • une taille des données souvent fixée par avance en relation avec SSEL (et cela est un problème pour nous).
  • la division pour la construction de l'horloge SPI
  • les polarités de l'horloge

Quand on est un peu habitué à une programmation SPI sur microcontrôleur, on sait que le SSEL est géré par un (ou plusieurs) bit(s) de PORT et ne nécessite en aucun cas un adressage automatique ni un SSEL automatique.

Nous voulons garder la gestion classique du SPI dans les microcontrôleurs car cela nous permettra de porter assez facilement des librairies toutes faites pour l'écran. En clair, à priori nous mettons à la poubelle le SSEL automatique. Mais pas si vite, n'avons-nous pas besoin d'un drapeau pour dire que la transmission est terminée ? Oui bien sûr, pour cela le signal SSEL est idéal. Nous allons donc garder ce signal mais ce n'est pas lui qui sortira physiquement du processeur.

Ressource SPI modifier

Comme à notre habitude nous allons utiliser un cœur SPI disponible chez OpenCore.org. Seule la partie spi_master.vhd nous intéresse.

Toutes les gestions présentées précédemment semblent nous convenir sauf que si l'on regarde de près on y voit que :

  • la gestion de la division est réalisée par un générique
  • la gestion des polarités est faite par un générique

Tout ceci est parfait pour une gestion connue au moment de la compilation matérielle mais en aucun cas pour une utilisation pouvant être générale et gérée par des registres de processeur. Il nous a donc fallu faire passer ces génériques en PORT, d'où la présence de ce fichier modifié :

Pour mettre tout cela dans le processeur, nous allons naturellement commencer par modifier le fichier io.vhd.

Modification du fichier io.vhd modifier

Voici le fichier modifié : il gère un peu trop de périphériques pour cette section mais nous le laissons tel quel.

Ce fichier se trouvant à l'intérieur du microcontrôleur il faut bien sûr aussi modifier le fichier correspondant au microconctroleur.

Modification du fichier de description du microcontrôleur modifier

Voici le fichier modifié : il gère lui aussi un peu trop de périphériques pour cette section mais nous le laissons tel quel.

Il ne vous manque plus que le fichier de contraintes.

Fichier de contraintes modifier

Le fichier de contraintes est naturellement très dépendant des cartes FPGA utilisées. Pour le travail de cette section, nous avons choisi une carte chinoise achetée autour de 20  (2018) comportant un spartan6LX9. Pour ce prix, vous n'avez naturellement pas de programmateur, il faut donc l'acheter en plus. Les programmateur pour Xilinx sont autour de 20  contrairement à ceux d'Altera qui sont à moins de 8  (transport compris - 2019).

## Clock signal
NET "I_CLK_100"            LOC = "P55" | IOSTANDARD = "LVTTL";   
Net "I_CLK_100" TNM_NET = sys_clk_pin;
TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 50000 kHz;

NET "I_CLR"          LOC = "P14" | IOSTANDARD = "LVTTL";

## Leds
NET "Q_PORTC<0>"         LOC = "P87" | IOSTANDARD = "LVTTL";           
NET "Q_PORTC<1>"         LOC = "P88" | IOSTANDARD = "LVTTL";
## i2c
NET "SCL"          LOC = "P1"  | IOSTANDARD = LVTTL | PULLUP ;#IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L45N_M3ODT,                     Sch name = JC3
NET "SDA"          LOC = "P5"  | IOSTANDARD = LVTTL | PULLUP ;#IOSTANDARD = "LVCMOS33";   #Bank = 3, Pin name = IO_L46P_M3CLK,                     Sch name = JC4
## i2c-like pour TM1637
NET "tm1637scl"          LOC = "P7"  | IOSTANDARD = LVTTL;# | PULLUP ;
NET "tm1637sda"          LOC = "P9"  | IOSTANDARD = LVTTL | PULLUP ;
## SPI
#SSEL est sur un bit de PORT #SSEL
NET "Q_PORTC<2>"   LOC = "P142" | IOSTANDARD = "LVTTL";  
NET "SCLK"         LOC = "P140" | IOSTANDARD = "LVTTL";
NET "MOSI"         LOC = "P138" | IOSTANDARD = "LVTTL";
NET "MISO"         LOC = "P134" | IOSTANDARD = "LVTTL";
## ILI9341 = SPI +:
# DC DATA/COMMAND
NET "Q_PORTC<3>"   LOC = "P132" | IOSTANDARD = "LVTTL";#DC 
NET "Q_PORTC<4>"   LOC = "P127" | IOSTANDARD = "LVTTL";#RESET 
NET "Q_PORTC<5>"   LOC = "P124" | IOSTANDARD = "LVTTL";#LED 
## Program Memory
INST cpu/opcf/pmem/pe_1 LOC = RAMB16_X1Y2;
INST cpu/opcf/pmem/pe_0 LOC = RAMB16_X0Y4;
INST cpu/opcf/pmem/pe_3 LOC = RAMB16_X1Y6;
INST cpu/opcf/pmem/pe_2 LOC = RAMB16_X0Y2;
INST cpu/opcf/pmem/po_1 LOC = RAMB16_X1Y0;
INST cpu/opcf/pmem/po_0 LOC = RAMB16_X0Y0;
INST cpu/opcf/pmem/po_3 LOC = RAMB16_X1Y4;
INST cpu/opcf/pmem/po_2 LOC = RAMB16_X0Y6;

Et le logiciel de tout cela modifier

Pour faire fonctionner les périphériques de cette section, il faut naturellement un peu de carburant : le logiciel. Nous sommes parti de la librairie utilisée dans la carte Tiva de l'étude avec un microcontrôleur du commerce et l'avons porté pour nos besoins.

Faites le tri de ce que vous avez besoin pour vos tests.


Reprise du problème sur notre Nexys 3 modifier

 
Visuel pour notre réalisation de gestion d'un écran par un FPGA

Nous avons repris ce problème sur une carte Nexyx 3. Peu de changement avec ce qui a été fait mais il nous a fallu un peu de temps pour reprendre. Voici le détail du câblage réalisé sur les connecteurs PMod de la Nexys 3. La couleur et l'ordre des fils n'étant pas visibles sur la photo, nous résumons dans un tableau comment la carte a été câblée en utilisant les connecteurs PMod C et D. Le fichier ucf ne correspond plus à celui qui a été donné ci-dessus pour la bonne et simple raison que nous avons changé de carte. Mais le tableau ci-dessous permet tout portage.

2.8 TFT SDO/MISO LED SCK SDI/MOSI DC RESET CS GND VCC
Connecteurs PMod (C et D) JC1 JC2 JD3 JD4 JD1 JC3 JD2 GND 3.3V
VHDL MISO Q_PORTC(5) SCLK MOSI Q_PORTC(3) Q_PORTC(4) Q_PORTC(2) XXX XXX
 
Connecteur spécifique des cartes de Digilent (FPGA et Microcontrôleurs)

Voir aussi plus de détails sur les connecteurs Pmod.


Ressources modifier

Convertisseur A/N simple pour Joystick modifier

Un article de Guido Nopper dans le magazine Elektor (septembre/octobre 2019) propose la réalisation d'un convertisseur analogique numérique sigma delta (donc simple) dans un CPLD. Nous n'avons pas pu résister à porter son code pour un FPGA. Il était réalisé en schématique Xilinx et c'est comme cela que nous allons le présenter. Comme ce chapitre est destiné aux "shields Arduino", nous allons nous proposer de lire un joystick bon marché (par exemple le Module joystick GT1079).

Principe théorique modifier

 
Schéma de principe d'un convertisseur Delta-Sigma

Voici un schéma de principe d'un convertisseur analogique numérique à modulation sigma-delta dans la figure ci-contre.

On y distingue un intégrateur, un comparateur, le tout bouclé et suivi d'une bascule D et d'un décimateur.

Lisez la partie Convertisseur Sigma Delta de l'article sur les conversions analogiques numériques dans wikipédia.

La réalisation pratique peut se faire avec deux résistances, un condensateur et une bascule D comme indiqué dans la figure. La sortie sur la bascule D se trouve être la modulation sigma Delta.

 
Principe de base d'une conversion analogique delta Sigma en logique

La moindre utilisation pratique devra réaliser la démodulation sigma delta et ne sera donc pas composée par une seule bascule D. Il faudra ajouter un certain nombre de composants comme indiqué dans la section suivante.

Notre réalisation modifier

Nous avons décidé d'utiliser les outils Xilinx de schématique comme cela a été fait dans Elektor. Notre schéma est pourtant assez différent de celui d'Elektor (suffisamment en tout cas pour respecter tout problème de copyright). Une des raisons fondamentale est que l'article d'Elektor envoie le résultat numérique dans un Convertisseur Analogique Numérique pour une comparaison des deux valeurs analogiques. En ce qui nous concerne, nous avons décidé de sortir en binaire sur les 8 leds disponibles sur notre carte FPGA.

Voici comment nous avons conçu notre convertisseur Analogique numérique sigma-delta.

 
Réalisation complète d'un convertisseur analogique numérique sigma delta

La bascule D du schéma de principe a été remplacée par deux bascules D en série. C'est naturellement ce que l'on fait quand on a un signal extérieur non synchronisé avec l'horloge du FPGA. Nous pensons cependant qu'ici cela n'est pas une nécessité absolue.

Le compteur CB16CE peut être remplacé par un CB8CE puisque nous prenons sa sortie de poids (7) comme horloge principale. Il faut cependant avoir à l'esprit que ce bit de poids 7 est lié à la valeur du condensateur. Pour information pour une résistance R de 4k7 avec :

  • un condensateur de 4n7 cela nécessite de prendre le bit de poids 7
  • un condensateur de 47nF cela nécessite de prendre le bit de poids 11
  • un condensateur de 470nF cela nécessite de prendre le bit de poids 15

Le premier compteur CB8CE en bas à gauche est destiné à compter le temps de comptage du compteur de résultat qui est le CB8CE en haut à droite. Quand le temps de comptage est terminé, ce compteur est mémorisé dans un registre FD8CE puis remis à 0 pour le comptage suivant.

Exercice 1 modifier

En cherchant les documentations des compteurs et registres utilisés dans le schéma, on vous demande de refaire un implantation complète en VHDL.

Autre réalisation modifier

Un de nos collègue, Gilles Millon, a fait une réalisation directe en VHDL en utilisant des principes assez différents de ce que nous avons fait. Mais des comparaisons des deux montages nous ont montrés des résultats pratiques complètement similaires.

Exercice 2 modifier

Nous vous demandons de faire la démarche inverse de l'exercice 1 : vous partez du programme VHDL et l'on vous demande de réaliser le schéma correspondant.

Leds adressables NeoPixel modifier

Nous allons étudier les protocoles séries WS2812S et WS2812B destinées aux leds adressables dans cette section.

Notre travail de départ va consister à analyser les publications light ws2812 library part 1 et light ws2812 library part 2.

  • Nous allons examiner comment utiliser les données de l'analyseur logique de la partie 1 pour tenter la réalisation d'un périphérique VHDL.
  • Nous allons ensuite tenter de faire fonctionner la librairie github:light ws2812 AVR dans notre SOC AVR
  • Les deux expériences précédentes nous amèneront à modifier le périphérique VHDL de la première étude pour l'adapter en périphérique. Des commentaires postés sur "light ws2812 library part 2" suggèrent d'utiliser un périphérique SPI pour cela avec de la DMA. Pour nous la DMA sera remplacée d'une manière ou d'une autre par de la logique VHDL.

Voir aussi modifier

Lecture de données d'une caméra Pixy 2 modifier

Nous avons eu l'occasion de réaliser dans ce long chapitre un processeur gérant le SPI. Ainsi nous avons géré un écran SPI 2,8" plus haut, ce qui nous a permis de valider le cœur SPI présent dans notre ATMega16.

Introduction modifier

Nous allons partir du règlement de la Coupe de France des IUT GEII 2020 à laquelle nous n'avons pas pu participer pour cause de pandémie. Ce règlement est encore disponible : Règlement 2020. En principe la coupe de France aura lieu en juin 2022 avec un règlement probablement assez similaire. Une vidéo de cette coupe de France qui a bien eu lieu est disponible maintenant.

Une des questions que l'on va se poser est : comment utiliser la caméra Pixy2 pour guider le robot jusqu'à une balle de tennis, le tout avec un FPGA ? On parle de balle de tennis ici mais n'importe quel objet appris par la caméra peut remplacer cette balle ! L'apprentissage sera fait dans PixyMon et non pas à l'aide du processeur/FPGA. Dans tout ce qui suit, nous considérons que l'apprentissage est réalisé. Voir bibliographie de cette section pour de plus amples informations.

Nous n'avons donc pas l'intention de détailler l'utilisation d'une caméra Pixy 2 dans cette section mais simplement de mettre en œuvre un dialogue entre notre FPGA et la caméra permettant de recevoir des données de la Pixy 2. Ce type de dialogue se réalise facilement avec un Arduino puisqu'il y a même un câble vendu avec, qui permet de relier directement la Pixy 2 au programmateur ICSP de celui-ci. C'est la raison pour laquelle nous avons mis cette section dans ce chapitre : la présence possible d'un Arduino rend la Pixy2 comme un éventuel shield. Ce dialogue avec l'ICSP a une particularité : c'est du SPI sans sélection (SS = Slave Select). Heureusement d'autres protocoles sont possibles et peuvent être choisis dans un moniteur spécifique (PixyMon) :

  • SPI avec "Slave Select"
  • I2C pas très conseillé car un peu lent
  • liaison série
  • valeur analogique

La valeur analogique pourrait être exploitée avec le convertisseur sigma/Delta d'une section précédente. Ce serait intéressant si plusieurs données analogiques étaient données en même temps, mais ce n'est pas le cas. Nous préferons explorer maintenant le SPI sans (SS = Slave Select) dans notre FPGA qui nous retournera au moins quatre valeurs exploitables :

  • position x et y du centre de la balle (ou plus exactement de son cadre rectangulaire ce qui en principe est très souvent identique)
  • grandeur Dx et Dy du cadre rectangulaire

Les deux dernières données permettent une évaluation de la distance de la balle tandis que les deux premières permettent une évaluation angulaire du cap.

Réalisation logicielle modifier

Nous allons commencer par décrire le protocole d'échange entre la caméra et le FPGA. Nous allons utiliser le protocole SPI sans Slave Select. Pour basculer au SPI avec "Slave Select" il y a deux choses à faire :

  • côté Pixy2 il suffit d'utiliser PixyMon pour basculer dans ce mode
  • côté FPGA, il suffit de prévoir un bit de PORT et gérer par vous même. C'est comme cela dans les microcontrôleurs aussi.

Protocole d'échange modifier

La connaissance du protocole d'échange entre la caméra et votre processeur est importante. Ce protocole ne dépend pas de la façon matérielle choisie : i2c, SPI sans SS, SPI avec SS, rs232. Voici comment il est documenté dans le document officiel (très partiellement traduit ici). Nous allons examiner par exemple le cas de la recherche de la résolution de la caméra.

  • La demande réalisée par votre processeur doit être conforme à la requête standard pour ce type de données :
Requête
Byte Description Value(s)
0 - 1 16-bit sync 174, 193 (0xc1ae)
2 Type de paquet 12
3 Longueur de données 1
4 Type (non utilisé réservé pour les versions futures) 0 - 255

Cette suite d'octets est obligatoirement à envoyer si vous voulez obtenir la réponse correspondant à la résolution de l'image en pixels de la part de la caméra Pixy2. Cette résolution pouvant être réglée à l'aide de PixyMon, il vous faut être sûr de la connaître si vous voulez positionner correctement un objet (par rapport à vous) dont les coordonnées vous sont retournées par la caméra.

  • La réponse de la caméra doit être conforme à :
Réponse
Byte Description Value(s)
0 - 1 16-bit sync 175, 193 (0xc1af)
2 Type de paquet 13
3 Longueur des données 2
4 - 5 16-bit checksum sum of payload bytes
6 - 7 16-bit Largeur d'image (en pixels) 0 - 511
8 - 9 16-bit Hauteur d'image(en pixels) 0 - 511

Nous allons maintenant nous intéresser à la façon de gérer ce type de données en langage C. Deux façons nous viennent à l'esprit :

  • Utilisation d'un type structure en C
/**** déclaration du type avec une structure ****/
/**** correspond au premier tableau ci-dessus ****/
struct getRes {
  uint16_t _16bitsSync;
  uint8_t TofPacket;
  uint8_t  LenOfPayload;
  uint8_t unUsed;
}; 
/**** déclaration avec initialisation d'une variable ****/
struct getRes requestRes = {0xC1AE,12,1,0};
  • Utilisation d'un tableau
/**** déclaration avec initialisation du tableau ****/
uint8_t ResData[5]={0xAE,0xC1,12,1,0};

Les deux méthodes ont leurs avantages et inconvénients. L'intérêt d'une structure est qu'elle permet d'accéder aux champs directement par leurs noms. Son inconvénient est qu'en général les sous-programmes utilisés pour envoyer ou recevoir des données utilisent des tableaux. Il faudra donc transtyper et ceci vous vaudra un warning de la part du compilateur.

L'inconvénient du tableau est qu'il ne faut pas vous tromper pour mettre les données : regardez comment est mis la valeur 0xC1AE dans le tableau. Vous aurez évidemment le même problème pour retrouver les données.

Nous vous laissons choisir.

Exercice modifier

Examinez dans la documentation anglaise appropriée le protocole d'échange pour getResolution() et pour getBlocks(sigmap, maxBlocks) et construisez des types de données pour réaliser correctement ces protocoles ainsi que les sous-programmes correspondants. Testez.

Indication La lecture de ce problème dans un autre cours vous apprendra que le secret de la réussite, nous l'avons d'abord trouvé en utilisant un Arduino. Ensuite seulement, il nous a été très aisé de le porter pour notre SOC/FPGA. Il suffisait d'écrire un sous-programme qui attende l'entête correspondante à la réponse. Une fois réalisé sur Arduino, il nous a fallu quelques minutes pour le faire fonctionner dans notre FPGA.

Voir aussi modifier

Voir aussi modifier

A regarder pour compléter ce chapitre modifier