Arduino – Controllo remoto con ENC28J60

Le applicazioni legate alla possibilità di poter controllare da remoto una scheda Arduino sono molteplici, si pensi ad esempio ad una serie di sensori di cui vogliamo conoscere i valori rilevati o ad un gruppo di dispositivi comandati da relè, i quali possono essere attivati o disattivati con un computer o uno smartphone dotati di una connessione Internet da qualsiasi parte ci troviamo.

Per fare questo il più delle volte si ricorre ad una delle Ethernet Shield ufficiali basate sul controller Wiznet W5100 o quella più recente e potente, la Ethernet Shield 2,basata invece sul chip W5500. Queste schede sono molto versatili perche dotate di uno slot in grado di ospitare una scheda SD sulla quale memorizzare intere pagine web e della capacità di implementare a livello hardware lo stack IP completo di protocolli di comunicazione TCP ed UDP, liberando in questo modo notevoli risorse al controllore di Arduino.

Un altro controller Ethernet molto usato, oggetto di questo articolo,  è quello basato sul chip ENC28J60 col quale si possono ottenere gli stessi risultati di base con un costo decisamente inferiore, perfetto se si vuole realizzare un piccolo sistema stand-alone. Una delle librerie necessarie per farlo funzionare è la UIPEthernet ( scaricabile a fondo pagina ) compatibile con la libreria  Ethernet.h, tant’è che alcuni esempi possono essere applicati all’ ENC28J60 cambiando solo l’intestazione, cioè sostituendo #include <Ethernet.h> e #include <SPI.h> con #include <UIPEthernet.h>.

 

 

 

Schema elettrico del dispositivo

Il circuito di prova usato per essere controllato da remoto è molto semplice, infatti dispone di 7 uscite che comandano altrettanti led e di un ingresso cui fa capo una resistenza tipo NTC da 22 Kohm usata come sensore di temperatura. Al posto dei led si sarebbero potuti usare dei relè pilotati da transistor senza cambiar nulla a livello di codice. Dallo schema vediamo inoltre i collegamenti tra il modulo ENC28J60 e Arduino secondo la tabella riportata più avanti.


Schema Arduino ENC28j60

 

 

Collegamento fra Arduino e il modulo Ethernet ENC28J60

 

Pin Arduino UNOPin Modulo ENC28J60Funzione
2INTUscita Interrupt
12SODati in uscita dal modulo Ethernet
13SCKClock
11SIDati in entrata al modulo Ethernet
ResetRSTReset
10CSChip Select : attivazione o meno del modulo
GNDGNDMassa
3.3 VVCCAlimentazione

 

 

 

Foto del modulo Ethernet ENC28J60

 

Il programma

In generale lo sketch svolge essenzialmente i compiti seguenti :

A) Stabilisce i pin usati come entrate e uscite

B) Ricava il valore della temperatura misurato dalla resistenza NTC

C) Configura il modulo di rete con i parametri necessari per il funzionamento, cioè MAC, IP e porta

D) Crea un mini Web Server che rimane in ascolto delle eventuali richieste in arrivo dai client

 

Il MAC sta per Media Access Control, chiamato anche indirizzo fisico, ed è un numero a 48 bit che identifica in maniera univoca a livello hardware un dispositivo all’interno di una rete. Di solito esso viene assegnato dal costruttore del dispositivo, ma nel nostro caso possiamo assegnarlo noi nel programma.

L’indirizzo IP (o Internet Protocol) è un numero formato da un gruppo di 4 cifre con valori compresi fra 0 e 255, separate da un punto e rappresenta l’indirizzo univoco che i dispositivi di calcolo come personal computer, tablet e smartphone utilizzano per identificarsi e comunicare con altri dispositivi in rete. Ci sono fondamentalmente due tipi di indirizzi Ip, ossia l’ IPv4 che è un numero a 32 bit in grado di assegnare circa 4.3 miliardi di indirizzi diversi e l’ IPv6 formato invece da 128 bit e in grado di assegnare circa 340 trilioni di trilioni di indirizzi diversi.  Inoltre, esso è formato da un gruppo di otto cifre anzichè quattro ed è stato progettato per risolvere il problema dell’esaurimento degli indirizzi del vecchio tipo IPv4, visto il numero sempre crescente di dispositivi intelligenti che si collegano ad Internet. Si stima che circa il 95% degli indirizzi IP siano di tipo IPv4 e appena il 5% quelli IPv6, quindi ci vorranno molti anni prima che anche quest’ultimo tipo venga esaurito.

Le porte sono dei numeri a 16 bit, compresi quindi tra 0 e 65535, che in una connessione TCP indicano quale applicazione o processo deve elaborare i dati in arrivo. Nel caso di un server HTTP la porta da impostare è appunto la numero 80.

 

 

/*
    Comando da remoto Arduino UNO - Ethernet ENC28J60

    By Mario de Nichilo
    18/08/2018

*/

#include <UIPEthernet.h>

/*

   Definizione delle entrate e uscite
*/

#define ledpin1  7
#define ledpin2  6
#define ledpin3  5
#define ledpin4  A0
#define ledpin5  A1
#define ledpin6  A2
#define ledpin7  A3

/*
    Costanti e variabili per misurare la temperatura attraverso la resistenza NTC
*/

byte pinNTC = A5;                         // pin entrata del sensore NTC
#define resistenza_In_Serie 10000         // resistenza posta in serie al sensore NTC ( in Ohm )
#define resistenza_Nominale_NTC 22000     // valore nominale del sensore NTC ( in Ohm )
#define temp_Nominale_NTC 25              // valore della temperatura nominale ( in gradi centigradi )
#define coeff_Thermistore 3950            // costante materiale del termistore ( è un valore comune )
float temperatura;

/*
    Indirizzo fisico,IP e porta del modulo ENC28J60
*/

byte mac[] = { 0x54, 0x34, 0x41, 0x30, 0x30, 0x31 };
IPAddress ip(192, 168, 1, 103);
EthernetServer server(80);


/*
    Variabili per aggiornare lo stato delle uscite
*/

char *statoLed1;
char *statoLed2;
char *statoLed3;
char *statoLed4;
char *statoLed5;
char *statoLed6;
char *statoLed7;

boolean pass;


/*
   Ricavo il valore della temperatura
   dall'NTC con l'equazione di SteinHart
*/

void valore_Termistore()

{
  float valore_ADC;
  float Resistenza;
  float steinhart;
  valore_ADC = analogRead(pinNTC);
  Resistenza = (1023 / valore_ADC) - 1;
  Resistenza = resistenza_In_Serie / Resistenza;
  steinhart = Resistenza / resistenza_Nominale_NTC;
  steinhart = log(steinhart);
  steinhart /= coeff_Thermistore;
  steinhart += 1.0 / (temp_Nominale_NTC  + 273.15);
  steinhart = 1.0 / steinhart;
  steinhart -= 273.15;
  temperatura = steinhart;

}


void setup() {

  Ethernet.begin(mac, ip);
  server.begin();

  pinMode(ledpin1, OUTPUT);
  pinMode(ledpin2, OUTPUT);
  pinMode(ledpin3, OUTPUT);
  pinMode(ledpin4, OUTPUT);
  pinMode(ledpin5, OUTPUT);
  pinMode(ledpin6, OUTPUT);
  pinMode(ledpin7, OUTPUT);
  pinMode(pinNTC, INPUT);
}



void Ether()
{
  pass = false;
  EthernetClient client = server.available();

  if (client)
  {
    boolean currentLineIsBlank = true;
    String buffer = "";
    while (client.connected())
    {
      if (client.available())
      {

        char c = client.read();
        buffer += c;
        /*
           Login alla pagina di controllo
        */
        if (buffer.endsWith("_W=1974"))
        {
          pass = true;
        }


        /*
           Segnala lo stato delle uscite sulla pagina web
        */

        if (digitalRead(ledpin1) == HIGH) {
          statoLed1 = "<td bgcolor=\"yellow\"> Uscita 7 : ON </td>";
        } else {
          statoLed1 = "<td bgcolor=\"white\"> Uscita 7 : OFF </td>" ;
        }

        if (digitalRead(ledpin2) == HIGH) {
          statoLed2 =  "<td bgcolor=\"yellow\"> Uscita 6 : ON </td>";
        } else {
          statoLed2 = "<td bgcolor=\"white\"> Uscita 6 : OFF </td>" ;
        }

        if (digitalRead(ledpin3) == HIGH) {
          statoLed3 =  "<td bgcolor=\"yellow\"> Uscita 5 : ON </td>";
        } else {
          statoLed3 = "<td bgcolor=\"white\"> Uscita 5 : OFF </td>" ;
        }


        if (digitalRead(ledpin4) == HIGH) {
          statoLed4 =  "<td bgcolor=\"yellow\"> Uscita A0 : ON </td>";
        } else {
          statoLed4 = "<td bgcolor=\"white\"> Uscita A0 : OFF </td>" ;
        }


        if (digitalRead(ledpin5) == HIGH) {
          statoLed5 =  "<td bgcolor=\"yellow\"> Uscita A1 : ON </td>";
        } else {
          statoLed5 = "<td bgcolor=\"white\"> Uscita A1 : OFF </td>" ;
        }

        if (digitalRead(ledpin6) == HIGH) {
          statoLed6 = "<td bgcolor=\"yellow\"> Uscita A2 : ON </td>";
        } else {
          statoLed6 = "<td bgcolor=\"white\"> Uscita A2 : OFF </td>" ;
        }


        if (digitalRead(ledpin7) == HIGH) {
          statoLed7 =  "<td bgcolor=\"yellow\"> Uscita A3 : ON </td>";
        } else {
          statoLed7 = "<td bgcolor=\"white\"> Uscita A3 : OFF </td>" ;
        }



        /*
            Comando uscite
        */


        if (buffer.indexOf("GET /?s=ON") >= 0)
        {
          pass = true;
          digitalWrite(ledpin1, HIGH);
        }
        if (buffer.indexOf("/?s=OFF") >= 0)
        {
          pass = true;
          digitalWrite(ledpin1, LOW);
        }

        if (buffer.indexOf("/?j=ON") >= 0)
        {
          pass = true;
          digitalWrite(ledpin2, HIGH);
        }
        if (buffer.indexOf("/?j=OFF") >= 0)
        {
          pass = true;
          digitalWrite(ledpin2, LOW);
        }

        if (buffer.indexOf("/?w=ON") >= 0)
        {
          pass = true;
          digitalWrite(ledpin3, HIGH);
        }
        if (buffer.indexOf("/?w=OFF") >= 0)
        {
          pass = true;
          digitalWrite(ledpin3, LOW);
        }

        if (buffer.indexOf("/?x=ON") >= 0)
        {
          pass = true;
          digitalWrite(ledpin4, HIGH);
        }
        if (buffer.indexOf("/?x=OFF") >= 0)
        {
          pass = true;
          digitalWrite(ledpin4, LOW);
        }

        if (buffer.indexOf("/?t=ON") >= 0)
        {
          pass = true;
          digitalWrite(ledpin5, HIGH);
        }
        if (buffer.indexOf("/?t=OFF") >= 0)
        {
          pass = true;
          digitalWrite(ledpin5, LOW);
        }

        if (buffer.indexOf("/?a=ON") >= 0)
        {
          pass = true;
          digitalWrite(ledpin6, HIGH);
        }
        if (buffer.indexOf("/?a=OFF") >= 0)
        {
          pass = true;
          digitalWrite(ledpin6, LOW);
        }

        if (buffer.indexOf("/?z=ON") >= 0)
        {
          pass = true;
          digitalWrite(ledpin7, HIGH);
        }
        if (buffer.indexOf("/?z=OFF") >= 0)
        {
          pass = true;
          digitalWrite(ledpin7, LOW);
        }


        /*
            Pagina visualizzata quando si tenta di raggiungere il modulo Ethernet
            attraverso il proprio indirizzo IP
        */

        if (pass == false && c == '\n' && currentLineIsBlank)
        {
          client.println(F("<!DOCTYPE html>"));
          client.println(F("<html>"));
          client.println(F("<head>"));
          client.println(F("<title>Login Arduino</title>"));
          client.println(F("</head>"));
          client.println(F("<body bgcolor=\"#CCFF66\"> <center>"));
          client.println(F("<h1>Accesso pagina di controllo<p>Arduino ENC28J60<p></h1>"));
          client.println(F("<form>"));
          client.println(F("<input type=\"text\" name=\"pass_W\" style=\"width:300px; height:45px\">"));
          client.println(F("<input type=\"submit\" value=\"Verifica\" style=\"width:100px; height:50px\">"));
          client.println(F("<p><h3>Immettere la password di accesso...</h3>"));
          client.println(F("</form></body></html>"));

          break;
        }

        /*
            Pagina di controllo
        */

        if (pass == true && c == '\n' && currentLineIsBlank)
        {

          client.println(F("HTTP/1.1 200 OK"));
          client.println(F("Content-Type: text/html"));
          client.println();
          client.println(F("<!DOCTYPE html>"));
          client.println(F("<html><meta http-equiv=\"refresh\" content=\"10\"><head>"));
          client.println(F("<style>"));
          client.println(F("div {border: 1px solid gray;padding: 8px;color:blue;font-size: 30px;text-align: center;background-color:whitesmoke;}"));

          client.println(F(".button {width:85%; box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19);background-color:#008CBA; border: 2px solid black;color:white;text-align: center;text-decoration: none;display: inline-block;font-size: 22px;margin: 2px;cursor: pointer; padding:16px 16px;} .button:hover {background-color:red;}"));
          client.println(F("table {font-family: arial, sans-serif;border-collapse: collapse;width:90%;  border-spacing: 5px; }"));
          client.println(F("td, th {border: 1px solid #dddddd;text-align:center;padding: 8px;}"));
          client.println(F("tr:nth-child(even) {background-color: #dddddd;}"));
          client.println(F("</style></head>"));
          client.println(F("<body bgcolor=\"whitesmoke\">"));


          client.println(F("<center><h1>"));
          client.println(F("<p style=\"border: 1px solid gray; color: blue; background: whitesmoke; padding: 20px; \">Arduino - Controllo Remoto con ENC28J60</h1><p></center><b><p><p>"));
          client.println(F("<center>"));
          client.println(F("<FORM>"));
          client.println(F("<table>"));
          client.println(F("<tr><td>"));
          client.println(F("<INPUT type=\"submit\" name=\"s\" value=\"ON\" class=\"button\"> </td>"));
          client.println(F("<td><INPUT type=\"submit\" name=\"s\" value=\"OFF\" class=\"button\"> </td>"));
          client.println(statoLed1);
          client.println(F("</tr><tr><td>"));
          client.println(F("<INPUT type=\"submit\" name=\"j\" value=\"ON\" class=\"button\"></td>"));
          client.println(F("<td><INPUT type=\"submit\" name=\"j\" value=\"OFF\" class=\"button\"></td>"));
          client.println(statoLed2);
          client.println(F("</tr><tr><td>"));
          client.println(F("<INPUT type=\"submit\" name=\"w\" value=\"ON\" class=\"button\"></td>"));
          client.println(F("<td><INPUT type=\"submit\" name=\"w\" value=\"OFF\" class=\"button\"></td>"));
          client.println(statoLed3);
          client.println(F("</tr><tr><td>"));
          client.println(F("<INPUT type=\"submit\" name=\"x\" value=\"ON\" class=\"button\"></td>"));
          client.println(F("<td><INPUT type=\"submit\" name=\"x\" value=\"OFF\" class=\"button\"></td>"));
          client.println(statoLed4);
          client.println(F("</tr><tr><td>"));
          client.println(F("<INPUT type=\"submit\" name=\"t\" value=\"ON\" class=\"button\"></td>"));
          client.println(F("<td><INPUT type=\"submit\" name=\"t\" value=\"OFF\" class=\"button\"></td>"));
          client.println(statoLed5);
          client.println(F("</tr><tr><td>"));
          client.println(F("<INPUT type=\"submit\" name=\"a\" value=\"ON\" class=\"button\"></td>"));
          client.println(F("<td><INPUT type=\"submit\" name=\"a\" value=\"OFF\" class=\"button\"></td>"));
          client.println(statoLed6);
          client.println(F("</tr><tr><td>"));
          client.println(F("<INPUT type=\"submit\" name=\"z\" value=\"ON\" class=\"button\"></td>"));
          client.println(F("<td><INPUT type=\"submit\" name=\"z\" value=\"OFF\" class=\"button\"></td>"));
          client.println(statoLed7);
          client.println(F("</tr></table>"));
          client.println(F("</FORM>"));
          client.println(F("<p>.<p><div> Valore sensore di temperatura su A5:"));
          client.println(temperatura);
          client.println(F(" gradi </div>"));
          client.println(F("</body></html>"));

          break;
        }



        if (c == '\n' || c == '\r') {
          currentLineIsBlank = true;
          buffer = "";

        } else  {
          currentLineIsBlank = false;
        }

      }
    }
    client.stop();
  }
}

void loop() {


  valore_Termistore();
  Ether();
  delay(1);





}

 

 

Una volta collegato il modulo ENC28J60 al router di casa, dal PC o Smartphone apriamo il browser e digitiamo l’indirizzo IP che abbiamo impostato, nel mio caso 192.168.1.103.  Il mini web server in esecuzione su Arduino risponderà prima con una pagina di login, dove sarà richiesta la password di accesso ( da noi impostata ), e successivamente con la pagina web di controllo. Ogni led collegato a ciascuna uscita potrà essere acceso o spento con degli appositi pulsanti ON-OFF, inoltre accanto a questi sarà visualizzata una sezione che ne indicherà  di volta in volta lo stato di attivazione. Il valore della temperatura ricavato dalla resistenza NTC ( attraverso l’apposita funzione valore_Termistore() ) verrà visualizzato invece in basso e tramite la funzione automatica di refresh delle pagina verrà aggiornato ogni 10 secondi. Il video seguente mostra quanto detto.

 

 

 

 

 

Eseguire il Port Forwarding 

Fin qui si è visto che per interagire con Arduino all’interno della nostra rete domestica ( LAN ) è sufficiente digitare l’indirizzo IP del modulo ENC28J60 da noi assegnato nello sketch. Se invece ci troviamo fuori dalla nostra abitazione e vogliamo fare la stessa cosa, ossia accedere anche dall’esterno, dovremo rendere il nostro dispositivo visibile attraverso l’impostazione nel router di una funzione chiamata NAT ( Network Address Translation ), cioè eseguire il cosiddetto port forwarding.  Per impostazione predefinita il router di casa è configurato in modo tale da lasciar passare le connessioni in uscita e bloccare quelle in entrata. E’ intuitivo quindi che per dialogare con il nostro Arduino dovremo far in modo che esso accetti le richieste in arrivo dall’esterno e questo lo si fa attraverso l’apertura di determinate porte e digitando nella barra del browser non più l’indirizzo IP privato ma quello pubblico del nostro router.

 

  • Per chiarire le idee, immaginiamo che nello sketch abbiamo scelto di assegnare al nostro ENC28J60 un indirizzo IP uguale a 192.168.1.100, mentre l’indirizzo IP pubblico del nostro router risulti 149.74.117.150. In pratica se ci troviamo a casa, o comunque all’interno della nostra LAN, per raggiungere il nostro dispositivo basterà digitare nella barra degli indirizzi del browser l’indirizzo 192.168.1.100, mentre se ci troviamo fuori casa, in un’altra città, regione o all’estero, dovremo digitare l’indirizzo 149.74.117.150.

 

I passi da seguire per impostare il NAT sul proprio router possono variare a seconda della marca e del modello ma in linea generale non dovrebbe essere complicato individuare tale voce. Nel mio caso, trattandosi di un TP-LINK ADSL2+, bisogna entrare nelle impostazioni avanzate, cliccare sulla voce NAT, porre la voce corrispondente su “NAT ATTIVO” e tramite un apposito pulsante accedere ad un’altra sezione in cui bisogna attivare la voce DMZ e digitare nell’apposita casella di testo l’indirizzo IP dell’ ENC28J60.

Per conoscere invece l’indirizzo IP pubblico del nostro router, la via più semplice consiste nel collegarsi ad uno dei tanti siti tipo :  www.mio-ip.it  e  www.whatismyip.org

Fatte queste due semplici operazioni, il nostro dispositivo è pronto per essere raggiunto da qualsiasi parte ci troviamo.

 

Test Arduino ENC28J60
Foto del circuito di prova su breadboard usato per il test

 

 

Nota : nella maggior parte delle connessioni domestiche l’indirizzo IP pubblico del router, fornito dall’ ISP ( la società con cui abbiamo fatto il contratto per Internet ) non è fisso ma dinamico, cioè cambia nel caso in cui si dovesse spegnere e riaccendere il router dopo un certo intervallo di tempo, magari a causa di qualche black out, ecc.

 

 

Qui in basso infine è possibile scaricare i file del progetto

Sketch : Arduino_ControlloRemoto_ENC28J60

Schema : Schema Arduino ENC28J60.jpg

Libreria UIPEthernet : arduino_uip