Construir un protocolo serie entre 2 Arduino

Hola a tod@s.

He estado revisando por Google y en la traducción de la página Arduino al Castellano, y no he encontrado o entendido información para realizar lo que os quiero exponer a continuación (Incluso en algunos casos la traducción esta incompleta o el código no es igual al original en ingles), sobre todo cuando he encontrado problemas en las traducciones. Si, tengo un problema con el ingles y en estos momentos es cuando me acuerdo cuando no lo estudie cuando tenía que hacerlo.

Estoy abierto a sugerencias de métodos de estudios para el ingles, aunque creo abre probado todo, menos la hipnosis, la dedicación ha tenido mucho que ver también X-)

Al grano:

El proposito es disponer de un Arduino en el que conectarle un Joystick formado por 2 potenciómetros (utilizando 2 entradas analógicas); un tercer potenciómetro (otra entrada analógica), y un pulsador (entrada digital). Leer los datos de estas entradas y mandarlas por el puerto serie para que un 2º Arduino lo reciba por puerto serie, y con los valores recibidos mover 2 servos (con los valores del joystick); controlar la velocidad y sentido de giro de un motor brushless (con el tercer potenciómetro); y con el valor del pulsador, controlar una salida digital.

Resumiendo. Utilizar un Arduino como una emisora de Radio Control, y el 2º Arduino como receptor de Radio Control. Después de crear el protocolo, el encampsular la comunicación serie en una señal inalámbrica será cuestión del elemento que acople al puerto serie en ambos Arduinos.

El conflicto lo tengo en el modo de mandar y recibir los datos por el puerto serie entre los dos Arduinos (Saber sacarle el partido a los comandos que controla el puerto serie).

Voy a compartir con vosotros una tormenta de ideas y luego una solución que he pensado, pero que no se si será la más correcta, y me gustaría me corrigierais si me equivoco, afirmarais si es cierto y me orientarais "en el buen camino".

Tormenta de ideas

-Con el comando "Serial.print" y "Serial.printlr" solo puedo mandar un único valor de una vez. Entonces, me obligo a realizar un envío por cada valor, no siendo posible mandar un carácter identificativo + un valor para identificarlo, algo como "x24" para saber que el dato "24" es para usarlo como "x".
-Con el comando "Serial.read" puedo leer los datos que están en el buffer del puerto serie. ¿Pero que leo?,¿un byte o 128 byte, que son los que tiene el buffer?
-¿De que manera puedo decirle al 2º Arduino que el dato que le llega es de tal sensor, para que luego lo utilice? o de otra manera ¿Como sabe el 2º Arduino que el dato que tiene en su buffer es el que espera para atacar a uno de sus elementos (servos, control motor o salida digital)?.
-Lo sencillo sería mandar en un solo envío por puerto serie un array con los datos de los sensores, y luego el 2º Arduino lee el buffer del puerto serie cada x tiempo ¿Se puede enviar o leer un array por puerto serie?

La idea que tengo para realizar el código:

Cambiar el concepto principal de: El Arduino Emisor es el que controla la comunicación mandando datos, y el Arduino receptor es tonto y solo mira lo que le llega por el puerto serie.
Por este otro concepto: Como solo puedo enviar y leer un dato en cada acción con el puerto serie, el Arduino Receptor será el que tome el control de la comunicación pidiéndole al Arduino emisor los datos que el necesita. Expongo el seudo código.

Seudo código

[Arduino Receptor] mando el carácter "x" por puerto serie, para que el Arduino emisor me envíe el valor del potenciómetro x del joystick.
[Arduino Receptor] bacio el buffer del puerto serie con el comando "Serial.fluch", y con la ayuda del comando "Serial.available" espero a que lleguen datos al buffer.
[Arduino Emisor] Estoy atendo al puerto serie con el comando "Serial.available" y cuando reciba una "x" envío el valor del potenciómetro del joystick con el comando "Serial.print"
[Arduino Receptor] Al recibir un dato por el puerto serie, lo entenderé como valor del eje X del joystick y lo utilizaré para mover el servo correspondiente.

Esto será idéntico con los demás sensores (potenciómetro eje Y del joystick, el potenciómetro para el control del motor brushless, y el pulsador para controlar la salida digital).

Dudas sobre mi solución: ¿No os parece que se realizan demasiadas llamadas al puerto serie para el envío de un solo datos?, y contra más datos, el tiempo en enviar todo se multiplica, a parte que penalizaré en consumo de batería de ambos Arduinos.

Espero haberme hecho entender.

Gracias por dedicarme unos minutos a leerme :wink:

Buenas nazgul.
Casualmente me propongo algo parecido a lo que tu pretendes. Por lo que me encuentro con el mismo problema. De momento estoy en esa fase que tu tan bien defines como: tormenta de ideas.
Siento no aportar ninguna respuesta, sino todo lo contrario, me explico.
Se me ocurre que se podría enviar primero un caracter identificando al eje y en en siguiente envio mandamos el dato numerico.
De esta manera el programa espera primero a un caracter, por ejemplo "x". Eso hace que se derive el programa a una rutina corresponciente al eje x, que queda a la espera del segundo dato que nos da el valor que deba volcar al pin corresponciente. Una vez hecho esto retorna al inicio.
De entrada ya se me ocurre el problema que supondria una perdida de un dato, pero bueno, solo pretendía darte mi idea.
Un saludo.

Buenas Nazgul.
OLVIDATE DE TODO LO QUE ESCRIBÍ ANTES!!!!
Vamos a ver, cómo te explico esto. He estado revisando http://arduino.cc/es/Reference/String .
En tu post observo que los datos que tratas de mandar por Serial.Print son del tipo “x24”, “y43”, etc.
O sea string’s. Lo que viene siendo una Array. Que si tu las guardas en un variable (p.e. InData [3]) tendrás en InData[1] la letra del eje y en [2] y [3] los dígitos. Si haces pasar InData[1] por un código condicional (if, case…) puedes mandar al programa a la zona de código en la que se espera el calor del eje correspondiente. Un vez allí una operación del tipo Valor=(InData[2]*10)+InData[3] te da el valor a sacar por la salida analógica (o lo que sea).
Ya te comenté que estoy en algo similar a lo tuyo. Cuando tenga algo de código generado te lo enseño.
Un saludo.

Gracias SatiTroRey por la aportación, ya pensaba que nadie savia utilizar el puerto serie tras 128 lecturas y ninguna respuesta. :roll_eyes:

Con la pista que me has datos, estoy intentando picar código para hacer pruebas en la creación del array a enviar por puerto serie.

Estoy teniendo un problema básico de concepto. como trocear el valor de 3 dígitos en 3 caracteres ascii. primero, no se como cortarlo, supongo que definiendo un array, pero no lo veo claro, y a demás no entiendo bien como trasformar un valor de tipo integer a string, se supone que con la función "char(int_x);" convierte el valor de la variable "int_x" definida previamente como integer, en tipo char/strings, pero me muestra el siguiente mensaje al compilar:

 sketch_feb19b:146: error: conflicting declaration 'char int_x1'
sketch_feb19b:142: error: 'int_x1' has a previous declaration as 'int int_x1'

Es tan escueta la explicación http://arduino.cc/es/Reference/CharCast que no entiendo donde esta el problema ¿no puede guardarse el valor transformado en la misma variable porque se ha declarado previamente como integer y no coincide? me estoy perdiendo con la contradicción en lo explicado en la página y lo que me dice el compilador (v1.0).

Creo que ya lo tengo, más o menos. en vez de utilizar la función "char()" para transformar un valor integer en string, lo estoy haciendo directamente con arrays, introduciendo valores int en un array de tipo char.

Ejemplo: 3 variables tipo int que tienen los valores numéricos 97, 98 y 99. los introduzco en un array de tipo int y luego creo un array de tipo char con los datos del array int. al enviar los valores del array tipo char por el canal serie, se pintan las letras "a" para la primera posición del array, "b" para la segunda, y "c" para la tercera. El cuarto carácter es para el "NUL" que se introduce solo en todos los arrays de tipo char.

int x= 97;
int y= 98;
int m= 99;

int val[] ={x, y, m};

char char_y[4] = {val[0], val[1], val[2]};
Serial.print(char_y[0]);

Resultado = "a" por pintar el ascii 97.

Si pongo Serial.pront(char_y); escribe todo el array, "abc" más el caracter NUL que no se ve al no ser printable.

Ya te contaré que tal los avances.

como lo he hecho yo en alguno de mis proyectos.

primero mandas un carácter para saber cuando empiezan los datos, por ejemplo $

luego elijes la trama, valores separados por comas por ejemplo. si mandas 3 valores la trama seria por emeplo asi
$123,23,56

luego aplicas una funcion para pasar de char a int u otro tipo y lo haces en las posiciones siguientes a $ y las comas.

os pueden ayudar:

http://arduino.cc/forum/index.php/topic,69309.0.html

que estan las funciones para aplicar directamente ( a INT)

y

http://arduino.cc/forum/index.php/topic,69929.0.html

por si quereis ademas pasar floats por serie

un saludo

Hola SantiTroRey y SrDonGato.

Perdonar por no responder antes. ando liado estos días y no puedo dedicarle el tiempo a cacharrear.

He leído por encima los enlacer SrDonGato, y ahora toca probarlo y masticarlo.

Os comento.

Por cierto, después de actualizar Arduino a la versión 1.0, he visto que hay más ejemplos, entre ellos ejemplo de canal serie, voy a probarlos.

Muchas gracias, y perdonar por el tiempo sin responder.

Por cierto, ¿Podéis recomendarme un libro sobre el lenguaje Procesing en Castellano? he comenzado a leer algo en la home oficial en ingles. En algún hilo antiguo he visto enlaces supuestamente a páginas con las referencias del código traducidas, pero los link están caídos.

He estado realizando pruebas con la librería que generaste SrDonGato. Siento decir que de primera no me sirve.

La libreria es para string de tamaño constante, y como bien dices en el hilo, Cabe la posibilidad de que no se sepa en un primer momento donde están las primeras cifras de los números, para ello hay que hacer un recorrido del string detectando en qué posiciones están las primeras cifras y luego aplicar la función en las posiciones obtenidas.

Así leído parece fácil :wink: pero programarlo tiene su tela. Estoy en ello.

He simplificado el protocolo, y pensaba leer por el puerto serie la trama "$60,60,160,0#", jugando con los caracteres de comienzo y fin de trama ir analizando las posiciones del string para detectar la posición de las comas, hasta el final del mismo y de ahí utilizar tu librería, ya que los valores pueden ser de 1 o 3 caracteres, y eso me modifica la posición del primer nº de cada dato y no puedo así pasarselo a tu librería.

No he encontrado una función que me devuelva el tamaño de un string, eso creo que me ayudaría.

No se si me estoy complicando mucho para mandar 4 valores de un Arduino a otro. ¿Que te parece?.

las funciones que hay valen para un string de cualquier tamaño, no importa si son de 10 o 30 caracteres

para saber la longitud del string (objeto) tienes length() - Arduino Reference

para saber cuantos caracteres tienes en un string (array) tienes el sizeof() - Arduino Reference
sizeof(string)/sizeof(char)

saber donde estan los números no es dificil, puedes sacar los numeros a la vez que recorres el array y sacar las posiciones y luego sacar todos los números a la vez. Usa un indice de posicion para recorrer toda el string o array

trabajo de noches esta semana, tengo poco tiempo para arduino, pero pon el codigo y la trama que quieres leer a er si te puedo orientar de como hacerlo

un saludo ¡

Gracias SrDonGato, pensaba que tardarías más en responder, las horas que son. jejejej

Gracias por la orientación, me has pillado leyendo el hilo de Igor R sobre la librería FSM.

Con las herramientas stringlength y sizeof, pude que las neuronas se me activen. :wink: gracias.

Los ojos se me están cayendo por las horas que son, por los datos, parece interesante.

Sobre la trama, es que me da igual, jejejej la intención es leer 3 potenciómetros y un pulsador en un Arduino, mandarlo por puerto serie. En el 2º Arduino recibirlos por puerto serie y con los datos, en un array utilizarlos con la librería servo para mover 2 servos, el tercer valor para tener un rango de nº con los que controlar la velocidad y giro de un motor y el 4º dato 0/1 para poner a 0/1 una salida digital.

El código que he creado hasta ahora, es la parte emisor. No me importa el formato de la trama, por lo que se puede cambiar a gusto, teniendo en cuenta luego en la recepción de la traba como masticarla.

  //Definir nº de pines donde colocar los sensores
  //Pin analogico de entrada para el eje X Joystick, Y Joystick, velocidad y sentido de giro Motor, orden de grabacion camara
int pin_eje_x = A0;
int pin_eje_y = A1;
int pin_motor = A2;
int pin_rec = 2;

  // Definir e inicializar variables donde guardar lecturas sensores
  //Recordatorio codigo: un Array o matriz empieza a contas sus casillas desde 0 no desde 1.
int valor_sensor [] = {90, 90, 128, 0};  

  //Variable utilizada como contador para las funciones for  
int i;

void setup()
{
  //Definir el pin del sensor digital como entrada.
pinMode(pin_rec, INPUT);


  //Inicializar el puerto serie para su utilizacion a 9600 bps
Serial.begin(9600);
}

void loop()
{
  //Leer los datos de las entradas analogicas y digital, y guardarlo como valores integer en el array.
valor_sensor[0] = analogRead(pin_eje_x);
valor_sensor[1] = analogRead(pin_eje_y);
valor_sensor[2] = analogRead(pin_motor);
valor_sensor[3] = digitalRead(pin_rec);

  //Normalizar valores leidos para ser tratados correctamente por los servos (0-180 grados); motor (0-255 punto medio 128).
  //Orden de grabacion no hace falta normalizar valor al ser una entrada digital (0 o 1) 
for (i=0; i<2; i=i++) {
  valor_sensor[i] = map(valor_sensor[i], 0, 1023, 0, 180);
}
  valor_sensor[2] = map(valor_sensor[2], 0, 1023, 0, 255);

  //enviar por puerto serie valores separandolo por comas, y utilizar el caracter $ para identificar el comienzo de la trama, el retorno de carro
  //sera el caracter de fin del trama
 Serial.print("$"); 
for (i=0; i<3; i=i++) {
  Serial.print(valor_sensor[i]);
  Serial.print(",");
}
Serial.print(valor_sensor[3]);
Serial.println();
delay (500);

}

te pongo un poco en pseudocodigo lo que tienes que hacer para interpretar el string recivido

int valor[] --> dodne guardas los valores calculados

si llega un caracter por serie
{
si es distinto de 13 (retorno de carro) cadena=cadena+caracter que llega
si no --> cadena=cadena+\0 ( se agrega carater nulo) --> funcion obtener datos del string(cadena)
}

funcion obtener datos del string(cadena)
{
si cadena[0]=='$' ( asi sabes si lo que has recibido es correcto)
{
si cadena[1] es un número{ valor[indice] = calculaint desde (cadena en la posición 1) --> después del $ siempre viene un número asi que lo calculas siempre--> indice++

si no es un número entonces break (hay un error en la cadena)

for x desde 2 a longitud de cadena de uno en uno ( vamos a calcular donde están los otros números)
{
si cadena[x] == ',' entonces valor[indice] = calculaint desde (cadena en la posicion x+1) --> indice++

else if cadena[x]=='\0' o '#' entonces indice=0 y x= longitud de la cadena+1
}
}
al final de esto tendras los valores en valor[] de 0 a los que envíes, vale para cuantos valores quieras enviar
creo no equivocarme en algo , lo he hecho de cabeza y como es lógico sin compilar jajajaja

comparte el código una vez lo hayas hecho y ayudarás a otros que quieran hacer lo mismo

un cordial saludo ¡

Ya me perdonaras SrDonGato, pero no he sido capaz de entender el pseudo código. :disappointed_relieved:

No se si podrías simplificarlo y transcribirlo más claramente.

Gracias por adelantado

más claramente es hacerte el codigo ...

a ver si saco un rato y te lo voy haciendo, el curro me tiene absorbido

pero vamos te puedes hacer una idea con esta parte
si llega un caracter por serie
{
si es distinto de 13 (retorno de carro) cadena=cadena+caracter que llega
si no --> cadena=cadena+\0 ( se agrega carater nulo) --> funcion obtener datos del string(cadena)
}

el codigo sería

char palabra[];
byte serial_count;

void setup()
{
	
	Serial.begin(19200);
	Serial.println("start");
}

void loop()
{
	char c;

	if (Serial.available() > 0)
	{
		c = Serial.read();

		if (c != '\n')
		{
			palabra[serial_count] = c;
			serial_count++;
		}
	}

	
	if (c == '\n' )
	{
		//procesar linea ( es una funcion que debes hacer)
		process_string(palabra);

		//borrar linea de comando para recibir a sigueinte completa ( debes diseñar la funcion)
		init_process_string();
	}

}

Esto es parte (modificado) del código de Ìñigo, que está en TxapuCNC_RX01.pde en Txapuzas electrónicas: Txapu-CNC: Software

leyendo código puedes aprender a atacar distintos problemas, echale un vistazo para completar tu código

Gracias SrDonGato, me pondré con ello. Es estado leyendo hilos sobre el tema, y todavía no he encontrado un ejemplo que mande más de un dato a la vez por puerto serie y que un Arduino analice la trama para identificar cada dato.

Lecturas sobre puerto serie:

http://arduino.cc/forum/index.php/topic,97303.0.html
http://arduino.cc/forum/index.php/topic,65230.0.html

Puedes mirar también las maquinas de estado (state machines)
Procesar los datos a medida que van llegando.
eg:
Master envia:

N2P4C1S4

donde Nnnnn es la id del nodo o esclavo
donde Pnnn es el pin a activar
donde Cnnn es el comando
donde Snnn envía el estado del Pnnnnn via serial port.

Esclavo procesa
si recibo una N proceso los dígitos siguientes si es mi id sigo sino return
si recibo P proceso los dígitos siguientes y asigno el pin
si recibo C proceso los dígitos siguientes y envió el comando al pin P
etc etc.

Esa es la idea luego tu ya lo haces de acuerdo a tus requerimientos.

edito : acabo de acordarme que Igor R hizo una libreria FSM http://arduino.cc/forum/index.php/topic,75826.0.html