LA RECEPCIÓN "DIRECTA"
Lo primero que cabe destacar de la recepción es que, a diferencia de la transmisión, no podemos predecir cuándo llegará algo al Serial. Por lo tanto, si sencillamente leemos un byte del puerto tenemos dos posibilidades:
- Que haya un dato en el buffer, con lo que la lectura será instantánea.
- Que no haya tal dato, con lo que leeremos un byte falso.
Así que la primera conclusión que podemos sacar es que Serial.available() es imprescindible, tanto si no queremos leer un dato erróneo como si no queremos que nuestro programa permanezca detenido innecesariamente y sólo entre en la rutina de recepción cuando haya llegado algo. Sin embargo, a menudo vemos que sólo se tiene en cuenta parcialmente:
-Primera opción… La mala:
if (Serial.available()) {
for (int a=0; a<30;a++) {
miBuffer[a]=Serial.read();
}
}
De esta forma vamos a tener muy probablemente errores de lectura. ¿Por qué? Efectivamente, entraremos en el for sólo cuando se detecta que hay al menos un byte en el buffer; pero luego intentamos leer 30 bytes del tirón sin volver a verificar si hay datos en el buffer. Entonces, esas 30 lecturas se habrán realizado en el tiempo en que por serial apenas habrán llegado uno o dos bytes. El resultado es que leeremos el primer byte correctamente, y todas, o casi todas las lecturas posteriores devolverán dato falso.
-Segunda opción… La rígida:
if (Serial.available()) {
for (int a=0; a<30;a++) {
while(¡Serial.available()); // Mientras no haya dato en el buffer permanecerá bloqueado en este comando
miBuffer[a]=Serial.read();
}
}
Si las cosas van bien, en cuanto entremos en el for, iremos pasando los 30 datos a medida que llegan y no tendremos lecturas falsas. El problema es que si por cualquier factor se interrumpe la recepción una vez iniciada, el programa permanecerá indefinidamente parado esperando el resto de la trama.
-Tercera opción… La optimista:
if (Serial.available()) {
for (int a=0; a<30;a++) {
miBuffer[a]=Serial.read();
delay(1); // Una pequeña pausa* para dar tiempo a que llegue el siguiente byte
}
}
*Habría que calcular la pausa necesaria dependiendo de la velocidad de transmisión.
Una vez entrados en el bucle, las 30 lecturas se realizarán en aproximadamente 30 milisegundos. Lo que no podremos dirimir es si alguna de las lecturas fue falsa. Deberemos, bien confiar en que nuestro canal de datos no tenga fallos, o implementar algún sistema de control de errores.
-Cuarta opción… La buena:
¿Por qué no combinar las dos anteriores, sustituyendo el delay, combinando millis + Serial.available de tal forma que si transcurren los milisegundos establecidos sin que haya llegado un dato salimos del bucle sin leer más datos? Podríamos implementarlo, pero resulta que hay varias funciones de lectura múltiple que ya implementan esa característica: readBytes, readBytesUntil, parseInt y parseFloat. La primera sería la adecuada a nuestro caso:
if (Serial.available()) {
int x = Serial.readBytes(miBuffer,30);
if (x==30){
// Leidos los 30 datos
} else {
// Sólo llegaron sin interrupción x bytes (¿error?)
}
}
readBytes volcará en myBuffer en este caso hasta 30 bytes, salvo que transcurra un timeout sin recibir el siguiente byte, que dejará de leer. Como además de volcar datos, devolverá un int con el número de datos leídos, podremos utilizar dicho valor para comprobar si se recibió toda la trama de forma continua.
El único pero es que si salta el timeout nuestro readBytes habrá tenido ocioso un segundo al procesador lo que, en ocasiones será asumible, y en otras no. Si está establecido que el otro dispositivo, una vez que inicia el envío de una trama lo va a hacer de forma continua, sabemos que entre byte y byte no van a transcurrir más de dos milisegundos (a 9600bps), por lo que podríamos reducir drásticamente el tiempo de timeout.
Si vamos a utilizar parseInt o parseFloat para recibir uno o varios valores, y no queremos sufrir un segundo de espera tras leer el último valor, podemos reducir el timeout, o poner carácter separador también tras el último valor de la trama, de forma que dicho valor se devuelva inmediatamente al recibir el separador, en lugar de por timeout al dejar de recibir datos.
Si vamos a recibir una trama de texto de tamaño variable con algún tipo de terminador, readBytesUntil será nuestro aliado.