Pub

samedi 27 mars 2021

Baromètre connecté : schéma et code

La toute première version du 22 mars dernier ne gérait pas correctement les problèmes de communication, mais après une modification du code le lendemain, le baromètre fonctionne depuis en permanence et il transmet correctement ses mesures à Weather Underground. La mesure de la pression atmosphérique ramenée au niveau de la mer (QNH) est transmise toutes les 5 minutes au site. En cas de problème de connexion, un nouvel essai est fait 2 minutes plus tard. Au delà de 5 échecs de connexion, le micro contrôleur redémarrera.

Les mesures sont consultables ici https://bit.ly/barogrelonges 

Faites défiler la page car le graphique est situé presque tout en bas. Par défaut, le site affiche en la pression en pouces de mercure mais l'on peut l'avoir en hPa (bouton réglages en haut à droite, puis °C pour avoir la pression en hecto-Pascal...).

Pour publier ses mesures sur Weather Underground, il faut d'abord s'être créé un compte et au moins un "device".
L'identifiant du device et son mot de passe associé sont indispensables pour la publication des mesures.
En ce qui concerne la connexion à Internet, il faudra rentrer le SSID et le mot de passe de votre connexion Wifi dans le programme.
Il faut de même renseigner correctement l'altitude du capteur (en mètres).
Donc en tout, il y a 5 constantes à personnaliser dans le programme.

Depuis le 23 mars 2021, cette version me donne satisfaction.


Le schéma de câblage des composants :





Le code de la v.2.0.0 du 23 mars 2021 :

/* Program name: esp32-baro-wu-2-0-0.ino
   Author: Guy Vanoverbeke @GuyVano
   Program last update (dd/mm/yyyy) : 23/03/2021 - V.2 R.0 C.0
   Arduino IDE V1.8.13
   Board: ESP-WROOM-32
   Function: Publish the barometric pressure from a BME280 sensor on the Weather Underground web site.
   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 <Wire.h>
#include <WiFi.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
Adafruit_BME280 bme; // I2C
#include <HTTPClient.h>
//
// Wifi SSID and password
//
const char* ssid       = "my Wifi SSID";
const char* password   = "my Wifi password";

//
// Weather Underground device ID and password
//
const char* wudevid    = "my WU device ID";
const char* wudevpw    = "my WU device password";

//
const float myaltitude = 186; // my sensor altitude in meters
//
const boolean autcnx = true; // autorize the connection in http flag
const boolean spr = false;   // true for serial prints or false for no prints
const unsigned long ndly = 300000; // 300000 for 5 minutes. Normal delay between each data upload
const unsigned long edly = 120000; // 120000 for 2 minutes. Retry delay after error
//
float temp = 0;
float hr = 0;
float qfe = 0;
float myqnh = 0;
float myqnhinches = 0;
String wumsg; // http connection url + data to transmit
boolean error = false; // error flag
boolean rderr = false; // sensor read error flag
boolean cnxok = false; // http connection ok flag
int serr = 0; // sensor error counter
int herr = 0; // internet error counter
int hcode = 0; // returned http code
int i = 0; // loop control
String htxt; // returned http text
unsigned snsrsts; // sensor status
//
void setup() {
  //
  pinMode(19, OUTPUT); // red LED for error
  pinMode(23, OUTPUT); // green LED ok
  //
  do {
    digitalWrite(19, LOW);
    digitalWrite(23, HIGH);
    delay(500);
    digitalWrite(19, HIGH);
    digitalWrite(23, LOW);
    delay(500);
    i++;
  } while (i < 6);
  // start the serial connection
  if (spr) {
    Serial.begin(115200);
    // wait for serial monitor to open
    while (! Serial);
  }
  //
  // Connect to WiFi
  //
  if (spr) {
    Serial.println("Connecting to Wifi...");
  }
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    if (spr) {
      Serial.print(">");
    }
  }
  if (spr) {
    Serial.println();
    Serial.println(" Wifi connected.");
    //
    Serial.println();
    Serial.println("BME280 linking...");
  }
  // default settings
  snsrsts = bme.begin(0x76);
  if (!snsrsts) {
    rderr = true;
    error = true;
    if (spr) {
      Serial.println("BME280 sensor not found.");
    }
  }
}
void loop() {
  //
  HTTPClient http;
  rdSensor();
  if (spr) {
    prtValues();
  }
  bldwuMsg();
  if ((!rderr) & (autcnx)) {
    http.begin(wumsg);
    hcode = http.GET();
    htxt = http.getString();
  }
  if (spr) {
    Serial.println();
    Serial.print("Returned code: ");
    Serial.print(hcode);
    Serial.print(" / ");
    Serial.println(htxt);
  }
  if (hcode == 200) {
    // a return code 200 means success
    herr = 0;
    error = false;
    cnxok = true;
  } else {
    herr = herr + 1;
    cnxok = false;
    error = true;
    if (spr) {
      Serial.println();
      Serial.print("HTTP error #");
      Serial.print(herr);
      Serial.println(".");
    }
  }
  //
  //
  //
  if (error) {
    digitalWrite(19, HIGH);
  } else {
    digitalWrite(19, LOW);
  }
  if (cnxok) {
    digitalWrite(23, HIGH);
  } else {
    digitalWrite(23, LOW);
  }
  if (herr > 5) {
    // too much errors, restart the MCU
    ESP.restart();
  }
  //
  //
  //
  if (error) {
    delay((edly - 2000));
  } else {
    delay((ndly - 2000));
  }
  digitalWrite(19, LOW);
  digitalWrite(23, LOW);
  delay(2000);
  hcode = 0;
  htxt = ' ';
}
void prtValues() {
  //
  // Print Temperature, Humidity and barometric pressure
  //
  Serial.print("Temperature = ");
  Serial.print(temp);
  Serial.println(" °C");
  Serial.print("Humidity = ");
  Serial.print(hr);
  Serial.println(" % ");
  Serial.print("Pressure = ");
  Serial.print(myqnh);
  Serial.println(" hPa");
}
void rdSensor() {
  //
  temp = bme.readTemperature();
  hr = bme.readHumidity();
  qfe = bme.readPressure() / 100.0F;
  // myqnh = qfe + (myaltitude * 0.1205F); // previous used formulae
  myqnh = qfe / pow((1 - (myaltitude * (0.0065 / 288.15))), 5.255); // OACI standard atmosphear formulae
  myqnhinches = myqnh * 0.02953F;
  if ((myqnh < 860) | (myqnh > 1090)) {
    //
    // myqnh is out of range, probably error sensor or wiring
    //
    serr = serr + 1;
    rderr = true;
    error = true;
    if (spr) {
      Serial.println();
      Serial.print("Error reading sensor #");
      Serial.print(serr);
      Serial.println(".");
    }
  } else {
    serr = 0;
    rderr = false;
    error = false;
  }
  //
}
void bldwuMsg() {
  //
  // build the internet message for Weather Underground publishing
  //
  wumsg = "https://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?ID=";
  wumsg = wumsg + wudevid + "&PASSWORD=" + wudevpw + "&dateutc=now&baromin=";
  wumsg = wumsg + myqnhinches + "&action=updateraw";
  //
  if (spr) {
    Serial.println();
    Serial.println(wumsg);
  }
}
// End of program 
esp32-baro-wu-2-0-0.ino - Thanks for watching !


Cordiales 73 !


**** Guy F8ABX - 27/03/2021 ***



lundi 22 mars 2021

Baromètre connecté avec ESP32

 Je viens de mettre à niveau mon circuit baromètre avec une nouvelle version 2.0 connectée. Cette fois-ci, je n'utilise plus d'Arduino Uno mais un ESP32 car ce dernier a l'avantage d'intégrer nativement le Wifi.
Le logiciel est pour l'instant une version de développement un peu frustre mais une version un peu plus présentable devrait voir le jour et je la publierai dans un autre article prochainement.


Le circuit :


Les composants sont réduits au minimum vital : la carte micro contrôleur ESP-WROOM-32, le module de mesure BME280 qui communique avec le micro contrôleur en I2C, deux LED et deux résistances de 220 ohms. Et bien sûr la plaque de prototypage, quelques fils de câblage et une alimentation secteur 5V.

Comme le montage est autonome, c'est-à-dire qu'il fonctionne seul sans le recours d'un pc, j'ai donc souhaité avoir un affichage minimal pour m'assurer de son bon fonctionnement :
La LED verte indique que la dernière connexion au serveur Internet de Weather Underground a réussi. La LED rouge indique qu'il y a eu un problème de connexion Internet ou bien un problème de mesure.

Actuellement, le baromètre publie ses mesures de pression atmosphérique (QNH) toutes les 5 minutes sur Weather Underground et l'on peut les voir ici :

 https://www.wunderground.com/dashboard/pws/IFAREI3/ 

ou via le raccourci https://bit.ly/barogrelonges

Par défaut sur Weather Underground, les pressions barométriques sont indiquées en pouces de mercure, mais on peut les avoir en hecto-Pascal en cliquant tout en haut à droite sur l'engrenage puis °C afin de passer en système métrique.

A bientôt !


**** Guy F8ABX - 22, 25/03/2021 ****


mardi 16 mars 2021

myTinyPLC : nouvelle version 1.3

 Voici la nouvelle version 1.3 de myTinyPLC avec désormais la possibilité de rentrer les instructions de programmation à partir du moniteur série.
Cela permet de programmer ou de reprogrammer myTinyPLC sans devoir modifier le programme dans l'IDE Arduino ni le recompiler !

Il y a maintenant deux modes de fonctionnement : le mode RUN où le programme s'exécute normalement et le mode EDIT où l'exécution est arrêtée et où l'on a alors la possibilité de rentrer les instructions du programme IL via le moniteur série. Compte tenu de la mémoire réduite de l'Arduino Uno, le mode EDIT est assez rudimentaire, j'en conviens.

Le mode EDIT permet néanmoins les actions suivantes :
- Lister le programme avec les numéros des pas, les codes instructions et leurs mnémoniques respectives.
- Saisir le programme en rentrant pour chaque pas de programme, son code instruction.
- Insérer un pas de programme si besoin, et décaler les suivants.
- Si besoin, on peut désactiver un pas de programme en rentrant l'instruction 80 (NOP : no operation) car il n'y a pas de suppression de pas de programme.


Mode opératoire :

myTinyPLC démarre automatiquement en mode RUN. Si des instructions sont déjà pré renseignées dans le programme C++ dans le tableau d'instructions stp[n], myTinyPLC va les exécuter.

Pour modifier ou saisir de nouvelles instructions, on passe en mode EDIT en tapant 0 puis Entrée (zéro et on valide) 
avec le clavier du pc en utilisant le moniteur série de l'IDE. Puis on se laisse guider par l'affichage du moniteur série sur le pc.


Voici un petit schéma de principe de l'arborescence du mode EDIT :


[RUN mode] (RUN is the default mode at start-up)
 |
 |
 +---> 0 <Enter> --->[EDIT mode]
     (zero)            |
                       +-
--> 0 <Enter> -+-> 0 <Enter> Exit EDIT mode ---->[RUN mode]
                       |                |
                       |                +-> 1 <Enter> [List the instructions]
                       |                                     |
                       |                                     +--> 0 <Enter>
                       |                                     |        |     End list
                       |                                     |        +-->[EDIT mode]
                       |                                     |    
                       |                                     +--> 1 <Enter
                       |                                              |     Continue
                       |                                              +-->[List inst]
                       |
                       |
                       +---> n <Enter> --[edit step n]--> op <Enter> ---->[EDIT mode]
                             :                             : 
                         step number                  instruction code
                        n from 1 to 99                 op from 0 to 99
                                                   if 88 then insert a step


Le code de myTinyPLC en version 1.3.0 du 16/03/2021 :

/* Program name: mytinyPLC-1-3-0.ino
   Author: Guy Vanoverbeke @GuyVano
   Program last update (dd/mm/yyyy) : 16/03/2021 - V.1 R.3 C.0
   - Release name "Dare Dary"
   Arduino IDE V1.8.13
   Board: Arduino UNO R3
   Function: PLC simulator with its own IL language.
    Simulateur d'automate programmable doté de son propre langage IL.
   -----------
   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);
//
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 p[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // potentiometer indexed position 0 to 9
//
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 m5 = 0; // used to store memory 5 value
boolean m6 = 0; // memory 6 is tm6 starter
boolean m7 = 0; // memory 7 is tm7 starter
boolean m8 = 0; // memory 8 is tm8 starter
boolean m9 = 0; // memory 9 is tm9 starter
//
boolean pm6 = 0; // previous state of memory 6
boolean pm7 = 0; // previous state of memory 7
boolean pm8 = 0; // previous state of memory 8
boolean pm9 = 0; // previous state of memory 9
//
boolean tm6 = 0; // timer 6
boolean tm7 = 0; // timer 7
boolean tm8 = 0; // timer 8
boolean tm9 = 0; // timer 9
//
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[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // bit Stack array
boolean edtmode = false; // true when edit mode
//
unsigned int ai1 = 0; // analog input 1 value calculated
int j = 0; // loop control
int k = 0; // loop control and temporary variable
long r = 0; // temporary variable
long s = 0; // temporary variable
long t = 0; // temporary variable
int istack0 = 0; //
//
const int nbstp = 99; //number of maximum IL program steps starting from 1
int stp[(nbstp + 1)] = {}; // IL program steps array dim = nbstp + 1
int stc = 0; // step counter
//
long ctrtm6 = 0; // current millis counter when timer 6 rised to High
long ctrtm7 = 0; // current millis counter when timer 7 rised to High
long ctrtm8 = 0; // current millis counter when timer 8 rised to High
long ctrtm9 = 0; // current millis counter when timer 9 rised to High
//
long dtm6 = 8000; // set default delay value for timer 6 in ms
long dtm7 = 2000; // set default delay value for timer 7 in ms
long dtm8 = 6000; // set default delay value for timer 8 in ms
long dtm9 = 4000; // set default delay value for timer 9 in ms
//
String ope = "ab";
//

void setup()
{
  //
  Serial.begin(9600); // open the serial port at 9600 bps:
  //
  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
  //
  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
  //
  j = 0;
  do {
    j++;
    stp[j] = 80; // write NOP in all steps
  } while (j <= (nbstp));
  //
  //
  lcd.begin(16, 2);
  lcd.print("mytinyPLC v1-3-0");
  lcd.setCursor(0, 1);
  lcd.print("project @guyvano");
  delay(1000);
  lcd.clear();
  //
  // v---- your IL program steps here / Votre programme IL ici
  //
  //
  // Light on the LEDs following the potentiometer position
  // IL program for myTinyPLC v.1.2 and over.
  //
  stp[1] = 71; // Read p(1)
  stp[2] = 41; // Write output 1
  stp[3] = 72; // Read p(2)
  stp[4] = 42; // Write output 2
  stp[5] = 73; // Read p(3)
  stp[6] = 43; // Write output 3
  stp[7] = 74; // Read p(4)
  stp[8] = 44; // Write output 4
  stp[9] = 99; // END
  //
  //
  // ^---- 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
  //
  //
  Serial.println(' ');
  Serial.println("in Run mode, type 0 <Enter> to enter Edit mode");
  Serial.println(' ');
}

void loop()
{

  //
  // If 0 <Enter> typed from the serial monitor is zero, run the edit module
  //
  if (Serial.available() > 0) {
    r = Serial.read();
    if (r == '0') {
      edtmode = true;
      edit();
    }
  }
  //
  // update IL program instruction counter
  // (one IL step evaluated 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) / 113;      // value 0..9
  //
  // set corresponding p[] array element
  //
  j = 0;
  do {
    // set all to 0
    p[j] = 0;
    j++;
  } while (j <= 9);
  p[ai1] = 1; // set to 1 the corresponding p[] array element
  //
  //
  // Start IL instruction scrutation (one IL step per loop)
  //
  //
  // check if instruction code is a one figure number and
  // if it is, consider it is a number and stack it in the integer stack
  //
  switch (stp[stc]) {
    //
    case 0:
      //
      istack0 = 0;
      break;
    //
    case 1:
      //
      istack0 = 1;
      break;
    //
    case 2:
      //
      istack0 = 2;
      break;
    //
    case 3:
      //
      istack0 = 3;
      break;
    //
    case 4:
      //
      istack0 = 4;
      break;
    //
    case 5:
      //
      istack0 = 5;
      break;
    //
    case 6:
      //
      istack0 = 6;
      break;
    //
    case 7:
      //
      istack0 = 7;
      break;
    //
    case 8:
      //
      istack0 = 8;
      break;
    //
    case 9:
      //
      istack0 = 9;
      break;
    //
    default:
      // no instruction match
      break;
  }
  //
  //
  // check if operation code is Read an input (instruction codes 10 to 14)
  //
  switch (stp[stc]) {
    //
    case 10:
      // read analog input 1
      istack0 = ai1;
      break;
    //
    case 11:
      // read digital input 1
      dwnstack();
      stack[0] = i1;
      break;
    //
    case 12:
      // read digital input 2
      dwnstack();
      stack[0] = i2;
      break;
    //
    case 13:
      // read digital input 3
      dwnstack();
      stack[0] = i3;
      break;
    //
    case 14:
      // read digital input 4
      dwnstack();
      stack[0] = i4;
      break;
    //
    default:
      // no instruction match
      break;
  }
  //
  // check if instruction code is Read a digital memory (instruction codes 21 to 29)
  //
  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;
    //
    case 25:
      // read digital memory 5
      dwnstack();
      stack[0] = m5;
      break;
    //
    case 26:
      // read timer 6 (bit)
      dwnstack();
      stack[0] = tm6;
      break;
    //
    //
    case 27:
      // read timer 7 (bit)
      dwnstack();
      stack[0] = tm7;
      break;
    //
    //
    case 28:
      // read timer 8 (bit)
      dwnstack();
      stack[0] = tm8;
      break;
    //
    case 29:
      // read timer 9 (bit)
      dwnstack();
      stack[0] = tm9;
      break;
    //
    default:
      // no operation match
      break;
  }
  //
  // check if instruction is Read an output (instruction codes 31 to 34)
  //
  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 a digital output (instruction codes 41 to 44)
  //
  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 (instruction codes 51 to 59)
  //
  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;
    //
    case 55:
      // write digital memory 5
      m5 = stack[0];
      break;
    //
    case 56:
      // write memory 6
      m6 = stack[0];
      break;
    //
    case 57:
      // write memory 7
      m7 = stack[0];
      break;
    //
    case 58:
      // write memory 8
      m8 = stack[0];
      break;
    case 59:
      // Start timer 9
      m9 = stack[0];
      break;
    //
    default:
      // no instruction match
      break;
  }
  //
  // check if instruction is to Write bit 0 or 1 (instruction codes 60 to 61)
  //
  switch (stp[stc]) {
    //
    case 60:
      // write 0
      dwnstack();
      stack[0] = 0;
      break;
    //
    case 61:
      // write 1
      dwnstack();
      stack[0] = 1;
      break;
    //
    default:
      // no instruction match
      break;
  }
  //
  // check if instruction is Set Timer Delay (instruction codes 66 to 69)
  // value in istack0 must be the delay in seconds.
  //
  switch (stp[stc]) {
    //
    case 66:
      // write timer 6 delay
      dtm6 = 1000 * istack0;
      break;
    //
    case 67:
      // write timer 7 delay
      dtm7 =  1000 * istack0;
      break;
    //
    case 68:
      // write timer 8 delay
      dtm8 =  1000 * istack0;
      break;
    //
    case 69:
      // write timer 9 delay
      dtm9 = 1000 * istack0;
      break;
    //
    default:
      // no instruction match
      break;
  }
  //
  // check if instruction is Read analog index potentiometer position
  //
  switch (stp[stc]) {
    case 70:
      // read p[0]
      dwnstack();
      stack[0] = p[0];
      break;
    //
    case 71:
      // read p[1]
      dwnstack();
      stack[0] = p[1];
      break;
    //
    case 72:
      // read p[2]
      dwnstack();
      stack[0] = p[2];
      break;
    //
    case 73:
      // read p[3]
      dwnstack();
      stack[0] = p[3];
      break;
    //
    case 74:
      // read p[4]
      dwnstack();
      stack[0] = p[4];
      break;
    //
    case 75:
      // read p[5]
      dwnstack();
      stack[0] = p[5];
      break;
    //
    //
    case 76:
      // read p[6]
      dwnstack();
      stack[0] = p[6];
      break;
    //
    case 77:
      // read p[7]
      dwnstack();
      stack[0] = p[7];
      break;
    //
    case 78:
      // read p[8]
      dwnstack();
      stack[0] = p[8];
      break;
    //
    case 79:
      // read p[9]
      dwnstack();
      stack[0] = p[9];
      break;
    //
    default:
      // no instruction match
      break;
  }
  //
  // check if instruction is a boolean operation (instruction codes 81 to 84)
  //
  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
  // ----------------------------------
  //
  //
  //     timer 6 evaluation
  //
  //
  if ((m6 == 1) & (pm6 == 0)) {
    if  (tm6 == 0) {
      // tm6 start
      ctrtm6 = millis();
      tm6 = 1;
      pm6 = 1;
    } else {
      // tm6 already started
      pm6 = 1;
    }
  }
  //
  if ((m6 == 0) & (pm6 == 1)) {
    // m6 has just felt to Low
    pm6 = 0;
  }
  if ((tm6 == 1) & ((ctrtm6 + dtm6) <= millis())) {
    // delay over switch off tm6
    tm6 = 0;
  }
  //
  //     timer 7 evaluation
  //
  if ((m7 == 1) & (pm7 == 0)) {
    if  (tm7 == 0) {
      // tm7 start
      ctrtm7 = millis();
      tm7 = 1;
      pm7 = 1;
    } else {
      // tm7 already started
      pm7 = 1;
    }
  }
  //
  if ((m7 == 0) & (pm7 == 1)) {
    // m7 has just felt to Low
    pm7 = 0;
  }
  if ((tm7 == 1) & ((ctrtm7 + dtm7) <= millis())) {
    // delay over switch off tm7
    tm7 = 0;
  }
  //
  //     timer 8 evaluation
  //
  if ((m8 == 1) & (pm8 == 0)) {
    if  (tm8 == 0) {
      // tm8 start
      ctrtm8 = millis();
      tm8 = 1;
      pm8 = 1;
    } else {
      // tm8 already started
      pm8 = 1;
    }
  }
  //
  if ((m8 == 0) & (pm8 == 1)) {
    // m8 has just felt to Low
    pm8 = 0;
  }
  if ((tm8 == 1) & ((ctrtm8 + dtm8) < millis())) {
    // delay over switch off tm8
    tm8 = 0;
  }
  //
  //     timer 9 evaluation
  //
  if ((m9 == 1) & (pm9 == 0)) {
    if  (tm9 == 0) {
      // tm9 start
      ctrtm9 = millis();
      tm9 = 1;
      pm9 = 1;
    } else {
      // tm9 already started
      pm9 = 1;
    }
  }
  //
  if ((m9 == 0) & (pm9 == 1)) {
    // m9 has just felt to Low
    pm9 = 0;
  }
  if ((tm9 == 1) & ((ctrtm9 + dtm9) < millis())) {
    // delay over switch off tm9
    tm9 = 0;
  }
  //
  //   write digital outputs value
  //
  digitalWrite(10, o1);
  digitalWrite(11, o2);
  digitalWrite(12, o3);
  digitalWrite(13, o4);
  //
  // display inputs on LCD line 1
  //
  lcd.setCursor(0, 0);
  lcd.print("i");
  lcd.print(i1);
  lcd.print(i2);
  lcd.print(i3);
  lcd.print(i4);
  //
  lcd.setCursor(5, 0);
  lcd.print("(");
  lcd.print(ai1);
  lcd.print(")");
  //
  //
  lcd.setCursor(12, 0);
  lcd.print("RUN ");

  //
  // display outputs and memories on LCD line 2
  //
  lcd.setCursor(0, 1);
  lcd.print("o");
  lcd.print(o1);
  lcd.print(o2);
  lcd.print(o3);
  lcd.print(o4);
  lcd.print(" m");
  lcd.print(m1);
  lcd.print(m2);
  lcd.print(m3);
  lcd.print(m4);
  lcd.print(m5);
  lcd.print(tm6);
  lcd.print(tm7);
  lcd.print(tm8);
  lcd.print(tm9);
  //
  // 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;
}

void edit()
{
  Serial.setTimeout(90000);
  do {
    //
    lcd.setCursor(12, 0);
    lcd.print("EDIT");
    //
    Serial.println("  ");
    Serial.println("Edit mode");
    Serial.println("=========");
    Serial.println("  ");
    Serial.println("Type 0 <Enter> to exit Edit mode or to list the IL program.");
    Serial.println("Type a line number <Enter> to modify the program step.");
    Serial.println(' ');
    Serial.println("Command (0/n)?");
    r = Serial.parseInt();
    Serial.print(">");
    Serial.println(r);
    if (r != 0) {
      Serial.println("  ");
      Serial.print("The actual program step ");
      Serial.print(r);
      Serial.println(" is:");
      Serial.print(r);
      Serial.print(':');
      Serial.println(stp[r]);
      Serial.println("  ");
      Serial.println("Please, complete the step with the new instruction code or 88 to insert.");
      Serial.print(r);
      Serial.print(": ?");
      s = Serial.parseInt();
      Serial.print(">");
      Serial.println(s);
      if (s == 88) {
        // shift the next steps
        Serial.println("  ");
        Serial.print("inserting a new step and shifting the following steps...");
        j = nbstp + 2;
        do {
          j--;
          stp[j] = stp[(j - 1)];
        } while (r < j);
        stp[r] = 80; // NOP
        Serial.println("  ");
        Serial.print("The new step ");
        Serial.print(r);
        Serial.println(" has been inserted.");
        Serial.print(r);
        Serial.print(':');
        Serial.println(stp[r]);
      } else {
        stp[r] = s;
        Serial.println("  ");
        Serial.println("The new step is:");
        Serial.print(r);
        Serial.print(':');
        Serial.println(stp[r]);
      }
    } else {
      Serial.println("  ");
      Serial.println("Command (0/1)? 0 <Enter>:Exit / 1 <Enter>:Program list");
      s = Serial.parseInt();
      Serial.print(">");
      Serial.println(s);
      switch (s) {
        case 0:
          edtmode = false;
          //
          break;
        //
        case 1:
          //
          j = 0;
          Serial.println("  ");
          Serial.println("  ");
          Serial.println("Program list:");
          Serial.println("  ");
          do {
            j++;
            Serial.print(j);
            Serial.print(':');
            Serial.print(stp[j]);
            Serial.print("\t");
            switch (stp[j])  {
              case 0:
                ope = "dec nbr 0";
                break;
              case 1:
                ope = "dec nbr 1";
                break;
              case 2:
                ope = "dec nbr 2";
                break;
              case 3:
                ope = "dec nbr 3";
                break;
              case 4:
                ope = "dec nbr 4";
                break;
              case 5:
                ope = "dec nbr 5";
                break;
              case 6:
                ope = "dec nbr 6";
                break;
              case 7:
                ope = "dec nbr 7";
                break;
              case 8:
                ope = "dec nbr 8";
                break;
              case 9:
                ope = "dec nbr 9";
                break;
              case 10:
                ope = "R analog inp.1";
                break;
              case 11:
                ope = "R inp.1";
                break;
              case 12:
                ope = "R inp.2";
                break;
              case 13:
                ope = "R inp.3";
                break;
              case 14:
                ope = "R inp.4";
                break;
              case 21:
                ope = "R mem.1";
                break;
              case 22:
                ope = "R mem.2";
                break;
              case 23:
                ope = "R mem.3";
                break;
              case 24:
                ope = "R mem.4";
                break;
              case 25:
                ope = "R mem.5";
                break;
              case 26:
                ope = "R tmr 6";
                break;
              case 27:
                ope = "R tmr 7";
                break;
              case 28:
                ope = "R tmr 8";
                break;
              case 29:
                ope = "R tmr 9";
                break;
              case 31:
                ope = "R outp.1";
                break;
              case 32:
                ope = "R outp.2";
                break;
              case 33:
                ope = "R outp.3";
                break;
              case 34:
                ope = "R outp.4";
                break;
              case 41:
                ope = "W outp.1";
                break;
              case 42:
                ope = "W outp.2";
                break;
              case 43:
                ope = "W outp.3";
                break;
              case 44:
                ope = "W outp.4";
                break;
              case 51:
                ope = "W mem.1";
                break;
              case 52:
                ope = "W mem.2";
                break;
              case 53:
                ope = "W mem.3";
                break;
              case 54:
                ope = "W mem.4";
                break;
              case 55:
                ope = "W mem.5";
                break;
              case 56:
                ope = "W tmr 6";
                break;
              case 57:
                ope = "W tmr 7";
                break;
              case 58:
                ope = "W tmr 8";
                break;
              case 59:
                ope = "W tmr 9";
                break;
              case 60:
                ope = "bit 0";
                break;
              case 61:
                ope = "bit 1";
                break;
              case 66:
                ope = "tmr 6 delay";
                break;
              case 67:
                ope = "tmr 7 delay";
                break;
              case 68:
                ope = "tmr 8 delay";
                break;
              case 69:
                ope = "tmr 9 delay";
                break;
              case 70:
                ope = "R p(0)";
                break;
              case 71:
                ope = "R p(1)";
                break;
              case 72:
                ope = "R p(2)";
                break;
              case 73:
                ope = "R p(3)";
                break;
              case 74:
                ope = "R p(4)";
                break;
              case 75:
                ope = "R p(5)";
                break;
              case 76:
                ope = "R p(6)";
                break;
              case 77:
                ope = "R p(7)";
                break;
              case 78:
                ope = "R p(8)";
                break;
              case 79:
                ope = "R p(9)";
                break;
              case 80:
                ope = "NOP";
                break;
              case 81:
                ope = "AND";
                break;
              case 82:
                ope = "OR";
                break;
              case 83:
                ope = "NOT";
                break;
              case 84:
                ope = "XOR";
                break;
              case 91:
                ope = "DUP";
                break;
              case 92:
                ope = "SWAP";
                break;
              case 93:
                ope = "DROP";
                break;
              case 99:
                ope = "END";
                break;
              //
              default:
                ope = "(unassigned)";
                break;
            }
            Serial.println(ope);
            if (j % (10) == 0) {
              Serial.println("  ");
              Serial.println("  ");
              Serial.println("Command (0/1)? 0 <Enter> Quit list / 1 <Enter> Continue list");
              t = Serial.parseInt();
              Serial.print(">");
              Serial.println(t);
              if (t == 0) {
                j = nbstp;
              }
            }

          } while ((stp[j] != 99) & ( j < nbstp));
          Serial.println("  ");
          Serial.println("End list.");
      }
    }
  } while (edtmode == true);
  Serial.setTimeout(1000);
  Serial.println("  ");
  Serial.println("End Edit mode, back to normal RUN mode.");
  Serial.println("Type 0 <Enter> for Edit mode.");
  //
  lcd.setCursor(12, 0);
  lcd.print("RUN ");
  //
  loop();
}
// End of myTinyPLC (version 1.3.0 "Dare Dary") - Thanks for watching !


Remarques :

La mémoire interne de l'Arduino Uno commence à être bien remplie ! En l'état de cette version, le compilateur affiche un message d'avertissement : La mémoire disponible faible, des problèmes de stabilité pourraient survenir.

Je souhaitais au départ pouvoir rajouter des servo-moteurs sur myTinyPLC mais j'abandonne cette idée car il faudrait développer la logique pour les calculs et il n'y a clairement plus de place pour cela. Je garde donc le projet myTinyPLC en l'état actuel de sa version 1.3 présentée ci-dessus.

En résumé : myTinyPLC est un simulateur d'automate programmable réalisé avec un Arduino Uno R3 et quelques composants courants d'un starter kit.
 
Il est possède :
 - 
son propre langage de programmation, de type Instruction List, et pouvant se programmer sans recourir à une recompilation dans l'IDE Arduino.
 - une entrée analogique : un potentiomètre, dont on peut connaître la position de 0 à 9 (0 = valeur minimale, en position tournée complètement vers la gauche, et 9  = 
valeur maximale, en position tournée complètement vers la droite). Avec les codes d'instructions de lecture du potentiomètre 70 à 79, il fait fonction de commutateur.
 - 4 entrées binaires (boutons poussoirs).
 - 4 sorties binaires (LEDs).
 - 5 mémoires binaires 0/1 internes (m1 à m5)
 - 4 temporisations réglables de 1 à 9 secondes (tmr6 à tmr9).
 - 2 modes de fonctionnement : un mode Exécution (RUN) et un mode Programmation (EDIT) pour entrer les instructions du programme IL avec le moniteur série.
 - Un afficheur LCD1602 ( 2 lignes de 16 caractères) permet de visualiser l'état des entrées binaires et du potentiomètre, ainsi que l'état des sorties et des mémoires, sans oublier le mode fonctionnement RUN ou EDIT.








Composants et câblage :


Composants :

1 Arduino Uno R3.
1 planche à contacts pour prototypage (breadboard 163 x 54 mm).
1 écran LCD1602.
4 boutons poussoirs.
2 potentiomètres 10 k ohms.
5 résistances 330 ohms 1/8W.
4 résistances 10 k ohms 1/8W.
1 LED rouge.
1 LED jaune.
1 LED verte.
1 LED bleue.
et un certain nombre de fils de câblage.


**** Guy F8ABX - 16/03/2021 ****