Giu 222014
 

Come spesso accade in questo blog, dopo tutta una serie di articoli su uno specifico argomento, vi propongo un progettino un po’ più articolato per integrare le nozioni precedentemente acquisite. L’idea è questa: utilizziamo due schede Arduino:  ad una delle due (che chiamerò ETH)  colleghiamo una shield ethernet per interagire con la rete locale e l’altra (DSP) la dotiamo di display LCD ed un sensore ds128b20. Tutte e due le colleghiamo poi ad un modulo nRF24L01 per far comunicare i due Arduino a distanza. In un prossimo futuro questo progetto servirà da base per qualcosa di più complesso, ma ci arriveremo per gradi.

Arduino – Display LCD – nrf24l01 e DS18B20.

Vediamo per prima la scheda DSP. La prima cosa da fare è conteggiare quanti pin  sono necessari per collegare tutte le periferiche:

  • 6pin per il display,
  • 1per il sensore
  • 5 per il modulo nRF24,
  • 2 per la comunicazione seriale.

In tutto sono 14 per cui  abbiamo sufficienti risorse per riuscirci. In alternativa avremmo dovuto usare uno shift register sul display per liberare alcuni pin o usare le porte analogiche.  La seconda cosa a cui dobbiamo pensare è se qualcuna delle periferiche da collegare ha bisogno di utilizzare alcuni pin specifici, non modificabili. E’ il caso del modulo nRF24 che utilizzando l’interfaccia SPI va forzatamente ad utilizzare almeno i pin 11,12 e 13 (mi riferisco ad Arduino UNO). Il display ed il sensore invece possono utilizzare qualunque altro pin digitale, ma attenzione. Se ad esempio come libreria usaste la LiquidCrystal modificata per utilizzare display anche con shift register e comunicazione SPI, sareste obbligati ad utilizzare gli stessi pin del bus SPI del modulo nRF24. In questo caso avreste un conflitto? No se il display è realmente di tipo SPI. Non sarebbe assolutamente un problema, infatti se si trattasse di un display SPI, questo avrebbe un suo pin SS come lo ha il modulo nRF24, e solo il modulo di volta in volta attivo riceverà i necessari comandi mentre l’altro resterà in attesa del suo turno. Se però una delle periferiche non è di tipo SPI allora non possiamo condividere i pin perchè ci sarebbe un reale conflitto. In questo caso la periferica non conoscerebbe il significato di “pin SS” per cui i dati che giungono sugli altri pin per lei sarebbero da interpretare. Tutto questo intricato discorso voleva mettere in risalto lo stretto rapporto che c’è fra software ed hardware e le relative implicazioni pratiche. Spero che il concetto sia chiaro, mi rendo conto che non sia poi così scontato.

Ma torniamo a noi. Per il modulo nRF24 andiamo ad occupare i pin dal 9 al 13 (rispetto al precedente articolo il 9 passa sul 10 ed il 7 sul 9), per il display usiamo quelli dal 3 all’ 8 ed il 2 lo usiamo per il DS18b20. Restano liberi lo 0 e 1 per la porta seriale e non dobbiamo nemmeno usare i pin analogici. Vediamo ora lo schema di collegamento, si noti il connettore di alimentazione aggiuntivo che ho usato per semplificare il disegno, ma nella pratica andremo ad usare la massa ed il pin 5V di Arduino. Si noti che lo schema del modulo NRF24 non ha la stessa disposizione di quanto visto nel precedente articolo in quanto il software usato per il disegno non ha quel tipo di modulo, comunque facendo riferimento al precedente articolo i pin 13,12 ed 11 restano invariati in quanto sono quelli dell’interfaccia SPI, vanno modificati solo il pin 9 che passa sul 10 ed il 7 che passa sul 9, nulla di più.A-lcd-ds18b20

Lo schema non è niente di nuovo, abbiamo già affrontato tutti gli aspetti nei relativi articoli, oggi abbiamo solo modificato i pin di collegamento che si rifletteranno sul software ma non c’è nulla di diverso. Se non siete assidui frequentatori di questo blog o comunque volete approfondire i singoli argomenti,, potete trovare tutto il necessario in questi articoli:

  1. Display LCD  
  2. nRF24L01
  3. Sensore DS18B20 e Shield Ethernet

Arduino – Shield Ethernet e nrf24l01.

La scheda ETH ha dei risvolti interessanti per il suo collegamento. Infatti abbiamo detto che andiamo ad usare l’Ethernet Shield ma, per chi non lo sapesse, questa utilizza il bus SPI per comunicare con Arduino. Se avete letto con attenzione l’articolo introduttivo sul bus SPI ricorderete che tutte le schede Arduino hanno diversi pin per la gestione hardware dell’SPI, però tutte hanno un connettore ICSP che riporta i diversi segnali e che è identico in tutte le schede e guarda caso l’ethernet shied utilizza proprio questo connettore per la comunicazione SPI. Ovviamente i pin dell’SPI saranno condivisi sia dalla shield Ethernet, che a sua volta lo condivide con il lettore SD, che dal modulo nRF24. Ogni periferica userà poi un pin di selezione (SS) che permetterà di decidere di volta in volta quale dispositivo vogliamo utilizzare da parte di Arduino. Più nello specifico il lettore SD utilizza il pin 4 come SS mentre il controller della scheda ethernet utilizza il pin 10. In definitiva sul bus SPI transitano i dati, ma se è attivo il pin 4 saranno letti dal lettore SD, se invece è attivo il 10 saranno letti dalla scheda ethernet.

Va da se che non possiamo usare il pin 10 per il modulo nrf24l01 per cui opteremo per il 9 e, a scalare, andremo ad usare il numero 8 (CE). Non ci resta che collegare il sensore ds18b20 al pin 7 e siamo pronti per l’avventura di quest’oggi. Nota: Nello sketch finale non ho preso in considerazione questo sensore, avrebbe complicato eccessivamente il codice per cui alla fine ho deciso di ignorarlo.

Qui sotto lo schema di collegamento:

eth-rf24-ds18b20Come nello schema sopra vi faccio notare che il modulo RF non è identico a quello in nostro possesso, purtroppo nel programma usato per il disegno non ne avevo uno con il connettore su due file, ma come già detto non cambia assolutamente nulla. Nella foto qui di seguito vediamo come dovrebbero presentarsi le due schede:

 IMGP0003Software 

Di fatto nel progetto odierno la parte più complessa non è certo quella hardware che abbiamo già ampiamente trattato, ma quella software. Il nostro sketch dovrà usare il sensore DS18B20 per misurare la temperatura, e fin qui nulla di nuovo, ma in secondo luogo deve essere in grado di inviare la lettura alla scheda remota. Ci sono due vie possibili per realizzare questo concetto. O creiamo un vero e proprio protocollo di comunicazione, oppure ci limitiamo ad inviare “informazioni fisse”.

Vediamo prima questo secondo metodo: Se ricordate questo percorso  l’abbiamo già visto studiando i moduli nRF24L01, infatti avevamo creato un esempio in cui una scheda inviava i propri millisecondi e l’altra rispondeva con i suoi. Potremmo fare la stessa identica cosa con la temperatura, una invia la propria e l’altra risponde con lo stesso dato, così è possibile fare anche un controllo su eventuali errori di trasmissione, giusto per fare un esempio.

Il primo metodo è invece più complesso ma molto più flessibile, infatti ci permette di trasportare diverse tipologie di dati. Ad esempio la scheda ETH potrebbe  inviare alla DSP  l’ora prelevata da internet, oppure potrebbe comunicare altri tipi di dati da visualizzare sul display o viceversa la DSP potrebbe comunciare altri dati alla ETH per distribuirli su internet, ad esempio dati di umidità se aggiungiamo il relativo sensore.

Ho scelto ovviamente di seguire questa seconda strada che però ci apre un’infinità di percorsi sulla creazione del protocollo di trasmissione. Per non appesantire troppo l’articolo ci limiteremo ad un protocollo molto ma molto semplice, in questo modo non ci discostiamo troppo dai precedenti esempi e sarà più facile la comprensione del funzionamento.

Più nel particolare ho pensato ad un protocollo composto da soli cinque bytes, il primo per indicare un “comando”, gli altri quattro per trasportare il dato vero e proprio. In questo modo possiamo inviare l’orario, la temperatura, codici d’errore e quanto più ci aggrada anche se in modo limitato dai soli 5 bytes.

Comunicazione e listati

Una volta deciso il protocollo dobbiamo decidere quali sono i comandi utilizzabili. Attualmente ho previsto i seguenti comandi:

  • T: Con T la scheda DSP invia la temperatura letta alla scheda ETH
  • H: La scheda ETH invia l’ora alla DSP in modo che possa scriverla sul Display.
  • E: Ognuna delle schede può segnalare all’altra un codice d’errore.
  • R: Comando inviato dopo il reset della scheda: permetta la sincronizzazione con la scheda remota dopo un riavvio.

Ho scelto di restringere il protocollo a soli quattro comandi in modo da mantenere i listati abbastanza semplici, senza eccessi. Ora andiamo ad analizzare i listati. Essendo relativamente lunghi, diventava piuttosto complesso mettere prima i listati e poi la loro descrizione, per cui ho optato per l’inserimento di commenti nei listati stessi anche se così si perde un po’ la linearità della programmazione. Faccio notare che buona parte dei listati sono ripresi dai vecchi articoli per cui, anche se rivisitati e corretti, le novità sono poche e sostanzialmente interessano solo la comunicazione radio fra le due schede. Vi ricordo che per la gestione dell’ora prelevata da internet, viene usata la versione più aggiornata della libreria Time che potete scaricare a questo indirizzo se non l’avete già disponibile.

DSP

// facciamo le necessarie inclusioni
#include <OneWire.h>
#include <LiquidCrystal.h>
#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>
#include <Time.h>

// alcuni semplici #define
#define N_SENSORI 5
#define ERR_GET_DATE_TIME 1

//...e definiamo le variabili globali
const long timeZoneOffset = 7200L;  
LiquidCrystal lcd(8, 7, 6, 5, 4, 3);
byte G_addr[N_SENSORI][8]; //qui memorizzo fino a N indirizzi
byte G_Devices; // variabile usata per tenere traccia del numero di sensori riconosciuti nel bus
OneWire ow(2); // inizializza il bus onewire sulla porta n°2 

float temperatura=1000; // definisce una temperatura 1000 come default (a cui diamo significato di misura non effettuata)
unsigned long tempo;    // usata per contare il tempo trascorso....
unsigned long errtime;  // tiene traccia del tempo passato dall'ultimo errore....
char ciclo=-1;

// i prototipi delle funzioni (non indispensabili)
void lookUpSensors();
float GetTemp(byte *);
void PrintAddress(byte *);
int CheckSensor(byte *);
void SendRF24Error(unsigned char);
void PrintTime();


// struct ed union usate per lo scambio di dati su nrf24
union DataMix
{
 char datachar[4];
 int dataint[2];
 float datafloat;
 unsigned long datalong;
} ;


struct McMajanData
{
  char Command;
  union DataMix Data;
} ;  


union dataRF24
{
 char databuffer[5];
 struct McMajanData IO;
};

dataRF24 IORF24;


//si comincia con le inizializzazioni
void setup(void)
{
  Serial.begin(9600);// inizializza la porta seriale a 9600
  Mirf.cePin = 9; //SS
  Mirf.csnPin = 10; // CSN
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  Mirf.setRADDR((byte *)"c0001"); //nome del client
  Mirf.payload = 5; // usiamo comandi composti da 5 bytes
  Mirf.channel =90; // canale numero 90
  Mirf.config();
  Mirf.configRegister(RF_SETUP,0x26); //250k
  //Mirf.configRegister(RF_SETUP,0x00); //1mb
  //Mirf.configRegister(RF_SETUP,0x0E); //2mb
  

  lcd.begin(16, 2); // inizializzo il display a 16x2 caratteri
  // cerco i sensori connessi alla schede....
  G_Devices=0;      // imposta a 0 il numero di sensori attualmente riconosciuti
  lookUpSensors(); // avvia la ricerca delle sonde di temperatura
  // intanto mostro un messaggio di benvenuto....
  lcd.setCursor(0, 0);
  lcd.print("www.mcmajan.com");
  lcd.setCursor(0, 1);
  lcd.print(" Stefano Smania");
  // ... per 3 secondi....
  delay(3000);
  lcd.clear(); // cancello il display
  lcd.setCursor(6, 0);
  lcd.print("-"); 
  tempo=errtime=0;

  //...e richiedo la sincronizzazione dell'ora se disponibile....
  IORF24.IO.Command='R';
  Mirf.setTADDR((byte *)"s0001");
  Mirf.send((byte *)IORF24.databuffer);
  while(Mirf.isSending()){}
}

void lookUpSensors() // ricerca dei sensory 1 wire.
{
  byte address[8]; // questo array conterrà l'indirizzo locale dei sensori

  Serial.print("--Ricerca avviata--"); // avvia la ricerca e lo scrive sulla porta seriale

  while (ow.search(address)) // loop finchè trova nuovi dispositivi
  {
   // Se il primo byte dell'indirizzo è 0x10, si tratta di una sonda DS18S20
   // il primo byte dell'indirizzo identifica il tipo di sensore
   // se  0x10 è un DS18S20, se è 0x28 è un DS18B20 (notare la S e B)
   if (address[0] == 0x10 || address[0] == 0x28) 
   {
     if(CheckSensor(address)==1) //crc ok
     {
        Serial.println("");
        if (address[0] == 0x10) Serial.print("Found DS18S20 : "); // notare che la S invece che la B
        else if (address[0] == 0x28) Serial.print("Found DS18B20 : ");
        PrintAddress(address);       
        for(int aa=0;aa<8;aa++) G_addr[G_Devices][aa]=address[aa]; // copia l'indirizzo
        G_Devices++; // incrementa il numero di devices memorizzati
        if(G_Devices==N_SENSORI+1) break; // impediamo sforamenti...
     }
    }

  }//end while
  Serial.println("");
  Serial.println("--Ricerca terminata--");
}

void loop(void)
{
   char * data;  
  // riceve comunicazioni ia nrf24
  if(!Mirf.isSending() && Mirf.dataReady())
  {
    Serial.println("Ricezione dati....");
    
    Mirf.getData((uint8_t *)IORF24.databuffer);
    if(IORF24.IO.Command=='H' || IORF24.IO.Command=='R') // ho ricevuto l'ora dalla scheda remota
    {
     Serial.println("Sincronizzazione orario....");
     
     setTime((unsigned long)(IORF24.IO.Data.datalong)); // impostiamo...
     tempo=errtime=millis();
     PrintTime();
     
    }
    if(IORF24.IO.Command=='E') // ho ricevuto un codice d'errore
    {
     Serial.println("Ricevuto codice d'errore : ");
     if(IORF24.databuffer[1]==1) Serial.println("Errore sincronizzazione orari ");
     else Serial.print(IORF24.databuffer[0],HEX); // utilizzo solamente il primo byte con codice errore
     lcd.setCursor(6, 0);
     lcd.print("E");  // in caso di errore scrivo E sul dispaly  
    }
    
  }
  
  if(errtime+300000<millis()) // ogni 5 minuti rimuove i messaggi d'errore dal display
  {
    errtime=millis();
    lcd.setCursor(6, 0);
    lcd.print("-");       
  }
  if(tempo+30000<millis()) // ripete il ciclo ogni 30 secondi
  {
  tempo=millis();
  for(int num=0;num<G_Devices;num++) // vado a leggere tutti i sensori registrati
  {
    temperatura=GetTemp(&G_addr[num][0]); // lego la temperatura
    if(num<2) // i primi due sensori li scrivo anche sul display
    {
     lcd.setCursor(0, num);
     lcd.print(temperatura);
      
    }
    PrintTime();
    PrintAddress(G_addr[num]);
    Serial.print(" -> ");
    Serial.println(temperatura);
  }
  
  
  if(ciclo==9 || ciclo==-1) // ogni 10 cicli invio la temperatura sulla scheda remota.
  {
    ciclo=-1; 
    
    IORF24.IO.Data.datafloat=temperatura;
    IORF24.IO.Command='T';
    Serial.println("Invio sincronizzazione temperatura");
    Mirf.setTADDR((byte *)"s0001");
    Mirf.send((byte *)IORF24.databuffer);
    while(Mirf.isSending()){}
     
  }
  ciclo++;
    } // millis

}

float GetTemp(byte * addr) // leggo le temperature
{
  byte present = 0;
  byte data[12];
  int i;
  byte address[8];
  for(i=0;i<8;i++) address[i]=*(addr+i); //copia l'indirizzo nella stringa locale
  ow.reset();
  ow.select(addr);
  ow.write(0x44,1);         // start conversion, with parasite power on at the end
  delay(1000);     // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.

  present = ow.reset();

  ow.select(addr);
  ow.write(0xBE);         // Read Scratchpad

  for ( i = 0; i < 9; i++) data[i] = ow.read();// we need 9 bytes //Serial.print(data[i], HEX);

  int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;

  double result;

  LowByte = data[0];
  HighByte = data[1];

  TReading = (HighByte << 8) + LowByte;
  SignBit = TReading & 0x8000;  // test most sig bit

  if (SignBit) TReading = (TReading ^ 0xffff) + 1; // 2's comp // negative

  Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25

  Whole = Tc_100 / 100;  // separate off the whole and fractional portions
  Fract = Tc_100 % 100;

  result = Whole;
  result += ((double)Fract/100);

  if(SignBit) result *= -1;


return result;

}

void PrintAddress(byte * address)
{
  // scrive l'indirizzo esadecimale dei sensori
  int i;
  for (i=0;i<8;i++)
  {
    if (address[i] < 9) Serial.print("0");
    Serial.print(address[i],HEX);
    if (i<7) Serial.print("-");
    }
}

int CheckSensor(byte * address)
{
  if (OneWire::crc8(address, 7) != *(address+7)) return(-1);// faccio il controllo del CRC8, se fallito ritorno -1
  else return(1); // cr8 OK, ritorno 1
} 

void SendRF24Error(unsigned char errorcode)
{
  // invio errore su nrf24  
  IORF24.IO.Command='E';
    IORF24.IO.Data.datachar[0]=errorcode;
     Mirf.setTADDR((byte *)"s0001");
     Mirf.send((byte *)IORF24.databuffer);
     while(Mirf.isSending()){}
}
    
void PrintTime() 
{
  // stampa l'ora sia sul display che via seriale.
  time_t t = now()+timeZoneOffset;
     String str;
     str=String(day(t))+"/";
     str=str+String(month(t))+"/";
     str=str+String(year(t));
     Serial.println(str);
     lcd.setCursor(6, 1);
     lcd.print("           ");
     lcd.setCursor(6, 1);
     lcd.print(str);
     
     
     str=String(hour(t))+":"+String(minute(t))+" ";
     Serial.println(str);
     
     lcd.setCursor(11, 0);
     lcd.print("     ");  
     
     lcd.setCursor(11, 0);
     lcd.print(str);    
     Serial.println(str);  
 
}

ETH

//una sola definizione....
#define NTP_PACKET_SIZE 48 
//faccio le necessarie inclusioni
#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>
#include <Ethernet.h>
#include <Time.h>

//prototipi delle funzioni (non sono indispensabili)
void SendTime24();
void SendWebTemp();
void SendRF24Error(unsigned char);
int getTimeAndDate();
unsigned long sendNTPpacket(IPAddress*);
void printDigits(int);

// definizione delle variabili usate

float temps[5]; // memorizzo i valori di temperatura
IPAddress ip(192,168,1,25); // indirizzo ip usato sulla LAN locale
byte remote[]={192,254,224,62}; // indirizzo del server remoto a cui inviare i dati di temperatura
byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x46, 0xE6 }; // indirizzo MAC
IPAddress timeServer(193,204,114,232); // Server NTP - 37,247,48,64 (vecchio)
const long timeZoneOffset = 7200L;  
EthernetUDP Udp; 
byte packetBuffer[NTP_PACKET_SIZE];  
unsigned long ntpLastUpdate = 0;  
char RX;
unsigned long tempo;
char buffer[256];

bool neterror; // se true indica la presenza di un errore di sincronizzazione oraria

EthernetServer ArduinoServer(80); 
EthernetClient ArduinoClient;

// struct ed union usate per lo scambio di dati su nrf24
union DataMix
{
 char datachar[4];
 int dataint[2];
 float datafloat;
 unsigned long datalong;
} ;


struct McMajanData
{
  char Command;
  union DataMix Data;
} ;  


union dataRF24
{
 char databuffer[5];
 struct McMajanData IO;
};

dataRF24 IORF24;

void setup(void)
{
  //faccio tutte le inizializzazioni del caso
  tempo=0;
  temps[0]=1000; // imposto le letture termiche a 1000 gradi
  temps[1]=1000; // per indicare che non sono ancore
  temps[2]=1000; // state lette
  temps[3]=1000;
  temps[4]=1000;
  
  Serial.begin(9600);// inizializza la porta seriale a 9600
   
  Mirf.cePin = 8; // il pin ce dell'nrf24 è collegato al pin 8 di Arduino
  Mirf.csnPin = 9; // csn su pin 9
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  Mirf.setRADDR((byte *)"s0001"); // nome della scheda server (ETH)
  Mirf.payload = 5; // usiamo comandi composti da 5 bytes
  Mirf.channel = 90; // canale numero 90
  Mirf.config();
  Mirf.configRegister(RF_SETUP,0x26); //250k 
  //Mirf.configRegister(RF_SETUP,0x06); //1mb
  //Mirf.configRegister(RF_SETUP,0x0E); //2mb
  
  
  Ethernet.begin(mac,ip); // inizializzo la ethernet
  Serial.println(Ethernet.localIP());
  ArduinoServer.begin(); 

  
  int trys; // per prima cosa faccio 10 tentativi di sincronizzazione oraria
  for(trys=0;trys<10;trys++) 
  {
   Serial.println("Try to sync... ");
   if(getTimeAndDate()) break; 
   Serial.println("Sync error... ");
   delay(1000);
 }
 
   IORF24.IO.Command='R'; // preparo l'invio alla scheda remota un messaggio di reset.
   Mirf.setTADDR((byte *)"c0001"); // setto il nome del client...
   Mirf.send((byte *)IORF24.databuffer); //...invio il comando
   while(Mirf.isSending()){} // attendo di completare l'invio....


}

int getTimeAndDate() // ottengo l'ora dal server NTP
 {
   neterror=1; 
   Udp.begin(8888); // localport
   sendNTPpacket(timeServer);
   delay(1000);
   if (Udp.parsePacket())
   {
     Udp.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer
     unsigned long highWord, lowWord, epoch;
     highWord = word(packetBuffer[40], packetBuffer[41]);
     lowWord = word(packetBuffer[42], packetBuffer[43]);  
     epoch = highWord << 16 | lowWord;
     epoch = epoch - 2208988800UL ;//+ timeZoneOffset;
     setTime(epoch);
     ntpLastUpdate = now();
     tempo=now();
     neterror=0;
     
     Serial.println("Time sync OK");
  
     SendTime24(); // se si sincronizza invia l'ora in remoto...
     
     
   }
   return !(neterror); // occhio al not!!
}
 
// Do not alter this function, it is used by the system
unsigned long sendNTPpacket(IPAddress& address)
{
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;
  packetBuffer[1] = 0;
  packetBuffer[2] = 6;
  packetBuffer[3] = 0xEC;
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;                  
  Udp.beginPacket(address, 123);
  Udp.write(packetBuffer,NTP_PACKET_SIZE);
  Udp.endPacket();
}


void printDigits(int digits) // stampa numeri a due cifre con due numeri
{
  if(digits < 10) Serial.print('0');
  Serial.print(digits);
}



void loop(void) // loop principale....
{
  if(!Mirf.isSending() && Mirf.dataReady()) // controllo se ho ricevuto dati rf24
  {
    Serial.println("Ricevuti dati...");
    Mirf.getData((uint8_t *)IORF24.databuffer);
    if(IORF24.IO.Command=='T') // lettura termica remota
    {
      Serial.println("ok ricezione temperatura: ");
      for(int i=4;i>0;i--) temps[i]=temps[i-1];
      temps[0]=(float)(IORF24.IO.Data.datafloat); // memorizzo la nuova temperatura
       
      Serial.print(float(IORF24.IO.Data.datafloat)); //... la stampo su seriale
      
      SendWebTemp(); //...e la invio su server remoto...
            
    }
    else if(IORF24.IO.Command=='R') SendTime24(); // se reset remoto invio l'ora
    else // comando non riconosciuto 
    {
    Serial.println("Ricevuto comando malformato.... ");
    Serial.print(IORF24.databuffer[0],HEX); // il codice del comando ricevuto in esedecimale
    SendRF24Error(2); // comando malformato
    }
  } // lettura dati RF24
  
  if(now()>tempo+3600 || neterror==1)  // sincronizza orario in caso di errore o ogni ora
  {
    if(!getTimeAndDate()) {Serial.println("Error sync");SendRF24Error(1);}
    else
    {
     // se la sincronizzazione va a buon fine, scrivo l'ora su seriale....
     
     time_t t = now()+timeZoneOffset;
     Serial.print("Time: ");
     Serial.print(hour(t));
     Serial.print(":");
     printDigits(minute(t));
     Serial.print(":");
     printDigits(second(t));
     String str;
     str=" - "+String(day(t))+"/";
     str=str+String(month(t))+"/";
     str=str+String(year(t));
     //str=str+" "+String(hour(t))+":"+String(minute(t))+" ";
     Serial.println(str);
     tempo=now(); // aggiorno la variabile "tempo" per verificare il passaggio della prossima ora....

   } //else
  
  }
 
// questo sotto lo fa sempre....deve rispondere subito ad un pc che si connette dalla rete locale.
// se ti colleghi in remoto Arduino ti fornisce i valori delle ultime 5 letture termiche.
  EthernetClient pc_client = ArduinoServer.available();
  if (pc_client) 
  {
    while(pc_client.connected())
    {
      if (pc_client.available())
      {
        RX = pc_client.read();
        Serial.write(RX);
        if (RX == '\n') //Richiesta terminata - Invio la risposta al client
        {
          pc_client.println("HTTP/1.1 200 OK"); //invio lo status code      
          pc_client.println("Content-Type: text/html"); //imposto il data type       
          pc_client.println();  
          pc_client.print("<html><body>"); //inizia codice html  
          
          pc_client.print("<h1>Arduino - Misura temperature</h1>"); 
          
          time_t t = now()+timeZoneOffset;
          pc_client.print(hour(t));
          pc_client.print(":");
          pc_client.print(minute(t));
          pc_client.print(":");
          pc_client.print(second(t));
          pc_client.print(" ");
          pc_client.print(day(t));
          pc_client.print(" ");
          pc_client.print(month(t));
          pc_client.print(" ");
          pc_client.print(year(t));
          pc_client.println(); 
          
          for(int num=0;num<5;num++) // vado a leggere tutti i sensori registrati
          {
            pc_client.println("<br>Lettura ");
            pc_client.print(num);
            pc_client.print(" : ");
            pc_client.print(temps[num]);
            
            
            
          }
          pc_client.print("</body></html>");
          
          delay(5);  // piccolo ritardo 
          
          break; // e chiudiamo il ciclo        
         }
      }
      
    } 
    pc_client.stop(); // termino la comunicazione     
   }
 
  delay(100); // piccolo ritardo
}


void SendRF24Error(unsigned char errorcode) // usata per inviare errori via nrf24
{
    IORF24.IO.Command='E';
    IORF24.IO.Data.datachar[0]=errorcode;
     Mirf.setTADDR((byte *)"c0001");
     Mirf.send((byte *)IORF24.databuffer);
     while(Mirf.isSending()){}
}

void SendTime24() // usata per inviare temperature via nrf24
{
   if(neterror) return;  // impedisce l'invio dell'ora se c'è un errore di sincronizzazione
   IORF24.IO.Data.datalong=now();
   IORF24.IO.Command='H';
  
   Mirf.setTADDR((byte *)"c0001");
   Mirf.send((byte *)IORF24.databuffer);
   while(Mirf.isSending()){}
}

void SendWebTemp() 
{
  
if(temps[0]!=1000 && neterror==0)
      {
        if (ArduinoClient.connect(remote, 80))
        {
        Serial.println("connected");
        //sprintf(buffer, "HEAD /path/to/script/newtemp.php?count=%d&temp=%d.%d&light=%d HTTP/1.1", Tcount, Whole, Fract, photocell);
        
        //sprintf(buffer, "HEAD /test/myscript.php?c1=%d",temps[1] );
        //ArduinoClient.print(buffer);
        
        ArduinoClient.print("HEAD /sendtemp.php?c1=");
        ArduinoClient.print(temps[0]); 
        ArduinoClient.print("&time=");
        ArduinoClient.print(now());  
                
        ArduinoClient.println(" HTTP/1.1"); // attenzione allo spazio
        //ArduinoClient.println("Host: 127.0.0.1");
        ArduinoClient.println("Host: www.mcmajan.com");//184,173,194,61
        ArduinoClient.println("User-Agent:Arduino");
        ArduinoClient.println("Accept: text/html");
        ArduinoClient.println("Connection: close");
        ArduinoClient.println();
        ArduinoClient.stop();
        }
        else 
        {
          Serial.println("connection failed");
          //lcd.setCursor(13, 1);
        }        
      }
  
}

Il software funziona in questo modo:

Scheda DSP: All’avvio oltre a tutte le varie inizializzazioni comunica alla scheda ETH l’avvenuto riavvio in modo che quest’ultima possa comunicare immediatamente l’ora prelevata da internet. Dopodichè ogni 30 secondi effettua la misurazione delle temperature ed ogni 5 minuti (10 cicli) invia la lettura alla ETH. Nel frattempo resta in attesa di eventuali aggiornamenti dell’ora, comunicazioni d’errore o richieste di invio dell’ultima misurazione effettuta.

Scheda ETH: Come la cugina, in fase di avvio invia un messaggio di reset per richiedere l’ultima misura termica, inoltre si connette ad internet dove effettua una prima sincronizzazione dell’orario che invia alla scheda remota. Successivamente ad ogni ora rieffettua la sincronizzazione oraria e la invia alla scheda remota. Inoltre resta in attesa di ricevere le letture termiche che memorizza in un array per tenere traccia degli ultimi 5 dati rilevati. Ad ogni ricezione invia il dato su un server remoto, inoltre se interrogata direttamente da rete locale fornisce una schermata con gli ultimi 5 valori memorizzati.

Considerazioni

I listati sono, spero, già autoesplicativi, ma vorrei soffermarmi su alcuni punti. In primo luogo vi faccio notare che la scheda DSP è preimpostata per leggere sino a 5 sensori 1wire anche se poi nella pratica sul display ne mostra fino a 2 (ma tutti su porta seriale) e alla scheda remota invia solo la lettura del primo. Questa apparente incongruenza nasce dal fatto che ho mantenuto la possibilità di leggere multipli sensori in quanto la scheda DSP potrebbe essere connessa ad una rete di sensori sparsi su tutta la casa. In questo modo diventa molto più facile modificare il software per una gestione più complessa di quella oggi proposta. Seguendo questa filosofia potremmo utilizzare una scheda per raccogliere dati di temperatura tramite una o più reti di sensori, dopodichè questi dati possono essere trasmessi alla scheda remota per l’invio in rete, cosa estremamente utile se ad esempio il router di casa non è vicino alla prima scheda rendendo la connessione stessa piuttosto complicata. L’altra cosa che vorrei far notare è che ho mantenuto l’invio remoto delle temperature per ottenere dei grafici via WEB. Per farlo ho utilizzato lo script php che vi avevo presentato in questo articolo e che potete usare tranquillamente, attenzione che ci sono due script, quello deputato a ricevere e salvare i dati e quello usato per costruire i grafici di temperatura. E’ stata mantenuta anche la funzionalità di semplice web server, per cui se la scheda ethernet viene interrogata via browser risponderà elencando le ultime cinque misurazioni effettuate dal sensore remoto. Essendo fatte ogni circa 10 minuti, avremo di fatto l’andamento temporale degli ultimi 50 minuti.

Altra cosa che vorrei farvi notare sono le strutture ed union che ho utilizzato per rappresentare i dati, cosa che ho fatto per evitare l’uso dei puntatori, approccio che io in genere preferisco, ma che ho evitato perchè non volevo rendere il listato troppo complesso per chi non conosce bene il C. In definitiva troverete una variabile globale IORF24 che rappresenta una struttura al cui interno c’è una union fra un array di caratteri e un’ulteriore struttura. Cosa significa? Significa che possiamo usare l’array di caratteri (databuffer) per accedere in modo diretto al buffer con i puntatori sia in lettura che scrittura (ricordo che lo abbiamo scelto di 5 bytes), ma per chi preferisce può accedere alla stuttura (IO) che a sua volta continene un char (Command) per il comando ed un buffer per i dati la cui istanza si chiama Data. IORF24Quest’ultimo a sua volta è una union di 4 char, 2 int, un float ed un unsigned logn che prendono rispettivamente i nomi di datachar, dataint, datafloat e datalong. In questo modo possiamo scambiare senza troppi problemi, dati di tutti i tipi, sia interi che char, float o long. Per chiarire meglio il concetto vi riporto la figura qui a lato.

Faccio notare anche che le azioni nella ETH non vengono temporizzate con la classica funzione millis() che calcola appunto il numero di millisecondi trascorsi, ma usa la funzione now della libreria time che utilizza i secondi ed è perciò anche più “umanamente comprensibile”. Invece nella DSP ho mantenuto la millis() così potete apprezzare entrambi i tipi di approccio.

Conclusioni

Anche questo articolo è terminato, spero che il progetto che vi ho proposto sia di vostro gradimento. Con la tecnica illustrata possiamo far comunicare schede a distanza per remotizzare relè, sensori, motori, display, etc. Devo ammettere che sono rimasto impressionato molto positivamente da questi moduli. Se posso fare un piccolo appunto, speravo in una portata leggermente maggiore, per questo appena posso acquisterò un modulo con antenna esterna per paragonare le portate di trasmissione. Peccato che non ne ho altri, avrei potuto usare un terzo modulo per fare da “ponte” fra i due per aumentare notevolmente la portata di trasmissione, ma state certi che prima o poi un articoletto in merito arriverà. Una piccola nota finale: spesso dopo lo spostamento di una delle due schede, mi sono ritrovato con il blocco della trasmissione e la necessità di reset delle due schede. Il problema potrebbe essere legato all’instabilità dei prototipi tenuti insieme da spezzoni di cavi e breadboard, ma se vi capita un buon reset generalmente risolve tutti i problemi. Per terminare vi lascio con un piccolo video che mostra il sistema in funzione: