Problemas con comunicación RS485-RS232 (ARDUINO)

Ayer hablaba de la importancia del código bien estructurado y optimizado.
Fíjate en esta sección del código:

¿Por qué están las variables Slave y command definidas dentro del conjunto de IFs anidados?
Siempre que se pueda, las variables se definen fuera.

¿Por qué lees los valores "I" y "F" directamente con un SerialRead() y, en cambio, lees "L" mediante la variable auxiliar command, que es un SerialRead()?
Eso sólo tendría sentido si usaras el contenido de command posteriormente, pero no en este caso, que sólo quieres saber si llegó una "L".

----------

En estas rutinas de lectura y escritura en puertos, el timing es crítico.
Todo ese código confuso, mal estructurado y no optimizado, son retrasos en la ejecución que luego hacen que la cosa funcione en la simulación, pero no en la realidad. Hay lugares del código donde no puede haber retrasos. En cambio, en otros lugares hay que introducirlos a propósito para no perder datos.


Ok.
Elige con qué instrucción quieres que te lo implemente:
For...Next
While...Wend
Do...Loop
Select...Case
If ...Then
Try...Catch
Foreach...
Más claro que el agua imposible. Me diste el pie para arrancar. Mañana te cuento cómo me fue
 
Este es un ejemplo usando la lectura del buffer de entrada en una string y procesando la string, esto te permite verificar que exista i y f antes de proceder a analizar los datos almacenados, no tengo una pantalla así que solo lo analiza y responde por el serial, pero te sirve para probar como manejar y decodificar el paquete.

C++:
const int LED = 13;
const int Enable = 2;


/*** Función para poner en espera por la respuesta ***/
bool esperarDato(int timeout) {
  while (Serial.available() == 0 && timeout) {
    delay(5); //ajustar el delay de cada ciclo
    --timeout;
  }
  if (timeout) return true;  //retorna verdadero si no se agotó el tiempo y detecta datos.
  else return false;         // retorna falso si se agotó el tiempo de espera.
}

void setup() {
  Serial.begin(9600);
  Serial.setTimeout(250); //Establece el tiempo sin recepción que desencadena el TimeOut

  pinMode(LED, OUTPUT);
  pinMode(Enable, OUTPUT);
  digitalWrite(LED, LOW);
  digitalWrite(Enable, HIGH);  // inicio de transmision al esclavo 1
}
void loop() {
  Serial.println("-");
  delay(10);
  digitalWrite(Enable, HIGH);  // inicio de transmision al esclavo 1
  Serial.println("I1LF");        //puede ir todo junto
  Serial.flush();
  digitalWrite(Enable, LOW);  // fin de transmision al esclavo 1


  if (esperarDato(2000)) {  //Cada ciclo son 5ms, para 2000*5ms = 10s si se recibe antes da verdadero, si se agota la espera es falso.
    String respuesta = Serial.readString();         //Simplemente leemos el buffer de entrada hasta que se agote la espera al finalizar la transmisión
    int iniciador = respuesta.indexOf('i');           //Localizar inicio, -1 es igual a no encontrado, 0 es un valor válido
    int finalizador = respuesta.indexOf('f');         //localizar fin de transmisión
    if (iniciador >= 0 && finalizador > iniciador) {  //se valida que ambos existan, iniciador debe ser igual o mayor a 0 y el finalizador mayor al iniciador.
      /***  determinar la posición de los separadores  ***/
      int mark1 = respuesta.indexOf(' ', iniciador + 1);
      int mark2 = respuesta.indexOf(' ', mark1 + 1);
      int mark3 = respuesta.indexOf(' ', mark2 + 1);
      /*************************************************************************
      Se genera una substring usando los marcadores y se convierten en int
      Esta parte de encarga de asignar las variables para decodificar el paquete
      *************************************************************************/
      int dispositivo = respuesta.substring(mark1+1, mark2).toInt();  //Decodifica el número de dispositivo
      int valor =  respuesta.substring(mark2+1, mark3).toInt();       //Decodifica el valor

      //esta sección solo es para componer la cadena con los datos detectados
      String dato = "Sensor " + String(dispositivo) + ":" + String(valor);
      Serial.println(dato);
    } else {
      Serial.println("Transmision inválida");
    }
  } else {
    Serial.println("Tiempo de espera agotado, sin respuesta");
  }
}

De ahí te recomendaría ver cómo puedes meterlo en un loop usando matrices para asignar los dispositivos en lugar de generar una variable para cada uno, así podrías reutilizar el código usando valor[dispositivo]
 
Última edición:
Este es un ejemplo usando la lectura del buffer de entrada en una string y procesando la string, esto te permite verificar que exista i y f antes de proceder a analizar los datos almacenados, no tengo una pantalla así que solo lo analiza y responde por el serial, pero te sirve para probar como manejar y decodificar el paquete.

C++:
const int LED = 13;
const int Enable = 2;


/*** Función para poner en espera por la respuesta ***/
bool esperarDato(int timeout) {
  while (Serial.available() == 0 && timeout) {
    delay(5); //ajustar el delay de cada ciclo
    --timeout;
  }
  if (timeout) return true;  //retorna verdadero si no se agotó el tiempo y detecta datos.
  else return false;         // retorna falso si se agotó el tiempo de espera.
}

void setup() {
  Serial.begin(9600);
  Serial.setTimeout(250); //Establece el tiempo sin recepción que desencadena el TimeOut

  pinMode(LED, OUTPUT);
  pinMode(Enable, OUTPUT);
  digitalWrite(LED, LOW);
  digitalWrite(Enable, HIGH);  // inicio de transmision al esclavo 1
}
void loop() {
  Serial.println("-");
  delay(10);
  digitalWrite(Enable, HIGH);  // inicio de transmision al esclavo 1
  Serial.println("I1LF");        //puede ir todo junto
  Serial.flush();
  digitalWrite(Enable, LOW);  // fin de transmision al esclavo 1


  if (esperarDato(2000)) {  //Cada ciclo son 5ms, para 2000*5ms = 10s si se recibe antes da verdadero, si se agota la espera es falso.
    String respuesta = Serial.readString();         //Simplemente leemos el buffer de entrada hasta que se agote la espera al finalizar la transmisión
    int iniciador = respuesta.indexOf('i');           //Localizar inicio, -1 es igual a no encontrado, 0 es un valor válido
    int finalizador = respuesta.indexOf('f');         //localizar fin de transmisión
    if (iniciador >= 0 && finalizador > iniciador) {  //se valida que ambos existan, iniciador debe ser igual o mayor a 0 y el finalizador mayor al iniciador.
      /***  determinar la posición de los separadores  ***/
      int mark1 = respuesta.indexOf(' ', iniciador + 1);
      int mark2 = respuesta.indexOf(' ', mark1 + 1);
      int mark3 = respuesta.indexOf(' ', mark2 + 1);
      /*************************************************************************
      Se genera una substring usando los marcadores y se convierten en int
      Esta parte de encarga de asignar las variables para decodificar el paquete
      *************************************************************************/
      int dispositivo = respuesta.substring(mark1+1, mark2).toInt();  //Decodifica el número de dispositivo
      int valor =  respuesta.substring(mark2+1, mark3).toInt();       //Decodifica el valor

      //esta sección solo es para componer la cadena con los datos detectados
      String dato = "Sensor " + String(dispositivo) + ":" + String(valor);
      Serial.println(dato);
    } else {
      Serial.println("Transmision inválida");
    }
  } else {
    Serial.println("Tiempo de espera agotado, sin respuesta");
  }
}

De ahí te recomendaría ver cómo puedes meterlo en un loop usando matrices para asignar los dispositivos en lugar de generar una variable para cada uno, así podrías reutilizar el código usando valor[dispositivo]
Excelente aporte; Anoche más o menos lo enfoque como mencionaste en otro tópico, pero con algunos agregados para ver si podía leer mas datos, más sensores y al parecer, en Proteus funcionó... Pero ahora que veo tu manera de manejar el string, me queda más claro y más seguro. Edito y vuelvo... Mepa que hoy queda un ejemplo potable de rs485 con Arduino...
@DOSMETROS Si cito los links de las páginas donde arranque y estaban mal los ejemplos, estaría en infracción?
 
Excelente aporte; Anoche más o menos lo enfoque como mencionaste en otro tópico, pero con algunos agregados para ver si podía leer mas datos, más sensores y al parecer, en Proteus funcionó... Pero ahora que veo tu manera de manejar el string, me queda más claro y más seguro. Edito y vuelvo... Mepa que hoy queda un ejemplo potable de rs485 con Arduino...
@DOSMETROS Si cito los links de las páginas donde arranque y estaban mal los ejemplos, estaría en infracción?
Siguiendo del ejemplo anterior, se suele usar distintos marcadores para cada valor, así es más fácil identificarlos en lugar de usar espacios, por ejemplo si usas un formato como:
i56t35v5.85s200f
Usas las letras como separador para identificar el dispositivo con la i, temperatura con t, voltaje con v, (puedes usar toFloat() para el decimal) otro sensor con s y el fin con f, ya solo usas indexOf(separador) para identificar donde cortar los datos.
 
Pregunta:
Cuales son los requerimientos temporales para ejecutar ese código??
Por que si bien lo que propone @Nuyel está OK, las funciones de procesamiento de strings lo están recorriendo una y otra vez bajo la acción de cada indexOf() y eso es una demanda tiempo potencialmente excesiva....dependiendo de los requerimientos temporales de ejecución del código, el contexto de ejecución y el baudrate del enlace.
Por eso comenté antes la posibilidad de emplear una máquina de estados que recibe, procesa y ubica cada caracter individual a medida que van llegando y deja para el final las conversiones de tipos que sean necesarias.
Ademas, esta forma de trabajo puede ser parte de la rutina de interrupción de recepción serie, con lo cual deja mucho tiempo libre para ejecutar otros procesos.
Claro que el formato de los datos debe estar perfectamente definido...
 
Pregunta:
Cuales son los requerimientos temporales para ejecutar ese código??
Por que si bien lo que propone @Nuyel está OK, las funciones de procesamiento de strings lo están recorriendo una y otra vez bajo la acción de cada indexOf() y eso es una demanda tiempo potencialmente excesiva....dependiendo de los requerimientos temporales de ejecución del código, el contexto de ejecución y el baudrate del enlace.
El problema con intentar aplicar un esquema de tiempo crítico es que Arduino tiene demasiado pasando en segundo plano como para intentarlo, como el mecanismo es de Consulta-Respuesta el único tiempo crítico es en realizar los cambios entre envió a recepción, para el caso de recepción darle tiempo para que el esclavo responda. Por ello se implementa un bucle que se queda a la espera de que el esclavo envíe la respuesta inmediatamente después de generar el cambio a modo lectura, de esta forma intencionalmente se espera a que el esclavo procese el comando y realice la muestra de datos, luego el esclavo debería enviar toda la respuesta junta, el ejemplo que puse le da hasta 10s de tiempo de espera lo que en la práctica es excesivo, pero igual si la recibe en 10ms sale del bucle y lee TODA la respuesta.

En la práctica si buscara velocidad preferiría usar un formato codificado en binario, por ejemplo, enviar la trama [byte dirección][byte comando] y recibir [dirección][longitud][dato1][valor1]....[daton][valorn][CRC], luego hacer las asignaciones directamente.

Pero no podemos tratar a Arduino con tanto detalle, el serialEvent no es ni siquiera un ISR sino más bien polling que ocurre después del loop dentro del main (no se ve, pero Arduino tiene un main()) validando si existen datos, el Arduino procesa la comunicación por interrupciones de una manera en la que el usuario no tiene control, esto evita que los novatos arruinen la comunicación, por eso en el código se usa el Serial.flush() que básicamente le dice "espera hasta que la transmisión termine" ya que sin ello el Serial.print solo manda los datos al buffer pero no significa que la transmisión esté completa.

Cuando se genera la recepción el Arduino procesa usando interrupciones y almacena los datos en un buffer de 64bytes interno, el delay() al basarse en software tampoco genera bloqueo que las deshabiliten por lo que no debería haber perdida de datos, solo hay problemas de sincronía ya que el delay de hecho se extiende si se ejecuta una interrupción, en cambio, si intentas meterte en una interrupción, en primer lugar no tenemos acceso a la del serial real, y ejecutar otras SÍ pueden suspender la recepción causando perdida de paquetes, por eso es preferible que el chip pierda todo el tiempo que quiera al recibir los datos y enviarlos teniendo la trama completa una vez que ya los procesó.

En resumen, esta implementación es mala en procesamiento, usa mucha memoria y ciertamente toma tiempo, pero en el tema de la transmisión y recepción no debería tener conflictos. Por ello es que se envía y queda a la espera de la respuesta, una vez que detecta entrada de datos queda en lectura hasta que la transmisión termine por timeOut (que el código establece en 250ms sin recibir nuevos datos), entonces ya lo procesa todo sin tener que preocuparse del tiempo ya que los esclavos no deberían transmitir hasta la siguiente consulta. Si la trama ocupa más de los 64 bytes del buffer sí le recomendaría entonces reducir el tiempo de verificación en la función de espera, pero tendrá que buscar el punto de equilibrio según su aplicación.
 
Última edición:
Atrás
Arriba