Parmi les périphériques que l'on retrouve dans tout microcontrôleur qui se respecte, un convertisseur analogique-numérique (ou ADC pour Analog Digital Converter) est intégré à la puce RP2040 de la carte. Vous trouverez ses caractéristiques dans la datasheet du RP2040.
Il s'agit d'un convertisseur fonctionnant par approximation successive (SAR - Successive Approximation Register) de résolution 12 bits. Le périphérique comprend un multiplexeur capable de diriger l'entrée du convertisseur vers les broches GPIO[26..28] de la carte.
Avec la tension de référence par défaut VREF=3,3 V, on calcule la résolution en Volt (quantum), soit :
q = VREF / 2n = 3,3 / 212 = 0,81 mV
Une résolution inférieure au millivolt et une vitesse d'acquisition des échantillons annoncée jusqu'à 500 ksps (kilo samples per second), le maker qui doit juste lire le port analogique connecté au curseur de son potentiomètre rotatif a de quoi être (largement) satisfait des performances. Si on rajoute les fonctionnalités telles que : acquisition one-shot ou en free-running, multi-entrées en round robin, accès à la pile FIFO des résultats, accès DMA, interruptions, etc. n'en demandez plus...
Commençons par un programme de démonstration de ce convertisseur. Je travaille dans VS Code avec sa nouvelle extension pour les Raspberry Pi Pico. Je configure donc un nouveau projet C/C++ :
Comme j'utilise toujours ma sonde de débogage, je sélectionne la feature UART pour faire remonter les valeurs de conversion jusqu'au PC de développement.
Mais surprise... Je ne vois pas de feature à cocher pour le convertisseur A/N, je vais devoir modifier le fichier de configuration CMakeLists.txt à la main pour la compilation avec le makefile. OK, un oubli dans une version Beta de l'extension VS Code ou j'ai manqué un truc, exécution...
Liaison UART activée, ajout de la librairie standard hadware-adc
Voici le fichier source adc-test.c, largement inspiré des démonstrations fournies dans la documentation, et que j'ai compilé avec succès :
Code C : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <stdio.h> #include "pico/stdlib.h" #include "hardware/gpio.h" #include "hardware/adc.h" int main() { stdio_init_all(); //printf("ADC Example, measuring GPIO26\n"); adc_init(); // Make sure GPIO is high-impedance, no pullups etc adc_gpio_init(26); // Select ADC input 0 (GPIO26) adc_select_input(0); const float conversion_factor = 3.3f / (1 << 12); // 12-bit conversion, assume max value == ADC_VREF == 3.3 V while (1) { uint16_t result = adc_read(); //printf("Raw value: 0x%03x, voltage: %f V\n", result, result * conversion_factor); printf("%u\r\n", result); sleep_ms(20); } } |
Rien d'extraordinaire... On initialise, on dirige l'entrée du convertisseur vers la GPIO[26], et on fait une acquisition one-shot toutes les 20 ms dans une boucle infinie. La valeur issue de la conversion A/N est dirigée de la sortie standard vers la sortie GPIO UART0 TX, transmise à la sonde via son entrée GPIO UART0 RX et redirigée finalement vers l'USB du PC de développement sur le port ttyACM0 (115 200 bauds).
Je flashe le programme et on peut observer les valeurs dans le terminal série (onglet Serial Monitor) :
Je tourne le potentiomètre et je vois bien les valeurs entre 0 et 4095 (=212-1) qui défilent dans le terminal série.
Pour poursuivre cette démonstration, j'ai voulu évaluer statistiquement la population des échantillons grâce à un programme Python, juste par curiosité. Le programme Python ci-dessous (qui inclue les bibliothèques serial, numpy et matplotlib) se connecte au port série ttyACM0, collecte 1000 valeurs de conversion successives, trace un histogramme sur lequel on superpose la courbe « en cloche » de la loi normale :
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import serial # gestion du port série import matplotlib.pyplot as plt # pour faire de beaux graphiques import numpy as np # connexion Linux au port série serial_port = serial.Serial(port = "/dev/ttyACM0", baudrate = 115200) # les mesures mesures = [] serial_port.flushInput() for i in range(1000): val = serial_port.readline() try: m = int(val) mesures.append(m) except: pass # fermeture du port série serial_port.close() #print(mesures) moyenne = np.mean(mesures) # calcul de la moyenne std_m = np.std(mesures, ddof=1) # calcul de l'écart-type print("Moyenne numérique = ", moyenne, "\nÉcart type numérique = ", std_m) # La fonction de la loi normale def gaussienne(x, A, moyenne, ecarttype): return A / (ecarttype*np.sqrt(2*np.pi)) * np.exp(-(x-moyenne)**2 / (2*ecarttype**2)) #Intervalle de définition x = np.linspace(min(mesures), max(mesures), 100) y = gaussienne(x, 1000, moyenne, std_m) # l'intervalle des mesures doit être adapté avec vos valeurs plt.hist(mesures, range= [int(moyenne)-7.5, int(moyenne)+8.5], bins=16, edgecolor = 'black') plt.xlabel("Valeur numérique") plt.ylabel("Fréquence") plt.legend(["mean:"+str(round(moyenne,2))+" std:"+str(round(std_m,2))]) plt.plot(x, y, 'red') plt.show() |
Par exemple, le potentiomètre rotatif étant réglé à une position intermédiaire quelconque, les valeurs dans le terminal série fluctuent autour de 2513. Je lance alors le programme Python et j'obtiens le graphique suivant :
Si je répète l'opération pour la même position du potentiomètre, et si j'augmente à 5000 ou 10000 échantillons, j'obtiens des répartitions très similaires avec des valeurs de moyennes et d'écarts-types très proches.
Quelques constats de ces graphiques :
- Vout = VREF x N/4095 = 3,3 x 2513/4095 = 2,03 V, valeur que je retrouve au multimètre au 1/100è de V près entre la masse et le curseur du potentiomètre. Le convertisseur ne renvoie pas n'importe quoi, c'est le minimum...
- Ce n'est pas très rigoureux comme constat, mais (à vue de pif) la répartition suit une loi normale, je le constate aussi pour d'autres positions du potentiomètre.
- L'écart-type est ici de 2,86, valeur que je retrouve à peu près pour d'autres positions du potentiomètre (écart-type toujours inférieur à 3). Si on admet que la population vérifie la loi normale, cela veut dire aussi que dans l'intervalle [moy - 2x(écart-type), moy + 2x(écart-type)], on retrouve 95% des échantillons. Avec un quantum=0,81 mV et un écart-type=3 dans le pire cas, 95% des échantillons sont à moins de 5 mV autour de la moyenne (2x3x0,81 = 4,83 mV).
Je n'ai rien démontré rigoureusement, mais tout cela est rassurant sur la justesse et la précision des mesures, et même sur la qualité du convertisseur A/N d'autant plus que la qualité du montage avec la plaquette de câblage et la longueur des fils que j'ai utilisés peuvent perturber les mesures.
Bref, rien d'excitant, et je ne pensais même pas rédiger un billet avec ce programme Python assez inutile, mais...
Comment doit-on interpréter les résultats pour quelques positions bien définies du potentiomètre ? Par exemple pour N=1535 :
Dans le terminal série, il y a beaucoup moins de fluctuations que d'habitude. On constate en effet sur le graphe qu'il n'y a plus de distribution en cloche, et que 85% de la population sont pile sur N=1535 ! Quelques échantillons sont un peu au-dessus de la moyenne, mais quasiment aucun échantillon n'est en-dessous. Cette dissymétrie par rapport à la moyenne est suspecte... Ce convertisseur serait-il ultra-précis pour certaines valeurs ? (La réponse est... non, c'est même le contraire !)
En fait, il n'y a pas de hasard, et des explications seront données dans la deuxième partie de ce billet, mais ce convertisseur présente des défauts...