Pub

lundi 1 mars 2021

myTinyPLC : un simulateur d'automate programmable avec Arduino

 Je suis heureux de vous présenter la version 1 de "myTinyPLC" mon petit simulateur d'automate programmable, conçu avec un Arduino Uno R3 et doté de son propre langage de programmation IL (instruction list) lui permettant d'effectuer des opérations de lecture, écriture, opérations logiques booléennes sur des entrées, des sorties et des mémoires.
 Cette première version dispose quatre entrées : (quatre boutons poussoirs) et quatre sorties (quatre LEDs).

 Pour le programmer, on utilise des codes d'instruction que l'on rentre sous forme 
de nombres à deux chiffres dans un tableau.

 Dans cette première version, il n'y a pas encore d'éditeur pour rentrer les codes instructions, il faut donc les rentrer dans un tableau dans le setup() en assignant une valeur aux différents pas de programme stp[1], stp[2], etc...

 Pour l'instant, on ne traite que des entrées, des sorties et mémoires binaires (0 ou 1). Une pile (stack) à 10 niveaux maximum et quatre mémoires sont là pour stocker les résultats intermédiaires. Si vous avez déjà utilisé une calculette RPN ou le langage Forth, vous serez familier de la logique de programmation de myTinyPLC. Sinon, ce n'est toutefois pas très compliqué et c'est plutôt pratique et efficace à l'usage.



Voici les différentes instructions du langage IL de myTinyPLC en version 1 :


Avec ce petit nombre d'instructions, on fait déjà beaucoup de choses !

Exemples de programmation :

Exemple 1:
On souhaite simplement allumer la LED 1 lorsqu'on appuie sur le bouton 1. Lorsqu'on arrête d'appuyer sur le bouton, la LED s'éteint.

Schéma électrique :

|     i1            o1       |   
+----] [-----------( )-------+ 
|                            |

Equation logique :

   i1 = o1

Naturellement, pas besoin d'un Arduino pour faire ça ! Mais c'est un exemple pour comprendre la logique de programmation.
On rentrera donc le programme sous cette forme, près les "//" sont des commentaires pour expliquer ce qu'on fait.

Le programme IL :

stp[1] = 11;
// Première instruction du programme : Read input 1 (signifie : lire l'entrée 1)
stp[2] = 41; // 2ème instruction : Write output 1 (écrire la sortie 1)
stp[3] = 99; // 3ème instruction : END (fin du programme et reboucler à la première instruction)

Exemple 2 :
On souhaite allumer la LED 1 si le bouton 1 et le bouton 2 sont appuyés. Si l'un des deux bouton n'est pas appuyé, la LED ne s'allumera pas. C'est l'opération logique ET.

Schéma électrique :

|     i1        i2                o1        | 
+----] [-------] [---------------( )--------+ 
|                                           |


Equation logique :

    
i1 . i2 = o1


Le programme IL :

stp[1] = 11; // 1ère instruction : Read input 1 (lire l'entrée 1)
stp[2] = 12; // 2ème instruction : Read input 2 (
lire l'entrée 2)
stp[3] = 81; // 3ème instruction : AND (exécuter l'opération logique ET)
stp[4] = 41; // 4ème instruction : Write output 1
(écrire la sortie 1)
stp[5] = 99; // 5
ème instruction : END (fin du programme, reboucler à la première instruction)


Exemple 3 :
On souhaite que la LED 1 soit allumée seulement si le bouton i1 n'est pas appuyé. Lorsque le bouton i1 est appuyé, la LED s'éteint.

Schéma électrique :
       __    
|      i1                      o1        |
+-----]/[---------------------( )--------+ 
|                                        |


Equation logique :
  __
  i1 = o1


Le programme IL :

stp[1] = 11; // 1ère instruction : Read input 1 (lire l'entrée 1)
stp[2] = 83; // 2ème instruction : NOT 
(exécuter l'opération NOT)
stp[3] = 41; // 3ème instruction : Write output 1 (écrire la sortie 1)
stp[4] = 99; // END 
(fin du programme, reboucler à la première instruction)

Exemple 4 :
On souhaite que la LED 1 soit allumée si un des deux bouton i1 ou i2 est appuyé, ou les deux.

Schéma électrique :
          
|        i1                      o1       |
+---+---] [----+----------------( )-------+ 
|   |          |                          |
|   |    i2    |                          |
|   +---] [----+                          |
|                                         |  


Equation logique :
  
  i1 + i2 = o1


Le programme IL :

stp[1] = 11; // 1ère instruction : Read input 1 (lire l'entrée 1)
stp[2] = 12; // 2ème instruction : Read input 2 (
lire l'entrée 2)
stp[3] = 82; // 3ème instruction : OR (exécuter l'opération logique OU)
stp[4] = 41; // 4ème instruction : Write output 1 
(écrire la sortie 1)
stp[5] = 99; // 5
ème instruction : END (fin du programme, reboucler à la première instruction)

Exemple 5 :
On souhaite que la LED 1 soit allumée et le reste lorsque l'on appuiera puis relâchera le bouton poussoir "Marche" i1. La LED 1 s'éteindra lorsqu'on appuiera le bouton poussoir "Arrêt" i2. On passe maintenant de la 
logique combinatoire à la logique séquentielle car ici l'état de sortie ne dépend plus seulement de l'état des entrées mais aussi d'un état de sortie.

Schéma électrique :
                          __
|        i1               i2        o1     |  bouton marche : i1
+---+---] [----+---------]/[-------( )-----+                  __
|   |          |                           |  bouton 
arrêt  : i2
|   |    o1    |                           |
|   +---] [----+                           |
|                                          |  


Equation logique :
             __
  (i1 + o1). i2 = o1


Le programme IL :

stp[1] = 11; // 1ère instruction : Read input 1 (lire l'entrée 1)
stp[2] = 31; // 2ème instruction : Read output 1 (
lire la sortie 1)
stp[3] = 82; // 3ème instruction : OR (exécuter l'opération logique OU)
stp[4] = 12; // 4ème instruction : Read input 2 
(lire l'entrée 2)
stp[5] = 83; // 5
ème instruction : NOT (inverser la valeur courante)
stp[6] = 81; // 6
ème instruction : AND (exécuter l'opération logique ET)
stp[7] = 41; // 7ème instruction : Write output 1 
(écrire la sortie 1)
stp[8] = 99; // 8ème instruction : END (fin du programme, reboucler à la première instruction)

Je donnerai d'autres exemples de programmes IL pour myTinyPLC dans un autre article.

*****

Voici le programme Arduino pour faire tourner myTinyPLC (version 1.1.1 "Brave Benoît c.1") :

/* Program name: mytinyPLC-1-1-1.ino
   Author: Guy Vanoverbeke @GuyVano
   Program last update (dd/mm/yyyy) : 01/03/2021 - V.1 R.1 C.1 - Release "Brave Benoît c.1"
   Arduino IDE V1.8.13
   Board: Arduino UNO R3
   Function: PLC simulator / simulateur d'automate programmable
   -----------
   Disclaimer:
   This program (in other words: this code, this software or this application) is a personal creation made as part of a hobby
   and it is given without guarantee of any kind and no support is provided. It is free of rights
   and can be reused freely as you wish.
*/
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
//
#include <Servo.h>
Servo myservo;
//
boolean i1 = 0; // used to store input 1 value
boolean i2 = 0; // used to store input 2 value
boolean i3 = 0; // used to store input 3 value
boolean i4 = 0; // used to store input 4 value
//
boolean m1 = 0; // used to store memory 1 value
boolean m2 = 0; // used to store memory 2 value
boolean m3 = 0; // used to store memory 3 value
boolean m4 = 0; // used to store memory 4 value
boolean mx = 0; // temporary memory used for Swap operation
//
boolean o1 = 0; // used to store output 1 value
boolean o2 = 0; // used to store output 2 value
boolean o3 = 0; // used to store output 3 value
boolean o4 = 0; // used to store output 4 value
boolean stack[10] = {0,0,0,0,0,0,0,0,0,0}; // Stack array
boolean slow = false; // true for slow running debug
//
int ai1 = 0; // analog input 1 value
int ao1 = 0; // analog output 1 value
int vsv = 0; // value servo to write in the analog output
int j = 0; // loop control
//
const int nbstp = 99; //number of maximum IL program steps
int stp[(nbstp+1)] = {}; // IL program steps array dim = nbstp + 1
int stc = 0; // step counter
//

void setup()
{
  //
  pinMode(A1, INPUT); // buton 1 as input 1
  pinMode(A2, INPUT); // buton 2 as input 2
  pinMode(A3, INPUT); // buton 3 as input 3
  pinMode(8, INPUT);  // buton 4 as input 4
  //
  myservo.attach(9); // servo connected to pin 9 (for future release)
  //
  pinMode(10, OUTPUT); // LED 1 (red) as output 1
  pinMode(11, OUTPUT); // LED 2 (yellow) as ouput 2
  pinMode(12, OUTPUT); // LED 3 (green) as output 3
  pinMode(13, OUTPUT); // LED 4 (blue) as output 4
  //
  lcd.begin(16, 2);
  lcd.print("mytinyPLC v1-1-1");
  lcd.setCursor(0, 1);
  lcd.print("project @guyvano");
  delay(2000);
  lcd.clear();

//
// v---- your IL program steps here / Votre programme IL ici
//
stp[1] = 11; // instruction 1: Read input 1 (lire l'entrée 1)
stp[2] = 31; // instruction 2: Read output 1 (lire la sortie 1)
stp[3] = 82; // instruction 3: OR (exécuter l'opération logique OU)
stp[4] = 12; // instruction 4: Read input 2 (lire l'entrée 2)
stp[5] = 83; // instruction 5: NOT (inverser la valeur courante)
stp[6] = 81; // instruction 6: AND (exécuter l'opération logique ET)
stp[7] = 41; // instruction 7: Write output 1 (écrire la sortie 1)
stp[8] = 99; // instruction 8: END (fin du programme, reboucler à l'instruction 1)
//
// ^---- end of your IL program / fin de votre programme
//
// it is a good idea to comment each line with the mnemonic of 
// the instruction, for a better IL program reading and understanding
//
}

void loop()
{
  //
  // update IL program instruction counter
  // (one IL step per loop)
  //
  stc = (stc % nbstp) + 1;
  //
  // digital input acquisition
  // at least for displaying them later
  // even if they are note read by the IL program
  //
  i1 = digitalRead(A1);
  i2 = digitalRead(A2);
  i3 = digitalRead(A3);
  i4 = digitalRead(8);
  //
  // analog input acquisition
  //
  ai1 = analogRead(A0);
  //
  // servo test    ----v
  // not managed in this version
  // only copy potentiometer value in servo
  //
  ao1 = ai1;
  vsv = map(ao1, 0, 1023, 0, 180);
  myservo.write(vsv);
  // end servo test ---^
  //
  // Start IL instruction scrutation (one IL step per loop)
  //
  //
  // check if operation code is Read an input
  // 
  switch (stp[stc]) {
    break;
    //
    case 11:
    // read digital input 1
    i1 = digitalRead(A1);
    dwnstack();
    stack[0] = i1;
    break;
    //
    case 12:
    // read digital input 2
    i2 = digitalRead(A2);
    dwnstack();
    stack[0] = i2;
    break;
    //
    case 13:
    // read digital input 3
    i3 = digitalRead(A3);
    dwnstack();
    stack[0] = i3;
    break;
    //
    case 14:
    // read digital input 4
    i4 = digitalRead(8);
    dwnstack();
    stack[0] = i4;
    break;
    //
    default:
    // no instruction match
    break;
  }
  //
  // check if instruction code is Read a memory
  // 
  switch (stp[stc]) {
    //
    case 21:
    // read digital memory 1
    dwnstack();
    stack[0] = m1;
    break;
    //
    case 22:
    // read digital memory 2
    dwnstack();
    stack[0] = m2;
    break;
    //
    case 23:
    // read digital memory 3
    dwnstack();
    stack[0] = m3;
    break;
    //
    case 24:
    // read digital memory 4
    dwnstack();
    stack[0] = m4;
    break;
    //
    default:
    // no operation match
    break;
  }
  //
  // check if instruction is Read an output
  // 
  switch (stp[stc]) {
    //
    case 31:
    // read digital output 1
    o1 = digitalRead(10);
    dwnstack();
    stack[0] = o1;
    break;
    //
    case 32:
    // read digital output 2
    o2 = digitalRead(11);
    dwnstack();
    stack[0] = o2;
    break;
    //
    case 33:
    // read digital output 3
    o3 = digitalRead(12);
    dwnstack();
    stack[0] = o3;
    break;
    //
    case 34:
    // read digital output 4
    o4 = digitalRead(13);
    dwnstack();
    stack[0] = o4;
    break;
    //
    default:
    // no instruction match
    break;
  }
  //
  // check if instruction is Write an output
  // 
  switch (stp[stc]) {
    //
    case 41:
    // write digital output 1
    o1 = stack[0];
    digitalWrite(10, o1);
    break;
    //
    case 42:
    // write digital output 2
    o2 = stack[0];
    digitalWrite(11, o2);
    break;
    //
    case 43:
    // write digital output 3
    o3 = stack[0];
    digitalWrite(12, o3);
    break;
    //
    case 44:
    // write digital output 4
    o4 = stack[0];
    digitalWrite(13, o4);
    break;
    //
    default:
    // no instruction match
    break;
  }
  //
  // check if instruction is Write a memory
  // 
  switch (stp[stc]) {
    //
    case 51:
    // write digital memory 1
    m1 = stack[0];
    break;
    //
    case 52:
    // write digital memory 2
    m2 = stack[0];
    break;
    //
    case 53:
    // write digital memory 3
    m3 = stack[0];
    break;
    //
    case 54:
    // write digital memory 4
    m4 = stack[0];
    break;
    //
    default:
    // no instruction match
    break;
  }
  //
  // check if instruction is a boolean operation
  // 
  switch (stp[stc]) {
    //
    case 81:
    // AND
stack[1] = stack[0] & stack[1];
    upstack();     
    break;
    //
    case 82:
    // OR
    stack[1] = stack[0] | stack[1];
    upstack(); 
    break;
    //
    case 83:
    // NOT
    if (stack[0] == 0) {
      stack[0] = 1;
    }
    else {
      stack[0] = 0;
    }
    break;
    //
    case 84:
    // XOR
    stack[1] = stack[0] ^ stack[1];
    upstack();
    break;
    //
    default:
    // no instruction match
    break;
  }
  //
  // check if instruction is a stack operation
  // 
  switch (stp[stc]) {
    //
    case 91:
    // DUP
    dwnstack();
    stack[0] = stack[1];     
    break;
    //
    case 92:
    // SWAP
    mx = stack[0];
    stack[0] = stack[1];
    stack[1] = mx;
    break;
    //
    case 93:
    // DROP
    upstack();
    break;
    //
    default:
    // no instruction match
    break;
  }    
  //
  // scrutation end of IL program array
  // ----------------------------------
  //
  // display inputs on LCD line 1
  //
  lcd.setCursor(0, 0);
  lcd.print(i1);
  lcd.print(i2);
  lcd.print(i3);
  lcd.print(i4);
  lcd.print('|');
  //
  if (ai1 < 1000) {
    lcd.print(' ');
  }
  if (ai1 < 100) {
    lcd.print(' ');
  }
  if (ai1 < 10) {
    lcd.print(' ');
  }
  lcd.print(ai1);
    lcd.print('|');
  //
  if (stc < 100) {
    lcd.print(' ');
  }
  if (stc < 10) {
    lcd.print(' ');
  }
  lcd.print(stc);
  lcd.print(':');
  lcd.print(stp[stc]);
  //
  // display outputs on LCD line 2
  //
  lcd.setCursor(0, 1);
  lcd.print(o1);
  lcd.print(o2);
  lcd.print(o3);
  lcd.print(o4);
  lcd.print('|');
  if (ao1 < 1000) {
    lcd.print(' ');
  }
  if (ao1 < 100) {
    lcd.print(' ');
  }
  if (ao1 < 10) {
    lcd.print(' ');
  }
  lcd.print(ao1);
  //
  //
  //  
  if (slow) {
    delay(2000);
  }
  //
  // Check if End of program
  //
  if (stp[stc] == 99) {
    stc = 0;
  } 
}

void dwnstack()
{
  //
  // shift the stack elements down
  // 
  stack[9]=stack[8]; // note : previous stack[9] is lost
  stack[8]=stack[7];
  stack[7]=stack[6];
  stack[6]=stack[5];
  stack[5]=stack[4];
  stack[4]=stack[3];
  stack[3]=stack[2];
  stack[2]=stack[1];
  stack[1]=stack[0];
  stack[0]=0;
}

void upstack()
{
  //
  // shift the stack elements up
  //
  stack[0]=stack[1];
  stack[1]=stack[2];
  stack[2]=stack[3];
  stack[3]=stack[4];
  stack[4]=stack[5];
  stack[5]=stack[6];
  stack[6]=stack[7];
  stack[7]=stack[8];
  stack[8]=stack[9];
  stack[9]=0; 
}
// End of myTinyPLC (version 1.1.1 "Brave Benoît") - Thanks for watching !


Quelques améliorations prévues qu'il serait bien que j'apporte dans les prochaines versions :

 - gérer des entrées/sorties analogiques (au moins une, dans un premier temps) et rajouter des instructions, une pile et des mémoires pour faire des opérations sur des nombres.

 - gérer des changements d'état temporisés, introduire des possibilités de délais à la fermeture ou à l'ouverture sur une sortie.

 - ajouter un module I2C pour y connecter l'afficheur LCD afin de libérer six ports entrées/sorties pour l'automate.

- ajouter un ou deux servo-moteurs.

 - programmer un éditeur d'instructions pour ne plus devoir rentrer "en dur" les codes instruction dans le tableau. Ainsi, la programmation de myTinyPLC pourra devenir totalement indépendante de l'IDE Arduino.

Comment :

 - définir un mode "Programmation" et utiliser astucieusement les quatre boutons poussoirs, le potentiomètre et l'afficheur pour naviguer dans un menu à choix multiples conduisant à pas à pas à l'élaboration du programme IL.

- ou bien rajouter un clavier alphanumérique (et un module I2C) pour rentrer les instructions avec ce clavier connecté à l'Arduino.

- ou bien se servir de la télécommande infrarouge fournie dans de nombreux kits de démarrage. 

à suivre...

Commentaires et suggestions sont les bienvenus !







*** Guy F8ABX - 01/03/2021 ***



Aucun commentaire:

Enregistrer un commentaire