What's the most elegant and efficient way to convert a number to a char array?

Hi! I'm working on a project where data (integer or double values) are read from the sensor and stored in a char array. The reason why I'm sticking with an 8-bit array type is because this data are going to be stored in the microcontrollers flash memory (using a modified version of Optiboot). The array is 128 bytes long, and data will be separated by a comma.

So, what is the simplest and most efficient way to convert an integer or double value and store it in this 128 byte char array?

Maybe use a ‘union’

union convert
{
char myChars[4]; // these are signed so not sure this would work
double sensor;
} data;

. . .

In your sketch somewhere.

. . .
index = someNumber;
. . .

data.sensor = sensor;

for(byte X = 0; X <4; X++)
{
myArray[index++ ] = data.myChars[x ]; //myArray is your 128 bytes
myArray[index++ ] = ‘,’ ; // comma
}

just to understand your question

if your double number is 337.25

do you want to have a char array with ASCII symbols '3' '3' '7' '.' '2' '5'

or do you want to get the 4 bytes representing the number 333.25 to be stored in 4 chars?

If the OP wants the BCD then that is a different story.

You are right, need some more information.

.

Bit shifting may be a friend here?

Essentially:

  1. Declare 4 bytes. Lowest, Low, High, Highest.
  2. Assign Lowest as the long 4 byte number. (The exra 3 bytes are truncated...)
  3. Assign Low as the long 4 byte number shifted left by a byte.
  4. Assign High as the long 4 bytes number shifted left 2 bytes.
  5. Assign Highest as the long 4 bytes left 3 bytes.

This is off the top of my head...but bitshifting may be the idea to split your longs in to 4 seperate bytes.

try this, I’ve offered ASCII and BINARY option

// On the Uno and other ATMEGA based boards, this occupies 4 bytes.
// That is, the double implementation is exactly the same as the float, with no gain in precision.
// On the Arduino Due, doubles have 8-byte (64 bit) precision.
// this code is meant only for Uno and other ATMEGA based boards


double a = 156.35;
byte aBinaryRepresentation[4];
char aASCIIRespresntation[30];

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  Serial.print("a=");
  Serial.println(a);

  // aBinaryRepresentation is the address in memory of the first element of the array == &(aBinaryRepresentation[0])
  // void * memcpy ( void * destination, const void * source, size_t num );

  Serial.println("BINARY");
  memcpy(aBinaryRepresentation, &a, sizeof(a));
  for (int i = 0; i < sizeof(a); i++) {
    Serial.print("[");
    Serial.print(aBinaryRepresentation[i], HEX);
    Serial.print("]");
  }
  Serial.println("");


  // ASCII representation
  // char* dtostrf ( double val, signed char width, unsigned char prec, char * s);
  // The dtostrf() function converts the double value passed in val into an ASCII
  // representation that will be stored under s.
  // The caller is responsible for providing sufficient storage in s
  // The minimum field width of the output string (including the possible '.' and
  // the possible sign for negative values) is given in width, and prec determines
  // the number of digits after the decimal sign. width is signed value, negative for left adjustment
  dtostrf (a, 20, 10, aASCIIRespresntation);

  Serial.println("ASCII");
  for (int i = 0; i < 20; i++) {
    Serial.print("[");
    Serial.print(aASCIIRespresntation[i]);
    Serial.print("]");
  }
  Serial.println("");

}

void loop() {
  // put your main code here, to run repeatedly:

}

Sorry for being a little short in the info, it makes perfect sense in my head ;)

My idea was to make an example sketch for use with the MightyCore and the MegaCore (which both have the modified bootloader) to demonstrate the "write to flash from application" functionality. The idea was to make a vanilla datalogger that takes data (integer or float), break it down to characters and add a comma at the end:

raw data converted to chars added comma at the end 123.456 --> '1','2','3','.','4','5','6' --> '1','2','3','.','4','5','6',','

New data will be added "behind" the previous data. When the array (stored in ram) is full, it gets written to a flash page and emptied.

EDIT: I have no experience with union. Whats the difference between unions and structs? :)

A union uses the 'same' memory for different variables. example: union myUnion { byte myBytes[2]; int sensor; } data;

if you did: data.sensor = 0xBEEF then data.myBytes[0] would be 0xBE and data.myBytes[1] would be 0xEF these could be written to two 8 bit memory locations

However, these might be the other way around, you would have to check how Arduino does it. data.myBytes[0] would be 0xEF and data.myBytes[1] would be 0xBE

A structure assigns separate memory.

Note: you can put a structure in a union.

See above code for generating the ASCII representation

Just adapt the width and precision to your needs for doubles

Similar function exist for int

Thanks J-M-L! After a little modification the output looks like this:

a = 156
ASCII
156.0000000000

does there exist a similar function to dtostrf for integer values?

Hi

yes there is a similar way for int, here is again the full code with both a double example and an int example

run it and see the output. the function you are looking for is:

char *  itoa ( int value, char * str, int base );
// On the Uno and other ATMEGA based boards, a double occupies 4 bytes.
// That is, the double implementation is exactly the same as the float, with no gain in precision.
// On the Arduino Due, doubles have 8-byte (64 bit) precision.
// this code is meant only for Uno and other ATMEGA based boards
// an int is 2 bytes.


double a = 156.35;
byte aBinaryRepresentation[4];
char aASCIIRespresntation[30];

int b = 156;
byte bBinaryRepresentation[4];
char bASCIIRespresntation[30];

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  // aBinaryRepresentation is the address in memory of the first element of the array == &(aBinaryRepresentation[0])
  // void * memcpy ( void * destination, const void * source, size_t num );


  Serial.println("DOUBLE VALUE");

  Serial.print("a=");
  Serial.println(a);

  Serial.println("BINARY");
  memcpy(aBinaryRepresentation, &a, sizeof(a));
  for (int i = 0; i < sizeof(a); i++) {
    Serial.print("[");
    Serial.print(aBinaryRepresentation[i], HEX);
    Serial.print("]");
  }
  Serial.println("");

  // ASCII representation
  // char* dtostrf ( double val, signed char width, unsigned char prec, char * s);
  // The dtostrf() function converts the double value passed in val into an ASCII
  // representation that will be stored under s.
  // The caller is responsible for providing sufficient storage in s
  // The minimum field width of the output string (including the possible '.' and
  // the possible sign for negative values) is given in width, and prec determines
  // the number of digits after the decimal sign. width is signed value, negative for left adjustment
  dtostrf (a, 20, 10, aASCIIRespresntation);

  Serial.println("ASCII");
  for (int i = 0; i < 20; i++) {
    Serial.print("[");
    Serial.print(aASCIIRespresntation[i]);
    Serial.print("]");
  }
  Serial.println("");

  //-----------------------
  Serial.println("");
  Serial.println("");
  Serial.println("");


  Serial.println("INT VALUE");

  Serial.print("b=");
  Serial.println(b);

  Serial.println("BINARY");
  memcpy(bBinaryRepresentation, &b, sizeof(b));
  for (int i = 0; i < sizeof(b); i++) {
    Serial.print("[");
    Serial.print(aBinaryRepresentation[i], HEX);
    Serial.print("]");
  }
  Serial.println("");


  // char *  itoa ( int value, char * str, int base );
  // Convert integer to string (non-standard function)
  // Converts an integer value to a null-terminated string using
  // the specified base and stores the result in the array given by str parameter.
  // If base is 10 and value is negative, the resulting string is preceded
  // with a minus sign (-). With any other base, value is always considered unsigned.
  // str should be an array long enough to contain any possible
  // value: (sizeof(int)*8+1) for radix=2, i.e. 17 bytes in 16-bits platforms and 33 in 32-bits platforms.

  itoa (b, bASCIIRespresntation, 10); // convert int b into it's ASCII represention in base 10

  Serial.println("ASCII");
  for (int i = 0; i < 20; i++) {
    Serial.print("[");
    Serial.print(bASCIIRespresntation[i]);
    Serial.print("]");
  }
  Serial.println("");



}

void loop() {
  // put your main code here, to run repeatedly:

}

you will get this in the console

DOUBLE VALUE
a=156.35
BINARY
[9A][59][1C][43]
ASCII
[ ][ ][ ][ ][ ][ ][1][5][6][.][3][5][0][0][1][0][0][0][0][0]



INT VALUE
b=156
BINARY
[9A][59]
ASCII
[1][5][6][][][][][][][][][][][][][][][][][]

hope it helps

Great! Thanks a lot :D I'll rewrite this code to make it work with the Optiboot flasher library :)

Oh, while I'm at it; are there a simple way to return the number of digits in a number? With the number 123 it should return 3, and with the number 123.456 it should return 7

Probably the easiest way is to initialize the array where you generate the ASCII representation with zeros everywhere and once converted you scan for the first zero byte

Challenge with double is that they are not very precisely represented on 4 bytes. Take my example above

a=156.35
And in ascii 
[1][5][6][.][3][5][0][0][b][1][/b]

Have you spotted the extra 1?

I'm not sure why you want to store ASCII and not the binary representation which is shorter and saves memory.

What is this: 'most elegant', 'simple way' stuff? ;)

I agree with storing the binary number, use binary to get ASCII later.

Use union or similar methods to store the binary. Only 4 bytes!

Why store ASCII?

.

The reason why I want to store the data in ASCII is that I'm planning on implementing a "memory dumper" that you run from a PC and get the stored data from flash so you can manipulate it programs like notepad or Excel. If everything is represented in ASCII characters it makes it really easy to copy the dumped data and continue to work on it. Of course binary data is more space efficient, but we're dealing with the spare flash memory here. On an ATmega128 there still might be ~100kB free space to store your content for a typical IO application ;)

Please have a look at the example I created a few weeks ago. It's a simple sketch that's collecting data from the serial monitor and stores it to the flash memory, where you'll be able to read it out in the same application. This is just characters, but I wanted to extend this and make it work for integers and double values too. Stored ASCII characters makes it easy to read out from the serial monitor, but if it's possible to store binary data separated by a comma (or a tab, or whatever) and read it out to the serial monitor line this I'd love to implement a better and more space saving solution :)

13.00,340.45,23145.24,234.02

Why can the data not be stored as its native binary form and then you convert to ASCII on the fly as Serial is used?

RAW Data (binary/native protocol) ----> ASCII Conversion (dynamic SRAM/Buffer) ----> Serial Output

if you are going to write a "memory dumper", just have that handle the conversion on the fly anyway...