Hi all,
I have encountered a strange phenomenon I have no explanation for. On an Arduino mega I read the input from a 6-channel spectrophotometric sensor via Serial1 (6 integer values separated by a comma and terminated by a "new line" character (0x0A), only the relevant portion of the code is shown):
void setup(){
begin.Serial1(9600);
}
void loop() {
if (Serial1.available()) {
vi = Serial1.parseFloat(); vi = (vi * f_vi); // correction factors (f_vi etc.) plus another
bl = Serial1.parseFloat(); bl = (bl * f_bl); // correction (experimental)
gn = Serial1.parseFloat(); gn = (gn * f_gn);
yl = Serial1.parseFloat(); yl = (yl * f_yl);
og = Serial1.parseFloat(); og = (og * f_og);
rd = Serial1.parseFloat(); rd = (rd * f_rd);
if (Serial1.read() == '\n') { // here follow the instructions what to do with these numbers}
// parseFloat instead of parseInt because the values are processed with float values
I have two nearly identical sensors: a AS7162 breakout board, connected via I2C with a Arduini Mini Pro. This device is connected with a cable to the Arduino Mega. Sensor 1 uses the AS7162 board from Pimoroni and has a 30 m cable with 4 lines, sensor 2 uses the Adafruit board and a 10 m cable with 3 lines plus shield (GND). When connected to a serial terminal both sensors do the same (as expected): they send 6 numbers, separated by a comma (0x2C) and as last bit \n (0x0A).
But connected with the Mega, only sensor 1 behaves as expected, sensor 2 does not. It stops at the second if. When changing the condition to
(Serial1.read() != '\n')
it works with sensor 2, but not with sensor 1. But how is that possible? The hex-output of my terminal shows with no doubt, that both sensors send a 0x0A!
I can only take a guess. If possible slow the baud down a notch. I have seen similar problems way in the past and it was caused by baud clock skew. The fact that it works on one board and not on the other assuming the code is the same points to a hard problem.
Yeah, the idea of too fast transmission popped up by me, too. Therefore I connected the sensors with an oscilloscope to see, if the signals were damaged due to too long cables (but 30 m ist't that far...), but the oscilloscope showed in both cases two identical pictures of a nice rectangle curve. Therefore I got away from this idea. But because of your post I'll slow the baud rate down (tomorrow...)
Thanks
-richard
I think you get this problem because your sketch is not fail-safe. If you don't understand my explanation, please ask.
With 9600 baud, the data is incoming very slow. That means you are already converting data that is still being received at that moment.
The parseFloat() waits for data, and it has a timeout, so that is okay, but the read() for '\n' is not okay.
When expecting the '\n', it might not have been received yet. The sketch can easily be too fast with 9600 baud.
The parseFloat() is finished when something is received which is not part of a float number. The trailing '\n' is a such a thing. Do you know if that '\n' is read or not by the parseFloat() ?
What if there is a space before the '\n' ?
When reading serial data, it is common to read a full line, including the '\n'. When everything is received correctly, then the data is converted.
Please specify the serial data. Are they whole numbers or do they have a dot with decimals ? I think you should not use parseFloat to convert integers to float.
Thanks for your thoughts. Drawing in the attachment. Both answers helped, though I’m not through with it. The sensors send “sentences”, for example: 1345,23689,45317,3245,136784,2346\n
Should I receive it as string and trim it to the 6 values?
For an echo and to see what is processed, I added some "Serial.print"s:
void loop() {
while (Serial1.available()) {
vi = Serial1.parseFloat(); vi = (vi * f_vi) / 135;Serial.println(vi); // correction factors (f_vi etc.) plus another
bl = Serial1.parseFloat(); bl = (bl * f_bl) / 128; // correction (experimental)
gn = Serial1.parseFloat(); gn = (gn * f_gn) / 128;
yl = Serial1.parseFloat(); yl = (yl * f_yl) / 125;
og = Serial1.parseFloat(); og = (og * f_og) / 129;
rd = Serial1.parseFloat(); rd = (rd * f_rd) / 123;
if (Serial1.read() == '\n') {Serial.println("newline");
The results were as follows:
Sensor 1 (the strange one):
<Serial Monitor>
newline
0.64
1.28
1.29
... (the values shown are correct)
→ \n is only read one time
And sensor 2 (the one that is OK):
newline
0.33 // why read 4 times???
1.20
0.67
0.33
newline // from here on it looks OK
0.33
newline
0.33
newline
0.33
As pointed out by koepel, there may be timing problems.
@ koepel: better use parseInt() and then convert to float? As float() converts every value to float, I think I can use
float vi = Serial1.parseInt();
?
Misunderstanding: It is only planned to use sensor 1 at Serial1, and sensor 2 at Serial2. But momentarily I only want to find out, if they work correctly at all. Therefore I use them alternatively at Serial1. I control them by my eyes: If sensor1 is plugged in, then sensor 1 is sending, if sensor2 is plugged in, then obviously sensor 2 is sending
Makes sense. I'd read until you see the linefeed then & capture the string so you can print it for debugging and then parse it. For best results, read and throw away until the first linefeed and then process for real.
You can not do a if (Serial1.read() == '\n'), because the bits of the '\n' could still be halfway receiving.
It is not nice to receive an integer with a parseFloat(), that is the wrong function for that purpose. The parseInt() returns a long, so it can be a large number. https://www.arduino.cc/en/Reference/ParseInt
A simple solution is a delay(10) before the Serial1.read().
A better solution is to put the whole line in a buffer. Then you can deal with every problem, for example not enough numbers. Do you know the scanf() function ?
What about a '#' or '*' as the first character of a line ? Then you don't have to throw away the first data to get in sync.
In the old days there was a 'STX' as first character and a 'ETX' as last character and probably a checksum byte before the 'ETX'.
Thank you all for the discussion. The idea to put things into a buffer and then read it at once has convinced me. I found an example by zoomkat (https://forum.arduino.cc/index.php?topic=121454.0) which was a little modified:
/*
//zoomkat 11-12-13 String capture and parsing
https://forum.arduino.cc/index.php?topic=121454.0
modified Nov, 2020 RMü
*/
String readString; //main captured String
String viStr; //data String
String blStr;
String gnStr;
String ylStr;
String ogStr;
String rdStr;
float vi;
float bl;
float gn;
float yl;
float og;
float rd;
int ind1; // where is the comma?
int ind2;
int ind3;
int ind4;
int ind5;
int ind6;
// constants for computing mW's:
float f_vi = 1 / 138.86;
float f_bl = 1 / 109.10;
float f_gn = 1 / 92.85;
float f_yl = 1 / 87.69;
float f_og = 1 / 80.83;
float f_rd = 1 / 77.12;
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
}
void loop() {
if (Serial1.available()) {
char c = Serial1.read(); //gets one byte from serial buffer
if (c == '*') {
//do stuff
Serial.println();
Serial.print("captured String is : ");
Serial.println(readString); //prints string to serial port out
ind1 = readString.indexOf(','); //finds location of first ,
vi = ((readString.substring(0, ind1)).toInt()) * f_vi /135; //captures first data String
ind2 = readString.indexOf(',', ind1 + 1 ); //finds location of second ,
bl = ((readString.substring(ind1 + 1, ind2)).toInt()) * f_bl / 128; //captures second data String
ind3 = readString.indexOf(',', ind2 + 1 );
gn = ((readString.substring(ind2 + 1, ind3)).toInt()) * f_gn / 128;
ind4 = readString.indexOf(',', ind3 + 1 );
yl = ((readString.substring(ind3 + 1, ind4)).toInt()) * f_yl / 125;
ind5 = readString.indexOf(',', ind4 + 1);
og = ((readString.substring(ind4 + 1, ind5)).toInt()) * f_og / 129;
ind6 = readString.indexOf(',', ind5 + 1);
rd = ((readString.substring(ind5 + 1)).toInt()) * f_rd / 123; //captures remain part of data after last ,
/*vi = viStr.toInt();
bl = blStr.toInt();
gn = gnStr.toInt();
yl = ylStr.toInt();
og = ogStr.toInt();
rd = rdStr.toInt();*/
Serial.print("vi = ");
Serial.println(vi);
Serial.print("bl = ");
Serial.println(bl);
Serial.print("gn = ");
Serial.println(gn);
Serial.print("yl = ");
Serial.println(yl);
Serial.print("og = ");
Serial.println(og);
Serial.print("rd = ");
Serial.println(rd);
Serial.println();
Serial.println();
readString = ""; //clears variable for new input
}
else {
readString += c; //makes the string readString
}
}
}