Imprimir varios valores de la respuesta RS485 MODBUS con ARDUINO Uno

Hola a todos. Estoy guiándome del tutorial Measure Soil Nutrient using Arduino & Soil NPK Sensor
Hardware:
RS485
Arduino Uno alimentación conectado pc
Cargador 9V (Solo para el sensor de suelo)
Sensor de suelo (lee 7 valores: hum, temp, ec, ph, n, p, k)
Software :
Arduino IDE

El manual esta incompleto:
https://forum.arduino.cc/uploads/short-url/vRWYhwqOArAuTNiYGpBubyAUuA8.pdf

El código de solicitud: 0x01, 0x03, 0x00, 0x00, 0x00, 0x07, 0x04, 0x08
y la respuesta son para: hum, temp, ec, ph, n, p, k

Este es el código:

#include <Wire.h>
//Library to convert Digital Output pins of the board to transmitter as well as receiver
#include <SoftwareSerial.h>
#define RE 8
#define DE 7
const byte requestframe[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x07, 0x04, 0x08};
byte values[19];
SoftwareSerial mod(2, 3); // RX, TX ( Creates a new SoftwareSerial object )
void setup() {
  Serial.begin(4800);
  mod.begin(4800);
  pinMode(RE, OUTPUT);
  pinMode(DE, OUTPUT);
}
void loop() {
  Hum();
  delay(3000);
}
void Hum() {
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(10);
  if (mod.write(requestframe, sizeof(requestframe)) == 8) {
    digitalWrite(DE, LOW);
    digitalWrite(RE, LOW);
    for (byte i = 0; i <19; i++) {
      values[i] = mod.read();
      //VALUES FOR EACH: hum, temp, ec, ph, n, p, k
      Serial.print(values[i], HEX);
      Serial.print(' ');
      delay(10);
    }
    Serial.print("| hum: ");
    Serial.print(values[4]);
    Serial.print(" % ");
    Serial.print(" temp: ");
    Serial.print(values[5]);
    Serial.print(" º ");
    Serial.print(" ec: ");
    Serial.print(values[6]);
    Serial.print("  ");
    Serial.print(" ph: ");
    Serial.print(values[7]);
    Serial.print(" ");
    Serial.print(" n: ");
    Serial.print(values[8]);
    Serial.print("  ");
    Serial.print(" p: ");
    Serial.print(values[9]);
    Serial.print("  ");
    Serial.print(" k: ");
    Serial.print(values[10]);
    Serial.print(" ");
    Serial.println(" ");
  }

}

Imagen #1 Prueba de solicitud de datos con software:

Imagen #2 Me guie de la sgte. imagen para realizar la conexión del circuito:

Imagen #3 Marco de solicitud y Marco de respuesta

Imagen #4 Salida en el monitor serie imprime datos incorrectos:

1.-En el tutorial realiza 7 iteraciones para obtener un solo valor.
2.-En mi caso el sensor mide 7 valores (hum, temp, ec, ph, n, p, k) y realizo 19 iteraciones no se si estoy
en lo correcto.
3.- Aun no entiendo bien. Desde que posición se debería imprimirse los datos de respuesta del arreglo "values"?

Por favor, ¿alguien que me guie para imprimir los datos correctos?. Estaré muy agradecido.

Te explico que es la trama que recibes y que se guarda en el array values:

byte values[19] = { 0x01, // ID del dispositivo.
                    0x03, // Función.
										0x0e, // Número de bytes de datos.
										0x00, 0x00, // Dato 1: Humedad.
										0x00, 0xfb, // Dato 2: Temperatura,
										0x00, 0x00, // Dato 3: Conductividad,
										0x00, 0x45, // Dato 4: PH,
										0x00, 0x00, // Dato 5: Nitrogeno,
										0x00, 0x00, // Dato 6: Fósforo.
										0x00, 0x00, // Dato 7: Potasio.
										0xdd, 0xf6  // CRC.
};

El primer byte se corresponde al identificador y el segundo a la función modbus.

El tercer byte indica la cantidad de bytes de datos en la trama. En tu ejemplo solicitas 7 registros, estos registros ocupan 2 bytes, por lo tanto en la respuesta obtienes que se envian 14 bytes de datos. Si compruebas en la trama contando los byte de datos verás que son 14.

A partir del cuarto byte, este incluido, empiezan los datos. En modbus se trabaja con registros de 16 bits. Esto quiere decir que cada dos bytes que recibes se unen para formar una palabra de 16 bits.

De esos dos pares de byte que recibes formando una palabra, el primero es la parte alta y el segundo la parte baja. En tu caso la humedad la recibes como dos bytes { 0x00, 0xfb }, unidos forman la palabra 0x00fb (251 en decimal).

De esta forma puedes leer todos los datos obteniendo una palabra de 16 bits que "casualmente" se corresponde con un entero en Arduino.

Te dejo un ejemplo con una función que te puede resultar útil.


byte values[19] = { 0x01, // ID del dispositivo.
                    0x03, // Función.
										0x0e, // Número de bytes de datos.
										0x00, 0x00, // Dato 1: Humedad.
										0x00, 0xfb, // Dato 2: Temperatura,
										0x00, 0x00, // Dato 3: Conductividad,
										0x00, 0x45, // Dato 4: PH,
										0x00, 0x00, // Dato 5: Nitrogeno,
										0x00, 0x00, // Dato 6: Fósforo.
										0x00, 0x00, // Dato 7: Potasio.
										0xdd, 0xf6  // CRC.
};

// Esta función coge el bufer respondido con la trama modbus, y obtiene el
// dato que se encuentra en la posición pos.
int getInverseInt(byte *bufer, int pos) {
  int a = pos*2 + 3; // Evitamos el id, funcion y número de datos.
	// Montamos la palabra y la devolvemos como un int.
	return (int)((word)bufer[a]<<8|(word)bufer[a+1]);
}

void setup() {
  Serial.begin(9600);
  
	// Mostramos los siete registros.
  for (int i=0; i<7; i++) {
    Serial.println(getInverseInt(values, i));
  }
}

void loop() {

}

Gracias @victorjam por su tiempo y la explicación :+1:
Realice cambios en mi código para mostrar cada valor

//Library to convert Digital Output pins of the board to transmitter as well as receiver
#include <SoftwareSerial.h>
#define RE 8
#define DE 7
const byte requestframe[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x07, 0x04, 0x08};
byte values[19];
SoftwareSerial mod(2, 3); // RX, TX ( Creates a new SoftwareSerial object )
void setup() {
  Serial.begin(4800);
  mod.begin(4800);
  pinMode(RE, OUTPUT);
  pinMode(DE, OUTPUT);
}
void loop() {
  Valores();
  // Mostramos los siete registros.
  delay(2000);
  for (int i = 0; i < 7; i++) {
    switch (i) {
      case 1:
        Serial.print("| hum: ");
        Serial.print(getInverseInt(values, i)/100.00);
        Serial.print(" % ");
        break;
      case 2:
        Serial.print(" temp: ");
        Serial.print(getInverseInt(values, i)/100.00);
        Serial.print(" º ");
        break;
      case 3:
        Serial.print(" ec: ");
        Serial.print(getInverseInt(values, i));
        Serial.print("  ");
        break;
      case 4:
        Serial.print(" ph: ");
        Serial.print(getInverseInt(values, i));
        Serial.print(" ");
        break;
      case 5:
        Serial.print(" n: ");
        Serial.print(getInverseInt(values, i));
        Serial.print("  ");
        break;
      case 6:
        Serial.print(" p: ");
        Serial.print(getInverseInt(values, i));
        Serial.print("  ");
        break;
      case 7:
        Serial.print(" k: ");
        Serial.print(getInverseInt(values, i));
        Serial.print(" ");
        break;
      default:
        //si nada coincide, hace lo predeterminado
        // default es optional
        break;
    }

  }
  Serial.println(" ");
  delay(1000);
}

void Valores() {
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(10);
  if (mod.write(requestframe, sizeof(requestframe)) == 8) {
    digitalWrite(DE, LOW);
    digitalWrite(RE, LOW);
    for (byte i = 0; i < 19; i++) {
      values[i] = mod.read();
      Serial.print(values[i], HEX);
      Serial.print(' ');
      delay(10);
    }
//    Serial.println(" ");
  }

}
// Esta función coge el bufer respondido con la trama modbus, y obtiene el
// dato que se encuentra en la posición pos.
int getInverseInt(byte *bufer, int pos) {
  int a = pos * 2 + 3; // Evitamos el id, funcion y número de datos.
  // Montamos la palabra y la devolvemos como un int.
  return (int)((word)bufer[a] << 8 | (word)bufer[a + 1]);
}

Respecto a humedad y temperatura la division entre 100.00.
aunque la impresión de cada variable son incorrectos.

La temperatura normal aquí esta por encima de los 21º grados y la humedad es mayor 60% por ciento.
¿Será que la comunicación por el protocolo de MODBUS con ARDUINO UNO este mal?
¿Me falta alguna librería o conexión adicional para que muestre los valores reales?
Cualquiera ayuda o sugerencia es bienvenida. Estaré muy agradecido :smiley:

Viendo las tramas que recibes, veo que no son validas ninguna y de tamaños distintos.

Creo que el problema puede deberse a una mala sincronización de tiempos y que la operación de lectura/escritura del bus se solapa.

Analizando tu función:

void Valores() {
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(10);
  if (mod.write(requestframe, sizeof(requestframe)) == 8) {
    digitalWrite(DE, LOW);
    digitalWrite(RE, LOW);
    for (byte i = 0; i < 19; i++) {
      values[i] = mod.read();
      Serial.print(values[i], HEX);
      Serial.print(' ');
      delay(10);
    }
//    Serial.println(" ");
  }

}

Primero pones el max485 en modo escritura y espera 10 ms. Luego escribes y sin controlar nada cambias directamente los pines a modo lectura. Quizás aún no se haya enviado la petición y esté pendiente.

Te pongo como lo haría yo, no necesariamente tiene que ser bueno o eficiente:

int valores() {
  // Poner el chip en modo escritura.
  digitalWrite(DE, HIGH);
	digitalWrite(RE, HIGH);
	// Enviamos los datos.
	mod.write(requestFrame, 8);
	// Esperamos a que los datos hayan sido enviados, para ello usamos la función
	// flush(), que espera a que se hayan enviado los bytes para continuar.
	// Ojo, al tratarse de softwareSerial creo que no funcionaba del todo bien.
	// Así que podemos usar un retraso del orden de microsegundos en su lugar.
	mod.flush();
	// Poner el chip en modo lectura.
	digitalWrite(DE, LOW);
	digitalWrite(RE, LOW);
  // Esperamos a que haya datos en el puerto antes de salir de la función.
	while (!mod.available());
	// Ahora ya hay datos, los leemos. 
	for (byte i=0; i<19; i++) {
	  values[i] = mod.read();
		Serial.print(values[i], HEX);
		Serial.print(' ');
		delay(10);
	}
	Serial.println();
}

Quizás esta sea la forma más simple, pero ten en cuenta que modbus es un protocolo de comunicaciones y tiene más miga: tiempos de espera entre caracteres y tiempo de espera entre tramas. Hay que controlar la cantidad de bytes recibidos, para ver si la trama es correcta o no. Analizar si es una excepción o no, etc.

En el caso de la recepción añadiria cosas como tiempo de espera, para dar un error de timeout si no recibo nada. También controlaria el número de bytes recibidos según la petición, en este caso son 19 lo que se esperan si se recibe otro número pasa algo...

Ya existen muchas librerias modbus, tanto para maestro como para esclavo. que puedes usar para ese proposito. Quizás convenga que le eches un vistazo.

Aprecio tu respuesta @victorjam :slight_smile:
Estaré viendo otras librerías para la comunicación con el protocolo MODBUS ya que sigo obteniendo los mismos valores.