Data String

Hi everyone,

I have made an altitude data logger with an Arduino Nano 5v 16MHz, and it currently cycles at 20Hz logging to an SD card. The write command to the SD card takes some time so I had an idea of rather than writing 5 separate pieces of information, put them all into a CSV type string and write to it once.

But I am unsure if the syntax in order to create such a string from known variables. I am getting an error about invalid conversion from int to char.

 if(id<samples && active==1)
  {
  File logFile = SD.open("TEST18.csv", FILE_WRITE);
 if(logFile)
{
  
  
  String dataString = String(id) + ", ";
  float time = (millis() - starttime)/1000;
  pressure = bmp085GetPressure(bmp085ReadUP());
  altitude = (float)44330 * (1 - pow(((float) pressure/p0), 0.190295));
  
  if (id==0 && active==1)
  {
   startheight = altitude;
  }
  
  float a = altitude - startheight;
  x=x+k*(a-x);
  
  logFile.print(dataString);            // Can a string be created before and only 1 write operation carried out? 
  logFile.print(time);
  logFile.print(", ");
  logFile.print(a);
  logFile.print(", ");
  logFile.println(x);
  logFile.close();

  //Serial.println(x);

}

id++;
  }

Any help would be much appreciated, thanks :slight_smile:

jaddion82052:
Any help would be much appreciated, thanks :slight_smile:

...no need to build your String, merely write each of the CSV elements sequentially....

I applaud your ingenuity in solving your timing issue.

The usual advice is to not use the String class on small-memory processors like the Arduino Nano. Use c strings instead.

Reasoning based on snippets of a program is really just a guessing game. Please provide your complete program.

"...an error about invalid conversion from int to char" is not as helpful as you may think. It would be better to properly post all error messages. Somebody with 46 posts should know this.

itoa() ?

The write command to the SD card takes some time so I had an idea of rather than writing 5 separate pieces of information, put them all into a CSV type string and write to it once.

@BulldowLowell correctly states

...no need to build your String, merely write each of the CSV elements sequentially....

The logfile.print commands are buffered in a 512 byte buffer, and the values are actually physically written to the card with logfile.close. Writing to the buffer is very fast, and whether the values get placed in the buffer one at a time or in bulk is not related to the actual physical write time.

Oops. Delta G beat me to it.

// Sample rate 20.0 Hz

#include <Wire.h>
#include <SD.h>
#define BMP085_ADDRESS 0x77  // I2C address of BMP085

const unsigned char OSS = 0;  // Oversampling Setting

// Calibration values
int ac1;
int ac2; 
int ac3; 
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1; 
int b2;
int mb;
int mc;
int md;
int buttonpin = 7;
int CS_pin = 10;
int led_pin = 5;
int id = 0;
float x=0;
float k=0.4;
float a = 0;
int active = 0;
float starttime, endtime, timetaken, samplerate, time1, time2;
float startheight;
float samples = 300;

// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5; 

short temperature;
long pressure;

// Use these for altitude conversions
const float p0 = 101325;     // Pressure at sea level (Pa)
float altitude;

void setup()
{

  Serial.begin(9600);
  Wire.begin();
  bmp085Calibration();
  Serial.println("Initializing card");
  pinMode(CS_pin, OUTPUT);
  pinMode(led_pin, OUTPUT);
  pinMode(buttonpin, INPUT);
  
  if(!SD.begin(CS_pin))
  {
    Serial.println("Card Failed");
    return;
  }
  Serial.println("Card Setup Ready");
  
  File logFile = SD.open("TEST18.csv", FILE_WRITE);
 if(logFile)
 {
  String header = "ID, Time (s), Altitude (m), X (m)";
  logFile.println(header); 
  logFile.close();
 }
}


void loop()
{
  int buttonstate = digitalRead(buttonpin);
  
  if (buttonstate == 1 && active == 0 && id < samples)
  {
    active = 1;
    digitalWrite(led_pin, HIGH);
    starttime = millis();
  }
  
  if (id==samples && active==1)
  {
    endtime = millis();
    timetaken = endtime - starttime;
    samplerate = samples/(timetaken/1000);
    Serial.println(samplerate);
    digitalWrite(led_pin, LOW);
    active = 0;
    id = id + 1;
  }
  
  if(id<samples && active==1)
  {
  File logFile = SD.open("TEST18.csv", FILE_WRITE);
 if(logFile)
{
  

  String dataString = String(id) + ", ";
  float time = (millis() - starttime)/1000;
  pressure = bmp085GetPressure(bmp085ReadUP());
  altitude = (float)44330 * (1 - pow(((float) pressure/p0), 0.190295));
  
  if (id==0 && active==1)
  {
   startheight = altitude;
  }
  
  float a = altitude - startheight;
  x=x+k*(a-x);
  
  
  logFile.print(dataString);            // Can a string be created before and only 1 write operation carried out? 
  logFile.print(time);
  logFile.print(", ");
  logFile.print(a);
  logFile.print(", ");
  logFile.println(x);
  logFile.close();

  //Serial.println(x);

}

id++;
  }
}


// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration()
{
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}

// Calculate temperature given ut.
// Value returned will be in units of 0.1 deg C
short bmp085GetTemperature(unsigned int ut)
{
  long x1, x2;
  
  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;

  return ((b5 + 8)>>4);  
}

// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
  
  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;
  
  // Calculate B4
  x1 = (ac3 * b6)>>13;
  x2 = (b1 * ((b6 * b6)>>12))>>16;
  x3 = ((x1 + x2) + 2)>>2;
  b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
  
  b7 = ((unsigned long)(up - b3) * (50000>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;
    
  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;
  
  return p;
}

// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
  unsigned char data;
  
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available())
    ;
    
  return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;
  
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  
  return (int) msb<<8 | lsb;
}

// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
  unsigned int ut;
  
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
  
  // Wait at least 4.5ms
  delay(5);
  
  // Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Read the uncompensated pressure value
unsigned long bmp085ReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
  
  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();
  
  // Wait for conversion, delay time dependent on OSS
  delay(2 + (3<<OSS));
  
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF6);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 3);
  
  // Wait for data to become available
  while(Wire.available() < 3)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  xlsb = Wire.read();
  
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
  
  return up;
}

There is all my code out of interest.

Ah ok, well thanks for the advice, I guess 20Hz will be the limit for this unless I move to a different data storage solution. (Which I don't really want to), thank you.

jaddion82052:

// Sample rate 20.0 Hz

#include <Wire.h>
#include <SD.h>
#define BMP085_ADDRESS 0x77  // I2C address of BMP085

const unsigned char OSS = 0;  // Oversampling Setting

// Calibration values
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;
int buttonpin = 7;
int CS_pin = 10;
int led_pin = 5;
int id = 0;
float x=0;
float k=0.4;
float a = 0;
int active = 0;
float starttime, endtime, timetaken, samplerate, time1, time2;
float startheight;
float samples = 300;

// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5;

short temperature;
long pressure;

// Use these for altitude conversions
const float p0 = 101325;    // Pressure at sea level (Pa)
float altitude;

void setup()
{

Serial.begin(9600);
  Wire.begin();
  bmp085Calibration();
  Serial.println("Initializing card");
  pinMode(CS_pin, OUTPUT);
  pinMode(led_pin, OUTPUT);
  pinMode(buttonpin, INPUT);
 
  if(!SD.begin(CS_pin))
  {
    Serial.println("Card Failed");
    return;
  }
  Serial.println("Card Setup Ready");
 
  File logFile = SD.open("TEST18.csv", FILE_WRITE);
if(logFile)
{
  String header = "ID, Time (s), Altitude (m), X (m)";
  logFile.println(header);
  logFile.close();
}
}

void loop()
{
  int buttonstate = digitalRead(buttonpin);
 
  if (buttonstate == 1 && active == 0 && id < samples)
  {
    active = 1;
    digitalWrite(led_pin, HIGH);
    starttime = millis();
  }
 
  if (id==samples && active==1)
  {
    endtime = millis();
    timetaken = endtime - starttime;
    samplerate = samples/(timetaken/1000);
    Serial.println(samplerate);
    digitalWrite(led_pin, LOW);
    active = 0;
    id = id + 1;
  }
 
  if(id<samples && active==1)
  {
  File logFile = SD.open("TEST18.csv", FILE_WRITE);
if(logFile)
{

String dataString = String(id) + ", ";
  float time = (millis() - starttime)/1000;
  pressure = bmp085GetPressure(bmp085ReadUP());
  altitude = (float)44330 * (1 - pow(((float) pressure/p0), 0.190295));
 
  if (id==0 && active==1)
  {
  startheight = altitude;
  }
 
  float a = altitude - startheight;
  x=x+k*(a-x);
 
 
  logFile.print(dataString);            // Can a string be created before and only 1 write operation carried out?
  logFile.print(time);
  logFile.print(", ");
  logFile.print(a);
  logFile.print(", ");
  logFile.println(x);
  logFile.close();

//Serial.println(x);

}

id++;
  }
}

// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration()
{
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}

// Calculate temperature given ut.
// Value returned will be in units of 0.1 deg C
short bmp085GetTemperature(unsigned int ut)
{
  long x1, x2;
 
  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;

return ((b5 + 8)>>4); 
}

// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
 
  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;
 
  // Calculate B4
  x1 = (ac3 * b6)>>13;
  x2 = (b1 * ((b6 * b6)>>12))>>16;
  x3 = ((x1 + x2) + 2)>>2;
  b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
 
  b7 = ((unsigned long)(up - b3) * (50000>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;
   
  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;
 
  return p;
}

// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
  unsigned char data;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available())
    ;
   
  return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2)
    ;
  msb = Wire.read();
  lsb = Wire.read();
 
  return (int) msb<<8 | lsb;
}

// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
  unsigned int ut;
 
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
 
  // Wait at least 4.5ms
  delay(5);
 
  // Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Read the uncompensated pressure value
unsigned long bmp085ReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
 
  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();
 
  // Wait for conversion, delay time dependent on OSS
  delay(2 + (3<<OSS));
 
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF6);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 3);
 
  // Wait for data to become available
  while(Wire.available() < 3)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  xlsb = Wire.read();
 
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
 
  return up;
}




There is all my code out of interest. 

Ah ok, well thanks for the advice, I guess 20Hz will be the limit for this unless I move to a different data storage solution. (Which I don't really want to), thank you.

I wonder if the sensor readings aren't affecting that as well... if you write constants to the SD card, how fast can you write? I'm not experienced with that sensor, but I am with the BME280 and it is poky (humidity).

floating point math is slow, too. I wonder if too if you reduced some of that and just stored raw data.

You can try a few things to speed that along.