Micro contrôleurs AVR/AVR et robotique : ASURO
ASURO est un robot mobile destiné à être monté soi-même. Son montage nécessite de bien connaître les composants et naturellement de savoir souder. Le Microcontrôleur qui anime ce robot est un Atmel AVR ATMega8. Ce robot mobile a été conçu en Allemagne. Il existe une page dans wikipédia allemand qui lui est consacrée :
- (de) ASURO si vous connaissez l'allemand
On y apprend, par exemple, qu'ASURO est un acronyme pour "Another Small and Unique Robot from Oberpfaffenhofen" (Oberpfaffenhofen est une ville bavaroise allemande). Cet article de wikipédia n'est ni traduit en anglais, ni traduit en français.
ASURO est un robot différentiel. Un robot différentiel est un robot ayant deux roues autopropulsées indépendantes et une ou deux sphères directionnelles.
Présentation en Image
modifierVoici une image vectorisée présentée ci-contre.
On y distingue un certain nombre d'éléments :
- quatre batteries de type AAA
- un interrupteur marche/arrêt
- deux moteurs à courant continu
- le microcontrôleur de type ATMega8
- a l'avant des capteurs de proximité (interrupteurs) permettant de détecter un contact
- une démultiplication par roue dentées
- une roue dentée avec alternance noir/blanc est aussi visible (en haut) pour une mesure de la position
- une programmation par infra-rouge
La programmation se fait à l'aide d'un bootloader (traduit par Chargeur d'amorçage dans Wikipédia). Cette programmation sans contact est une très bonne idée pour mettre au point des programmes. En effet, comme son nom l'indique, la robotique mobile est mobile ! Chaque fois que vous envoyez un programme dans le robot, son test consiste à se déplacer. Une programmation par contact (appelée programmation in-situ) nécessitera alors de débrancher les contacts... À noter que les anciennes versions proposaient une programmation par RS232 ou par USB. Aujourd’hui seul l’USB est proposé. Cela nécessite un logiciel spécifique de programmation. Celui-ci est disponible pour Windows et pour Linux.
Nous allons maintenant nous intéresser à la schématique électronique et ses liens avec la programmation en c.
Étude de la détection d'obstacles
modifierLe schéma électronique de la détection d'obstacles est donné maintenant :
Comme le montre le schéma les broches ADC4 et PD3 sont utilisées. Il est temps maintenant de partir de ce schéma et de se demander comment peut-on détecter un choc sur l'un des capteurs ?
Étude de la partie odométrie
modifierNous allons nous intéresser dans cette section à la mesure de vitesse de chacun des moteurs et de ses conséquences sur le positionnement du robot. C'est ce que l’on appelle Odométrie.
Nous commençons par présenter la façon utilisée pour mesurer la vitesse. On peut voir sur la figure ci-dessous une roue dentée sur lequel des secteurs angulaires noirs sont alternés avec des secteurs blancs.
Prenez bien le temps de voir tout cela pour bien comprendre, en particulier en regardant les deux côtés du robot pour voir comment tout ceci est assemblé.
Étude de la cinématique d'un robot différentiel
modifierDans la suite tout ce qui concerne la roue droite sera indicée d et la roue gauche indicé g.
Imaginons le robot différentiel suivant représenté ci-contre.
Nous noterons :
- et les déplacements respectifs des roues gauche et droite
- et les vitesses respectives des roues gauches et droite
- le déplacement du robot
- le rayon des roues de propulsion du robot
- la vitesse du robot
- l'écart entre les deux roues, sa largeur de voie
- la vitesse angulaire de rotation autour de l'axe du cercle
Il est facile d'écrire la vitesse de rotation du robot autour de l'axe du cercle à partir des deux vitesses linéaires des roues :
Cette formule peut être généralisée en remarquant que comme R peuvent dépendre du temps :
Il est facile d’en déduire les deux formules :
Il vient finalement :
Le fait que l'intégration soit linéaire permet de trouver facilement :
D(t) est la distance parcourue par le centre de l'axe des moteurs, et représentent les distances parcourues par respectivement la roue gauche et la roue droite. Quant à l'angle, il est donné par :
si L est l’entre-axe des roues.
On a inversé la droite et la gauche pour rester dans la convention habituelle (qui n’est pas celle du dessin) : angle positif quand il tourne dans le sens contraire des aiguilles d'une montre).
Le plus surprenant est que cette formule est indépendante de la trajectoire, aussi complexe soit-elle ! Il est donc possible de connaitre parfaitement l'orientation du robot si l’on a mémorisé le parcours de chacune des roues ce qui est facilement réalisable sur de petites distances.
Application pratique aux calculs
modifierSi l’on repart des deux formules de la distance parcourue et de l'angle :
D(t) est la distance parcourue par le centre de l'axe des moteurs, et représentent les distances parcourues par respectivement la roue gauche et la roue droite. Quant à l'angle, il est donné par :
si L est l’entre-axe des roues.
Il est facile de les généraliser aux variations. Ces formules deviennent facilement :
L'intégration se fait alors par :
On peut déterminer les variations de l'angle et de la position du robot comme le squelette de programme le fait ci-après :
dAlpha = (dRight-dLeft); //variation de l'angle
dDelta = (dRight+dLeft)/2; //variation de l'avancement
//conversion en radian
alpha += dAlpha / entraxeEnTick; //calcul des décalages selon X et Y
dX = cosf(alpha) * dDelta;
dY = sinf(alpha) * dDelta;
//conversion de la position en mètre
X += dX / tickParMetre;
Y += dY / tickParMetre;
Ce code devra être adapté à vos unités.
Le calcul du cosinus et du sinus sont des opérations lourdes dans les architectures 8 bits. Ils ne devront ainsi en aucun cas se trouver dans le code d'une interruption. |
Étude de l'électronique
modifierVoici le schéma électronique correspondant à cette odométrie.
Comme il est très visible sur ce document, l'électronique présentée est associée à un côté : LEFT pour la gauche et RIGHT pour la droite.
Marier l'électronique et la cinématique
modifierNous avons donné plus haut les équations de la cinématique directe. Elles font intervenir des vitesses de rotation, mais nous sommes intéressé par une transformation de ces équations avec des données que notre microcontrôleur est capable de connaître. Le microcontrôleur est incapable de connaître la vitesse instantanée mais peut par contre détecter une alternance de secteur noirs et blancs (ce que l’on appellera tick dans la suite).
Voici donc les équations de navigation à l'estime (dead reckoning) pour les coordonnées (x et y), à partir de ( ) pour un robot différentiel avec des encodeurs sur chaque roue :
où est le nombre de ticks enregistré sur la roue une, est le nombre de ticks enregistré sur la roue deux, est le rayon de chacune des roues, est la séparation entre les deux roues, et est le nombre ticks pour une rotation complète de la roue.
Le calcul de ces équations par un microcontrôleur est un défi sur la taille programme. En effet le calcul d'un cosinus et sinus est très consommateur. Nous vous recommandons d’utiliser l'algorithme CORDIC.
Ne prenez surtout pas tel quel l'algorithme CORDIC publié en C dans Wikipédia. Il est seulement présent à titre de démonstration. Nous nous sommes amusé à le compiler tel quel avec "avr-gcc" et cela nous a donné un programme de taille légèrement inférieure à 8 ko, c'est-à-dire qu’il prend toute la place dédié au programme dans ASURO ! Il y a une autre façon de faire qui évite les fonctions "atan" et "pow" de la librairie mathématique mais qui utilise un codage des nombres en virgule fixe et donc oblige à développer des transformations de ce codage vers les nombres virgule flottante si l’on veut afficher.
Un calcul CORDIC en format Q3.13
modifierLe format Q3.13 est un format virgule fixe, sur 16 bits (3+13) avec 3 bits de partie entière et 13 bits de partie fractionnaire. Il est choisi ici pour contenir les angles en radian comme les résultats des sinus et cosinus sur 16 bits pour éviter les calculs trop longs sur une architecture 8 bits.
Nous donnons en brut un programme qui sera amélioré plus tard. La grande amélioration consiste à noter les valeurs données par la deuxième boucle (d'affichage) pour initialiser manuellement le tableau "atantb" et ainsi éviter la première boucle et donc les appels aux fonctions "atan" et "pow" très consommatrices de ressources !
//TODO Faire disparaitre complètement math.h
// Format utilisé ici : Q3.13 (virgule fixe)
#include <stdio.h>
//#include <math.h>
float HexQ3_13ToFloat(int val);
int float2HexQ3_13(float val);
int main()
{
int nb_iter; // Nombre d'itérations
int K = 0x136F; //=0.6073 en Q3.13
int x = K, y = 0; // Valeur approchante de cos(beta) et sin(beta)
int x_Nouveau; // Variable temporaire
int beta = 0; // Angle à chercher
// tableau de valeurs précalculées rempli
int atantb[14]={0x1921,0xED6,0x7D6,0x3FA,0x1FF,0xFF,0x7F,0x3F,0x1F,0xF,0x7,0x3,0x2,0x1};
int i = 0; // declaration de l'indice d'iteration
printf("Calcul par la méthode CORDIC de sinus : \n\n\n Veuillez entrer beta\n");
// scanf("%d",&beta); // entrer la valeur de beta
// beta = float2HexQ3_13(0.7853);
beta = float2HexQ3_13(-1.047);
// scanf("%d",&nb_iter); // Entrer le nombre d'itération
nb_iter = 14;
// itération CORDIC proprement dite :
for(i = 0; i < nb_iter; i++) {
// Si beta<0 rotation dans le sens trigo
if(beta < 0) {
x_Nouveau = x + (y>>i);
y -= x>>i;
beta += atantb[i];
}
// sinon dans l'autre sens
else {
x_Nouveau = x - (y>>i);
y += (x>>i);
beta -= atantb[i];
}
x = x_Nouveau;
}
// Affichage du résultat :
printf("cos(beta) = %f , sin(beta) = %f \n", HexQ3_13ToFloat(x),HexQ3_13ToFloat(y));
return 0;
}
float HexQ3_13ToFloat(int val){
float temp;
int i_temp;
char i;
if (val < 0) i_temp = -val; else i_temp = val;
temp = ((i_temp & 0x6000)>>13);
for (i=0;i<13;i++)
if (i_temp & (1<<i)) temp += pow(2,(i-13));
if (val < 0) return -temp; else return temp;
}
int float2HexQ3_13(float val){ //OK checked
int temp;
char i;
float f_temp;
if (val < 0) f_temp = -val; else f_temp = val;
temp = ((int) floor(f_temp)<<13);
f_temp = f_temp - floor(f_temp);
for (i=0;i<13;i++) {
temp|=((int)floor(2*f_temp)<<(12-i));
f_temp = 2*f_temp - floor(2*f_temp);
}
if (val < 0) return -temp; else return temp;
}
Notez dans ce programme l'absence de multiplications qui ont été remplacées par des décalages (dans le programme principal). Les seules ressources importantes sont dans les fonctions d'affichage ("printf") mais qui ne sont là que pour réaliser des tests. Leur suppression en remplaçant l’affichage et la demande d'angle par des fonctions réalisées dans un hyperterminal permet de passer d'un programme initialement de 8 ko à 1,1 ko.
Les sous-programmes donnés ci-dessus sont très utiles pour mettre au point les valeurs des tableaux sous un système Linux ou Windows. Mais ils ne fonctionnent pas correctement dans l'AVR, particulièrement "HexQ3_13ToFloat".
Le format Q3.13 choisi n'est probablement pas adapté aux problèmes de robotique mobile à moins d'évaluer la distance avec une très grande unité puisque la plus grande valeur positive est légèrement inférieure à 4 !
Quelles en sont les conséquences pour une mesure des vitesses en C ?
Étude de la partie commande des moteurs
modifierLa commande des moteurs de l'ASURO utilise le timer 1. Nous allons donc commencer par une documentation correspondante.
Comparaison et PWM pour le timer 1 de l'ATMega8
modifierVoici la documentation du timer 1 de l'ATMega8. Il s'agit d'un timer 16 bits comme déjà évoqué mais qui possède un mode de fonctionnement sur 8 bits comme l'indique la figure ci-dessous.
Commande des moteurs
modifierVoici l'électronique de commande des moteurs du robot mobile ASURO.
Exercice
modifierASURO est un robot mobile différentiel (2 roues propulsées par deux moteurs et une demi-balle de ping pong comme roue (point ?) libre. L'ensemble est commandé par un ATMega8. Sa commande de propulsion est réalisée par le montage ci-dessus.
1°) Analyse du schéma. Pour les trois entrées PD5, PD4 et PB1 pouvez-vous examiner les 8 possibilités pour la commande du moteur "LEFT" en précisant quelles combinaisons feront des courts-circuits ?
Mettre un tableau dans une solution étant impossible pour moi, je donne la solution en format préformaté :
|D5|D4|B1||T4|T3|T2|T1| +--+--+--++--+--+--+--+ |0 | 0| 0|| B| P| B| P| |0 | 0| 1|| B| P| B| P| |0 | 1| 0|| B| P| B| P| |0 | 1| 1|| B| P| P| B| |1 | 0| 0|| B| B| B| P| |1 | 0| 1|| P| B| B| P| |1 | 1| 0|| B| B| B| B| |1 | 1| 1|| P| B| P| B|
- P signifie passant
- B signifie bloqué
Il n'y a que deux lignes intéressantes :
- 011
- 101
et pas de court-circuit
2°) Le sous-programme "MotorState est donné maintenant :
/**
* Motor configuration.
* values: FWD, RWD, BREAK, FREE
* @param left left motor
* @param right right motor
*/
inline void MotorState(unsigned char left, unsigned char right)
{
PORTD = (PORTD &~ ((1 << PD4) | (1 << PD5))) | left;
PORTB = (PORTB &~ ((1 << PB4) | (1 << PB5))) | right;
}
En vous aidant de la question 1°) pouvez-vous expliquer ce que fait l'instruction
MotorState(FWD,FWD);
si la constante FWD est définie comme :
#define FWD (1 << PB5) /* (1 << PD5) */
Pourquoi RWD est-il défini comme :
#define RWD (1 << PB4) /* (1 << PD4) */
On a trouvé à la question précédente que deux lignes intéressantes :
- 011
- 101
FWD : met D5 à 1, ce qui veut dire que c’est la ligne "101" qui est concernée.
RWD : met D4 à 1 ce qui veut dire que c’est la ligne "011" qui est concernée
Pour ces deux lignes le poids faible est relié à PB1/OC0A qui est la sortie PWM (OC0A)
3°) Pouvez-vous donner parmi les instructions suivantes du sous-programme Init() (fourni avec ASURO), celles qui sont absolument nécessaires à la réalisation gestion du timer2.
void Init(void) {
//-- serial interface programmed in boot routine and already running --
// prepare 36kHz for IR - Communication
TCCR2 = (1<<WGM20)|(1<<WGM21)|(1<<COM20)|(1<<COM21)|(1<<CS20);
OCR2 = 0x91; // duty cycle for 36kHz
TIMSK |= (1 << TOIE2); // 36kHz counter for sleep
// prepare RS232
UCSRA = 0x00;
UCSRB = 0x00;
UCSRC = 0x86; // No Parity | 1 Stop Bit | 8 Data Bit
UBRRL = 0xCF; // 2400bps @ 8.00MHz
// I/O Ports
DDRB = IRTX | LEFT_DIR | PWM | GREEN_LED;
DDRD = RIGHT_DIR | FRONT_LED | ODOMETRY_LED | RED_LED;
// for PWM (8-Bit PWM) on OC1A & OC1B
TCCR1A = (1 << WGM10) | (1 << COM1A1) | (1 << COM1B1);
// tmr1 running on MCU clock/8
TCCR1B = (1 << CS11);
// A/D Conversion
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1); // clk/64
ODOMETRY_LED_OFF;
FrontLED(OFF);
BackLED(ON,ON);
BackLED(OFF,OFF);
StatusLED(GREEN);
MotorState(FWD,FWD);
MotorSpeed(0, 0);
sei();
}
Seules les trois premières lignes concernent le timer 2. On voit qu'une interruption d'overflow du timer 2 est autorisée mais son code n’est pas donné ici.
4°) Quel est le mode de fonctionnement choisi pour le Timer2 ?
Documentation supplémentaire :
(COM21,COM20)2
00 : Opération normale OC0 déconnecté
01 : Réservé
10 : RAZ de OC0 quand comparaison
11 : Mise à 1 de OC0 quand comparaison
Quel est le rapport cyclique choisi ?
C'est le mode 11 : Mise à 1 de OC0 quand comparaison
Il semblerait que le rapport cyclique du coup soit (0xFF-0x91)/0xFF = 0,431 Mais ceci est faux ! Il manque une donnée en fait pour le calculer, c’est le contenu de l'interruption. Le voici
ISR(TIMER2_OVF_vect)
{
TCNT2 += 0x25;
count36kHz++;
if(!count36kHz) timebase++;
}
Il montre que le compteur ne compte pas 255 fois mais 0xFF-0x25 = 218 fois. Ainsi le rapport cyclique est 0,505
5°) À partir du schéma donné, pouvez-vous dire quel timer est utilisé pour la gestion du PWM ? Pouvez-vous donner parmi les instructions du sous-programme Init() (ci-dessus), celles qui sont absolument nécessaires à la gestion du timer correspondant. Dans quel mode PWM est-il ?
L'apparition de PB1/OC1A et PB2/OC1B dans le schéma a déjà été évoqué (en tout cas la première). Cela montre que c’est le timer 1 qui est utilisé pour le rapport cyclique.
Pour répondre sur le mode de fonctionnement on redonne ici le schéma du timer 1
Le code fait apparaître une utilisation en PWM avec un timer 1 de 8 bits :
TCCR1A = (1 << WGM10) | (1 << COM1A1) | (1 << COM1B1);
où seul WGM10 est positionné à 1 !
6°) La gestion des deux vitesses par MLI est confiée à un sous-programme que l’on vous demande de compléter.
/**
* sets motor speed. range: 0..255
* @param left_speed left motor
* @param right_speed right motor
*/
inline void MotorSpeed(unsigned char left_speed, unsigned char right_speed)
{
= left_speed; // a compléter
= right_speed; // a compléter
}
inline void MotorSpeed(unsigned char left_speed, unsigned char right_speed)
{
OCR1A = left_speed;
OCR1B = right_speed;
}
On utilise les deux comparateurs du timer 1 pour commander les deux moteurs.
La programmation
modifierL'Atmel AVR ATMega8 est naturellement programmable en C. Le robot est livré avec une librairie écrite en C.
Voir aussi
modifier