Buses de campo para Arduino.... RS485

.
.
Originalmente, toda la información la publiqué en el foro de Arduino en español. Puedes seguirlo aquí.

El RS485 es un estandar de comunicación serie en el cual se pueden tener varios dispositivos en topología de bus y de una manera muy económica. Aquí explicaremos brevemente como realizar una pequeña red basada en 2 hilos.
Dado que sólo se usan dos hilos balanceados (diferencial), las comunicaciones son semiduplex. Para configurar el driver como transmisor o receptor, el driver dispone de una entrada digital para su configuración.

Para darle conectividad RS485 a nuestro arduino, sólo necesitamos de un convertidor de niveles para usar la UART del micro, y por menos de 1 euro tendremos todo lo necesario.
Los chips más usados son:
- Max485
- SN75176 (siendo éste el más económico)




La norma RS485 sólo establece las características físicas, no definiendo ningún protocolo ó forma de conexionado.

Señalamos unos links muy recomendables:
http://www.i-micro.com/pdf/articulos/rs-485.pdf
http://www.maxim-ic.com/appnotes.cfm?appnote_number=763&CMP=WP-1
http://www.neoteo.com/rs485-domotica-al-alcance-de-tu-mano-15810.neo
Documento con tramas de ejemplo para solucionar problemas
http://www.sbc-support.ch/faq/files/files.get.php?ZSESSIONID=n38bphov4ls4cdsvtbd...



La manera más sencilla de aprender como realizar un protocolo, es basarte en algo que ya existe. Para ello, me he basado en el protocolo profesional usado por Fuji para sus variadores industriales.
Véase: http://www.cdautomation.com/download/ENG_L_M_FUJI_RS485_COMM_for_FRENIC-Mini.PDF

Veamos un diagrama explicativo de la trama:



Las tramas son de 15 bytes:
  • Byte 1: Byte de start ( 0 hexadecimal ).
  • Byte 2-3: SCII de la dirección del arduino.
  • Byte 4: Byte ENQ, ACK ó NAK (0x05h, 0x06h y 0x15h) .
  • Byte 5: ASCII del comando petición.
  • Byte 6 y 7: ASCII del número de función.
  • Byte 8: Byte signo (Positivo 0x20h y Negativo 2D)
  • Byte 9-12: ASCII con el dato (0x00h-0xFFFFh)
  • Byte 13: Byte fin de texto (0x03h)
  • Byte 14-15: Checksum (suma de byte 2 al byte13)

Con esta trama se dispone de un byte que indica función + 2 bytes con el número de la función, por lo cual se pueden hacer multitud de combinaciones… Algunos ejemplos pueden ser: A01 realiza una petición de datos de la entrada analógica 1, P01 realiza una configuración de PWM 1 con el dato enviado en los 4 bytes de datos,… Elección del diseñador!!!
Se puede observar que los comandos están en ASCII y los bytes de control no. Esto ayuda enormente en la programación, ya que si recibimos 0x00 (suponiendo que es el byte de start), nuestro programa sabe que es un inicio de trama y no lo entiende como un "0" (número cero), ya que éste se enviaría en ASCII (0x30).
A continuación, adjunto una tabla ASCII sacada de http://www.cs.utk.edu/~pham/ascii_table.jpg





Veamos un ejemplo:
Queremos que un arduino (maestro) envíe la orden de encendido/apagado de un led conectado a otro arduino arduino (esclavo). El interruptor está en el maestro y la actuación (encendido/apagado) en el esclavo.

Según el estado del interruptor, el maestro envía la trama de petición de encendio/apagado, al esclavo ,cuya dirección es 01. Se ha decidido que la función se llamará D y el número 00 (completamente configurable).
Ej: 0x00 0x30 0x31 0x05 0x44 0x30 0x30 0x20 0x30 0x30 0x30 0x31 0x03 0x01 0xEE
El esclavo, contesta con un ACK (byte 4) al maestro confirmando el dato recibido.
Ej: 0x00 0x30 0x31 0x06 0x44 0x30 0x30 0x20 0x30 0x30 0x30 0x31 0x03 0x01 0xEF

Mejor,lo vemos con un video: http://www.youtube.com/watch?v=ABcjU0Ua-d4


(el interruptor es un triste cable que lo llevo a masa o VCC.... sorry!!!)

Para poder ver las tramas, bastará un conversor USB <--> RS232 basado en chip ftdi, ó un MAX232+MAX485,... Si tienes una placa Arduino de sobra, quitale el micro y tendrás un conversor USB --> RS232.



 
El software para monitorizar lo que está pasado, puede ser el hyperterminal de windows ó alguno gratuito que añaden mejoras como poder ver los datos en hexadecimal, grabar,...:
RealTerm
FREE SERIAL TERMINAL MONITOR


Ahora veamos un ejemplo con 3 arduinos interconectados. El mestro dispone de 2 switch, los cuales comandan el encendido/apagado de un led conectado a cada uno de los esclavos.




Veamos el video de funcionamiento: http://www.youtube.com/watch?v=S9FSQaToVZ4




Ahora toca el código:

IMPORTANTE: El checksum lo hago directamente en hexadecimal en vez de convertirlo a ASCII. Se debería cambiar el código, para no tener problemas. Esta publicación se trata de un acercamiento de como manejar el bus rs485 y explicar como implementar un protocolo, por lo que no está 100%.




MAESTRO:


//----------------------------------
//RS 485
//By Igor Real
//24-06-09
//----------------------------------


byte            data[12];
unsigned long   previous_time;
unsigned long   previous_time2;
byte            times_repeat=5;
byte            times_repeat2=5;

byte            state=0;
byte            state2=0;

#define  pinCONTROL    02
#define  myaddress     01
  

void setup() {

   pinMode(13,OUTPUT);
   pinMode(pinCONTROL,OUTPUT);
   digitalWrite(13,HIGH);
   digitalWrite(12,LOW);
   pinMode(8,INPUT);
   pinMode(9,INPUT);
   digitalWrite(pinCONTROL,LOW);
   Serial.begin(9600);
   Serial.println("Empezamos");
   state=0;
   state2=0;
}

void loop()
{

  if (digitalRead(8)==state){
    state=!state;
    times_repeat=0;  
  }
  if (digitalRead(9)==state2){
    state2=!state2;
    times_repeat2=0;  
  }

  
  
  
  if (times_repeat<4){
    Serial.flush();  
    //(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4)
    if (digitalRead(8)==HIGH){  
      sendMSG(48,49,68,48,48,32,48,48,48,49);
    }else {
      sendMSG(48,49,68,48,48,32,48,48,48,48);
    }
    times_repeat=times_repeat+1;
  
    previous_time=millis();
    while (((millis()-previous_time)<500) && (Serial.available()!=15)){
      ;;
    }
  
    if (Serial.available()>=15){
      if (receiveMSG()==1){
        Serial.println("Trama correcta");
        if (data[0]==48 && data[1]==49 && data[2]==6){
          //ACK  
          times_repeat=5;
          Serial.println("ACK recibido");
        }
      }
     }
   }  
  
  

  if (times_repeat2<4){
    Serial.flush();  
    //(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4)
    if (digitalRead(9)==HIGH){  
      sendMSG(48,50,68,48,48,32,48,48,48,49);
    }else {
      sendMSG(48,50,68,48,48,32,48,48,48,48);
    }
    times_repeat2=times_repeat2+1;
  
    previous_time2=millis();
    while (((millis()-previous_time2)<500) && (Serial.available()!=15)){
    ;;
    }
  
    if (Serial.available()>=15){
      if (receiveMSG()==1){
        //Serial.println("Trama correcta");
        if (data[0]==48 && data[1]==50 && data[2]==6){
          //ACK  
          times_repeat2=5;
          //Serial.println("ACK recibido");
        }
      }
    }
  }
  
  
  
}


//------------------------
//FUNCIONES
//------------------------

byte receiveMSG(){

  byte  byte_receive;
  byte  state=0;
  byte  cont1=1;
  byte  trace_OK=0;

  unsigned int checksum;
  unsigned int checksum_trace;
  
  
  
  while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       if (checksum_trace==checksum){
           trace_OK=1;
       }else{
         trace_OK=0;
       }
       break;
     }
  }
  return trace_OK;

}



void sendMSG(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+5+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(5,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)& 255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);

  
  
}



void sendACK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(6,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}

void sendNAK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(15,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}



byte hex2num(byte x){

  byte result;
  
  if (x>=48 && x<=57){
    result=x-48;  
  }else if (x>=65 && x<=70){
    switch(x){
      case 65:
        result=10;
        break;
      case 66:
        result=11;
        break;
      case 67:
        result=12;
        break;
      case 68:
        result=13;
        break;
      case 69:
        result=14;
        break;
      case 70:
        result=15;
        break;    
    }  
  }
  return result;  
}



 

ESCLAVOS:
(hay que cambiar la dirección en myaddress a 01 y 02,dependiendo del esclavo)


//----------------------------------
//RS 485
//By Igor Real
//24-06-09
//----------------------------------


byte  data[12];
byte  address;
byte  function;
byte  function_code;
unsigned int data_received;
byte  byte_receive;
byte  state=0;
byte  cont1=1;
byte  trace_OK=0;
unsigned int checksum;
unsigned int checksum_trace;

#define  pinCONTROL    02
#define  myaddress     02
  

void setup() {

   pinMode(13,OUTPUT);
   pinMode(pinCONTROL,OUTPUT);
   digitalWrite(2,LOW);
   Serial.begin(9600);
   Serial.println("Empezamos");

}

void loop()
{

   while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       //Serial.println("Se ha recibido byte Start");
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       address=0;
       data_received=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
       //Serial.print("Primer Byte Checksum");      
       //Serial.print(checksum_trace,HEX);
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       //Serial.println(byte_receive,HEX);
       //Serial.println("Recibida trama");
       //Serial.print("Checksum Trace= ");
       //Serial.println(checksum_trace,HEX);
       //Serial.print("Checksum= ");
       //Serial.println(checksum,HEX);
       //Serial.println(checksum,DEC);
       //Serial.println("Trama= ");
       //Serial.print(data[0]);
       //Serial.print(data[1]);
       //Serial.print(data[2]);
       //Serial.print(data[3]);
       //Serial.print(data[4]);
       //Serial.print(data[5]);
       //Serial.print(data[6]);
       //Serial.print(data[7]);
       //Serial.print(data[8]);
       //Serial.print(data[9]);
       //Serial.print(data[10]);
       //Serial.println(data[11]);      

       if (checksum_trace==checksum){
         trace_OK=1;
        
         address=(hex2num(data[0])<<4)+(hex2num(data[1]));
         function=data[3];
         function_code=(hex2num(data[4])<<4)+(hex2num(data[5]));
         data_received=(hex2num(data[7])<<12)+(hex2num(data[8])<<8)+(hex2num(data[9])<<4)+(hex2num(data[10]));
        
         //Serial.println("TRAZA CORRECTA");
         //Serial.println(address,DEC);
         //Serial.println(data_received);
         if (address==myaddress){
           if ((function=='D') && (function_code==0) && data[2]==5){
             if (data_received==1){
               digitalWrite(13,HIGH);
               //Serial.println(data_received,DEC);
               sendACK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
             }else if (data_received==0){
               digitalWrite(13,LOW);
               sendACK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
             }
           }
         }
       }else{
         //Serial.println("TRAZA INCORRECTA");  
         sendNAK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
       }
     }

  }
  
}


//------------------------
//FUNCIONES
//------------------------

byte receiveMSG(){

  byte  byte_receive;
  byte  state=0;
  byte  cont1=1;
  byte  trace_OK=0;

  unsigned int checksum;
  unsigned int checksum_trace;
  

  
  
  while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       if (checksum_trace==checksum){
           trace_OK=1;
       }else{
         trace_OK=0;
       }
       break;
     }
  }
  return trace_OK;

}



void sendMSG(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+5+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(5,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)& 255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);

  
  
}



void sendACK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(6,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}

void sendNAK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(15,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}



byte hex2num(byte x){

  byte result;
  
  if (x>=48 && x<=57){
    result=x-48;  
  }else if (x>=65 && x<=70){
    switch(x){
      case 65:
        result=10;
        break;
      case 66:
        result=11;
        break;
      case 67:
        result=12;
        break;
      case 68:
        result=13;
        break;
      case 69:
        result=14;
        break;
      case 70:
        result=15;
        break;    
    }  
  }
  return result;  
}

Ahora veamos como cablear una red de topología bus:

 
En cada Arduino o dispositivo de la red, se cablea con una Y soldada en la cual salen dos conectores de la misma familia. Debe tener la longitud más corta posible.
Cada conector tiene Alimentación, Señal A, Señal B y Masa.

De esta forma, si se quiere ir ampliando la red con dispositivos, bastará con ir intercalandolos ó poniendolos a final. Ya que cada dispositivo tiene conectores macho y hembra, por lo que encaja perfecto.
Se puede preparar un par de conectores, con una resistencia terminadora puesta en los terminales correspondientes a la Señal A y Señal B del bus RS485 para cerrar los extremos (siempre quedará un par de conectores libres). Si se necesita añadir más dispositivos, se desconecta dicho conector con la resistencia terminadora, se intercala el nuevo dispostivo y se vuelve a cerrar la red.

Llevar alimentación en el cableado, no cuesta nada y todo será más fácil para futuras ampliaciones...
El cable de Señal A y B debe ser trenzado.
Recomiendo este link acerca del conexionado: http://www.rs-485.com/download/485%20network%20topology.pdf 
.
.
.
.
Saludos!!
.
Igor R.
.
.

42 comentarios:

  1. Excelente iniciativa.
    Cristal

    ResponderEliminar
  2. Hola Igor, muchas gracias por tan currado articulo. Tengo que conectar mi arduino duemilanove con un "analizador de red" llamado "circuitor", éste lleva un rs485 y usa protocolo modbus. Así que lo que necesiro es dar soporte al arduino para rs485, el problema es que no tengo mucha idea de electronica. He visto en tu articulo que usas el chip max485, pero no sé como lo conectas en la placa de prototipado ni como lo conectas al arduino. ¿puedes charme un mano?

    ResponderEliminar
  3. Hola Felix!!

    Voy a poner el pin-out con una placa diecimila.

    PIN-OUT MAX485
    1 -> RO (Receiver Output) al pin 0 (Rx)
    2 -> Receive enable a un pin digital (ej: pin2)
    3 -> Driver enable al pin digital (ej:pin2)
    4 -> DI (Driver In) al pin 1 (Tx)
    5 -> GND a gnd de arduino
    6 -> SEÑAL A del bus RS-485
    7 -> SEÑAL B del bus RS-485
    8 -> Vcc a 5 voltios de arduino


    Saludos

    ResponderEliminar
  4. Muchísimas gracias!, acabo de pedir 2 max485, mañana me pondré con ello. Como de momento no tengo el "circuitor" para probar que funciona voy a montar 2 arduino + max485 y enviar tramas entre ellos a ver si soy capaz, para lo bueno o la malo te lo hago saber, espero que sea para bien :)

    ResponderEliminar
  5. Hola Felix,

    Si tienes un conversor de FTDI lo puedes usar junto a un MAX485 para tener una especie de sniffer en el ordenador. Dispone de una patilla (TXDEN) que controla envio/recepción en las comunicaciones half-duplex.

    Otra forma,si tienes dos placas diecimila ó duemilanove, lo que puedes hacer a una de ellas le quitas el micro y lo usas como conversor USB <-> Serie. Al añadirle el Max485, podrás usar el RealTerm, hyperterminal ó similar para ver lo que está ocurriendo en el bus.(Sólo recepción).

    Si en el ordenador dispones de puerto serie, con un max232 y el max485 también te vale.

    Es una forma más sencilla para depurar, y más sencilla de empezar, que intentar comunicar directamente dos Arduinos. Asi haces una comunicacion Arduino <-> PC mediante rs485. Cuando tengas el "circuitor" podrás ver que es lo que ocurre en el bus con tu PC.

    Una vez que tengas dominado la parte física, te queda implementar el protocolo modbus rtu en tu Arduino.(http://www.modbus.org y http://freemodbus.berlios.de/)


    Saludos

    ResponderEliminar
  6. Hola Igor, ya estoy intentando que esto funcione.
    Para probarlo lo que quiero hacer es enviar algo, cualquier cosa de un arduino a otro y activar un led cuando lo reciba
    Te digo las conexiones que he hecho:

    1 -> RO (Receiver Output) -> pin 0 (Rx)
    2 -> Receive enable -> pin2
    3 -> Driver enable -> pin3
    4 -> DI (Driver In) -> pin 1 (Tx)
    5 -> GND a gnd de arduino
    6 -> SEÑAL A del bus RS-485 Conectada a señal A del otro chip
    7 -> SEÑAL B del bus RS-485 conectada a la señal B del otro chip
    8 -> Vcc a 5 voltios de arduino

    Aquí están las fotos:
    http://picasaweb.google.es/jaaaelpumuki/Arduino?feat=directlink

    Lo que no tengo claro es porque usan por ejemplo el pin13 en maestro, o porque tienes un pin de control y 2 de estado(8 y 9) o es que los de estado 8 y 9 son el A y el B del chip?

    ResponderEliminar
  7. Hola Felix,

    Una cosa que a primera vista he visto. A y B deben estar trenzados entre si y debe haber resistencia terminadora en ambos extremos.
    El pin2 y pin3 puedes llevarlos al mismo pin de Arduino, ya que trabajan con lógica inversa, por lo que juntandolos ya tienes lo que buscas (le he llamado pinCONTROL=2). En "High" habilitará el driver como transmisor y "Low" como receptor.

    Los pines 8 y 9 son las entradas de los botones, que están enchufados al Maestro y controlan el encendido/apagado en el Esclavo. Es decir, cuando hay un cambio en esa input, le envio la orden correspondiente al esclavo.

    El pin 13 es donde tengo el led en los esclavos.



    Saludos

    ResponderEliminar
  8. muchas gracias! qué es exactamente una resistencia terminadora. Aquí tengo 2 resistencias una de 10 y de 2 de 1.

    ResponderEliminar
  9. Hola Felix,

    Te recomiendo leer este link: http://www.maxim-ic.com/appnotes.cfm?appnote_number=763&CMP=WP-1

    La resistencia terminadora se debe poner, para que no ocurra lo mismo que en las antenas (ondas estacionarias) ó por ejemplo, cuando alumbras con un foco al agua... Si tienes un cambio de impedancia, parte de la onda es reflejada.

    Tienes que poner 100 ó 120 ohm.


    Saludos

    ResponderEliminar
  10. Te paso fotos de las conexiones por separado a ver si hay algo mal, porque no tengo ni idea de que pasa:
    - A y B resistencia en paralelo y cable entrelazado:
    http://picasaweb.google.es/lh/photo/eGRtcDgH6glUd-dI4Al6NQ?feat=directlink
    http://picasaweb.google.es/jaaaelpumuki/Arduino#5403538363171657090

    - RE y DE unidos con pin 2 de arduino:
    http://picasaweb.google.es/lh/photo/Pnderl5aGhSBQOXQOf9I-w?feat=directlink

    - Gnd con gnd y Vcc con 5v arduino:
    http://picasaweb.google.es/lh/photo/Rzud7VMsumvcDMpCqYLZbg?feat=directlink
    http://picasaweb.google.es/lh/photo/ZSoKf1pW8juquqzcpv4qsQ?feat=directlink

    - DI con pin 1 TX y R0 con pin 0 RX:
    http://picasaweb.google.es/lh/photo/KfDrnlb0nFpTh_o_txFcKg?feat=directlink
    http://picasaweb.google.es/lh/photo/KfDrnlb0nFpTh_o_txFcKg?feat=directlink

    ResponderEliminar
  11. No me cuadra nada!!! je,je,je
    A y B son pines 6 y 7
    Re y De son pines 2 y 3
    ....

    Revisa todo!!! Mira la foto del datasheet del Max485 para que veas como van los pines=> http://www.maxim-ic.com/images/qv/1111.gif

    Si ves el componente, y teniendo la marca que tiene el encapsulado, los pines van empezando por la izquierda el 1,2,3,4 y en la derecha desde abajo hacia arriba 5,6,7 y 8.

    ResponderEliminar
  12. A y B son pines 6 y 7, 6 y 7 del arduino o del max485?
    Y RE y DE no tenían que ir unidos y luego al pin 2 del arduino?

    , yo el esquema que tengo del max es este:
    http://picasaweb.google.es/jaaaelpumuki/Arduino#5403242571437031314

    ResponderEliminar
  13. Pero tu tienes encapsulado DIP, no µMAX...
    Fijate en el link que te di, que vienen los dos... pero uno es para encapsulado DIP y otro µMAX. Son diferentes los pin-out. En la web de maxim puedes verlo...

    ResponderEliminar
  14. opppssss :):):) joer era eso!!!!!. ya está!!! ahora a por ModBus!!! Mil gracias!!

    ResponderEliminar
  15. Me alegro!!! Informa como vas con tu proyecto de Modbus....

    ResponderEliminar
  16. Si quieres hacer pruebillas de MODBUS RTU a mi esta aplicación me va de vicio.
    http://sites.google.com/site/plcsimulator/

    Yo lo utilizo como esclavo en integración de sistemas, para debuggear mi protocolo cuando ya lo he implementado sobre la plataforma y va de vicio.

    Siempre puedes, como dice Igor, primero afianzar que has desarollado bien el protocolo MODBUS, y luego dejar a los arduinos que se digan cosillas :P

    Un saludo!

    P.D. Mandanos si te apetece colaborar la implementación MODBUS y la publicamos por aquí.

    ResponderEliminar
  17. Que cambio le hago al programa para poder enviar una lectura de un potenciometro en los esclavos y recibirlos en el maestro.

    Muchas gracias

    ResponderEliminar
  18. Hola igor que es el circuito que esta en color negro en la placa donde esta el max485???

    ResponderEliminar
  19. Te refieres al jumper?? Es para añadir o no la resistencia terminadora.

    ResponderEliminar
  20. Hola Igor, felicidades!! no solo tu proyecto es interesante sino también aplicable y funcional en diferentes áreas. Te felicito y agradezco tu atención para todos tus seguidores
    por el momento estoy involucrada en un proyecto de una red de comunicación basada en el estándar RS485 y estoy comenzando en esto de las comunicaciones, tengo algunas dudas q seguro me podrás orientar.
    Cuando haces mención del protocolo FUJI es simplemente para tomar su estructura de trama como una referencia??
    La propuesta de un protocolo de comunicación sigue reglas específicas si será utilizado para RS485 o Rs232??

    ResponderEliminar
  21. Hola,
    Gracias!!
    He usado un protocolo como orientación, ya que me parecía que estaba muy bien.
    El RS485 sólo define la capa física, por lo que es el usuario quien tiene que hacer todas las "reglas" (protocolo) para intercambiar la información entre los diferentes dispositivos de la red RS485.
    Algún otro ejemplo de protocolo es MODBUS.

    El protocolo es algo soft, por lo que puedes utilizarlo en rs232 también.


    Saludos

    ResponderEliminar
  22. Hola Igor,
    Excelente aporte. Voy aplicarlo como Modbus en un enlace Arduino --> Wincc flexible de Siemens y necesito saber si tienes algo ya probado.
    Te lo pregunto porque he visto que trabajaste con automatas por ahí no?.
    Saludos

    ResponderEliminar
  23. No tengo nada, asi que estaria genial si pudieses documentar tus progresos. Los podria publicar por aqui si quieres.
    Salu2

    ResponderEliminar
  24. OK, Igor , acepto, publicalo. Solo necesitaré mucha ayuda y horas... Je,Je. Otros dedican el tiempo libre a la Cerveza,etc. Bueno he comenzado intentando implementar parte de tu proyecto Excel arduino a Wincc, porque sabes que dicha aplicación tiene Vbscript, pero no me reconoce algunos comandos como NETCOMM2.
    Primera cuestión, has visto algo de Wincc?

    ResponderEliminar
  25. También te comento que en el propio soft, aparecen unas conexiones tipo modicon Modbus seleccionas hay rs 485. Por lo tanto, otra puerta y otra cuestión. Es compatible este protocolo que has diseñado con modicon Modbus 485.
    Permite hasta 19200,par,8,1. Je,je.
    La trama dice que es RTU Standar.

    ResponderEliminar
  26. Hola,

    me gustaría saber para que sirve el bit se signo.

    Gracias.

    ResponderEliminar
  27. Hola Igor,
    Gracias por tu aporte, me esta sirviendo de gran ayuda pero tengo dos dudas en el código.
    1- if (times_repeat<4) yo entiendo que este apartado se ejecuta 5 veces y no se el porque (perdona mi ignorancia pero es que soy nuevo)

    2- while (((millis()-previous_time)<500) && (Serial.available()!=15)){ ;;} No se para que funciona.

    Un saludo

    ResponderEliminar
  28. Hola Lluis,

    La idea es que cuando mandas un mensaje, esperas la respuesta de tu esclavo diciendo que todo ha ido bien. Este mensaje es el ACK. Entonces tienes que tener mecanismo para asegurarte que se recibe el mensaje. Uno sencillo es mandarlo y estar esperando la respuesta. Necesitas tener un timeout, es decir el tiempo máximo que se queda esperando a que dicha respuesta llegue, antes de volver a mandar otra vez el mensaje.
    Entonces en el ejemplo, si se recibe un mensaje que no es el ACK o no se recibe nada, se intenta 5 veces más.
    Todos estos mecanismos se pueden mejorar, pero es un ejemplo básico.

    Espero que quede más claro.

    Saludos.

    ResponderEliminar
  29. Gracias Igor, me has resuelto las dudas. Ahora se que el while es necesario.

    Un saludos Lluis

    ResponderEliminar
  30. Hola Igor, esta muy bueno este post, pero tengo una duda, yo ahorita he intentado comunicar los atmega sin utilizar las placas Arduino, los he conectado directo (Tx del maestro al Rx Esclavo) y nomas no da nada, ¿Tendrá algo que ver las placas Arduino? y la otra, ¿el rs485 es necesario para comunicar los atmega? como apenas estoy empezando pues se me hace mucha duda. Gracias y Saludos!

    ResponderEliminar
  31. Hola Igor,

    muchas gracias por documentar tan bien el trabajo.

    Soy nuevo en todo esto de los bites y los bytes y no consigo convertir un variable float en un array de 4 bytes que pueda asignar a los 4 bytes de datos de la trama.

    Esto me sería muy útil para poder recoger datos de sensores!


    Gracias!

    ResponderEliminar
  32. Muy bueno tu articulo, ayudo mucho...
    Ahora lo que quiero hacer es que los 3 arduinos sean master y esclavo a la vez (es decir, que reciban y envien datos, cualquier dato). Esto es posible? No encuentro nada por Google...

    ResponderEliminar
  33. Hola que tal ...
    Es muy interesante tu trabajo sobre todo porque ayuda a muchas personas
    me gustaria saber si podrias pasarme los diagramas de conexion y algunos
    ejemplos con los cuales pueda probar te lo agradeceria de ante mano :D

    btk_20@hotmail.com

    Julio

    ResponderEliminar
  34. hola que tal:

    Soy nuevo en esto y ando un poco perdido, alguien me podría echar una mano en el tema de comunicaciones por modbus con arduino.

    Gracias.

    ResponderEliminar
  35. hola que tal:
    Lo primero agradecer el trabajo a igor sin gente asi muchos no aprenderiamos nada. Soy nuevo en este mundo como otros muchos. me gustaria se podrias explicar como colocar el valor de un sensor en el array de los 4 datos de la trama pues lo he intentado de todas las maneras que en mi cabeza entran pero no consigo que funcione bien. creo que ya te han echo la pregunta pero como no he visto respuesta te lo vuelvo a preguntar aver si podrias ayudarme. desde ya muchas gracias

    ResponderEliminar
    Respuestas
    1. Hola,
      Voy a explicar como hacerlo para un word (2 bytes = 16 bits). Luego es extrapolarlo si se requieren más, aunque la conversión del Arduino es de 10 bits (entre 0 y 1023), por lo que cabe en 2 bytes:

      word LecturaADC=analogRead(A0);
      byte mybyte1;
      byte mybyte2;

      // Byte 1 tiene el byte mas significativo
      mybyte1=(LecturaADC & 0xFF00) >>8;
      mybyte2=(LecturaADC & 0x00FF);

      Lo que se hace es una mascara para quedarte con la parte que se quiere, y luego una operación de bit shift.
      http://arduino.cc/en/Reference/Bitshift
      http://arduino.cc/en/Tutorial/BitMask

      Entonces ya tienes el dato que quieres enviar separado en 2 bytes. Cuando lo recibes, hay que hacer la operación contraria. Esta vez, para enseñar otro método, en vez de operación bitwise, lo hago asi:

      word DatoRecibido= 256*byte1+byte2;

      Nota.- Las operaciones de desplazar bits a izquierda o derecha, es equivalente a multiplicar o dividir entre 2,4,8,.... (según los bits a desplazar).


      Espero que ayude,

      Salu2



      Igor

      Eliminar
    2. uisss creo k he repetido la misma pregunta dos veces , pido mil perdones. muchas gracias por la respuesta igor voy a probar a ver si me sale y te cuento

      Eliminar
    3. buenas igor decirte que he probado tu explicacion y funciona correctamente el problema que ahora tengo es que el sesonr que tengo en el esclavo es un sensor de ultrasonidos por ping y yo creo que la libreria ultrasonic no se deve llevar muy bien con el codigo pues no me envia nunca el valor que devuelve el sensor si yo a la variable donde almaceno el valor del sensor le asigno un valor fijo en vez de el del sensor lo envia bien asique supongo que tendra ver con los delays de la libreria ultrasonic lo que aga que no se envie ningun dato. crees que estoy en lo cirto? o me equivoco en algo? muchas gracias por tu tiempo

      Eliminar
    4. te añado algo mas de informacion igor si en vez de tomar la lectura del sensor ping tomo la lectura de un potenciometro si envia bien los datos, eso si a excepcion de que si el valor del pot es inferior a 255, que en caso de ser menor ya no envia bien la trama y por lo tanto no recive el maestro el valor.

      Eliminar
    5. igor decirte que ya he solicionado lo del sensor ping la solucion a sido sumarle 1000 al valor obtenido por el sensor antes de hacer la conversion y luego en el maestro al dato recivido dividirlo entre 1000 y ya muestra correctamente el dato en un lcd muchas gracias por tu ayuda.

      Eliminar
  36. hola igor
    Estoy tratando de hacer un proyecto con un arduino como maestro y 10 sensores de Co2 http://www.senseair.se/wp-content/uploads/2012/02/PSP110_k30.pdf. Estos sensores entienden modbus de 8bytes en el canal UART como lo menciona en el datasheet. La referencia de como aplicar el protocolo esta en http://senseair.se/wp-content/uploads/2011/05/10.pdf donde tambien menciona de la implementacion mediante un bus fisico rs485. No soy un experto en electronica y un principiante en rs485, pero despues de ver varios videos y buscar mucha informacion, y sobre todo, leer tu post en el foro de arduino y luego tu blog creo que puedo intentar hacerlo. Mi gran duda es, podrias decirme como se hace para referenciar un solo sensor dentro de la red rs485?. Creo que esto lo terminaria haciando el sensor. En tu ejemplo existe un arduino en cada extermo y en el codigo se hardcodea el ID del sensor, pero aqui solo esta la logica interna del sensor, y mi idea es conectarlo directamente al MAX485. Es posible esto? puedo leer cada sensor por separado ? Lamentablemente en los ejemplos que brinda el doc, la informacion esta direxionada mediante 0xFE (any sensor)...
    Agradeceria muchisimo tu ayuda.

    Saludos! y excelente el trabajo realizado!

    ResponderEliminar