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é.
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 ##
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
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;}
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).
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();
}
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.
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.