Estas últimas dos semanas he estado haciendo pruebas con el estándar de comunicación RS485, este estándar es el que a nivel de físico permitirá la interconexión de todos los módulos esclavos a su maestro en el proyecto de domótica.
Hardware
Podéis encontrar muchísima información sobre el RS485 en internet pues es muy usado en el sector industrial, pero a modo de resumen sus características son las siguientes:
- Permite interconectar de 32 a 254 estaciones según chip usado.
- Permite comunicaciones a larga distancia, hasta 1200 metros.
- Su velocidad de transmisión varia entre 35Mbit/s a 100kbit/s según distancia del cable y tipo de chip.
- Funciona con 2 o 4 cables de datos:
- Con 2 cables:
- Comunicación bidireccional.
- Todas las estaciones pueden hacer de Maestro y Esclavo.
- Half duplex: No permite que dos estaciones envíen datos al mismo tiempo.
- Con 4 cables:
- Comunicacion bidirecional.
- Solo puede haber 1 Maestro y N Esclavos.
- Full duplex: Permite enviar datos por 2 hilos y recibir por los otros 2 al mismo tiempo.
- Con 2 cables:
- Al ser un estándar de comunicación diferencial los bits se obtienen de la diferencia de tensión entre ambos hilos y no entre masa, por lo que no hace falta cable de GND.
- Por lo mismo que el punto anterior, tampoco le afectan las interferencias usando cable trenzado, como el de ethernet.
Las primeras pruebas del estándar RS485 que realice fue con los chips Maxim MAX490 que en teoría permiten una comunicación Full duplex con 4 hilos, este chip tiene 8 patas: 2 de alimentación, 2 de datos para el Arduino, y 4 de datos para la conexión entre las estaciones. Sin embargo solo conseguí que el maestro se pudiese conectar a todos los esclavos, pero no que todos los esclavos pudieran conectarse al maestro… solo conseguí que el esclavo mas cercano al maestro pudiera enviarle datos.
En otros chips como el MAX485 o el MAX491 hay unos pines que permiten indicar si vas a usar el canal para transmitir datos o para recibirlos, alta o baja impedancia, como el MAX490 no los tiene parece que los esclavos chocaban entre sí aun programando el código para que no coincidieran en ningún momento.
Finalmente me he decidido por los Maxim MAX485 que aun que no permiten que la comunicación simultanea en ambos sentidos (solo hay 2 hilos), funcionan bien. El lado bueno es que me ahorrare una pasta en el cable de cobre ya que el de teléfono de 6 hilos es bastante mas caro y raro de encontrar que el de 4 🙂
Software
Hasta ahora tenemos el estándar de comunicación, pero también hace falta un protocolo de datos para que las estaciones se entiendan entre si. Generalmente se usa el protocolo CanBus, usado en muchos sitios incluso en los coches, el problema es que en Arduino se encuentran muchísimas librerías CanBus-Esclavo, pero pocas o ninguna CanBus-Maestro ya que están mas pensadas para conectarte a maestros de terceros: coches, sistemas industriales, etc.
Tras buscar muchas librerías he encontrado una que me ha gustado bastante, que aunque no es perfecta funciona bien y es con la que estoy haciendo pruebas. Realmente lo de la librería es secundario pues todas usaran el mismo hardware así que se puede cambiar si mas adelante se queda corta o crear una a medida.
La librería de la que hablo es la Inter-Chip Serial Communications, o ICSC, usada por ejemplo en los ChainDuinos, la comunicación se realiza mediante diferentes paquetes con los cuales incluso se comprueba que hayan llegado completos.
Una de las cosas malas que tiene la librería es que vienen solo 2 ejemplos y tampoco es que sean muy buenos, yo he creado unos cuantos para probar el funcionamiento, son los siguientes y vienen cada uno de ellos con una pequeña explicación:
Comunicación en un solo sentido: Control LED
Como primer ejemplo he intentado que el código fuese lo mas simple posible, permite que un Arduino que hace de maestro active los LEDs de los demás que hacen de esclavos. Con cada comunicación les envía una petición a los esclavos y estos cambian el estado del led al estado contrario en el que estén.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <ICSC.h> #define ADDR_MASTER 0 #define BUS_CS 2 #define CMD_LED 'L' #define NUM_SLAVES 3 //Replaced by the total number of slaves ICSC bus(Serial1, ADDR_MASTER, BUS_CS); unsigned char slave = 0; void setup() { Serial1.begin(115200); bus.begin(); } void loop() { slave = (slave+1 <= NUM_SLAVES) ? slave+1 : 1; bus.send(slave, CMD_LED); delay(1000); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <ICSC.h> #define ADDR_SLAVE 1 //Replace with the number ID of slave #define BUS_CS 2 #define CMD_LED 'L' #define pLED 13 ICSC bus(Serial1, ADDR_SLAVE, BUS_CS); void setup() { Serial1.begin(115200); bus.begin(); bus.registerCommand(CMD_LED, &led); pinMode(pLED, OUTPUT); } void loop() { bus.process(); } void led(unsigned char sender, char command, unsigned char len, char *data) { digitalWrite(pLED, !digitalRead(pLED)); } |
Comunicación en ambos sentidos: Control LEDs
En el segundo ejemplo la comunicación es bidireccional y tanto el maestro como los esclavos se comunican entre sí. Para ello el maestro solicita a cada esclavo que encienda su led y tras terminar que confirmen que han realizado la tarea, de este modo el maestro recibe esa confirmación e indica mediante otro led que todo ha ido bien.
El maestro tiene 4 leds, el del pin 13 que parpadea al mismo tiempo que el esclavo al que se le solicita que lo encienda, y otros 3 leds uno para cada esclavo para indicar quién ha devuelto la respuesta.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#include <ICSC.h> #define ADDR_MASTER 0 #define BUS_CS 2 #define CMD_LED 'L' #define CMD_RESPONSE 'R' #define NUM_SLAVES 3 //Replaced by the total number of slaves #define pLED0 13 #define pLED1 40 #define pLED2 41 #define pLED3 42 ICSC bus(Serial1, ADDR_MASTER, BUS_CS); unsigned char slave = 0; void setup() { Serial1.begin(115200); bus.begin(); bus.registerCommand(CMD_RESPONSE, &response); pinMode(pLED0, OUTPUT); pinMode(pLED1, OUTPUT); pinMode(pLED2, OUTPUT); pinMode(pLED3, OUTPUT); } void loop() { bus.process(); if( millis() % 1000 == 0 ) { slave = (slave+1 <= NUM_SLAVES) ? slave+1 : 1; bus.send(slave, CMD_LED); digitalWrite(pLED0, HIGH); delay(500); digitalWrite(pLED0, LOW); } } void response(unsigned char sender, char command, unsigned char len, char *data) { if( sender == 1 ) { digitalWrite(pLED1, HIGH); delay(500); digitalWrite(pLED1, LOW); } else if( sender == 2 ) { digitalWrite(pLED2, HIGH); delay(500); digitalWrite(pLED2, LOW); } else if( sender == 3 ) { digitalWrite(pLED3, HIGH); delay(500); digitalWrite(pLED3, LOW); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <ICSC.h> #define ADDR_MASTER 0 #define ADDR_SLAVE 1 //Replace with the number ID of slave #define BUS_CS 2 #define CMD_LED 'L' #define CMD_RESPONSE 'R' #define pLED 13 ICSC bus(Serial1, ADDR_SLAVE, BUS_CS); void setup() { Serial1.begin(115200); bus.begin(); bus.registerCommand(CMD_LED, &led); pinMode(pLED, OUTPUT); } void loop() { bus.process(); } void led(unsigned char sender, char command, unsigned char len, char *data) { digitalWrite(pLED, HIGH); delay(500); digitalWrite(pLED, LOW); bus.send(ADDR_MASTER, CMD_RESPONSE); } |
Comunicación en ambos sentidos: Envió de datos
En los dos ejemplos anteriores hemos visto como enviar paquetes de datos vacíos entre Arduinos, básicamente son pings y ya esta. Pero eso no es todo lo que nos permite la librería, también podemos enviar datos primitivos como números enteros, carácter o arrays de caracteres, concretamente los tipos: int, long, char y char*.
No esta mal pero es mejorable ya que podemos querer enviar números decimales o booleanos, o podemos querer enviar varios datos en el mismo paquete de datos. Viendo los métodos send de la librería no hay ninguno que permita enviar un array de bytes… pero si de chars que viene siendo lo mismo.
En combinación con las ya conocidas estructuras Struct y Union podemos saltarnos esta limitación, se usan como siempre con la diferencia que el array de la estructura Union es de tipo char en vez de tipo byte para que no de error de compilación por la librería.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
#include <ICSC.h> #define ADDR_MASTER 0 #define BUS_CS 2 #define CMD_CALC 'C' #define CMD_RESULT 'R' #define NUM_SLAVES 3 //Replaced by the total number of slaves #define count(x) sizeof(x)/sizeof(*x) struct CALC{ int numA; int numB; char opts; int result; }; union MEMORY{ CALC calc; char b[sizeof(CALC)]; } memory; ICSC bus(Serial1, ADDR_MASTER, BUS_CS); unsigned char slave = 0; char operations[] = {'+', '-', '*'}; void setup() { Serial1.begin(115200); bus.begin(); bus.registerCommand(CMD_RESULT, &result); Serial.begin(9600); } void loop() { bus.process(); if( millis() % 1000 == 0 ) { slave = (slave+1 <= NUM_SLAVES) ? slave+1 : 1; memory.calc = { millis()/1000, 2, operations[slave-1] }; bus.send(slave, CMD_CALC, count(memory.b), memory.b ); } } void result(unsigned char sender, char command, unsigned char len, char *data) { for( int i=0 ; i<len ; i++) { memory.b[i] = data[i]; } Serial.print("The slave "); Serial.print(sender); Serial.print(" has calculated: "); Serial.print(memory.calc.numA); Serial.print(" "); Serial.print(memory.calc.opts); Serial.print(" "); Serial.print(memory.calc.numB); Serial.print(" = "); Serial.print(memory.calc.result); Serial.println(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include <ICSC.h> #define ADDR_MASTER 0 #define ADDR_SLAVE 1 //Replace with the number ID of slave #define BUS_CS 2 #define CMD_CALC 'C' #define CMD_RESULT 'R' #define count(x) sizeof(x)/sizeof(*x) struct CALC{ int numA; int numB; char opts; int result; }; union MEMORY{ CALC calc; char b[sizeof(CALC)]; } memory; ICSC bus(Serial1, ADDR_SLAVE, BUS_CS); void setup() { Serial1.begin(115200); bus.begin(); bus.registerCommand(CMD_CALC, &time); } void loop() { bus.process(); } void time(unsigned char sender, char command, unsigned char len, char *data) { for( int i=0 ; i<len ; i++) { memory.b[i] = data[i]; } switch( memory.calc.opts ) { case '+': memory.calc.result = memory.calc.numA + memory.calc.numB; break; case '-': memory.calc.result = memory.calc.numA - memory.calc.numB; break; case '*': memory.calc.result = memory.calc.numA * memory.calc.numB; break; } bus.send(ADDR_MASTER, CMD_RESULT, count(memory.b), memory.b); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
The slave 1 has calculated: 0 + 2 = 2 The slave 2 has calculated: 1 - 2 = -1 The slave 3 has calculated: 2 * 2 = 4 The slave 1 has calculated: 3 + 2 = 5 The slave 2 has calculated: 4 - 2 = 2 The slave 3 has calculated: 5 * 2 = 10 The slave 1 has calculated: 6 + 2 = 8 The slave 2 has calculated: 7 - 2 = 5 The slave 3 has calculated: 8 * 2 = 16 The slave 1 has calculated: 9 + 2 = 11 The slave 2 has calculated: 10 - 2 = 8 The slave 3 has calculated: 11 * 2 = 22 The slave 1 has calculated: 12 + 2 = 14 The slave 2 has calculated: 13 - 2 = 11 The slave 3 has calculated: 14 * 2 = 28 The slave 1 has calculated: 15 + 2 = 17 The slave 2 has calculated: 16 - 2 = 14 The slave 3 has calculated: 17 * 2 = 34 The slave 1 has calculated: 18 + 2 = 20 The slave 2 has calculated: 19 - 2 = 17 The slave 3 has calculated: 20 * 2 = 40 The slave 1 has calculated: 21 + 2 = 23 The slave 2 has calculated: 22 - 2 = 20 The slave 3 has calculated: 23 * 2 = 46 The slave 1 has calculated: 24 + 2 = 26 The slave 2 has calculated: 25 - 2 = 23 The slave 3 has calculated: 26 * 2 = 52 The slave 1 has calculated: 27 + 2 = 29 The slave 2 has calculated: 28 - 2 = 26 The slave 3 has calculated: 29 * 2 = 58 The slave 1 has calculated: 30 + 2 = 32 The slave 2 has calculated: 31 - 2 = 29 The slave 3 has calculated: 32 * 2 = 64 The slave 1 has calculated: 33 + 2 = 35 The slave 2 has calculated: 34 - 2 = 32 The slave 3 has calculated: 35 * 2 = 70 The slave 1 has calculated: 36 + 2 = 38 The slave 2 has calculated: 37 - 2 = 35 The slave 3 has calculated: 38 * 2 = 76 The slave 1 has calculated: 39 + 2 = 41 The slave 2 has calculated: 40 - 2 = 38 The slave 3 has calculated: 41 * 2 = 82 The slave 1 has calculated: 42 + 2 = 44 The slave 2 has calculated: 43 - 2 = 41 The slave 3 has calculated: 44 * 2 = 88 The slave 1 has calculated: 45 + 2 = 47 The slave 2 has calculated: 46 - 2 = 44 The slave 3 has calculated: 47 * 2 = 94 The slave 1 has calculated: 48 + 2 = 50 The slave 2 has calculated: 49 - 2 = 47 The slave 3 has calculated: 50 * 2 = 100 The slave 1 has calculated: 51 + 2 = 53 The slave 2 has calculated: 52 - 2 = 50 The slave 3 has calculated: 53 * 2 = 106 |
Hola Alberto,
Enhorabuena por tu blog, estoy haciendo un proyecto con un ROV (submarino tripulado remoto) y lo quiero hacer con comunicación rs485.
Llevaría cuatro motores brushless, un sensor de presión para medir la profundidad, un sensor para medir la alcalinidad del agua, otro para medir la turbiedad. Estos sensores irían en el arduino exclavo, donde esta información llegaría al arduino maestro y este indicaría los valores de esos sensores en una pantalla lcd.
Los motores brushless se controlan por joystinck que son los típicos usados en las consolas de video juegos, con potenciómetro de 10 k
Desde el exterior se podrá activar dos o más salidas, digitales para tema de iluminación.
Por favor Alberto, me puedes echar una mano en este proyecto.
Gracias de antemano.
Un saludo
Diego
Hola Diego,
No tengo tiempo para ir haciendo los proyectos de la gente, si eres novato con Arduino te recomiendo que hagas funcionar cada parte del proyecto de forma separada hasta que entiendas y te funcione correctamente todo (circuito y código), por último juntas todas las partes y ya lo tendrás.
Si te atascas con algo seguro que alguien ha documentado más información que puede servirte para seguir y solucionar el problema, si no pues preguntas al que lo documentó o en foros/webs donde se reúna gente que entienda.
Por mi parte si tienes dudas sobre esta entrada o algo que haya publicado sólo tienes que preguntar.
Saludos.
Muy buena pinta! Tendré que probarlo Estaba buscando algún ejemplo de código puesto que el Hardware ya lo tengo montado. He estado trabajando con la librería PJON, pero sólo consigo comunicar 1 slave con el controlador. Los otros 2 slaves todavía no he conseguido hacerlos funcionar en el mismo bus (1 controlador + 3 slaves)
Por cierto, viendo las características que tiene que tener el bus RS485 y por lo que he podido leer, he deducido (no sé si estaré equivocado):
1) las resistencias pull-up y pull-down solo han de estar en 1 único nodo.
2) la resistencia de «carga» de final de bus, sólo ha de estar en el primer nodo del bus y en el último, pero no en los intermedios.
Para distancias tan cortas como la de la foto supongo que no tendrá incidencia (quizá sí si se utilizan velocidades muy grandes), pero para buses de cientos de metros probablemente sí…
Es decir, según lo anterior:
– en dos de los chips TTL->RS485 hay que quitar las 3 resistencias. (nodos intermedios)
– en el nodo MASTER (que se supone que es el primero del bus, es decir, el que está en un extremo) habría que dejar las 3.
– en el último nodo del bus habría que dejar únicamente la resistencia de carga.
Esto se puede hacer con un destornillador, levantando la resistencia con mucho cuidado…
Hola! Muchas gracias por el excelente tutorial! Llevo varios días buscando una alternativa para poder implementar una comunicación entre varias estaciones con RS485. Las finalistas fueron la librería de Nick Gammon «Non blocking» y ICSC, he tenido varios problemas con ambas para interpretar el manejo de las distintas variables en memoria, etc. Finalmente tu blog me ayudó a poder hacer funcionar un set up mínimo como para poder seguir trabajando.
Como dices, es una lástima que la librería no posea mas ejemplos, estoy haciendo un gran esfuerzo por adaptar tus ejemplos a mi proyecto, es probable que vuelva con alguna consulta pero no quería dejar de agradecerte por compartir este trabajo.
Saludos!
Wow, esto es una mina de oro. Muchas gracias!
Hola, soy nuevo al mundo de MobBus, de todas la librerias que a visto esta se me hace las mas fácil para entender. pero si tengo una pregunta, y le a entendado pero no mas no puedo hacer.. si queremos hacer el processo al reves, como comensar ?
Del ejemplo final, obtuve el código con el cual pude transferir data de dos esclavos a un maestro. Use tres arduinos mega para hacer debug ya que estos tienen tres puerto serie cada uno. Muy buena librería, gracias