C Language: Create single string from several different variable types
John Kauffman            
            
                Posts: 653            
            
                    I'm trying to do what must be a common objective: save experimental data to SD in CSV format to use in Excel. But I can't find all needed code in tutorials or help examples or StackOverflow
My code list below is long but that is because of careful comments and long variable names.
Problems are at ???, mainly the string length calculation, how to prepare data, how to append each type of datum to string.
                            
I'm getting several data from each trial.
Data are mix of int, float, char and string.
My approach is to get all values into a string (with rounding, pad and commas) then open SD file and append the single string.
(repeat for each trial)
Examples in Learn C tutorial, from what I see, only save a hard-coded string.Data are mix of int, float, char and string.
My approach is to get all values into a string (with rounding, pad and commas) then open SD file and append the single string.
(repeat for each trial)
My code list below is long but that is because of careful comments and long variable names.
Problems are at ???, mainly the string length calculation, how to prepare data, how to append each type of datum to string.
/*  StoreDataStringver22.c
Goal:
Concatenate experimental data to string as CSV
Then append to SD file and transfer to Excel
Notes:
  Data types = int, float, char, string
  Goal is the string "dataFullRecord" w/commas
  Data are each fixed length      
Unsolved issues
  round/truncate ints and floats to defined length
  Convert each type to string
  Concatenate
*/
#include "simpletools.h"                      // Include simple tools
int main()                                    // Main function
{
// Vars to hold data for each trial until written
  int datum1_Int;         // 4 digits always (pad as needed)
  float datum2_Float;     // 5 digits always (need pad) +1 for DP?
  char datum3_Char;       // 1 character always
  char datum4_String[3];  // 3 character always
  // might need constant for Comma or Return
// String to hold all data of one trial
  // ??? Size in bytes of total string
    // int            4
    // float         5  +1 for DP?
    // char         1
    // String[3]   3 plus +1 for terminating 0?
    // Commas  3
    // Return added to string or auto by SD append?
  char dataFullRecord[17]; //no Return character
  
// Get data from experiment - dummy values here
[indent]  datum1_Int = 12; // round/pad to 4 digits
  datum2_Float = 9.876543; // round/pad to 5 digits 
  datum3_Char = "A"; //
  datum4_String[4] = "June"; //always 4 char ?+1 for terminal 0
[/indent]
// Build string
    // how to concatenate?
    // how to round/pad int & float to fixed length?
    // how to convert int & float to string?
    // best way to include commas?
    // need Return character?
  // Attempt #6 error: doesn't know function strCopy 
    strCopy(dataFullRecord[](), cstr(round(datum1_int,3))
    strCopy(dataFullRecord[](), ",")
    strCopy(dataFullRecord[](), cstr(round(datum2_float,4))
    strCopy(dataFullRecord[](), ",")
    strCopy(dataFullRecord[](), datum3_char)
    strCopy(dataFullRecord[](), ",")
    strCopy(dataFullRecord[](), datum4_string[])
}
                
                            
Comments
An example using PropWare to do this would like a bit like this:
// Some includes.... using namespace PropWare; int main() { const SD driver; FatFS filesystem(driver); filesystem.mount(); FatFileWriter writer(filesystem, "myfile.csv"); writer.open(); Printer filePrinter(writer, false); // The second parameter controls "cooked" mode. When true, \r is auto-inserted before any \n character // Vars to hold data for each trial until written int datum1_Int; // 4 digits always (pad as needed) float datum2_Float; // 5 digits always (need pad) +1 for DP? char datum3_Char; // 1 character always char datum4_String[3]; // 3 character always // If datum4_String is null-terminated, like a standard C-string, you could use the %s instead of three %c filePrinter.printf("%04d,%05f,%c,%c%c%c\n", datum1_Int, datum2_Float, datum3_Char, datum4_String[0], datum4_String[1], datum4_String[2]); return 0; }But if you prefer to stick with what is already available in SimpleIDE and its included libraries, then your best bet is either sprint or fprintf. sprint is smaller since it is part of the "Simple" libraries, but its a 2-step process: create the string, write the string. fprintf will be much larger (because it is part of the C standard library) but easier, because you can write directly to the SD card.
Easier still would be to use sprintf to create the entire record:
Calling strlen() on the result would tell you the length of it.
There is a function called itoa() which will convert from int to ascii, but there's no corresponding ftoa() for floats. For that you'd need to copy an implementation from online somwhere or roll your own. Often it's enough to do something like this:
This won't work if the float it outside of the displayable range of integers - floats can represent very large numbers - but for typical uses it works ok.
Whoops! I don't know why I said "sscan" in my post :P fixing now..
I will switch to sprintf to study this evening as below. It looks like the conversion I was looking for is done easily by the formating codes in the second argument.
To add another value named datum4_string which is a string[3], what would be formater code and syntax of additional argument?
sprintf( dataFullRecord, "%04d,%f,%c", datum1_Int, datum2_Float, datum3_Char );
The number of formatting characters supported varies by implementation, but you're asking for pretty basic stuff here so you'd have access to these anywhere (accept for the special functions that end in "i" provided by Simple, such as printi and sscani). Check out this page for detailed descriptions and examples for the various formatting characters:
http://www.cplusplus.com/reference/cstdio/printf/
And now that you understand they all function the same, I'll show you again the example for PropWare's Printer.printf, which you can use for sprintf as well:
filePrinter.printf("%04d,%05f,%c,%c%c%c\n", datum1_Int, datum2_Float, datum3_Char, datum4_String[0], datum4_String[1], datum4_String[2]);So to convert that to sprintf would be
I did it this way because I had written the convert & write-to-file code for saving ping readings vs angle as I had a robot map a space. I then just copied the snippet for a temperature reading device, and then added a couple of devices that read temperature, pressure, and humidity. As I added data that I wanted to save, I just recopied the snippet.
This is the code I use for saving the T, P, H data. ( I open & close the file after each record since I am taking reading every 10 minutes, and don't want to lose data if the batteries die.)
The numbers I am converting are all floats with 2 decimal places, so I could have used a for() loop, but I have also used that method for chars and ints. If using non floats, I will usually use "sprinti" (with the Simple Libraries) since that saves about 3 to 4k bytes. In this case there was a lot of floating point math, so using the integer/char only formatting function wasn't worth it.
atemp, apres, and ahum are all declared as chars with length of 12, e.g. char atemp[12];
Tom
FILE* fp = fopen(filen, "a"); // The next statements change a number to text and save it in a format excel can use len1 = sprint(atemp, "%.2f", T); // convert float to string fwrite(&atemp, len1, 1, fp); // Write degrees to SD card, first column of record fwrite(",", 1, 1, fp); // Write comma separates columns len2 = sprint(apres, "%.2f", P); fwrite(&apres, len2, 1, fp); // Write pressure to SD card column 2 of record fwrite(",", 1, 1, fp); // Write comma separates columns len3 = sprint(ahum, "%.2f", RHt); fwrite(&ahum, len3, 1, fp); // Write humidity to SD card column 3 of record fwrite("\n", 1, 1, fp); // Write newline, next value will be in new row fclose(fp); // Close file3 out of 4 data look great when I print the buffer string named dataFullRecord.
(One change I made for the variable datum3_char: Changing the assigned value from "A" to 65.)
But the datum3_string (="Jun") always prints odd characters. Do I have to add the terminating zero to the string I am making?
Here are central lines:
// Create string and print sprintf(dataFullRecord , "%04d,%05f,%c,%c%c%c", datum1_Int, datum2_Float, datum3_Char, datum4_String[0], datum4_String[1], datum4_String[2]); print("%s\n",dataFullRecord); // output result: 0012,9.876543,A, > // last datum not correctHere is the full code:
/* StoreDataStringver2. Goal: Concatenate to 1 string values of differetn data types */ #include "simpletools.h" // Include simple tools #include "simpletext.h" #include "simpletools.h" // Include simple tools int main() // Main function { // Vars to hold data for each trial until written int datum1_Int; // 4 digits always (pad as needed) float datum2_Float; // 5 digits always (need pad) +1 for DP? char datum3_Char; // 1 character always char datum4_String[3]; // 3 character always // String to hold all data of one trial char dataFullRecord[17]; // int stringLength; // Get data from experiment - dummy values here datum1_Int = 12; // round/pad to 4 digits datum2_Float = 9.876543; // round/pad to 5 digits datum3_Char = 65; // = A. Doesn't work with "A" datum4_String[3] = "Jun"; //always 3 char ?+1 for terminal 0 // Create string and print sprintf(dataFullRecord , "%04d,%05f,%c,%c%c%c", datum1_Int, datum2_Float, datum3_Char, datum4_String[0], datum4_String[1], datum4_String[2]); print("%s\n",dataFullRecord); // output result: 0012,9.876543,A, > // last datum not correct // alternate Create string and print sprintf(dataFullRecord , "%04d,%05f,%c,%0.3s", datum1_Int, datum2_Float, datum3_Char, datum4_String); print("%s\n",dataFullRecord); // output result: flash of text then a question mark }Tom
Is that a C thing? I init strings like that all the time - though I generally let the compiler determine the length for me
The difference is that in John Kauffman's code, he is declaring the variable and then assigning to it later, while your are declaring and initializing it at the same time. His code would have worked fine in C++, and yours would have worked fine in C - however, your way is definitely better because it lets the compiler figure out the length.
Now it is on to appending the string to SD, once for each trial.
But why is string length less than observed?
// Create string and print stringLength = sprintf(dataFullRecord , "%4d,%3.2f,%c,%s", datum1_Int, datum2_Float, datum3_Char, datum4_String); print("%s\nLength = %d\n\n\n",dataFullRecord,stringLength);The output is But I count length = 15, including the 2 leading spaces.
In my first experiments writing to the card I only get the whole string saved and read by using stringLength+4
" 12.9.88,A,Jun"
char *a; char *b; a = "Hey"; b = "World"; printf ("%s %s\n", a, b); a = "No"; b = "Way"; printf ("%s %s\n", a, b);There's no copying of strings going on there, just moving pointers around. a and b are pointers, so what changes is the value they hold, which is an address in this case.That does sound like a bug, although it's not there in the most recent PropGCC, so it's been fixed since the SimpleIDE release (which was quite some time ago
As Jason suggested, strlen() on the string will work.
#include <stdio.h> void main(void) { char dataFullRecord[100]; int datum1_Int = 12; float datum2_Float = 9.88; char datum3_Char = 'A'; char datum4_String[4] = "Jun"; int stringLength; // Create string and print stringLength = sprintf(dataFullRecord , "%4d,%3.2f,%c,%s", datum1_Int, datum2_Float, datum3_Char, datum4_String); printf("%s\nLength = %d\n\n\n",dataFullRecord,stringLength); }And here is the output:http://forums.parallax.com/discussion/160431/propgcc-now-in-the-parallax-github/p1