RobotDYN - Arduino MEGA 2560 a ESP8266 - sériová komunikace

21.02.2022 Arduino #arduino #esp #programování

Návod je ukázkou, jak jednotlivé platformy desky RobotDYN mohou mezi sebou komunikovat pomocí sériových linek. Předkládá možnost ovládání dvou LED pomocí HTML rozhraní, uloženém na webovém serveru desky ESP8266.


Tento příspěvek navazuje na předchozí články: RobotDYN - Arduino MEGA 2560 s vestavěným WiFi - ESP8266, který se zabývá základním nastavením desky a jejím zprovozněním, RobotDYN - Arduino MEGA 2560 a ESP8266 - obousměrná komunikace - základ, který ukazuje základní ovládání jedné LED přes webovou aplikaci.

Pokud chceme ovládat více zařízení a sledovat zároveň jejich stav, musí se přistoupit k inovaci v podobě rozšířené sériové komunikace. Podrobné vysvětlení sériové komunikace, její princip, protokoly apod. je uvedeno v článku Sériová komunikace. Zde je uvedena část sériové komunikace týkající se mikrokontrolerů Arduino.

Pro sériovou komunikace v Arduino se používají piny TX/RX, tedy využívá se logické úrovně TTL (5V nebo 3,3V v závislosti na desce). Tyto piny by se neměli připojovat přímo k sériovému portu RS232, který pracují s +/- 12V a může tak poškodit desku. Sériové rozhraní se používá pro komunikaci mezi deskou Arduino a počítačem nebo jinými zařízeními. Všechny desky Arduino mají alespoň jeden sériový port (také známý jako UART nebo USART): Serial. Komunikuje na digitálních pinech 0 (RX) a 1 (TX) a s počítačem přes USB. Pokud tedy chceme využít tuto funkci, nelze zároveň použít piny 0 a 1 pro digitální vstup nebo výstup [1].

 

Board USB CDC name Serial pins Serial1 pins Serial2 pins Serial3 pins
Uno, Nano, Mini 0(RX), 1(TX)
Mega 0(RX), 1(TX) 19(RX), 18(TX) 17(RX), 16(TX) 15(RX), 14(TX)
Leonardo, Micro, Yún Serial 0(RX), 1(TX)
Uno WiFi Rev.2 Connected to USB 0(RX), 1(TX) Connected to NINA
MKR boards Serial 13(RX), 14(TX)
Zero SerialUSB (Native USB Port only) Connected to Programming Port 0(RX), 1(TX)
Due SerialUSB (Native USB Port only) 0(RX), 1(TX) 19(RX), 18(TX) 17(RX), 16(TX) 15(RX), 14(TX)
101 Serial 0(RX), 1(TX)

Ke komunikaci s deskou Arduino se využívá vestavěný sériový monitor, který je součástí IDE pro Arduino.

Standardní Arduino má jeden hardwarový sériový port, ale sériová komunikace je také možná pomocí softwarových knihoven pro emulaci dalších portů (komunikačních kanálů) pro zajištění konektivity k více než jednomu zařízení. Softwarový sériový port vyžaduje velkou pomoc od řadiče Arduino k odesílání a přijímání dat, takže není tak rychlý a efektivní jako hardwarový port.

Arduino Mega má čtyři hardwarové sériové porty, které mohou komunikovat až se čtyřmi různými sériovými zařízeními. Pouze jeden z nich má vestavěný USB adaptér (lze připojit USB-TTL adaptér k jakémukoli jinému sériovému portu). Níže uvedená tabulka ukazuje názvy portů a piny používané pro všechny sériové porty Arduino Mega [2]. Tyto vlastnosti má i deska RobotDYN a pro předkládaný projekt se také využívají.

Projekt se opět skládá ze dvou částí:

  1. Programování ESP8266
  2. Programování Mega2560

Program pro ESP8266

Pro zprovoznění webového serveru s asynchronním voláním, je nutné nastavení přepínače DIP následujícím způsobem:

1 2 3 4 5 6 7 8
CH340 připojit k ESP8266 (nahrát program) OFF OFF OFF OFF ON ON ON NOUSE

Následně se nahraje programový kód do ESP8266.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266mDNS.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

const char* ssid = "MegaEsp";
const char* password = "12345678";

const char* PARAM_INPUT_1 = "action";
const char* PARAM_INPUT_2 = "output";
const char* PARAM_INPUT_3 = "state";

 String inString;

AsyncWebServer server(80);
MDNSResponder mdns;


const char index_html[] PROGMEM = R"rawliteral(
    <!DOCTYPE HTML><html>
    <head>
      <title>ESP Web Server</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
        html {font-family: Arial; display: inline-block; text-align: center;}
        h2 {font-size: 18px;}
        p {font-size: 12px;}
        body {
                font-family: Arial,Helvetica,sans-serif;
                background: #181818;
                color: #EFEFEF;
                font-size: 16px
            }
            section.main {
                display: flex
            }

            section.main {
                flex-direction: column
            }
        #content {
                display: flex;
                flex-wrap: wrap;
                align-items: stretch
            }
            .input-group {
                display: flex;
                flex-wrap: nowrap;
                line-height: 22px;
                margin: 5px 0;
                width:100rem;
            }

            .input-group>label {
                display: inline-block;
                padding-right: 10px;
                text-align: left;
            }

            .input-group input,.input-group select {
                flex-grow: 1
            }
        .switch {
                display: block;
                position: absolute;
                font-size: 16px;
                height: 0;
                right:0;
            }

            .switch input {
                outline: 0;
                opacity: 0;
                width: 0;
                height: 0;
                position: absolute
            }

            .slider {
                width: 50px;
                height: 22px;
                cursor: pointer;
                background-color: grey;
                position: absolute;
                right: 10px;
            }

            .slider,.slider:before {
                display: inline-block;
                transition: .4s
            }

            .slider:before {
                position: relative;
                content: "";
                height: 16px;
                width: 16px;
                left: -14px;
                top: 3px;
                background-color: #fff
            }

            input:checked+.slider {
                background-color: #ff3034
            }

            input:checked+.slider:before {
                -webkit-transform: translateX(26px);
                transform: translateX(26px)
            }
      </style>
    </head>
    <body>
        <section class="main">
        <div id="content">
            <h2>ESP Web Server</h2>

            %BUTTONPLACEHOLDER%

        </div>
        </section>
    <script>
    function toggleCheckbox(element) {
      var xhr = new XMLHttpRequest();
      if(element.checked){ 
        xhr.open("GET", "/update?action=update&output="+element.id+"&state=1", true); 
      } 
      else { 
          xhr.open("GET", "/update?action=update&output="+element.id+"&state=0", true); 
      }
      xhr.send();
    }
    document.addEventListener('DOMContentLoaded', function (event) {
        var xhttp = new XMLHttpRequest();
          xhttp.onreadystatechange = function() {
            if (this.readyState == 4 && this.status == 200) {
              var lastStateArr = this.responseText.split("n");
              var stateArr = lastStateArr[lastStateArr.length-2].split("|");
              for(var i=1;i<stateArr.length;i++){
                if(stateArr[i]==1){
                  document.getElementById(i).checked = true;
                }else{
                  document.getElementById(i).checked = false;
                }
              }
            }
          };
          xhttp.open("GET", "/state?action=getstate&output=0&state=0", true);
          xhttp.send();
    });
    </script>
    </body>
    </html>
)rawliteral";

String getValue(String data, char separator, int index){
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length()-1;

  for(int i=0; i<=maxIndex && found<=index; i++){
    if(data.charAt(i)==separator || i==maxIndex){
        found++;
        strIndex[0] = strIndex[1]+1;
        strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }

  return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}

String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons ="";
    //String outputStateValue = outputState();
    buttons+= "<div class="input-group" id="output-group1"><label for="output">Light<span id="outputState1"></span></label><div class="switch"><input id="1" type="checkbox" class="default-action" onchange="toggleCheckbox(this)" ><label class="slider" for="1"></label></div></div>";
    buttons+= "<div class="input-group" id="output-group2"><label for="output">Fan<span id="outputState2"></span></label><div class="switch"><input id="2" type="checkbox" class="default-action" onchange="toggleCheckbox(this)" ><label class="slider" for="2"></label></div></div>";
    return buttons;
  }
  return String();
}

void setup() {
  Serial1.begin(115200);
  Serial.begin(115200);

  WiFi.softAP(ssid, password);
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);

  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  // Information about kontrolet
  Serial.println("");
  Serial.println("ESP8266 board info:");
  Serial.print("tChip ID: ");
  Serial.println(ESP.getFlashChipId());
  Serial.print("tCore Version: ");
  Serial.println(ESP.getCoreVersion());
  Serial.print("tChip Real Size: ");
  Serial.println(ESP.getFlashChipRealSize());
  Serial.print("tChip Flash Size: ");
  Serial.println(ESP.getFlashChipSize());
  Serial.print("tChip Flash Speed: ");
  Serial.println(ESP.getFlashChipSpeed());
  Serial.print("tChip Speed: ");
  Serial.println(ESP.getCpuFreqMHz());
  Serial.print("tChip Mode: ");
  Serial.println(ESP.getFlashChipMode());
  Serial.print("tSketch Size: ");
  Serial.println(ESP.getSketchSize());
  Serial.print("tSketch Free Space: ");
  Serial.println(ESP.getFreeSketchSpace());

  /* Settings for public WiFi 
  WiFi.begin(ssid, password);
  Serial.println("");
     
  // connect to local wifi NO to AP wifi

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }

  Serial.println("");
  Serial.print("Connected to "); 
  Serial.println(ssid);
  Serial.print("IP address: "); 
  Serial.println(WiFi.localIP());
  */

  Serial.print("AP IP address: ");
  Serial.println(IP);

  if (mdns.begin("esp8266", WiFi.localIP())) {
    Serial.println("MDNS responder started");
  }

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    // GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>&action=<inputMessage3>
    if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2) && request->hasParam(PARAM_INPUT_3)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value()+"|"+request->getParam(PARAM_INPUT_2)->value()+"|"+request->getParam(PARAM_INPUT_3)->value();
      //Serial.println(inputMessage);
    } else {
      inputMessage = "For GETSTATE message no sent";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", inString);
    inString="";
  });

  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    // GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>&action=<inputMessage3>
    if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2) && request->hasParam(PARAM_INPUT_3)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value()+"|"+request->getParam(PARAM_INPUT_2)->value()+"|"+request->getParam(PARAM_INPUT_3)->value();
      //Serial.println(inputMessage);
    } else {
      inputMessage = "For UPDATE message no sent";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");
  });
  
  server.begin();
  Serial.println("HTTP server started");

}

void loop() {

}

void serialEvent() {
  while (Serial.available()) {
    char inChar = Serial.read();

    inString += inChar;
    if (inChar == 'n') {
      String output = getValue(inString,'|',0);
    }
  }
}

 

Jak kód funguje

V následujících odstavcích je vysvětleno, jak kód funguje.

Import knihoven

Nejdříve se naimportují požadované knihovny. The WiFiClient, ESPAsyncWebServer a ESAsyncTCP jsou potřebné k sestavení webového serveru.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266mDNS.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

 

Nastavení přihlašovacích údajů k síti

V tomto příkladu je modul ESP8266 reprezentován AccessPoint. Lze se, ale připojit k již existující síti. Pro oba případy je nutné se vložit síťové přihlašovací údaje do následujících proměnných.

const char* ssid = "MegaEsp";
const char* password = "12345678";

 

Definice proměnných

Pro tento ukázkový příklad, kdy se ovládá LED dioda, je definován vstup a název parametru pro asynchronní odesílání zpráv.

const char* PARAM_INPUT_1 = "action";
const char* PARAM_INPUT_2 = "output";
const char* PARAM_INPUT_3 = "state";

String inString;

  

Vytvoření objektu AsyncWebServer na portu 80.

AsyncWebServer server(80);

 

Nastavení ESP8266 jako přístupového bodu (AP)

V sekci setup() se pro nastavení ESP8266 jako přístupového bodu využívá metoda softAP():

 WiFi.softAP(ssid, password);

Existují také další volitelné parametry, které lze metodě softAP() předat:

.softAP(const char* ssid, const char* password, int channel, int ssid_hidden, int max_connection)
  • ssid (definováno dříve): maximálně 31 znaků
  • password (definováno dříve): minimálně 8 znaků. Pokud není zadáno, přístupový bod bude otevřený (maximálně 63 znaků)
  • channel: Číslo kanálu Wi-Fi (1-13). Výchozí hodnota je 1
  • ssid_hidden: pokud je nastaveno na true, skryje SSID
  • max_connection: maximální počet současně připojených stanic, od 0 do 8

Dále se získá IP adresa přístupového bodu pomocí softAPIP() a vytiskne je v Serial Monitor.

IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);

 

Webová stránka - přepínač

Webová stránka obsahuje definici stylů a skriptů pro asynchronní volání událostí. Veškerý HTML kód je uložen v proměnné index_html. V HTML nejsou zahrnuta tlačítka pro ovládání LED. To proto, aby se dali měnit jejich vlastnosti v závislosti na aktuálním stavu LED. Je tedy pro tlačítka vytvořen zástupný symbol %BUTTONPLACEHOLDER%, který bude nahrazen textem HTML pro vytvoření tlačítek později v kódu (to se provádí ve funkci procesor()).

<div id="content">
            <h2>ESP Web Server</h2>

            %BUTTONPLACEHOLDER%

</div>

 

procesor()

Funkce procesor() nahrazuje všechny zástupné symboly v HTML textu skutečnými hodnotami. Nejprve zkontroluje, zda texty HTML obsahují nějaké zástupné symboly %BUTTONPLACEHOLDER%.

if(var == "BUTTONPLACEHOLDER"){

Tlačítka jsou definovány v proměnné buttons. Důležité je, že každé tlačítko má své jedinečné ID. Tento identifikátor poskytuje hodnotu, která se uplatní, při ovlivňování LED a zpětnému nastavení tlačítek při výpadku, restartu aplikace.

buttons+= "<div class="input-group" id="output-group1"><label for="output">Light<span id="outputState1"></span></label><div class="switch"><input id="1" type="checkbox" class="default-action" onchange="toggleCheckbox(this)" ><label class="slider" for="1"></label></div></div>";
buttons+= "<div class="input-group" id="output-group2"><label for="output">Fan<span id="outputState2"></span></label><div class="switch"><input id="2" type="checkbox" class="default-action" onchange="toggleCheckbox(this)" ><label class="slider" for="2"></label></div></div>";

 

Požadavek HTTP GET na změnu stavu výstupu (JavaScript)

Když se stiskne tlačítko, je zavolána funkce toggleCheckbox(). Tato funkce vytvoří požadavek na různé adresy URL pro zapnutí nebo vypnutí LED.

function toggleCheckbox(element) {
      var xhr = new XMLHttpRequest();
      if(element.checked){ 
        xhr.open("GET", "/update?action=update&output="+element.id+"&state=1", true); 
      } 
      else { 
          xhr.open("GET", "/update?action=update&output="+element.id+"&state=0", true); 
      }
      xhr.send();
}

Chceme-li rozsvítit LED, odešle se požadavek na adresu /update?action=update&output="+element.id+"&state=1:

if(element.checked){ 
    xhr.open("GET", "/update?action=update&output="+element.id+"&state=1", true); 
}

 

Na rozdíl od předchozích příkladů, se zde odesílají tři parametry:

  1. action - identifikuje událost, podle které se rozhodne co má v platformě Mega nastat,
  2. output - číslo, které identifikuje, jaké tlačítko bylo stisknuto a tím také ukazuje, jaká LED resp. její stav. bud aktualizován.
  3. state - logická hodnota, kde 1 znamená LED rozsvítit, 0, LED zhasnout.

HTTP GET požadavek pro nastavení výchozího stavu (JavaScript)

Aby byl stav výstupu na webovém serveru aktualizovaný, volá se následující funkce, která při svém prvním spuštění nastaví tlačítka do stavu, který odpovídá uloženým hodnotám na platformě Mega. Je to důležité např. z důvodu výpadku spojení mezi aplikací a serverem. Tento skript potom zajišťuje získání aktuálních hodnot.

document.addEventListener('DOMContentLoaded', function (event) {
        var xhttp = new XMLHttpRequest();
          xhttp.onreadystatechange = function() {
            if (this.readyState == 4 && this.status == 200) {
              var lastStateArr = this.responseText.split("n");
              var stateArr = lastStateArr[lastStateArr.length-2].split("|");
              for(var i=1;i<stateArr.length;i++){
                if(stateArr[i]==1){
                  document.getElementById(i).checked = true;
                }else{
                  document.getElementById(i).checked = false;
                }
              }
            }
          };
          xhttp.open("GET", "/state?action=getstate&output=0&state=0", true);
          xhttp.send();
    });

 

Zpracování požadavků

Následující programové kódy zajišťují, co se stane, když ESP8266 obdrží požadavky na uvedené URL adresy.

Když je požadavek přijat na root / URL, odešle se HTML stránka a také procesor.

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

Následující řádky zkontrolují, zda jsme obdrželi požadavek na URL /update?action=update&output="+element.id+"&state=1 nebo /update?action=update&output="+element.id+"&state=0. Na sériový monitor se zašle hodnota parametru v podobě inputMessage. Důlěžitá je skladba resp. obsah proměnné inputMessage. Hodnoty jednotlivých parametrů , protože tuto zprávu bude číst ze sériového vstupu program v Mega 2560. Pokud by se nepoužívala druhá platforma, tak by bylo možné již rovnou poslat hodnotu na digitální vstup. Ale to by bylo možné pouze v případě, že LED je připojena k ESP8266.

server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    // GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>&action=<inputMessage3>
    if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2) && request->hasParam(PARAM_INPUT_3)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value()+"|"+request->getParam(PARAM_INPUT_2)->value()+"|"+request->getParam(PARAM_INPUT_3)->value();
      //Serial.println(inputMessage);
    } else {
      inputMessage = "For UPDATE message no sent";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");
  });
  
  server.begin();
  Serial.println("HTTP server started");

}

Když je přijat požadavek na URL /state, odešle se aktuální stav výstupu:

server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(Serial.read()).c_str());
  });

Smyčka loop() je v tomto případě prázdná, protože ovládání rozsvícení a zhasínání LED zajišťuje část Mega 2560. Do ESP8266 by ve smyčce byl program jen tehdy, pokud by LED byla připojena na pin k ESP8266.

Příjem ze sériové linky z Mega2560

Jediný způsob, který na platformě RobotDYN spolehlivě funguje je využití funkce serialEvent(). Všechny ostatní způsoby příjmu dat, jejich šifrování a další zpracování selhaly.

void serialEvent() {
  while (Serial.available()) {
    char inChar = Serial.read();

    inString += inChar;
    if (inChar == 'n') {
      String output = getValue(inString,'|',0);
    }
  }
}

 

Ve funkci serialEvent() přijímáme jednotlivé hodnoty z link Serial až do té doby, kdy narazíme na znak nového řádku - "n". Ten nám signalizuje, že řetězec, který obsahuje identifikaci akce, stavu a hodnoty je celý lze jej rozparsovat na dílčí hodnoty.

Získávání hodnot z řetězce

Na rozdíl od předchozích příkladů, kde docházelo k přenosu stavu jedné LED pouhým číslem 0/1, se zde využívá obousměrné sériové komunikace a vždy dochází k zaslání řetězce, který obsahuje kombinaci názvu události a stavů, oddělených symbolem roury - "|". K rozparsování tohoto řetězce se využívá funkce getValue, která podle uvedeného znaku rozloží řetězec a vrátí hodnotu podle požadované pozice.

String getValue(String data, char separator, int index){
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length()-1;

  for(int i=0; i<=maxIndex && found<=index; i++){
    if(data.charAt(i)==separator || i==maxIndex){
        found++;
        strIndex[0] = strIndex[1]+1;
        strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }

  return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}

 

Program pro Mega2560

Aby bylo možné využívat více senzorů, využije se platforma Mega2560, která je součástí desky RobotDYN. Pro naprogramování Mega2560 je nutné přepnout přepínače DIP následujícím způsobem:

1 2 3 4 5 6 7 8
CH340 připojit k ATmega2560 (nahrát program) OFF OFF ON ON OFF OFF OFF NOUSE

Po nastavení přepínačů a připojení desky k napájení, nahraje se následující programový kód:

#include <Arduino.h>
#include <EEPROM.h>

#define PIN_LED_A 8
#define PIN_LED_B 9

String inString;
int pinControl;
String resA;
const int countPin=2;

void setup() {
  Serial.begin(115200);
  Serial3.begin(115200);

  for(int p=0;p<=countPin;p++){
    digitalWrite(p, LOW);
    EEPROM.update(p, 0);
  }
}

void loop() {
  delay(200);
}

String getValue(String data, char separator, int index){
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length()-1;

  for(int i=0; i<=maxIndex && found<=index; i++){
    if(data.charAt(i)==separator || i==maxIndex){
        found++;
        strIndex[0] = strIndex[1]+1;
        strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }

  return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}

void getStateFromEEP(){
  resA="";
  for (int count = 1; count <= countPin; count++) {
    int valState = EEPROM.read(count);
    if(valState==0){
      EEPROM.write(count,0);
    }
    resA=resA+"|"+valState;
  }
       
  Serial3.println("setstate"+resA);
}

void serialEvent3() {
  while (Serial3.available()) {
    char inChar = Serial3.read();

    inString += inChar;
    if (inChar == 'n') {

      Serial.println("IN "+inString);   

      String action = getValue(inString,'|',0); // number of output
      String output = getValue(inString,'|',1);  // state for LED 0/1
      String state = getValue(inString,'|',2);  // action - update, getstate
      output.trim();
      state.trim();
      action.trim();
      
      Serial.println("Akce "+action);
      
      if(action=="getstate"){  
        getStateFromEEP();
      }
      
      if(action=="update"){
        if(output.toInt()==1){
          //Serial.println("Output "+output);

          pinControl=PIN_LED_A;
        }
        if(output.toInt()==2){
          //Serial.println("Output "+output);

          pinControl=PIN_LED_B;
        }
        
        if (state.toInt()==1) {
          //Serial.println("State ON "+state+" PIN " + pinControl);

          digitalWrite(pinControl, HIGH);

        } else if (state.toInt()==0) {
          //Serial.println("State OFF "+state+" PIN " + pinControl);

          digitalWrite(pinControl, LOW);

        } else {
          Serial.println("Wrong command");
        }
        EEPROM.update(output.toInt(),state.toInt());
        getStateFromEEP();
      }
      
    }
  }
  inString = "";
  
}

 

Jakmile je kód úspěšně nahrán do Mega2560, nastaví se DIP přepínače do následujících poloh.

1 2 3 4 5 6 7 8
CH340 připojit k Mega2560 COM3 připojit k ESP8266 ON ON ON ON OFF OFF OFF NOUSE

Jak kód funguje

V následujících odstavcích je vysvětleno, jak funguje kód pro Mega2560.

Definice proměnných

Proměnné PIN_LED_A a PIN_LED_B obsahují čísla pin, na které jsou připojeny obě LED. Proměnná inString je pomocná proměnná při čtení dat ze sériové linky Serial3, tj. z ESP8266. pinControl je pomocná proměnná, které je přiřazen pin LED v závsislosti na zaslané hodnotě tlačítka. Proměnná resA obsahuje řetězec s hodnotami pro zpětné zaslání do ESP8266. Konstanta countPin obsahuje počet připojených LED. Pomáhá při čtení uložených stavů pro jednotlivé LED z paměti.

#define PIN_LED_A 8
#define PIN_LED_B 9

String inString;
int pinControl;
String resA;
const int countPin=2;

Získávání dat z ESP8266

Z platformy ESP8266 jsou data získávána prostřednictví Serial3.Read() ve funkci serialEvent3().

void serialEvent3() {
  while (Serial3.available()) {
    char inChar = Serial3.read();

    inString += inChar;
    if (inChar == 'n') {

      Serial.println("IN "+inString);   

      String action = getValue(inString,'|',0); // number of output
      String output = getValue(inString,'|',1);  // state for LED 0/1
      String state = getValue(inString,'|',2);  // action - update, getstate
      output.trim();
      state.trim();
      action.trim();
      
      Serial.println("Akce "+action);
      
      if(action=="getstate"){  
        getStateFromEEP();
      }
      
      if(action=="update"){
        if(output.toInt()==1){
          pinControl=PIN_LED_A;
        }
        if(output.toInt()==2){
          pinControl=PIN_LED_B;
        }
        
        if (state.toInt()==1) {
          digitalWrite(pinControl, HIGH);
        } else if (state.toInt()==0) {
          digitalWrite(pinControl, LOW);
        } else {
          Serial.println("Wrong command");
        }
        EEPROM.update(output.toInt(),state.toInt());
        getStateFromEEP();
      }
      
    }
  }
  inString = "";
  
}

Funkce serialEvent3() odchytává data zaslaná sériovým monitorem. V cyklu while se neustále čte vstup a ukládá se do proměnné inChar. Zřetězení hodnot je v proměnné inString. Následně se testuje, kdy zaslaný řetězec obsahuje znak zalomení řádku - n. Tento znak ukazuje, že se jedná o konec hodnoty zaslané jako parametry z ESP8266. Na základě rozparsovaných parametrů se určí, jaká akce se má provést.

Například v podmínce if(action=="getstate") se volá funkce getStateFromEEP() pro načtení uložených aktuálních hodnot z paměti a následně je sériovou linkou Serial3 zpět do ESP8266.

if(action=="getstate"){  
        getStateFromEEP();
      }

 

void getStateFromEEP(){
  resA="";
  for (int count = 1; count <= countPin; count++) {
    int valState = EEPROM.read(count);
    if(valState==0){
      EEPROM.write(count,0);
    }
    resA=resA+"|"+valState;
  }
       
  Serial3.println("setstate"+resA);
}

 

V podmínce if(action=="update") se testuje, jaká LED má být rozsvícena. Také se zde ukládají stavy jednotlivých LED do paměti. Pozice v paměti odpovídá parametru output, což je v ESP hodnota ID tlačítka.

if(action=="update"){
        if(output.toInt()==1){
          //Serial.println("Output "+output);

          pinControl=PIN_LED_A;
        }
        if(output.toInt()==2){
          //Serial.println("Output "+output);

          pinControl=PIN_LED_B;
        }
        
        if (state.toInt()==1) {
          //Serial.println("State ON "+state+" PIN " + pinControl);

          digitalWrite(pinControl, HIGH);

        } else if (state.toInt()==0) {
          //Serial.println("State OFF "+state+" PIN " + pinControl);

          digitalWrite(pinControl, LOW);

        } else {
          Serial.println("Wrong command");
        }
        EEPROM.update(output.toInt(),state.toInt());
        getStateFromEEP();
      }

Literatura

[1] Serial. Arduino [online]. Arduino, 2022 [cit. 2022-02-21]. Dostupné z: https://www.arduino.cc/en/reference/serial

[2] MARGOLIS, Michael. Serial Communications. Arduino Cookbook [online]. O’Reilly Media, 2021 [cit. 2022-02-21]. Dostupné z: https://www.oreilly.com/library/view/arduino-cookbook/9781449399368/ch04.html