Aller au contenu

Micro contrôleurs AVR/La conversion analogique numérique

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

Nous allons présenter dans ce chapitre la conversion analogique numérique. Même si nous nous contentons de l’application aux AVR, un certain nombre de principes généraux sont étudiés. Des applications simples sont aussi présentées.

La conversion analogique numérique dans le monde Arduino

[modifier | modifier le wikicode]

L'environnement Arduino possède une primitive simple d'utilisation : analogRead. Elle retourne un nombre sur 10 bits puisque les convertisseurs sont des convertisseurs 10 bits. Cela veut dire qu’ils sont capables de retourner une valeur entre 0 et 1023, valeur représentant une tension entre 0 et 5V. Par exemple le programme

// programme d'exemple lecture conversion et envoi sur liaison
void setup()
{
  Serial.begin(9600);
}
 
void loop()
{
  Serial.print("Photocoupleur : ");
   Serial.println(analogRead(A2),DEC);
   delay(500);  
}

enverra dans la liaison série la valeur lue sur l'entrée repérée par A2.

Malgré la simplicité de cet exemple, il est nécessaire de se poser des questions importantes lorsqu'on utilise la conversion, en particulier sur la tension de référence.

Documentation de la conversion analogique numérique

[modifier | modifier le wikicode]

La documentation est donnée sous forme de schéma un peu plus loin. Commençons donc par des généralités.

Généralités

[modifier | modifier le wikicode]

La compréhension de cette partie nécessite d’avoir à l'esprit des idées générales sur le fonctionnement d'une conversion analogique numérique. Nous les rappelons maintenant :

  • Une conversion n’est pas instantanée
  • elle se fait avec un "algorithme" qu’il faut "dérouler" donc nécessite une horloge
  • Une tension de référence est nécessaire pour réaliser une conversion car celle-ci est essentiellement basée sur une comparaison.
    • En interne, elle est de 1,1 V dans les versions ATMagaX8/XX8 mais de 2,56 V dans les ATMega8/16/32.
    • En externe elle peut utiliser les broches AREF ou VREF. VREF permet d’utiliser Vcc comme référence.

La formule magique qui permet de calculer la conversion est :

où ADC est un nombre entier sur 10 bits. Ve est la tension d'entrée présente sur le convertisseur tandis que, comme son nom l'indique, Vref est la tension de référence.


Comment choisit-on la tension de référence avec la librairie Arduino

[modifier | modifier le wikicode]

Par exemple si l’on désire utiliser la tension de référence interne (discutée plus loin) on doit écrire

void setup() {
    //permet de choisir une tension de référence de 1.1V
    analogReference(INTERNAL);
}

tandis que l’utilisation d'une référence externe (sur le broche AREF) se fait par :

void setup() {
    //permet de choisir une tension de référence externe à la carte
    analogReference(EXTERNAL);
}

Une tension de référence de 2,56 V est utilisée comme référence pour une convertisseur d'un ATMega8 sur 10 bits.

1°) Quelle est la résolution en tension de ce convertisseur ?

2°) A quelle tension correspond le nombre 0x12F ?

3°) Une tension de 2V est présente sur l'entrée. Quelle sera la valeur de la conversion ?

4°) Un thermomètre LM35C peut mesurer une température entre -40 °C et +110 °C avec une précision de 1,5 °C et une résolution de 10 mV/°C. Quel nombre retournera le convertisseur pour une température de 45 °C ?

Il est grand temps d'expliquer le fonctionnement de la conversion.

Des registres pour la conversion analogique numérique

[modifier | modifier le wikicode]

Pour le registre ADMUX, le dessin est autosuffisant. Notez quand même les différentes possibilités sur les choix de la référence et de l'entrée convertie. Les entrées AREF et AVCC existent et doivent être connectées à la masse par l'intermédiaire d'une capacité de 100nF. AVCC est en plus connectée à VCC.

Conversion Analogue Numérique dan un ATMega328

Les quatre bits de sélection MUX3:0 laissent penser qu’il y a 16 entrées possibles à sélectionner, mais ce n’est pas vraiment le cas comme le montre le dessin. Huit sont clairement des convertisseurs tandis que d'autres sont reliés en interne et d'autres encore pas utilisées.

Pour le registre ADCSRA, ADEN autorise la conversion tandis que ADSC la fait démarrer (SC=Start Conversion). Ce bit est à 1 pendant toute la durée de conversion et repasse à 0 lorsque celle-ci est terminée.

  • ADATE relève du déclenchement automatique. On le mettra systématiquement à 0. Il est lié au registre ADCSRB que nous n'étudierons pas.
  • ADIF est le bit qui est positionné à 1 quand la conversion est terminée. Il peut être lié à une interruption en positionnant ADIE à 1. L'interruption effacera le bit ADIF. Si aucune interruption est utilisée, il faut effacer ADIF en écrivant un 1 dedans.

La division d'horloge doit être choisie pour être réalisée à 200kHz au maximum.

Donner le morceau de programme qui lance la conversion et attend la fin de conversion. Deux techniques sont à explorer :

  • une avec le bit ADSC
  • une avec le bit ADIF (qui est un flag et nécessite une attention particulière)

Pouvez-vous expliquer le result=ADCH du programme ci-dessous ainsi que chacun des commentaires. Y a-t-il erreur dans un commentaire ?

#include <avr/io.h>
int main() {
        unsigned char result;
        // Choose AREF pin for the comparison voltage
        //   (it is assumed AREF is connected to the +5V supply)
        // Choose channel 3 in the multiplexer
        // Left align the result
        ADMUX = (1 << REFS0) | (1 << ADLAR) | (3);
        // Start the ADC unit,
        // set the conversion cycle 16 times slower than the duty cycle
        ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADSC);
        // Wait for the measuring process to finish
        while (ADCSRA & (1 << ADSC)) continue;
        // Read the 8-bit value
        result = ADCH;
}

Exemple d'utilisation avec interruption

[modifier | modifier le wikicode]

Voici un exemple d'utilisation de la conversion analogique/numérique et son interruption associée.

#include <avr/io.h>
#include <avr/interrupt.h>
ISR(ADC_vect) {
  PORTD = ADCL;
  PORTB = ADCH;
  ADCSRA |= (1<<ADSC);
}
int main() {
  unsigned char result;
  DDRB = 0xFF;
  DDRD = 0xFF;
  DDRA = 0; // make port A an input for ADC
  ADCSRA = 0x8F; //enable interrupt select clk/128
  ADMUX = 0xC0; //{{Unité|2.56|{{Abréviation|V|volt}}}}ref and ADC
  sei();
  ADCSRA |= (1 << ADSC); //start conversion
  while (1); // wait forever
  return 0;
}

Nous ne donnerons pas plus d'information sur ce morceau de code et laissons le lecteur le lire attentivement.

Applications : lecture de plusieurs interrupteurs

[modifier | modifier le wikicode]

L'utilisation d'un bit de PORT par interrupteur devient vite très consommatrice de broches du composant. Nous allons étudier un moyen simple d’éviter cette augmentation.

Réseau de résistances et interrupteurs pour Convertisseur Analogique Numérique

Voici un premier schéma d'exemple ci-contre

1°) Trouver les valeurs des tensions en fonction de l'appui sur les boutons poussoirs. On supposera pour ce calcul que Vcc = 5V.

2°) Calculer les ADC correspondants sur 10 bits si VREF = 5V.

3°) Écrire un programme complet capable d'afficher complètement l'état des 4 interrupteurs sous forme binaire.

On peut faire encore plus que dans le cas de l'exercice 4 en décodant tout un clavier avec une seule entrée.

Relier un clavier entier sur un CAN

1°) Calculer dans un tableau les tensions et valeurs 10 bits correspondantes pour l'appui d'une touche du clavier si la tension Vcc est fixée à 5V (ainsi que VREF).

2°) Peut-on prendre en compte l'appui de plusieurs touches ?

3°) Écrire un sous programme de lecture du clavier qui retourne la touche appuyée. On utilisera obligatoirement un switch.

Détection avec interruption

[modifier | modifier le wikicode]

En robotique mobile, des interrupteurs peuvent être utilisés pour détecter des obstacles. Il faut alors réagir de toute urgence. Une interruption peut être adaptée à cette situation.

L'utilisation d'interruption dans ce contexte est cependant assez subtil dans la mesure où l'interruption CAN est seulement déclenchée quand la conversion est terminée. Il faut donc essayer de coupler tout cela avec ce que l’on appelle une interruption externe.

Préalable sur l'interruption externe

[modifier | modifier le wikicode]

Cette partie détaille l’utilisation des interruptions INT0 et INT1, attachées aux pin PD2 et PD3 pour l'AVR ATMega328.

Registre EICRA

[modifier | modifier le wikicode]

Le registre EICRA permet de choisir le mode de déclenchement de l'interruption avec deux bits de réglage par interruption (soit 4 pour l'ATMega328).

EICRA bit 7 6 5 4 3 2 1 0
Fonction ----- ----- ----- ----- ISC11 ISC10 ISC01 ISC00
Valeur initiale 0 0 0 0 0 0 0 0

Le tableau suivant donne la valeur des bits ISCx0 et ISCx1 pour configurer le mode de déclenchement associé à l'interruption INTx :

ISCx1 ISCx0 Déclenchement de l'interruption sur :
0 0 Un niveau bas sur l'entrée INTx
0 1 Un changement d'état sur l'entrée INTx
1 0 Un front descendant sur l'entrée INTx
1 1 Un front montant sur l'entrée INTx

Registre EIMSK

[modifier | modifier le wikicode]

Le registre EIMSK permet d'autoriser ou non les interruptions INT1 et INT0.

EIMSK bit 7 6 5 4 3 2 1 0
Fonction ----- ----- ----- ----- ----- ----- INT1 INT0
Valeur initiale 0 0 0 0 0 0 0 0

Une mise à '1' du bit INTx permet d'autoriser l'interruption associée.

Registre EIFR Exernal Interrupt Flag Register

[modifier | modifier le wikicode]

Le registre EIFR permet d'observer l'état des interruptions INT1 et INT0.

EIFR bit 7 6 5 4 3 2 1 0
Fonction ----- ----- ----- ----- ----- ----- INTF1 INTF0
Valeur initiale 0 0 0 0 0 0 0 0

Le bit INTFx passe à '1' lors du déclenchement de l'interruption.

Pour déclencher une interruption à chaque changement d'état de la patte PD2 (donc sur les fronts montant et descendant), on pourra utiliser le code suivant :

ISR(INT0_vect) // programme d'interruption : le programme principal est interrompu,
{ // l'interruption exécutée et ensuite le programme principal continu normalement son exécution
  PORTB^=0x01; // modification de la sortie PB0
}
void setup() {
  DDRB=0x0F; // configuration de PB0 en sortie
  cli(); // arrêt des interruptions
  EICRA=0x01; // mode de déclenchement de l'interruption
  EIMSK=0x01; // choix des interruptions actives
  sei(); // autorisation des interruptions
}

void loop() {

}


Application au robot Asuro

[modifier | modifier le wikicode]
Les détecteurs de contact de l'ASURO

Voici un exemple concernant le robot Asuro qui utilise un ATMega8 présenté dans un autre chapitre. Sur l'ATMega8 une interruption liée à la conversion analogique numérique existe mais elle n'est déclenchée que lors de la terminaison de la conversion et non lors d'un changement sur une entrée. C'est pour cela que les concepteurs d'Asuro ont utilisé le schéma ci-contre. L'appui sur un interrupteur sera aussi détecté sur l'entrée PD3 qui peut elle déclencher une interruption.

1°) Calculer les changements de tensions lors des appuis d'interrupteurs si la tension d'alimentation est 5V.

2°) On vous donne un extrait d'un morceau de programme pour le Robot ASURO. Pouvez-vous déduire de ce programme les valeurs manquantes de ce morceau de programme ?


i = ADCL + (ADCH << 8);
taste = ((1024.0/(float)i - 1.0) * 63.0 + 0.5);

// K1 ou K2 (ou non exclusif)
if (taste == || taste == || taste == ) { //links kollidiert 

// K5 ou K6 (ou non exclusif)
if (taste == || taste == || taste == ) { //rechts kollidiert 
 
Interrupteurs K1 en haut et dans l’ordre avec K6 en bas

Indications : links en allemand signifie gauche tandis que rechts signifie droite. Les interrupteurs sont montrés dans la photo ci-contre. Ils sont disposés dans l’ordre de K1 à K6 avec donc K1 en haut et K6 en bas. Cette information vous permet de trouver lesquels sont activés pour la gauche et pour la droite.

3°) Donner le squelette d'un programme qui arrête le robot quand un des interrupteurs est fermé.

Solution de l'exercice 6

[modifier | modifier le wikicode]

1°) Si nous utilisons un tableur comme ci-dessous,

A B C D E
1 Vcc R23 VADC ADC
2 5 1000000
3 R30 68000 =A2*B3/(B2+B3) =(C3*1023/A2) K6

qui est à continuer vers le bas pour chacun des interrupteurs, nous obtenons les résultats :

A B C D E F
1 Vcc R23 VADC ADC ((( 1024.0/(float)i - 1.0)) * 63.0 + 0.5)
2 5 1000000
3 R30 68000 0,3183520599 65 K6 1,4889598068
4 R29 33000 0,1597289448 32 K5 927,9378126617
5 R28 16000 0,0787401575 16 K4 1911,5186616902
6 R27 8200 0,0406665344 8 K3 7690,99860525
7 R26 4000 0,0199203187 4 K2 15765,9574780059
8 R25 2000 0,0099800399 2 K1 31531,353372434
9 R25//R26 0,0066577896 1 584,5714285714 K1 & K2 47296,7492668623
10 R29//R30 0,1086745961 22 978,9390191898 K6 & K5 2838,8948907742

Nous avons supposé la tension de référence à 5V dans ce calculs. Cette tension ne semble pas très adaptées pour certaines variations. Bien sûr il est possible de changer les valeurs des résistances pour avoir un domaine de variation plus grand, mais n'oubliez pas qu'il faut détecter un front descendant sur l'entrée qui est branchée sur INT1. C'est à cause de cela que l'on est obligé d'opérer avec des résistances qui réalisent une grosse variation quand un interrupteur est appuyé.

Il est possible de changer la tension de référence pour avoir une dynamique plus grande sur ADC. Pour mémoire, la tension de référence est de 2,56 V pour l'ATMega8 de l'Asuro et celle de l'ATMega328 est de 1,1 V (pour l'Arduino UNO).

2°) Le calcul présenté dans l'énoncé ne nous convainc pas vraiment. Nous n'avons d'ailleurs pas réussi à retrouver plusieurs années après l'écriture de cet énoncé un programme original qui proposait ce calcul de la variable "taste" qui de notre point de vue ne sert pas à grand chose, à part à faire un calcul en flottant qui consomme beaucoup de ressources d'un microcontrôleur 8 bits. En effet nous pensons au vue des calculs présentés en question 1 qu'il faudrait mieux ne pas faire ce calcul et plutôt faire un test de comparaison directement sur i du genre :


i = ADCL + (ADCH << 8); // avec nos calculs ADCL suffit

// K1 ou K2 ou K3 (ou non exclusif)
if (i<=12) { //links kollidiert 

// K5 ou K6 ou K4 (ou non exclusif)
if (i> 12 ) { //rechts kollidiert 
 

3°) La documentation schématique de l'Asuro montre que c’est INT1 qu’il faut utiliser. On n'a pas le choix, ce sont les concepteurs qui ont choisi avec leur Circuit Imprimé.

La seule documentation dont vous disposez un peu plus haut dans ce cours est celle de l'ATMega328. Nous allons faire comme si c'était ce processeur qui équipait l'Asuro.

ISR(INT1_vect) // programme d'interruption : le programme principal est interrompu,
{ // l'interruption exécutée et ensuite le programme principal continu normalement son exécution
  .... // arret des moteurs par exemple
}
void setup() {
  ... // configuration des moteurs, des CANs et autres...
  cli(); // arrêt des interruptions
  EICRA = (1 << ISC11) ; // mode de déclenchement de l'interruption sur front descendant
  EIMSK = (1<< INT1); // choix de interruption active
  sei(); // autorisation des interruptions
}

void loop() {

}

Exercice final de synthèse

[modifier | modifier le wikicode]

Il s'agit tout simplement dans cet exercice de brancher un potentiomètre sur l'entée ADC0 d'un ATMega328 (entrée A0 d'un Arduino UNO) et de commander un servomoteur à l'aide du timer1. Évidemment la position du servomoteur dépend directement de la position du potentiomètre.

Commande du servomoteur

[modifier | modifier le wikicode]

Cette partie a déjà été étudiée dans un chapitre précédent. Le code correspondant se trouve dans l'exercice 3 de l'étude du Timer 1.

Programme complet

[modifier | modifier le wikicode]

Réaliser une conversion analogique numérique est l'objet de ce chapitre. Si vous êtes arrivés ici, vous en maîtrisez certains aspects. En particulier vous savez que le résultat de la conversion analogique numérique vous donnera un résultat entre 0 et 1023 suivant la position du potentiomètre.

Si vous relisez la partie sur le timer 1 et la commande du servomoteur vous savez que la valeur du registre du PWM pour le moteur devra lui varier entre 32 et 156.

Panneau d’avertissement Les valeurs numériques données ci-dessous peuvent dépendre légèrement de vos servomoteurs. Ce que vous devez vérifier avant de les utiliser est qu'elles ne positionne pas le servomoteur en butée. C'est tout.
Circuit de simulation sous Tinkercad pour notre problème


En résumé nous avons des valeurs entre 0-1023 que nous devons transformer en valeurs entre 32-156. Ce n'est pas un problème difficile à priori mais si vous écrivez bêtement la formule en C :

commandeServo = ADC * (156-32)/1023 + 32;

(qui est la bonne formule) et bien vous obtiendrez toujours 32 dans commandeServo ! Si vous ne comprenez pas pourquoi c'est parce que vous avez oublié que la division en C est entière et donc que (156-32)/1023 donnera toujours 0. Pour obtenir le bon résultat il faut forcer la division en division flottante ou tout simplement précalculer le nombre (156-32)/1023 qui vaut 0.1212....

Nous avons conçu un circuit pour nos essais à l'aide de Tinkercad. Ce circuit est présenté dans la vignette ci-contre.

Écrire le code complet qui résout ce problème.

L'utilisation d'un convertisseur analogique numérique pour lire l'état de plusieurs interrupteurs est décrite pour une autre architecture dans :

  • ARM Microcontroller Interfacing Harware and Software, Warwick A. Smith, Elektor 2010