Aller au contenu

Micro contrôleurs AVR/AVR et robotique : mini-Q 2WD

Leçons de niveau 14
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
AVR et robotique : mini-Q 2WD
Icône de la faculté
Chapitre no 14
Leçon : Micro contrôleurs AVR
Chap. préc. :AVR et robotique : pololu
Chap. suiv. :Sommaire
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Micro contrôleurs AVR : AVR et robotique : mini-Q 2WD
Micro contrôleurs AVR/AVR et robotique : mini-Q 2WD
 », n'a pu être restituée correctement ci-dessus.

Nous allons étudier une nouvelle platine robotique dans ce chapitre. Encore une nouvelle, dirons certains, alors que l'étude des deux autres n’est pas complète !

Et bien oui et nous assumons ce choix.

La platine Asuro a un rapport qualité/prix intéressant. C'est la seule des trois platines qui se programme à distance. Cela devrait être toujours le cas car la programmation d'un robot consiste à faire un programme a le mettre dans le robot et à l'essayer. Pour cette dernière étape il faut déconnecter le fil de programmation, ce que nous n'avons pas à faire avec Asuro. Elle a cependant des inconvénients. Parmi ceux-ci la mécanique avec la demi balle de ping-pong n’est pas exceptionnelle. Les batteries sont de type AAA.

La platine Pololu a été abandonnée en enseignement à cause essentiellement du fait qu’il n'y a aucun moyen de savoir de combien a tourné chaque roue... ou plutôt que pour le faire il faut acheter un ensemble roue plus codeur assez coûteux. Les batteries sont elles aussi de type AAA. Les exemples fournis sont très intéressants et peut-être trop intéressants : on a quasiment tout.

La platine mini-Q possède, quant à elle, des codeurs sur ses roues et utilisent des batteries de type AA. Cela a été suffisant pour la choisir comme base de notre enseignement. Signalons que le petit circuit de programmation vendu avec cette platine nous semble trop fragile pour une utilisation intensive avec des étudiants. D'ailleurs la version 2 commercialisée à partir de 2015, utilise maintenant une platine compatible "Leonardo" avec un connecteur USB micro ce qui nous semble plus adapté à une utilisation répétée.

Schéma du miniQ 2WD vous donne son schéma interne (qui comporte une erreur d'ailleurs).

  • La carte Arduino équivalente est la : Arduino nano /w ATMega328
  • Le programmateur d'origine est : AVR ISP
  • Le port série est en général /dev/ACM0 sous Linux

Nous allons présenter dans ce chapitre la programmation du robot miniQ en langage C pur. Nous rappelons que les platine compatible Arduino sont programmables en C pur à partir de l'environnement Arduino. Il suffit d’utiliser un "main()" et l'éditeur de lien se débrouille. Évidemment les primitives Arduino ne sont alors plus disponibles ou alors ont un fonctionnement non prévisible.

Une version plus orientée "langage Arduino" de ce chapitre est en cours de construction aussi. Elle est disponible ICI dans le Wiki de l'IUT de Troyes.

Boutons poussoirs et Conversion Analogique Numérique (Robot version 1)

[modifier | modifier le wikicode]

L'entrée de conversion analogique numérique numéro 5 est reliée comme le montre la figure ci-dessous.

Push buttons and CAN
Push buttons and CAN

Utilisation du timer 0 pour la MLI

[modifier | modifier le wikicode]

Un chapitre entier a été consacré au timer 0 dans ce livre. Vous avez intérêt à le relire avant d'aller plus loin.

Certains se demandent probablement s'il ne serait pas mieux d’utiliser le timer 1 pour la réalisation de la MLI (Modulation de Largeur d'Impulsion). C'est probablement vrai, mais c’est impossible à réaliser sans interruption. Nous allons essayer d'expliquer pourquoi maintenant.

Commande des deux moteurs

[modifier | modifier le wikicode]
Module de commande des moteurs

Le timer 0 comme les autres timers permet une commande simultanée de deux moteurs. Puisqu’il s'agit du timer 0, les deux bits associés sont OC0A en PD6 (bit 6 du PORTD) et OC0B en PD5. Si le concepteur du robot a câblé ces deux bits comme commande des moteurs il est naturel d’utiliser le timer 0. C'est le cas comme on peut le voir sur le schéma de principe. Vous voyez bien PD5 connecté à EN1... mais par contre PD6 connecté à IN2. C'est forcément une erreur : PD6 doit être connecté à EN2. L'erreur est naturellement sur le schéma et non sur la carte, heureusement pour nous !

Côté puissance, la commande nécessite deux signaux :

  • ENi qui reçoit donc la MLI
  • INi qui détermine le sens de rotation

avec i=1 ou i=2 pour chacun des moteurs.

Le code d'initialisation de la PWM (ou MLI) est examiné maintenant.

Code pour initialiser la MLI

[modifier | modifier le wikicode]

Rappelons avant de commencer comment est câblé le module de commande des moteurs avec le processeur à l'aide d'une figure ci-contre)

Interfacer l'ATMega368 aux modules de puissance (Version corrigée)

Voici le code fourni avec la platine est le suivant :

void pwm_init(void)
{ 
	TCCR0A = 0XA3; 
	TCCR0B = 0X03;//clk/64
	TCNT0 = 0X00; //initialisation timer 0
	TIMSK0 = 0X00;//Aucune interruption timer 
}

Ce que fait la ligne TCCR0B=0X03 est décrit dans le commentaire. On rappelle que la division de l'horloge est choisie par les trois bits de poids faibles de TCCR0B

  • (000) arrêt
  • (001) division par 1
  • (010) division par 8
  • (011) division par 64
  • (100) division par 256
  • (101) division par 1024
  • (110) front descendant de T0
  • (111) front montant de T0

T0 est un bit qui n’est pas placé au même endroit suivant la famille.

Ce que fait la ligne TCCR0A = 0XA3 est un peu plus complexe et nécessite la lecture de deux tableaux (qui sont rappelés). Voici le premier pour le poids fort de TCCR0A.

Mode PWM rapide et comparaison
COM0A1 COM0A0 Description
0 0 Opération Normale PORT, OC0A déconnecté
0 1 WGM02=0 Opération Normale PORT, OC0A déconnecté
0 1 WGM02=1 Basculement de OC0A sur la comparaison
1 0 Mise à 0 de OC0A sur la comparaison et à 1 à BOTTOM
1 1 Mise à 1 de OC0A sur la comparaison et à 0 à BOTTOM

qui est identique pour COM0B1 et COM0B0. Les deux bits de poids fort du 0xA3 choisissent donc le troisième mode, et idem pour B. On utilise donc pour les deux moteurs la mise à zéro de OC0A (et aussi OC0B) qui sont les deux sorties reliées à EN1 et EN2.

L'autre tableau à lire est :

Description des bits pour la génération de forme d'onde
Mode WGM02 WGM01 WGM00 Mode de fonctionnement Bas si Mise à jour de OCRAx si Drapeau TOV0 positionné si
0 0 0 0 Normal 0XFF immédiatement MAX
1 0 0 1 PWM à phase correct OXFF TOP BOTTOM
2 0 1 0 CTC OCR0A immédiatement MAX
3 0 1 1 PWM rapide 0XFF BOTTOM MAX
4 1 0 0 Reservé - - -
5 1 0 1 PWM à phase correct OCR0A TOP BOTTOM
6 1 1 0 Reservé - - -
7 1 1 1 PWM rapide OCR0A BOTTOM TOP

Même si WGM02 est dans un autre registre, il n'y a aucune difficulté de remarquer que c’est la ligne 3 (PWM rapide) qui est choisie. Ceci est facilement trouvé avec la figure qui est rappelée :

La comparaison avec le timer 0

Code pour utiliser les moteurs

[modifier | modifier le wikicode]

Il est possible de trouver des exemples de programmation du robot dans cette page commerciale par exemple. À partir de ces exemples nous présentons un code source légèrement modifié pour être conforme au schéma de principe. Nous avons aussi renommé les paramètres pour savoir qui est moteur gauche et droit et diminué aussi leur taille.

//*************** Motor_Control ***********************
//* utilise le timer0 pour commander les deux moteurs *
//* nécessite un appel de pwm_init() avant 1er appel *
//*****************************************************
void Motor_Control(char MG_DIR,char MG_EN,char MD_DIR,char MD_EN)
{
  //////////MGauche////////////////////////
  if(MG_DIR==FORW)//si vers l'avant 
    PORTD |=(1<<7);//IN2 passé à 1
  else
    PORTD &=~(1<<7);//IN2 passé à 0  
  OCR0A = MG_EN;//Rapport cyclique
 
  ///////////MDroit//////////////////////
  if(MD_DIR==FORW)//si vers l'avant 
    PORTD |=(1<<4);//IN1 passé à 1 
  else
    PORTD &=~(1<<4);//IN1 passé à 0  
  OCR0B = MD_EN;//Rapport cyclique
}

Les deux directions sont définies avec les paramètres MG_DIR et MD_DIR avec G pour Gauche et D pour Droite). Les vitesses des moteurs sont réglées avec MG_EN et MD_EN variant de 0 (arrêt) à 255 (pleine vitesse).

Travail à faire : Exercice 1

[modifier | modifier le wikicode]

Vous devez réaliser une commande du robot pour qu’il réalise un huit (deux cercles connectés par un point). Vous choisirez le rayon de vos cercles en jouant sur les rapports cycliques. Puis vous jouerez sur les temporisations pour gérer tout cela. Bien entendu, le fait qu’il n'y ait pas d'information de retour sur l'endroit où vous êtes complique un peu la mise au point.

1°) Voici un code complet d'initialisation et d'utilisation des moteurs :

//******************** Ce code sera modifié pour devenir du C pur
//******************** Revenez voir de temps en temps
//******************** Ce code n'a pas encore été essayé
#include <avr/io.h> 
#undef F_CPU
#define F_CPU 16000000UL
#include <util/delay.h>
#define FORW 1
#define BACK 0
//***** declaration des prototypes
void setup();
void pwm_init(void);
void Motor_Control(char MG_DIR,char MG_EN,char MD_DIR,char MD_EN);

int main(){
  setup();
  Motor_Control(1,100,1,100);
  _delay_ms(5000);
  Motor_Control(0,0,0,0);
  _delay_ms(1000);
  Motor_Control(0,100,0,100);
  _delay_ms(5000);
  Motor_Control(0,0,0,0);
  while(1);
  return 0;
}

void setup() {
  DDRD = 0XF2;//PORTD
  DDRB = 0XFE;//PORTB en sortie sauf b0
  pwm_init();
}

void pwm_init(void) { 
	TCCR0A = 0XA3; 
	TCCR0B = 0X03;//clk/64
	TCNT0 = 0X00; //initialisation timer 0
	TIMSK0 = 0X00;//Aucune interruption timer 
}

void Motor_Control(char MG_DIR,char MG_EN,char MD_DIR,char MD_EN) {
  //////////MGauche////////////////////////
  if(MG_DIR==FORW)//si vers l'avant 
    PORTD |=(1<<7);//IN2 passé à 1
  else
    PORTD &=~(1<<7);//IN2 passé à 0  
  OCR0A = MG_EN;//Rapport cyclique 
  ///////////MDroit//////////////////////
  if(MD_DIR==FORW)//si vers l'avant 
    PORTD |=(1<<4);//IN1 passé à 1 
  else
    PORTD &=~(1<<4);//IN1 passé à 0  
  OCR0B = MD_EN;//Rapport cyclique
}

Modifier ce code pour réaliser un simple cercle. Seule la partie main() est à modifier !

Panneau d’avertissement

Vos essais seront faits sur la table de travail.

  • Il y a des risques de chutes ! Soyez donc prudents !
  • Ne mettez pas de vitesses excessives !
  • N'oubliez pas que votre câble USB n’est pas infini !

2°) Réaliser maintenant votre huit (deux cercles se touchant par un point). On pourra réaliser tout cela dans le noir avec une prise de trajectoire par appareil photo à pose longue.

3°) Étude du rayon du cercle. Nous avons vu en cours que Essayez d'étudier si le rayon peut s'écrire et désignent le rapport cyclique des moteurs gauche et droit (qui varient entre 0 et 255) et K est une constante à déterminer.

Utiliser les détecteurs infra-rouge frontaux

[modifier | modifier le wikicode]

Le robot dispose de cinq détecteurs infra-rouge pour le suivi de lignes. Ce n’est pas ces détecteurs que nous allons étudier mais un détecteur et deux émetteurs dirigés respectivement vers la droite et vers la gauche. Cela devrait nous permettre de détecter des obstacles. Le détecteur est relié à INT0/PB0 et peut donc être géré par une interruption.

L'idée générale est d'envoyer un train d'impulsions en émission et de compter ce que l’on reçoit.

Code d'émission

[modifier | modifier le wikicode]

Vous pouvez utiliser le code suivant :

// train d'impulsion Gauche
void L_Send(void) {
  int i;
  for(i=0;i<24;i++) {
    PORTB &=~(1<<1);//b1 à 0
    _delay_us(12);//attente
    PORTB |=(1<<1);//b1 à 1
    _delay_us(12);//attente
  }
}
//train d'impulsion Droit
void R_Send(void) {
  int i;
  for(i=0;i<24;i++) {
    PORTB &=~(1<<2);//b2 à 0
    _delay_us(12);
    PORTB |=(1<<2);//b2 à 1
    _delay_us(12);
  }
}

Si vous regardez attentivement le schéma de principe vous déduisez facilement que l'émission se fait à l'aide d'un zéro.

Réception par interruption

[modifier | modifier le wikicode]

L'interruption est d’abord initialisée à l'aide du code :

void pcint0_init(void) {
	PCICR = 0X01;
	PCMSK0 = 0X01;//Autorisation de l'interruption INT0
}

Le code de l'interruption est tout simple :

ISR(PCINT0_vect) {
	count++;//on incrémente le compteur pour chaque impulsion reçue
}

setup() associé

[modifier | modifier le wikicode]

La terminologie "setup()" employée dans le titre de cette section est naturellement issue de l'environnement Arduino. Même si nous programmons en C, l’idée d'imposer une architecture de programme divisée en deux parties (setup() et loop()) nous semble pertinente. C'est pourquoi nous la reprenons ici.

Les lignes de code associé à la détection infra-rouge à mettre dans la partie setup() du programme sont :

void setup() {
  DDRD = 0XF2;//PORTD
  DDRB = 0XFE;//PORTB en sortie sauf b0
  pcint0_init();
  sei();
}

Un code pour éviter les obstacles

[modifier | modifier le wikicode]

Tout ce qui précède peut être utilisé pour détecter et donc éviter les obstacles. L’idée générale est très simple :

  • on initialise le compteur "count" à 0
  • On émet à droite 24 impulsions plusieurs fois, on compte ensuite ce que l’on reçoit (cela est réalisé automatiquement à l'aide d'une interruption).
  • si c’est supérieur à 20, on recule suffisamment on tourne du bon côté et on repart en marche avant.
  • on initialise le compteur à 0
  • On émet à gauche 24 impulsions plusieurs fois, on compte ce que l’on reçoit.
  • si c’est supérieur à 20, on recule suffisamment on tourne du bon côté et on repart en marche avant.

La mise au point de ce code peut prendre beaucoup de temps.

Indications : le code pour émettre plusieurs fois peut être par exemple :

count = 0;
for(i=0;i<20;i++) { //left transmitter sends 20 pulses
L_Send40KHZ();
delayMicroseconds(600);
}
if(count>DISTANCE_IR)//if recieved a lot pulse , it means there's a obstacle

Ce code émet 20 fois 24 impulsions.

Le DISTANCE_IR du "if(count>DISTANCE_IR)" peut être défini par une constante. Divers essais chez moi on montré que la valeur 35 est un bon point de départ. Si vous augmentez cette valeur vous détecterez les obstacles plus près et si vous la diminuez ce sera le contraire. Il est défini comme une constante DISTANCE_IR dans le code de la section suivante et nous vous encourageons très fortement d’utiliser cette technique pour la mise au point.

Travail à faire : exercice 2

[modifier | modifier le wikicode]

Vous devez mettre au point un code qui permette de ne jamais percuter des obstacles, c'est-à-dire qui évite les obstacles qu’il détecte à droite comme à gauche.

Indication : à ce stade vous n'avez pas de documentation sur l’utilisation complète des PORTs B et D. On vous demande d’utiliser le code :

#define SPEED 60
#define DISTANCE_IR 35
 
int count;//count the motor speed pulse
 
void setup() {
  pcint0_init();
  sei();               //enable the interrupt
  Motor_Control(FORW,SPEED,FORW,SPEED);//run motor
}
int main() {
  setup();
  while(1) {
    Eviter_Obstacle();//obstacle avoidance
  }
}

Ce code doit être naturellement complété par le code donné dans la section Utiliser les détecteurs infra-rouge frontaux ainsi que par l'écriture du sous-programme "Eviter_Obstacle()" qui est le cœur même de ce que l’on cherche à faire. Vous pouvez noter qu'une constante SPEED est définie à 60 ce qui évite de faire de la casse. Une constante DISTANCE_IR est définie à 35 et devra être utilisée dans le test après réception.

Tout ce qui précède peut être utilisé pour détecter et donc éviter les obstacles. L’idée générale est très simple :

  • on initialise le compteur "count" à 0
  • On émet à droite 24 impulsions plusieurs fois, on compte ensuite ce que l’on reçoit (cela est réalisé automatiquement à l'aide d'une interruption).
  • si c’est supérieur à 20, on recule suffisamment on tourne du bon côté et on repart en marche avant.
  • on initialise le compteur à 0
  • On émet à gauche 24 impulsions plusieurs fois, on compte ce que l’on reçoit.
  • si c’est supérieur à 20, on recule suffisamment on tourne du bon côté et on repart en marche avant.

La mise au point de ce code peut prendre beaucoup de temps.

Indications : le code pour émettre plusieurs fois peut être par exemple :

count = 0;
for(i=0;i<20;i++) { //left transmitter sends 20 pulses
    L_Send40KHZ();
    delayMicroseconds(600);    
}

if(count>DISTANCE_IR)//if recieved a lot pulse , it means there's a obstacle

Ce code émet 20 fois 24 impulsions.

Le DISTANCE_IR du "if(count>DISTANCE_IR)" peut être défini par une constante. Divers essais chez moi on montré que la valeur 35 est un bon point de départ. Si vous augmentez cette valeur vous détecterez les obstacles plus près eet si vous la diminuez ce sera le contraire. Il est défini comme une constante DISTANCE_IR dans le code de la section suivante et nous vous encourageons très fortement d’utiliser cette technique pour la mise au point.

Travail à faire : exercice 3

[modifier | modifier le wikicode]

On vous donne un labyrinthe dont vous ne connaissez pas la forme et on vous demande de le parcourir, si possible sans heurter les bords.

Utilisation de l'infra-rouge pour suivre une ligne

[modifier | modifier le wikicode]

Le robot mini-Q est équipé de cinq couples leds/photo-transistors (infra-rouge) dirigés vers le bas. Ils sont destinés à mesurer la réflexion du support sur lequel roule le robot. Si vous prenez un support blanc et que vous fixez un scotch noir vous arriverez à détecter le support noir qui n'a pas la même réflectance infra-rouge que le support blanc.

La mise au point sera difficile car sur le robot que nous avons essayé les sensibilités des capteurs ne sont pas identiques. La calibrage de chacun des capteurs peut se faire par le programme simple :

//*********** Code Arduino à transformer en C pur pour ce document
int data[5]={0X00,0X00,0X00,0X00,0x00};//save the analog value
void setup(){
Serial.begin(9600);
}
void Read_IRLine(void){ //read the analog value
// les capteurs A0, A1 sont peu sensibles sur le robot essayé !!!
data[0]=analogRead(A0); // gauche
data[1]=analogRead(A1);
data[2]=analogRead(A2);
data[3]=analogRead(A3);
data[4]=analogRead(A7); // droite
}

void loop(){
Read_IRLine();
for (char i=0;i<5;i++){
Serial.print(data[i]);
Serial.print(" ");
}
Serial.println(" ");
delay(1000);
}

qui affichera toutes les secondes les valeurs des 5 photo-transistors infra-rouges. On ne désire pas utiliser ce programme puisque nous avons comme objectif de faire du C pur. Vous allez alors constater que le ce simple programme est bien plus long en C pur, mais nous allons le réaliser en plusieurs étapes.

Travail à réaliser : Exercice 4

[modifier | modifier le wikicode]

Le travail à réaliser se décompose en plusieurs questions.

Ce travail consiste à utiliser la liaison série pour calibrer vos 5 capteurs. Calibrer veut dire ici, trouver les seuils en-dessous desquels on considère que l’on a du noir. Ces seuils ne sont pas tous identiques pour tous les robots et pour un même robot pour tous les capteurs. Pour les trouver, vous positionnez une fois le capteur sur du blanc, une fois sur du noir et le seuil sera à mi-chemin.

Un moyen de faire cela de manière assez facile à ajuster consiste à définir des constantes comme ci-dessous :

#define SEUILGG 985
#define SEUILG 970
#define SEUIL 750
#define SEUILD 700
#define SEUILDD 750


On désire calibrer les cinq capteurs avec une plaque blanche de test comprenant une piste en chatterton noir. Pour cela on va d’abord régler les problèmes de la liaison série. On vous demande de partir de la librairie ci-dessous (voir le chapitre correspondant) :

#define F_CPU 16000000	// 16 MHz oscillator.
#define BaudRate 9600
#define MYUBRR (F_CPU / 16 / BaudRate ) - 1 
 
unsigned char serialCheckRxComplete(void)
{
	return( UCSR0A & _BV(RXC0)) ;		// nonzero if serial data is available to read.
}
 
unsigned char serialCheckTxReady(void)
{
	return( UCSR0A & _BV(UDRE0) ) ;		// nonzero if transmit register is ready to receive new data.
}
 
unsigned char serialRead(void)
{
	while (serialCheckRxComplete() == 0)		// While data is NOT available to read
	{;;} 
	return UDR0;
}
 
void serialWrite(unsigned char DataOut)
{
	while (serialCheckTxReady() == 0)		// while NOT ready to transmit 
	{;;} 
	UDR0 = DataOut;
}
 
void serialInit(void) {
  //Serial Initialization
 	/*Set baud rate 9600 */ 
	UBRR0H = (unsigned char)(MYUBRR>>8); 
	UBRR0L = (unsigned char) MYUBRR; 
	/* Enable receiver and transmitter */
	UCSR0B = (1<<RXEN0)|(1<<TXEN0); 
	/* Frame format: 8data, No parity, 1stop bit */ 
	UCSR0C = (3<<UCSZ00);	
}

On vous demande de réaliser un sous-programme "void serialWrite16(uint16_t can)" capable d'afficher la variable 16 bits "can" en clair et en décimal sur votre liaison série.

Indication : voici un code qui affiche en hexadécimal :

void serialPutsHexa(uint16_t can){
  int8_t i=0,digit=0;
  char char_digit;
  serialWrite('0');serialWrite('X');
  for (i=12;i>-1;i-=4) {// only four digits
     digit = (can >> i) & 0x0F;
     char_digit=digit+0x30;
     if (char_digit>0x39) char_digit += 7;
     serialWrite(char_digit);
  }  
}

Il suffit donc de le modifier en calculant les milliers, les centaines, les dizaines et les unités et en les transformant en caractères.

Il va nous falloir maintenant lire les convertisseurs analogique numérique. Le prototype de ce que l’on cherche à faire est :

uint16_t readAnalog(uint8_t mux);

Indication :

void initAnalog() {
  ADMUX = (1 << REFS0);//reference Vcc
  // Start the ADC unit,
  // set the conversion cycle 16 times slower than the duty cycle
  ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADSC);
}

uint16_t readAnalog(uint8_t mux) {
  uint16_t result;
  ADMUX |= (mux & 0x0F); // mettre les 1 sans modifier
  ADMUX &= ~(mux & 0x0F); // mettre les 0 sans modifier
  ADCSRA |= (1 << ADSC); // start
  while (ADCSRA & (1 << ADSC));
  result = ADCH;
  result <<= 8;
  result += ADCL;
  return result;
}

Une fois les seuils de chaque capteur trouvé, convertir l’ensemble des données en un nombre binaire sur 5 bits. Le bit de poids faible sera à 1 si une ligne noire est présente sous le capteur A0 et ainsi de suite jusqu'au bit b4. Tester de nouveau avec un affichage binaire.

Indication : vous pourrez vous inspirer du code Arduino d'un un travail dévaluation donné à l'IUT de Troyes et en particulier du code suivant :

int data[5]={0X00,0X00,0X00,0X00,0x00};//save the analog value
#define SEUILGG 941
#define SEUILG 500
#define SEUIL 400
#define SEUILD 880
#define SEUILDD 600
char Read_IRLine(void){ //read the analog value
    char result=0;
    data[0]=readAnalog(0); // gauche
    data[1]=readAnalog(1); 
    data[2]=readAnalog(2);
    data[3]=readAnalog(3);
    data[4]=readAnalog(7); // droite
    if (data[0] < SEUILGG) result |= 0x01; else result &= 0xFE;
    if (data[1] < SEUILG) result |= 0x02; else result &= 0xFD;
    if (data[2] < SEUIL) result |= 0x04; else result &= 0xFB;
    if (data[3] < SEUILD) result |= 0x08; else result &= 0xF7;
    if (data[4] < SEUILDD) result |= 0x10; else result &= 0xEF;
    return result;
}

qui convertit les 5 données analogiques des capteurs infra-rouge en un nombre binaire sur 5 bits. Ce nombre peut ainsi être utilisé dans un "switch". Cherchez les valeurs autorisées de Read_IRLine.

Vous commencerez ensuite à essayer de réaliser un programme qui suit une ligne fermée en avançant. Le principe est d'accélérer le bon moteur quand on dévie de la ligne.

La valeur 0x00 sera traitée comme valeur aberrante dans un premier temps : on a perdu la piste donc on s'arrête. Maisdans un deuxième temps, vous pouvez aussi faire ce que nous a fait un étudiant : faire marche arrière dans ce cas précis... et c’est pas mal du tout comme idée.

Capteurs infra-rouge sur les roues pour se repérer

[modifier | modifier le wikicode]

Les roues sont réalisées de telle façon qu'un photo-coupleur infrarouge permette de compter des ticks. Il y en a environ 8 par tours, cela donne une résolution assez faible. Nous ne chercherons donc pas à réaliser des calculs de trajectoires précis.

Le travail de calcul de trajectoire à partir de données internes s’appelle Navigation à l'estime et cela réalise une intégration (d'un point de vie mathématique). Le meilleur moyen de s'en persuader est d'imaginer les conséquences de petites erreurs sur des points de mesure. L'incertitude de la position augmente au fur et à mesure du déplacement.

Pourquoi le problème de se repérer 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.

Code de départ

[modifier | modifier le wikicode]

Voici le code permettant d’utiliser les capteurs. Il commence par une initialisation :

//encoder value
void interrupt01_init(void)
{
  EICRA = 0X0F;
  EIMSK = 0X03;
}

Puis vient lesinterruptions proprement dites qui incrémentent chacune son compteur.

ISR(INT0_vect)//motor encoder interrupt
{
  if(++count_r==120)
    count_r=0;
}
ISR(INT1_vect)
{
  if(++count_l==120)
    count_l=0;
}

Travail à faire : exercice 5

[modifier | modifier le wikicode]

On vous demande d'exécuter votre code de suivi de ligne sur une forme inconnue d'avance et d’utiliser les photo-transistors infra-rouge des roues pour reconstruire la trajectoire suivie.

  • l'évaluation du programme sera fait sans référence particulière : vous ne savez pas quand vous revenez au point de départ (aucune marque sur le circuit)
  • l'évaluation du programme est fait avec une référence particulière. Une barre perpendiculaire à la trajectoire (donc détectable avec les photo-transistors) est présente au point de départ et au point d'arrivée. Cette contrainte forte devrait permettre de diminuer les erreurs sur la trajectoire.

Travail à faire : exercice final

[modifier | modifier le wikicode]

Votre robot sera enfermée dans une pièce/labyrinthe (maquette de taille en rapport avec le robot) ayant une forme non connue à l'avance. Il devra se déplacer en évitant les murs et essayer en même temps de déduire la forme de la pièce.

MiniQ version 2

[modifier | modifier le wikicode]

Voici un site commercial qui propose de la documentation et une présentation de la version 2 (compatible Arduino Leonardo) du Robot mobile miniQ. Contrairement à la version 1, tous les codes proposés comme exemples sont fournis en langage Arduino. Ce qui est présenté ici est, par contre, en code C standard.

On rappelle que l’utilisation du code C nécessite une entête. Elle sera de la forme :

#include <avr/io.h>
//#include <avr/interrupt.h>

#undef F_CPU
#define F_CPU 16000000UL
#include <util/delay.h>

Boutons poussoirs et Conversion Analogique Numérique (Robot version 2)

[modifier | modifier le wikicode]
Boutons poussoirs et conversion analogique numérique dans la deuxième version du MiniQ

Une nouvelle version du robot MiniQ est disponible depuis 2015. La grande différence est qu’il est architecturé autour du 32U4 au lieu de l'ATMega328. Cela peut paraître anecdotique, mais les périphériques du 32U4 sont relativement différents de ceux du 328 en ce qui concerne la conversion analogique numérique et la MLI.

Le robot MiniQ (version 2) possède trois interrupteurs montés comme dans la figure ci-contre. Le point important est le numéro du convertisseur (AN8). Notez aussi le nombre d'interrupteurs : 3 (qui sont appelés de KEY1 à KEY3. KEY1 est du côté gauche (avec la convention usuelle du regard dirigé vers l'avant).

Trouver l'interrupteur appuyé

[modifier | modifier le wikicode]

Exercice 1 :

Conversion analogique numérique pour ATMega32U4

1°) Montrer que vous savez faire un programme Arduino qui utilise A6 (Conversion Analogique Numérique) et affiche dans un moniteur série les différentes valeurs suivant le/les boutons appuyés. Remplir alors le tableau ci-dessous.

Key3 Key2 Key1 ADC8 (V et N)
0 0 0 ?
0 0 1 ?
0 1 0 ?
0 1 1 ?
1 0 0 ?
1 0 1 ?
1 1 0 ?
1 1 1 ?

Faire un programme qui affiche correctement (en clair) la touche appuyée. Pour cela on cherchera les points milieux des valeurs trouvées dans le tableau.

2°) Écrire un sous-programme en C pur d'initialisation de la conversion analogique numérique.

Conversion en binaire

[modifier | modifier le wikicode]

Continuer l'exercice précédent en réalisant un sous-programme qui retournera un nombre binaire représentant l'interrupteur appuyé.

b7 b6 b5 b4 b3 b2 b1 b0
0 0 0 0 0 key3 key2 key1

Ce tableau indique que quand key1 est appuyé on a un 1 dans b0 tandis que si c’est key3 le 1 correspondant est dans b2....

Le prototype de la fonction demandée sera :

unsigned char conversion(int can);

Touches incrémentation/décrémentation

[modifier | modifier le wikicode]

Réaliser un sous-programme capable d'incrémenter/décrémenter une variable (de type unsigned char donc variant de 0 à 255) en fonction de l'appui sur les boutons.

Utilisation du timer 4 pour commander les moteurs

[modifier | modifier le wikicode]

Les moteurs du miniQ version 2 sont commandés par le timer4. Il s'agit d'un timer assez complexe, qui peut fonctionner sur 10 bits et permet la commande de transistors de puissance FET (commandes inversées avec temps morts). Heureusement, nous nous contenterons du fonctionnement simple sur 8 bits.

Un exemple pour le moteur gauche

[modifier | modifier le wikicode]

Le moteur gauche est commandé par la broche PC6(=/OC4A) du PORTC.

MLI avec le timer4 pour la sortie /OC4A
void initMoteurG() { 
  DDRC |=0x40; // PC6 en sortie
  TCCR4B |= 0x06; // division par 64
  TCCR4A |= 0x42; // PWM en et /OC4A
  TCCR4E |= 0x01; // 0C4OE0 pour /OC4A
}

void setVitesseG(unsigned char speedG) {
  OCR4A = 255-speedG; // rapport cyclique
}

main() {
  // put your setup code here, to run once:
  initMoteurG();
  setVitesseG(155); 
  while(1); 
}

La sélection marche avant marche arrière n’est pas réalisée. Passons maintenant à l'autre moteur.

Un exemple pour le moteur droit

[modifier | modifier le wikicode]

Le moteur droit est connecté à la sortie PD7/OC4D du PORTD. Voici le code permettant de gérer correctement le moteur droit.

MLI avec timer 4 pour la sortie OC4D
void initMoteurD() { 
  DDRD |= 0x80; // PD7 en sortie
  TCCR4B |= 0x06; // division par 64
  TCCR4C |= 0x09; // PWM en et OC4D et sans /OC4D
  TCCR4E |= 0x20; // 0C4OE5 pour OC4D
}

void setVitesseD(unsigned char speedD) {
  OCR4D = speedD; // rapport cyclique
}

Travail à faire

[modifier | modifier le wikicode]

Exercice 2 : Reprendre les exemples de code et refaire une initialisation commune pour les deux moteurs. On prendra alors soin de remplacer l'hexadécimal par le nom des bits.

Exercice 3 : On désire reprendre le travail de lecture des interrupteurs en C (voir plus haut). Écrire un programme C capable de lire ADC8 (ATTENTION c’est A6 pour l'Arduino). L'appui sur le bouton gauche KEY1 fera tourner le moteur gauche, tandis que l'appui sur le bouton droit fera tourner le moteur droit.

Exercice 4 : refaire un sous-programme qui gère les deux moteurs à la fois et le sens. On a déjà utilisé ce genre de sous-programme, il avait le prototype :

void Motor_Control(char MG_DIR,char MG_EN,char MD_DIR,char MD_EN);

Indication : les commandes de sens pour respectivement le moteur droit et le moteur gauche sont les broches PE6 et PD6.

Suivi de lumière en C

[modifier | modifier le wikicode]

Dans cette section nous désirons réaliser un programme permettant au robot de suivre une source de lumière.

Programme de départ (version Arduino)

[modifier | modifier le wikicode]
void setup() {
  pinMode(5,OUTPUT);//EN1 init the pins for motor
  pinMode(12,OUTPUT);//IN1=M1_DIR
  pinMode(6,OUTPUT);//EN2
  pinMode(7,OUTPUT);//IN2=M2_DIR
}

void MotorG(unsigned char M1_DIR,unsigned char M1_EN)//control the motor
{
  //////////M1-->left motor////////////////////////
  if(M1_DIR==0)//en avant
    digitalWrite(12,0);
  else
    digitalWrite(12,1);
  if(M1_EN==0)
    analogWrite(5,LOW);
 else
    analogWrite(5,M1_EN);
}

void MotorD(unsigned char M2_DIR,unsigned char M2_EN)//control the motor
{
  //////////M2-->right motor////////////////////////
  if(M2_DIR==0)//en avant
    digitalWrite(7,0);
  else
    digitalWrite(7,1);
  if(M2_EN==0)
    analogWrite(6,LOW);
 else
    analogWrite(6,M2_EN);
}

void loop() {
  // put your main code here, to run repeatedly:
  int n;
  unsigned char keys;
  n=analogRead(A5);
  if (n > 800) {
    MotorG(0,75);
    MotorD(1,75);
  } else if (n > 600) {
    MotorG(0,50);
    MotorD(1,50);    
  } else if (n > 350) {
    MotorG(0,0);
    MotorD(0,0);  
  } else if (n > 350) {
    MotorG(1,50);
    MotorD(0,50);
  } else {
    MotorG(1,75);
    MotorD(0,75);
  }
  delay(50);
}

Éliminer toute la partie non nécessaire pour calibrer les capteurs de lumière en utilisant la liaison série.

Reprendre le code donné et utiliser toute la partie d'utilisation des moteurs en C déjà réalisé auparavant.

Réaliser l'initialisation convertisseur analogique numérique CAN0. On est prêt maintenant à retirer le setup() et le loop() pour ne faire qu'un main.

Suivi de ligne en C

[modifier | modifier le wikicode]

Le premier problème à résoudre est de calibrer les cinq seuils des cinq capteurs infrarouges. L'expérience nous a montré qu'ils peuvent avoir des réponses assez différentes et donc que cette étape est nécessaire.

Comment calibrer correctement les capteurs pour trouver les seuils

[modifier | modifier le wikicode]

Ceci peut être réalisé par un simple programme Arduino qui nous permet de garder la liaison série :

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  int data[5];
  uint8_t i;
  // put your main code here, to run repeatedly:
  for (i=0;i<5;i++)
   {
    data[i]=analogRead(i);//store the value read from the sensors
    Serial.print(data[i]);Serial.print(" - ");
  }
  Serial.println();
  delay(500);
}

mais nécessite de graver la séquence d'initialisation (bootloader) si vous utilisez un programmateur. Cela prend environ 1mn.

Un essai avec un Robot m'a donné les résultats suivants.

Position du capteur extrême gauche gauche centre droite extrême droite
broche µc 36 37 38 39 40
entrée CAN ADC7 ADC6 ADC5 ADC4 ADC1
entrée Arduino A0 A1 A2 A3 A4
BLANC 836 982 980 974 970
NOIR 210 390 338 305 305
Seuils 523 686 659 639 637

Abandon de la gestion des convertisseurs Analogique sous Arduino

[modifier | modifier le wikicode]

Nous voulons maintenant faire la même chose que le programme précédent mais avec une gestion des Convertisseurs Analogiques Numériques en C.

void initCANs() {
  ADMUX |= (1<<REFS0); //reference à 5V
  ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // division par 128 : pas pressé
  ADCSRA |= (1<<ADEN);//|(1<<ADATE); // demarrage logique de conversion
  ADCSRA |= (1<< ADSC); // start first conversion
  while((ADCSRA & (1<< ADSC)) != 0); // attente fin conversion
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  initCANs();
}


void loop() {
  int data[5];
  uint8_t muxs[]={7,6,5,4,1};
  uint8_t i;
  // put your main code here, to run repeatedly:
  for (i=0;i<5;i++){
   // choix des CANx avec MUX2,MUX1,MUX0
    ADMUX &= 0xF8; // mise à 0 de MUX2 MUX1 MUX0
    ADMUX |= muxs[i]; //choix  CANx
    delay(1); // au cas où un peu de temps est nécessaire
    ADCSRA |= (1<< ADSC); // start conversion
    while((ADCSRA & (1<< ADSC)) != 0); // attente fin conversion
    data[i]=ADC;//store the value read from the sensors
    Serial.print(data[i]);Serial.print(" - ");
  }
  Serial.println();
  delay(500);
}

Gestion du suivi de ligne en C

[modifier | modifier le wikicode]

Nous disposons d'à peu près de tout le code nécessaire à résoudre le problème avec tout ce que l'on vient de faire. Mais il nous faut de la remise en forme.

Le premier sous-programme que nous allons réaliser est un sous programme qui s'appelle "sense5IR()". Son objectif est de retourner une valeur pondérée. L'idée est d'allouer un poids aux 5 capteurs infrarouges :

Position du capteur extrême gauche gauche centre droite extrême droite
entrée CAN ADC7 ADC6 ADC5 ADC4 ADC1
Poids +24 +12 0 -12 -24

Si chaque fois que vous avez un capteur qui détecte du noir vous ajoutez son poids et que vous divisez par le nombre de capteurs qui détectent du noir, vous aurez une valeur discrète entre +24 et -24. C'est cette valeur discrète que devra nous retourner "sense5IR()".

Écrire la fonction "int16_t sense5IR()" en lui ajoutant "void initCANs()".

La boucle d'exécution du programme d'un robot est en générale caractérisée par le paradigme sense plan act :

  • lire des capteurs (sense en anglais)
  • planifier (plan en anglais)
  • agir (act en anglais)

Écrire la partie planification et le programme principal associé.

Indication : La planification consiste à calculer les vitesses des moteurs droit et gauche à partir du résultat de la fonction "sense5IR()". L'idée générale est d'augmenter et diminuer les vitesses des moteurs pour rattraper la ligne suivie. Une relation de proportionnalité peut être choisie, mais il est possible de réaliser ici un régulateur PID.

À ce stade vous disposez de tout le nécessaire pour réaliser un suivi de ligne.

Détection d'obstacle en C

[modifier | modifier le wikicode]

Sont présents sur le robot, un détecteur infrarouge et deux émetteurs dirigés respectivement vers la droite et vers la gauche. Ils permettent de savoir si le nombre d'impulsions reçu (par écho) peut laisser penser à la présence d'un obstacle. Le détecteur est relié à INT0/PB0 et peut donc être géré par une interruption qui ne fera que compter le nombre d'impulsions reçu. À noter que cette interruption est incapable, par elle-même de savoir qui a émis, la droite ou la gauche.

L'idée générale est donc d'envoyer un train d'impulsions en émission et de compter ce que l'on reçoit.

Nous ne nous attarderons pas sur la partie récepteur. Il suffit de configurer l'interruption associée à la broche PB0 et d'incrémenter une variable :

volatile uint8_t count=0;

ISR(PCINT0_vect) {
	count++;//on incrémente le compteur pour chaque impulsion reçue}
}

// autorisation interruption PCINT0
void setPCINT0() {
  PCICR = (1<<PCIE0);
  PCMSK0 = (1<<PCINT0);  //Autorisation de l'interruption INT0
}

Ajouter ce code à votre programme.

Le robot dispose de 2 émetteurs dont le tableau suivant donne les informations importantes :

Position Broche Timer
Droite PB4 ----
Gauche PC7 OC4A

Si vous suivez, il va falloir de nouveau configurer un timer ! Le tableau ci-dessus pourrait laisser penser qu'il faut prendre le timer 4, mais on rappelle que l'on utilise déjà ce timer pour la MLI des moteurs. On décide de prendre le TIMER0.

On rappelle que l'on aura besoin de code à travers une interruption pour basculer PB4 et PC7 puisqu'ils ne sont reliés à aucun OC0X.

Émetteurs et Timer0

[modifier | modifier le wikicode]
La comparaison avec le timer 0

Nous allons utiliser le mode comparaison du timer0 afin d'obtenir une fréquence très précise d'émission de 38kHz.

  • Choisir des valeurs de prédiviseur et de comparaison permettant d'obtenir une fréquence de comparaison de 76kHz.
  • Ajouter la configuration du timer a votre programme avec un sous-programme :
void setTIMER0_interrupt_ctc() {
  // set PB4 and PC7 as output

  // choix du prédiviseur

  // mode remise à zéro sur comparaison (mode CTC = (010) contrairement à la figure !!!

  // valeur de OCR0A

  // autorisation de l'interruption de comparaison (bit OCIE0A du registre TIMSK0) 
  
}
  • Ajouter l'interruption de comparaison associée a votre programme. N'oubliez pas que cette interruption doit émettre soit du côté droit soit du côté gauche. Il faudra donc lui associer une variable globale "volatile uint8_t left;" qui conditionnera si l'émission se fait à droite ou à gauche. Par exemple :
volatile uint8_t left=0;
ISR(TIMER0_COMPA_vect)
{
  if (left)
    PORTC ^= (1<<PC7);
  else
   //.....?????

}
Début d’un principe
Fin du principe


Émission droite/gauche

[modifier | modifier le wikicode]

Pour commencer on utilisera les primitives "_delay_us()" pour attendre quelques microsecondes.

  • left <- 0
  • count <- 0
  • Répéter 20 fois :
    • Émettre pendant 300us sur l'émetteur droit en autorisant l'interruption de comparaison
    • Arrêter d'émettre
    • On coupe l'émission pendant 600 us
  • Lire valeur du compteur de réception. S'il est plus grand que 20 on prend les mesures appropriées (recul puis rotation appropriée puis marche avant)

On doit faire la même chose avec l'émission à gauche.

  • left <- 1
  • count <- 0
  • Répéter 20 fois :
    • Émettre pendant 300us sur l'émetteur gauche en autorisant l'interruption de comparaison
    • Arrêter d'émettre
    • On coupe l'émission pendant 600 us
  • Lire valeur du compteur de réception. S'il est plus grand que 20 on prend les mesures appropriées (recul puis rotation appropriée puis marche avant)


À vous de jouer, vous disposer maintenant d'un détecteur d'obstacles. Vous devez l'associer aux solutions de l'exercice 4 qui sont gestion complète des commandes des moteurs.

  • Écrire un programme qui répond au cahier des charges suivant :
  • le robot avance en ligne droite
  • s'il y a un obstacle à droite (count >20 par ex) alors
    • reculer
    • tourner sur place vers la gauche
    • on repart en marche avant
  • s'il y a un obstacle à gauche (count >20 par ex) alors
    • reculer
    • tourner sur place vers la droite
    • on repart en marche avant