@markd833
Well, I actually did not try your code because I saw that my own code was working as expected when using WinModbus to simulate a working sensor.
Today I got the Sensor Monitoring Software from the customer support of JXCT and as it turns out both of the sensors do not respond to it. So my guess is that there are some Scammers selling broken Sensors on Amazon. Or I had really bad luck to coincidentally buy two different sensors from two different sellers and both are broken.
But again, the user manuals even provided erroneous CRC codes. And looking at the manual again I see another funny detail: the "unblocked" word. Did they just use Tipex to overwrite the company name of a legit manufacturer? 
Well, I'm done with this project for now. I will send both sensors back to Amazon and hope to get a refund.
Thanks a lot for your dedication and help @markd833 ... the idea to simulate a working sensor with WinModbus was really helpful to understand that there was nothing wrong with my code. (In terms of functionality, not clean code
)
That said, maybe somebody finds my code helpful to track down sensor problems. It should be working with all kinds of soil sensors. (NPK-Sensor, Nitrogen Sensor, Phosphorus Sensor, Potassium Sensor, EC Sensor, PH Sensor, Moisture Sensor, Temperature Sensor).
The usage is simple. Just type a number (or several) from 0 to 7 into the serial Monitor and you get some information about what is getting sent and what is getting received.
Alternatively there's a standalone Mode which sends a specific frame every 5 seconds.
#include <ArduinoRS485.h>
#include <SoftwareSerial.h>
const int PH = 0;
const int MOISTURE = 1;
const int TEMP = 2;
const int EC = 3;
const int NITROGEN = 4;
const int PHOSPHORUS = 5;
const int POTASSIUM = 6;
const int NPK = 7;
const int ledPin = 13; // Built-in LED
const int DI = 4; // pins to connect DI, DE, RE and RO of the MAX485 module
const int DE = 5;
const int RE = 6;
const int RO = 7;
const boolean standaloneMode = false; // standaloneMode does send an inquiry frame every 4.8 seconds
const int standaloneFrame = NITROGEN; // defines which frame is getting sent in standalone mode
const byte sensorID = 0x01;
const byte function = 0x03; // 0x03 means reading from register. Other functions are not needed to read the sensor
byte inquiry_frame[8]; //global variable used by different functions. I did not take the time to figgure out how arrays are passed to functions properly in C language.
const byte inquiry_frames[10][8] = {
//CRC according to manual. Will get overwritten later in the code to fix wrong CRC numbers
{sensorID,function, 0x00, 0x06, 0x00, 0x01, 0x64, 0x0B}, //0: PH 1,3,0,6,0,1,228,12
{sensorID,function, 0x00, 0x12, 0x00, 0x01, 0x25, 0xCA}, //1: Moisture
{sensorID,function, 0x00, 0x13, 0x00, 0x01, 0x75, 0xcf}, //2: Temp
{sensorID,function, 0x00, 0x15, 0x00, 0x01, 0x95, 0xce}, //3: EC
{sensorID,function, 0x00, 0x1e, 0x00, 0x01, 0xB5, 0xCC}, //4: Nitrogen (N)
{sensorID,function, 0x00, 0x1f, 0x00, 0x01, 0xE4, 0x0C}, //5: Phosphorus(P)
{sensorID,function, 0x00, 0x20, 0x00, 0x01, 0x85, 0xc0}, //6: Potassium (K)
{sensorID,function, 0x00, 0x1e, 0x00, 0x03, 0x34, 0x0D}, //7: NPK... would give back 6 bytes
{sensorID,function, 0x09, 0x06, 0x09, 0x01, 0x61, 0xc7}, //8: not valid, test frame to avoid bytes with zeros (0x00)
{sensorID,function, 0x00, 0x12, 0x00, 0x02, 0x65, 0xCB} //9: Temperature and Humidity
/* //Correct CRC
{sensorID,function, 0x00, 0x06, 0x00, 0x01, 0x0b, 0x64}, //0: PH
{sensorID,function, 0x00, 0x12, 0x00, 0x01, 0x24, 0x0f}, //1: Moisture
{sensorID,function, 0x00, 0x13, 0x00, 0x01, 0x75, 0xcf}, //2: Temp
{sensorID,function, 0x00, 0x15, 0x00, 0x01, 0x95, 0xce}, //3: EC
{sensorID,function, 0x00, 0x1e, 0x00, 0x01, 0xe4, 0x0c}, //4: Nitrogen (N)
{sensorID,function, 0x00, 0x1f, 0x00, 0x01, 0xb5, 0xcc}, //5: Phosphorus(P)
{sensorID,function, 0x00, 0x20, 0x00, 0x01, 0x85, 0xc0}, //6: Potassium (K)
{sensorID,function, 0x00, 0x1e, 0x00, 0x03, 0x65, 0xcd}, //7: NPK... would give back 6 bytes
{sensorID,function, 0x09, 0x06, 0x09, 0x01, 0x61, 0xc7}, //8: not valid, test frame to avoid bytes with zeros (0x00)
{sensorID,function, 0x00, 0x12, 0x00, 0x02, 0x65, 0x0d} //9: Temperature and Humidity
*/
};
SoftwareSerial modbusSerial(RO,DI);
void setReceive(){
digitalWrite(DE, LOW);
digitalWrite(RE, LOW);
}
void setTransmit(){
digitalWrite(DE, HIGH);
digitalWrite(RE, HIGH);
}
void blinking(int t, int count){
for(int i = 0;i < count; i++){
digitalWrite(ledPin, HIGH);
delay(t);
digitalWrite(ledPin, LOW);
delay(t);
}
}
// Compute the MODBUS RTU CRC
uint16_t ModRTU_CRC(byte buf[], int len) {
uint16_t crc = 0xFFFF;
for (int pos = 0; pos < len; pos++) {
crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc
for (int i = 8; i != 0; i--) { // Loop over each bit
if ((crc & 0x0001) != 0) { // If the LSB is set
crc >>= 1; // Shift right and XOR 0xA001
crc ^= 0xA001;
}
else // Else LSB is not set
crc >>= 1; // Just shift right
}
}
// Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
//{Serial.println(crc, HEX);
return crc;
}
void prepareInquiryFrame(int frameNumber){
setTransmit();
for(int i = 0;i<8;i++){
inquiry_frame[i] = inquiry_frames[frameNumber][i];
}
//calculating CRC
int crc = ModRTU_CRC(inquiry_frame, 6);
//extracting low byte and high byte
inquiry_frame[6] = (crc & 0xFF);
inquiry_frame[7] = ((crc >> 8) & 0xFF);
setReceive();
}
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(DE, OUTPUT);
pinMode(RE, OUTPUT);
//setTransmit();
setReceive();
digitalWrite(ledPin, LOW);
Serial.begin(9600); // Turn on the "main" serial port for debugging via USB Serial Monitor
modbusSerial.begin(9600);
}
int count = 0;
void loop() {
// receiving data
if(Serial.available()){
blinking(300,1);
byte input = Serial.read();
boolean sendFrame = true;
int loops = 8;
//getting inquiry Frame blinks twice slow, or blink fast 10x on unusable inputs
if(input>='0' && input<='9'){
prepareInquiryFrame(input-48);
blinking(300,2);
} else {
blinking(50,10);
sendFrame = false;
}
Serial.println("-----------------------------------");
Serial.print("Inquiry Frame Number: ");
Serial.println((char)input); Serial.println();
Serial.println("Complete Inquiry Frame:");
for(int i = 0; i<8; i++){
Serial.print(inquiry_frame[i], HEX);
Serial.print(", ");
}
Serial.println();
Serial.print("Calculated CRC: ");
Serial.print(inquiry_frame[6], HEX);
Serial.print(" ");
Serial.println(inquiry_frame[7], HEX);
setTransmit();
if(sendFrame){
//Send inquiry frame
modbusSerial.write(inquiry_frame, sizeof(inquiry_frame));
modbusSerial.flush();
} else {
modbusSerial.write(input);
modbusSerial.flush();
}
setReceive();
} else if(modbusSerial.available()){
char input[100];
blinking(100,2);
Serial.println();
Serial.println("modbusSerial.available(): "+String(modbusSerial.available()));
modbusSerial.readBytes(input, 100);
Serial.println("strlen(): "+String(strlen(input)));
Serial.print("data: ");
for (int i = 0; i<strlen(input); i++){
Serial.print(input[i], HEX);
Serial.print(", ");
}
Serial.println();
Serial.flush();
//empty buffer of modbusSerial
while(modbusSerial.available()){
//delay(100);
}
} else if (standaloneMode){
prepareInquiryFrame(TEMP);
//Send inquiry frame
modbusSerial.write(inquiry_frame, sizeof(inquiry_frame));
modbusSerial.flush();
blinking(300, 3);
delay(3000); //blinking 3 times plus delay sums up to 4.8 seconds
} else {
if(count>20 && !standaloneMode){
Serial.print(".");
count = 0;
} else {
count++;
}
digitalWrite(ledPin, LOW);
}
Serial.flush();
delay(50);
count++;
}