Outils pour utilisateurs

Outils du site


tuto_avr_eclipse_utilisation_du_convertisseur_analogique-digital

====== [TUTO] [AVR] [Eclipse] Utilisation du convertisseur Analogique-Digital ====== ---- ===== Introduction ===== Dans ce tutoriel, je vous propose de mettre en oeuvre le convertisseur analogique-numérique (ADC((Analog to Digital Converter))) et d'envoyer le résultat de la lecture à un PC connecté au travers de la liaison USB de nos Arduinos préféré Arduino Uno et Arduino Nano). On commencera par implémenter la partie communication, a l'aide de la librairie de communication Série que l'on a créé lors du Tutoriel précédent (([[http://fablab-robert-houdin.org/wiki/doku.php?id=tuto_avr_eclipse_librairie_de_communication]])). On s'appliquera ensuite a utiliser configurer et lire le résultat de la numérisation d'un signal analogique. Comme signal analogique, on peut utiliser n'importe quoi : un simple potentiomètre, un capteur de température genre LM335, une thermistance CTN, ou autre. Pour les besoins du présent tutoriel, nous n'utiliserons rien ; le ATmega328P disposant d'une sonde de température interne. Il n'y a donc pas de câblage. Le but est de faire des acquisitions-numérisations, pas d'illustrer le calcul en virgule fixe, ni de travailler sur un capteur tel qu'une thermistance CTN qui est non linéaire, et implique donc un traitement adapté. Mes sources, si vous voulez les télécharger pour les étudier ou vous y référer, elles sont là : [[https://github.com/spelle/AVR_ADC.git]]. ---- ===== Creation du Projet ===== Pour ma part, le projet, je l'ai nommé //AVR_ADC// ; toujours la même convention de codage. Et toujours pareil aussi, j'ai commencé par la creation de mon repository, clonage, etc... Se référer aux tutoriels précédents pour plus de précisions à ce sujet(([[tuto_avr_eclipse_un_1er_projet_-_clignotage_d_une_led|[TUTO] [AVR] [Eclipse] Un 1er Projet - Clignotage d'une LED]])). **[ACTION]** Créer un nouveau projet C/C++ de type //AVR Cross Target Application// nommé //AVR_ADC//, pour un //ATmega328P// cadencé à //16MHz// La seule chose a faire, est éventuellement renommer la configuration //Debug// en un nom, plus représentatif comme //ATmega328P_16MHz// par exemple, et demander l'optimsation en taille. **[ACTION]** Cliquer sur le menu //Project// > //Build Configurations >// > //Manage...//\\ **[ACTION]** Supprimer la configuration //Release//\\ **[ACTION]** Renommer la configuration //Debug// en //ATmega328P_16MHz// **[ACTION]** Afficher les propriétés du projet : click-droit > //Properties//\\ **[ACTION]** Dans le panneau de droite, sélectionner //C/C++ Build// > //Settings// **[ACTION]** Dans //AVR Compiler// > //Optimizations//, sélectionner //Size Optimizations (-Os)// pour //Optimitzation Level//\\ Répéter l'opération pour //AVR C++ Compiler// : **[ACTION]** Dans //AVR C++ Compiler// > //Optimizations//, sélectionner //Size Optimizations (-Os)// pour //Optimitzation Level//\\ **[ACTION]** Appliquer les changements et valider Il convient ensuite d'ajouter un fichier source ; conformément à notre convention de nommage, nous l'appellerons ainsi : //AVR_ADC.c//. ---- ===== Communication Série ===== ==== Le fichier d'en-tête AVR_ADC.h ==== Nous allons commencer par créer le canal qui nous permettra de Debugger notre programme. Notre Arduino ne disposant pas de moyen sophistiqué de debogage (//JTAG// par exemple), il nous faut le faire nous même. Notre moyen de debuggage sera l'envoie de traces de la part de notre Arduino. >> Il existe bien un moyen qui est la Debug Wire, mais ni notre Arduino, ni nosIDE (Arduino ou Eclipse) ne sont capable d'en tirer partie. Pour ce faire, nous allons ajouter un fichier d'en-tête a notre projet. Il va s'appeller ''AVR_ADC.h'' : <file c AVR_ADC.h> /* * AVR_ADC.h * * Created on: 6 nov. 2013 * Author: A127590 */ #ifndef AVR_ADC_H_ #define AVR_ADC_H_ #include <avr/io.h> #include <avr/interrupt.h> #include <avr/sleep.h> #include <AVR_USART_lib.h> #undef PDEBUG_INIT #undef PDEBUG #ifdef DEBUG #define PDEBUG_INIT( BR ) USART_Init( BR ) // no need of a terminal \n. put by default #define PDEBUG( ... ) printf( __VA_ARGS__ ) #else /* DEBUG */ #define PDEBUG_INIT( BR ) /* not debugging: nothing */ #define PDEBUG( ... ) /* not debugging: nothing */ #endif /* DEBUG */ #endif /* AVR_ADC_H_ */ </file> Pour faire simple, La serie de directives préprocesseur fait que si le symbol //DEBUG// est defini, le compilateur substituera //USART_Init// à //PDEBUG_INIT// et //printf([...])// à //PEDBUG([...])//. Si le symbol //DEBUG// n'est pas défini, //USART_Init// et //PDEBUG// sont substitués par une chaîne vide, et seront donc ignorées. Un tel artifice nous permet de poser des traces de debuggage. Le symbole //DEBUG// devra être défini pendant tant que nous seront en debuggage et enlevé lorsque le projet (Thermostat d'ambiance) sera fini. ---- ==== Le fichier source AVR_ADC.c ==== Le fichier AVR_ADC.c ne présente pas difficultés. Nous avons repris celui du projet [[tuto_avr_eclipse_2nd_projet_-_timer_led|AVR_Blink_A_LED_Timer]]. <file c AVR_ADC.c> /* * AVR_ADC.cpp * * Created on: Oct 9, 2013 * Author: lhotse */ #include "AVR_ADC.h" ISR(TIMER1_COMPA_vect) { // Toggle the LED PORTB ^= (1 << PORTB5); // Toggle the LED PDEBUG( "Hello World !\n" ) ; } int main( void ) { // Set Port B direction Register DDRB |= (1<<DDB5) ; // For LED on Pin PORTB5 // Configure the Timer/Counter1 Control Register A TCCR1A = 0 ; // Configure the Timer/Counter1 Control Register B TCCR1B = (1 << WGM12) ; // Configure timer 1 for CTC mode TIMSK1 = (1 << OCIE1A); // Enable CTC interrupt OCR1A = 62499 ; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64 sei(); // Enable global interrupts PDEBUG_INIT( 9600 ) ; TCCR1B |= (1 << CS12) ; // Start timer at Fcpu/64 while( 1 ) { sleep_mode(); } return( 0 ) ; } </file> Les seules nouveautés sont le //PDEBUG_INIT// avant le démarrage du Timer ; et le //PDEBUG// dans l'ISR. Ceci produisant l'envoi d'un message //Hello World !// toutes les secondes. Afin que les symboles PDEBUG et PDEBUG_INIT aient une substitution, il convient de définir le symbole DEBUG au moment de la compilation. **[ACTION]** Afficher les Propriétés du projet : Clique-droit >> Properties.\\ **[ACTION]** Selectionner C/C++ Build > Settings.\\ **[ACTION]** Dans //AVR Compiler// > //Symbols//, ajouter la définition du symbole //DEBUG// dans la liste //Define Syms (-D)// ; Le symbole est juste défini, sans valeur.\\ **[ACTION]** Dans //AVR C++ Compiler// > //Symbols//, ajouter la définition du symbole //DEBUG// dans la liste //Define Syms (-D)// ; Le symbole est juste défini, sans valeur.\\ **[ACTION]** Appliquer/Valider les changements. Il convient ensuite de compiler le projet. ---- ==== Compilation et Premiers Tests ==== Si une compilation est lancée a ce moment là, le compilateur se plaint de ne pas trouver le fichier d'en-tête //AVR_USART_lib.h//. Il convient donc de l'ajouter. **[ACTION]** Afficher les Propriétés du projet : Clique-droit >> Properties.\\ **[ACTION]** Selectionner C/C++ Build > Settings.\\ **[ACTION]** Dans //AVR Compiler// > //Directories//, ajouter le chemin contenant le fichier d'en-tete AVR_USART_lib.h ("${workspace_loc:/AVR_USART_lib}").\\ **[ACTION]** Dans //AVR C++ Compiler// > //Directories//, ajouter le chemin contenant le fichier d'en-tete AVR_USART_lib.h ("${workspace_loc:/AVR_USART_lib}").\\ **[ACTION]** Appliquer/Valider les changements. La compilation échoue ensuite à l’édition des liens. Le compilateur se plaint de ne pas trouver de référence pour les fonctions //USART_Init//, //USART_Transmit//, et //USART_putchar_printf//. Il convient de mentionner au compilateur de se lier à la librairie //AVR_USAR_lib//. **[ACTION]** Afficher les Propriétés du projet : Clique-droit >> Properties.\\ **[ACTION]** Selectionner C/C++ Build > Settings.\\ **[ACTION]** Dans //AVR C++ Linker// > //Libraries//, ajouter la librairie //AVR_USART_lib// dans la liste //Librarie (-l)//\\ **[ACTION]** Dans //AVR C++ Linker// > //Libraries//, ajouter le chemin vers la librairie //AVR_USART_lib// dans la liste //Libraries Paths (-L)// ("${workspace_loc:/AVR_USARt_lib/${ConfigName}}").\\ **[ACTION]** Appliquer/Valider les changements. La compilation réussit. Le téléversement est une formalité. Pour tester, un terminal série (e.g. : putty) est tout indiqué. On doit recevoir le message //Hello World !!// toutes les secondes. ---- ===== Conversion Analogique-Numérique ===== Nous nous proposons donc de mettre en oeuvre le Convertisseur Analogique-Numerique (ADC((Analog to Digital Converter)) de nos microcontrolleurs préférés. Pour simplifier les choses, nous allons utiliser le Single-Conversion Mode. A l'aide de notre timer, nous allons donc effectuer une lecture toutes les secondes, et afficher le resultat au travers de notre liaison serie. Le fichier AVR_ADC.c en est modifié comme suit : <file c AVR_ADC.c> /* * AVR_ADC.cpp * * Created on: Oct 9, 2013 * Author: lhotse */ #include "AVR_ADC.h" ISR(TIMER1_COMPA_vect) { // Toggle the LED PORTB ^= (1 << PORTB5); // Toggle the LED // Read the ADC value uint16_t uiADCvalue = ADCL ; uiADCvalue |= (ADCH<<8) ; PDEBUG( "Read ADC value : %i\n", uiADCvalue ) ; // Restart a conversion ADCSRA |= (1 << ADSC) ; } int main( void ) { // Set Port B direction Register DDRB |= (1<<DDB5) ; // For LED on Pin PORTB5 // Configure the Timer/Counter1 Control Register A TCCR1A = 0 ; // Configure the Timer/Counter1 Control Register B TCCR1B = (1 << WGM12) ; // Configure timer 1 for CTC mode TIMSK1 = (1 << OCIE1A); // Enable CTC interrupt OCR1A = 62499 ; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64 sei(); // Enable global interrupts PDEBUG_INIT( 9600 ) ; TCCR1B |= (1 << CS12) ; // Start timer at Fcpu/64 // Select the 1.1V voltage reference (needed for acquiring the Internal Temperature Sensor). ADMUX |= (1<<REFS0) | (1<<REFS0) ; // Select ADC8 ADMUX |= (1<<MUX3) ; // Enable the ADC, set prescaler to F_CPU/128 ADCSRA |= (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) ; // Start a Single Conversion PRR &= ~(1 << PRADC) ; ADCSRA |= (1 << ADSC) ; while( 1 ) { sleep_mode(); } return( 0 ) ; } </file> L'initialisation de l'ADC est effectué grace aux lignes suivantes : * Comme mentionné dans la datasheet, le capteur de temperature interne doit etre acquis en utilisant la reference interne de 1.1V. <code c> // Select the 1.1V voltage reference (needed for acquiring the Internal Temperature Sensor). ADMUX |= (1<<REFS0) | (1<<REFS0) ; </code> * L'ADC acquiert le signal au travers d'un multiplexeur. Il y a 8 cannaux externe, et 1 interne mais il n'est possible d'acquerir qu'un seul signal a la fois au travers du multiplexeur piloté par les bits MUXn du registre ADMUX <code c> // Select ADC8 ADMUX |= (1<<MUX3) ; </code> * L'ADC doit ensuite etre activé. Par la meme occasion, le prescaler est selectionné. Le prescaler a pour but de fournir a l'ADC une clock comprise entre 50kHz et 200kHz. Notre clock etant de 16MHz, la seule valeur possible est Fcpu/128 = 125kHz <code c> // Enable the ADC, set prescaler to F_CPU/128 ADCSRA |= (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) ; </code> * Le demarrage de la Conversion se par simple ecriture du bit ADCSC ((ADC Start Conversion)). Il est necessaire de desactiver l'economie d'energie relative a l'ADC (mettre a 0 le bit PRADC ((ADC Power Reduction)) du registre PRR ((Power Reduction Register))). <code c> // Start a Single Conversion PRR &= ~(1 << PRADC) ; ADCSRA |= (1 << ADSC) ; </code> L'initialisation de l'ADC est terminée ; une premiere conversion a été initiée ; le timer est demarré. La prochaine interruption arrivera dans une seconde ; ce qui est largement suffisant pour que la conversion se finisse. D'après la datasheet la première conversion prends 25 temps de cycles; les suivantes en prennent 13 ; à 125kHz, je vous laisse faire le calcul. L'ISR du timer en est donc modifié de la maniere suivante : * La conversion precedente est largement terminée. Il est possible de lire les registres de donnée de l'ADC. <code c> // Read the ADC value uint16_t uiADCvalue = ADCL ; uiADCvalue |= (ADCH<<8) ; </code> >> N.B. : Il faut lire les deux registres independamment ; une logique empeche la mise a jour de ces deux registres tant que le registre ADCH n'est pas lu apres un lecture du registre ADCL. * L'affichage de la valeur lue est trivial a l'aide de la fonction printf(). <code c> PDEBUG( "Read ADC value : %i\n", uiADCvalue ) ; </code> * Une autre conversion est ensuite lancée. <code c> // Restart a conversion ADCSRA |= (1 << ADSC) ; </code> Sur la base du meme code, simplement en changeant la reference de tension et la selection du canal, il est facile d'aller acquerir les autres cannaux de l'ADC. <code c> // Select the AVcc voltage reference ADMUX |= (1<<REFS0) ; // Select ADC0 ADMUX &= ~(1<<MUX3) & ~(1<<MUX2) & ~(1<<MUX1) & ~(1<<MUX0) ; </code> Bien entendu, l'utilisation de l'ADC complexifie notre code, aussi il parait opportun de créer une librairie pour ce faire. Cela peut faire l'objet d'un petit exercice... Concernant le calcul effectif de la temperature, il est abordé dans un tutoriel ditinct relatif aux [[les_sondes_de_temperature_thermistances_ctn|sondes de temperature type thermistance CTN]]. ---- ===== Remarques, Propositions d'améliorations, Questions ===== Vous pouvez poster vos remarques, propositions d’amélioration, et questions sur le forum, dans la discussion prévue a cet effet : [[http://fablab-robert-houdin.org/fablab/phpBB-3.0.11-fr/phpBB3/viewtopic.php?f=3&t=17]] ----

tuto_avr_eclipse_utilisation_du_convertisseur_analogique-digital.txt · Dernière modification: 2013/11/13 15:02 par spelle