Por qué "a veces" se queda pegado el programa? Arduino

Muy buenas!! Espero que estén bien haciendo sus quehaceres!

Tengo aquí, un muy buen proyecto, que una vez listo, me encantaría subirlo bien explicado al foro, ya que tiene muchísimas cosas útiles.

El proyecto se trata de un arduino UNO al cual se encuentran conectados: 2x acelerómetros, una tarjeta SD, un reloj RTC DS1307, y una tarjeta BluetoothBee, a través de la cual conecto el arduino al computador, mediante un puerto COM virtual. Cuenta además con un botón normal y un led indicador. Todo funciona muy bien, excepto por una cosa, que es mi pregunta de hoy.

El código lo muestro más abajo.

Antes de hacer lo que dije (subir esto al foro completamente), necesito resolver un último problemita. Verán en el código (un poco largo, pero no difícil de entender), que hay una sección donde hay un "for", donde dentro de el se envía por serial una larga cadena de datos, que son los datos obtenidos desde los acelerómetros.

El problema es, que a veces y no siempre, el programa al tomar las mediciones y enviármelas por serial, se cuelga. No me queda otra que resetearlo... Cada vez que aprieto el botón el cual es leído por el primer "while" de la función "loop()", se comienza con la toma de datos, y es precisamente allí donde a veces llega hasta la mitad de la medición sin entregar el último valor leído en forma completa, y ... ya se pegó de nuevo. A veces funciona muy bien, y hace la medición completa.

Las condiciones del entorno son, que No hay ruido electromagnético o muy poco (escritorio, no hay router cerca) y que las conexiones están casi todas soldadas y bien conectadas. Mientras mido, no toco el circuito con nada.

Alguien me podría decir entonces, cuál es o podría ser una razón para que el programa se quede pegado en la parte del "for"? Puede ser por culpa de la programación? O solamente podría ser por algo de hardware?

Muchísimas gracias, y además, gracias por la paciencia.
Muchos saludos!!

Código:
#include <SD.h>                            // Librería de la tarjeta SD que incluye SPI. Se molestará junto con la librería SPI, siesque se usa.
#include <Wire.h>                          // Librería de comunicaciones I2C. Por defecto: 100kHz.

#define ADLX_A (0x1D)                      // Dirección I2C del Acelerómetro 1
#define ADLX_B (0x53)                      // Dirección I2C del Acelerómetro 2
#define DS1307 (0x68)                      // Dirección I2C del RTC DS1307

#define ActivityLed (0x07)                 // LED indicador está en Pin 7
#define InitSensor (0x05)                  // Primer sensor en Pin5 (sensores simulables por un interruptor común)
#define SecondSensor (0x04)                // Segundo sensor en Pin4
#define DistanciaSensores 1                // Distancia entre sensores en [metros]

                                           // Registros de Acelerómetros
#define BW_RATE (0x2C)                     // Data rate and power mode control
#define POWER_CTL (0x2D)                   // Power Control Register
#define DATA_FORMAT (0x31)                 // Data format control
#define DATAZ0 (0x36)                      // Z-Axis Data 0
#define DATAZ1 (0x37)                      // Z-Axis Data 1
#define Rango (0x00)                       // Rango: 0x00=2, 0x01=4, 0x02=8, 0x03=16[g]
#define OffsetA 0                          // Offset que se le darán a los resultados de las mediciones
#define OffsetB 0

#define CS_SD 10                           // ChipSelect Pin = 10, para la tarjeta SD

// Variables para el programa base
char Values_Z[2];                          // Arreglo de 2 espacios para el dato del eje Z de los acelerómetros
char buffer[15];                           // Se utiliza para crear un archivo nuevo, nombre.extensión
String NombreArchivoNuevo="Datos",Offset_A,Offset_A2,Rang;
int z,i,NumeroArchivo,Range;
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
int SegundosMedicion=10; //120 no funciona.
float Zeta,Velocidad;
//unsigned long Tiempo,Tiempo1,Tiempo2;

/////////////////////////////////// -- SETUP -- /////////////////////////////////////////////////////////////////////////////////////////////////////////////

void setup(){ 
  pinMode(ActivityLed,OUTPUT);
  pinMode(5,OUTPUT);
  pinMode(InitSensor,INPUT);
  pinMode(SecondSensor,INPUT);
  pinMode(CS_SD, OUTPUT);
  digitalWrite(CS_SD, HIGH);

  Serial.begin(115200); // 57600 es la velocidad de programación. Para lo demás podría ser hasta 1000000 o 2000000(no probado).
  Wire.begin();  // Predeterminado: 100(kHz) de comunicación

  // Inicializar tarjeta SD
  if (!SD.begin(CS_SD)) {
    Serial.println("Falla de carga de tarjeta SD.");
    Led(5);
  }
  else {
    Serial.println("Tarjeta SD inicializada - OK.");
    Led(1);
  }
  
  // Inicializar acelerómetros
  writeTo(ADLX_A, BW_RATE, 0x0D);    // 0x0D = 800Hz and 400 Bandwidth, probablemente es lo correcto para I2C a 400kHz
  writeTo(ADLX_B, BW_RATE, 0x0D);
  writeTo(ADLX_A, DATA_FORMAT, Rango);
  writeTo(ADLX_B, DATA_FORMAT, Rango);
  writeTo(ADLX_A, POWER_CTL, 0x08);
  writeTo(ADLX_B, POWER_CTL, 0x08);

  //SelfTest();        // Hacer un SELF-TEST de los acelerómetros - REVISAR

  // Preparar el contenido de los archivos
  switch(Rango) {
    case 0x01:
      Rang="+-4[g]";
      Range=4;
      break;
    case 0x02:
      Rang="+-8[g]";
      Range=8;
      break;
    case 0x03:
      Rang="+-16[g]";
      Range=16;
      break;
    default:
      Rang="+-2[g]";
      Range=2;
  }
}

void loop(){
  File Archivo;

  while(digitalRead(InitSensor)==0) {}  // Mientras el primer sensor de llegada del tren no indique nada...
//  Tiempo1=millis();
  digitalWrite(ActivityLed,HIGH);    // Inicio de mediciones
  Serial.println("Tren entrando.");
  
  TWBR=72;     // Para que I2C funcione a 100(kHz). 72 para 100kHz y 12 para 400kHz
  getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);  // Extraer datos del reloj

  TWBR=12;     // Para que I2C funcione a 400(kHz). 72 para 100kHz y 12 para 400kHz
  
  File root = SD.open("/");
  NumeroArchivo=LastFile(root);
  Serial.println(NumeroArchivo);

  NumeroArchivo=NumeroArchivo+1;
  
  NombreArchivoNuevo = NombreArchivoNuevo + NumeroArchivo + ".csv";
  Serial.println(NombreArchivoNuevo);
  NombreArchivoNuevo.toCharArray(buffer,15);
  Archivo = SD.open(buffer,FILE_WRITE);  //Otra cosa es: SD.remove("Datos.csv");
  
/*  while(digitalRead(SecondSensor)==0) {}  // Mientras el segundo sensor de llegada del tren no indique nada...
  Tiempo2=millis();
  Tiempo=(Tiempo2-Tiempo1)*1000;
  Velocidad=(DistanciaSensores/Tiempo)*3.6;
  
  for(int j=0;j<=400*SegundosMedicion;j++){          // MEDICIONES
  
    digitalWrite(6,HIGH);                            // Para medir frecuencia de muestreo
    digitalWrite(6,LOW);
    
     readFrom(ADLX_A, DATAZ0, 2);
     z = ((int)Values_Z[1]<<8)|(int)Values_Z[0];
     z=z+OffsetA;
     Zeta=(z*Range*9.80665)/512;
     Serial.println(Zeta, DEC);
     Archivo.print(Zeta);
     Archivo.print(";");
     
     readFrom(ADLX_B, DATAZ0, 2);
     z = ((int)Values_Z[1]<<8)|(int)Values_Z[0];
     z=z+OffsetB;
     Zeta=(z*Range*9.80665)/512;
     Serial.println(Zeta, DEC);
     Archivo.println(Zeta);
  }

  Serial.println("FIN");
  Archivo.println("Fin;Fin");
  digitalWrite(ActivityLed,LOW);    // Fin de lectura de acelerómetros
  
  Archivo.close();
}

/////////////////////////////////// -- FUNCIONES -- /////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Write commands via I2C
void writeTo(int DEVICE, byte address, byte val) {
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.write(address); // send register address
  Wire.write(val); // send value to write
  Wire.endTransmission(); // end transmission
}
 
// Reads num bytes starting from address register on device in to _buff array
void readFrom(int DEVICE, byte address, int Bytes_A_Leer) {
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.write(address); // sends address to read from
  Wire.endTransmission(); // end transmission
 
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.requestFrom(DEVICE, Bytes_A_Leer); // request X bytes from device
 
  int i = 0;
  while(Wire.available()) // device may send less than requested (abnormal)
  {
    Values_Z[i] = Wire.read(); // receive a byte
    i++;
  }
  Wire.endTransmission(); // end transmission
}

// To find the last file on the SD card
int LastFile(File dir) {
  boolean Count = true;
  File temp;
  int NumArch=0,NumMaxArch=0,NA1,NA2,NA3;
  String NomArch;
  
   while(true) {
     File entry=dir.openNextFile();
     if(!entry && Count) {      // Si no encuentra archivo y no encontró ninguno, crear el primero.
       temp=SD.open("Vacio100.csv",FILE_WRITE);
       temp.close();
       NumMaxArch=100;
       break;
     }
     if(!entry && !Count) break; // Si no encuentra archivo pero ya encontró otros, salir de este Loop.

     NomArch=entry.name();
     NA1=NomArch.charAt(5);        // setCharAt(num,"x") sirve para cambiar un caracter por otro.
     NA2=NomArch.charAt(6);
     NA3=NomArch.charAt(7);
     NumArch=((NA1-48)*100)+((NA2-48)*10)+(NA3-48);  // Ordenar los números y restarles 48, para convertirlos de ascii a dec.
     if(NumArch>NumMaxArch) NumMaxArch=NumArch;
     Count=false;
   }
   return(NumMaxArch);
}

void Led(int e) {
  for(int i=0;i<e;i++) {
    digitalWrite(ActivityLed,HIGH);
    delay(100);
    digitalWrite(ActivityLed,LOW);
    delay(100);
  }
}

byte bcdToDec(byte val) {
  return ((val/16*10)+(val%16));
}

void getDateDs1307(byte *second,    // Función para extraer hora y fecha del reloj
          byte *minute,
          byte *hour,
          byte *dayOfWeek,
          byte *dayOfMonth,
          byte *month,
          byte *year)
  {
  // Reset the register pointer
  Wire.beginTransmission(DS1307);
  Wire.write(0);
  Wire.endTransmission();

  Wire.requestFrom(DS1307, 7);

  // A few of these need masks because certain bits are control bits
  *second     = bcdToDec(Wire.read() & 0x7f);
  *minute     = bcdToDec(Wire.read());
  *hour       = bcdToDec(Wire.read() & 0x3f);  // Need to change this if 12 hour am/pm
  *dayOfWeek  = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month      = bcdToDec(Wire.read());
  *year       = bcdToDec(Wire.read());
}
 
Última edición:
Es muy dificil contestarte a eso, pero cuando hay un "bug" la cosigna siempre es, divide y reina. Tenes que aislar cada paso del programa, ver si estas seguro que se cuelga dentro del loop (poniendo impresiones en lugares claves) y una vez que estas seguro, entrar dentro de la funcion.

Si, como parece, el problema es la comunicacion... esa comunicacion, tiene algun mecanismo de time out por si se cuelga? Preferible descartar una trama a colgar todo el sistema.
 
Hm, buen consejo. No lo había aplicado aquí, porque el único momento donde el LED indicador (llamado ActivityLed) se encuentra prendido, es en ese "for" que está allí... y lo que ocurre es que el LED derrepente nunca más se apaga, y en el SerialMonitor dejan de llegar datos (el último llegó partido).

Lo que pienso hacer, son algunas cosas:
- Poner dentro del "for" algo que desactive o vuelva a cero el WatschdogTimer (siesque hay alguno) (Eso es lo que yo entiendo a lo que te refieres con un "TimeOut", y más allá de este, no conozco ninguno...)
- Cambiar la comunicación serial por "SoftwareSerial". A ver si ese tipo de puerto funciona mejor.

Más allá de esto todavía no tengo ideas...

Igual hay que decir, que llegué a sospechar de la calidad de alguna librería de Arduino, y pensé en cambiarme completamente a programar en AVR Studio.... pensando que programar allí, debe ser 100% bueno y eficiente (fuera de la forma de programar del programador (yo)). No sé si estoy en lo cierto con eso...

Gracias por tu respuesta, y saludos!
 
No, cuando me refiero a timeout me refiero a nivel de protocolo, si estas esperando una respuesta de un puerto no esperas para siempre, despues de un cierto tiempo supones que el dato se perdio y reinicias el puerto, no a todo el micro como seria con el watchdog.
 
No, estoy haciendo algo aún más grave:
En vez de hacer lo que haces tu, no estaba haciendo nada. Quiero decir, que mandaba más y más datos por el puerto serie sin nunca "escuchar qué me quiere decir". Es decir, estoy suponiendo burdamente que todos los datos que yo meta en el buffer de salida, supongo que se envían correctamente, mientras, tal vez, esté tratando de meterle otro dato a la fuerza mientras todavía no se envió el anterior. Necesito poner cosas como "while(!serial) {}", o sea, esperar a que esté listo para enviar otro dato, o algo así.

Y creo que te entendí lo que me quieres decir. Más que poner un Timer, podría poner ese "while" para esperar. Tal vez el WDT podría encargarse entonces, de resetear todo si aún así el puerto se queda trancado. Y claro, por qué no, tabién enviar un dato de vuelta (desde el otro dispositivo) que confirme recepción.

Voy a implementar lo que dije, y respondo aquí si lo logré solucionar con eso o no.
Muchas gracias y saludos!
 
Timer (y)

Un timer termina, redundancia, si o si

While (n)

Una instrucción While también se queda esperando pero si no hay un final de instrucción o un escape se quedará eternamente.

Aplicable a la respuesta de algún periférico ;)
 
Estimados, espero que estén muy bien.

Problema solucionado. Lo que ocurría, es que estaba llenando el Buffer de salida serial (overflow).

Les entendí lo que me quieren decir con lo del Timer, pero no lo aplicaré tal cual, porque no quiero tener "hoyos grandes" en medio de la cadena de medición.

Lo que hice, (lo que muestro en el código a continuación), es meter un While, que espere a que el buffer de salida vuelva a niveles bajos de ocupación, antes de seguir enviando información.
Para cualquier otro problema, usé también un WatchdogTimer de 1 segundo. Pero eso es secundario.

En el código, debo admitir que yo mismo no estoy seguro del valor que puse en la línea del "while"... pero a partir de entonces, no he tenido ningún problema más para medir. Nunca más se me pegó el programa justo mientras transmitía información por serial.

Nótese: el Buffer de salida tiene 128 bytes.

Alguien puede comentarme en forma crítica la línea del "while"? Que esto me salió más por chiripazo que por saber realmente lo que estoy haciendo... con resultados para mí casualmente favorables.

Muchos saludos, y gracias!!

Código:
  for(int j=0;j<=400*SegundosMedicion;j++){          // MEDICIONES
    wdt_reset();
    while(Serial.available()>20) {}                  // Mientras hayan menos de 20 bytes en el                                                           buffer Serial, esperar a que hayan más disponibles
  
    digitalWrite(6,HIGH);                            // Para medir frecuencia de muestreo con un téster
    digitalWrite(6,LOW);
    
     readFrom(ADLX_A, DATAZ0, 2);
     z = ((int)Values_Z[1]<<8)|(int)Values_Z[0];
     z=z+OffsetA;
     Zeta=(z*Range*9.80665)/512;
     Serial.println(Zeta, DEC);
     Archivo.print(Zeta);
     Archivo.print(";");
     
     readFrom(ADLX_B, DATAZ0, 2);
     z = ((int)Values_Z[1]<<8)|(int)Values_Z[0];
     z=z+OffsetB;
     Zeta=(z*Range*9.80665)/512;
     Serial.println(Zeta, DEC);
     Archivo.println(Zeta);
  }
 
Atrás
Arriba