Need help with floating point into array formatting

Hi gang,
I've got this code that does a lot of stuff, running on a '1284P.
Having a problem getting it to compile. Seems to be related to a couple of lines in one tab that convert a floating point number into an array. I need it as an array so it can be sent to a GPS/GSM module to send out as a text message.
I have the same formatting in two tabs. One place it works as expected, the other it does not.
In d2_volts_pressure, 3 instances of this:

float v1 = analogRead (v1Pin) * (5 / 1023.) * 10.0904; // check voltage divider nominal level 48v
//Serial.print ("v1 (48V, need >47V to be good, and not fall below 44V) = ");
//Serial.println (v1);
v1int = (unsigned int)(v1 * 100.0); // assuming v1int will have 4 digits
String tempConv1 = String(v1int); // construct string representation
tempConv1.toCharArray(v1char, 5); // string representation is 4 chars plus null terminator, copy 4 characters, ignore null terminator

and also this

pressureint = (unsigned int)(pressure * 10.0); // assuming v1int will have 4 digits
String pressureConv = String(pressureint); // construct string representation
pressureConv.toCharArray(Pressurechar, 5); // string representation is 4 chars plus null terminator, copy 4 characters, ignore null terminator

Those all convert fine and get texted out okay as values like 15.47, 12.38, 47.25 for the first 3, and 150.0 for the pressure.

In the d4_temp0_2 tab, I've got the same thing 3 more times, attempting to send out values like 125.0 and 250.0

 temp0Int = (unsigned int)(TemperatureF0 * 10.0); // assuming TemperatureF will have 4 digits
 String heatConv0 = String(temp0Int); // construct string representation
 heatConv0.toCharArray(TemperatureF0char, 5); // string representation is 4 chars plus null terminator, copy 4 characters, ignore null terminator

When the d4 tab is commented out, everything runs great.
When the tab is left in, I get this set of errors in IDE 1.0.6 and a similar set in 1.6.9:

d4_temp0_2.ino: In function 'void loop()':
d4_temp0_2:160: error: jump to case label
d4_temp0_2:155: error: crosses initialization of 'String heatConv0'
d4_temp0_2:313: error: jump to case label
d4_temp0_2:308: error: crosses initialization of 'String heatConv1'
d4_temp0_2:155: error: crosses initialization of 'String heatConv0'

and in 1.6.9:

C:\Users\CrossRoadsFencing.CrossRoads\Documents\ArduinoStuff\Tom Bijou 1284P truck controller\Controller2Rev16e\d4_temp0_2.ino: In function 'void loop()':
d4_temp0_2:160: error: jump to case label [-fpermissive]
case 4:
^
d4_temp0_2:155: error: crosses initialization of 'String heatConv0'
String heatConv0 = String(temp0Int); // construct string representation
^
d4_temp0_2:313: error: jump to case label [-fpermissive]
case 5:
^
d4_temp0_2:308: error: crosses initialization of 'String heatConv1'
String heatConv1 = String(temp1Int); // construct string representation
^
d4_temp0_2:155: error: crosses initialization of 'String heatConv0'
String heatConv0 = String(temp0Int); // construct string representation
^
Using library SPI at version 1.0 in folder: C:\Arduino 1.6.9\hardware\arduino\avr\libraries\SPI 
exit status 1
jump to case label [-fpermissive]

Any ideas on how to fix this? I've tried making all the variables unique, even the case statement numbering (hence 4-5-6 vs 1-2-3, altho the code is in different switch:case areas and shouldn't matter), nothing I can think of helps.

Thanks

Controller2Rev16f.zip (15.4 KB)

you don't see it at first but your code where you create the String object is in a massive switch/case.

in C++ you cannot declare local variable in a case statement unless you create a {} block for the case.

so try with

switch (tempToRead) {
case 3: [b][color=red]{[/color][/b]
...
  [color=green]String heatConv0 [/color]= String(temp0Int); // construct string representation
  heatConv0.toCharArray(TemperatureF0char, 5); // string representation is 4 chars plus null terminator, copy 4 characters, ignore null terminator
  tempToRead = 4;
  break;
[b][color=red]}[/color][/b]

need to do this every time this will be in a switch statement

side note - try to remove those String construction just for the sake of getting the toCharArray after... use C functions for this or you'll get possibly at some point possibly some memory errors if this is called often

Having a function span more than one file is not a great idea. Have a function span more than 50 lines isn't
usually a good idea come to that. Shorter functions, more of them, well named. If the switch does nothing
more than call a different function for each case that's going to be much more readable and maintainable
and self-documenting...

J-M-L - thanks, that lets it compile now.

Testing - and there's the output I wanted:
00.00000,000.00000,47.78,15.23,13.06,00101,150.0,260.0,255.0,265.0>
GPS, GPS, voltage, voltage, voltage, switch settings, pressure, temperature, temperature, temperature - perfect! Thanks again :smiley: :smiley: :smiley:

MarkT - this code setup was easier to figure out quick than figuring out how to move the channel count thru the code block and making multiple functions out of it. Especially as I needed to add to each one to fake an output here and one there to test functionality of other tabs.
The only function spanning one tab is loop(), there no other function calls. Everything is running inline. I don't do functions in the traditional sense, but I do set flags and have a code block run it, then clear the flag. This code is also state machine like to allow stuff to be responsive while waiting for GPS/GSM module responses to come back. (checking for OK or a > depending on what is going on).
Each Tab in my file works as one file, more or less. Having more tabs means the screen width needs to be wider to see them all, which meant you can't see them all and have the Serial Monitor open for debugging, and with 1.0.6 and earlier you can't pan across the tabs, so that's kind of a limit. 8-10 tabs is about the limit of what I find workable.

Good news!

Happy coding

I may have spoke to soon. With additional testing, I discovered I had a typo in the cycling thru all 3 states.

At the very end, if I change tempToRead from 0 to 3 so it correctly cycles thru all 3 MAX31865 chips, it screws up operating:

    // ****  End Temperature Reading Code  **************
    // convert to array format for sending to GPS module 

    temp2Int = (unsigned int)(TemperatureF2 * 10.0); // assuming TemperatureF will have 4 digits
    String heatConv2 = String(temp2Int); // construct string representation
    heatConv2.toCharArray(TemperatureF2char, 5); // string representation is 4 chars plus null terminator, copy 4 characters, ignore null terminator

    tempToRead = 0;

    break;
  }
}

// SPI mode changes - need for shift registers
SPI.setDataMode(SPI_MODE0); // choices 0,1,2,3 - reset to 0 for shift registers

If it is 0, the code sequences correctly, once it seems, the jump to the non-existent case 0 then never reads the switches to fake the high/low temperature again.

What am I missing still?
Thanks

d4_temp0_2.ino (20.9 KB)

And when I say screws up, the GPS/GSM setup code at the top of loop never completes, it seems to be in this limbo land of trying to initialize the GPS/GSM module, then GPS readings start getting stuck in the middle that don't complete either.

Hmm, I've apparently got some timing that needs work, having the temperature code taking going on every pass thru loop with having to clear out the error registers is just killing my responsiveness it seems.

A first task to address would be to clean up the code from all those useless Strings

String tempConv1 = String(v1int); // construct string representation
tempConv1.toCharArray(v1char, 5); // string representation is 4 chars plus null terminator, copy 4 characters, ignore null terminator

You are shooting holes all over the place in the Heap and if I remember correctly I don't even think that toCharArray adds the null at the end of the charArray for you, so v1Char is not properly terminated. I don't know if that null termination is important or not in that code, but from a memory management standpoint what they do is poor.

What the code segment above does is just put in v1Char the ASCII representation of v1int that you know is max 4 digits. So this is a great place to use itoa() which Converts an integer value to a null-terminated string. So you do just itoa(v1int, v1char,10);

At the very end, if I change tempToRead from 0 to 3 so it correctly cycles thru all 3 MAX31865 chips, it screws up operating

Just thinking aloud here... I'm on my phone so can't see the full code - thus this comment may be totally irrelevant - but if 0 means read one MAX31865, shouldn't the max value you put in that variable be 2, to read 0,1,2 which are 3 values? And if you put 3, won't that be reading 0,1,2,3 with 3 not existing thus leading to unknown results or waiting for timeout for something inexistant not answering?

Hi J-M-L,
Thanks. I'll give itoa() a shot. Data manipulation is not my strong suit in software. Getting ASCII for the GPS/GSM module was a pain in the butt.

Yes, turns out the case #s are not the problem. 0,1,2 or 3,4,5, I was doing more testing last night and I believe the problem is the 700mS period needed to clear the error registers in the MAX31865s. I pared the code down to just 1 chip (of 3), and I could see the GPS results come dribbling in one character at a time when I spread out the GPS location requests to a minute or two between requests (vs 15 seconds) and eventually the entire string was read in and flow proceeded normally. With 2 chips even more time was needed, and with 3 it got ridiculous. So now I am in the process or rewriting the code blink-without-delay style to let everything else run normally while waiting for one to clear out and then the 2nd to clear out. It's a little awkward as they occur in an else{ } statement after a sample is requested, but I'll get it figured out.

itoa() worked, tried it one of 7 places where it can be used.
Would ftoa() work similar?
http://www.cplusplus.com/forum/beginner/2927/
I don't see how you limit it to one or two decimal places tho.

Look at this mess I had to come up with for multiple blink-without-delay passes to clear the two fault registers, somewhat convoluted, but it seems to work from little testing I've done. Adding two more channels tomorrow and then readings with a real probe or two.

    byte fault_test = lsb_rtd&0x01;
    if (fault_test == 0x01){
      //     Serial.print ("Channel 0");
      //     Serial.print (" Error was detected. The RTD resistance measured is not ");
      //     Serial.println ("within the range specified in the Threshold Registers."); 
      //     Serial.println(" "); 
    TemperatureF0char[0] = 'T'; // Maybe send out as TPxx to show the Fault_Error byte.
    TemperatureF0char[1] = 'M';
    TemperatureF0char[2] = 'P';
    TemperatureF0char[3] = '0';
    errorTimer0Running = 1; // flag to show timeout for register clearing is running
    clearErrorReg0A = 1; // flag to show error register needs clearing
    clearErrorReg0B = 1; // flag to shoe error register needs clearing
     
    }
  } // end no fault detected
      /*
    // For human purposes, description of fault codes
    temp = 0; //temporary variable created: Purpose is to find out which error bit is set in the fault register
    temp = Fault_Error & 0x80; //Logic Anding fault register contents with 0b10000000 to detect for D7 error bit
    if(temp>0) {
      //     Serial.println("Bit D7 is Set. Is the RTD device disconnected from RTD+ or RTD-? ");
      //     Serial.println("Please verify your connection and High Fault Threshold Value");
      //     Serial.println("");
    }
    temp = Fault_Error & 0x40;
    if(temp>0) {
      //     Serial.println("Bit D6 is Set. It's Possible your RTD+ and RTD- is shorted.");
      //     Serial.println("Please verify your connection and your Low Fault Threshold Value."); 
      //     Serial.println(""); 
    }
    temp = Fault_Error & 0x20;
    if(temp>0){
      //     Serial.println("Bit D5 is Set. Vref- is greater than 0.85 * Vbias");
      //     Serial.println(""); 
    }
    temp = Fault_Error & 0x10;
    if(temp>0){
      //     Serial.println("Bit D4 is Set. Please refer to data sheet for more information");
      //     Serial.println(""); 
    }
    temp = Fault_Error &0x08;
    if(temp>0){
      //     Serial.println("Bit D3 is Set. Please refer to data sheet for more information");
      //     Serial.println(""); 
    }
    temp = Fault_Error &0x04;
    if(temp>0){
      //     Serial.println("Bit D2 is Set. Please refer to data sheet for more information");
      //     Serial.println(""); 
    }
*/
  // fault detected, clear out fault registers

if (errorTimer0Running == 1){
  if (clearErrorReg0A == 1){
    clearErrorReg0A = 0; // flag so only clear register 1 time
    // send write address to clear faults
    digitalWrite (cs0, LOW); 
    digitalWrite (cs0, LOW);
    SPI.transfer(0x80); // send out register #
    delayMicroseconds(10); // see if slight delay between write & read helps
    SPI.transfer(0b10000010); // send out data - Fault register isn't cleared automatically. Users are expected to clear it after every fault.
    digitalWrite(cs0, HIGH);
    startErrorClear0A = millis(); // start timer for clearing error register A
    errorReg0ARunning = 1; // flag to show timer running
  }
  // 700mS delay for register to self clear
    if ((errorReg0ARunning == 1) && ((currentMillis - startErrorClear0A) >= oneSecond)){
      errorReg0ARunning = 0; // timeout complete
    //delay (700); // need 700
    }
    // Register A cleared, start register B
    if ((errorReg0ARunning == 0) && (clearErrorReg0B == 1)){
      clearErrorReg0B = 0; // flag so only clear register 1 time
    // send write address to clear faults
    digitalWrite (cs0, LOW); 
    digitalWrite (cs0, LOW);
    SPI.transfer(0x80); // send out register #
    delayMicroseconds(10); // see if slight delay between write & read helps
    SPI.transfer(0b10100000); // send out data - Setting the device in autoconfiguration again. 0b10100000 or 0b11000000?
    digitalWrite(cs0, HIGH);
          startErrorClear0B = millis(); // start timer for clearing error register B
          errorReg0BRunning = 1;
    }
    if (errorReg0BRunning == 1 && ((currentMillis - startErrorClear0B) >= oneSecond)) {
   errorReg0BRunning = 0; // timeout complete
    //delay (700); // need 700
    errorTimer0Running = 0; // both error registers cleared
  }
}

Clear register, timeout, clear 2nd register timeout, set flags to allow next reading.
Next step is to have 3 sets of these running at once. Convoluted!

CrossRoads:
itoa() worked, tried it one of 7 places where it can be used.
Would ftoa() work similar?
ftoa - C++ Forum
I don't see how you limit it to one or two decimal places tho.

There is no 'ftoa()'.
Try dtostrf(). Here's a simple example of it's use:-

void setup()
{
    //  dtostrf(val, width, precision, buffer);

    Serial.begin(115200);
    double dVal = 123.4567;
    char buffer[10];
    dtostrf(dVal, 5, 2, buffer);  // Prints "123.46"
    Serial.println(buffer);

    dtostrf(dVal, 4, 2, buffer);  // Prints "123.46"
    Serial.println(buffer);

    dtostrf(dVal, 0, 3, buffer);  // Prints "123.457"
    Serial.println(buffer);

    dtostrf(dVal, 6, 2, buffer);  // Prints "123.46"
    Serial.println(buffer);

    dtostrf(dVal, 7, 2, buffer);  // Prints " 123.46"
    Serial.println(buffer);

    dVal=0.12345;
    dtostrf(dVal, 0, 2, buffer);  // Prints "0.12"
    Serial.println(buffer);
}

void loop(){}

Edit:
Why twice? :-

digitalWrite (cs0, LOW); 
digitalWrite (cs0, LOW);

Yes dtostrf() is one way to go

from the manual:

char * dtostrf(double val, char width, byte precision, char * s)

The dtostrf() function converts the double value passed in val into an ASCII representationthat will be stored under s. The caller is responsible for providing sufficient storage in s and returns the pointer to the converted string s.

Conversion is done in the format "[-]d.ddd". The minimum field width of the output string (including the possible '.' and the possible sign for negative values) is given in width, and precision determines the number of digits after the decimal sign. width is signed value, negative for left adjustment.

---> so be smart about width and precision do define the length of the buffer.

An alternative way to go for this is to go back to integer arithmetics

say v holds your voltage in V and is calculated as a float to be 3.65938471 and you want to display that with 3 digit precision such as 3.659 V, then a "trick" if this is to multiply v by 1000 and store that into an int (unsigned and/or long if necessary) and let the arduino do the maths, then use itoa() to get the ASCII representation and on the display instead of showing Volts you display milli Volts --> 3659mV that uses the same number of space on a LCD (you have the 'm' but no longer have the decimal '.')

An alternative if you find that dtostrf() is too slow is to again x1000 your value in an int, use itoa() into a buffer and then when you want to display that string instead of printing it in one go is that you print the first part of the buffer until the 3 last chars, print a dot and then print the last 3 chars.


I believe the problem is the 700mS period needed to clear the error registers in the MAX31865s

I don't get that. clearing the error register is done by setting bit 1 of the config register to 1. this is done in taking CS low, doing two SPI.transfer() and taking CS HIGH again — which you do (besides taking cs low twice)

    digitalWrite (cs0, LOW);
    SPI.transfer(0x80); // send out register #
    delayMicroseconds(10); // see if slight delay between write & read helps
    SPI.transfer(0b10000010); // send out data - 
    digitalWrite(cs0, HIGH);

how can that be 700ms??? (even with the 10ns delay that is not needed).

OldSteve,
Sending chip select twice seemed to provide the time needed for the MAX31865 to do it's thing. I got that code working a couple of years ago and have not messed with it since.
J-M-L,
To be honest, I got the conversion code from Hani, an application engineer at Atmel, for a project where I had 15 of these chips on a board, and it worked well. I've tweaked it to output degrees F instead of C, other minor things, but left the basic conversion and error clearing alone. Looking at page 12 of the datasheet, looks like 500-600uS, not mS, are all that is needed. I can play with changing those numbers, see if I can detect any difference in the outputs from the chip.

looks like 500-600uS, not mS, are all that is needed.

Yes that makes much more sense.

how do you run them? one conversion at a time or Continuous conversion?

I run one at a time, 3 chips in a row. Filling out the blink without delay error clearing and itoa() for the other floats I send out now, see how that all works.

Ok may be you had also a memory limit error with all the Strings.

Running one conversion at a time is about 3 time slower than continuous mode so you could basically get the 3 in the time it takes you to read one today if you were to move to that mode.

No problems with memory, 16K SRAM can hold a lot.
Got it all working now, used dotstrf() as suggested.

float v2 = analogRead (v2Pin) * (5 / 1023.) * 4.0432; // check voltage divider nominal level 18V
//Serial.println("");
Serial.print (", v2 = ");
Serial.print (v2);
v2char[0] = '0'; // clear out array to start
v2char[1] = '0';
v2char[2] = '0';

v2char[3] = '0';

v2char[4] = '0';

if (v2 < 10.0){
      dtostrf (v2, 4, 2, &v2char [1]);  // number, width, decimal places, buffer
}
else{
      dtostrf (v2, 5, 2, &v2char [0]);  // number, width, decimal places, buffer
}

Puts values like 12.48 and 09.43 into the array for me, just copied this 7 times for the 7 different values I send out.
Less messy than itoa() and trying to manipulate the decimal point in the array.

A few suggestions


In general I don't trust the pre-processor to do the right constant calculation for me. it's too easy to forget a decimal point or arrange math in a way you would loose precision so for float v2 = analogRead (v2Pin) * (5 / 1023.) * 4.0432; // check voltage divider nominal level 18V I actually would use a calculator and calculate myself what (5 / 1023.) * 4.0432 is worth and use that like

#define ADJUST_VALUE 0.019761485826002 // (5 / 1023.) * 4.0432
...
float v2 = analogRead (v2Pin) * ADJUST_VALUE; // check voltage divider nominal level 18V

In your case that's fine the way you did it and will lead to the right behavior though. so feel free to keep it.


  v2char[0] = '0'; // clear out array to start
  v2char[1] = '0';
  v2char[2] = '0';
  v2char[3] = '0';
  v2char[4] = '0';

when I see this obviously I'm thinking of a for loop.

  for(int i=0;i<5;i++) v2char[i] = '0';

BUT then if you think even more globally about what you want to do, the next thing your code does is

if (v2 < 10.0)  dtostrf (v2, 4, 2, &v2char [1]);  // number, width, decimal places, buffer
else dtostrf (v2, 5, 2, &v2char [0]);  // number, width, decimal places, buffer

The dtostrf() function converts the double value passed in val into an ASCII representation that will overwrite most of your v2char you just set --> So there is no reason to set those '0' characters there by default.

You know that v2 is the result of an analogRead() so a positive number between 0 and 1023 and that you multiplied by a positive constant that is less than 1 - actually less than 0.02 so you know that v2 is between 0 and 20,46. that's lots of good information for code optimization as you definitely know you won't have more than 2 trailing digits (since the first integer part is at max 20) and as you want 2 digits after the decimal point, you won't have more than 5 chars total.

NOTE that it means that hopefully you declared v2char[6] (at least) and not v2char[5] otherwise the dtostrf() call will overwrite memory after your array with the training '\0' character it adds

Then your code calls either dtostrf (v2, [color=red]4[/color], [color=blue]2[/color],...) or dtostrf (v2, [color=red]5[/color], [color=blue]2[/color], ...)

What you know:
2 is the number of digits you want after the decimal point always.
dtostr() will add a training null char into your array and you are doing some math to see where you want to start writing in memory.

--> but you know that your array will ends with a dot, two digits and an '\0' after calling dtostrf()

The only thing dtostrf() won't do for you is put a leading '0' (seems this is what you want). dtostrf()
will put a leading space instead of a '0' if your value is less than 10. that is if v2 is 2.6753 your char array will be " 2.67" with a leading space that looks good because you asked a minimum width of 5. and if it's above 10 then that space will have the right char.

--> so if you prefer a leading '0' rather than a space (personal preference but I find this annoying because it's not a normal mathematical notation) then the only thing you need to do after the call dtostrf() is to set the first char to '0' if it is a space. so your code can become

float v2 = analogRead (v2Pin) * (5 / 1023.) * 4.0432; // check voltage divider nominal level 18V
dtostrf (v2, 5, 2, v2char);  // number, width, decimal places, buffer
if (v2char[0] == ' ') v2char[0] = '0'; // put a leading '0' instead of a space if needed

final note, if you were to use dtostrf (v2, [b][color=red]-5[/color][/b], 2, v2char); then your string would be right aligned with a TRAILING space if needed. so 2.3456 would be represented as "2.34 " and 12.3456 would be represented by "12.34" with no leading space.

hope this helps

If it were me, I would do it either like this

  int v2cent = (int)((v2*100.0)+0.5);
  sprintf(v2char, "%02d.%02d", (v2cent/100), (v2cent%100));

or else this

  int v2cent = (int)((v2*100.0)+0.5);
  v2char[0] = (char)('0'+(v2cent/1000)); 
  v2char[1] = (char)('0'+((v2cent/100)%10));
  v2char[2] = '.';
  v2char[3] = (char)('0'+((v2cent/10)%10));
  v2char[4] = (char)('0'+(v2cent%10));
  v2char[5] = 0;

Usual warning: untested code, yadda yadda...