Sincronización de datos en el Serial

Hola

Estoy realizando un montaje con Arduinos (un UNO y un Mini), el UNO genera paquetes de datos que contiene 12 bytes, los 4 primeros indican la acción y los otros 8 indican los datos.

Como ejemplo:

Paquete 1: 0x00, 0x1F, 0x01, 0x00, 0x24, 0x06, 0x2D, 0x70, 0x18, 0x020, 0x34, 0x21

Paquete 2: 0x01, 0x1C, 0x10, 0x03, 0x32, 0x00, 0x2F, 0x60, 0x51, 0x060x32, 0x23

Estos paquetes quiero mandarlos al Arduino Mini para que los procese.

Las conexiones son:

UNO Tx -> Mini Rx

UNO Rx -> Mini Tx

Es decir, que los comunico por el puerto hardware.

Como dice Luis Llamas, los programadores “mayores” utilizan bytes para comunicarse, por lo que me puse manos a la obra. En el UNO se montan los 12 bytes en una matriz y se envían al serial con Serial.write(matriz). En el Mini, se utiliza lo siguiente

If(Serial.available()>12{

Char read_buffer[12] ={0x00};

Serial.readBytes(read_buffer,sizeof(read_buffer));

//proceso de los bytes

}

Bien, pero para que esto funcione los datos tienen que estar coordinados, es decir que si el UNO está encendido y luego se enciente el Mini, el read_buffer se llena con los 12 primeros que pueden ser una parte de un paquete y otra del siguiente, por ejemplo

0x2D, 0x70, 0x18, 0x020, 0x34, 0x210x01, 0x1C, 0x10, 0x03, 0x32, 0x00, 0x2F

Lo cual la interpretación es ya errónea siempre. Como los bytes pueden tener cualquier valor, no puedo utilizar uno para indicar el inicio y otro para terminarlo, o por lo menos no veo como hacerlo. ¿Cómo se coordinan los envíos de bytes por el Serial?

Otra cosa que veo, es que ambos Serial está ajustado a 115200 baudios y con esta velocidad read_buffer contiene bytes que no corresponden con lo que tiene que ser, es decir, lee mal los bytes. Si bajo la velocidad a 9600, se leen perfectamente, pero se nota la diferencia en tiempo de ejecución.

Como no sabia resolver el problema, cambié el método de envío, pasando los bytes a una cadena String (con ‘\n’ final) con los datos en modo texto, lo cual es más fácil de procesar, pero más lento, ya que en el UNO hace el cambio de byte a String y en el Mini, al revés.

If(Serial.available()>24{

String read_buffer;

Serial.readStringUntill(‘\n’);

//proceso de los bytes

}

Aquí vuelvo a tener el problema de sincronización, el primer paquete está incompleto, pero sólo es el primero, los siguientes ya los lee correctamente, salvo que tengo el mismo problema con la velocidad, con 115200baudios lee muchas veces una cadena de caracteres “raros” y no debía ser ya que readStringUntill espera a que llegue el carácter final o el timeout del Serial, pero aseguro que el timeout no llega, ya que lo tengo en 1s.

Las cuestiones que no he logrado encontrar (o no he sabido buscar) y por eso pregunto por aquí son:

¿Cómo se sincronizan los paquetes de datos cuando se trabaja con bytes?

¿Por qué afecta la velocidad del Serial a la lectura? Cuando monitorizo el envío del UNO con el Serial del IDE se reciben los datos correctamente?

Gracias de antemano por vuestro tiempo

Si solo deseas una comunicación unidirecional puedes trabajar con timeouts y CRC , el maestro envía mensajes cada X tiempo (mayor que el tiempo de envió del mensaje). El esclavo al recibir un byte comienza a contar, si después de un tiempo no recibe mas, o los 12, descarta el mensaje y limpia el buffer. Si recibe los 12 con el CRC comprueba si es un mensaje valido, y si no es, lo descarta.
Ahora una pregunta ¿Que estas intentando hacer? Pregunto, porque desde el punto vista educativo esta bien entender como comunicarse con Bytes, pero desde el punto de vista practico, es como reinventar la rueda. Existen multitud de protocolos implementados en Arduino que ya hacen eso de manera transparente.

PeterKantTropus tiene usted toda la razón, no indiqué que la comunicación es bidireccional, de vez en cuando el Mini manda datos al UNO también.

El UNO recibe mensajes continuamente de un módulo CAN, algunos de estos mensajes son importantes, se deben procesar y contestar inmediatamente. Los otros hay procesarlos y almacenarlos para cuando sean necesarios. En mi primera idea el UNO hacía todo este trabajo, leia los mensajes del CAN, los procesaba, contestaba los inmediatos, procesaba los otros y almacenaba la información necesaria, posteriormente los enviaba al mini ya procesados cuando el Mini los requería. Cuando hice las pruebas me di cuenta que no se procesaban correctamente algunos de los mensajes importantes. Analizando el código me di cuenta que el tiempo de proceso de todos los mensajes, almacenamiento y contestación al Mini era tan largo que perdía la lectura de algunos mensajes, lógamente, entre ellos algunos de los importantes.

Por tanto, tenía que cambiar la estrategia. Con esta nueva estrategia el UNO sólo se encarga de leer los mensajes del CAN, de procesar y contestar los importantes y enviar el resto al Mini. En las pruebas el UNO ya funciona correctamente y procesa los importantes correctamente, pero tiene que enviar los otros en el menor tiempo posible al Mini (por eso de enviarlos en crudo, con el mínimo proceso posible), luego el Mini los procesa y se puede permitir el lujo de perder alguno de los mensajes no importantes.

Si, puedo procesarlos minimamente con carácter de inicio de mesaje, mensaje en texto y caracter fin de mensaje, pero con esto también pretendo aprender más sobre el puerto serie. ¿Cuáles serían esos protocolos implementados en Arduino?
Tiene que ser una minucia, pero no entiendo porque con el monitor serie, los datos enviados por el UNO se reciben correctamente y al leerlos con el Mini a 115200 baudios los lee corruptos tanto en bytes como en texto.

Muchas gracias por su tiempo

Hace unos dias @J-M-L sugirió en este hilo un muy buen tutorial

Serial Input Basic - updated

El Ejemplo 2 tal vez se ajuste a lo que necesites.
Como verás hay un caraceter que se usa como endMarker que en este caso es '\n' pero podria ser cualquiera.

Surbyte,
Sí, es muy buen tutorial y el cual ya leí al empezar con este proyecto. La diferencia con el ejemplo 2 es que envia nombres y los caracteres (bytes) están limitados entre 0x20 y 0x3F (caracteres imprimibles), por lo tanto se puede usar cualquier otro para marcar el inicio y el final del mensaje o trama. En mi caso los datos admiten cualquier valor entre 0x00 y 0xFF, por lo tanto dentro de tipo byte o usigned char no quedan valores para marcar inicio o final del mensaje o trama de datos.
Si por ejemplo pongo de delimintador el '\n' (0x0A) para finalizar la trama, cuando uno de los 12 datos de la trama sea 0x0A lo tomará como fin de datos y ya se desincroniza todas las siguientes lecturas.
Si por ejemplo los datos son:
0x0B, 0x00, 0x1F, 0x01, 0x00, 0x24, 0x06, 0x2D, 0x0A, 0x18, 0x020, 0x34, 0x21, 0x0A
y se usa 0x0B como marcador iniciador de trama y 0x0A como marcador de fin de trama.
El 0x0A en cursiva es un dato y el negrilla es el fin de trama. Cuando el recptor lo lea, se quedará en el dato en cursiva y tomará el resto hasta el 0x0A en negrilla como basura ya que la siguiente trama volverá a empezar por un 0x0B. Esto no es un sistema robusto, ya que está comprometido a muchas lecturas erróneas.
Gracias Surbyte por la respuesta.

Acá estas leyendo datos de 12 en 12, pero en el momento que tienes problemas con un solo byte el mensaje se "corre" y empieza a leer basura . Por eso es importante trabajar con timeout.
Una forma de hacerlo en una comunicación bidirecional (pero aun maestro-esclavo) el maestro inicia la comunicación enviando el mensaje con CRC al final de mensaje, este CRC esta para comprobar la integridad del mensaje, no para determinar su final.
Cuando en el esclavo se detecta un byte se dispara una cuenta de tiempo, si dentro de cierto intervalo no se completaron los 12 bytes, se envía un mensaje de error al maestro y limpia el buffer. Si se completo antes del tiempo, se comprueba con el CRC si este esta integro , si no coincide se envía un mensaje de error y se limpia el buffer, si esta correcto contesta con la información necesaria.
El maestro al enviar el mensaje también dispara un cronometro si dentro de cierto tiempo no recibe respuesta del esclavo reenvía el mensaje. Y si recibe algo actúa según consecuencia si es el mensaje es de correcto o no.
Esto es solo una forma de hacerlo, puede haber mas variaciones (por ejemplo no usar CRC). Con protocolos me refería a por ejemplo modbus , I2C que ya tienen implementado todo lo anterior y mas

PeterKantTropus, gracias de nuevo por su tiempo.

Acá estas leyendo datos de 12 en 12, pero en el momento que tienes problemas con un solo byte el mensaje se "corre" y empieza a leer basura . Por eso es importante trabajar con timeout.

Efectivamente, por eso quería sinconizarlos.
Al final veo que la manera más sencilla de sincronizarlos es con el mensaje de texto con los byte en hexadecimal. Transformarlos en texto no demora mucho tiempo al servidor y no parece que pierda ningún mensaje de los que le llega del CAN. Luego tampoco es gran cosa pasarlo de nuevo a bytes en el receptor. Lo que pasa es que sigo sin saber porqué a 115200 baudios el readBytesUntill lee muchos datos corruptos y a menos velocidad los lee correctamente. Y si los leo con el Serial del IDE a 115200 los lee correctamente.
Muchísimas gracias de nuevo por vuestro tiempo.

Bueno, al final le he perdido el miedo a otros modos de comunicación. He cambiado a I2C y, después de un montón de lecturas, pruebas y alguna que otra chapucilla, he conseguido sincronizar los datos.

Muchas gracias por vuestra inestimable ayuda.