Les entrées analogiques

Introduction aux entrées analogiques

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.

Mesure de température avec une thermistance CTN

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



Interrupteur crépusculaire avec une photorésistance

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.

Interrupteur crépusculaire géré par une machine à états

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;
  }
}