Very High Speed Integrated Circuit Hardware Description Language/Commande de robot mobile et périphériques associés

Leçons de niveau 16
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
Commande de robot mobile et périphériques associés
Icône de la faculté
Chapitre no 18
Leçon : Very High Speed Integrated Circuit Hardware Description Language
Chap. préc. :Réalisation d'un coprocesseur CORDIC
Chap. suiv. :Utiliser un processeur externe
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Very High Speed Integrated Circuit Hardware Description Language : Commande de robot mobile et périphériques associés
Very High Speed Integrated Circuit Hardware Description Language/Commande de robot mobile et périphériques associés
 », n'a pu être restituée correctement ci-dessus.

Nous allons nous intéresser dans ce chapitre à la commande de robots différentiels mobiles par une carte FPGA. L’idée est naturellement d’utiliser un processeur pour les calculs mais aussi de développer des périphériques associés. La platine robotique utilisée est celle de Digilent car elle est adaptée pour une commande en 3,3 V.

Introduction[modifier | modifier le wikicode]

La robotique mobile qui nous intéresse dans ce chapitre, est constitué par des petits robots propulsés en général à l'aide de deux roues. Ce type de robot est appelé "robot mobile différentiel", mais par souci de simplification, nous omettrons souvent le terme "différentiel". Voici quelques exemples de robots mobiles différentiels sur l'image ci-dessous.

Différents types de robots mobiles différentiels

Vous voyez sur cette image des robots mobiles plus ou moins sophistiqués qui sont en général commandés par un micro-contrôleur. Dans ce chapitre nous allons nous intéresser essentiellement à remplacer le micro-contrôleur de commande par un FPGA. Ce n’est pas le prix qui nous attire dans cette voie (les FPGA restent des circuits chers par rapport aux micro-contrôleurs). Ce qui nous intéresse est l'étude de périphériques conçus maison pour propulser un robot et l'aider à se repérer dans l'espace.

Le robot Asuro, à gauche dans la photo ci-dessus, a été étudié en détail dans le chapitre d'un autre livre : AVR et robotique : ASURO. La compréhension de l'odométrie du présent chapitre nécessitera la lecture de la section Étude de la cinématique d'un robot différentiel de cet autre projet.

Remplacer un micro-contrôleur par un FPGA ne pose en général que quelques problèmes sur les voltages. La famille Arduino par exemple gère des tensions de 5 V à part le "DUE". Par contre les FPGAs sont depuis longtemps déjà en 3,3 V. Pour éviter tout problème à ce niveau, nous avons choisi un Robot de chez Digilent car cette entreprise réalise à la fois des cartes FPGA et des cartes micro-contrôleur. On aura une compatibilité électrique entre les deux. Présentons maintenant cette base robotique.

Présentation de la platine Digilent[modifier | modifier le wikicode]

La platine de robotique mobile de Digilent nous semple être un très bon point de départ pour un robot évolutif. Il est assez facile d'ajouter des modules extérieurs (et en particulier les fixer). La carte de commande originale utilise un PIC32 (CEREBOT MX4ck) mais nous avons retiré cette carte pour la remplacer par une carte FPGA. La Basys2 plus petite en taille que cette carte se fixe parfaitement sur le robot mais la Nexys3 peut certainement faire aussi l'affaire.

Le robot digilent modifié pour accueillir une carte FPGA Basys2

Nous avons à disposition Line-Following Motor Robot Kit mais il nous semble que MRK-BASIC moins chère peut suffire si vous n'avez pas l'intention de suivre des lignes.

Cette platine contient une partie mécanique évolutive, deux commandes de puissance pour deux moteurs à courant continu, et un éventuel détecteur infra-rouge capable de gérer 4 photo-transistors.

La carte de commande est architecturée autour d'un micro-contrôleur PIC32. Elle ne sera pas détaillée ici puisqu'elle sera remplacée de toute façon par une carte FPGA.

Depuis quelques années Digilent développe des modules d'interface aux capteurs appelés PMOD. L'intérêt de ceux-ci est qu’ils s'interfacent facilement aux cartes à micro-contrôleurs comme aux cartes FPGA. Ils sont décrits brièvement dans le chapitre Utiliser un processeur externe de manière pratique.

La gestion de la détection infra-rouge[modifier | modifier le wikicode]

Il s'agit d'un module pouvant gérer 4 photo-transistors infrarouge avec le standard pmod de Digilent : module pmodLS1. L'intérêt de ces modules, comme déjà évoqué, est qu’ils se connectent sur les cartes micro-contrôleurs comme sur les cartes FPGA modernes (Basys2, Nexys2, Nexys3,... et bien d'autres encore). Ce module demande à être calibré à l'aide d'un réglage de potentiomètre.

Pour calibrer, nous vous conseillons d’utiliser un scotch noir sur un fond blanc (en bois) et de mettre le robot dans différentes positions par rapport au scotch. Des leds présentes sur le module doivent s'allumer et s'éteindre suivant la position.

Ces modules sont présentés plus loin.

Nous allons poursuivre par la présentation de la commande de puissance des deux moteurs.

Les modules de commande de moteur : PmodHB5[modifier | modifier le wikicode]

Les moteurs à commander sont à courant continu (MT-MOTOR). Le module Pmod qui se charge de la commande en puissance s’appelle PmodHB5.

La description des PmodHB5 est disponible sur le site de Digilent et pour ceux qui sont allergiques à l'anglais, une description sommaire est disponible chez Lextronic.

Les moteurs ont deux codeurs incrémentaux à disposition. Le lien déjà présenté, (mais rappelé ici : MT-MOTOR), montre sur la photo à droite des codeurs incrémentaux. Nous allons les présenter brièvement maintenant.

Les codeurs incrémentaux[modifier | modifier le wikicode]

Deux signaux carrés en quadrature (rotation dans le sens des aiguilles d'une montre).

La théorie de ces codeurs est représentée ci-contre. On vous représente deux signaux appelés phases qui sont déphasés. Ce qui caractérise ces signaux est que le déphasage dépend du sens de rotation. La figure représente ces deux signaux pour un sens de rotation dans le sens des aiguilles d'une montre. A est alors en retard de 90°.

Dans le cas d'une rotation dans l'autre sens, A sera en avance de 90° sur B. Ces informations conduisent à une description de suite d'états pour les deux sens de rotation.

Les deux diagramme d'états correspondant sont

Suite d'états pour
rotation dans le sens
des aiguilles d'une montre
Phase A B
1 0 0
2 0 1
3 1 1
4 1 0
Suite d'états pour
rotation dans le sens
contraire des aiguilles d'une montre
Phase A B
1 1 0
2 1 1
3 0 1
4 0 0

L'exploitation de ces codeurs en tant que périphérique fera appel à ces données et sera présentée plus loin dans ce chapitre.

Interfacer le robot à la carte FPGA[modifier | modifier le wikicode]

Remplacer la platine de commande originale par un FPGA ne pose absolument aucun problème particulier. Pour éviter une trop longue description verbale, nous vous demandons de vous reporter aux quelques images du robot du chapitre.

Commander les moteurs du robot avec notre FPGA[modifier | modifier le wikicode]

Nous allons utiliser un processeur pour prendre les décisions qui s'imposent en fonction des données des capteurs. Le processeur enfoui dans notre FPGA sera l'ATMega16 déjà décrit dans ce livre.

Détail de la commande des moteurs

Vous pouvez remarquer sur la photo ci-dessus, que l’on à remplacé le support fixe par une bille permettant un roulement plus facile. Une roue castor libre pourrait tout aussi bien faire l'affaire.

Les modules de commande de moteur : pmodHB5[modifier | modifier le wikicode]

Nous avons décidé d’utiliser le module HB5RefComp fourni par Digilent dont voici l'entité en VHDL :

entity HB5RefComp is
    Port ( ck : in  STD_LOGIC;          -- system clock (25MHz)
           bitDirIn : in  STD_LOGIC;    -- User direction request
           vecDfIn : in  STD_LOGIC_VECTOR(7 downto 0); -- Duty factor value 
           bitEnOut : out  STD_LOGIC:= '0';   -- Enable pin for Pmod HB5
           bitDirOut : out  STD_LOGIC:= '1'); -- Dir pin for Pmod HB5
end HB5RefComp;

Ce module s'occupe de générer "bitEnOut" et "bitDirout" nécessaires à la commande de l'électronique de puissance à partir d'une horloge (à 25 MHz pour nous) d'un bit de direction "bitDirIn" et d'un rapport cyclique "vecDfIn" sur 8 bits.

Le module HB5RefComp[modifier | modifier le wikicode]

Comme nous avons changé quelques options par rapport au fichier original téléchargé, nous donnons sa version intégrale maintenant.

Comme nous avons deux moteurs à commander, il nous faudra utiliser ce module en double. Nous avons décidé d’utiliser PORTB pour le rapport cyclique du moteur gauche et PORTC pour celui du moteur droit. Les deux bits de direction seront ramenés sur DDRB :

  • moteur droit est relié au bit b0 de DDRB
  • moteur gauche est relié au bit b1 de DDRB

Avant de présenter le code source de io.vhd, nous présentons comme d'habitude le problème sous forme de schéma :

Commande de la partie puissance par le processeur ATMega16 enfoui dans le FPGA

Interface des pmodHB5 avec l'ATMega16[modifier | modifier le wikicode]

Voici le fichier "io.vhd" modifié pour prendre en compte deux moteurs. Bien sûr les sorties LeftEnOut, LeftDirOut, RightEnOut, RightDirOut sont à prolonger jusqu'à la sortie du processeur et donc à gérer correctement dans l'ucf. Voici les parties du fichier ucf concernées :

####### Only for Basys2 ########
#
NET "RightDirOut" LOC = "A9"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JC1
NET "RightEnOut" LOC = "B9"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JC2
#NET "PIO<82>" LOC = "A10" | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JC3
#NET "PIO<83>" LOC = "C9"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JC4
#
NET "LeftDirOut" LOC = "C12"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JD1
NET "LeftEnOut" LOC = "A13"  | DRIVE = 2  | PULLUP ; # Bank = 2, Signal name = JD2
#NET "PIO<86>" LOC = "C13"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JD3
#NET "PIO<87>" LOC = "D12"  | DRIVE = 2  | PULLUP ; # Bank = 2, Signal name = JD4 

où l’on voit que le moteur droit est relié au connecteur JC tandis que le moteur gauche est relié au connecteur JD.

Et voici donc le fichier io.vhd comme promis :

Programmation des pmodHB5 avec l'ATMega16[modifier | modifier le wikicode]

Voici un exemple de programme qui utilise cette interface.

int main(int argc, char * argv[])
{
  while(1){
  // choix de la direction : en avant : voir code iotimer.vhd
    DDRB = 0x00;
  // moteur droit en avant
    PORTC = 0x55;
  // moteur gauche en avant
    PORTB = 0x55;
    _delay_ms(1000);
  // on arrête
    PORTC = 0x00;
    PORTB = 0x00;
  // changement de direction pour un moteur :
    DDRB = 0x01;
    PORTC = 0x55;
    PORTB = 0x55; 
    _delay_ms(1000);
  // on arrête
    PORTC = 0x00;
    PORTB = 0x00; 
  // changement de direction pour un moteur :
    DDRB = 0x02;
    PORTC = 0x55;
    PORTB = 0x55; 
    _delay_ms(1000);
  // on arrête
    PORTC = 0x00;
    PORTB = 0x00; 
  // changement de direction pour deux moteurs :
    DDRB = 0x03;
    PORTC = 0x55;
    PORTB = 0x55; 
    _delay_ms(1000);
  // on arrête
    PORTC = 0x00;
    PORTB = 0x00; 
  }
  return 0;
}

Que fait ce programme ?

Arrivé à ce point, il serait probablement très utile de réaliser des sous-programmes permettant d'aller en ligne droite pendant un certain temps et de tourner de 90° ou mieux encore de tourner d'un angle fourni en paramètre. Pour le moment nous laissons le soin de réaliser ce travail au lecteur. Mais d'autres sous-programmes (intéressants ?) sont présentés un peu plus loin...


Maintenant que nous sommes capables de faire bouger notre robot, le problème de savoir où l’on est à tout moment se pose sérieusement.

Se repérer dans l'espace[modifier | modifier le wikicode]

Dans cette section, nous allons examiner comment se repérer dans l'espace à l'aide de compteurs (un par roues, donc deux compteurs). Ces compteurs devront mémoriser la distance de déplacement effectuée par chacune des roues. Mais expliquons d’abord comment ceci va être réalisé en pratique.

La commande des moteurs par les modules PModHB5 nous a pris seulement deux fils plus l'alimentation comme on peut facilement le voir dans le schéma qui explicite l'interface de cette commande. Pourtant une autre information est disponible, comme déjà évoqué. Il s'agit de codeurs incrémentaux à effet Hall. Nous désirons les mettre en œuvre dans cette section.

Nous allons d’abord tenter de répondre à la question de la difficulté du problème.

Pourquoi le problème est difficile[modifier | modifier le wikicode]

Avant de se poser des questions il faut essayer de bien comprendre ce que veut dire connaître exactement l'état du robot. Celui-ci est donné par trois paramètres même si l’on est en deux dimensions : . x et y sont naturellement le repérage de la position sur les deux axes x et y et est l'orientation du robot par rapport à l'axe des x. Regardez la figure ci-contre pour vous convaincre de la pertinence de ces trois données.

Distances rouges identiques, distances bleues identiques mais point final différent

Imaginons que vous ayez fait déplacer votre robot pendant un temps T quelconque. Vous relevez les compteurs de chacune des roues et convertissez en centimètres parcourus et trouvez deux nombres A et B. Est-il possible d’en déduire la nouvelle position (et orientation) sachant que l’on connaît exactement la position de départ ? Autre manière de poser le problème : je connais parfaitement ainsi que la distance parcourue par chacune des roues est-il possible d’en déduire  ? Malheureusement la réponse à cette question est partiellement négative ! Il n'est possible que d’avoir mais pas le reste.

Tout cela est résumé simplement par la figure ci-contre. Dans cette figure, les deux longueurs (en pointillés) rouges sont identiques (et égales à A) et les deux longueurs (en pointillés) bleues sont elles aussi identiques (et égales à B) et pourtant on obtient deux points d'arrivées différents. Seule l'orientation finale est identique. Cela montre qu'aucune formule ne permet le calcul de xf à partir de xd, A et B.

Doit-on abandonner l’idée de trouver le point final à partir du point de départ ? Non, heureusement. En fait le problème présenté devient possible si les deux distances parcourues A et B deviennent différentielles (c'est-à-dire toutes petites). C'est quand même une mauvaise nouvelle malgré tout. Cela veut dire qu’il nous faut mémoriser les distances parcourues par chacune des roues à des instants précis et assez fréquemment pour se rapprocher de l’idée mathématique de différentielle. Nous essaierons dans un premier temps dix mesures par seconde. Nous utiliserons des compteurs sur 8 bits en complément à deux... et dix fois par seconde nous mémoriserons donc les valeurs des deux compteurs (droit et gauche) dans une mémoire.

Le problème de la gestion de la mémoire sera examiné plus loin. Pour ce qui est de la taille, une mémoire de 2 ko permet de mémoriser 1024 valeurs de 16 bits correspondant aux deux compteurs (roue droite et roue gauche). 1024 valeurs correspondent à 100 s (car 10 enregistrements par seconde) soit une minute et demi. Pas grandiose ! mais suffisant pour examiner quelques problèmes intéressants avec des étudiants.

Une autre solution qui sera examinée dans un futur projet (voir en fin de chapitre) sera de calculer au fur et à mesure les valeurs de x et y.

Pour l'instant nous allons présenter le module fourni par Digilent qui est destiné à mesurer la vitesse angulaire d'une roue.

Présentation du module de mesure de vitesse de Digilent[modifier | modifier le wikicode]

Mesurer la vitesse à l'aide des modules PModHB5 est une opération qui est proposée par Digilent à l'aide d'un module VHDL appelé "HallTradSpeedDecoder" dont voici l'entité :

entity HallTradSpeedDecoder is
    Port ( ck : in  STD_LOGIC;
           pinSA : in  STD_LOGIC;
           pinSB : in  STD_LOGIC;
           SpeedOut : out  STD_LOGIC_VECTOR (12 downto 0)); -- output for speed and direction 
end HallTradSpeedDecoder;

On voit apparaître une horloge "ck" qui est supposée être à 100 MHz (puisque ce module est destiné à la Nexys3), deux entrées "pinSA" et "pinSB" supposées être en provenance du codeur incrémental et une vitesse de sortie sur 12 bits. La vitesse est directement exprimée en tours par minute et codée en BCD.

Nous allons essayer de modifier ce module pour tenter de réaliser un périphérique capable de mémoriser les positions.

Bien comprendre la base de temps[modifier | modifier le wikicode]

Dans le module original apparaît une base de temps. Nous allons la changer.

La base de temps est (100 000 000 x 60) /(12 x 19) = 26315789. Cette valeur est entièrement liée au fait que l’on a 12 fronts sur les deux entrées pour un tour de l'axe moteur associé à un facteur de réduction de 19. Ainsi, si l’on compte pendant ce temps on aura directement la vitesse en tour par minute.

On veut une mise à jour d'une dizaine de fois par seconde.

Supprimer la gestion BCD[modifier | modifier le wikicode]

La gestion du BCD était utile dans le module original dans la mesure où la vitesse devait être affichée sur des afficheurs sept segments. En ce qui nous concerne l'information sera donnée au processeur qui pourra faire toutes les conversions imaginables par programme.

Cette suppression de la gestion du BCD est simple à réaliser : il suffit de repérer les commentaires d'incrémentation et de décrémentations et de les réaliser en binaire. C'est beaucoup plus simple et l’on peut espérer n'avoir qu'un octet pour mémoriser ce nombre (au lieu de trois).

Utiliser le timer de l'AVR embarqué comme base de temps[modifier | modifier le wikicode]

La base de temps du module original est réalisée par :

--.................. code supprimé pour simplification

constant DivFactor: integer := 26315789; -- division factor

--.................. code supprimé pour simplification

-- cntSpeed Enable signal
cntSpeedEn <= '1' when TimeBaseCounter = 0 else '0';
						 
-------------------------------
-- System Clock Devider Process
-------------------------------
TimeBaseCounterProc: process(ck)
begin
	if ck'event and ck = '1' then
		if TimeBaseCounter /= DivFactor - 1 then 
			TimeBaseCounter <= TimeBaseCounter + 1;
		else TimeBaseCounter <= 0;
		end if;
	end if;
end process;
	
--.................. code supprimé pour simplification

Nous allons supprimer ce timer pour le remplacer par celui de l'AVR décrit dans un chapitre précédent. L'intérêt est que celui de l'AVR est capable de déclencher une interruption. Ainsi à chaque interruption les deux compteurs du côté droit et du côté gauche seront sauvés en mémoire par l'AVR.

Le pseudo-timer de l'AVR ne présente pas de préscalaire. Il est sur 20 bits et déborde avec une fréquence de 95 Hz sur la Nexys3 qui sera d'environ 43 Hz sur la Basys2. Il faut donc la diviser par 4 pour avoir environ 10 mises à jour par seconde. Cela revient tout simplement à ajouter deux bits au timer.

Décodeur incrémental simple[modifier | modifier le wikicode]

Nous avons fini par abandonner l’utilisation du code de Digilent pour utiliser une variation beaucoup plus simple. Il s'agit d'un code proposé par Ken Chapman (le concepteur du picoBlaze) pour décoder un bouton incrémental. Voici donc ce code modifié pour nos besoins :

Ce code fonctionne correctement à priori même sans gestion des rebonds. C'est une particularité des signaux incrémentaux de ne pas nécessiter toujours d'une gestion d'anti-rebonds. Cela reste vrai pour un affichage (qui est une fonction lente) mais si l’on échantillonne le compteur pendant des rebonds, il y aura une erreur qui finira par s'accumuler. Tout ceci nécessite donc une vérification minutieuse.

Interfacer ce module à l'ATMega16[modifier | modifier le wikicode]

Comme d'habitude nous présentons l'interface avec l'ATMega16 sous forme de schéma :

Connecter un décodeur incrémental à l'ATMega16

Cette figure présente une version provisoire de l'interface. Nous avons connecté l'entrée "reset" de notre module à l'entrée "I_CLR" du module io.vhd (même si ce n’est pas visible sur le dessin) et ceci ne peut rester comme cela. La raison ? Rappelons qu’il nous faut :

  • mémoriser les valeurs de la sortie "position" dans une mémoire au fur et à mesure que le temps s'écoule. Cela signifie en particulier qu'une fois la valeur lue, il faut remettre à 0 le compteur position par programme. Ceci est impossible si l'AVR n'a pas accès au "reset".
  • mémoriser peut-être les valeurs dans un grand compteur (au moins 16 bits) ce qui éviterait de perdre des données lors des lectures intermédiaires. En effet un reset en même temps qu'une incrémentation ou décrémentation fera perdre une donnée

Tout ceci est susceptible d'encore évoluer... mais sera certainement stable lorsque nous présenterons le cahier des charges du projet 2014

Réaliser un programme pour les tests[modifier | modifier le wikicode]

Pour tester ce module sur une seule roue, nous avons réalisé le programme suivant :

La compréhension de ce code nécessite de lire la section sur la manette Nunchuk un peu plus loin.

L'idée de ce code est d’utiliser la manette Nunchuk pour déplacer le robot et sans arrêt lire la valeur d'un compteur de position sur 8 bits et de l'afficher directement sur des leds.

Réaliser un programme de traitement de données pour trouver la trajectoire[modifier | modifier le wikicode]

Nous allons essayer de retrouver la trajectoire de notre robot mobile à partir des données fournies par l'AVR. L’idée générale est que l'AVR mémorise les valeurs de ses compteurs régulièrement et qu'une fois terminé ces valeur sont transmise à un PC en vue d'un calcul. Le calcul se faisant sur un PC on peut utiliser les librairies standard du C, en particulier les cosinus et sinus.

Autre moyen de se repérer : le module infrarouge PmodLS1[modifier | modifier le wikicode]

Le module infrarouge permet de se repérer mais pas en absolu. Il permet en effet de suivre une ligne, par exemple mais cela nécessite naturellement que cette ligne soit dessinée. Une autre possibilité est de réaliser un quadrillage et espérer repérer les lignes horizontales et verticales quand on les franchit. Mais réaliser un programme qui réalise cela doit être assez complexe : Nous n'avons jamais rencontré d'exemple sur Internet !

Avant de se lancer dans une programmation compliquée, revenons sur terre pour examiner le PmodLS1. Ce module est branché directement sur le connecteur JA de la Basys2. Mais il demande à être calibré à l'aide d'un réglage de potentiomètre. Le réglage peut se faire facilement à l'aide d'un programme tout simple :

int main(int argc, char * argv[])
{
  while(1){
    PORTD=PINB; 
  }
  return 0;
}

si l’on a pris soin de sortir le PORTD sur les LEDs (ceci se fait dans l'ucf) et de mettre les entrées du connecteur JA sur PINB. Voici l'extrait de l'ucf correspondant :

####### Only for Basys2 ########
#
# Pin assignment for LEDs
NET "Q_PORTD<7>" LOC = "G1" ; # Bank = 3, Signal name = LD7
NET "Q_PORTD<6>" LOC = "P4" ; # Bank = 2, Signal name = LD6
NET "Q_PORTD<5>" LOC = "N4" ;  # Bank = 2, Signal name = LD5
NET "Q_PORTD<4>" LOC = "N5" ;  # Bank = 2, Signal name = LD4
NET "Q_PORTD<3>" LOC = "P6" ; # Bank = 2, Signal name = LD3
NET "Q_PORTD<2>" LOC = "P7" ; # Bank = 3, Signal name = LD2
NET "Q_PORTD<1>" LOC = "M11" ; # Bank = 2, Signal name = LD1
NET "Q_PORTD<0>" LOC = "M5" ;  # Bank = 2, Signal name = LD0
# Loop Back only tested signals #photo transistor
NET "I_PINB<0>" LOC = "B2"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JA1
NET "I_PINB<1>" LOC = "A3"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JA2
NET "I_PINB<2>" LOC = "J3"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JA3
NET "I_PINB<3>" LOC = "B5"  | DRIVE = 2  | PULLUP ; # Bank = 1, Signal name = JA4

Si vous prenez une surface blanche sur laquelle vous avez collé du scotch noir vous devez voir les différentes leds s'allumer et s'éteindre en fonction de leur positions par rapport à la ligne noir.

Pour information nous avons eu l’idée de remplacer le module pmodLS1 par un Arduino micro en 3,3 V pour utiliser ses convertisseurs analogiques numériques et ainsi pouvoir régler les seuils (blanc/noir) de manière indépendante. Mais les étudiants chargés de ce projet en 2014/2015 n'ont pas abouti. Il a été présenté dans ce livre comme un projet autour d'un MSP430 mais nous avons changé au dernier moment de processeur pour des questions de réalisation de circuit imprimé. Mais sans carte digne de remplacer les pmodLS1, nous abandonnons l'infra-rouge pour le moment.

Le gros problème du module PModLS1 est qu'il n'y qu'un potentiomètre pour régler le seuil de chacun des capteurs infrarouges. Un collègue s'est lancé dans une réalisation avec un potentiomètre par capteur. Espérons qu'elle aboutira bientôt.

Cet abandon nous a amené à étudier un autre domaine que nous allons présenter maintenant.

Contrôle du robot par une manette Nunchuk[modifier | modifier le wikicode]

Nous avons déjà utilisé la manette Nunchuk pour contrôler un jeu vidéo dans un autre chapitre. Nous avons donc décidé d’utiliser le travail déjà fait pour notre robot.

Visualisez Using an Arduino and Wii Nunchuck to Control a Robot (Youtube). Vous y verrez un exemple de ce que l’on cherche à faire mais avec un autre robot.

Détails sur la connexion entre la Basys2 et la Nunchuk

Interfacer la manette Nunchuk[modifier | modifier le wikicode]

Pour ceux qui ont lu ce livre en entier cette section paraîtra comme du déjà vue. C'est normal on reprend ici le travail fait dans ce domaine pour le pacman.

Un module capable de gérer TOUTES les données de la Nunchuk est disponible dans un autre chapitre. C'est lui que vous allez utiliser et interfacer dans "io2.vhd". Vous utiliserez deux registres réservés à l’i2c dans l'ATMega16 :

  • TWDR en adresse 0x23 pour les données
  • TWCR en adresse 0x56 pour les commandes et états commande TWEN est en bit b2 (écriture) et l'état TWINT = data présent en bit b7 (lecture)

Voici un condensé sous forme de schéma de ce qu’il y a à faire :

Comment interfacer le module I2C pour lire les données de la Nunchuk

Même si cette figure ne représente pas tous les registres et PORTs que nous utilisons dans le projet pacman complet elle nous montre l'essentiel. L'ensemble du travail consiste donc à câbler et ajouter des lignes dans les deux "case" des deux process de lecture et d'écriture. Ce qui est peut être un peu délicat à comprendre est la réalisation du signal "s_readFIFO" tout en haut de la figure. Ce signal est là pour vider le FIFO au fur et à mesure de ses lectures par le processeur. Il sera relié à l'entrée readFIFO du module NunchukTop.


Réaliser un programme pour utiliser la manette Nunchuk[modifier | modifier le wikicode]

Nous allons vous donner deux sous-programmes pour vous aider à démarrer votre projet.

Lecture du FIFO et demande de données[modifier | modifier le wikicode]

Le sous-programme présenté ci-dessous est appelé avec un nom très long qui, nous l'espérons, sera assez évocateur. Il fait deux choses :

  • lecture du FIFO qui a été rempli par la manette
  • départ d'une nouvelle demande d'information à la Nunchuk

Il devra être inséré dans un boucle (éventuellement infinie) pas trop rapide pour que la fois suiivante le FIFO soit correctement rempli.

Voici le code source correspondant :

//******************************************************************************************************************************
// function readFIFOandAskForNextData()
// purpose: read FIFO and put data in array and start the request of next data
// arguments: array of unsigned char to store FIFO data
// 
// return: 
// note: you cannot understand if you never read the corresponding VHDL code (in io2.vhd)
//******************************************************************************************************************************
void readFIFOandAskForNextData(unsigned char nunchukDATA[]){
  unsigned char i;
  while(!(TWCR & (1<<TWINT))); // attente données dans le FIFO 	
  for (i=0;i<6;i++) //lecture du Nunchuk du FIFO dans tableau
     nunchukDATA[i]=TWDR;
  // on demande une nouvelle lecture par le pérphérique : toutes données seront pour la prochaine fois
  TWCR |= (1<<TWEN); //depart
  TWCR &= ~(1<<TWEN);// arret pour attendre la prochaine fois : voir machine d'états pour comprendre
}

Attente d'un départ[modifier | modifier le wikicode]

Il est naturellement possible de bloquer le robot en attendant un événement sur la Nunchuk.

Voici comment on peut bloquer tout en attendant un appui sur le bouton Z :

//******************************************************************************************************************************
// function waitStart()
// purpose: wait Z button start : Doesn't work properrly at the moment !!!
// arguments:
// 
// return: 
// note: you cannot understand if you never read the corresponding VHDL code (in io2.vhd)
//******************************************************************************************************************************
void waitStart(){
  unsigned char data[6],loop=1;
  while(loop) {
    readFIFOandAskForNextData(data);
    if ((data[5] & 0x01)==0x00) loop=0;
    _delay_ms(10); // car data_present mal géré et on ne prend pas de risque
  }

On rappelle pour comprendre le code, que les deux boutons sont peésents dans le 6° octet retourné par la Nunchuk (donc la case 5 de notre tableau) que les deux boutons sont les deux poids faibles et qu’ils sont à 1 lorsqu’ils ne sont pas appuyés.

Programme Joystick[modifier | modifier le wikicode]

Calibrage Joystick analogique :

  • gauche à fond : 0x20
  • droite à fond : 0xE1
  • repos 7F
  • avant à fond : 0xE0
  • arrière à fond : 0x25
  • repos 0x80

Mise en pratique du repérage dans l'espace avec des codeurs de roues[modifier | modifier le wikicode]

Nous avons présenté de manière théorique le repérage dans l'espace à l'aide de codeurs de roues. Nous allons tenter maintenant de mettre cela en pratique avec notre robot.

Calcul des coordonnées x et y du robot[modifier | modifier le wikicode]

L'objectif est de coupler le module de repérage de position au coprocesseur CORDIC. L'intérêt de ce projet est d’éviter la mémorisation continue des compteurs de roue en calculant au fur et à mesure l'état . La formule de calcul faisant intervenir un cosinus et un sinus, nous allons coupler les compteurs avec le coprocesseur CORDIC du précédent chapitre.

Ajouter un décodeur incrémental[modifier | modifier le wikicode]

Nous allons vous donner une version corrigée du projet de l'année passée. Cette version possède un seul décodeur incrémental pour la roue gauche. On vous demande à partir de ce code :

  • de gérer le reset de manière automatique lors de la lecture de la position
  • d'ajouter un décodeur incrémental pour l'autre roue en le gérant de la même manière. Par exemple la position peut être reliée à PINC (adresse 0x33)
  • ajouter la gestion des afficheurs sept segments (les 4) pour faciliter les tests.

Un programme d'affichage automatique des valeurs de position gauche et droite sera alors, par exemple :

#include <avr/io.h>
#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"

void readFIFOandAskForNextData(unsigned char nunchukDATA[]);
void enAvant(unsigned char droite, unsigned char gauche);
void enArriere(unsigned char droite, unsigned char gauche); 
void tourneDroite(unsigned char gauche);
void tourneGauche(unsigned char gauche);

const unsigned char digit7segs[16]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E};
volatile unsigned char s7segs,selANi=0xFE;
volatile unsigned int toPrint; // l'interruption se servira de cela pour affichier
ISR(TIMER0_OVF_vect) // ISR(_VECTOR(9))
{
    //selANi <<= 1;
    //if (selANi <= 0xEF) selANi = 0xFE;
    PIND = selANi;
    switch (selANi) {
      case 0xFE : DDRD = digit7segs[toPrint&0x000F];selANi=0xFD;break;
      case 0xFD : DDRD = digit7segs[(toPrint>>4)&0x000F];selANi=0xFB;break;
      case 0xFB : DDRD = digit7segs[(toPrint>>8)&0x000F];selANi=0xF7;break;
      case 0xF7 : DDRD = digit7segs[(toPrint>>12)&0x000F];selANi=0xFE;break;
      default : DDRD = 0xBF;selANi=0xFE; 
    }
    TCNT0 = 192; // remet TOV0 à 0 !!!!
}

int main(int argc, char * argv[])
{ 
  unsigned char nunchukDATA[6],vitesseDroite,vitesseGauche,avant,leftPosition,rightPosition,totalLeftPosition,totalRightPosition;
  totalLeftPosition=totalRightPosition=0;
  TIMSK |= 0x01;
  sei();
  TCNT0 = 0; // only way to clear TOV0 at the moment
  while(1){
    readFIFOandAskForNextData(nunchukDATA);
    if (!(nunchukDATA[5] & 1)) {// Z button
      if (nunchukDATA[1]>0xA0) {vitesseDroite=vitesseGauche=nunchukDATA[1]-0x80; avant=1;}
        else  if (nunchukDATA[1]<0x60) {vitesseDroite=vitesseGauche=0x80-nunchukDATA[1];avant=0; }
          else {vitesseGauche=vitesseDroite=0x00;}
      if (nunchukDATA[0]>0xA0) vitesseDroite = vitesseDroite -nunchukDATA[0]+0x80;
      else  if (nunchukDATA[0]<0x60) vitesseGauche = vitesseGauche + nunchukDATA[0]-0x80;
      if (avant) enAvant(vitesseGauche,vitesseDroite); else enArriere(vitesseGauche,vitesseDroite);
    } else if (!(nunchukDATA[5] & 2)) {// C button
      if (nunchukDATA[2]>0xB0) tourneDroite(0x55);
      else if (nunchukDATA[2]<0x55) tourneGauche(0x55);
           else if (nunchukDATA[3]>0x90) enAvant(0x55,0x55);
                else if (nunchukDATA[3]<0x80) enArriere(0x55,0x55);  
		 else enAvant(0x00,0x00);	
	}
	else enAvant(0x00,0x00);
    //PORTA = vitesseGauche;
    leftPosition = PIND;
    rightPosition = PINC;
    PORTD = leftPosition;
    totalLeftPosition += leftPosition;
    totalRightPosition += rightPosition;
    toPrint = totalLeftPosition;
    toPrint <<= 8;
    toPrint += totalRightPosition;
    _delay_ms(100);
}
  return 0;
}

Les sous-programmes manquants ont déjà été publiés dans cette page.

L'idée de ce programme est d'ajouter au fur et à mesure les valeurs des positions droites et gauches de les mettre dans une seule et même variable ("toPrint") qui sera affichée par l'interruption sur les 4 afficheurs 7 segments de la carte Basys 2.


Adapter CORDIC à notre problème[modifier | modifier le wikicode]

Nous allons nous poser la question de savoir comment adapter l'algorithme CORDIC à ce problème. En fait une question nous taraude depuis quelques temps. Nous la posons et tenterons de la résoudre plus tard (Lecteurs revenez de temps en temps voir où l’on en est ?).

Le radian comme le degré ne sont pas des bonnes unités d'angle pour nous. Elles nécessitent, en effet, toutes les deux des calculs arithmétiques compliqués (divisions). Notre unité sera plutôt un nombre de ticks sur une roue (pour parcourir une distance). Par exemple tourner de 90° nécessitera d'exécuter un certain nombre de ticks et c’est ce nombre qui doit devenir notre unité d'angle. Pas besoin de le diviser par l'entre-axe des roues (avantage sur le radian). La question est donc : Comment adapter la partie correspondante au calcul d'angle dans CORDIC à cette nouvelle unité ? et sa question corollaire Quelles sont les valeurs pré-calculées que l’on devra mettre en mémoire ? Si nous répondons correctement à ces deux questions, chacun d'entre-vous pourra l'adapter à son propre robot.

Prenons des exemples concrets

  • pour le robot ASURO décrit dans un autre projet la rotation sur place de 90° se fait avec 27 ticks. Cette valeur nécessite 5 bits seulement, on est très loin des 16 bits du format Q3.13 ! Comment se traduira l'équation de récurrence sur les angles dans ce contexte ?
  • pour le robot Digilent de ce chapitre, ce nombre de ticks est évalué à 114 (si rapport de réduction 1:19 [soit 1,5 tour x 19 x 4]). Un essai direct a donné entre 69 et 74, et on sera encore loin des 16 bits puisque 7 bits suffisent. Ici encore comment cela interviendra-t-il sur l'équation de récurrence des angles ?

Nous avons eu l'intuition par une nuit d'insomnie, qu’il fallait certainement reporter les 7 bits dans la partie haute (disons b13-b7) du format Q3.13 et compléter par des zéros sur les poids faibles (b6-b0). Tout cela en complément à deux. Si l’on s'arrête aux 6 bits initiaux on n'aura jamais assez d'itérations pour le calcul. Ceci ne reste qu'une intuition pour le moment et demande à être expérimenté.

Voir aussi[modifier | modifier le wikicode]