< Micro contrôleurs AVR
fin de la boite de navigation du chapitre

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

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

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

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.

Remarque

On trouve parfois dans les documentations officielles la valeur de 1024 au lieu du 1023 ici. Nous ne sommes pas d'accord avec cette valeur de 1024 qui est une valeur sur 11 bits et non sur 10 bits !!!

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

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);
}

Exercice 1

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 10mV/°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

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.

Exercice 2

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)

Exercice 3

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

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

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

Exercice 4

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.

Exercice 5

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

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

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

Registre EICRA

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 76543210
Fonction --------------------ISC11ISC10ISC01ISC00
Valeur initiale 00000000

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

ISCx1ISCx0Déclenchement de l'interruption sur :
00Un niveau bas sur l'entrée INTx
01Un changement d'état sur l'entrée INTx
10Un front descendant sur l'entrée INTx
11Un front montant sur l'entrée INTx

Registre EIMSK

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

EIMSK bit 76543210
Fonction ------------------------------INT1INT0
Valeur initiale 00000000

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

Registre EIFR Exernal Interrupt Flag Register

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

EIFR bit 76543210
Fonction ------------------------------INTF1INTF0
Valeur initiale 00000000

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

Exemple

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() {

}
Remarque

L'appel "sei();" peut être remplacé par "interrupts();" avec l'Arduino.

Application au robot Asuro

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.

Exercice 6

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

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

ABCDE
1 VccR23VADCADC
2 51000000
3 R3068000=A2*B3/(B2+B3)=(C3*1023/A2)K6

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

ABCDEF
1 VccR23VADCADC((( 1024.0/(float)i - 1.0)) * 63.0 + 0.5)
2 51000000
3 R30680000,318352059965K61,4889598068
4 R29330000,159728944832K5927,9378126617
5 R28160000,078740157516K41911,5186616902
6 R2782000,04066653448K37690,99860525
7 R2640000,01992031874K215765,9574780059
8 R2520000,00998003992K131531,353372434
9 R25//R260,00665778961584,5714285714K1 & K247296,7492668623
10 R29//R300,108674596122978,9390191898K6 & K52838,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

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

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

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.

Voir aussi

Articles

Livres

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
Cet article est issu de Wikiversity. Le texte est sous licence Creative Commons - Attribution - Partage dans les Mêmes. Des conditions supplémentaires peuvent s'appliquer aux fichiers multimédias.