Problema Arduino & ENC28J60

Ciao a tutti! :slightly_smiling_face:

Mi sto imbattendo in un problema che sono sicuro sia possibile risolverlo ma per ora non ne esco :pleading_face:

Dico subito che sto utilizzando Arduino Nano con Atmega328P e gli esempi che ho condiviso funzionano perfettamente senza nessun problema anche se tale micro non è consigliabile per via delle poche risorse di memoria che ha, nel caso si vogliono fare progetti più complessi ma non è quello che voglio.

Quello che desidero fare:

Fare un sistema a rete cablata con protocollo TCP/IP dove ogni Arduino Nano connesso al suo ENC28J60 mi trasmetta e riceva dalla rete ethernet facendo da ponte via seriale USART.

1.Un dispositivo è un Arduino Nano con ENC28J60
2.Ogni dispositivo ha un indirizzo MAC e IP unico.
3.Ogni dispositivo sono tutti connessi ad uno switch di rete.
4.Ogni dispositivo fa da ponte tra la comunicazione seriale UART <> Rete.
5.Un solo dispositivo della rete è un server, gli altri tutti client.

Quello che non riesco a fare:

Semplicemente non riesco ad avere più di un client connessi al server per scambiarsi i dati utilizzando la libreria UIPEthernet (GitHub - UIPEthernet/UIPEthernet: UIPEthernet library for Arduino IDE,Eclipse with arduino plugin and MBED/SMeshStudio (AVR,STM32F,ESP8266,Intel ARC32, Nordic nRF51, Teensy boards,Realtek Ameba(RTL8195A,RTL8710)), ENC28j60 network chip. Compatible with Wiznet W5100 Ethernet library API.).

Ovviamente, prima di provare a fare tale progetto, ho provato prima ad controllare se funzionavano le schede ENC28J60.

Le schede sono queste:

Ho provato a scrivere un codice di esempio per mandare un messaggio al PC tramite IP dal browser e funziona.

Poi ho provato ad creare un server ed un solo client per scambiare dati dalla rete tramite seriale.

Poi, per testare bene le velocità, ho connesso ad entrambi gli Arduino due pulsanti e due led.

Ingressi pulsanti:
GPIO: 3, 4

Uscite led:
GPIO: 5, 6

Il funzionamento è semplice:

Quando viene premuto il pulsante del GPIO 3 di un dispositivo si accende il led del GPIO 5 dell'altro dispositivo.

Se viene rilasciato il pulsante il led si spegne.

Entrambi i dispositivi comandano il led dell'altro dispositivo tramite il loro pulsante, praticamente bidirezionale.

La cosa bella è che funziona perfettamente e le velocità sono perfette per quello che devo fare!!

Allego il codice del Server e del Client della prova funzionante:

Server:

#include <SPI.h>
#include <UIPEthernet.h>

//Inputs
#define Pin_In_1 3
#define Pin_In_2 4

//Outputs
#define Pin_Out_1 5
#define Pin_Out_2 6

byte MAC_Address[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01};
IPAddress ip(192, 168, 1, 5);

EthernetServer server(80);

EthernetClient client;

bool Input, Last_Input;

void setup() {

  delay(10);

  Serial.begin(115200);

  pinMode(Pin_In_1, INPUT);
  pinMode(Pin_In_2, INPUT);

  pinMode(Pin_Out_1, OUTPUT);
  pinMode(Pin_Out_2, OUTPUT);

  delay(50);

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

  Serial.println("Server V7");
  Serial.print("IP: ");
  Serial.println(Ethernet.localIP());

}

void loop() {

  //Connessione
  //---------------------------------------------------------------------------
  if (!client || !client.connected()) {

    client = server.available();

  }

  //Input
  //---------------------------------------------------------------------------
  Input = digitalRead(Pin_In_1);

  if (Input != Last_Input) {

    if (Input == 1) {

      client.print("C1ONx");

    } else {

      client.print("C1OFFx");

    }

    Last_Input = Input;

  }


  //Output
  //---------------------------------------------------------------------------
  if (client && client.connected()) {

    while (client.available() > 0) {

      String Data = client.readStringUntil('x');

      if (Data == "L1ON") {

        digitalWrite(Pin_Out_1, 1);

      } else if (Data == "L1OFF") {

        digitalWrite(Pin_Out_1, 0);

      }

    }

  }

}

Client:

#include <SPI.h>
#include <UIPEthernet.h>

//Inputs
#define Pin_In_1 3
#define Pin_In_2 4

//Outputs
#define Pin_Out_1 5
#define Pin_Out_2 6

byte MAC_Address[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x02};
IPAddress ip(192, 168, 1, 6);

IPAddress serverIP(192, 168, 1, 5);

EthernetClient client;

bool Input, Last_Input;

void setup() {

  delay(10);

  Serial.begin(115200);

  pinMode(Pin_In_1, INPUT);
  pinMode(Pin_In_2, INPUT);

  pinMode(Pin_Out_1, OUTPUT);
  pinMode(Pin_Out_2, OUTPUT);

  delay(50);

  Ethernet.begin(MAC_Address, ip);

  Serial.println("Client A");
  Serial.print("IP: ");
  Serial.println(Ethernet.localIP());

}

void loop() {

  delay(1);

  //Connessione
  //---------------------------------------------------------------------------
  if (!client.connected()) {

    Serial.println("Connessione al Server...");

    if (client.connect(serverIP, 80)) {

      Serial.println("Connesso!");

      delay(100);

      client.print("0x");

    } else {

      Serial.println("Fallito");

      delay(1000);

      return;

    }

  }

  //Input
  //---------------------------------------------------------------------------
  Input = digitalRead(Pin_In_1);

  if (Input != Last_Input) {

    if (Input == 1) {

      //client.write('1');
      client.print("L1ONx");

    } else {

      //client.write('0');
      client.print("L1OFFx");

    }

    Last_Input = Input;

  }

  //Output
  //---------------------------------------------------------------------------
  while (client.available() > 0) {

    String data = client.readStringUntil('x');

    if (data == "C1ON") {

      digitalWrite(Pin_Out_1, 1);

    } else if (data == "C1OFF") {

      digitalWrite(Pin_Out_1, 0);

    }

  }
  
}

Dopo questa prova ho provato sul lato server a creare dei client in array ma succede che quando un client si connette l'altro non funziona.

Non ho delle solide competenze su come funziona bene la libreria UIPEthernet e il chip ENC28J60 per non parlare del protocollo TCP/IP :roll_eyes:

Avevo letto che tale libreria dovrebbe supportare 4 client connessi contemporaneamente.
Ho dato una letta all'esempio "AdvancedChatServer" ma è scritto in una maniera che proprio non capisco :neutral_face:

Ho scoperto anche che il software che ho scritto per il lato server comunica con il client senza specificare il suo indirizzo IP...

Vorrei capire se con la prova funzionante che ho fatto posso espandere il sistema a più client... ovviamente entro i limiti della libreria e memoria degli ATMEGA328P.

PS: ho provato tale programma server e client anche con gli STM32F103C8T6 (Scheda Blue Pill) e funziona molto bene.

Questo è il programma che ho scritto con array di client sul lato server che non funziona...
cioè funziona ma si connette sempre ad un solo client :roll_eyes:

L'ho provato sia con Arduino Nano (senza superare la criticità della memoria) e anche sulla scheda STM32...

#include <SPI.h>
#include <UIPEthernet.h>

//Inputs
#define Pin_In_1 3
#define Pin_In_2 4

//Outputs
#define Pin_Out_1 5
#define Pin_Out_2 6

byte MAC_Address[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01};
IPAddress ip(192, 168, 1, 5);

EthernetServer server(80);

//Clients
#define Num_Clients 4
EthernetClient client[Num_Clients];

bool Input, Last_Input;

void setup() {

  delay(10);

  Serial.begin(115200);

  pinMode(Pin_In_1, INPUT);
  pinMode(Pin_In_2, INPUT);

  pinMode(Pin_Out_1, OUTPUT);
  pinMode(Pin_Out_2, OUTPUT);

  delay(50);

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

  Serial.println("Server STM32 V6");
  Serial.print("IP: ");
  Serial.println(Ethernet.localIP());

}

void loop() {

  //Connessione
  //---------------------------------------------------------------------------

  for (byte i = 0; i < Num_Clients; i++) {

    if (!client[i] || !client[i].connected()) {

      EthernetClient newClient = server.available();

      if (newClient) {

        client[i] = newClient;

        Serial.print("New connection > ");
        Serial.println(i);

      }

    }

  }

  //Input
  //---------------------------------------------------------------------------
  Input = digitalRead(Pin_In_1);

  if (Input != Last_Input) {

    for (byte i = 0; i < Num_Clients; i++) {

      //if (client[i] && client[i].connected()) {

      if (Input == 1) {

        client[i].print("ON1x");

      } else {

        client[i].print("OFF1x");

      }

      //}

    }

    Last_Input = Input;

  }

  //Output
  //---------------------------------------------------------------------------
  for (byte i = 0; i < Num_Clients; i++) {

    if (client[i] && client[i].connected()) {

      while (client[i].available() > 0) {

        String Data = client[i].readStringUntil('x');

        if (Data == "L1ON") {

          digitalWrite(Pin_Out_1, 1);

        } else if (Data == "L1OFF") {

          digitalWrite(Pin_Out_1, 0);

        } else if (Data == "L2ON") {

          digitalWrite(Pin_Out_2, 1);

        } else if (Data == "L2OFF") {

          digitalWrite(Pin_Out_2, 0);

        }

      }

    }

  }

}

Qualsiasi aiuto è apprezzato!!! :heart: :smiling_face_with_tear:

Grazie in anticipo!!!!

Per quello che vuoi fare, ci sono problemi proprio di progettazione della modalità di comunicazione.

In una connessione client/server le richieste arrivano SEMPRE dal client ed il server risponde di conseguenza. Tu invece cerchi di fare anche il contrario quando leggi gli ingressi sia lato server che lato client e questo è concettualmente sbagliato.

Devi cambiare approccio, ad esempio implementando un semplice protocollo master/slave dove il master fa il polling degli slave leggendone lo stato o per inviare dei comandi specifici.

Oppure senza reinventare la ruota, usa il protocollo Modbus TCP adottando una topologia a stella, cosa che io ti consiglierei caldamente.

Ho risolto, ho fatto in maniera tale che quando si crea una connessione non viene più sovrascritta e funziona non bene.... BENISSIMO!!!

Codice:

Master

//Connessione
    //---------------------------------------------------------------------------
    EthernetClient Client_Temp = server.available();

    if (Client_Temp) {

      for (byte i = 0; i < Num_Client; i++) {

        if (!client[i]) {

          if (Client_ID[i] == 0) {

            client[i] = Client_Temp;
            Client_ID[i] = ID_Value;

            Serial.print("ID.");
            Serial.print(i);
            Serial.print(".");

            break;

          }

        }

      }

    }

Allego il programma completo con 3 Arduino connessi ad uno Switch dove uno è server e gli altri 2 client.
V9.zip (4,1 KB)

A parte l’abbondanza di cicli while() che personalmente non vedo mai di buon occhio, ma che possiamo tralasciare se ti stanno bene gli algoritmi bloccanti (e quindi un comportamento poco reattivo), ci sono alcuni errori critici che andrebbero sistemati. Ad esempio nel server:

  • Ciclo bloccante non gestito nella funzione Init_Connection(): il while (Start_Connection == 0) blocca completamente l'esecuzione e se un client non invia "CLAOK" o "CLBOK", il sistema rimane bloccato per sempre (andrebbe aggiunto un timeout o gestione fallback)

  • Mancanza della gestione delle disconnessioni: non c'è codice per rilevare disconnessioni improvvise nel loop principale e quindi il client disconnesso rimane allocato, ma senza possibilità di riconnessione.

  • Logica errata nella verifica connessioni: il ciclo che controlla Start_Connection si interrompe al primo client non connesso. Se Client_ID[0] è diverso da ID_Value + 1, esce immediatamente senza controllare gli altri.

  • Protocollo di comunicazione troppo semplice e nessuna validazione dei dati ricevuti. C’è un problema di sicurezza: questo è un aspetto che tendiamo sempre a sottovalutare quando implementiamo questi progettini “casalinghi”, ma purtroppo dispositivi “IoT” insicuri come in questo caso sono una porta spalancata per eventuali malintenzionati che per qualsiasi ragione dovessero avere accesso ala tua rete.

Problemi analoghi ci sono anche lato client.

Ciò che mi chiedo io in questi casi però è sempre la stessa cosa… perché tutta questa voglia di reinventare la ruota?

L'esempio proposto è una base sui cui partire, non dev'essere preso come soluzione finale sicura, quindi:

  1. Troppi cicli while:

Gli ho messi apposta per creare degli stati bloccanti, precisamente nel setup, quindi all'avvio del sistema.

Nel loop principale, sia lato server che lato client, c'è ne solo uno che è la prassi per cercare di forzare la lettura di dati dalla rete per evitare overflow del buffer (almeno da quanto ne so e da consiglio di gente più esperta di me).

2.Ciclo bloccante:

Questo non è un errore:
il sistema deve bloccarsi se non riceve i messaggi "CLAOK" e "CLBOK" e il loop continua finché non avviene la connessione dei 2 client che a loro volta rispondono.
Quindi deve partire solo se tutti i client sono connessi.
Ovviamente è scontato che devo aggiungere una specie di segnalazione di mancata connessione, ma come ho detto è solo una base su cui partire.

3.Controllo della connessione:

Anche questo è voluto, il sistema vuole che la connessione, se cade, bisogna resettare tutto!
E' scontato che dovrò implementare una gestione di errore in caso di disconnessione accidentale di un client.

4.Logica errata:

Il sistema non ha una logica errata:
se all'avvio un client è fisicamente sconnesso (cavo ethernet scollegato) il sistema si blocca finché non si ricollega il cavo.
A sua volta il client è bloccato in un loop e tenta la connessione e se avviene tutto parte, ma solo all'avvio.

5.Protocollo troppo semplice:

Questo, come ho detto, è una base funzionante sui cui partire e non dev'essere presa come soluzione finale per progetti più complessi.

In conclusione:

Mi permetto di dire che non è molto corretto dare per scontato che sono progettini casalinghi e quindi per tale uso :roll_eyes: e pensare che se funziona è una soluzione da usare così com'è... :upside_down_face:
...oltre al fatto che percepire illogico un programma senza sapere effettivamente come l'ho pensato e perché l'ho voluto fare così dandolo per certo sbagliato non è il massimo :neutral_face:

Infatti, non ho mai spiegato il motivo della mia ricerca e cosa ci devo fare, quindi andare a percezione anche no...
come il fatto che mi parli di un sistema "IoT" quando nel mio caso è un sistema chiuso non collegato alla rete internet, quindi ISOLATO DAL MONDO e i malintenzionati non avranno MAI ACCESSO al sistema.

PS: potrebbe essere un sistema Local IoT, on-premise o intranet ma comunque l'accesso al sistema è impenetrabile stando a casa propria e con una rete internet e un pc per come l'hai inteso tu...

Il dubbio che hai sul fatto che voglio reinventare la ruota è perché è un progetto di ricerca per l'università al quanto complesso, e sono costretto a fare le cose così come da consegna e in più capire per poi esporre tutti i passi...

il trovare una cosa "già fatta" non mi và bene...
soprattutto all'università dove ti richiedono di sapere tutto...

Quindi la mia non era trovare una qualsiasi soluzione come l'utilizzo del protocollo Modbus TCP che è già fatto e che uso da anni nel mio lavoro... ma usare la libreria UIPEthernet e i chip ENC28J60 per fare una cosa simile... quasi...

Ovviamente farò un sistema più sicuro e robusto con tutto quello che serve.

Mi scuso in anticipo di eventuali fraintendimenti e per essere stato sincero nel dire come ho percepito a primo impatto le risposte ricevute.

E anche per la mancanza di ulteriori informazioni sul motivo del progetto e per come lo volevo risolvere.

Comunque grazie del tuo utilissimo aiuto, saluti :waving_hand:

Sinceramente non ho capito il tono un po’ stizzito della tua risposta.

Il software che hai postato è chiaramente in una fase embrionale. Sei arrivato qui chiedendo un parere e ti ho dato alcuni suggerimenti basati sul mio modo di progettare software, proprio nell’ottica di aiutarti a farlo crescere. Se li hai percepiti come denigratori mi dispiace, non era quella l’intenzione.

I consigli possono essere accettati oppure no: spesso servono solo ad offrire un’angolazione diversa su un problema, anche se poi si decide di procedere in un altro modo.

Detto questo, ribadisco l’aspetto tecnico che ho cercato di evidenziare: un algoritmo che va in blocco indefinito a causa di una condizione non prevista o di un errore non gestito non può essere considerato buono, indipendentemente dal fatto che questo sia voluto o meno.

Non è una questione di opinioni: è un principio fondamentale di progettazione. Se qualcuno ti ha detto che questa è la prassi, probabilmente ti ha dato una versione un po’ troppo semplificata della realtà — e a mio avviso non adeguata per un lavoro di livello universitario.

Per quanto riguarda il termine “casalingo”: non volendo ipotizzare troppo su obiettivi e contesto, ho usato un’espressione che ritenevo neutra per indicare che, senza maggiori informazioni, è difficile capire la finalità del progetto. L’idea era quella di inquadrare il livello attuale di maturità del codice, non certo giudicare il tuo lavoro.

Mi riferivo anche a un discorso più generale che vale per tutti. Per dire, io stesso ho iniziato a dare il giusto peso a questo aspetto soltanto negli ultimi tempi, imparando a farlo come metodo operativo indipendentemente dalla destinazione finale del progetto.

In ogni caso, al di là dei fraintendimenti, spero che da questo scambio tu possa comunque ricavare qualcosa di utile.

In bocca al lupo per i tuoi studi.

Perdonami, io non voglio accendere inutili flame, ma dove leggi che ho scritto che non si può fare?

Io ho scritto (e lo ribadisco) che, in un modello client/server “tradizionale” come quello che hai implementato, è concettualmente sbagliato.

concettualmente sbagliato != non si può fare

Mi ripeto, nel modello in questione il client è sempre quello che avvia la comunicazione e il server risponde. Il server attende richieste, non le genera. Farlo agire al contrario rompe il paradigma alla base del modello stesso: chi agisce “al contrario” diventa di fatto un client.

E non si tratta di opinioni “da esperti di forum”, ma di concetti teorici ben descritti nei libri di riferimento accademico dove si parla di reti o nei documenti che descrivono le specifiche tecniche dei protocolli di comunicazione. Giusto un paio di citazioni da testi di cui si trova liberamente il pdf online:

“Request-reply is commonly used to implement communication in the client-server model: the client issues a request and the server responds to it.”

Andrew S. Tanenbaum - “Computer Networks”

“A common pattern of communication used by application programs structured as a client/server pair is the request/reply message transaction: a client sends a request message to a server, and the server responds with a reply message…”

Larry L. Peterson, Bruce S. Davie - “Computer Networks: A Systems Approach”

Anche il Modbus è uno dei tanti protocolli disponibili che adotta proprio questo schema dove però i ruoli sono ben definiti: il master è il client che invia delle richieste a più slave-server che rispondono. Siccome è tra i più semplici da implementare, ampiamente diffuso e conosciuto, da li il suggerimento di usarlo.

Io ti ho suggerito di usare un approccio più rigoroso e formalmente corretto facendo riferimento a standard esistenti, ma se la tua implementazione ibrida rispetta il modello “basta che funziona” e adotta il protocollo di comunicazione “a modo mio” e ti sta bene, che devo dire… buon per te!

{7A82DDD4-29B5-4440-BC15-6F4B35C04A07}

Se cancelli i post sembro uno scemo che se la suona e se la canta… :face_with_bags_under_eyes:

Vabbè, chiudo qui.

A me è stata data una consegna dagli insegnanti dove il server deve mandare dati al client e viceversa ed essendo una ricerca probabilmente è un qualcosa di nuovo che nessuno sa BHO...

E comunque si vogliono che reinvento la ruota facendo un protocollo personale... come dici tu "a modo mio"...

Cioè credo di averlo intuito perché mi hanno detto che di non applicare protocolli software già esistenti 乁⁠(⁠ ⁠⁰͡⁠ ⁠Ĺ̯⁠ ⁠⁰͡⁠ ⁠)⁠ ⁠ㄏ

Siamo più di 30 persone che ci lavoriamo e a me è capitato l'argomento più sbagliato uffa...

Poi essendo una materia trasversale la sto metabolizzando ora e probabilmente in futuro capirò cosa è effettivamente giusto e sbagliato.

Non posso negare la corretta via dei testi che mi hai citato...

li conosco ma stranamente mi hanno detto: NON LEGGERLI SENNÒ VAI IN CONFUSIONE ಠ⁠︵⁠ಠ

Scusa ho eliminato perché non mi sembrava corretto polemizzare su tutto...

È un periodo delicato tra esami e sto progetto divento pazzo ಠ⁠‿⁠ಠ

Grazie della comprensione.