MODBUS RTU ARDUINO

Hola,buenas no soy capaz de enviar una trama (F703450200026451) [F7 es la direccion del esclavo , 03 es la Functions Code,4502 Direccion de datos del primer registro solicitado,0002 el numero total de los registros solicitados, 6451 Valor CRC]

desde arduino mediante el modbus RTU y que el dispositivo me responda el "response" del dispositivo segun el manual seria (F70304000005546E93),
me gustaria aprender como mandar tramas mediante modbus RTU estoy usando la libreria ModbusMaster, no se si es la correcta para este caso.Muchas gracias un Saludo

No entiendo la pregunta. ¿Quieres enviar una trama a pelo? o ¿quieres usar la libreria?

La libreria te simplifica el trabajo de crear la trama y solo tienes que especificar el esclavo, la dirección del registro y cuantos quieres leer.

Si lo que quieres es enviar la trama, eso es otra cosa. No hace falta la libreria:

uint8_t trama[8] = { 0xF7, 0x03, 0x45, 0x02, 0x00, 0x02, 0x64, 0x51 };

void enviar() {
  int i;
  for (i=0; i<8; i++) Serial.write(trama[i]);
}

Y así ya has enviado la trama, pero previamente la has tenido que crear tu: coger el id del esclavo, la función , direccion, registros y calcular el CRC. Esto es un ejemplo simplificado.

Para leer, tendrias que esperar a que haya datos y leer todo lo que venga por el buffer.

uint8_t bufer[64];
int i;

i=0;
while ( Serial.available() ) {
 bufer[i]=Serial.read();
 i++;
}

Una vez tengas la trama recibida en bufer tienes que sacar el id, la función, ver si es una excepcion, tratar los datos, verificar el CRC...

Creo que he simplificado mucho lo que hay que hacer.

Mi consejo es que si no quieres acabar loco, utilices la libreria. Será más fácil y rápido. A no ser que sea por animo y querer aprender como funciona ModBus.

Perdona mi ignorancia antes de nada, no he usado muchas librerías y no se como puedo usar esta, no entiendo en la parte del void loop el funcionamiento de este, por eso mi ignorancia con las librerias he aqui el codigo que estoy intentando aprender a usar de la libreria modbusmaster :

#include <ModbusMaster.h>


// instantiate ModbusMaster object
ModbusMaster node;


void setup()
{
 // use Serial (port 0); initialize Modbus communication baud rate
 Serial.begin(19200);

 // communicate with Modbus slave ID 2 over Serial (port 0)
 node.begin(2, Serial);
}


void loop()
{
 static uint32_t i;
 uint8_t j, result;
 uint16_t data[6];
 
 i++;
 
 // set word 0 of TX buffer to least-significant word of counter (bits 15..0)
 node.setTransmitBuffer(0, lowWord(i));
 
 // set word 1 of TX buffer to most-significant word of counter (bits 31..16)
 node.setTransmitBuffer(1, highWord(i));
 
 // slave: write TX buffer to (2) 16-bit registers starting at register 0
 result = node.writeMultipleRegisters(0, 2);
 
 // slave: read (6) 16-bit registers starting at register 2 to RX buffer
 result = node.readHoldingRegisters(2, 6);
 
 // do something with data if read is successful
 if (result == node.ku8MBSuccess)
 {
   for (j = 0; j < 6; j++)
   {
     data[j] = node.getResponseBuffer(j);
   }
 }
}

Algun sitio donde pueda encontrar informacion para esta librería soy muy nuevo en esto

Por favor, edita tu post para poner el código entre las etiquetas [ code ] [ /code ] correspondiente. Mira las NORMAS.

Primero de todo, nunca he usado la libreria ModbusMaster, así que no me he entretenido mucho en mirarla. En su lugar uso SimpleModbus, por nada en particular, es la primera que me encontré, me gustó, la destripe, y la he modificado en mi conveniencia.

Mi consejo es que te pares un rato a leer los ejemplos e intentar analizar la librería ModbusMaster. He intentado explicarte como funciona el ejemplo dentro del código de lo que hace:

#include <ModbusMaster.h>


// Crea un objeto maestro modbus.
ModbusMaster node;


void setup()
{
 // Este ejemplo no utiliza rs485 si no que utiliza el puerto serie para
 // realizar la comunicación.
 Serial.begin(19200);

 // Con begin especificamos el esclavo con el que queremos comunicarnos
 // y la conexión que vamos a especificar.
 node.begin(0xF7, Serial);
}


void loop()
{
 static uint32_t i;
 uint8_t j, result;
 uint16_t data[6];
 
 //*******************************************************************************
 // ESTA PARTE DE CÓDIGO ES PARA ENVIAR UN DATO, NO TIENE NADA QUE VER
 // CON TU TRAMA. SI LO BORRAS NO PASA NADA, IGUALMENTE TE LO EXPLICO.

 // La variable i es un contador de pasos, en cada ciclo de loop se incrementa.
 // Se ha declarado en el loop y por lo tanto solo es accesible en el loop, pero
 // para poder modificarla se ha declarado como static, así cada vez que pase 
 // por la declaración, no la crea ya que existe. De otra forma, cada vez que 
 // que pase por la declaración la crearía y le asignaría el valor 0.
 // Para novatos, yo la habría declarado como global (fuera del loop).
 i++;
 
 // Con esta funciones se introducen los datos que queremos transmitir. En este
 // enviamos el valor del loop actual, como es un entero largo no cabe en un
 // registro de 16 bits, por eso lo divide.
 node.setTransmitBuffer(0, lowWord(i));
 node.setTransmitBuffer(1, highWord(i));
 
 // Enviamos los datos que hemos escrite en el buffer, para ello utilizamos
 // la función Modbus 16. En nuestro caso enviamos el valor de i a la dirección 0
 // con dos registros.
 result = node.writeMultipleRegisters(0,2);
 //*******************************************************************************

 // Ahora seguimos con tu trama, quiere leer la dirección 0x4502 y leer dos datos. Quieres leer los
 // registros Holding, para ello usamos la función readHoldingRegisters.
 result = node.readHoldingRegisters(0x4502, 2);
 

 // En 'result' tendremos un codigo de error si ha ido mal: un error de recepcióm
 // una excepción, etc. Asi que comprobamos que la trama sea correcta y la leemos
 if (result == node.ku8MBSuccess)
 {
   for (j = 0; j < 2; j++)
   {
	 // Leemos los word de la petición, los guardamos en data y ahi tenemos los
	 // datos que queremos. 
     data[j] = node.getResponseBuffer(j);
   }
 }


 delay(1000);
}

El ejemplo básico es una comunicación simple usando la señal serie TTL del arduino. Para una comunicación RS485 hay que hacer unos cambios en el código, pero no has especificado sobre que estás trabajando (rs232 o rs485).

Echa un vistazo al código y según vayas viendo cosas que no entiendas te podré ir explicando.

Muchas gracias antes de nada,me esta sirviendo de mucha ayuda tus explicaciones, veras usó rs485,tengo un aparato industrial que me han conseguido para realizar pruebas,se trata de leer y escribir datos por modbus y mostrarlos en una pantalla en este caso la presión que tiene la bomba de agua si le mando esta trama (F703450200026451) F7 es la dirección que es la que tendría que poner en

void setup(){Serial.begin(19200);
node.begin(0xF7,RS485);
}

, si no me equívoco.
Tendría que sustituir la i por los valores de mi trama verdad?

Tengo una duda si quisiera pasar la trama por el rs485, ¿cuando mandas los datos los transforma en ASCII?
Un saludo, muchas gracias
Te adjunto unas fotos, del funcionamiento de la bomba


parametros 2.PNG

parametros 2.PNG

Debes leerte un poco la documentación de la libreria y los ver los ejemplos que trae.

Mira esté código:

#include <ModbusMaster.h>

ModbusMaster node;    // Objeto modbus maestro.
long presion;              // Variable de presión.

void setup() {
	Serial.begin(19200);   // Inicio puerto serie.
	node.begin(0xF7, Serial); // La comunicación será con la dirección 0xF7 a través del puerto serie.
}

void loop() {
	uint8_t result;
	result = node.readHoldingRegisters(0x4502,2); // Realizamos la petición, dirección 0x4502, 2 registros.
	if ( result == node.ku8MBSucess ) {
		// Si la petición ha sido correcta y no ha fallado tendremos un buffer con los
		// registros, en la imagen no se especifica, asi que supondre que en el primero
		// esta la parte alta del valor y en el segundo la baja.
		uint16_t a, b;
		uint32_t c;
		a = node.getResponseBuffer(0); // Primer registro.
		b = node.getResponseBuffer(1); // Segundo registro.
                c = a<<16 | b; // Los unimos para obtener un entero sin signo de 32bits.
		presion = (long)c; // Convierto el entero sin signo a long (con signo).
	}
        // Otro código, por ejemplo, mostrar el valor en un lcd.
}

Con ese código vas a leer la presión que te diga el dispositivo. Como ves, solo hago la lectura. De hecho ese parametro en la imagen pone que es read only (solo lectura), con lo que si lo intento escribir dará una excepción.

Así que utilizo: begin(id esclavo, puerto serie) para iniciar una comunicación con un esclavo determinado. readHoldingRegisters(dirección registro, cuantos registros) para leer el valor en dos registros.

¿Por que dos registros?. Los registros en ModBus son de 16 bits. La presión se almacena en esa dirección como un entero de 32 bits. Por lo tanto, para leer la presión has de leer 2 registros.

El resultado de la operación se guarda en la variable result. Si la operación ha ido bien en el buffer de recepción estarán los datos recibidos desde el esclavo.

La función getResponseBuffer(indice) te da el registro que le digas en el indice de los datos recibidos, y como dije, seran 16 bits; por lo tanto debes unir los dos registros para formar un entero de 32bits.

El puerto serie trabaja con binario, no con ASCII, que cuando tu hagas un serial.print(texto), te escriba un texto no quiere decir nada. Un carácter ASCII es un determinado valor binario, un byte, nada mas.

¿Se puede saber qué aparato es? Por que aún no has especificado el hardware que vas usar.

Muchas gracias, por tus respuestas tan rápidas y eficaces , el hardware que uso es una PumpMeter de KSB esta el manual de internet.
Me surgió una duda a la hora de escribir valores, tendría que usar el de múltiples registros por que son dos pero como escribiría el valor no entiendo el funcionamiento. ¿para modificar las revoluciones de la bomba ?
y cuando te dan varias opciones seria modificar solo a dichas opciones?
Se pueden leer o escribir en varios registros a la vez?

Según la imagen en la dirección 0x4520 y con tamaño UINT16 (entero sin signo de 16 bits), eso quiere decir que solo usa un registro. Para ello se hace uso de la función writeSingleRegister(dirección, valor)

Por ejemplo:

#include <ModbusMaster.h>

ModbusMaster node;
unsigned int rpm = 1000; // variable de las rpm.

void setup() {
	Serial.begin(19200);
	node.begin(0x7f, Serial);
}

void loop() {
	// Aquí un código para establecer la rpm que queremos
	// que tenga, ya sea por un teclado, botones, etc..
	// Finalmente escribimos las rpm.
	result = node.write(0x4520, rpm);
	if ( result == node.ku8MBSuccess ) 
	{
		// Se ha hecho correctamente.
	}
	else
	{
		// Hay error, mostrar en LCD, por ejemplo.
	}
}

En el caso de querer escribir varios registros a la vez si tendremos que usar la funcion writeMultipleRegisters(direccion, numero). En la imagen veo que el siguiente valor de la tabla es la potencia y usa 32 bits (UINT32. Hay que usar entonces el buffer de transmisión para enviar los datos:

#include <ModbusMaster.h>
unsigned long potencia = 100; 

void setup() {
	Serial.begin(19200);
	node.begin(0x7f, Serial);
}

void loop() {
    //
	// ... manejo de la potencia ...
	// 
	// Escribir la pontencia:
	node.setTransmitBuffer(0, highWord(potencia));
	node.setTransmitBuffer(1, lowWord(potencia));
	result = node.write(0x4521, 2);
	if ( result == node.ku8MBSuccess ) 
	{
		// Se ha hecho correctamente.
	}
	else
	{
		// Hay error, mostrar en LCD, por ejemplo.
	}
}

Podrías cambiar varios registros a la vez, por ejemplo las revoluciones y la potencia en una misma petición, pero solo lo puedes hacer si los registros estan seguidos, es decir, direcciones consecutivas:

#include <ModbusMaster.h>
unsigned int  rpm = 1000;
unsigned long potencia = 100; 

void setup() {
	Serial.begin(19200);
	node.begin(0x7f, Serial);
}

void loop() {
	//...
	node.setTransmitBuffer(0, rpm); // Primer registro son las rpm.
	node.setTransmitBuffer(1, highWord(potencia)); // Siguiente direccion
	node.setTransmitBuffer(2, lowWord(potencia)); 
	result = node.write(0x4520, 3); // Dirección del primer registro y total de 3 a escribir.
	//...
}

Yo tendría cuidado con el número de registros a escribir, el UART del Arduino tiene poco tamaño y eso puede afectar a como se envia la trama y, quizás, el receptor no la reciba bien. Aunque una docena de registros no sería mucho problema.

Igualmente cuando haces una llamada readHoldingRegisters, debes especificar la dirección de inicio y leer tantos registros como quieras o sea necesario.

#include <ModbusMaster.h>

ModbusMaster node;    
uint16_t rpm;
uint32_t potencia;

void setup() {
	Serial.begin(19200);
	node.begin(0xF7, Serial);
}

void loop() {
	uint8_t result;
	result = node.readHoldingRegisters(0x4520,3); // leemos rpm y potencia.
	if ( result == node.ku8MBSucess ) {
		rpm = node.getResponseBuffer(0); // Primer registro.
		uint16_t a, b;
        a = node.getResponseBuffer(1);
		b = node.getResponseBuffer(2); 
        potencia = a<<16 | b; // Los unimos para obtener un entero sin signo de 32bits.
		
	}
    // ...    
}

MUCHAS GRACIAS!!!! Un Saludo

4 posts were split to a new topic: Modbus RTU Consulta