4. Exemples d'utilisation de la carte Arduino en physique▲
4-1. La carte Arduino Uno : un système d'acquisition▲
Parmi les nombreuses façons d'utiliser la carte Arduino, l’une des possibilités est d'en faire un système d'acquisition permettant au physicien ou au chimiste de récolter à l'aide d'un ou plusieurs capteurs les informations essentielles à l'analyse d'une expérience. Dans ce contexte la carte Arduino peut-être utilisée comme :
- serveur de données (data server) vers un langage de programmation à travers une liaison série ou USB ;
- enregistreur de données (datalogger) autonome avec un shield SD, les données sont sauvegardées sur une carte SD pour ensuite être lues et exploitées à l'aide d'un langage de programmation ou d'un logiciel ;
- pseudo système embarqué (embedded system) avec traitement des données, des opérations de calcul doivent être faites en réponse à un évènement extérieur avant stockage ou transmission de l'information.
Il est également possible de piloter des actionneurs à partir des sorties numériques. Certaines sorties numériques peuvent simuler une sortie analogique avec la modulation de largeur d'impulsion (pulse width modulation ou PWM). Il est ainsi envisageable de réaliser par exemple la caractéristique d'un dipôle résistif.
4-2. Synthèse additive des couleurs avec un actionneur de type diode électroluminescente RGB▲
Dans un premier temps, l'objectif est d'utiliser une DEL capable de produire trois couleurs différentes et de réaliser une synthèse additive avec les couleurs rouge, vert et bleu.
4-2-1. Les schémas électriques (Documentation Arduino)▲
4-2-2. La réalisation▲
13. À partir du programme test (cf 2.5Le premier programme), écrire un programme Arduino permettant de faire clignoter la LED avec une fréquence de 1 Hz et avec une alternance des couleurs rouge, vert, bleu.
14. Modifier votre programme pour obtenir le clignotement de la LED, mais avec l'alternance de couleurs jaune, cyan, magenta et blanc.
15. Combien de couleurs peut-on obtenir en utilisant la fonction digitalWrite
?
16. Il est possible d'augmenter le nombre de couleurs grâce à la fonction : analogWrite
. Lire l’API Arduino pour comprendre l’utilisation de cette fonction.
17. Écrire un programme Arduino permettant d’obtenir quatre niveaux d’intensité différents sur un même canal (0, 75, 125, 250) avec un changement toutes les secondes. Attention, il faudra peut-être modifier la position de votre DEL suivant vos attentes. En effet, seules les broches précédées d'un ∼ sont utilisables avec la fonction : analogWrite
18. Combien de couleurs peut-on obtenir avec la fonction : analogWrite
?
4-3. Étude statistique de la mesure d'une tension avec la carte Arduino Uno▲
4-3-1. Meilleure estimation de la moyenne et de l'écart type d'une distribution de valeurs▲
La meilleure estimation de la valeur vraie d'une grandeur kitxmlcodeinlinelatexdvpXfinkitxmlcodeinlinelatexdvp , notée kitxmlcodeinlinelatexdvp\bar{x}finkitxmlcodeinlinelatexdvp est donnée par la moyenne arithmétique définie comme suit :
kitxmlcodelatexdvp\bar{x}= \frac{1}{n}\sum_{i=1}^n x_ifinkitxmlcodelatexdvpPar suite, la meilleure estimation de l'écart type sur la population des kitxmlcodeinlinelatexdvpx_ifinkitxmlcodeinlinelatexdvp est donnée par :
kitxmlcodelatexdvp\sigma_x = \sqrt{\frac{1}{n-1}\sum_{i=1}^n(x_i-\bar{x})^2}finkitxmlcodelatexdvpDans l'hypothèse où toute erreur systématique a été écartée et où les mesures individuelles kitxmlcodeinlinelatexdvpx_ifinkitxmlcodeinlinelatexdvp sont reparties selon une loi normale, on appelle "incertitude type élargie" la valeur donnée par :
kitxmlcodelatexdvp\Delta(x) = t_{n,p\%}\times \frac{\sigma_x}{\sqrt{n}} = t_{n,p\%} \times u(x)finkitxmlcodelatexdvpAvec kitxmlcodeinlinelatexdvpt_{n,p\%}finkitxmlcodeinlinelatexdvp coefficient de Student donné par les tables et kitxmlcodeinlinelatexdvpu(x)finkitxmlcodeinlinelatexdvp incertitude type.
L'intervalle de confiance s'exprime sous la forme : kitxmlcodeinlinelatexdvp[\bar{x} - \Delta x; \bar{x}+\Delta x]finkitxmlcodeinlinelatexdvp
Les résultats des mesures effectuées de la grandeur kitxmlcodeinlinelatexdvpXfinkitxmlcodeinlinelatexdvp sont ensuite présentés sous la forme :
kitxmlcodelatexdvpX = \bar{x} \pm \Delta xfinkitxmlcodelatexdvp4-3-2. Effet de la discrétisation lors de l'acquisition▲
Le montage suivant va nous permettre d'étudier l'effet de la discrétisation d'une tension analogique à travers le convertisseur analogique-numérique (CAN) de la carte Arduino Uno.
19. À l'aide du chapitre sur les entrées / sorties de la carte Arduino, écrire un programme Arduino permettant d'obtenir sur la liaison série les valeurs numériques de cette tension en fonction du temps.
20. Modifier le code pour obtenir exactement 100 mesures.
21. Effectuer un copier-coller de vos valeurs dans un logiciel permettant de réaliser le graphique suivant. Les logiciels libres Veusz et LabPlot peuvent être un bon compromis à des logiciels plus connus, mais souvent plus onéreux.
On constate que les valeurs prises par la tension mesurée sont discrètes. Cette discrétisation est due à la conversion analogique numérique du CAN de la carte Arduino Uno. De manière générale si les valeurs analogiques sont codées avec une précision sur n-bits alors nous disposons de kitxmlcodeinlinelatexdvp2^nfinkitxmlcodeinlinelatexdvp valeurs possibles dans l'intervalle kitxmlcodeinlinelatexdvp[0,2^n − 1]finkitxmlcodeinlinelatexdvp. En d'autres termes, il faut bien prendre conscience que toutes les valeurs analogiques de l'intervalle kitxmlcodeinlinelatexdvp[0,V_{ref}]finkitxmlcodeinlinelatexdvp ne seront pas exactement représentables par les valeurs numériques. Il est intéressant de noter que le plus petit écart entre deux valeurs numériques représente le pas en tension encore appelé résolution noté par la suite kitxmlcodeinlinelatexdvp\partial ufinkitxmlcodeinlinelatexdvp
kitxmlcodelatexdvp\partial u =\frac{V_{ref}}{2^n}finkitxmlcodelatexdvp22. Déterminer la résolution associée à la tension 3.3 V de la carte Arduino Uno, sachant que d'après les spécifications constructeur kitxmlcodeinlinelatexdvpV_{ref}finkitxmlcodeinlinelatexdvp = 5 V et que kitxmlcodeinlinelatexdvpnfinkitxmlcodeinlinelatexdvp = 10.
23. Repérer sur le graphique ci-dessus quelques intervalles de tension dont la longueur correspond à celui du pas en tension.
24. Quelle est la valeur numérique d'une mesure dans l'intervalle [0; 4.8 mV] ?
25. À quel intervalle en tension peut-on associer la valeur numérique 7 ?
26. En déduire la valeur moyenne de la tension délivrée par la broche 3.3 V.
4-3-3. Mesure avec la carte Arduino Uno et loi normale▲
La mesure d'une tension constante engendre un grand nombre de valeurs identiques, mais nous observons également une certaine fluctuation de cette valeur sans doute due à l'état électronique du circuit qui n'est pas rigoureusement le même dans le temps et à une erreur aléatoire de mesure. Intéressons-nous à cette erreur aléatoire et essayons de vérifier qu'elle suit bien une loi normale.
Pour cela, on mesure une tension U ≃ 3.3 V (toujours avec le montage précédent) en réalisant une acquisition de N = 1000 points. On peut alors effectuer un traitement statistique de l'erreur aléatoire basé sur la répétition des mesures de la tension kitxmlcodeinlinelatexdvpufinkitxmlcodeinlinelatexdvp. Dans notre cas, il s'agit d'extraire les meilleures estimations de la valeur moyenne de kitxmlcodeinlinelatexdvpufinkitxmlcodeinlinelatexdvp notée kitxmlcodeinlinelatexdvp\bar{u}finkitxmlcodeinlinelatexdvp et de l'écart type kitxmlcodeinlinelatexdvp\sigma_ufinkitxmlcodeinlinelatexdvp.
4-3-4. Récupération et stockage des données avec Python▲
Nous allons maintenant utiliser Python pour établir une liaison USB destinée au transfert des données de Arduino vers Python, puis pour stocker les données reçues en vue d'un traitement ultérieur. Pour cela il faut commencer par ouvrir un nouveau notebook que l'on pourra renommer : discrétisation, attention pas d'accent dans les noms de Notebook.
Dans la première cellule de code, recopier les importations des packages nécessaires à la gestion de cet exemple.
Code Python
Les déclarations de packages.
2.
import
serial # gestion du port série
import
matplotlib.pyplot as
plt # pour faire de beaux graphiques
Dans une deuxième cellule, nous allons nous concentrer sur l'écriture du code principal permettant de récupérer et d'organiser les données. Attention, la connexion Python avec le port série demande à être adaptée en fonction de votre système d'exploitation (3.4.2Lecture des données envoyées par la carte Arduino avec Python).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
# connexion Linux au port série
serial_port =
serial.Serial
(
port =
"/dev/ttyACM0"
, baudrate =
9600
)
# les mesures
mesures =
[]
temps =
[]
serial_port.flushInput
(
)
for
i in
range(
1000
):
val =
serial_port.readline
(
).split
(
)
try
:
t =
float(
val[0
])
m =
float(
val[1
])
mesures.append
(
m)
temps.append
(
t)
except
:
pass
# fermeture du port série
serial_port.close
(
)
- Lignes 4 et 5 : déclaration de deux listes qui recevront les mesures de l'expérience (comme dans les colonnes d'un tableur).
- Ligne 6 : on vide la mémoire tampon de la liaison série. Cela permet d'effacer d'éventuelles données de l'acquisition précédente restées en mémoire.
- Ligne 7 : on démarre une boucle qui permet de récupérer 1000 valeurs. Toutes les instructions associées à la boucle sont indentées.
-
Ligne 8 : permet de lire le flux de données envoyé sur le port série par Arduino. Rappelez-vous que le programme Arduino envoie deux valeurs sur le port série, le temps et la mesure du capteur. Il faut donc séparer ces deux valeurs. Pour cela, nous utilisons la fonction split(). Elle sépare les valeurs grâce à l'espace que nous avons laissé et les range dans une liste Python. Une liste Python peut-être vue comme un tableau dont chaque case porte un numéro.
- La première case a le numéro 0.
- Pour ajouter une valeur dans la liste, on utilise la fonction append()
- Pour lire la valeur contenue dans la première case de la liste val, on écrit : val[
0
], pour lire la valeur contenue dans la n+1 ième case on écrit : val[n], pour lire les valeurs comprises entre les cases n et m incluses on écrit : val[n:m+
1
]
-
Ligne 9 :
try
permet de gérer une erreur d'exécution dans un programme sans pour autant que le programme s'arrête brutalement.
Le mécanisme de gestion des exceptions s'effectue en deux phases :- la levée d'exception avec la détection d'erreurs : le problème se trouve dans les lignes 10 et 11 lors de la conversion ;
- le traitement approprié : ligne 15, nous décidons de ne rien faire avec le mot clé
pass
- Ligne 10 à 13 : nous essayons de convertir les données reçues en valeurs décimales et nous les ajoutons aux listes temps et mesure . N'oublions pas que les données envoyées par Arduino sont au format texte. Il est donc nécessaire de les convertir en valeur numérique. La conversion réalisée est de type float, soit des valeurs décimales.
- Ligne 14 et 15 : si cela ne marche pas, on passe et on lit une nouvelle ligne envoyée par Arduino
- Ligne 17 : surtout ne pas oublier de fermer le port de communication
Normalement plus de mystère, vous pouvez maintenant recopier le code correspondant à l'acquisition des mesures. Le gros avantage c'est que l'on écrit le code une fois pour toutes. Il peut même être déjà disponible dans un notebook que vous donnez à vos élèves. Mais rien n'empêche de le modifier pour satisfaire une demande particulière. Par exemple, on peut vouloir enregistrer les données de manière persistante sur le disque dur ou bien sur une carte SD.
4-3-5. Affichage des données sous forme d'histogramme▲
2.
3.
4.
5.
# l'intervalle des mesures doit être adapté avec vos valeurs
plt.hist
(
mesures, range=
[718.5
, 728.5
], bins=
10
, edgecolor =
'black'
)
plt.xlabel
(
"Valeur numérique"
)
plt.ylabel
(
"Fréquence"
)
plt.show
(
)
L'histogramme des mesures est représenté ci-dessous :
4-3-6. Traitement des données : moyenne, écart type et loi normale▲
En vue du calcul de la moyenne et de l'écart type de la distribution, nous allons ajouter à la cellule des packages, le package scipy. Remarquer la différence de syntaxe avec l'ajout de la totalité d'un package.
from
scipy import
std, mean
En fait, nous n'ajoutons pas toutes les fonctionnalités de ce module, mais seulement celles dont nous avons besoin, c'est-à-dire std pour l'écart type et mean pour la moyenne.
Dans une nouvelle cellule, il ne reste plus qu'à calculer moyenne et écart type
2.
3.
moyenne =
mean
(
mesures)
std_m =
std
(
mesures, ddof=
1
)
print
(
"Moyenne numérique = "
, moyenne, "
\n
Écart type numérique = "
, std_m)
Moyenne numérique = 723.891
Écart type numérique = 1.4727929250237457
On peut également ajouter la représentation graphique de la fonction de distribution normale définie par :
kitxmlcodelatexdvpf: x \mapsto \frac{1}{\sigma_x\sqrt{2\pi}}e^{\frac{-(x-\bar{x})^2}{2\sigma^2_x}}finkitxmlcodelatexdvpoù kitxmlcodeinlinelatexdvp\bar{x}finkitxmlcodeinlinelatexdvp = moyenne et kitxmlcodeinlinelatexdvp\sigma_xfinkitxmlcodeinlinelatexdvp = écart type
La surface totale comprise entre la courbe de la fonction kitxmlcodeinlinelatexdvpffinkitxmlcodeinlinelatexdvp et l'axe des kitxmlcodeinlinelatexdvpxfinkitxmlcodeinlinelatexdvp vaut 1. Pour tracer la fonction kitxmlcodeinlinelatexdvpffinkitxmlcodeinlinelatexdvp appliquée à notre exemple, il est donc nécessaire que l'aire sous la courbe de kitxmlcodeinlinelatexdvpffinkitxmlcodeinlinelatexdvp dans l'intervalle étudié soit égale à l'aire des rectangles de l'histogramme. L'expression de la fonction kitxmlcodeinlinelatexdvpffinkitxmlcodeinlinelatexdvp devient :
kitxmlcodelatexdvpf: x \mapsto \frac{A}{\sigma_x\sqrt{2\pi}}e^{\frac{-(x-\bar{x})^2}{2\sigma^2_x}}finkitxmlcodelatexdvpkitxmlcodeinlinelatexdvpAfinkitxmlcodeinlinelatexdvp représente l'aire sous la courbe que l'on calcule avec la valeur de l'intervalle de classe et le nombre d'échantillons soit : kitxmlcodeinlinelatexdvpA = 1000∗1finkitxmlcodeinlinelatexdvp
Avoir une distribution normale des échantillons, cela signifie que 95 % des mesures les plus proches de la moyenne sont dans un intervalle dont la largeur est de : kitxmlcodeinlinelatexdvp4\sigma_x \simeq 6finkitxmlcodeinlinelatexdvp
kitxmlcodelatexdvpx = 724 \pm 3finkitxmlcodelatexdvpAvec Python
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
import
numpy as
np
# 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)
#Le graphique
plt.hist (
mesures, range=
[718.5
, 728.5
], bins=
10
, edgecolor =
'black'
)
plt.xlabel
(
"Valeur numérique"
)
plt.ylabel
(
"Fréquence"
)
plt.plot
(
x, y, 'red'
)
plt.show
(
)
Les graphiques ci-dessus montrent que la loi de distribution de la mesure d'une tension délivrée par la carte Arduino Uno est approximativement une loi normale, c'est-à-dire que la probabilité de trouver la grandeur kitxmlcodeinlinelatexdvpXfinkitxmlcodeinlinelatexdvp dans l'intervalle kitxmlcodeinlinelatexdvp[\bar{x} - 2\sigma_x, \bar{x}+2\sigma_x]finkitxmlcodeinlinelatexdvp est de 95 %. Ce qui permet d'appliquer le principe d'incertitude élargie à 95 %.
Dans l'hypothèse où toute erreur systématique a été écartée et où les kitxmlcodeinlinelatexdvpNfinkitxmlcodeinlinelatexdvp mesures individuelles kitxmlcodeinlinelatexdvpx_ifinkitxmlcodeinlinelatexdvp sont réparties selon une loi normale
kitxmlcodelatexdvp\Delta x = t_{(N,p\%)}\frac{\sigma_x}{\sqrt{N}}finkitxmlcodelatexdvpavec kitxmlcodeinlinelatexdvpt_{(N,p\%)}finkitxmlcodeinlinelatexdvp le coefficient de student
27. Réaliser l'histogramme de droite avec les informations suivantes.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
"""
On peut afficher deux histogrammes superposés en appliquant un effet
de transparence (alpha) sur le dernier histogramme affiché.
"""
#... à compléter
plt.hist
(
x1, bins =
bins, edgecolor =
'red'
,
hatch =
'/'
, label =
'x1'
)
plt.hist
(
x2, bins =
bins, color =
'yellow'
, alpha =
0.5
,
edgecolor =
'blue'
, label =
'x2'
)
#...
plt.legend
(
)
plt.show
(
)
2.
3.
4.
# génération d'un tableau de points tirés au hasard suivant une loi normale
import
numpy as
np
n =
1000
# nombre de points
x =
np.random.normal
(
moyenne, std_m, n) # tableau de points
28. Réaliser la même étude statistique en remplaçant l'alimentation Arduino 3.3 V par une alimentation stabilisée et comparer moyenne et écart type.
29. À l'aide des documents ci-dessous, étudier l'influence de la tension de référence sur la carte Arduino Uno pour une mesure de tension ≃ 0.8 V
Tension de référence de la carte Arduino
Le microcontrôleur de l'Arduino possède plusieurs tensions de référence utilisables selon la plage de variation de la tension que l'on veut mesurer… Pour cela, rien de matériel, tout se passe au niveau du programme, car il existe une fonction analogReference. Cette fonction prend en paramètre le nom de la référence à utiliser.
- DEFAULT : la référence de 5 V par défaut.
- INTERNAL : une référence de 1.1 V sur la carte Arduino Uno
- EXTERNAL : la référence est celle appliquée sur la broche AREF
Comment utiliser la fonction analogReference ?
2.
3.
4.
5.
void
setup ( ) {
// permet de choisir une tension de référence de 1.1 V
analogReference
(INTERNAL) ;
}
4-3-7. Des idées pour la suite▲
- On peut compléter cet exemple en mesurant une même tension, d'une part avec la carte Arduino Uno avec une méthode statistique et d'autre part avec un multimètre numérique. En récupérant la précision du multimètre sur la notice, on peut exprimer l'intervalle de confiance de la mesure et ainsi comparer les résultats. Il est alors envisageable de mettre en évidence une éventuelle erreur systématique si les intervalles de confiance sont disjoints.
- L'incertitude type évoluant kitxmlcodeinlinelatexdvp\frac{1}{\sqrt{N}}finkitxmlcodeinlinelatexdvp avec kitxmlcodeinlinelatexdvpNfinkitxmlcodeinlinelatexdvp le nombre de mesures, on peut aussi se questionner sur l'évolution de la largeur de l'intervalle de confiance quand le nombre de mesures augmente.
4-4. Mesure de fréquence avec capteur analogique de type photorésistor ou photodiode▲
4-4-1. Le montage électrique▲
D'après la doc Arduino
4-4-2. Mesurer la fréquence d'un stroboscope (application smartphone)▲
Deux possibilités s'offrent à nous. Soit on utilise un stroboscope classique que l'on trouve normalement au laboratoire de physique ou bien il peut être remplacé par une application smartphone.
- Télécharger n'importe quelle application de stroboscope sur votre smartphone, pour cette expérience j'ai utilisé Stroboscopique
- ou télécharger l'application Physics Toolbox Suite, puis cliquer sur stroboscope dans le menu de gauche.
- Régler la fréquence sur 1 Hz
- Placer le flash de votre téléphone au-dessus de la photorésistance ou de la photodiode
Le code Arduino associé à cette expérience est extrêmement simple, il nous suffit de lire les valeurs envoyées par le capteur (photorésistance ou photodiode) sur l’une des entrées analogiques de la carte Arduino. Rappelez-vous, celle-ci propose 6 entrées analogiques (de A0 à A5) connectées à un convertisseur analogique-numérique 10 bits (210 valeurs possibles de 0 à 210 −1). Ce qui permet de transformer la tension d'entrée comprise entre 0 et 5 V en une valeur numérique entière comprise entre 0 et 1023. La résolution (écart entre 2 mesures) est de : 5 volts / 210 intervalles, soit 0.0049 volt (4.9 mV).
30. Compléter puis téléverser le code Arduino.
31. Comment faudrait-il modifier le code pour que le nom de variable valeur référence bien une tension (attention au type de la variable).
Code Arduino
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
// Variables à déclarer
void
setup() {
Serial
.begin(19200
);
}
void
loop() {
// À compléter // valeur numérique lue sur la broche A0
Serial
.print(valeur); // Envoi la mesure au PC par la liaison série (port USB)
Serial
.print("
\t
"
); // Ajout d'un espace
Serial
.println(millis
()); // Envoi de la valeur temps puis retour à la ligne
// Une éventuelle temporisation
}
32. À l'aide du moniteur série, observer les résultats obtenus.
33. À l'aide d'un tableur, comment tracer le graphique correspondant à kitxmlcodeinlinelatexdvpu = f (t)finkitxmlcodeinlinelatexdvp ?
Nous allons maintenant utiliser Python pour automatiser la gestion des données envoyées par le capteur. Pour cela, il faut commencer par ouvrir un nouveau notebook que l'on pourra renommer : stroboscope.
Dans la première cellule de code, recopier les importations des packages nécessaires à la gestion de cet exemple.
Code Python
34. Écrire le code Python permettant d'utiliser les instructions suivantes pour l'affichage du résultat.
L'affichage sous la forme d'un graphique
2.
3.
4.
5.
6.
7.
# On évite les effets de bord en éliminant
#les valeurs de début et de fin de transmission
plt.plot (
temps[100
:900
], mesure[100
:900
])
plt.xlabel
(
"Temps (s)"
)
plt.ylabel
(
"Intensité"
)
plt.grid
(
)
plt.show
(
)
Photodiode (f = 1 Hz et 4 Hz)
Photorésistance (f = 1 Hz et 5 Hz)
On voit qu'après chaque flash (supposé suffisamment court), le photorécepteur reste conducteur pendant une durée qui va dépendre du type de photorécepteur :
- pour la photodiode, le temps de réponse est très court, de l’ordre de quelques microsecondes. Cela illustre la bonne réponse en fréquence de la photodiode ;
- pour la photorésistance, le temps de réponse est relativement court, mais elle reste conductrice durant plusieurs dixièmes de secondes
On peut améliorer la lecture du flux de données afin d'assouplir l'utilisation du code Python. Pour cela, nous allons écrire deux fonctions Python dont nous détaillerons l'utilisation.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
def
acquisition
(
n, serial_port):
'''
Cette fonction permet de faire l’acquisition des données reçues
sur le port USB en fonction du temps
Elle renvoie deux listes : temps et mesures (du capteur)
n <int> : nombre total de valeurs à lire
serial_port <serial> : le port série ouvert à la communication
'''
i =
0
temps, mesures =
[], []
while
i <
n:
val =
serial_port.readline
(
).split
(
)
try
:
t =
float(
val[1
])
m =
float(
val[0
])
temps.append
(
t)
mesure.append
(
m)
i =
i +
1
except
:
pass
return
temps, mesures
35. Comment le code de la fonction acquisition a-t-il été modifié par rapport au code précédent et pourquoi ?
Pour lancer une acquisition avec 1000 points :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
# connexion Linux au port série
serial_port =
serial.Serial
(
port =
"/dev/ttyACM1"
, baudrate =
19200
)
serial_port.setDTR
(
False
)
time.sleep
(
0.1
) # attention le module time est nécessaire
serial_port.setDTR
(
True
)
serial_port.flushInput
(
)
temps, mesure =
acquisition (
1000
, serial_port)
# fermeture du port série
serial_port.close
(
)
4-4-3. Fixer la durée d'acquisition▲
Dans l'exemple précédent, l'acquisition dépend d'un nombre de points. Mais il est souvent plus utile de pouvoir contrôler le temps d'acquisition. Le code Arduino ne change pas et le code Python ne va subir qu'une toute petite modification au niveau de la boucle. Au lieu de compter un nombre de points, nous allons définir un temps d'acquisition. Rappelons que le code Arduino transmet à chaque itération de la fonction loop une ligne contenant une valeur et une date d'acquisition. Pour contrôler le temps d'acquisition, il suffit donc de surveiller la différence entre la date en cours d'acquisition et la date du début d'acquisition. Comme les dates d'acquisition sont dans une liste temps, nous allons surveiller temps[-
1
] -
temps[0
] avec :
- temps[
-
1
] le dernier élément de la liste temps ; - temps[
0
] le premier élément de la liste.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
# ouverture du port série
serial_port =
serial.Serial
(
port =
"/dev/ttyACM0"
, baudrate =
19200
)
serial_port.setDTR
(
False
)
time.sleep
(
0.1
)
serial_port.setDTR
(
True
)
serial_port.flushInput
(
)
# les mesures
mesure =
[]
temps =
[]
duree =
10000
end =
False
while
end ==
False
or
temps[−1
] − temps[0
] <=
duree:
val =
serial_port.readline
(
).split
(
)
try
:
t =
float(
val[1
])
m =
float(
val[0
])
temps.append
(
t)
mesure.append
(
m)
end =
True
except
:
pass
# fermeture du port série
serial_port.close
(
)
36. Écrire une fonction acquisition_temps
(
duree, serial_port) qui prend en paramètres la durée d'acquisition et la connexion au port série. Cette fonction renvoie dans l'ordre la liste des dates et mesures de l'acquisition.
L'affichage sous la forme d'un graphique
2.
3.
4.
5.
6.
7.
8.
# attention les deux listes doivent contenir le même nombre de valeurs.
plt.plot
(
temps, mesure)
plt.title
(
"Fréquence d'un stroboscope"
)
plt.ylabel
(
'Intensité'
)
plt.xlabel
(
'Temps (ms)'
)
plt.grid
(
)
plt.show
(
)
4-5. Utilisation d'un bouton-poussoir pour déclencher l'acquisition▲
L'objectif est d'ajouter à l'expérience du stroboscope, un bouton-poussoir pour déclencher l'acquisition côté Arduino afin que Python puisse enregistrer les données transférées. Dans cet exemple, très fortement inspiré d'une activité de Jean-Luc Charles(5), nous parlerons d'automate.
Concept d'automate
Un automate fini est un modèle mathématique des systèmes ayant un nombre fini d'états et que des actions (externes ou internes) peuvent faire passer d'un état à un autre.
Rien de mieux qu'un exemple pour comprendre :
- à l'état initial, l'automate est à l'état WAIT : l'acquisition est en attente ;
- l'appui sur le bouton- poussoir fait passer l'automate dans l'état START : l'acquisition démarre ;
- un nouvel appui sur le bouton- poussoir fait passer l'automate de l'état START à l'état STOP : l'acquisition est suspendue ;
- les appuis successifs font passer successivement de l'état START à l'état STOP, et de l'état STOP à l'état START.
Image extraite d'une activité de Jean-Luc Charles(6) |
4-5-1. Le montage électrique▲
La broche numérique 3 de la carte Arduino est utilisée comme une entrée numérique qui reste à LOW tant que le bouton n'est pas enfoncé. Le bouton se comporte comme un interrupteur qui ne laisse pas passer le courant tant qu'il est en position haute. Dans cet exemple, la broche 3 est en mode INPUT : pinMode
(3
, INPUT
), pour indiquer que la broche est en mode lecture. Elle ne va donc pas piloter du courant, mais être à l'écoute du courant qui lui arrive.
4-5-2. Le code Arduino▲
Côté Arduino, ça donne quoi ? Commençons par les variables globales et la fonction setup.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
// État en cours de l'automate
int
etat;
// État à mémoriser
int
oldEtat;
//Les états possibles de l'automate
const
int
WAIT =
2
;
const
int
START =
1
;
const
int
STOP =
0
;
// Les broches utilisées
//capteur
const
int
broche =
A0;
//bouton poussoir
const
int
BP =
3
;
void
setup() {
//initialisation des variables
oldEtat =
LOW
;
etat =
WAIT;
//config E/S
pinMode
(BP, INPUT
);
//liaison série
Serial
.begin(19200
);
}
Comme convenu dans l'introduction, nous avons défini les différents états de notre automate et initialisé une variable oldEtatBP qui nous permettra de garder en mémoire l'état du bouton avant un nouvel appui. On remarquera également que l'état de notre automate est WAIT, nous attendons le démarrage de l'acquisition.
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.
void
loop() {
int
etatBP =
digitalRead
(BP); // Lecture du bouton
if
(oldEtat ==
LOW
&&
etatBP ==
HIGH
) {
//gestion des états
if
(etat ==
WAIT)
{
etat =
START;
}
else
if
(etat ==
STOP)
{
etat =
START;
}
else
if
(etat ==
START)
{
etat =
STOP;
}
}
//Traitement des états
if
(etat ==
START) {
int
valeur =
analogRead
(broche);
Serial
.print(valeur);
Serial
.print("
\t
"
);
Serial
.println(millis
());
}
oldEtat =
etatBP;
delay
(10
);
}
Il n'y a plus qu'à tester le programme :
- téléverser le programme dans la mémoire de la carte Arduino ;
- ouvrir le moniteur série ;
- lancer l'acquisition en appuyant une première fois sur le bouton ;
- stopper l'acquisition en appuyant une deuxième fois sur le bouton ;
- on peut recommencer autant de fois que l'on veut…
37. Modifier le programme pour que lorsque l'acquisition s'arrête, c'est-à-dire que l'on appuie sur le bouton pour la deuxième fois, la chaîne -1\t -1 s'affiche dans le moniteur série. Attention, dans le moniteur série, le \t sera remplacé par une tabulation.
4-5-3. Le code Python▲
Attention le code Arduino ci-dessous fonctionnera correctement uniquement si vous avez répondu à la question précédente, si cela pose un problème consulter la solution dans les annexes, compléter le code Arduino et poursuivez.
2.
3.
# les modules à importer
import
serial
import
matplotlib.pyplot as
plt
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
# ouverture du port série et synchronisation des données entre arduino et Python.
serial_port =
serial.Serial
(
port =
"/dev/ttyACM0"
, baudrate =
19200
, timeout =
None
)
serial_port.flushInput
(
)
# les mesures
mesure =
[]
temps =
[]
end =
False
while
end ==
False
:
val =
serial_port.readline
(
).split
(
)
if
val[0
] ==
b'-1'
: # Bouton poussoir à l'état STOP
end =
True
# Terminaison de la boucle
else
:
try
:
t =
float(
val[1
])
m =
float(
val[0
])
temps.append
(
t)
mesure.append
(
m)
except
:
pass
# fermeture du port série
serial_port.close
(
)
Pour tester l'ensemble, assurez-vous que vous avez bien effectué les étapes de la section précédente côté Arduino :
- valider les cellules du Notebook ;
- normalement sur la gauche de la deuxième cellule, vous observez une petite étoile : In [*] ;
- pas de panique avec le bouton, on a tout notre temps ;
- positionner votre stroboscope au-dessus de la photorésistance ;
- lancer l'acquisition des valeurs en appuyant une première fois sur le bouton ;
- terminer l'acquisition en appuyant une deuxième fois sur le bouton, si tout s'est bien passé, l'étoile de votre In [*] disparaît pour laisser place à un nombre ;
- afficher vos résultats dans un graphique.
À chaque fois que l'on termine une acquisition, il faut revalider la cellule du notebook contenant le code ci-dessus pour mettre en attente le code Python d'une nouvelle acquisition. L'instruction serial_port.close() réinitialise le code Arduino et met donc l'automate côté Arduino dans l'état WAIT. Il n'y a plus qu'à appuyer sur le bouton…
4-6. Temps de réponse d'une thermistance de type CTN▲
4-6-1. Présentation de la thermistance CTN▲
La dépendance en température d'une résistance CTN n'est pas linéaire, elle peut être bien approximée en fonction de la résistance kitxmlcodeinlinelatexdvpR_{Th}finkitxmlcodeinlinelatexdvp de la thermistance à l'aide de la relation suivante :
kitxmlcodelatexdvp\theta = \frac{1}{\frac{\ln\left(\frac{R_{Th}}{R_0}\right)}{\beta} + \frac{1}{T_0}} - 273.15finkitxmlcodelatexdvpkitxmlcodeinlinelatexdvp\betafinkitxmlcodeinlinelatexdvp est une constante de température (kelvin), kitxmlcodeinlinelatexdvpR_0finkitxmlcodeinlinelatexdvp est une résistance de référence (ohms) et kitxmlcodeinlinelatexdvpT_0finkitxmlcodeinlinelatexdvp est la température à laquelle s'applique la résistance de référence (kelvin). Ces constantes sont caractéristiques de la thermistance utilisée.
Le temps de réponse d'un capteur en température n'est pas nul, car le capteur ne s'adapte jamais instantanément à une variation de température du milieu ambiant (air, eau, huile moteur…). Si la température du milieu ambiant passe d'une valeur kitxmlcodeinlinelatexdvp\thetafinkitxmlcodeinlinelatexdvp initiale à une valeur kitxmlcodeinlinelatexdvp\thetafinkitxmlcodeinlinelatexdvp finale supérieure à la température initiale kitxmlcodeinlinelatexdvp\thetafinkitxmlcodeinlinelatexdvp initiale, le temps de réponse kitxmlcodeinlinelatexdvpt_{90\%}finkitxmlcodeinlinelatexdvp est la durée nécessaire pour que la température mesurée par le capteur passe de la valeur kitxmlcodeinlinelatexdvp\thetafinkitxmlcodeinlinelatexdvp initiale à la valeur : |
4-6-2. Le montage électrique▲
Il est possible d'acheter des thermistances CTN (10 K) étanches pour un prix très raisonnable (< 1 € ref sur Alixpress : NTC Thermistance, précision capteur de température 10 K 1% 3950 Sonde Étanche 1 m)
- kitxmlcodeinlinelatexdvp\beta = 3380\ K\pm 1%finkitxmlcodeinlinelatexdvp
- kitxmlcodeinlinelatexdvpR_0 = 10\ k\Omega\pm 1%finkitxmlcodeinlinelatexdvp
- plage de mesure : -20 à 105 °C avec kitxmlcodeinlinelatexdvpT_0 \approx 298\ Kfinkitxmlcodeinlinelatexdvp
4-6-3. Les codes du montage▲
Côté Arduino
Le code à compléter ci-dessous peut-être l'occasion de discuter de la conversion d'une valeur numérique codée sur 10 bits (A0 et A1) en valeur analogique. Pour compléter ce code, nous pourrons utiliser la fonction map.
La fonction map avec Arduino
Réétalonne un nombre appartenant à un intervalle de valeurs vers un autre intervalle de valeurs. Dans notre cas, la valeur numérique kitxmlcodeinlinelatexdvp\in [0, 1023]finkitxmlcodeinlinelatexdvp en valeur analogique kitxmlcodeinlinelatexdvp\in [0\ V, 5\ V]finkitxmlcodeinlinelatexdvp
map
(valeur, limite_basse_source, limite_haute_source, limite_basse_destination, limite_haute_destination)
- valeur : le nombre à réétalonner
- limite_basse_source : la valeur de la limite inférieure de la fourchette de départ
- limite_haute_source : la valeur de la limite supérieure de la fourchette de départ
- limite_basse_destination : la valeur de la limite inférieure de la fourchette de destination
- limite_haute_destination : la valeur de la limite supérieure de la fourchette de destination
Il est possible d'obtenir des informations supplémentaires et des exemples liés à cette fonction avec l'API d'Arduino.
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.
//Fonction setup(), appelée au démarrage de la carte Arduino
void
setup()
{
Serial
.begin(9600
); // initialisation de la communication série à 9600 bps
}
// Fonction loop(), appelée en boucle si la carte Arduino est alimentée
void
loop()
{
// Déclaration des variables
unsigned
long
temps =
millis
();
double
U, Uther, tensionU, tensionUther ;
// lecture des tensions U et Uther sur A0 et A1 et initialisation
// du compteur temporel
temps =
millis
() /
1000
;
U =
analogRead
(0
);
// conversion de la tension lue sur A0 en valeur analogique
tensionU =
// conversion de la tension lue sur A0 de mV en V
tensionU =
Uther =
analogRead
(1
);
// conversion de la tension lue sur A1 en valeur analogique
tensionUther =
// conversion de la tension lue sur A1 de mV en V
tensionUther =
// Envoi les mesures sur le port série
Serial
.print(tensionU);
Serial
.print("
\t
"
);
Serial
.print(tensionUther);
Serial
.print("
\t
"
);
Serial
.println(temps);
// intervalle de temps d'une seconde (1000 ms) entre
// deux exécutions de la boucle donc entre deux mesures
delay
(1000
);
}
Côté Python
2.
3.
4.
5.
6.
# Cellule 1 : les modules à importer
import
serial
import
time
import
numpy as
np
import
matplotlib.pyplot as
plt
%
matplotlib auto
Dans la cellule des modules à importer, rien de nouveau à part la ligne 6. Dans les exemples précédents, nous avions l'habitude d'écrire %
matplotlib inline. Nous avons remplacé inline par auto afin de pouvoir afficher une fenêtre extérieure au Notebook. Cette fenêtre offre quelques fonctionnalités de base étendues comme la possibilité de suivre la courbe avec un réticule.
La cellule suivante donne la définition d'une fonction permettant d'effectuer la synchronisation temporelle pour le transfert des valeurs entre Arduino et Python. Les instructions liées à cette fonction ont déjà été décrites dans la partie Communication Arduino - Python via le port sérieCommunication Arduino - Python via le port série
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
# Cellule 2
def
synchro_arduino (
port_name, vitesse):
"""
Cette fonction initialise et renvoie une référence sur la connexion
avec la carte Arduino à travers le port série (USB).
Elle permet également de synchroniser le lancement de l'acquisition
côté Arduino avec la récupération des données côté Python.
"""
serial_port =
serial.Serial
(
port =
port_name, baudrate =
vitesse)
serial_port.setDTR
(
False
)
time.sleep
(
0.1
)
serial_port.setDTR
(
True
)
serial_port.flushInput
(
)
return
serial_port
À vous de compléter la fonction modelisation_CTN afin de calculer la température correspondante aux mesures reçues par Python.
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.
# Cellule 3
def
modelisation_CTN
(
mesures) :
"""
Cette fonction réalise le traitement des données, associées au capteur
thermistance, reçues de la carte Arduino.
Elle renvoie :
tensionU -> float : la tension délivrée par la carte Arduino
tensionUther -> float : la tension aux bornes du capteur
Rther -> float : la valeur de la résistance de la thermistance
temps -> float : la date de la mesure
temperature -> float : la valeur de la température
Elle prend en argument la liste des mesures effectuées par Arduino
tensionU -> float
tensionUther -> float
temps -> float
"""
Rzero =
10000
# en ohms
beta =
3380
# en Kelvins
Tzero =
298
# en Kelvins
tensionU, tensionUther, temps =
mesures
Rther =
tensionUther *
(
1
/
(
tensionU−tensionUther) *
Rzero)
temperature =
# À compléter
return
tensionU, tensionUther, Rther, temps, temperature
La dernière cellule concerne essentiellement l'affectation des valeurs à afficher à la bonne structure de données, dans notre cas des listes Python. Cette cellule est pratiquement identique à celle des exercices précédents sans le bouton- poussoir. Libre au lecteur de l'adapter s'il en ressent le besoin. J'ai juste ajouté le formatage des données pour une bonne lecture dans la sortie du Notebook. J'ai essayé de faire en sorte que cela ressemble à un tableau. On peut faire bien mieux en utilisant un module plus adapté comme Pandas avec ses DataFrames mais cela sortirait du cadre de cette formation.
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.
# Cellule 4
# ouverture du port série
serial_port =
synchro_arduino
(
"/dev/ttyACM0"
, 9600
)
# les mesures
temperature =
[]
temps =
[]
duree =
100
# en seconde (1min 40s)
end =
False
# On s'occupe de l'affichage des résultats
head =
"tensionU
\t
tensionUther
\t
Rther
\t
temps
\t
temperature
\n
"
print
(
head.expandtabs
(
10
))
fmt =
"{0:.2f}
\t
{1:.2f}
\t
{2:.2f}
\t
{3:.2f}
\t
{4:.2f}"
.expandtabs
(
16
)
# on récupère les données et on modélise
while
end ==
False
or
temps[−1
] − temps[0
] <
duree :
data_arduino =
serial_port.readline
(
).split
(
)
try
:
mesures =
np.array
(
data_arduino, dtype=
'float'
) # String -> flottant
mesures =
modelisation_CTN
(
mesures) # Calcul température
temps.append
(
mesures[3
]) # remplissage liste des temps
temperature.append
(
mesures[4
]) # remplissage liste des températures
print
(
fmt.format
(*
mesures)) # formatage de l'affichage
end =
True
except
:
pass
# fermeture du port série
serial_port.close
(
)
4-6-4. L'expérience et ses résultats▲
De nombreuses expériences sont possibles, pour ma part j'ai déposé quelques glaçons dans le fond d'un premier récipient avec un peu d'eau puis j'ai rempli un deuxième récipient avec un peu d'eau à la température de la pièce.
- Téléverser le code Arduino dans la mémoire de la carte.
- Valider les cellules 1, 2 et 3.
- Plonger la thermistance environ 30 secondes dans le récipient avec les glaçons.
- Plonger la thermistance dans l'eau du deuxième récipient puis valider la cellule 4.
Voici les résultats obtenus dans le Notebook.
Le graphique : température = f(Temps)
Exploitation
Le temps de réponse kitxmlcodeinlinelatexdvpt_{90\%}finkitxmlcodeinlinelatexdvp peut ainsi être calculé facilement avec Python
2.
3.
4.
5.
Tinitial =
temperature[0
] # première valeur dans la liste
Tfinal =
np.mean
(
temperature[60
: −1
]) # une moyenne sur les dernières valeurs
T =
Tinitial +
0.9
*
(
Tfinal − Tinitial) # température à t90%
print
(
'Tinitial = {0:.2f} C ; Tfinal = {1:.2f} C'
.format
(
Tinitial, Tfinal))
print
(
'T(t90%) = {0:.2f} C'
.format
(
T))
Tinitial = 1.35°C ; Tfinal = 23.08°C
T(t90%) = 20.90°C
On peut ainsi facilement tracer la droite Temperature = T avec l'instruction plt.axhline
(
y=
T,color=
'b'
)
Le réticule permet de lire la valeur de kitxmlcodeinlinelatexdvpt \simeq 20.7\ sfinkitxmlcodeinlinelatexdvp
4-7. Modulation par largeur d'impulsion (MLI)▲
Les sorties numériques 3,5,6,9,10,11 de la carte Arduino Uno (celles précédées d'un ˜ ) sont capables de générer un signal modulé par largeur d'impulsion qui va osciller très rapidement entre 0V et 5V, mais sans jamais prendre de valeurs intermédiaires. Attention, la fréquence du signal n'est pas la même pour toutes les broches. Nous avons une fréquence d'environ 490 Hz pour les broches 3, 9, 10, 11 et une fréquence d'environ 980 Hz pour les broches 5 et 6. Nous pouvons visualiser le signal fourni par la sortie 11 à l'aide d'un oscilloscope grâce au code suivant, en remplaçant l'intensité par une valeur comprise dans l'intervalle [0, 255] :
2.
3.
4.
5.
6.
7.
void
setup() {
pinMode
(11
, OUTPUT
);
analogWrite
(11
, intensite);
}
void
loop() {
}
Pour les valeurs d'intensité respectives de 51, 128, 191 nous obtenons :
La valeur d'intensité représente le rapport cyclique du signal. S'il est à 0 % le signal de sortie est toujours à 0 volt, de même lorsque le rapport cyclique est à 100 % (donc une intensité de 255) le signal de sortie est toujours à 5 volts. Pour une valeur d'intensité de 51, le rapport cyclique est à 20 % et le signal est égal à 5 volts pendant 20 % du temps.
38. Quelle est la valeur du rapport cyclique pour des intensités de 128 et 191 ?
Il s'agit maintenant d'utiliser une de ces sorties pour tracer la caractéristique d'un dipôle résistif. Pour ce faire, nous avons besoin d'une tension analogique continue. L'idée est de filtrer le signal avec un filtre passe-bas pour ne conserver que la composante continue de notre signal. Il faut donc que le filtre possède une fréquence de coupure inférieure à la fréquence fondamentale du signal PWM.
Avec une résistance de 15 kΩ, un condensateur de 1 µF et des intensités de 51, 128 et 191, nous obtenons :
39. Ajouter au montage une résistance de 10 kΩ et une photorésistance pour tracer la caractéristique de cette dernière pour trois éclairages différents.
40. Modifier le code Arduino et concevoir le code Python pour obtenir le résultat suivant
41. Faire de même avec une diode RGB pour tracer la caractéristique sur chaque couleur.