Créer une application Yoctopuce en Delphi

Créer une application Yoctopuce en Delphi

Cette semaine, on se propose de vous montrer en détail comment coder, en Delphi, une application Windows qui utilise des modules Yoctopuce. Pour rendre le sujet un peu intéressant, on va appliquer ça à de la régulation de température en utilisant juste un corps chauffant et un relais, exemple pratique à l'appui.

On suppose que vous avez déjà quelques connaissances de base en Delphi et que vous avez déjà lu notre article sur la structure logique des modules Yoctopuce.

Mise en place

La première chose à faire consiste télécharger l'API Yoctopuce pour Delphi. Décompressez le fichier où bon vous semble, le répertoire indispensable est "sources". Pour que le compilateur sache où trouver l'API, lancez Delphi, et dans le menu "tools/options/delphi/options/library" ajoutez le chemin vers le répertoire "Sources" de l'API. La capture d'écran ci-dessous montre l'interface de Delphi 10.2, mais le principe reste le même pour toutes les versions de Delphi.

Configuration de Delphi 10.2
Configuration de Delphi 10.2


Notez que si vous ne voulez pas modifier la configuration de votre environnement Delphi, vous pouvez aussi directement inclure les fichiers de l'API dans votre projet. Dans notre cas, vous aurez besoin de yjson.pas, yocto_api.pas, yocto_relay.pas et yocto_temperature.pas.

Enfin, remarquez que le répertoire sources contient un sous-répertoire "dll", qui contient lui-même le fichier yapi.dll, vous en aurez besoin plus tard.

Le projet

L'idée est de montrer comment réguler précisément la température d'un système à l'aide d'un corps chauffant piloté par un simple relais, c'est-à-dire en tout-ou-rien. Pour illustrer la chose, on a fabriqué un petit banc d'essai qui se présente sous la forme d'une petite boîte métallique contenant deux ampoules halogène 12V 50W. Les ampoules sont pilotées par un Yocto-LatchedRelay et sont alimentées par une grosse alim 12V: les ampoules consomment à elles seules 8A. La température de l'air dans la boîte est mesurée par un Yocto-Thermocouple.



Notre petit banc d'essai
   


Pour rendre la programmation plus facile, on a donné un nom logique aux fonctions relay et temperature1 des deux modules. A l'aide du VirtualHub, on a appelé le relais "Box-Lamp" et la sonde de température "Box-Temp".

On donne un nom logique aux fonctions qu'on compte utiliser
On donne un nom logique aux fonctions qu'on compte utiliser



Un simple test démontre que ce banc d'essai est particulièrement efficace. En moins de 75 secondes, la température dans la boîte passe de 30 à 120°C et la courbe laisse penser qu'on pourrait monter bien plus haut. En revanche, le refroidissement de la boîte étant purement passif, la température met bien plus de temps à redescendre.

Performances "naturelles" de la boîte.
Performances "naturelles" de la boîte.


La question est donc de savoir si on arrive à maintenir une température arbitraire constante dans cette boîte. Voyons comment coder une application Delphi qui fait ça.

L'application

Initialisation de l'application

On commence par créer une nouvelle application VCL forms, a.k.a. une application Windows tout ce qu'il y a de plus standard. La première chose à faire consiste à initialiser l'API Yoctopuce le plus tôt possible dans la vie de l'application. Pour cela, dans le "Project manager" de Delphi, faites un "clic droit/View Source" (ou Ctlr-V) directement sur le nœud principal de votre projet. Vous allez obtenir le code source du point d'entrée de l'application. Il suffit alors de l'éditer pour rajouter un appel à YRegisterHub.

program Project1;
uses
  Vcl.Forms,dialogs,yocto_api,
  Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
var
  errmsg:string;
begin
  if (YRegisterHub('127.0.0.1',errmsg)<>YAPI_SUCCESS) then
     messagedlg(errmsg,mtwarning,[mbok],0);
   else
   begin
    Application.Initialize;
    Application.MainFormOnTaskbar := True;
    Application.CreateForm(TForm1, Form1);
    Application.Run;
  end;
end.


Notez qu'on a mis "127.0.0.1" pour le paramètre de YRegisterHub. En effet, on compte faire marcher l'application a travers le VirtualHub, ce qui nous permettra de faire tourner même temps l'application Yocto-Visualization et ainsi d'obtenir facilement des courbes de température.

Avant de lancer l'application pour la première fois, pensez à copier le fichier yapi.dll dans le même répertoire que l'exécutable, sinon ça ne marchera pas du tout.

Creation du form

Dans le form de l'application, on place:

  • Un champ TEdit en "read only" pour afficher la température courante
  • Un deuxième champ TEdit en "read only" pour afficher la température de consigne
  • Deux boutons TButton "+" et "-" pour ajuster la température de consigne
  • Deux boutons TButton "start" et "stop" pour lancer et arrêter l'expérience
  • Un TTimer, réglé à 1000ms

Création de l'interface
Création de l'interface


Initialisation du form

A l'initialisation du form, on récupère les objets correspondant à notre relais et notre sonde de température à l'aide des fonctions YFindTemperature et YFindrelay. On vérifie qu'ils sont en état de marche grâce à isOnline() et on les stocke dans des variables du form: Sensor et Relay.

procedure TForm1.FormCreate(Sender: TObject);
begin
   Sensor := YFindTemperature('Box-Temp');
   Relay := YFindrelay('Box-Lamp');
   if not(Sensor.isOnline()) then
     begin
       MessageDlg('No temperature sensor named Box-Temp',mtwarning,[mbok],0);
       RunButton.Enabled:=false;
     end;
   if not(Relay.isOnline()) then
     begin
       MessageDlg('No relay named Box-Lamp',mtwarning,[mbok],0);
       RunButton.Enabled:=false;
     end;
end;



Code l'interface

Le code de gestion l'interface n'a pas beaucoup d'intérêt, il consiste simplement à pouvoir changer la valeur de consigne et lancer/arrêter l'expérience.

procedure TForm1.IncButtonClick(Sender: TObject);
begin
   TargetValue.Text := inttoStr(strtoint(TargetValue.Text)+1);
end;

procedure TForm1.DecButtonClick(Sender: TObject);
begin
   TargetValue.Text := inttoStr(strtoint(TargetValue.Text)-1);
end;

procedure TForm1.RunButtonClick(Sender: TObject);
begin
  RunButton.Enabled :=false;
  StopButton.Enabled:=true;
  running:=true;
end;

procedure TForm1.StopButtonClick(Sender: TObject);
begin
  RunButton.Enabled:=true;
  StopButton.Enabled:=false;
  running:=false;
end;



La boucle de contrôle

On aborde maintenant la partie vraiment intéressante du problème: comment maintenir une température constante dans la boîte? On a un TTimer qui appelle un callback une fois par seconde. C'est dans ce callback qu'on va placer notre code de contrôle, mais il y a plusieurs manières de s'y prendre.

Bête et méchant

La méthode la plus simple consiste à allumer les lampes quand la température est trop basse et l'éteindre quand elle est trop haute.

// Basic ON/OFF regulation
procedure TForm1.Timer1Timer(Sender: TObject);
var
 temp : double ;
 target : double ;
 delay:integer;
begin
   if Sensor.isOnline() then
     begin
        target := strtofloat(TargetValue.Text)  ;
        temp :=  Sensor.get_currentValue();
        CurrentValue.Text :=  format('%.2f', [temp]  )+   Sensor.get_unit();
        if (running and relay.isOnline()) then
         begin
          if temp<target  then  relay.pulse(timer1.Interval+100)
                          else  relay.set_state(0);
         end;
     end;
end;


Notez qu'à la place de simplement allumer le relais avec set_state(1), on a utilisé la méthode "Pulse()", ainsi le relais reviendra automatiquement à sa position de repos une fois le délai donné en paramètre écoulé, et ce même si l'application de contrôle s'arrête. Cela afin d'éviter que la boîte ne parte en surchauffe incontrôlée. Le résultat de cet algorithme est correct, sans plus: on voit pas mal d'oscillations sur la courbe de température.

Bête contrôle allumé/éteind, ça oscille pas mal
Bête contrôle allumé/éteind, ça oscille pas mal


On pourrait probablement diminuer l'amplitude de ces oscillations en diminuant la période du timer, mais faire basculer un relais électro-mécanique plus d'une fois par seconde n'est vraiment pas très raisonnable.

Utiliser un PID

En fait, il existe une méthode beaucoup plus efficace pour contrôler des systèmes qui ont de l'inertie: le PID. C'est un algorithme qu'on a déjà utilisé dans un article précédent. En gros, cela consiste à calculer l'erreur entre la valeur de température actuelle et la valeur désirée. On calcule la commande du relais sous la forme d'une combinaison de trois facteurs, un premier Proportionnel à l'erreur, un second proportionnel à l'Intégrale de l'erreur et un troisième, proportionnel à la Dérivée de l'erreur. Chacun de ces facteurs est pondéré par une constante, respectivement A,B et C. La difficulté consiste à trouver les valeurs correcte pour ces constantes. Google vous aidera à trouver tout plein de théories à ce sujet. Nous, on s'est juste contenté de les ajuster jusqu'à obtenir quelque chose de raisonnable. Voici le code du PID en question.

var
 PID_Data : array[0..10] of double;
 PID_Ptr :  integer;
function   PID(target,current:double):double;
var err,P,I,D : double;
    n:integer;
const
    PID_A = 150.0;
    PID_B = 100.0;
    PID_C = -50.0;
 begin
  err :=   target-current;
  if PID_Ptr<length(PID_Data)-1 then
     begin
         PID_Data[PID_Ptr]:=err;
         inc(PID_Ptr);
     end
     else
     begin
       move(PID_Data[1],PID_Data[0],(length(PID_Data)-1)*sizeof(double));
       PID_Data[length(PID_Data)-1] := err;
     end;
   if PID_Ptr>2 then
   begin
     P :=   PID_Data[PID_Ptr-1];
     I:=0;
     for n:=0 to  PID_Ptr-1 do    i:=i+  PID_Data[n];
     I := I / PID_Ptr;
     D :=   ( PID_Data[PID_Ptr-1] -    PID_Data[0] ) /  PID_Ptr;
     PID := PID_A * P +    PID_B * I + PID_C * D;
   end else PID:=0;
 end;


Ce PID renvoie directement valeur que l'on doit utiliser pour piloter le relais. On ne peut évidement pas fermer le relais "à moitié", par contre on peut jouer sur le temps pendant lequel il va rester fermé, c'est à dire le paramètre de la fonction pulse().

// PID, no slew control
procedure TForm1.Timer1Timer(Sender: TObject);
var
 temp : double ;
 target : double ;
 delay:integer;
begin
   if Sensor.isOnline() then
     begin
        target := strtofloat(TargetValue.Text)  ;
        temp :=  Sensor.get_currentValue();
        CurrentValue.Text :=  format('%.2f', [temp]  )+   Sensor.get_unit();
        if (running and relay.isOnline()) then
         begin
           delay:=  round(PID(target,temp));
           if (delay<0) then delay:=0;
           if (delay>timer1.Interval+100)  then delay:=timer1.Interval+100;
           Relay.pulse(delay );
         end;
     end;
end;


On obtient alors une bien meilleure régulation. Parce contre, on a un overshoot assez flagrant au départ. Cela vient du fait qu'à pleine puissance, le système est capable de faire monter la température plus vite qu'il ne peut la contrôler, une embardée thermique en quelque sorte.

Le PID donne un meilleur résultat, mais il y a un overshoot au début
Le PID donne un meilleur résultat, mais il y a un overshoot au début


La solution consiste à empêcher la consigne de varier trop vite. Plutôt que d'utiliser directement la consigne pour nourrir le PID, on utilise une variable "virtualTarget" qui converge tranquillement vers la consigne à la vitesse maximale de 1°C par seconde.

// PID, with slew control
procedure TForm1.Timer1Timer(Sender: TObject);
var
 temp : double ;
 target : double ;
 delay:integer;
begin
   if Sensor.isOnline() then
     begin
        target := strtofloat(TargetValue.Text);
        if abs(target-virtualTarget)>1 then
          begin
            if virtualTarget< target
               then  virtualTarget:=virtualTarget+1
               else  virtualTarget:=virtualTarget-1;
          end else   virtualTarget:=target;
        temp :=  Sensor.get_currentValue();
        CurrentValue.Text :=  format('%.2f', [temp]  )+   Sensor.get_unit();
        if (running and relay.isOnline()) then
          begin
            delay := round( PID(virtualTarget,temp) );
            if (delay<0) then delay:=0;
            if (delay>timer1.Interval+100)  then delay:=timer1.Interval+100;
            Relay.pulse(delay );
           end;
     end;
end;


On obtient alors un résultat tout à fait acceptable avec une température tenue à quelques dixièmes de degré près. Ce qui est quand même remarquable quand on pense qu'on a affaire à un système contrôlé en tout-ou-rien avec un bête relais électro-magnétique.

Le contrôle de la montée en température évite l'overshoot
Le contrôle de la montée en température évite l'overshoot


Si vous avez envie d'essayer par vous-même, vous trouverez le code source complet de l'application dans ce fichier zip.

Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.