emelianov / modbus-esp8266

Most complete Modbus library for Arduino. A library that allows your Arduino board to communicate via Modbus protocol, acting as a master, slave or both. Supports network transport (Modbus TCP) and Serial line/RS-485 (Modbus RTU). Supports Modbus TCP Security for ESP8266/ESP32.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Getting Illegal Data Value (03) rather than Illegal Data Address (02)

techlobo opened this issue · comments

Just wrote a sample script to test Modbus_RTU_Slave functionality for Coil and DI operations using callbacks.

The basic Read / Write operations (for Coils), and Read operations (for Discrete Inputs)(or 'Ists' as the library refers to them) work fine, and I am getting correct message data in both directions.

However if I specify a starting address outside of the declared range for the Coils / DI's then I am receiving an 'Illegal Data Value' error rather than an 'Illegal Data Address' error.

I haven't found where the 'Illegal Data Value' exception is being raised yet, as it occurs before the defined callbacks are actioned, and it will take a bit to work through the internal operation of the library.

To try and minimise the possibility that I was calling the library functions incorrectly I have modified a copy of the 'onGetShared,ino' example to create an RTU slave operating over RS485 using Serial1 on the ESP32.

The modified example is included below:

`/*
Modbus-Arduino Example - Publish multiple DI as coils (Modbus IP ESP8266/ESP32)

Original library
Copyright by André Sarmento Barbosa
http://github.com/andresarmento/modbus-arduino

Current version
(c)2018 Alexander Emelianov (a.m.emelianov@gmail.com)
https://github.com/emelianov/modbus-esp8266
*/

//#ifdef ESP8266
// #include <ESP8266WiFi.h>
//#else //ESP32
// #include <WiFi.h>
//#endif
//#include <ModbusIP_ESP8266.h>
#include <ModbusRTU.h>

//Used Pins
//#ifdef ESP8266
// uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8};
//#else //ESP32
uint8_t pinList[] = {18, 19};
//#endif

#define RXPIN GPIO_NUM_16
#define TXPIN GPIO_NUM_17
#define REDEPIN GPIO_NUM_5
#define BAUDRATE 115200

#define LEN sizeof(pinList)/sizeof(uint8_t)
#define COIL_BASE 0

#define SLAVE_ID 1

//ModbusIP object
//ModbusIP mb;

// Using RTU 485
ModbusRTU mb;

// Callback function to read corresponding DI
uint16_t cbRead(TRegister* reg, uint16_t val) {
// Checking value of register address which callback is called on.
// See Modbus.h for TRegister and TAddress definition
if(reg->address.address < COIL_BASE)
return 0;
uint8_t offset = reg->address.address - COIL_BASE;
if(offset >= LEN)
return 0;
return COIL_VAL(digitalRead(pinList[offset]));
}
// Callback function to write-protect DI
uint16_t cbWrite(TRegister* reg, uint16_t val) {
return reg->value;
}

// Callback function for client connect. Returns true to allow connection.
//bool cbConn(IPAddress ip) {
// Serial.println(ip);
// return true;
//}

void setup() {
Serial.begin(115200);

// Init RTU comms port
Serial1.begin(BAUDRATE, SERIAL_8N1, RXPIN, TXPIN);

// Attach Modbus
mb.begin(&Serial1);

// WiFi.begin("ssid", "password");

// while (WiFi.status() != WL_CONNECTED) {
// delay(500);
// Serial.print(".");
// }

// Serial.println("");
// Serial.println("WiFi connected");
// Serial.print("IP address: ");
// Serial.println(WiFi.localIP());
for (uint8_t i = 0; i < LEN; i++)
pinMode(pinList[i], INPUT);
// mb.onConnect(cbConn); // Add callback on connection event
mb.server(SLAVE_ID);
// mb.slave(SLAVE_ID);

mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils.
mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add single callback for multiple Coils. It will be called for each of these coils value get
mb.onSetCoil(COIL_BASE, cbWrite, LEN); // The same as above just for set value
}

void loop() {
//Call once inside loop() - all magic here
mb.task();
delay(10);
}`

As can be seen I've just commented out the WiFi, added the RTU capability (including setting up Serial1), and changed the Pin values slightly to match some of the ones I had connected.

I am using QModMaster connected via USB to RS485 adaptor to communicate with the slave, and its reading the two defined coils (discrete inputs) no problem - either singly (using start address of 0 or 1 - depending on coil), or as a pair (using start address of 0).

However if I set start address to 2 and do a single read I get 'Illegal Data Value' exception, where I would have expected to get 'Illegal Data Address'.

Is there something wrong with how I've got this set up, or is there something in the spec that states that this should be Illegal Data Value and not Address?

Another point of note is that if I request to read 2x coils starting at address 1, then I actually get two values provided - whereas I would have expected an Illegal Data Address exception again, as the 2nd coil in the read operation is outside of the defined range for the coils. This happens for multiple coils. Now the response received is just the value for the 'real' coil at address 1, but as it is a valid response QModMaster then interprets the values for the other coils from the blank bits in the byte. This continues to increase in line with the number of coils requested, with the library sending back multiple bytes - provided that at least one of the addressed coils is within range.

Unless I'm calling the library incorrectly, then I think that an Illegal Data Address exception should be raised in this scenario.
This happens for both Coils and Discrete Inputs (Ists). I haven't tried registers yet (as I didn't need them for my project).

I'm only just starting with Modbus so please let me know if I'm messing this up somehow!

However if I set start address to 2 and do a single read I get 'Illegal Data Value' exception, where I would have expected to get 'Illegal Data Address'.

It's reasonable note. Fixed in d79deb2.

Another point of note is that if I request to read 2x coils starting at address 1, then I actually get two values provided - whereas I would have expected an Illegal Data Address exception again, as the 2nd coil in the read operation is outside of the defined range for the coils. This happens for multiple coils. Now the response received is just the value for the 'real' coil at address 1, but as it is a valid response QModMaster then interprets the values for the other coils from the blank bits in the byte. This continues to increase in line with the number of coils requested, with the library sending back multiple bytes - provided that at least one of the addressed coils is within range.

Unless I'm calling the library incorrectly, then I think that an Illegal Data Address exception should be raised in this scenario.
This happens for both Coils and Discrete Inputs (Ists). I haven't tried registers yet (as I didn't need them for my project).

The library behaviour for this is controlled by

//#define MODBUS_STRICT_REG

in ModbusSettings.h. If define is commented the only first register existent is checked. Otherwise full range check is rerformed.

Ok thanks for the update. Away at the moment so will be a couple of weeks before I can check this, but will do ASAP. Will provide feedback then.

However I have a question regarding the second part of your response - 'The library behaviour for this is controlled by //#define MODBUS_STRICT_REG'

Shouldn't this be the default position, or is there a significant performance penalty?

However I have a question regarding the second part of your response - 'The library behaviour for this is controlled by //#define MODBUS_STRICT_REG'

Shouldn't this be the default position, or is there a significant performance penalty?

Default behaviour is left as it were in original library:

//Check Address
//Check only startreg. Is this correct?
//When I check all registers in range I got errors in ScadaBR
//I think that ScadaBR request more than one in the single request
//when you have more then one datapoint configured from same type.

Fair enough - I still think that it should be on by default, but can just set this myself now that I am aware of it.