Heltec WiFi LoRa 32V3 with RS485 Sensor

If you’re going to do this sort of thing, you should probably be using a logic level converter, not just a voltage divider but, as you noted earlier, it looks like your particular module is designed to take input from either 3.3V or 5V in any case.

There must be something else going on here. The only reason you’re seeing the “Time out” message is that there’s more than a second’s delay between the start time and the time of that particular test inside the readDO_DOS_TEM() function. Does that necessarily mean that there’s a communications problem?

I’m not actually sure that I understand what’s happening inside the readDO_DOS_TEM() function, but is there a difference in the way different processors handle reading input? That function seems to be checking whether or not there’s something to read, but not putting the read data anywhere. That may be logical if the function simply checked the content of some input buffer without resetting any pointers. Then a little later the readN() function seems to be going back and actually reading in the data. That sort of assumes that the buffer pointer is still at the beginning of the input data stream (or have I missed a ‘pointer reset’ somewhere?). If it’s not, if it’s where the initial read left it, there won’t be any input left to read will there?

I think I’d be paring that [readDO_DOS_TEM()] function right back to a simple write and read [directly into a buffer, printing what’s written and anything that’s received to the Serial Monitor], to see if we are actually communicating with the sensor.

Certainly in the test [ESP32] sketch referred to in my earlier comment, there’s no ‘pre-reading’, the data is read directly into an array for subsequent processing.

1 Like

I couldn’t yet look much deeper into everything but I looked a bit more into the readDO_DOS_TEM() function.

Added some comments to it so I understand roughly what it does.
void readDO_DOS_TEM(void) {
  uint32_t val = 0, val1 = 0, val2 = 0; // defines 32-bit Integer for 3 Values
  uint8_t Data[18] = { 0 }; // defines a Data array with 18 fields
  uint8_t ch = 0; // defines ch as 0 for generating the Data array
  bool flag = 1; // flag is True
  long timeStart = millis(); // actual time since code started
  long timeStart1 = 0; // second time used for checking if there's a delay
  while (flag) { // while True

    if ((millis() - timeStart1) > 100) { // 100 implies 0.1 seconds
      while (RS485.available() > 0) { // checks if the RS485 is available for communication // was mySerial, now RS485
        RS485.read(); // reads what is communicated via RS485 // was mySerial, now RS485
      }
      RS485.write(Com, 8); // RS485 writes to Com array // was mySerial, now RS485 // error due to Com being undefined, define before void setup()
      timeStart1 = millis(); // sets timeStart1 to the same value as millis() [time since the code execution was started]
    }

    if ((millis() - timeStart) > 1000) { // if there's a delay of more than 1 second
      Serial.println("Time out"); // print "Time out" to the serial monitor
      //return -1;
    }

    if (readN(&ch, 1) == 1) { // calls the readN function which actually seems to write something into the buffer
      if (ch == 0x01) { // checks if ch is equal to address 0x01
        Data[0] = ch; // if yes that gets put into the 1st entry of the Data array
        if (readN(&ch, 1) == 1) { // calls the readN function which actually seems to write something into the buffer
          if (ch == 0x03) { // checks if ch is equal to address 0x03
            Data[1] = ch; // if yes that gets put into the 2nd entry of the Data array
            if (readN(&ch, 1) == 1) { // calls the readN function which actually seems to write something into the buffer
              if (ch == 0x0C) { // checks if ch is equal to address 0x0C
                Data[2] = ch; // if yes that gets put into the 3rd entry of the Data array
                // it seems to me that starting from here, the code starts fetching the actual sensor data
                if (readN(&Data[3], 14) == 14) { // calls the readN function which actually seems to write something into the buffer
                  if (CRC16_2(Data, 15) == (Data[15] * 256 + Data[16])) { // calls the CRC16_2 function
                    val = Data[3]; // get value from 3rd field
                    val = (val << 8) | Data[4]; // bit-shift content of 4th field
                    val = (val << 8) | Data[5]; // bit-shift content of 5th field
                    val = (val << 8) | Data[6]; // bit-shift content of 6th field
                    float *dos = (float *)&val;
                    float Dos = *dos * 100.00;
                    Serial.print("DO Sat = "); // print Oxygen Saturation
                    Serial.print(Dos, 1);
                    Serial.print("%  ");
                    val1 = Data[7]; // get value from 8th field
                    val1 = (val1 << 8) | Data[8]; // bit-shift content of 9th field
                    val1 = (val1 << 8) | Data[9]; // bit-shift content of 10th field
                    val1 = (val1 << 8) | Data[10]; // bit-shift content of 11th field
                    float *Do = (float *)&val1;
                    Serial.print("DO = "); // print Dissolved Oxygen content in [mg/L]
                    Serial.print(*Do, 2);
                    Serial.print(" mg/L  ");
                    val2 = Data[11]; // get value from 12th field
                    val2 = (val2 << 8) | Data[12]; // bit-shift content of 13th field
                    val2 = (val2 << 8) | Data[13]; // bit-shift content of 14th field
                    val2 = (val2 << 8) | Data[14]; // bit-shift content of 15th field
                    float *tem = (float *)&val2;
                    Serial.print("TEM = "); // print Temperature in [°C]
                    Serial.print(*tem, 1);
                    Serial.println("°C");
                    flag = 0; // set flag to False
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

I have to admit, the other sensors I used from DFRobot so far usually just returned a voltage difference via analogRead() and then converted that into sensor values based on existing formulas, which makes this code more difficult for me to understand. Technically speaking, a voltage difference is returned here as well it’s just that the ModBus transmits it differently.

@nmcc You said you worked a lot with ModBus, do you have some resources/references which you can recommend for beginners?

A sensor with an RS485 interface takes a measurement and stores the result in a local register. The Modbus protocol is used to set or query and return the content of relevant registers—configuration settings, values read by the sensor etc. There’s a lot more going on than just returning a ‘voltage difference’. Here is a link to a tutorial on the Modbus RTU protocol.

For the time being though, are you able to just modify the code sample for one of the sensors discussed in the original link I sent—replace the sent data with your data string and set the appropriate baud rate—load, run it and note the result? That should tell you if you are communicating with the sensor.

Playing catchup from the weekend cooking for the family gathering on Mothers Day - Mary Berry Stilton & Leek Quiche (including pastry made from scratch), home baked Gammon, other bits and then Raspberry & Pistachio Friands. @bns will confirm I can cook as he & his wife survived a week here - if you get to the UK, come up North and you can try some.

Plus I have about 50,000 friends in the garden that make me proper honey.

And I can look at this thread in detail in the morning!

1 Like

OK peeps, this is what I see:

No level conversion is required - there’s the boost to 5V and the MOSFETs for level conversion

The Hackster.IO project is interesting, overly complex, flashy, clickbait and doesn’t help with a different baud rate for the ModBus RTU than the standard 9600. Also the OP added in a booster convertor when there is already one on the adapter board. If anything it will go in to my archive of links of how LLM’s get trained on BS :cow2::poop:

To meet the aspirations of Julia, we need to put the code back together because the changes between the two code bases aren’t in the spirit of Arduino which is very very naughty.

So, taking the original code supplied for the Arduino Uno and works on the Minima R4, we will now perform the magic Arduino transformation, thus proving Julia’s desire for a plug & play world are not totally in vein.

And also proving that most of the offerings provided on websites are great steaming piles of :cow2::poop:

#include <SoftwareSerial.h>
SoftwareSerial mySerial(2, 3);                                         //TX,RX

No DFRobot, it’s not Tx,Rx, it’s actually the other way round, dumbasses.

Julia, this uses software to act as a serial port, this library isn’t available for the ESP32 as it’s not necessary but rather than saying Potato, we are going to say Potarto to switch the serial over to:

//#include <SoftwareSerial.h>  // Note, commented out
HardwareSerial mySerial(2).    // Use UART #2 because you can assign any pins you like

Now we’ve redirected it, there is no chance we have to hack about with anything related to the serial in the main body of the code. Extra typing leads to potential mayhem as you demonstrated with the confusion over which serial port was which. But we do need to alter the setup of the serial port to tell the ESP32 which pins to use:

mySerial.begin(4800);

becomes

mySerial.begin(4800, SERIAL_8N1, 47, 48); // baud rate, bits, RX, TX

I’d personally bypass the whole Vext and put my red lead on to pin 2 which comes from the USB so will be 5V so will replicate the official DFRobot Uno wiring diagram.

Just to annoy us all the wiring has Rx going to Rx & Tx going to Tx on the adapter. What the Actual Ffffffff? Normally we spend out lives bending our brains about crossing the serial streams. But not today.

So make sure pin 47 is on the adapter R connection and pin 48 is on the adapter T connection.

Easy enough to swap them round for testing thou.

Yeah, we all know what ‘assume’ means - it means make an Ass of U & Me. The COM definitions are ModBus commands - they have no impact at all on any board or snail or postcard used to convey them. To abstract them out, they are are poured in to the serial pipe untouched, they never impact anything other than the sensor which takes it, does the command and sends stuff back.

Hopefully this helps in terms of why Arduino exists. We only have to change the comms hardware - everything else should fall in to place and no other code needs to be altered.

The DFRobot programmer needs to be directed to the nearest cave to live out the rest of his/her days - having Com, Com1 and Com2 is just obtuse, the first one should have been Com0. And a comment to explain they were ModBus commands would have been useful. Even worse, they then say Timeout for Com, Timeout2 for Com1 and Timeout3 for Com2. Arrrrrrggggggghhhhhhhh.

// error due to Com being undefined, define before void setup()

I suspect this came about because you were code shuffling, which is why just changing the pipe rather than using the CopyNPasta keys helps.

If this simplification fails, we can look at other pins - the other obvious free ones on the pinmap are 45 & 46 but they are used at boot time so things can get messy / break / not-work.

In some ideal world, you have an Adafruit SAMD based board as a different vendor/MCU to test against to help sanity check.

Enough for now, bizarre chilli with noodles night, head chef signing off.

Dear Julia,

I come to you with a heavy heart and much anguish in my soul.

I need my own RTD PT 100 temperature sensor add-on board so I designed it using a MAX31865 referring to the official data sheet guide and for sanity checking, looked at the Adafruit schematic.

Why the MAX31865 I hear you ask. Good question! It’s because I can get Adafruit, Sparkfun & clone boards to run alongside mine to check I got it right.

So I made one, it has tiny tiny pins so when it wasn’t reading the same as the Adafruit board with the same sensor, I made another one. And that did the very same thing. Not sure if consistent errors are good, but hmmmmm.

And then, by luck, I spotted that the Adafruit example sketch uses a value of 430Ω and lo, they have fitted a 430Ω resistor on their board. Or more accurately, they give you two resistors and you have to solder one of them on, which is no mean feat as it’s an 0805 SMD resistor aka 0.08" by 0.05" otherwise known as 2x1.2mm. But I soldered that on years ago so forgot all about it.

However, the Adafruit schematic has a 400Ω resistor with a note to use a 400Ω resistor. And the data sheet says:

A reference resistor equal to four times the RTD’s 0NC resistance is optimum for a platinum RTD. Therefore, a PT100 uses a 400I reference resistor, and a PT1000 uses a 4kI reference resistor.

So now I’ve changed the example to use 400 for the reference, all is working as expected.

I’m telling you this because it literally just happened and it hopefully conveys the hot mess of total time wasting that occurs for us that build hardware & write code, even after, erm, cough, 20+ years of building my own embedded hardware and 35+ years of being paid to code.

So it’s not you. It is the collective universe and the rush to market.

Best wishes

Nick

Hi Nick,

thank you for taking the time to look at the code and offer help!

It works perfectly now, thank you so much!

Would it be okay for you if I added the code to my GitHub and made it public? If you have a GitHub account I’d tag you and DFRobot, so others could also find the working code easily.

Code now looks like this:
//#include <SoftwareSerial.h> // library is not available for ESP32, uses software to act as a serial port
//SoftwareSerial mySerial(2, 3); // this is RX, TX (remember for Arduino code)
HardwareSerial mySerial(2); // using UART #2 so that any pin can be assigned

// these are ModBus commands, not impacted by the boards being used
uint8_t Com[8] = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x06, 0xC5, 0xC8 };   
uint8_t Com1[8] = { 0x01, 0x03, 0x10, 0x22, 0x00, 0x01, 0x20, 0xC0 };  
uint8_t Com2[8] = { 0x01, 0x03, 0x10, 0x20, 0x00, 0x01, 0x81, 0x00 };  
float sal, ap;

void setup() {
  
  // Power supply
  pinMode(Vext, OUTPUT);
  digitalWrite(Vext, LOW);
  
  Serial.begin(9600);
  mySerial.begin(4800, SERIAL_8N1, 47, 48); // baud rate, bits, RX, TX
}
void loop() {
  readDO_DOS_TEM();
  Atmospheric_pressure();
  Serial.print("AP = ");
  Serial.print(ap, 2);
  Serial.print("Kpa  ");
  Salinity();
  Serial.print("Salinityt = ");
  Serial.print(sal,0);
  Serial.println("%  ");
  Serial.println("   ");
  delay(1000);
}

void readDO_DOS_TEM(void) {
  uint32_t val = 0, val1 = 0, val2 = 0;
  uint8_t Data[18] = { 0 };
  uint8_t ch = 0;
  bool flag = 1;
  long timeStart = millis();
  long timeStart1 = 0;
  while (flag) {

    if ((millis() - timeStart1) > 100) {
      while (mySerial.available() > 0) {
        mySerial.read();
      }
      mySerial.write(Com, 8);
      timeStart1 = millis();
    }

    if ((millis() - timeStart) > 1000) {
      Serial.println("COM Time out"); // more sensible error description
      //return -1; // remove these as they produce errors (a void function cannot have a return value!)
    }

    if (readN(&ch, 1) == 1) {
      if (ch == 0x01) {
        Data[0] = ch;
        if (readN(&ch, 1) == 1) {
          if (ch == 0x03) {
            Data[1] = ch;
            if (readN(&ch, 1) == 1) {
              if (ch == 0x0C) {
                Data[2] = ch;
                if (readN(&Data[3], 14) == 14) {
                  if (CRC16_2(Data, 15) == (Data[15] * 256 + Data[16])) {
                    val = Data[3];
                    val = (val << 8) | Data[4];
                    val = (val << 8) | Data[5];
                    val = (val << 8) | Data[6];
                    float *dos = (float *)&val;
                    float Dos = *dos * 100.00;
                    Serial.print("DO Sat = ");
                    Serial.print(Dos, 1);
                    Serial.print("%  ");
                    val1 = Data[7];
                    val1 = (val1 << 8) | Data[8];
                    val1 = (val1 << 8) | Data[9];
                    val1 = (val1 << 8) | Data[10];
                    float *Do = (float *)&val1;
                    Serial.print("DO = ");
                    Serial.print(*Do, 2);
                    Serial.print(" mg/L  ");
                    val2 = Data[11];
                    val2 = (val2 << 8) | Data[12];
                    val2 = (val2 << 8) | Data[13];
                    val2 = (val2 << 8) | Data[14];
                    float *tem = (float *)&val2;
                    Serial.print("TEM = ");
                    Serial.print(*tem, 1);
                    Serial.println("°C");
                    flag = 0;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

void Atmospheric_pressure(void) {
  uint8_t Data[12] = { 0 };
  uint8_t ch = 0;
  bool flag = 1;
  long timeStart = millis();
  long timeStart1 = 0;
  while (flag) {

    if ((millis() - timeStart1) > 100) {
      while (mySerial.available() > 0) {
        mySerial.read();
      }
      mySerial.write(Com1, 8);
      timeStart1 = millis();
    }

    if ((millis() - timeStart) > 1000) {
      Serial.println("COM1 Time out"); // more sensible error description
      //return -1; // remove these as they produce errors (a void function cannot have a return value!)
    }

    if (readN(&ch, 1) == 1) {
      if (ch == 0x01) {
        Data[0] = ch;
        if (readN(&ch, 1) == 1) {
          if (ch == 0x03) {
            Data[1] = ch;
            if (readN(&ch, 1) == 1) {
              if (ch == 0x02) {
                Data[2] = ch;
                if (readN(&Data[3], 4) == 4) {
                  if (CRC16_2(Data, 5) == (Data[5] * 256 + Data[6])) {
                    ap = (Data[3] * 256 + Data[4]) / 100.0;
                    flag = 0;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

void Salinity(void) {
  uint8_t Data[12] = { 0 };
  uint8_t ch = 0;
  bool flag = 1;
  long timeStart = millis();
  long timeStart1 = 0;
  while (flag) {

    if ((millis() - timeStart1) > 100) {
      while (mySerial.available() > 0) {
        mySerial.read();
      }
      mySerial.write(Com2, 8);
      timeStart1 = millis();
    }

    if ((millis() - timeStart) > 1000) {
      Serial.println("COM2 Time out"); // more sensible error description
      //return -1; // remove these as they produce errors (a void function cannot have a return value!)
    }

    if (readN(&ch, 1) == 1) {
      if (ch == 0x01) {
        Data[0] = ch;
        if (readN(&ch, 1) == 1) {
          if (ch == 0x03) {
            Data[1] = ch;
            if (readN(&ch, 1) == 1) {
              if (ch == 0x02) {
                Data[2] = ch;
                if (readN(&Data[3], 4) == 4) {
                  if (CRC16_2(Data, 5) == (Data[5] * 256 + Data[6])) {
                    sal = Data[3] * 256 + Data[4];
                    flag = 0;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

uint8_t readN(uint8_t *buf, size_t len) {
  size_t offset = 0, left = len;
  int16_t Tineout = 500;
  uint8_t *buffer = buf;
  long curr = millis();
  while (left) {
    if (mySerial.available()) {
      buffer[offset] = mySerial.read();
      offset++;
      left--;
    }
    if (millis() - curr > Tineout) {
      break;
    }
  }
  return offset;
}

unsigned int CRC16_2(unsigned char *buf, int len) {
  unsigned int crc = 0xFFFF;
  for (int pos = 0; pos < len; pos++) {
    crc ^= (unsigned int)buf[pos];
    for (int i = 8; i != 0; i--) {
      if ((crc & 0x0001) != 0) {
        crc >>= 1;
        crc ^= 0xA001;
      } else {
        crc >>= 1;
      }
    }
  }

  crc = ((crc & 0x00ff) << 8) | ((crc & 0xff00) >> 8);
  return crc;
}

(might fix some of their spelling errors beforehand though and improve comments)

Oh man, that resistor would have made me so mad, I would have needed to go on a walk just to cool down tbh :sweat_smile:

Good to hear you’ve got it sorted.

Publish away for the benefit of all - the concept of just swapping the serial plumbing is very much the way of Arduino which has been rather lost in the many layers of excitement that has built up over the years.

Tagging ‘HeadBoffin’ would be cool but not essential.

The best way for people to find it would be to put a comment in at the top of the code with the sensor name & URL. And for sure, drop that random T at the end of Salinity and anything else you think is appropriate.

1 Like

It was aggravating but is pretty typical of the current state of play. Frankly I’m surprised if something I implement from a third-party works first time. At least if it’s my own code it’s my own fault, but if you publish something, gee whiz, it should do the job.

So, today was taking the ESP32 WiFi IP address assigned and sending it to a web server so users can access the on-board web server without having to jump through hoops to find out what its IP address.

On device:

  • Get IP address, easy - however it is little endian which looks like it is in reverse.
  • Turn in to hex to make it a little more compact for the web submission - and somewhat readable for debugging.

On server:

  • Take the hex string, convert to an integer, then split it up in to four bytes to look like a normal IP address.
  • Try again using new discovered PHP functions to do this sort of thing - fail.
  • Spot that the Arduino library is returning the address little endian - aka reversed.
  • Do a web search, use an answer on Stack Overflow, doesn’t work, some issues with how the PHP interpreter is compiled.
  • Write code to turn cut the hex sent up in to four and rebuild as string.

Just a small part of the thing and probably only 20 minutes, but still.

Thank you, will do that! I’ll let you know once I have it up and add a link to the repo at the top of this thread so anyone can access things quickly.

EDIT: And here we are now, the repository for the code is ready.

20 minutes for you are several hours for others though :sweat_smile:

Today I (temporarily) gave up on setting up a LoRaWAN gateway after my father said “instead of getting even angrier, why don’t you just write to the support again?”. Imagine trying to set up something where the web GUI resets everything when you click “save” or the GUI crashes when you set a WiFi connection (which it apparently doesn’t do when you do the exact same thing on the over 115€ more expensive cellular version… [capitalism™ :roll_eyes:]) so you need to reboot the whole thing to factory settings… and this is just getting that thing connected to the internet! Which, you know, should be the easiest step imho. But well, support will earliest answer on Monday so I’ll try to find a proper tutorial in the meantime :sweat_smile:

Which gateway, what firmware version?

It’s a Milesight UG56-868M, firmware version 56.0.0.8-r3.