RS-485 modbus library


#101

@peergum you’re a genius man. The solutions so intuitive too. I was too focused on the library. Thanks alot!


#102

If that works, I’ll probably add the option to pass parity (e.g. setParity method).


#103

Yep, your solution worked great. :smile:
I also noticed that you added the set slave ID functionality. I was thinking about daisy chaining a few of these devices together but I am confused about how slave IDs work for multiple devices. I’ve only been working with one device at a time so that is easy to address when calling the constructor. However, having multiple slave devices seems more complicated. How do I set which device to have which slaveID? After that’s established, I am guessing I can easily call your setSlave function to switch slaves. One last thing is, I’m not sure how you would wire these devices up either, since all the connections are just A and B.


#104

you have to configure the IDs on the devices themselves. Then you code will address each device by using corresponding ID. There’s no automatic configuration/discovery, although you can scan for devices using successive IDs, if you know a common register they use (e.g. serial number), so you have to know in advance what IDs are used on your bus.

As far as chaining them, you should check some modbus doc online, but note you will have to use terminations on each end of your chain. Note that having more than one device on the bus is generally a source of noise and require more care. Also any device you connect to the bus as to use short wires. In other words, do short "T"s, not long "Y"s :wink:


#105

I updated the library on github and particle, both with a speed improvement and an optional additional parity parameter to the setSpeed() method.

I also added a simple example on how to use the lib.


#106

Thanks for all the help! I think I’ll have to scan through Slave ID’s and see if that works for me. However, forgive me if I’m wrong, wouldn’t putting two identical devices on the same modbus line have conflicting slave ID’s (since they come pre configured with it).

In terms of your library, is there a reason why you went with the custom pre/post transmission functionality for 485 chips as opposed to the other Particle IDE library (found at https://github.com/lithiumhead/ModbusMaster/blob/master/src/ModbusMaster.cpp) which simply has an enableTXPin function which handles the pre/post transmission stuff. Sorry for all the questions.


#107

You’ll have to connect them one by one and configure their ID with a program, unless they have a panel to configure them.

As mentioned earlier, I wanted to be able to act on the TX and ^RX pins of the MAX separately, so I could lower both simultaneously and put the MAX in sleep mode, which you can’t do if you wire them together. I use the pre- and post- transmission function to act on the two pins. If saving battery is not a string requirement for you you could use the other library I guess.


#108

I like your library because I would like the ability to set the slave ID which your library offers. The only issue I’m having right now is receiving messages back, they’re all timing out (error E2). When I write to a single register, it seems to work as the Slave will do the command but the return value will always be a timeout. Any suggestions on why this may be the case?

The only thing my pre and post transmission functions do is digitalWrite max485 enable pin high and low respectively. Is this incorrect?


#109

I suggest you enable debugging and monitor on serial usb. That should give you some clues. Also add something like delay(10) in your idle function. It will wait 10ms before checking received bytes. If that works you can always tweak later to a shorter delay. If you want to try longer timeouts change the value in the modbusmaster.h file.


#110

Thank you for your suggestions and I tried them all but did not work. However, after looking at the differences between the library I used before and your one, I noticed that previously the pin setup was done for me (as in pinMode) and all I had to do to fix the issue was pinMode(enablepin, OUTPUT). How silly of me and sorry for the bother. Again, thanks for the library, very useful :smiley:


#111

@DriftingShadows, @peergum,
Thanks for the discussion and library. I am sure it will help me getting modbus working on my project.
Do you have any example sketches showing good practices using this library to respond to commands, or to copy registers to structures/variables and vice versa, etc?

My slave device has EEPROM registers defined for some config variables and I need to be able to update them by Modbus as well as through a web app. I also have a couple of sensor readings that I want to make available on modbus.

FWIW, I am using serial4 (pins C2, C3) rather than serial1, with DE,RE tied together to C4.


#112

peergum has an example sketch in his library. Have you checked that out?


#113

@peergum thanks for the port!

I have some questions on how to use the library correctly. :stuck_out_tongue:

Doing this:

uint8_t result = slave.readHoldingRegisters(3914,4);

    if (!result) {
        uint16_t value = slave.getResponseBuffer(0);
        Log.info("Received: %0x",value);
    } else {
        Log.warn("Read error");
    }

and the serial info is:

0000346294 [app] TRACE: RX:
0000346304 [app] TRACE: - 1
0000346304 [app] TRACE: - 3
0000346304 [app] TRACE: - 8
0000346304 [app] TRACE: - 2c
0000346304 [app] TRACE: - 81
0000346305 [app] TRACE: - 42
0000346305 [app] TRACE: - 48
0000346305 [app] TRACE: - 0
0000346305 [app] TRACE: - 0
0000346305 [app] TRACE: - 0
0000346306 [app] TRACE: - 0
0000346306 [app] TRACE: - e9
0000346306 [app] TRACE: - be
0000346306 [app] INFO: Status: 0
0000346306 [app] INFO: Received: 2c81

Which is cool cos i need the 0x42482C81 bytes and they are available!

Just wondering how I can extract the float32 value instead of just 2c81?


#114

My initial suggestion, until someone smarter replies:
If it is just the formatting, instead of
Log.info(“Received: %0x”,value);
use
Log.info(“Received: %f”,value); // for float

If you need the full 32 bits, replace the
uint16_t value = …
with
float value = …


#115

Yes, I am looked at the usage.ino. It provides an example of establishing a serial connection and includes a line
uint8_t result = slave.readHoldingRegisters(8000,1);
However, what I am wondering is how people set up registers in the first place (coil, 32bit, etc.) for use in their code.
It is just a matter of having a handling function for the buffer that parses the command and payload for each possibility? Is the payload only 8 bits?


#116

@kennethlimcp, I’ve just quickly popped into this thread and haven’t looked through it completely and really don’t have time right now. However, I created my own Modbus TCP library a couple of years ago to interface with a water quality device from YSI. In the process, I had to deal with the fact that the YSI Modbus controller’s registers held floating point numbers. As it turns out, they’re Little Endian instead of Big Endian (or vice versa). Whichever the case, they’re the opposite of what Particle uses.

To address the issue, I created the following union:
union {
float f;
char b[4];
} pResponse; //primary response
union {
float f;
char b[4];
} sResponse; //secondary response

And then, load the union in reverse order like this (sorry, it’s just a hack from my program–the quickest way for me to show what I did. I hope it helps.). It assumes a read request was made to the device and the resulting response is in inputBuffer):

void readYSI () {
  //This function reads the sensor's primary and secondary responses and places them in pResponse and sResponse
  Serial.println("readYSI called...");
  pResponse.f = -100; // initialize to something obvious
  sResponse.f = -100; // initialize to something obvious
  Serial.println("Connecting to ysi…");
  if (ysi.connect(analyzer,port)) {
    Serial.println("Connected!!!");
    ysi.write(readSensor,12);
    readStart = millis();//grab the current time in milliseconds
    while (!ysi.available() && (millis()-readStart<1000) ) {
      //we'll wait until data is available or 1 second--whichever comes first
    }
    if (ysi.available()) {
      gYSIstate = "online";
      //read the data into our input buffer starting with index of 1 (not 0)
      Serial.println("We have data!");
      int i = 1;
      while (ysi.available()) {
        inputBuffer[i]= ysi.read();
        if (logging){
          Serial.print(i);
          Serial.print(":");
          Serial.println(inputBuffer[i],HEX);
        }
        i++;
      }
      Serial.print("we read in this many bytes: ");
      Serial.println(i-1);
      Serial.println("we need 201 bytes for a complete response");
      if (i==202){
        processYSIdata();
        grovestreamsPUT();
      }
      else {
        gYSIstate = "offline1";
        Serial.println("The correct number of bytes were not received");
      }
    }
    else {
      gYSIstate = "offline2";
      Serial.println("No data from YSI this time");
    }
    Serial.println();
    Serial.println("disconnecting.");
    ysi.stop();
  }
  else {
    //We were not able to connect to the YSI device
    gYSIstate = "offline3";
    Serial.println("Connection failed.  Damn it!");
  }
}

void processYSIdata() {
//we process the data from the YSI analyzer
//Sensors are stored in the YSI in the following format 2, 1, 3, 4, 6, 5, 8, 9, 7, 11, 10, 12
char c;
  printPreamble();
  for (int sensor = 1; sensor<13; sensor++) { //sensors 1 to 12
    Serial.print("Sensor: ");
    Serial.println(sensor);
    for (int registerByte = 1; registerByte<17;registerByte++){ // registers 1 to 8 and there are 2 bytes per register
      c = inputBuffer[dataStartByte+((sensor-1)*16)+(registerByte-1)];
      if (logging) {
        Serial.print(registerByte);
        Serial.print(":");
        Serial.println(c,HEX);
      }
      if (registerByte==9)  {pResponse.b[3] = c;}
      if (registerByte==10) {pResponse.b[2] = c;}
      if (registerByte==11) {pResponse.b[1] = c;}
      if (registerByte==12) {pResponse.b[0] = c;}
      if (registerByte==13) {sResponse.b[3] = c;}
      if (registerByte==14) {sResponse.b[2] = c;}
      if (registerByte==15) {sResponse.b[1] = c;}
      if (registerByte==16) {sResponse.b[0] = c;}
    }
    primaryResponses[sensor-1] = pResponse.f;
    Serial.print("pResponse.b[0]: ");
    Serial.println(pResponse.f);
    secondaryResponses[sensor-1] = sResponse.f;
    Serial.print("sResponse.b[0]: ");
    Serial.println(sResponse.f);
  }
}

void printPreamble() {
  //Print the TCP preamble along with the first part of the Modbus response
  if (logging) {
    for (int i = 1;i<6;i++){
      Serial.print("Byte ");
      Serial.print(i);
      Serial.print("=");
      Serial.println(inputBuffer[1],HEX);
    }
  }
  Serial.print("TCP message length: ");
  Serial.println(inputBuffer[6],HEX);
  Serial.print("Modbus address: ");
  Serial.println(inputBuffer[7],HEX);
  Serial.print("Function performed: ");
  Serial.println(inputBuffer[8],HEX);
  Serial.print("Number of bytes returned by YSI: ");
  Serial.println(inputBuffer[9],HEX);
  Serial.println("==============");
}

#117

Hi Kenneth,

I was about to suggest the way @ctmorrison did, using unions.

I had found the same method in someone else’s code (possibly here in the community), so I have some converters at the end of my derived classes from the modbus one(one per type of sensor, since they have different abilities and coding standards, e.g. for ascii storage).

The unions are defined in the beginning of the modbus header.

Here are some of the helpers I used in one sensor class (Nuflo sensors):

float Nuflo::_convertHEXtoFLOAT(uint16_t* data){
    FloatData floatData;

    for(int i=0; i<2; i++) {
        floatData.u[1-i] = data[i];
    }

    return floatData.f;
}

void Nuflo::_convertFLOATtoHEX(uint16_t *buffer, float value) {
    FloatData floatData;
    floatData.f = value;
    buffer[0] = floatData.u[1];
    buffer[1] = floatData.u[0];
}

int Nuflo::_convertHEXtoINT(uint16_t* data){
    IntData intData;

    intData.u[0] = data[1];
    intData.u[1] = data[0];

    return intData.i;
}

String Nuflo::_convertHEXtoSTRING(uint16_t* data, uint8_t length){
    String s = String("");
    char c;

    // read each byte, end of string if 0
    for(int i=0; i<length; i++) {
        c = highByte(data[i]);
        if (!c || c==0xFF) {
            break;
        }
        s.concat(c);
        c = lowByte(data[i]);
        if (!c || c==0xFF) {
            break;
        }
        s.concat(c);
    }
    s.trim();

    return s;
}

void Nuflo::_convertSTRINGtoHEX(uint16_t* data, String text, uint8_t length){
    char c;

    for(uint i=0; i<length; i++) {
        c = i<text.length() ? text.charAt(i) : 0xFF;
        data[i/2] = c << 8;
        i++;
        c = i<text.length() ? text.charAt(i) : 0xFF;
        data[i/2] += c;
    }
}

/*
 * This converts DATE+TIME as 2 consecutive FP32 to time_t
 */
time_t Nuflo::_convertHEXtoTIME(uint16_t* data, bool dateFirst = DATE_FIRST){
    unsigned int date, hours;
    tm instant;

    if (dateFirst == DATE_FIRST) {
        date = truncf(_convertHEXtoFLOAT(data));
        hours = truncf(_convertHEXtoFLOAT(data+2));
    } else {
        hours = truncf(_convertHEXtoFLOAT(data));
        date = truncf(_convertHEXtoFLOAT(data+2));
    }

    instant.tm_mon = (int)(date/10000-1);
    instant.tm_mday = (int)((date/100) % 100);
    instant.tm_year = (int)(date % 100)+100;
    instant.tm_hour = (int)(hours/10000);
    instant.tm_min = (int)((hours/100) % 100);
    instant.tm_sec = (int)(hours % 100);

    return mktime(&instant);
}

Hope that helps.


#118

I’m not totally clear about what you’re trying to achieve here, and what the problem is.

If you need to setup your device (provided that you have the proper comm configuration), you can just use the writeHolidingRegister, writeMultipleRegisters, or writeCoilRegister functions, who will deal with sending the command, the payload and deal with CRC and number of bytes.

In other words: put your bytes in a buffer, call the corresponding write function, and you’re done…


#119

Just a very short video of what can be done with a little electron and a display :wink:
The device is doing much more than that, obviously, this is just a quick peek…

Note the RGB LED’s mirroring on the display…

Cheers
Phil.


#120

@peergum, this video is unavailable (at least for me)