Problema con el borrado de string y entrada por Serial

Hola a todos!

Tras una larga busqueda ,(asesoramiento de profesores incluido),no he conseguido eliminar datos residuales que quedan almacenados en el string del programa.

Me explico:

Se trata de un programa que al introducir datos a través del Serial los lee y ejecuta un movimiento repetitivo en unos servos.

Si introduzco los datos y espero el ciclo todo correcto pero si mientras ejecuta el bucle le llegan mas datos por el serial acaba amontonándolos y realiza el bucle con interferencias con parte de otros ciclos.Lo que pretendo es que almacene las cadenas que llegan y después la ejecute sin interferencias o, si no es posible, que al menos no pueda llegarle nada mientras ejecuta el ciclo hasta que termine y ahí ya almacene los nuevos datos en el string.

He intentado varias fórmulas para borrar el string ( inputstring="", Serial.end() para que no entren mas datos mientras está ejecutando, Serial.flush() para borrarlo, etc. Sin un buen resultado.

Una forma poco limpia es ,como se vé en el skech, poner ( case’o’:inputString="" ), pero me parece un poco parchazo.

Podríais echarle un vistazo?

Aprovecho para agradecer la ayuda de Surbyte, Noter, y Max_saeta , sin sus consejos en anteriores ocasiones no habría llegado a hacer este skech!

Saludos a todos!

#include <SoftwareSerial.h>
#include <Servo.h>

const byte max_repet = 7;		//	Maximo numero de repeticiones

byte laserPin = 4;
byte x = 90;
byte y = 90;

byte minX = 10;
byte maxX = 150;
byte minY = 30;
byte maxY = 130;
Servo servoV;  
Servo servoH;

String inputString= "";         // String para soportar la cadena a tratar
boolean stringComplete = false;  // Nos informa del estado de la salida.

void setup() {
  servoV.attach(2);
  servoH.attach(3);
  pinMode(laserPin, OUTPUT);

  // Arrancamos el serial:
  Serial.begin(9600);

  // Reservamos algo de memoria por si las moscas:
  inputString.reserve(200);
 
}

void loop() {
  if (stringComplete) {
   Serial.print("comienzo secuencia "); 
   Serial.println  (inputString);
    for(int u = 0;u< max_repet;u++){
      Serial.print(u);
    Serial.println  (inputString);
    for(int i =0; i < inputString.length(); i++){
     
      switch (inputString.charAt(i)) {
        case '1':
            delay(100);
              for(int n = 0; n < 7; n++){ 
                    delay(80);
                    digitalWrite(laserPin,HIGH);
                    delay(80);
                    digitalWrite(laserPin,LOW);
              }
            break;
          
          case '0':
            digitalWrite(laserPin, LOW);
            break;
          case '-':
            delay(200);
            break;
            case'o':
            inputString= "";
            break;
          case 'C':
            x = 90;
            y = 90;
            servoH.write(x);
            servoV.write(y);
            delay(200);
            break;
        default:
          if(inputString.charAt(i) == 'l' || inputString.charAt(i) == 'r' || inputString.charAt(i) == 'u' || inputString.charAt(i) == 'd') moveLaser(inputString.charAt(i), 1);
          if(inputString.charAt(i) == 'L' || inputString.charAt(i) == 'R' || inputString.charAt(i) == 'U' || inputString.charAt(i) == 'D') moveLaser(inputString.charAt(i), 5);
         
          servoH.write(x);
          servoV.write(y);
        }

      
    }
    }// Limpia el string:
    Serial.print("final de secuencia ");
    Serial.println(inputString);
    inputString = "";
    stringComplete = false;
     

    
  }





/*
  SerialEvent Cuando un dato llega al Rx.
  */
void serialEvent() {
Serial.begin(9600);
delay(10);
  while (Serial.available()) {
    
    // Obtenemos la emision:
    char inChar = (char)Serial.read();
    // Lo añadimos al String de entrada
    inputString += inChar;
    if (inChar == 'x') {
      stringComplete = true;
      
    }
  }
}

void moveLaser(char dir, int amount){
  if      ((dir == 'r' || dir == 'R') && x > minX) x -= amount;
  else if ((dir == 'l' || dir == 'L') && x < maxX) x += amount;
  else if ((dir == 'u' || dir == 'U') && y < maxY) y += amount;
  else if ((dir == 'd' || dir == 'D') && y > minY) y -= amount;

}

LOL. por ejemplo como mandas una secuencia para hacer lo que dices ?

Usas Serial.flush() para borrar el buffer del puerto Serie?

Flush()

Waits for the transmission of outgoing serial data to complete. (Prior to Arduino 1.0, this instead removed any buffered incoming serial data.)

Hola!

En cuanto a la pregunta de Naruto128 envío la secuencia por el puerto serial del terminal o a través de una aplicación en java vía bluetooth. En ambos casos da el el mismo fallo. Aunque no es necesario para la ejecución del programa puse unos print para ver la evolución en pantalla.comienza bien dos o tres veces pero a medida que envío nuevos datos mientras ejecuta el programa amontona restos de secuencias anteriores (se puede observar si se envían alternativamente secuencias de mayúsculas y minúsculas y las mezcla).

Hola Surbyte, Sí, he intentado borrar con Serial.flush() el buffer de entrada justo a la salida del loop en el que ejecuta las secuencias pero sin resultado. Como puedes ver en el skech lo intento con inputString="" pero también me da fallo.

La solución más radical que últimamente he intentado es finalizar la entrada del serial con el Serial.end() ,(también sería una solución posible aunque peor)pero no consigo reactivarlo con Serial.begin(9600).

En fín, que llevo tiempo con ello haciendo mil pruebas pero no doy con la manera de conseguirlo...

Alguna idea?

Gracias por las respuestas.

Perdón, envié un skech anterior :blush: …este es el bueno.
Saludos.

#include <SoftwareSerial.h>
#include <Servo.h>

const byte max_repet = 7;		//	Maximo numero de repeticiones

byte laserPin = 4;
byte x = 90;
byte y = 90;

byte minX = 10;
byte maxX = 150;
byte minY = 30;
byte maxY = 130;
Servo servoV;  
Servo servoH;

String inputString= "";         // String para soportar la cadena a tratar
boolean stringComplete = false;  // Nos informa del estado de la salida.

void setup() {
  servoV.attach(2);
  servoH.attach(3);
  pinMode(laserPin, OUTPUT);

  // Arrancamos el serial:
  Serial.begin(9600);

  // Reservamos algo de memoria porsi las moscas:
  inputString.reserve(200);
 
}

void loop() {
 
  if (stringComplete) {
   Serial.println("comienzo secuencia 7 repeticiones"); 
   
    for(int u = 0;u< max_repet;u++){
        Serial.print  (u+1);
      Serial.println  (inputString);
    for(int i =0; i<inputString.length(); i++){
    
      //Serial.print(inputString.charAt(i) ); 
      switch (inputString.charAt(i)) {
        case '1':
            delay(100);
              for(int n = 0; n < 7; n++){ 
                    delay(80);
                    digitalWrite(laserPin,HIGH);
                    delay(80);
                    digitalWrite(laserPin,LOW);
              }
            break;
          
          case '0':
            digitalWrite(laserPin, LOW);
            break;
          case '-':
            delay(200);
            break;
            case'o':
            inputString= "";
            break;
          case 'C':
            x = 90;
            y = 90;
            servoH.write(x);
            servoV.write(y);
            delay(200);
            break;
        default:
          if(inputString.charAt(i) == 'l' || inputString.charAt(i) == 'r' || inputString.charAt(i) == 'u' || inputString.charAt(i) == 'd') moveLaser(inputString.charAt(i), 1);
          if(inputString.charAt(i) == 'L' || inputString.charAt(i) == 'R' || inputString.charAt(i) == 'U' || inputString.charAt(i) == 'D') moveLaser(inputString.charAt(i), 5);
         
          servoH.write(x);
          servoV.write(y);
        }

      
    }
    }// Limpia el string:
    Serial.println("fin secuencia. LIMPIEZA STRING");
   
    inputString = "";
    
    stringComplete = false;
    
    
  }
}




/*
  SerialEvent Cuando un dato llega al Rx.
  */
void serialEvent() {
  while (Serial.available()) {
    // Obtenemos la emision:
    char inChar = (char)Serial.read();
    // Lo añadimos al String de entrada
    inputString += inChar;
    if (inChar == 'x') {
      stringComplete = true;
      
    }
  }
}

void moveLaser(char dir, int amount){
  if      ((dir == 'r' || dir == 'R') && x > minX) x -= amount;
  else if ((dir == 'l' || dir == 'L') && x < maxX) x += amount;
  else if ((dir == 'u' || dir == 'U') && y < maxY) y += amount;
  else if ((dir == 'd' || dir == 'D') && y > minY) y -= amount;

}

Bien, entonces sitúa mejor tu problema porque a diferencia de tus profesores no hemos visto tu equipo. Dinos que arduino usas y simplemente si le envias los datos a través del monitor serie usando la PC?

Ahora me doy cuenta que ya habías probado Serial.flush(). No leo que Arduino usas. asi que tal vez mi pregunta sea tonta pero me permite visualizar la situación y el contexto.

Para que pones SoftwareSerial si no la usas?

Como usas una secuencia comandada por interrupciones SerialEvent te llena siempre el String aún cuando al final de tu proceso lo borres. Asi que no importa que hagas, si vienen tramas muy rápidas eso será inevitable como lo veo yo.

Sería bueno que hables de quien le envía datos y ya sabemos que no llegarán mas rapido que 9600 bps. Asi que algo pasa que no alcanza a procesarlos durante el loop. por ahora es lo que veo, y hasta que nos cuentes algo mas acerca de los datos que son recibidos.

Te cuento Surbyte, lo he probado con dos tarjetas, la arduino uno y nano ambas con el procesador 328 at mega y el fallo es el mismo.

Puse Software.Serial para enviarle los datos vía bluetooth a un receptor por tx y rx aunque también con ello da el fallo que comento por lo que de momento todo lo hago vía PC hasta que solucione el problema.

Los datos que recibe definen el ángulo que tomaran los servos y el último verifica la cadena.

Ej: (dddddd1llll1rrr1x), donde "d" mueve un grado el servo a derecha, "1" enciende laser, "l" mueve laser 1 grados izquierda...etc hasta que con x pone la cadena en "true".

Al enviarlo funciona, tanto desde el PC como desde la aplicación de java vía bluetooth, pero cuando se envían muchas secuencias antes de que termine el ciclo acumula restos en las nuevas secuencias.

Si como comentas es dificil borrar lo viejo ya utilizado y que no se mezcle con lo nuevo podría poner en falso la entrada por el serial hasta que terminara el ciclo del bucle? Por ejemplo usando Serial.end? Lo he intentado y si desconecta pero no sé como llamar de nuevo al Serial.begin() y que funcione (todo esto visto desde mi prisma novato). No me importa que mientras está ejecutando el ciclo no pueda meter nada nuevo hasta que acabe...

Es una idea aunque no se si es factible.

Gracias y saludos.

Tu código tiene muchos delay, eso retarda mucho la ejecución del switch case. Debes reescribirlo sin usar delay a ver si de ese modo se resuelve.

Hola. Un poco de elucubraciones (que no certezas). La función serialEvent, no es una rutina de servicio propiamente dicha (la del puerto serie ya la está utilizando el arduino para ir volcando lo que va llegando a una cola, que es la que luego leemos con los comandos Serial). La función serialEvent se llama tras cada ciclo de loop, osea que sería poco más o menos como si al final de tu loop la llamas tú.

Creo que lo que está ocurriendo en tu caso es que una vez que se recibe una trama y empieza a procesarla la cola de Serial se llega a desbordar, pues no vas leer de ese buffer hasta el nuevo serialevent (que se produce como dije al final del loop); osea hasta que no terminas de procesar esa trama.

Habría que tener en cuenta entonces estos factores: ¿Qué tamaño tiene una de tus tramas? ¿Cuánto tiempo se tarda en procesar una de tus tramas? ¿Cuántos caracteres pueden llegar en ese tiempo?

El tiempo que tarda esta muy afectado por esos delay que se ven en el código

case 1: demora 100mseg mas 8x 160mseg = 1380 mseg case 0: 0 mseg case -: 200 mseg case o: 0 mseg case C: 200 mseg

en función de esto yo empezaria mejorando el case 1 a ver si resuelve la situacion, no se que tanto se ejecuta, pero achicarlo es un avance porque ahora es el cuello de botella.

Aca esta la respuesta: SerialEVent es el culpable de todos tus males

/*
SerialEvent occurs whenever a new data comes in the
hardware serial RX. This routine is run between each
time loop() runs, so using delay inside loop can delay
response. Multiple bytes of data may be available.
*/

SerialEvent ocurre cada vez que hay nuevos datos ingresando por el RX. Esta rutina corre cada vez que termina de ejecutarse un loop, de modo que el usar delay dentro del loop puede retardar su respuesta. Multiples bytes de datos pueden estar disponibles.

Entonces eso es lo que pasa. Los delays demoran la lectura y por lo tanto cuando se libera y ejecuta SerialEvent capta tramas incompletas.

Asi que estaba errando, y lo que necesitas es que tu rutina serie no pierda una sola trama y que se acumulen para ser ejecutadas. Supongo que en algun momento dejaras de enviar.
Ya que por ejemplo el comando 1, demora 1.3 seg.

Mira a ver si estas rutinas que usan interrupciones buffereadas para el Serial solucionen tu problema

Hola!

Note, Las secuencias que se envían tienen de 100 a 130 caracteres, el proceso es de aproximadamente minuto y medio y en el tiempo del proceso le pueden llegar dos o tres secuencias de el mismo número de caracteres. Como la idea es que se envíen vía bluetooth ,quiero evitar que los envíen de forma masiva cosa que controlo por la exclusividad que concede una conexión bluetooth ( no permite más de un dispositivo conectado ) , retengo la conexión un tiempo y así evito la conexión de otros, aún así quiero evitar el problema de acumulación de datos que se solapen, o bien porque los reciba y procese de forma ordenada o bien porque mientras procesa una cadena no pueda entrar otra por Serial.event.

Surbyte,

gracias por el enlace, lo he estado echando un vistazo pero me temo que es un poco complicado para mi,(sólo llevo con arduino unos pocos meses y antes apenas sabía lo que era un programa), podríais orientarmeen cómo adaptarlo a mi skech?

Gracias por las respuestas...en este foro da gusto!

Saludos!

Si las tramas ocupan de 100 a 130 caracteres nunca vas a conseguir capturar una entera ya que el buffer del serial solo es de 64 bytes (64 caracteres) y lo demas se va perder .a medida que vas leyendo se va vaciando el buffer pero si entra otra trama antes de vaciarlo rellenara hasta volver a agotar el buffer y se mezclara con la otra mitad.deberias intentar limitar el tamaño de las tramas a esos 64 bytes como maximo para poder recoger una secuencia completa.

Entonces de todas es seguro que el serial se pude desbordar, lo lo que creo que necesitas implementar ambas cosas: - Seguir recibiendo mientras ejecutas una trama. - Control de flujo para decirle al dispositivo bt "espera un poco que tengo el buffer lleno". Ahora estoy con celular. Cuando pueda te doy más ideas.

Ya te di la solución al problema

Mira a ver si estas rutinas que usan interrupciones buffereadas para el Serial solucionen tu problema El buffer es de 200 bytes aunque creo que hay que hacer lo que dice noter unido a esto.

La ejecución del case ´1´ implica 1.3 segundos. Lo has visto!!! Eso rompe cualquier esquema que quieras implementar asi que lo que debes hacer es reescribir ese procedimiento.

Hola Jose!

eso tenía entendido pero el caso es que me soporta cadenas de mas de 64 caracteres sin problemas (las envío de 150 y va perfecto), el problema surge cuando envío más mientras ejecuta el loop. Creo que la solución va a pasar por inhabilitar la entrada de datos mientras esta en el bucle (si es que es posible).

Noter, gracias, estoy a la espera!

Surbyte, he limado el tiempo del case "1" pero necesito un delay que haga posible la intermitencia del laser y no veo otro método para ello :confused: . Quizás la solución vaya más por el camino que apunta Noter...

Gracias y saludos.

Bueno. Pues te cuento a ver si sabes cómo ir encarando el asunto.

Personalmente, sobre todo en arduino, prefiero utilizar arrays de caracteres a strings porque evito posibles problemas de memoria con las reasignaciones que se puedan producir al hacer operaciones como concatenar, asignar, etc.; pero supongo que también lo puedas hacer con strings.

Entonces, tenemos que lidiar con tres problemas:

  • Evitar desbordamiento del buffer. Para ello, sencillamente no utilices serialEvent. Llama a tu función con otro nombre, por ejemplo vuelcaDatos() y llámala tanto dentro del for(int i; ...) como del for (int n...), con lo que estaremos revisando el buffer de recepción con suficiente frecuencia para evitar el desborde de esos 64 bytes

  • Evitar la sobreescritura del string que estamos "reproduciendo". Es decir, seguir escribiendo a continuación del último dato recibido, aunque se reciba el caracter stringcomplete. Para reproducir, en lugar del for con el inputstring.lenght, utiliza un while caracter no sea terminador. El problema será cuando terminemos de reproducir, eliminar el "trozo" de cadena que hemos reproducido. Osea, vamos a agregar al final de la cadena lo que vayamos recibiendo y vamos a eliminar de la cadena los trozos que vayamos reproduciendo.

  • Evitar que cuando no dispongamos de más espacio el otro dispositivo deje de enviarnos datos. Lo lógico sería enviarle un carácter de parada (y uno de arranque cuando podamos volver a recibir). Otra opción más radical sería enviar a la basura todo lo que llegue después.

Mira a ver si entiendes la idea y puedes empezar a implementarla por tí. Te ayudo donde te vayas atrancando.

Saludos.

Gracias Noter, me pongo a ello, aunque quizás lo simplifique un poco más para no enviar cadenas tan largas.

Como cada letra implica un movimiento de un grado o 5 grados , envío los grados a continuación de una letra que identifique el servo, algo así como , V 200 (vertical 200 grados), y que lea el entero siguiente con Parseint y mueva el servo.

así creo que acortaría la cadena no?

Gracias por la ayuda a todos, seguiré trabajando en ello.

Saludos.

Esta claro por tus explicaciones que no se puede achicar los tiempos de movimiento del laser pero si se puede modificar como se llega a cada envento a realizar (case) sin quedarme demorado hasta que se cumpla. Se me ocurre usar una máquina de estados. Una máquina de estados es aquella que tiene definidos diferentes estados de acción. Estoy en espera. Estoy moviendo el laser, Estoy moviendo otra cosa, Borro el String, etc. Es lo mismo que tu has hecho pero visto de otro modo. Entonces: que lees Case 1 y es el estado Laser. Antes se ejecutaba y se quedaba hasta terminar ese estado. Ahora hara lo mismo de un modo diferente. Usando millis() y ya habras leído como cambian las cosas. Entonces caes en el estado Laser, y dice delay(1000) que obvio se hará de otro modo... y luego el for ... con sus dos delays de 80mseg c/u. tambien con millis(). En cada caso la secuencia ejecuta y sale y da lugar a leer el buffer por ejemplo y tal vez liberar el lugar que ocupaba el comando que ahora ejecuta Laser. Espero me explique bien. Entonces entra y sale por ese estado, y hasta no completarlo no puede pasar a otro estado.

Recien al terminar, pasa a un estado que podría ser, LeerNuevoComandoDelBuffer Te lo he dicho explicado para ver que opinas. Esto aplicado a todo.. hace que no pierdas comandos pero claro, hay que esperar a que se ejecuten. hay mas no se puede hacer.

Gracias surbyte, me pongo a ello, intentaré minimizar el tamaño de la cadena a enviar a ver si ahorro espacio. Intento asimilar lo que me dices aunque ahora se me hace un mundo! :cold_sweat:

Gracias por la ayuda a todos!

saludos