MassaBUS incontra Arduino

 

I DISPOSITIVI MASSABUS SONO ORA IN VENDITA COMPLETI E AGGIORNATI QUI!

Molte persone oggi conoscono Arduino, una piattaforma di sviluppo che in pochi anni è diventato un vero e proprio ecosistema composto da schede, librerie e moduli aggiuntivi detti "shields". I vantaggi dell'utilizzo di arduino sono derivanti da tanti fattori, primo fra tutti il fatto che sia compeltamente open source. Un altro vantaggio non indifferente è la possibilità di disporre di un microcontrollore programmabile al volo direttamente tramite usb attraverso un IDE di sviluppo multipiattaforma.

Non è quindi un mistero la volontà di far incontrare il sistema per domotica MassaBUS con Arduino. Partiamo da un presupposto, la caratteristica principale di MassaBUS è di essere un sistema flessibile: le schede sono dei semplici servi di un cervello centrale che può mutare indipendemente dalla struttura del sistema. Il cervello centrale fino ad ora (come mostrato nei miei articoli su MassaBUS) era un PC avente in esecuzione un programma di gestione. Per quelli a cui piace modificare ed evolvere i progetti di continuo il PC è un ottima soluzione in quanto unisce praticità e velocità di cambiamento (basta aggiornare il software), però spesso questa soluzione è esagerata per la maggior parte degli utilizzi dove magari si vuole aggiornare l'impianto di tanto in tanto (o anche mai!!). In questo caso Arduino può sostituire efficacemente il PC centrale, meantenedo la praticità di riprogrammazione con il vantaggio di rappresentare una soluzione meno onerosa garantendo bassisimi consumi senza utilizzare sistemi embedded avanzati.

Le schede del sistema MassaBUS si vanno quindi a collegare tramite bus RS485 a un nodo centrale costituito da una scheda Arduino avente in esecuzione il programma di gestione del sistema (con incluse eventuali shield ethernet o wifi...).

In questa pagina vi mostrero le funzioni necessarie per implementare in Arduino il protocollo MassaBUS, un programma di esempio e uno schema per realizzare la shield RS485 necessaria per interfacciare Arduino UNO (o Leonardo, PRO) con il bus RS485 utilizzato da MassaBUS. Puoi acquistare la scheda già montata e collaudata per Arduino direttamente qui.

INTERFACCIA ARDUINO RS485

Per utilizzare il bus RS485 con Arduino è necessario disporre di una shield di interfaccia RS485. Qui ne vedremo un tipo realizzato appositamente per utilizzare tutte le caratteristiche del MassaBUS. Infatti la scheda oltre ad avere i componenti indispensabili all'uso dell'RS485 dispone anche delle funzioni di terminazione linea e polarizzazione linea attivabili tramite dip-switch. Inoltre dispone di un led per segnalare la trasmissione di un pacchetto (o altre funzioni a piacimento) ed infine un pulsante che può essere utilizzato ad esempio per prove, ripristini o altre funzioni.

Ecco lo schema elettrico dell'interfaccia:

Come visibile nello schema sopra il componente principale della shield è l'integrato MAX485 che si occupa di trasformare il bus RS485 in segnali 0-5V adatti per l'utilizzo con microcontrollore a 5V come quello della scheda Arduino UNO. L'integrato utilizza il pin RX e TX dell'UART di Arduino nonchè un pin di output (2) che controlla l'abilitazione della trasmissione nel bus (essendo half-duplex). L'abilitazione della ricezione (cioè i dati sul bus vengono portati nel pin RX di Arduino) può essere eseguita con 2 modalità selezionabili tramite ponticello TX ECHO: la prima prevede l'attivazione della ricezione solo quando la trasmissione è disattivata, mentre la seconda mantiene la ricezione sempre attiva pertanto sul pin RX di arduino avremo anche l'ECO del pin TX. La modalità per l'uso con MassaBUS è quella in cui la ricezione è disattivata durante la trasmissione.

Le uscite del BUS RS485 sono collegate ad un dip switch che tramite il selettore 1 permette di attivare la terminazione della linea (solo se la scheda è all'inizio o alla fine del bus) e tramite il selettore 2 e 3 permette l'attivazione della polarizzazione della linea (attivazione di entrambi necessaria in una sola scheda del BUS).

In seguito possiamo trovare delle piccole aggiunte che fanno da contorno alla scheda: un led al pin 3 di Arduino e un pulsante al pin 5 di Arduino. Il led può essere utile per indicare la trasmissione di un pacchetto, mentre il pulsante (attivo con ingresso alto su Arduino) può essere utile per ripristinare in particolari condizioni i valori di default del sistema (ipotizzando l'uso della ethernet shield insieme a questa shield, esattamente come avviene nei dispositivi di rete). La resistenza da 470 all'ingresso di Arduino dove è collegato il pulsante serve come protezione per Arduino in caso che tale pin di ingresso venga erroneamente impostato come uscita. Ovviamente sia il led che il pulsante sono utilizzabili a vostra discrezione!

Puoi acquistare la scheda già montata e collaudata per Arduino direttamente qui.

IL CODICE PER ARDUINO

Essendo Arduino il nodo centrale, esso si occupa anche di far partire la comunicazione e una volta fatto si aspetta una risposta. Ecco quindi che il codice per arduino si divide in due parti: una si occupa della trasmissione del pacchetto (tramite una funzione chiamata "code_data_to_send") e di una funzione di ricezione chiamata dalla funzione di decodifica ("decode_ric_data") che a sua volta è chiamata da una routine di cattura dei dati seriali ("serialEvent") che si verifica ogni volta che il primo byte del pacchetto arriva nella seriale di Arduino. Di seguito trovate tutte le funzioni con le rispettive chiamate.

Le funzioni sono state inserite in un programma di esempio che si occupa di scrivere ogni 5 secondi due valori alternati in output. Per provare subito il programma ed effettuare i primi esperimenti di utilizzo è necessario collegare ad Arduino + RS485 una scheda Multi I/O Device o Mini I/O Device con indirizzo pre programmato 2. Nei relè di uscita, una volta caricato il programma, vedrete variare l'output ogni 5 secondi.

//-----------------------------------------------------------------
// Esempio applicativo del protocollo MassaBus con Arduino
// Il programma per la prova di funzionamento richiede una scheda
// di output (Multi I/O o Mini I/O con indirizzo pre impostato a 2)
// Durante la prova ogni circa 5 secondi l'output sulla scheda cambierà
// di stato
//------------------------------------------------------------------
//Definisco numero byte bus
#define SIZE_BUS_DATA 11
//Definisco il timeout massimo per la seriale
#define MAX_MILLIS_TIMEOUT 50
//Inizializzazioni
int led_act = 13; //Pin di arduino con il LED incluso
int bus_dir = 2; //Pin di selezione della direzione
int count; //Variabile conteggio generica
long mil_mem = 0; //Variabile memorizzazione millisecondi
byte my_adr = 1; //Mio indirizzo nel bus
byte send_compose[SIZE_BUS_DATA]; //Array invio pacchetto
//Variabili procedure ricezione (riguardano l'ultimo pacchetto decodificato)
byte recived_data[SIZE_BUS_DATA]; //Array del pacchetto ricevuto
boolean correct_data; //True se il pacchetto era corretto
byte adress_reciver_ric; //L'indirizzo destinatario
byte adress_sender_ric; //Indirizzo mittente
byte type_ric; //Tipologia pacchetto
byte data1_ric; //Campo dato 1
byte data2_ric; //Campo dato 2
//Procedura creazione trama da inviare su BUS
//Campi: indirizzo destinatario,tipologia pacchetto, dato 1, dato 2)
void code_data_to_send(byte adress_reciver, byte type_s, byte data1_s, byte data2_s)
{
 //Variabili utilizzate nella procedura
byte checksum_send; //checksum completo
byte checksum_send_low; //checksum diviso (parte bassa)
byte checksum_send_hi; //checksum diviso (parte alta)
byte type_to_send; //Tipologia pacchetto
byte data_send_1; //Primo campo dati da inviare
byte data_send_2; //Secondo campo dati da inviare
byte adress_sender; //Indirizzo mittente
//Inizio procedura
type_to_send = type_s << 2; //shift di due bit per spazio riservato zero flag
//se l'indirizzo è in modalità normale (destinatario diverso da 255)
if(adress_reciver != 255){
  adress_sender = my_adr; //Metto come mittente il mio indirizzo
 } else {
  adress_sender = 255; //Altrimenti anche il mittente a 255
 } 
if(data1_s == 0){ //se data1 è pari a 0
  data_send_1 = 48; //lo sostituisco con il codice ASCII 48
  type_to_send = type_to_send | 1; //Imposto a 1 il rispettivo zero flag
 } else {
  data_send_1 = data1_s; //altrimenti mantengo il dato
 }
if(data2_s == 0){ //se data2 è pari a 0
  data_send_2 = 48; //lo sostituisco con il codice ASCII 48
  type_to_send = type_to_send | 2; //Imposto a 1 il rispettivo zero flag
 } else {
  data_send_2 = data2_s; //altrimenti mantengo il dato
 }
//Calcolo e assegnazione bit checksum
//Effettuo XOR con tutti i campi necessari
checksum_send = adress_reciver ^ adress_sender ^ type_to_send ^ data_send_1 ^ data_send_2;
//Suddivido il checksum in due parti (alta e bassa) a 4 bit ciascuna
//Imposto di ogni parte il bit più significativo a 1 come previsto da protocollo
checksum_send_low = (checksum_send & 15) | 128;
checksum_send_hi = (checksum_send >> 4) | 128;
// Composizione array stringa seriale
//Ad ogni rispettivo byte del pacchetto assegno il rispettivo dato
send_compose[0] = 'S';
send_compose[1] = adress_reciver;
send_compose[2] = adress_sender;
send_compose[3] = type_to_send;
send_compose[4] = data_send_1;
send_compose[5] = data_send_2;
send_compose[6] = checksum_send_low;
send_compose[7] = checksum_send_hi;
send_compose[8] = 3; //Terminazione
send_compose[9] = 3;
send_compose[10] = 3;
//Invio il pacchetto
send_data();
}
//Procedura invio dati su bus tramite seriale
void send_data(){
 // setto il flag di trasmissione
 UCSR0A=UCSR0A |(1 << TXC0);
 Serial.flush();
 // abilito led
 digitalWrite(led_act, HIGH);
 //attivo il MAX485 in trasmissione
 digitalWrite(bus_dir, HIGH);
 //Mando l'array di byte
 Serial.write(send_compose, SIZE_BUS_DATA);
 //Attendo la fine della trasmissione
 Serial.flush();
 while (!(UCSR0A & (1 << TXC0)));
 //Disabilito led e max485
 digitalWrite(bus_dir, LOW); 
 digitalWrite(led_act, LOW);
}
//Procedura decodifica dati
void decode_ric_data(){
//Variabili checksum separati
byte chk_low;
byte chk_hi;
//Composizione complessiva del checksum
byte checksum;
//Variabile checksum ricalcolato
byte calcolated_checksum;
//imposto a default la variaible pacchetto corretto
correct_data = false;
//Verifico che il primo pacchetto sia S e ci sia la terminazione (come da protocollo MassaBUS)
if((recived_data[0] == 'S')&&(recived_data[8] == 3)){ //Controllo start byte e terminazione
 //Ricostruzione byte checksum
 //Separo la parte bassa dal pacchetto
 chk_low = recived_data[6] & 15;
 //Separo la parte alta dal pacchetto
 chk_hi = recived_data[7] & 15;
 //Mette insieme i due byte per ricostrire checksum (con shift del più alto)
 checksum = (chk_hi << 4) | chk_low; 
 //controllo il dato ricevuto con il checksum (lo ricostruisco con i byte ricevuti)
 calcolated_checksum = recived_data[1] ^ recived_data[2] ^ recived_data[3] ^ recived_data[4] ^ recived_data[5];
 //Verifico che il checksum sia uguale (calcolato e ricevuto) e verifico che i due byte dei
 //pacchetti dei checksum abbiano il bit più significativo alto (come da protocollo)
 if((calcolated_checksum == checksum) && ((recived_data[6] & 128) == 128) && ((recived_data[7] & 128) == 128)){
   //se il checksum è corretto
   //Setto nella variabile condivisa l'indirizzo del destinatario (forse questa scheda!!!)
   adress_reciver_ric = recived_data[1];
   //Setto nella variabile condivisa l'indirizzo del mittente
   adress_sender_ric =recived_data[2];
   //preleva il pacchetto di tipologia (saltando gli zero flag)
   type_ric = recived_data[3] >> 2; 
   //Verifica degli zero flag (sia primo che secondo campo dati)
   //Se dato 48 e rispettivo zero flag a 1 setto data a 0
   //altrimenti setto il valore ricevuto
   if((recived_data[4]==48)&&((recived_data[3] & 1)==1)){
     //se data1 = 0 (controllo zero flag a 1)
     data1_ric=0;
   } else {
     data1_ric=recived_data[4];
   }
   //Verifica degli zero flag
   //Se dato 48 e rispettivo zero flag a 1 setto data a 0
   //altrimenti setto il valore ricevuto
   if((recived_data[5]==48)&&((recived_data[3] & 2)==2)){
     //se data2 = 0 (controllo zero flag a 1)
     data2_ric=0;
   } else {
     data2_ric=recived_data[5];
   }
   //Indico che il pacchetto è stato decodificato correttamente
   correct_data = true;
 }
}
 //Se il pacchetto è decodificato correttamente
 if(correct_data){
   //Chiamo la procedura che gestisce il pacchetto decodificato
   packet_data_elaboration();
 }
}
// the setup routine runs once when you press reset:
void setup() { 
  // inizializzo led indicazione invio pacchetto e led selezione direzione
  pinMode(led_act, OUTPUT); 
  pinMode(bus_dir, OUTPUT); 
  Serial.begin(9600);
}
//evento lanciato durante la ricezione di dati seriali
void serialEvent() {
  byte index_buffer_rx = 0;
  boolean get_byte_continue = true;
  //Prendo i millisecondi attuali
  long last_millis = millis();
  long mem_millis;
  //Inizializzo conteggio millisecondi di timeout
  int count_millis = 0;
  //Inizializzo varibile indicante dati corretti
  boolean bus_data_avaible = false;
  //Verifico disponibilità dati
  if(Serial.available()){
    //Prelevo primo dato
    recived_data[index_buffer_rx++] = Serial.read(); 
    //Attivo loop di ricezione pacchetti
    while(get_byte_continue){
      //Parte gestione byte ricevuti
      if(Serial.available()){
        recived_data[index_buffer_rx++] = Serial.read(); 
        //Verifico se il buffer ha un pacchetto completo
        // (-2 per riconosere il pacchetto conteggiando da 0)
        if(index_buffer_rx > (SIZE_BUS_DATA - 2)){
          //Fermo la ricezione di byte (dopo uscirà dal ciclo)
          get_byte_continue = false;
          //Indico mettendo in true che il pacchetto è stato ricevuto correttamente
          bus_data_avaible = true;
        }
      }
      //Seconda parte (conteggia i milliscondi del ciclo while)
      //Se supera timeout esco (evitando stalli del software!)
      //Parte gestione timeout
      //Prelevo i millisecondi attuali dall'accensione di arduino
      mem_millis = millis();
      //Se diversi da quelli memorizzati precedentemente
      if(mem_millis != last_millis){
        //Salvo in memoria gli attuali
        last_millis = mem_millis;
        //Incremento il conteggio dei millisecondi
        count_millis++;
        //Se supera soglia di millisecondi impostati (costante)
        if(count_millis > (MAX_MILLIS_TIMEOUT - 1)){
          //Esco dal ciclo mettendo in flase la variabile
          get_byte_continue = false;
          //bus_data_avaible rimane in false (pacchetto non corretto!!)
        }
      }
     }
  }
  //Se all'uscita del while i dati sono corretti
  if(bus_data_avaible){
    //Chiamo la procedura di decodifica pacchetto
    //Nell'array recived_data ho tutti i byte del pacchetto
    //Pronti da analizzae...
    decode_ric_data();  
  }
}
void loop() { 
  //Attendo 5 secondi (non uso delay per permettere ricezione seriale)
 if (millis() != mil_mem){
   mil_mem = millis();
 if(count++ > 5000){
   count = 0;
   //Invio in output 5 (tipologia 12 output, indirizzo 2)
 code_data_to_send(2, 12, 5, 0);
 }
}
}
 
void packet_data_elaboration() {
//Se il pacchetto è indirizzato qui e arriva dal dispositivo 2 con tipologia 12
if ((adress_reciver_ric == 1) && (adress_sender_ric == 2) && (type_ric == 12)){
  if(data1_ric = 5){
    delay(5000); //attendo 5 sec
    code_data_to_send(2, 12, 10, 0);
    //Invio in output 10 (tipologia 12 output, indirizzo 2)
  }   
 }
}

Di seguito trovate il sorgente per Arduino.

DOWNLOAD SORGENTE ARDUINO

Buon lavoro!

Commenti   

# fabrizio 2013-02-16 17:00
Marco proprio oggi ti ho mandato a mezzogiorno un messaggio dove sentivo la necessita di interfacciare il tuo sistema domotico con un rete internet.
Per me slegarsi da qualsiasi prodotto già confezionato è fondamentale, la mia proposta del enc28j60 che potrai leggere meglio nell' email che ti ho mandato farebbe si oltre al costo irrisorio di essere padroni assoluti del sistema. ciao un saluto fabrizio
Rispondi | Rispondi con citazione | Citazione

Aggiungi commento



Anti-spam: complete the task
Elettronica Progetti MassaBUS incontra Arduino