Sending command to external hardware and receive/print the string back from it

Hi guys,
I am a little bit frustrated cause I am not able to go through the following simple problem:
I have an external device (a conductivity sensor) with an RS485 interface. I am therefore using a RS485 shield to convert the sensor readings into TTL logic for my Arduino Mega. The sensor is not providing continuous output data, whereas the sensor is providing in output the ASCII value of its readings only when a specific command is send to it.

What I need to do therefore is sending this command (VALAR) over serial from my Arduino Mega to the sensor, waiting for the sensor to send the data back to Arduino (about 200ms) and only after Arduino has fully received it, printing it on my Serial Monitor (this just initially, in the future I might need to use this value within my sketch).

I have figured (I think) how to write that command and have also the code for printing serial data that is working for another device. However nothing is coming in output to my serial monitor.

int incomingByte = 0;   // for incoming serial data

char receivedChar_Conductivity;
const byte numChars_Conductivity = 32 ;
char receivedChars_Conductivity[numChars_Conductivity];
char tempChars_Conductivity[numChars_Conductivity]; // temporary array for use when parsing
char Conductivity[numChars_Conductivity] = {0};
int integerConductivity = 0;
float floatConductivity = 0.00;
boolean newData_Conductivity = false;
int num_Conductivity;
unsigned long time;

void setup() {
Serial.begin(115200);
Serial1.begin(115200);
}  // end of setup

void loop() {
  Serial1.write(0x56); // V
  Serial1.write(0x41); // A
  Serial1.write(0x4C); // L
  Serial1.write(0x41); // A
  Serial1.write(0x52); // R
  Serial1.write(0x0D); // CR carriage return in order to end the command

incomingByte = Serial.read();
  
recvWithEndMarker_Conductivity();
if (newData_Conductivity == true) {
    strcpy(tempChars_Conductivity, receivedChars_Conductivity);
        // this temporary copy is necessary to protect the original data
        //   because strtok() used in parseData() replaces the commas with \0
parseData_Conductivity();
showParsedData_Conductivity();
newData_Conductivity = false;
}

if (incomingByte=='0') { //pressing 0 into the serial monitor to stop the program
   while(1) { }
}

}

void recvOneChar_Conductivity() {
    if (Serial1.available() > 0) {
        receivedChar_Conductivity = Serial1.read();
        newData_Conductivity = true;
    }
}

void recvWithEndMarker_Conductivity() {
    static byte ndx_Conductivity = 0;
    char endMarker_Conductivity = '\n';
    char rc_Conductivity;
   
    while (Serial1.available() > 0 && newData_Conductivity == false) {
        rc_Conductivity = Serial1.read();

            if (rc_Conductivity != endMarker_Conductivity) {
                receivedChars_Conductivity[ndx_Conductivity] = rc_Conductivity;
                ndx_Conductivity++;
                if (ndx_Conductivity >= numChars_Conductivity) {
                    ndx_Conductivity = numChars_Conductivity - 1;
                }
            }
            else {
                receivedChars_Conductivity[ndx_Conductivity] = '\0'; // terminate the string
                ndx_Conductivity = 0;
                newData_Conductivity = true;
            }
        }
}

void showNewData_Conductivity() {
    if (newData_Conductivity == true) {
        Serial.print(receivedChar_Conductivity);
        newData_Conductivity = false;
    }
}

void parseData_Conductivity() {      // split the data into its parts

    char * strtokIndx_Conductivity; // this is used by strtok() as an index

    strtokIndx_Conductivity = strtok(tempChars_Conductivity," ");      // get the first part - the string
    strcpy(Conductivity, strtokIndx_Conductivity); // copy it to messageFromPC
 
    integerConductivity = atoi(strtokIndx_Conductivity);     // convert this part to an integer

    floatConductivity = atof(strtokIndx_Conductivity);  // convert this part to a float
    
    

}


void showParsedData_Conductivity() {
    Serial.print("Conductivity: ");
    Serial.print(floatConductivity);
    Serial.print("\t");
    
  Serial.print("Time: ");
  time = millis();
  Serial.println(time);
}

Any help would be highly appreciated. Thank you.

I have figured (I think) how to write that command

Why not

Serial1.print("VALAR\r");

Much simpler

Are you sure that the device can receive at 115200 baud ?
What is the device ?

If you are using RS485, then you should also have an additional signal (or two) that switches your RS485 transceiver chip from receive to transmit. That assumes that you are using half duplex RS485, which tends to be more common.

The also assumes that you are not using one of the auto switching RS485 modules.

Provide links to the RS485 module and conductivity module and we can better advise you.

UKHeliBob:
Why not

Serial1.print("VALAR\r");

Much simpler

Are you sure that the device can receive at 115200 baud ?
What is the device ?

Why Serial1.print("VALAR\r") and not Serial1.write?

Yes the device has a Baud Rate 115.2 kBit/s (default) or 9600 (configurable) (DATASHEET)

Why Serial1.print("VALAR\r") and not Serial1.write?

It does the same thing as your series of Serial1.write()s, ie it sends ASCII codes for the letters, but is much easier to write and understand

he device has a Baud Rate 115.2 kBit/s (default) or 9600 (configurable) (DATASHEET)

How do you configure it ?

markd833:
If you are using RS485, then you should also have an additional signal (or two) that switches your RS485 transceiver chip from receive to transmit. That assumes that you are using half duplex RS485, which tends to be more common.

The also assumes that you are not using one of the auto switching RS485 modules.

Provide links to the RS485 module and conductivity module and we can better advise you.

You are right, I took a look at the RS485 click board. There is a R/T pin that according to his status (HIGH/LOW) set the board as transmitter or receiver

Yes, you need to drive the R/T line appropriately to switch the device from receive to transmit and back again.

In order to avoid the usual problem that occurs when starting out with RS485, remember that when you have finished your calls to print() or write(), that the data has not been completely transmitted - just loaded into the transmit buffer. You need to wait until the last byte of data has been transmitted out of the UART hardware before switching back to receive mode, otherwise you will chop off the end of your message. Use the flush() function to wait until all bytes have gone, and then revert back to receive mode.

markd833:
Yes, you need to drive the R/T line appropriately to switch the device from receive to transmit and back again.

In order to avoid the usual problem that occurs when starting out with RS485, remember that when you have finished your calls to print() or write(), that the data has not been completely transmitted - just loaded into the transmit buffer. You need to wait until the last byte of data has been transmitted out of the UART hardware before switching back to receive mode, otherwise you will chop off the end of your message. Use the flush() function to wait until all bytes have gone, and then revert back to receive mode.

I have indeed changed my code to the following, but is printing only few values and completely random.

int incomingByte = 0;   // for incoming serial data
byte RT=41;

char receivedChar_Conductivity;
const byte numChars_Conductivity = 32 ;
char receivedChars_Conductivity[numChars_Conductivity];
char tempChars_Conductivity[numChars_Conductivity]; // temporary array for use when parsing
char Conductivity[numChars_Conductivity] = {0};
int integerConductivity = 0;
float floatConductivity = 0.00;
boolean newData_Conductivity = false;
int num_Conductivity;
unsigned long time;

void setup() {
pinMode (RT, OUTPUT);
Serial.begin(115200);
Serial1.begin(115200);
}  // end of setup

void loop() {

digitalWrite(41,HIGH);
Serial1.print("VALAR\r");
digitalWrite(41,LOW);

incomingByte = Serial.read();
  
recvWithEndMarker_Conductivity();
if (newData_Conductivity == true) {
    strcpy(tempChars_Conductivity, receivedChars_Conductivity);
        // this temporary copy is necessary to protect the original data
        //   because strtok() used in parseData() replaces the commas with \0
parseData_Conductivity();
showParsedData_Conductivity();
newData_Conductivity = false;
}

if (incomingByte=='0') { //pressing 0 into the serial monitor to stop the program
   while(1) { }
}

}

void recvOneChar_Conductivity() {
    if (Serial1.available() > 0) {
        receivedChar_Conductivity = Serial1.read();
        newData_Conductivity = true;
    }
}

void recvWithEndMarker_Conductivity() {
    static byte ndx_Conductivity = 0;
    char endMarker_Conductivity = '\n';
    char rc_Conductivity;
   
    while (Serial1.available() > 0 && newData_Conductivity == false) {
        rc_Conductivity = Serial1.read();

            if (rc_Conductivity != endMarker_Conductivity) {
                receivedChars_Conductivity[ndx_Conductivity] = rc_Conductivity;
                ndx_Conductivity++;
                if (ndx_Conductivity >= numChars_Conductivity) {
                    ndx_Conductivity = numChars_Conductivity - 1;
                }
            }
            else {
                receivedChars_Conductivity[ndx_Conductivity] = '\0'; // terminate the string
                ndx_Conductivity = 0;
                newData_Conductivity = true;
            }
        }
}

void showNewData_Conductivity() {
    if (newData_Conductivity == true) {
        Serial.print(receivedChar_Conductivity);
        newData_Conductivity = false;
    }
}

void parseData_Conductivity() {      // split the data into its parts

    char * strtokIndx_Conductivity; // this is used by strtok() as an index

    strtokIndx_Conductivity = strtok(tempChars_Conductivity," ");      // get the first part - the string
    strcpy(Conductivity, strtokIndx_Conductivity); // copy it to messageFromPC
 
    integerConductivity = atoi(strtokIndx_Conductivity);     // convert this part to an integer

    floatConductivity = atof(strtokIndx_Conductivity);  // convert this part to a float
    
    

}


void showParsedData_Conductivity() {
    Serial.print("Conductivity: ");
    Serial.print(floatConductivity);
    Serial.print("\t");
    
  Serial.print("Time: ");
  time = millis();
  Serial.println(time);
}

Actually I need to put a delay before of switching back to transmit mode right? The receiving part (from sensor to Arduino) is the one that will take more time and where I have to wait until the last byte of data has been transmitted. Correct?

The order of events generally goes:

  • Switch your RS485 driver to transmit mode
  • Send your data/command
  • Wait for your data to completely transmit
  • Switch your RS485 driver back to receive mode

Use the serial flush() function to wait for the data to complete transmission, and then switch back to receive mode. The idea is to only put your RS485 module into transmit mode for the time it takes to send out what you have to and then get back to receive mode, so you do not "hog" the bus as it were.

Generally you then wait for a response to come back from the other device and then decode it.

markd833:
The order of events generally goes:

  • Switch your RS485 driver to transmit mode
  • Send your data/command
  • Wait for your data to completely transmit
  • Switch your RS485 driver back to receive mode

I think you mean the following:

void loop() {

digitalWrite(41,HIGH); //transmit mode
Serial1.print("VALAR\r"); // send command
Serial1.flush(); // wait to complete the transmission

digitalWrite(41,LOW); // receiver mode
incomingByte = Serial.read();
recvWithEndMarker_Conductivity();  //receive the string and print it
if (newData_Conductivity == true) {
    strcpy(tempChars_Conductivity, receivedChars_Conductivity);
        // this temporary copy is necessary to protect the original data
        //   because strtok() used in parseData() replaces the commas with \0
parseData_Conductivity();
showParsedData_Conductivity();
newData_Conductivity = false;
}

}

41 is the digital pin connected to R/T pin of the RS485 click.
However it is not working at all... I am not able to see anything on the Serial Monitor. Not even one reading

It looks like the Tx side is ok. As you don't appear to receive anything coming out of your serial receive processing code, I would suggest an initial quick change to your code to remove all the receive processing and simply print out every character received. That should help determine if the remove device is actually responding to the command.

markd833:
simply print out every character received. That should help determine if the remove device is actually responding to the command.

Good point actually. I ve changed the code to the following:

int incomingByte = 0;   // for incoming serial data
byte RT=41;

void setup() {
pinMode (RT, OUTPUT);
Serial.begin(115200);
Serial1.begin(115200);
}  // end of setup

void loop() {
digitalWrite(41,HIGH); //transmit mode
Serial1.print("VALAR\r"); // send command
Serial1.flush(); // wait to complete the transmission
digitalWrite(41,LOW); // receiver mode
incomingByte = Serial.read();
if (Serial1.available()) {
    char inByte = Serial1.read();
    Serial.print(inByte);
  }

if (incomingByte=='0') { //pressing 0 into the serial monitor to stop the program
   while(1) { }
}

}

It prints an infinite string of symbols: " I5VALAR1I5VALAR8U1I5⸮ " and so on.........
So something is arriving on the RX pin.
Interesting thing is:
if I suddenly unplug the RX pin from ARDUINO, the string in the serial monitor stops (of course you would say :slight_smile: )
Odd thing is:
if I unplug the channel A and B of my RS485 device, the string in the serial monitor is not stopping but is still getting some characters (not sure why...I expected the string to stop)

That sounds like progress. One suggestion I would make at this stage is to send the command like you have and then wait in a receive loop for, say, 2 or 3 seconds, just to capture all the characters coming back. You may be hammering the remote device with commands. It will also, hopefully, indicate if the remote device is simply echoing your command of if it is actually outputting data.

markd833:
send the command like you have and then wait in a receive loop for, say, 2 or 3 seconds, just to capture all the characters coming back.

I used the following code and only this came out from the serial monitor in almost one minute of execution (see attached):

int incomingByte = 0;   // for incoming serial data
byte RT=41;
unsigned long t;

void setup() {
pinMode (RT, OUTPUT);
digitalWrite(41,HIGH);
Serial.begin(115200);
Serial1.begin(115200);
}  // end of setup

void loop() {

digitalWrite(41,HIGH); //transmit mode
Serial1.print("VALAR\r"); // send command
Serial1.flush(); // wait to complete the transmission
digitalWrite(41,LOW); // receiver mode
incomingByte = Serial.read();
t=millis();
while (millis()<t+3000){
if (Serial1.available()) {
    char inByte = Serial1.read();
    Serial.print(inByte);
  }
}

}

markd833:
It will also, hopefully, indicate if the remote device is simply echoing your command of if it is actually outputting data.

I think the device is definitely echoing back my command. However, I have already double checked the Tx/Rx connections

Ok, I think we have reached the point where we need to figure out your hardware.

From your previous post(s) I can see that you are using an Arduino Mega with an RS485 interface board connected to Serial1. In your posts you mention an RS485 Click, so I'm guessing that it's the little module from MIKROE here.

You've not detailed your connections to this module, but I assume that you have wired up +5V, GND, TX, RX & T/R to your Mega. There are also 3 jumpers on the RS485 module - are they all fitted?

You've not said (or i've missed it!) which conductivity sensor you are using? Can you post a link to its data sheet.

How have you connected up this sensor?

As a basic sanity check, just to check there isn't a short between TX & Rx - if you disconnect the RS-485 wires, do you still get the command echoed back? Hopefully not ...

markd833:
I'm guessing that it's the little module from MIKROE here.

Correct!

markd833:
I assume that you have wired up +5V, GND, TX, RX & T/R to your Mega. There are also 3 jumpers on the RS485 module - are they all fitted?

Yes, they are all fitted.
Also:
-) TX of arduino is connected to TX of MIKROE
-) RX of arduino is connected to RX of MIKROE

markd833:
Can you post a link to its data sheet.

How have you connected up this sensor?

Sensor is this HDU-CDTP Conductivity Sensor. Pinout of the sensor is attached, particularly I have connected:
BROWN --> VCC of MIKROE
BLUE --> GND of MIKROE
WHITE--> - of MIKROE
BLACK--> + of MIKROE

markd833:
As a basic sanity check, just to check there isn't a short between TX & Rx - if you disconnect the RS-485 wires, do you still get the command echoed back? Hopefully not ...

Yes, disconnecting RS485 wires I am not getting my command echoed back anymore.

D10S:
Correct!
Yes, they are all fitted.
Also:
-) TX of arduino is connected to TX of MIKROE
-) RX of arduino is connected to RX of MIKROE

The UART peripheral transmits data on the TX pin and receives data on the RX pin. This is also the case for the receiving device. So the wiring should be TX->RX and RX->TX.

I found this schematic on the Mikroe website for the RS485 module:


(I have no idea why the image appears so small on the forum - download it to view a readable one!)

It might be worth quickly swapping over the Tx & Rx wires as it's not clear what signals the silkscreen TX & RX labels on the actual board are referring to. This chip has the same pinout as a MAX485 chip, so pin 1 (marked R) is where the data received over the RS485 bus comes out and goes to your Mega. Similarly, pin 4 (marked D) is where the data from your Mega goes to be transmitted over the RS485 bus.

Looking at the actual pictures on the Mikroe website, it's not that clear but it does look like the track that goes to the pin with the silkscreen label RX goes to pin 4 of the chip. This would suggest that your Mega Tx pin should connect to the module pin labelled RX. And therefore your mega Rx pin should connect to the module pin labelled TX.

This all assumes that the image on the Mikroe website is correct!

markd833:
I found this schematic on the Mikroe website for the RS485 module:


(I have no idea why the image appears so small on the forum - download it to view a readable one!)

nicolajna:
The UART peripheral transmits data on the TX pin and receives data on the RX pin. This is also the case for the receiving device. So the wiring should be TX->RX and RX->TX.

Thank you both. I actually was mislead by another post on this forum where (if you look at the MIKROE schematic) pin 1 (marked R) of the ADM485 was connected to RX on arduino.
Now is perfectly working and I am getting my conductivity value.
I also managed to process the data, transforming the string into a float value for elaboration within a wider sketch. In this scenario, since I need to place the reading + elaboration within a while for 200 ms (otherwise the serial is not reading anything cause you would hammer the slave device), this will stop the operation of the code and I cannot afford it. There is any way to put this into a parallel branch while my code will still run?

int incomingByte = 0;   // for incoming serial data
byte RT=41;

char receivedChar_Conductivity;
const byte numChars_Conductivity = 64 ;
char receivedChars_Conductivity[numChars_Conductivity];
char tempChars_Conductivity[numChars_Conductivity]; // temporary array for use when parsing
char Conductivity[numChars_Conductivity] = {0};
int integerConductivity = 0;
float floatConductivity;
float floatCoeff;
float floatTemp;
boolean newData_Conductivity = false;
int num_Conductivity;
unsigned long time;
unsigned long t;

void setup() {
pinMode (RT, OUTPUT);
Serial.begin(9600);
Serial1.begin(115200);
Serial1.print("USRBDW 9600\r"); // send command
Serial1.flush(); // wait to complete the transmission
}  // end of setup

void loop() {

digitalWrite(41,HIGH); //transmit mode
Serial1.print("VALAR\r"); // send command
Serial1.flush(); // wait to complete the transmission

digitalWrite(41,LOW); // receiver mode
incomingByte = Serial.read();
t=millis();
while (millis()<t+200){
recvWithEndMarker_Conductivity();  //receive the string and print it
if (newData_Conductivity == true) {
    strcpy(tempChars_Conductivity, receivedChars_Conductivity);
        // this temporary copy is necessary to protect the original data
        //   because strtok() used in parseData() replaces the commas with \0
parseData_Conductivity();
showParsedData_Conductivity();
newData_Conductivity = false;
}
}

}

void recvWithEndMarker_Conductivity() {
    static byte ndx_Conductivity = 0;
    char endMarker_Conductivity = '\r';
    char rc_Conductivity;
   
    while (Serial1.available() && newData_Conductivity == false) {
        rc_Conductivity = Serial1.read();

            if (rc_Conductivity != endMarker_Conductivity) {
                receivedChars_Conductivity[ndx_Conductivity] = rc_Conductivity;
                ndx_Conductivity++;
                if (ndx_Conductivity >= numChars_Conductivity) {
                    ndx_Conductivity = numChars_Conductivity - 1;
                }
            }
            else {
                receivedChars_Conductivity[ndx_Conductivity] = '\0'; // terminate the string
                ndx_Conductivity = 0;
                newData_Conductivity = true;
            }
        }
}

void showNewData_Conductivity() {
    if (newData_Conductivity == true) {
        Serial.print(receivedChar_Conductivity);
        newData_Conductivity = false;
    }
}

void parseData_Conductivity() {      // split the data into its parts

    char * strtokIndx_Conductivity; // this is used by strtok() as an index

    strtokIndx_Conductivity = strtok(tempChars_Conductivity, "/"); 
    floatConductivity = atof(strtokIndx_Conductivity);     // convert this part to an integer

}


void showParsedData_Conductivity() {
  Serial.print("Conductivity: ");
  Serial.print(floatConductivity,8);
  Serial.print("\t");
   
  Serial.print("Time: ");
  time = millis();
  Serial.println(time);
}

I'm not a 100% percent sure I understand what it is that you want to achieve. Do you want to perform multiple tasks in parallel?

The CPU cannot execute multiple instructions at once, so what you'll have to do is "fake it". The typical approach is implementing the tasks that you want to perform as state machines. You break every task into smaller steps that execute quickly and transition between them based on some input. By rapidly switching between advancing the state machine of multiple tasks, you can effectively create the illusion that the CPU does multiple things at the same time.

It requires some practice getting good at this technique, but is well worth the effort since it makes constructing programs a lot easier.

I've given a little example below which should demonstrate what i mean. Feel free to ask if I need to clarify something.

#define TIME_BETWEEN_COMMANDS 200
#define TOGGLE_TIME 100

enum ReceiveTaskState {
	WAIT,
	SEND_COMMAND,
	RECEIVE_RESPONSE
}

void someOtherTask() {
	static uint32_t lastToggleTime = 0;

	if (millis() - lastToggleTime > TOGGLE_TIME) {
		digitalWrite(someLed, !digitalRead(someLed));
		lastToggleTime += TOGGLE_TIME;
	}
}

void receiveTask() {

	static enum ReceiveTaskState currentReceiveState = WAIT;
	static uint32_t lastCommandTime = 0;
	
	switch(currentReceiveState) {

		case WAIT:
		if (millis() - lastCommandTime > TIME_BETWEEN_COMMANDS) {
			lastCommandTime += TIME_BETWEEN_COMMANDS;
			currentReceiveState = SEND_COMMAND;
		}
		break;

		case SEND_COMMAND:
		digitalWrite(41, HIGH);
		Serial1.print("VALAR\r")
		currentReceiveState = RECEIVE_RESPONSE;
		break;

		case RECEIVE_RESPONSE:
		digitalWrite(41, LOW);
		if (Serial.available()) {
			// Read the response here
			currentReceiveState = WAIT;
		}
		break;

	}
}

void loop() {
	receiveTask();
	someOtherTask();
}