RS485 Modbus RTU library

Hi all,

A month ago, I designed my project using the RAK4631 and shared it on the forum. I’m now trying to communicate between my sensor and the RAK4631 using the RS485 Modbus RTU protocol.

Official Arduino library, ArduinoModbus, is not compiling. This issue has already been reported here. I tried the recommended solution: using the Modbus-esp8286 library. I was able to compile it, but I didn’t fully understand how to communicate with the sensor and failed to get a write response (error 0xE4). Here is my adapted code from example.

#include <ModbusRTU.h>

ModbusRTU mb;  // Modbus RTU object

// Modbus response
bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) {
  Serial.print("Request result: 0x");
  Serial.print(event, HEX);
  Serial.println();
  return true;
}

void setup() {
  Serial.begin(115200);
  Serial1.begin(9600, SERIAL_8N1);
  mb.begin(&Serial1);               
  mb.setBaudrate(9600);           
  mb.master();                    
}

bool coils[20]; 

void loop() {
  uint16_t offset = 0;   // first coil
  uint16_t numregs = 1;      // coils to read
  uint8_t unit = 1;          // Modbus slave 1

  if (!mb.slave()) {
    // send request
    mb.readCoil(1, offset, coils, numregs, cbWrite, unit);
  }

  mb.task();  // execute Modbus task
  yield();    
  Serial.println(coils);
}

I also found a more recent post suggesting using Modbus-Master-Slave-for-Arduino along with the adapted RUI3_ModbusRtu library. However, this doesn’t compile (dozens of errors such as: RAK11310-Modbus\RUI3_ModbusRtu.cpp:121:11: error: 'Serial' was not declared in this scope 121 | port = &Serial; and RAK11310-Modbus\RUI3_ModbusRtu.cpp: In member function 'void Modbus::start()':).

Which library and solution would you recommend?

I’m not very familiar with Modbus communication… I usually manage to read values from my sensor by sending the correct query (from the datasheet) and then parsing the bits containing my data from the response. I was able to get the correct response from the sensor using a USB-RS485 converter and serial communication. Here is my query:

Standard Modbus-RTU protocol, baud rate: 9600; check bit: none; data bits: 8; stop bit: 1

Request

Address Func Code Dev Ad High Dev Ad Low Register Low Register High CRC16 CRC16
0x01 0x03 0x00 0x00 0x00 0x01 low high

Send request: 01 03 00 00 00 01 84 0A

Response

Address Func Code Data Length Register 0 Data High Register 0 Data Low CRC16 CRC16
0x01 0x03 0x02 0x1A 0x0A low high

Data: 1A 0A (hex) = 6666 / 1 decimal
Reading value: 666.6 NTU

Can someone help me get this communication working?

Thank you !

I’m using :

  • ArduinoIDE 2.3.4
  • bootloader RUI3
  • Board manager RAKwireless RUI nRF Boards 4.1.0
  • mobdus-esp8266 4.1.0
  • ArduinoRS485 1.0.2

For RUI3 I have written an example using RS485 Modbus Modbus with a customized Modbus library (not published in the Arduino Library Manager).
I am using this with my RUI3-RS485-Soil-Sensor and RUI3-RS485-Wind-Sensor

Thank you, Bernd, for sharing your customized library. It seems to be exactly what I was looking for.
However, I wasn’t able to compile it:

RUI3-RAK5802-Modbus-Master\custom_at.cpp: In function 'int status_handler(SERIAL_PORT, char*, stParam*)' : RUI3-RAK5802-Modbus-Master\custom_at.cpp:205:41: error: 'class RAKLoraP2P' has no member named 'pfreq'

I tried adding your WisBlock-API-V2 library, which solves the RAKLoraP2P error, but another one related to bluefruit.h appears. It seems that WisBlock-API is not compatible with the RUI bootloader.

For now, I don’t need any LoRa communication. I just want to use the modbus_read_register() function and store data on the RAK15001 Flash Module. I may also use an external microSD logger, like the DF Robot Fermion.

I am using it with Arduino/PlatformIO as well, but there might be some changes.
Check out this example Hydroponic-Sensor

RUI3_ModbusRtu.cpp and RUI3_ModbusRtu.h worked there.
Usage is in RAK5802-RS485.cpp

Thanks again for sharing this great project.
I managed to compile and upload it using PlatformIO and read the correct values from my sensor !
The Arduino IDE didn’t work, though. However, I had to go back to the RAK4631 BSP bootloader instead of RUI3. I don’t really understand why, since I thought the library was developed for RUI3…?

I wasn’t able to set the wake-up timer. I tried AT+SENDINT=60 or AT+SENDINT=60000 (for 1 minute), but it stays locked on 5 seconds. Is there a way to change this in the code? I couldn’t find the variable…

I will try to build my project based on your “hydroponic sensor node.” However, your coding style is unfamiliar to me (I started with Arduino and the setup()/loop() structure…). Do you have any resources that can help?

Finally, do you think it’s possible to write data to the RAK15001 Flash Module and then access it (copy/paste to a laptop would be the dream solution) using TinyUSB?

The background on the RS485/Modbus code comes from a requirement to have something for RUI3 that is small enough to work on a RAK3172 (STM32WLE5 with little flash).
I found the code, did some changes to have it working on RUI3.
Then I used it in Arduino/PIO projects as well, just kept the name.

The send interval is not hard-coded, it is set with ATC+SENDINT=xx where xx is the time in seconds. This is part of the WisBlock-API framework I used for this application. I use this in nearly all of my Arduino/PIO projects and it works always for me.
When you send ATC+SENDINT=60, what is the response the device is sending back.
Please use the ATC instead of AT when you send the command.

For the code style, my attempt with the WisBlock-API is to have an event driven application instead of using the loop() which is most of the time not power efficient. setup() and loop() are inside the WisBlock-API, but used in a way that guarantees lowest power consumption. The MCU sleeps unless there is an event, like a timer, an external interrupt or a LoRa event.
You can find a little more explanation in the README of the WisBlock API.

For the flash module, I never used it, I usually use our FRAM modules, which have much higher write cycles (but are as well much more expensive).
I don’t know a method to use TinyUSB (or other USB libraries) to mount it as a flash drive.

I did write some code to read data over USB from an SD-Card, but it is quite slow. It uses an AT command to start sending the data from the SD-Card to the USB port. Then on the PC side, I have an application that receives the data and saves it into files.
It is quite complicated and it is really slow. Usually I just get the SD-Card out of the module and plug it directly into the PC.

Many thanks, Bernd, for your complete answer!

The WisBlock API documentation was exactly what I needed. I have already written an event-based code for the ESP32, but I still need to get more familiar with the entire RAK ecosystem.

I managed to send AT commands via BLE Serial (it doesn’t works over USB) and change the SENDINT (both AT and ATC cmd seem to work). However, my device does not wake up. It seems to get stuck somewhere because the serial communication doesn’t finish:

  • It reads the registers from the Modbus sensor 5 times.
  • It doesn’t compute the final value.
  • I added MYLOG("MODR", "Turb %.2f", batt_level_f / 10); after read_rak5802() and read_batt() in main.cpp, but it doesn’t print to the serial output.
13:40:39:569 -> [APP] Setup application
13:40:39:701 -> [APP] Initialize application
13:40:40:202 -> [USR_AT] Added 2 User AT commands
13:40:40:202 -> [USR_AT] Valid Hydro settings found, level = 1000
13:40:40:204 -> [USR_AT] Valid Hydro settings found, cal factor = 100
13:40:40:204 -> [MODR] Send read request 1 over ModBus
13:40:40:244 -> [MODR] pH 0000 0.00
13:40:40:244 -> [MODR] No data received
13:40:42:279 -> [MODR] Send read request 2 over ModBus
13:40:42:279 -> [MODR] pH 0260 60.80
13:40:42:279 -> [MODR] pH 60.80
13:40:44:321 -> [MODR] Send read request 3 over ModBus
13:40:44:321 -> [MODR] pH 05DD 150.10
13:40:44:321 -> [MODR] pH 150.10
13:40:46:364 -> [MODR] Send read request 4 over ModBus
13:40:46:364 -> [MODR] pH 05F7 152.70
13:40:46:364 -> [MODR] pH 152.70
13:40:48:405 -> [MODR] Send read request 5 over ModBus
13:40:48:405 -> [MODR] pH 05F7 152.70
13:40:48:405 -> [MODR] pH 152.70
|

It might be related to the LoRa communication. I don’t have a network available to join at the office, and my current project will not be connected for now. Is there a way to properly disable LoRa (WAN and P2P) functionality? Otherwise, I just disable the LoRa-related parts in main.cpp:

  • if (g_lorawan_settings.lorawan_enable){...}
  • void lora_data_handler(void){...}

Ok, maybe I forgot the TinyUSB flash device solution. But I assume I can print my logged data (about 5000 rows: datetime, value, bat_lvl) to the serial terminal using an AT command (related to api_ble_printf())?
Using an SD card would be an easy solution, but the SPI bus is already occupied by the RS485 module. The only solution I found is to use an external UART SD data logger (DF Robot Fermion), but I would much prefer to only use RAK components.

I hope you can help me again. These RAK devices, combined with your Git repositories, are great for environmental monitoring and DIY projects, low-cost and versatile. If I manage to meet my needs, they will definitely be my first choice for future applications!

AT commands should work the same over USB. Make sure your commands are ending with

<CR><LF>

For the “no connection” problem, switch to LoRa P2P with AT+NWM=0 and it will send always.

It works with CRLF, thanks!
AT+NWM=0 also solved my issue; the RAK6431 goes into sleep mode and wakes up with the timer in LoRaP2P mode.
I will try to write custom AT commands to read and manage data in the flash memory.
I’ll open a new topic if I have any questions or issues with my future development.

Greetings.

PS. I also managed to power my sensor with RAK 19002 12V module (SLOT A), however polarity is inverted on the datasheet !