Premier montage
Câblage
Codes pour générer les signaux
L'oscilloscope
Version "basique"
Version "à fréquence maximale"
Version "à temps contrôlé"
Premiers résultats
Observations
Courbes obtenues
Montée en fréquence
Signal rectangulaire
Signal MLI ou PWM
Autres modèles d'oscilloscopes
Mesures simultanées
Mesures en temps réel
Repousser les limites
Un octet par mesure
Réglage du CAN
En pratique
L'objectif de ce projet est :
Les premiers essais sont effectués en basse fréquence : 0,5 Hz. Pour tester l'oscilloscope, un générateur de signaux rectangulaire ou sinusoïdal est bien utile. Ce n'est pas évident de réaliser un tel générateur de signaux, avec une carte Arduino. Par contre, il est facile de faire varier l'intensité lumineuse d'une DEL. De couleur blanche, placée a proximité immédiate d'une photorésistance, elle permettra de recueillir un signal à afficher sur l'ordinateur. Le montage est positionné dans un endroit sombre de préférence.
On utilise donc deux cartes Arduino. Une carte fait varier l'intensité lumineuse d'une DEL. L'autre carte récupère le signal pour l'envoyer à l'ordinateur. Un programme en Python affiche les données du signal.
Certains numéros associés aux connecteurs d'une carte Arduino sont précédés du signe "~". Cela signifie qu'ils peuvent fonctionner selon deux modes :
C'est ce type de connecteur qui est choisi. Le câblage est le suivant :
La résistance qui protège la DEL ne doit pas avoir une valeur trop importante, pour éviter de limiter son intensité lumineuse. Les données sont les suivantes :
La résistance minimale se calcule avec la loi d'Ohm :
Signal rectangulaire :
#define DEL 6 void setup() { pinMode(DEL, OUTPUT); } void loop() { digitalWrite(DEL, HIGH); delay(500); digitalWrite(DEL, LOW); delay(1500); }
Signal sinusoïdal :
#define DEL 6 float periode=2; void setup() { pinMode(DEL, OUTPUT); } void loop() { for (float t=0; t<periode; t=t+0.01) { analogWrite(DEL, (int)(127.5+127.5*sin(t*2*PI/periode))); delay(10); } }
Le signal lu (0 à 1023) est envoyé à l'ordinateur toutes les 0,05 s.
void setup() { Serial.begin(115200); } void loop() { Serial.println(analogRead(0)); delay(50); }
La bibliothèque matplotlib est mise à contribution. La cinquième ligne du programme est à adapter selon le nom du port USB. Les valeurs sont lues toutes les 0,05 s, pendant 8 s.
#!/usr/bin/python3 import time import serial import matplotlib.pyplot as plt portUSB=serial.Serial('/dev/ttyUSB0', 115200) temps=[] tension=[] duree=8 t=0 dt=0.05 while (t<duree) : temps.append(t) tension.append(0.004887*float(portUSB.readline())) t=t+dt time.sleep(dt) plt.figure("Oscilloscope") plt.xlabel("Temps en s") plt.ylabel("Tension en V") plt.grid(True) plt.plot(temps,tension) plt.show()
Le nombre maximal de mesures par unité de temps s'obtient... avec le code le plus simple!
void setup() { Serial.begin(115200); } void loop() { Serial.println(analogRead(0)); }
Le code est presque identique au précédent. Le temps dt entre deux mesures est déterminé empiriquement. La première valeur envoyée à l'ordinateur engendre parfois une erreur, ne pouvant être convertie en nombre. Ce problème est ici corrigé.
#!/usr/bin/python3 import serial import matplotlib.pyplot as plt portUSB=serial.Serial('/dev/ttyUSB0', 115200) temps=[] tension=[] duree=8 t=0 dt=0.000422 while (t<duree) : try : temps.append(t) tension.append(0.004887*float(portUSB.readline())) t=t+dt except : pass plt.figure("Oscilloscope") plt.xlabel("Temps en s") plt.ylabel("Tension en V") plt.grid(True) plt.plot(temps, tension) plt.show()
Le temps s'obtient avec la fonction micros(). La fonction millis() est utilisable, mais les résultats obtenus sont moins bons lorsque la fréquence du signal à mesurer augmente.
void setup() { Serial.begin(115200); } void loop() { Serial.print(micros()); Serial.print(" "); Serial.println(analogRead(0)); }
Le code ressemble au précédent. La première valeur envoyée à l'ordinateur engendre parfois une erreur. Quand ce n'est pas le cas, elle est parfois fausse. La première valeur lue n'est donc pas traitée. Une première valeur de temps est crée artificiellement pour permettre à la boucle de fonctionner. Elle est détruite une fois l'ensemble des valeurs récupérées.
#!/usr/bin/python3 import serial import matplotlib.pyplot as plt portUSB=serial.Serial('/dev/ttyUSB0', 115200) temps=[] tension=[] duree=0.05 lu=portUSB.readline() temps.append(0) while (temps[-1]<duree) : lu=portUSB.readline().split() temps.append(0.000001*float(lu[0])) tension.append(0.004887*float(lu[1])) del temps[0] plt.figure("Oscilloscope") plt.xlabel("Temps en s") plt.ylabel("Tension en V") plt.grid(True) plt.plot(temps, tension) plt.show()
Les premiers essais, effectués en basse fréquence, engendrent des courbes similaires avec les trois types d'oscilloscope. On remarque que la résistance de la photorésistance :
Dans la troisième version de l'oscilloscope, le temps est contrôlé par la carte Arduino. Cependant, il est aussi possible de le contrôler par l'ordinateur, avec le code ci-dessous. Le Python n'est pas assez rapide, les essais ont montré des résultats... moins réguliers.
import time ... debut=time.time() while (temps[-1]<duree) : temps.append(time.time()-debut) ...
Résultat avec le signal rectangulaire :
Et avec le signal sinusoïdal :
Un signal rectangulaire est crée et lu directement, avec la troisième version de l'oscilloscope, sans passer par la photorésistance. Les résultats obtenus sont corrects jusqu'à une fréquence de 100 Hz environ (les 8 s sont remplacés par 0,05 s).
Code pour obtenir un signal rectangulaire à 100 Hz :
#define DEL 6 void setup() { pinMode(DEL, OUTPUT); } void loop() { digitalWrite(DEL, HIGH); delay(5); digitalWrite(DEL, LOW); delay(5); }
Résultat obtenu :
Un signal MLI (modulation de largeur d'impulsions) ou, en anglais, PWM (pulse width modulation) permet de commander un servomoteur. L’intervalle de temps entre chaque impulsion, autrement dit la période, est généralement de 20 ms. Les impulsions ont une durée de 1 ms pour la position angulaire 0°, et une durée de 2 ms pour la position angulaire 180°. On est à la limite de ce que peut visualiser l'oscilloscope.
Code pour générer le signal MLI :
#include <Servo.h> Servo leServo; void setup() { leServo.attach(6); } void loop() { leServo.write(70); // Position angulaire de 70° }
Résultat obtenu pour la position 180° :
Et la position 0° :
Une carte Arduino possède six entrées analogiques. Il est donc possible d'effectuer six mesures en même temps. On va ici relever quatre tensions qui correspondent à la commande d'un moteur pas à pas.
// Numéros des connecteurs #define C1 8 #define C2 9 #define C3 10 #define C4 11 // Paramètres du moteur pas à pas #define nbPas 20 #define vitesseRotation 250 // Objet associé au moteur #include <Stepper.h> Stepper moteur(nbPas, C1, C2, C3, C4); void setup() { pinMode(C1, OUTPUT); pinMode(C2, OUTPUT); pinMode(C3, OUTPUT); pinMode(C4, OUTPUT); moteur.setSpeed(vitesseRotation); } void loop() { moteur.step(20000); }
void setup() { Serial.begin(115200); } void loop() { Serial.print(micros()); Serial.print(" "); Serial.print(analogRead(0)); Serial.print(" "); Serial.print(analogRead(1)); Serial.print(" "); Serial.print(analogRead(2)); Serial.print(" "); Serial.println(analogRead(3)); }
#!/usr/bin/python3 import serial import matplotlib.pyplot as plt portUSB=serial.Serial('/dev/ttyUSB0', 115200) tension1=[] tension2=[] tension3=[] tension4=[] temps=[] duree=0.05 lu=portUSB.readline() temps.append(0) while (temps[-1]<duree) : lu=portUSB.readline().split() temps.append(0.000001*float(lu[0])) tension1.append(0.004887*float(lu[1])) tension2.append(0.004887*float(lu[2])) tension3.append(0.004887*float(lu[3])) tension4.append(0.004887*float(lu[4])) del temps[0] plt.figure("Oscilloscope") plt.xlabel("Temps en s") plt.ylabel("Tension en V") plt.grid(True) plt.plot(temps, tension1, color='#2277bb') # Bleu plt.plot(temps, tension2, color='#ff7700') # Orange plt.plot(temps, tension3, color='#22aa22') # Vert plt.plot(temps, tension4, color='#dd2222') # Rouge plt.show()
Il s'agit de créer une animation montrant l'évolution de la tension en fonction du temps. Cette animation peut se présenter de multiples manières.
En théorie, les données devraient s'afficher au fur et à mesure qu'elles arrivent. En pratique, la mise à jour de l'affichage prend du temps, il est difficile de produire une animation fluide avec un tracé précis. L'idée est de récupérer plusieurs valeurs entre entre deux images consécutives, en cherchant le meilleur compromis.
Coté Arduino, la tension est envoyée dix fois par seconde :
void setup() { Serial.begin(115200); } void loop() { Serial.print(micros()); Serial.print(" "); Serial.println(analogRead(0)); delay(100); }
Dans le code ci-dessous, la première lecture n'est pas prise en compte. La deuxième lecture permet à la boucle de fonctionner. Ensuite, deux lectures sont faites avant chaque mise à jour de l'affichage. Les essais ont montré une animation de cinq images par seconde environ.
#!/usr/bin/python3 import serial import matplotlib.pyplot as plt portUSB=serial.Serial('/dev/ttyUSB0', 115200) temps=[] tension=[] duree=25 plt.figure("Oscilloscope") lu=portUSB.readline() lu=portUSB.readline().split() temps.append(0.000001*float(lu[0])) tension.append(0.004887*float(lu[1])) while (temps[-1]<duree) : lu=portUSB.readline().split() temps.append(0.000001*float(lu[0])) tension.append(0.004887*float(lu[1])) lu=portUSB.readline().split() temps.append(0.000001*float(lu[0])) tension.append(0.004887*float(lu[1])) plt.ylim(-0.2,5.2) plt.xlabel("Temps en s") plt.ylabel("Tension en V") plt.grid(True) plt.plot(temps, tension, color='#2277bb') plt.pause(0.000001)
Exemple de charges et décharges d'un condensateur (attention à la polarité) de 2200 μF ralenties par une résistance de 270 Ω :
Ces oscilloscopes présentent des avantages et des limites.
Référence sur le connecteur | Tensions (V) | |
---|---|---|
Minimale | Maximale | |
GND | 0 | 5 |
3.3V | -3,3 | 1,7 |
5V | -5 | 0 |
Au lieu d'envoyer 1 à 4 octets représentant les caractères allant de 0 à 9, suivis de 2 octets pour le retour chariot et le saut de ligne, la carte Arduino UNO peut aussi transmettre 2 octets désignant des nombres compris entre 0 et 1023... éventuellement suivis du saut de ligne. Et pour une plage de valeurs de 0 à 255 jugée satisfaisante, 1 seul octet par mesure suffit :
void setup() { Serial.begin(115200); } void loop() { Serial.write(analogRead(0)/4); }
Pour cette dernière solution, le code en Python devient :
#!/usr/bin/python3 import serial import matplotlib.pyplot as plt portUSB=serial.Serial('/dev/ttyUSB0', 115200) temps=[] tension=[] duree=0.05 t=0 dt=0.000112 while (t<duree) : tension.append(0.0196*portUSB.read()[0]) temps.append(t) t=t+dt plt.figure("Oscilloscope") plt.xlabel("Temps en s") plt.ylabel("Tension en V") plt.grid(True) plt.plot(temps,tension) plt.show()
Le temps dt entre deux mesures, déterminé empiriquement, montre une nette augmentation de la fréquence : Environ 9000 mesures par seconde.
Le CAN (convertisseur analogique-numérique) ou DAC en anglais (digital analog converter) fonctionne par cycles, pour des raisons technologiques. Ces cycles dépendent d'une horloge dont la fréquence peut être modifiée. En l'augmentant, on diminue le temps nécessaire pour effectuer une mesure. Le CAN, système relativement complexe, se configure avec 5 registres de 8 bits :
Les 3 bits ADPS2, ADPS1 et ADPS0 du registre ADCSRA déterminent le rapport entre la fréquence principale de l’Arduino et celle du CAN. Par défaut, ces 3 bits sont à 1.
ADPS2 | ADPS1 | ADPS0 | Rapport |
---|---|---|---|
1 | 1 | 1 | 128 |
1 | 1 | 0 | 64 |
1 | 0 | 1 | 32 |
1 | 0 | 0 | 16 |
0 | 1 | 1 | 8 |
0 | 1 | 0 | 4 |
0 | 0 | 1 | 2 |
0 | 0 | 0 | 2 |
Le code ci-dessous permet de connaître la valeur par défaut du registre ADCSRA. On obtient 135 soit 10000111 en notation binaire.
void setup() { Serial.begin(115200); } void loop() { Serial.println(ADCSRA); delay(1000); }
Le code ci-dessous permet :
int lu; unsigned long debut; void setup() { Serial.begin(115200); ADCSRA=134; } void loop() { debut=millis(); for (int i=0; i<1000; i++) { lu=analogRead(0); } Serial.print("Temps de lecture en µs : "); Serial.println(millis()-debut); delay(1000); }
Ce test a permis de trouver les valeurs suivantes :
ADCSRA | Temps de lecture (µs) | |
---|---|---|
Décimal | Binaire | |
135 | 10000111 | 112 (parfois 111 ou 113) |
134 | 10000110 | 56 (parfois 55 ou 57) |
133 | 10000101 | 28 (parfois 27 ou 29) |
132 | 10000100 | 15 (parfois 14 ou 16) |
131 | 10000011 | 8 (parfois 9) |
130 | 10000010 | 5 (parfois 4 ou 6) |
Le signal à lire, rectangulaire, s'obtient avec la fonction tone(). Par exemple, pour une fréquence de 1000 Hz :
void setup() { pinMode(6, OUTPUT); tone(6, 1000); } void loop() { }
En réglant la valeur de ADCSRA à 134 :
void setup() { Serial.begin(115200); ADCSRA=134; } void loop() { Serial.write(analogRead(0)/4); }
En réglant dt à 0,000061 s et en effectuant des mesures sur 0,005 s :
#!/usr/bin/python3 import serial import matplotlib.pyplot as plt portUSB=serial.Serial('/dev/ttyUSB0', 115200) temps=[] tension=[] duree=0.005 t=0 dt=0.000061 while (t<duree) : tension.append(0.0196*portUSB.read()[0]) temps.append(t) t=t+dt plt.figure("Oscilloscope") plt.xlabel("Temps en s") plt.ylabel("Tension en V") plt.grid(True) plt.plot(temps,tension) plt.show()
On obtient un peu plus de 16000 mesures pas seconde :