La plupart des microcontrôleurs offrent la possibilité de mesurer les différences de potentiels. Cett opération de mesure exploite un convertisseur analogique/numérique pour convertir la différence de potentiels mesurée en une grandeur numérique codée sur N bits et proportionnelle à la différence de potentiels présente à l'entrée du convertisseur. Dans le cas de l'Arduino Uno et de son ATmega328p, la conversion s'opère sur 10 bits et exploite une structure de convertisseur à approximation successive. Cela signifie qu'il faudra un certain nombre de périodes d'horloge de conversion pour obtenir la valeur numérique.
La vidéo suivante apporte les explications de base sur la conversion analogique/numérique dans les microcontrôleurs et propose un premier exemple d'utilisation avec le framework Arduino.
Composant très bon marché, la thermistance permet de mesurer la température avec une précision suffisante dans de très nombreuses situations et ce d'autant plus que sa forte non linéarité peut aisément être compensée par son exploitation judicieuse dans un diviseur de tension. La vidéo suivante s'intéresse à différentes possibilités de modélisation de la thermistance CTN (coefficient de température négatif = la résitance diminue lorsque la température augmente) : deux programmes Arduino sont également proposés pour la mise en oeuvre de ces modèles.
Pour ceux que cela intéresse, voici le code Scilab en vrac que j'ai utilisé pour tracer les caractéristiques :
PAS1 = 25;
PAS2 = 100
T25 = 25+273.15;
R25=10000;
B = 3950;
VCC = 5;
R0 = 10000;
clf;
T=-10+273.15:0.1:50+273.15;
//T=-2+273.15:0.1:12+273.15;
axeT = T-273.15;
// 1/T = 1/T25 + 1/B * ln(Rt/R25)
// B*(1/T - 1/T25) = ln(Rt/R25)
// R25*exp(B*(1/T - 1/T25)) = Rt
Rt = R25*exp(B*(1./(T) - 1/T25));
// Axis y1
cu=color("red");
plot2d(axeT,Rt,style=cu)
a = gca();
a.font_size = 3;
xlabel("$T(" + char(176) + " C) = T-273.15$","FontSize",5)
ylabel("$Rt(T) = 10000.e^{\beta.(1/T - 1/(25+273.15))}$","FontSize",5,"Color", "red")
gca().children(1).children(1).thickness = 3;
//title("$Rt(T)$","FontSize", 5, "color","black")
xgrid(color("grey70")*[1 1])
Vt = R0./(R0+Rt);
// Axis y2
c=color("blue");
h2=newaxes();
h2.font_size = 3;
h2.font_color = c;
set(h2, "filled", "off", "foreground", c, "font_color", c);
plot2d(axeT,Vt,style=c)
h2.axes_visible(1)="off";
h2.y_location="right";
h2.children(1).children(1).thickness=3;
ylabel("$Vt(T)/V_{CC}$","FontSize",5,"color",c)
title("$Rt(T) \text{ et } Vt(T)/V_{CC} \text{ et } \frac{1}{V_{CC}}.\frac{\partial Vt(T)}{\partial T}$","FontSize", 5, "color","black")
/*
waitClick();
//Vtprime = VCC*B*exp(B*(1./(T) - 1/T25))./((T.*(1+exp(B*(1./(T) - 1/T25))))^2);
Vtprime = R0*R25*B*exp(B*(1./(T) - 1/T25))./((T.*(R0+R25*exp(B*(1./(T) - 1/T25))))^2);
// Axis y3
c=color("darkgreen");
h3=newaxes();
h3.font_color=c;
plot2d(T-273.15,Vtprime,style=c)
h3.axes_visible(1)="off";
h3.filled="off";
h3.font_size = 3;
h3.y_location="middle";
h3.children(1).children(1).thickness=3;
ylabel("$\frac{1}{V_{CC}}.\frac{\partial Vt(T)}{\partial T}$","FontSize",5,"color",c, "position", [6 0.0105], "font_angle", 0)
function Vt = calcVt(T)
T=T+273.15;
Rt = R25*exp(B*((1/T) - (1/T25)));
Vt = R25*VCC/(R25+Rt);
endfunction
function waitClick()
[%v0,%v1,%v2,%v3,%v4] = xclick();w = bool2s(%v0>64);
endfunction
waitClick();
// Animation
sca(a);
xstring(22, 54000, "$R_0=$");
gce.font_size = 6;
xstring(30.5, 54000, "$\Omega$");
gce.font_size = 6;
xstring(26, 54000, "10000");
t = get("hdl");
t.font_size = 6;
for R0 = 5000:PAS1:25000
Vt = R0./(R0+Rt);
Vtprime = R0*R25*B*exp(B*(1./(T) - 1/T25))./((T.*(R0+R25*exp(B*(1./(T) - 1/T25))))^2);
h2.children(1).children(1).data = [T-273.15;Vt]';
h3.children(1).children(1).data = [T-273.15;Vtprime]';
//h3.ylabel("$\frac{1}{V_{CC}}.\frac{\partial Vt(T)}{\partial T}$","FontSize",4.5,"color",c, "position", [18 0.047])
// Légende
t.text = string(R0);
sleep(3);
if R0 == 10000 then
waitClick();
end
end
waitClick();
//*******************************************************************
//* Zoom sur la portion intéressante pour la sonde de réfrigérateur *
//*******************************************************************
clf;
R0=20000;
T=-2+273.15:0.1:12+273.15;
// 1/T = 1/T25 + 1/B * ln(Rt/R25)
// B*(1/T - 1/T25) = ln(Rt/R25)
// R25*exp(B*(1/T - 1/T25)) = Rt
Rt = R25*exp(B*(1./(T) - 1/T25));
// Axis y1
cu=color("red");
plot2d(T-273.15,Rt,style=cu)
a = gca();
a.font_size = 3;
xlabel("$T(" + char(176) + " C) = T-273.15$","FontSize",5)
ylabel("$Rt(T) = 10000.e^{\beta.(1/T - 1/(25+273.15))}$","FontSize",5,"Color", "red")
gca().children(1).children(1).thickness = 3;
//title("$Rt(T)$","FontSize", 5, "color","black")
xgrid(color("grey70"))
Vt = R0./(R0+Rt);
// Axis y2
c=color("blue");
h2=newaxes();
h2.font_size = 3;
h2.font_color=c;
plot2d(T-273.15,Vt,style=c)
h2.axes_visible(1)="off";
h2.filled="off";
h2.y_location="right";
h2.children(1).children(1).thickness=3;
ylabel("$Vt(T)/V_{CC}$","FontSize",5,"color",c)
title("$Rt(T) \text{ , } Vt(T)/V_{CC} \text{ , } \frac{1}{V_{CC}}.\frac{\partial Vt(T)}{\partial T}$","FontSize", 5, "color","black")
//Vtprime = VCC*B*exp(B*(1./(T) - 1/T25))./((T.*(1+exp(B*(1./(T) - 1/T25))))^2);
Vtprime = R0*R25*B*exp(B*(1./(T) - 1/T25))./((T.*(R0+R25*exp(B*(1./(T) - 1/T25))))^2);
// Axis y3
c=color("darkgreen");
h3=newaxes();
h3.font_color=c;
plot2d(T-273.15,Vtprime,style=c)
h3.axes_visible(1)="off";
h3.filled="off";
h3.y_location="middle";
h3.children(1).children(1).thickness=3;
ylabel("$\frac{1}{V_{CC}}.\frac{\partial Vt(T)}{\partial T}$","FontSize",4.5,"color",c, "position", [18 0.047])
sca(a);
xstring(6, 36000, "$R_0=$");
gce.font_size = 6;
xstring(8.1, 36000, "$\Omega$");
gce.font_size = 6;
xstring(7, 36000, "10000");
t = get("hdl");
t.font_size = 6;
for R0 = 17000:PAS2:21000
Vt = R0./(R0+Rt);
Vtprime = R0*R25*B*exp(B*(1./(T) - 1/T25))./((T.*(R0+R25*exp(B*(1./(T) - 1/T25))))^2);
h2.children(1).children(1).data = [T-273.15;Vt]';
h3.children(1).children(1).data = [T-273.15;Vtprime]';
//h3.ylabel("$\frac{1}{V_{CC}}.\frac{\partial Vt(T)}{\partial T}$","FontSize",4.5,"color",c, "position", [18 0.047])
// Légende
t.text = string(R0);
waitClick();
end
// Coordonnées avec la souris
function souris_event(win, x, y, ibut)
if ibut==-1000 then return,end
[x,y]=xchange(x,y,'i2f')
gcf().info_message = msprintf('Event code %d at mouse position is (%f,%f)',ibut,x,y);
endfunction
*/
sca(h2);
xstring(2, 0.5, "");
yt = get("hdl");
yt.font_size = 3;
//xstring(2, 0.4, "T=");
xt = get("hdl");
xt.font_size = 4;
while(1)
xy = locate(1)
yt.text = "T="+ string(xy(1)) + " Vt/Vcc=" + string(xy(2));
yt.data(1) = xy(1)-0.3;
yt.data(2) = xy(2)+0.002;
//t.text = string(xy);
end
On découvre aujourd'hui la photorésistance également appelée LDR pour Light Dependent Resistor. Objectif : allumer une LED lorsque la luminosité ambiante tombe sous un certain seuil.
Les machines à états sont une réponse méthodologique pour gérer des processus plus ou moins complexes. Dans le cas de l'interrupteur crépusculaire qui reste une application simple, faire intervenir différentes conditions d'évolutions du système en fonction du temps et du niveau de luminosité conduit rapidement à une programmation manquant de lisibilité et de fiabilité sans utiliser de machine à états. Dans ce cas de figure, l'usage d'une machine à états évite également d'avoir recours à des structures bloquantes basées sur l'usage d'instruction délais de longues durées. Les deux vidéos suivantes exposent la description d'une solution reposant sur une machine à états.
Voici les codes sources correspondants.
Pour la première vidéo :
#include <Arduino.h>
#define PIN_LED 3
void setup() {
pinMode(PIN_LED, OUTPUT);
Serial.begin(9600);
delay(100);
}
void loop() {
const uint16_t SEUIL_NUIT = 400;
const uint16_t SEUIL_JOUR = 800;
const uint32_t DELAI_JOUR = 5000; // Durée minimale en millisecondes de détection du jour avant extinction
const uint32_t DELAI_NUIT = 1000; // Durée minimale de détection de la nuit avant allumage
static uint32_t heureJour = 0; // Heure de la dernière détection de jour
static uint32_t heureNuit = 0; // Heure de la détection du début de la nuit
enum tEtats {JOUR, DEBUT_NUIT, NUIT, DEBUT_JOUR};
static tEtats etat = JOUR;
uint16_t luminosite = analogRead(A0);
switch(etat) {
case JOUR:
if (luminosite <= SEUIL_NUIT) {
etat = DEBUT_NUIT;
heureNuit = millis();
}
break;
case DEBUT_NUIT:
if (luminosite >= SEUIL_JOUR) etat = JOUR;
if ((millis() - heureNuit) >= DELAI_NUIT) {
etat = NUIT;
digitalWrite(PIN_LED, HIGH);
}
break;
case NUIT:
if (luminosite >= SEUIL_JOUR) {
heureJour = millis();
etat = DEBUT_JOUR;
}
break;
case DEBUT_JOUR:
if ((millis() - heureJour) >= DELAI_JOUR) {
digitalWrite(PIN_LED, LOW);
etat = JOUR;
}
if (luminosite < SEUIL_JOUR) etat = NUIT;
break;
}
}
Version améliorée de la seconde vidéo :
#include <Arduino.h>
#define PIN_LED 3
void setup() {
pinMode(PIN_LED, OUTPUT);
Serial.begin(9600);
delay(100);
}
void loop() {
const uint16_t SEUIL_NUIT = 400;
const uint16_t SEUIL_JOUR = 800;
const uint32_t DELAI_JOUR = 5000; // Durée minimale en millisecondes de détection du jour avant extinction
const uint32_t DELAI_NUIT = 1000; // Durée minimale de détection de la nuit avant allumage
static uint32_t heureJour = 0; // Heure de la dernière détection de jour
static uint32_t heureNuit = 0; // Heure de la détection du début de la nuit
enum tEtats {JOUR, DEBUT_NUIT, NUIT, DEBUT_JOUR};
static tEtats etat = JOUR;
static tEtats etatSuivant = JOUR;
// Prise en compte de l'état suivant
etat = etatSuivant;
// Lecture des entrées
uint16_t luminosite = analogRead(A0);
uint32_t _millis = millis();
// Sorties
switch(etat) {
case JOUR:
digitalWrite(PIN_LED, LOW);
break;
case DEBUT_NUIT:
digitalWrite(PIN_LED, LOW);
break;
case NUIT:
digitalWrite(PIN_LED, HIGH);
break;
case DEBUT_JOUR:
digitalWrite(PIN_LED, HIGH);
break;
}
// Calcul de l'état suivant de la machine à états
switch(etat) {
case JOUR:
if (luminosite <= SEUIL_NUIT) {
etatSuivant = DEBUT_NUIT;
heureNuit = _millis;
}
break;
case DEBUT_NUIT:
if (luminosite > SEUIL_NUIT) etatSuivant = JOUR;
else
if ((_millis - heureNuit) >= DELAI_NUIT) {
etatSuivant = NUIT;
}
break;
case NUIT:
if (luminosite >= SEUIL_JOUR) {
etatSuivant = DEBUT_JOUR;
heureJour = _millis;
}
break;
case DEBUT_JOUR:
if (luminosite < SEUIL_JOUR) etatSuivant = NUIT;
if ((_millis - heureJour) >= DELAI_JOUR) {
etatSuivant = JOUR;
}
break;
default: etatSuivant = JOUR;
}
}
Vous pouvez contribuer par vos dons (1€, 5€, 10€ ou au choix) au développement de nouveaux tutoriaux nécéssitant d'investir dans du matériel spécifique, de financer la fabrication de circuits imprimés permettant d'illustrer certains aspects théoriques et pratiques...
Vous avez un compte PayPal ? Faites votre don en évitant les frais PayPal :
Vous n'avez pas de compte PayPal, cliquez sur le bouton ci-dessous :