Enviar y recibir multiples bytes por puerto serial [SOLUCIONADO] [COMO]

Estoy tratando de escribir un intérprete de comandos con diversas funciones:

  • escribir en una eeprom
  • encender/apagar relay
  • obtener status
  • etc

Resulta que hice varios intentos y hasta ahora lo único que logré es un 50%:

byte EntradaByte[10];   // almacenador de entrada
int contador = 0;


void setup() {
        Serial.begin(9600);     // abre puerto serial
}

void loop() {
  
  
  
}

void serialEvent() {

  byte ByteCarga = Serial.read();
  
  if (contador < 9) {
    EntradaByte[contador] =  ByteCarga;
    contador++;
  }
  else
  {
    if (EntradaByte[0] == 33 ) { // si la cabecera es "!"
    Serial.println("OK"); // Ok de prueba
    }
    contador = 0;
  }
  
}

Entonces funciona así:
Yo envío: "!234567890" y recibo "OK"
Si envío: "1234567890" no recibo nada.

Hasta ahí estamos bien, pero resulta que si envío:
"123"
El programa espera el resto de la cadena para procesar y responder (en caso de que la cabecera sea !). No logro aplicar un timeout, para que, en caso de error, si alguien envía "!125" en lugar de la cadena completa se "resetee" el intérprete.

He visto varios ejemplos, pero la verdad es que no se me ocurre como aplicarlo al código. Considero que esto es de máxima imporantica si quiero interactura con la PC. Estaré muy agradecido por la ayuda.

con un temporizador basado en millis() puedes hacer el timeout

cada vez que lees un caracter reseteas el tiempo por debajo del cual sigue interpretando el array completo, en caso de que se supere el tiempo de espera pones el contador a 0

Descubrí una solución sin temporizador y muy efectiva por cierto. En estos momentos no estoy en frente de mi equipo, cuando regrese a casa lo publicaré.

La opcion del @Srdongato es muy buena.

He aqui como lo he resuelto, podemos mejorarlo, claro está.

// ## PUERTO SERIE ##
  while (Serial.available() > 0) { 
    EntradaByte[contador] = Serial.read(); // Lee el byte y lo mete en la String[]
    contador++; // Suma un valor
  }
  
  if (contador == 12) { // Si el valor llega a 12
    if (EntradaByte[0] == 0x21 && EntradaByte[11] == 0x21) { // Si el primer byte y el ultimo es 0x21 (!) 
      switch (EntradaByte[1]) { // Ve el segundo byte, que es la orden
        case 82: //Letra R // Si el segundo byte es R entonces hace...
         // HAZ ALGO
         Serial.write(0x21); //Responde 0x21 (!)
         break;
      }
    }
    contador = 0; // Vuelve a poner el contador a cero
    Serial.flush(); // vacía el buffer del puerto serie
  }
  // ## FIN PUERTO SERIE ##

Todo dentro de void loop().

Buen trabajo @DrJuano.

no es el timeout que pretendías hacer, esperas a que se llene con 12 bites para interpretar la linea completa.
en el caso de que pierdas comunicación y se pierdan caracteres y recibas luego otra trama completa no funcionaría
por ejemplo si recibes
!123 y despumes de un tiempo recibes !123456789

tu programa interpretaría la linea completa !123!1234567

y no como lo que he entendido que pretendías que desechara !123 y empezara de nuevo por !1234567890 que es la siguiente trama correcta que recibes

como te he comentado es lo suyo

Efectivamente, no produce el efecto completo que mencionas de desechar. Por un momento creí que si, pero no desecha. Aunque no sé porqué no lo hace, ya que al final de la secuencia el contador se pone en cero y el buffer del puerto serial se elimina, haya no haya comando en la string[].

eso lo hace siempre y cuando el contador llegue a 12 y no antes.

un timeout la unicaforma de hacerlo en controlando tiempos

cada vez que lees un caracter pones

referencia_timeout=millis();

el if que tienes con contrador == 12 está bien , quita la ultima parte de

Serial.flush(); // vacía el buffer del puerto serie

si mientras el micro procesa la linea que pretendes llega una trama buena, un caracter por ejemplo, con esa linea la borras, por lo que pierdes informacion.

y ahora haces el reset del contador si se supera cierto tiempo

if (( millis()-referenciatimeout ) > tiempotimeout){ contador=0;}

Quedaría algo así:

void loop() {

  while (Serial.available() > 0) { 
    EntradaByte[contador] = Serial.read(); // Lee el byte y lo mete en la String[]
    contador++; // Suma un valor
    timeout = millis();  // carga timeout
  }

  if (( millis() - timeout ) > 800) { contador=0; }  // si pasaron más de 800 milisegundos, entonces, vuelve a poner el contador a cero

  if (contador == 12) { // Si el valor llega a 12 // si el contador llego exitosamente a 12, entonces ejecuta el proceso del comando
    if (EntradaByte[0] == 0x21 && EntradaByte[11] == 0x21) { // Si el primer byte y el ultimo es 0x21 (!) 
      switch (EntradaByte[1]) { // Ve el segundo byte, que es la orden
        case 82: //Letra R // Si el segundo byte es R entonces hace...
         // HAZ ALGO
         Serial.println(0x21); //Responde 0x21 (!)
         break;
      }
    }
    contador = 0; // Pone el contador a cero para evitar repeticiones
  }
  
}

Probado y funciona perfecto. Gracias SrDonGato. Es dificil a veces comprender el tema del loop. Recién empiezo con Arduino y estoy experimentando bastante. Tengo en mente algun proyecto comercial. Espero que esto ayude a otro que venga por lo mismo. Ahora voy a tratar de generar algo que verifique la secuencia recibida (CRC).

Has entendido perfectamente la filosofía de la que te hablé :slight_smile:

lo bordas si en vez de 800 pones una variable , de manera que la puedas modificar al principio del codigo fácilmente

Un saludo

@DrJuano

no seria mejor ir procesando los datos a medida que entre y no esperar a tener EntradaByte[12] para procesar?
inicio del mensaje sera : <
fin del mensaje sera : >

//Pseudocodigo

if Serial.avaiable(); 
Entradabyte = Serial.read()
switch (Entradabyte)
{
case '<':
    clean buffer
case'>':
   procesarDatos();

default: //todo lo demas
    if (contador <= numero de datos deseados)
             grabar dato en cDatos[x]
    else
        cleanBuffer;
}
procesarDatos()
{
     verificar longitud();
      hacerAlgoConLosDatosEn_cDatos();
}

yOPERO:

Como lo planteas se ve un poco más complicado, especialmente si uno quiere recibir un paquete de datos. Se me hace que es mas facil tener un paquete completo y luego ver que se hace con él. Por lo menos, he visto dispositivos que se comunican con protocolos en forma de paquetes.

El cliente siempre tiene la razón y si con eso te vale, perfecto.

Como cultura general el serial recibe los datos uno por uno y lo que llamamos paquete de datos son series de bytes individuales que se procesan<almacenan/cumplen condiciones> en el receptor.

yOPERO: creo que cantamos la misma canción con diferente música. :slight_smile:

En realidad me refería a que, para mi, lo más sencillo sería "armar el paquete" y luego interpretarlo en lugar de ir interpretandolo a medida que se recibe byte por byte. Me suena más complicada la segunda opción.