Programmer l'ATmega328P sans Arduino

De fablabo


Dsc01541.jpg

Description

Contributeur·ice·s

License

GPL : GNU General Public License

Compétences requises

Matériaux

Machines

Fichier(s) source

Difficulté

Durée

Coût




Bonjour à tous les membres de PING.


J'ai eu envie d'écrire ce petit tuto au fil de l'écoute des conversations à l'association, en effet il me semble important d'apporter des éléments de compréhension et de calculs à chacun pour les cartes Arduino UNO (autant commencer par le commencement) afin de programmer le microcontrôleur ATmega328P en restant dans les spécifications constructeur.


Ici nous allons commencer par quelque chose de simple, lire une broche de la carte Arduino reliée en interne au convertisseur analogique/numérique du microcontrôleur ATmega328P, ceci sans utiliser l'ide Arduino car il bride la compréhension du fonctionnement interne du microcontrôleur.


Cette idée m'est venue en entendant la phrase suivante : "Je peux faire lire à ma carte Arduino un signal avec une impédance de 1MΩ en entrée du convertisseur analogique/numérique."


Vous allez voir que cette affirmation est importante, car qui prendra cette info pourra y croire et faire des montages qui ne fonctionneront pas, ou fonctionneront dans certaines conditions extrêmes et extrêmement spécifiques qui dérogent complètement à la fiche technique du microcontrôleur considéré.


Exemple complet de lecture ADC avec l'ATmega328P (via votre carte Arduino UNO ou pas) :

Dsc07569.jpg
Dsc01673.jpg
Dsc08343.jpg

La première chose à prendre en considération lorsque nous souhaitons programmer le microcontrôleur d'une carte Arduino UNO, est la fiche technique de l'ATmega328P : fiche technique de l'ATmega328P


À l'aide de cette fiche technique nous pouvons écrire le programme suivant qui est complet (je vais expliquer les différentes lignes après) :
#define _ADCSRA (*(volatile unsigned char *)(0x7a))
#define _ADMUX (*(volatile unsigned char *)(0x7c))
#define _ADCLH (*(volatile unsigned int *)(0x78))


void startAnalog()
{
      _ADCSRA = 0b10000101;
}


unsigned int readAnalog()
{
      _ADMUX = 0b01000000;
      _ADCSRA |= 0b01000000;


      while ((_ADCSRA & 0b01000000) != 0b00000000)
      {
      }


      return _ADCLH;
}


int main()
{
      startAnalog();


      while (true)
      {
            readAnalog();
      }


      return 0;
}


Cet exemple est minimaliste mais permet sans bibliothèque additionnelle et sans ide Arduino de faire fonctionner le convertisseur analogique/numérique du microcontrôleur connecté sur la broche PC0.


Dans le programme la première chose à écrire se sont les directives préprocesseur qui permettent d’appeler les pointeurs des adresses de registres dont vous allez avoir besoin :
#define _ADCSRA (*(volatile unsigned char *)(0x7a))
#define _ADMUX (*(volatile unsigned char *)(0x7c))
#define _ADCLH (*(volatile unsigned int *)(0x78))


ADCSRA permet de démarrer le convertisseur analogique/numérique, de choisir le pré-diviseur d'horloge, et aussi de démarrer une conversion et de savoir si elle s'est terminée.


ADMUX permet d'indiquer le bon canal connecté à la bonne broche que nous souhaitons lire, et d'indiquer si la référence de tension est interne ou externe.


ADCLH est la valeur numérique sur 10 bits de la tension mesurée sur le canal choisi.


La fonction principale :
int main()
{
      startAnalog();


      while (true)
      {
            readAnalog();
      }


      return 0;
}


Nous souhaitons démarrer le convertisseur analogique/numérique, et effectuer une lecture d'une façon périodique, c'est pourquoi nous pouvons écrire deux fonctions : startAnalog et readAnalog
Nul besoin de plus dans cette fonction principale.


La fonction startAnalog est très simple, mais renseigner un octet cohérent pour ce registre ADCSRA mérite que l'on s'y attarde plus qu'un petit peu :
void startAnalog()
{
      _ADCSRA = 0b10000101;
}


Détail de l'octet 0b10000101 :
_ADCSRA = 0b10000101;
ADC activé & pré-diviseur d'horloge = 32


La fréquence du microcontrôleur est de 16MHz. La fréquence du multiplexeur du convertisseur analogique numérique est donc égale à :
16000000Hz / 32 = 500000Hz = 500kHz


La fiche technique du microcontrôleur indique que la ligne du convertisseur analogique/numérique a une capacité de 14pF.
Soit le calcul du temps alloué à la charge de la capacité de 14pF à la fréquence du multiplexeur :
1000000µs / (16000000Hz / 32) = 2µs


La formule de charge de la capacité de 14pF avec une impédance d'entrée de 1kΩ (la capacité est considérée assez chargée à partir de 99% soit 5 fois la constante de temps) :
1000Ω * 0.000000000014F * 5τ = 0.00000007s = 70ns
Avec une impédance de 1kΩ en entrée le condensateur met 70ns à se charger à 99%.


La formule de charge de la capacité de 14pF avec une impédance d'entrée de 100kΩ (la capacité est considérée assez chargée à partir de 99% soit 5 fois la constante de temps) :
100000Ω * 0.000000000014F * 5τ = 0.000007s = 7µs
Avec une impédance de 100kΩ en entrée le condensateur met 7µs à se charger à 99%.


Toujours dans la fiche technique du microcontrôleur, il est indiqué que le multiplexeur du convertisseur analogique numérique peut fonctionner avec une impédance en entrée de 1kΩ à 100kΩ, ceci est cohérent avec le choix du pré-diviseur d'horloge qui peut aller de 2 à 128, cela se retrouve par calcul (la capacité est considérée assez chargée à partir de 99% soit 5 fois la constante de temps) :
(1 / (16000000Hz / 2)) / (0.000000000014F * 5τ) = 1785.714Ω ≈ 1.7kΩ
(1 / (16000000Hz / 128)) / (0.000000000014F * 5τ) = 114285.714Ω ≈ 114kΩ


Il est indiqué également que le convertisseur analogique/numérique est optimisé pour une impédance en entrée de 10kΩ, ci-dessous la formule de charge de la capacité de 14pF avec une impédance d'entrée de 10kΩ (la capacité est considérée assez chargée à partir de 99% soit 5 fois la constante de temps) :
10000Ω * 0.000000000014F * 5τ = 0.0000007s = 700ns
Avec une impédance de 10kΩ en entrée le condensateur met 700ns à se charger à 99%.


En respect de la fiche technique du microcontrôleur évoquant une optimisation avec une impédance de 10kΩ, un pré-diviseur d'horloge par exemple de 32 convient comme le montre le calcul suivant :
((1 / (16000000Hz / 32)) / 0.000000000014F) / 10000Ω = 14.285τ ≈ 14t
Le condensateur sera chargé au dela de 99% (puisque 99% = 5τ).


Contrairement à ce qui est souvent dit, une fréquence de fonctionnement du microcontrôleur plus élevée n'est pas forcément intéressante, un exemple avec une fréquence de microcontrôleur plus basse réglée sur 8MHz :
(1 / (8000000Hz / 2)) / (0.000000000014F * 5τ) = 3571.428Ω ≈ 3.5kΩ
(1 / (8000000Hz / 128)) / (0.000000000014F * 5τ) = 228571.428Ω ≈ 228kΩ
Les calculs ci-dessus montrent que la capacité de la ligne du convertisseur analogique/numérique sera davantage chargée avec un microcontrôleur cadencé à 8MHz, mais cela signifie qu'avec une impédance d'entrée de 1kΩ vous pouvez dans les deux cas (8MHz ou 16MHz) choisir un pré-diviseur d'horloge de 2 seulement, en revanche avec une impédance d'entrée de 100kΩ il faut choisir un pré-diviseur d'horloge de 128 pour un microcontrôleur cadencé à 16Mhz, et un pré-diviseur d'horloge de 128 ou 64 pour un microcontrôleur cadencé à 8Mhz.


Calcul de l'impédance maximale en entrée en fonction du pré-diviseur d'horloge de 32 pour charger la capacité à 99% :
(1 / (16000000Hz / 32)) / (0.000000000014F * 5τ) = 28571,428Ω ≈ 28kΩ max en entrée
Avec 10kΩ en entrée, un microcontrôleur cadencé à 16MHz, et un pré-diviseur d'horloge de 32, la charge de la capacité aura largement le temps de faire plus que 5 x la constante de temps et donc de se charger à plus de 99%.


Maintenant vous pouvez renseigner un octet cohérent avec votre matériel pour le registre ADCSRA !
Si vous n'utilisez qu'un port ADC vous ne verrez sans doute pas de phénomène négatif au fait d'utiliser une impédance d'entrée trop élevée par rapport à la fréquence de multiplexage choisie, car la capacité sera chargée sur plusieurs échantillons successifs, mais la malfaçon n'en reste pas moins présente et sera bien visible lorsque vous utiliserez plusieurs ports.


La suite est plus succinct, explications de la fonction readAnalog :
_ADMUX = 0b01000000;
C'est le port PC0 relié à l'ADC0 (convertisseur analogique/numérique canal 0) & le choix de la broche AVCC pour l'alimentation du convertisseur analogique/numérique et référence de tension avec condensateur externe via la broche AREF.


ADCSRA |= 0b01000000;
Correspond au démarrage d'une conversion analogique/numérique.


while ((_ADCSRA & 0b01000000) != 0b00000000)
{
}
Ceci boucle tant que la conversion n'est pas terminée, le bit repasse à 0 si elle est terminée.


return _ADCLH;
C'est la valeur numérique sur 10 bits de la tension mesurée sur PC0.


Il ne manque plus qu'à compiler le programme avec avr-gcc, le compilateur gcc dédiée au microcontrôleurs d'architecture AVR, et le téléverser dans le circuit intégré avec avrdude.


N'hésitez pas si vous avez des questions et/ou des suggestions à apporter.