- Why “Strings” are bad for Arduino?
- Which is the most efficient and fastest solution to read and store the data from Accelerometer and GPS?
Stings are evil for Arduino
An Uno or other ATmega328-based board only has 2048 bytes SRAM.
The SRAM is composed by three parts:
- The Static
- The Heap
- The Stack
The Heap is where, randomly, a C++ string information is allocated.
If I add an information to my C++ String the system copies itself and deletes the old copy leaving a hole in the heap, that grows up with the cycles.
When I wrote my first sketch for reading and writing the information from an MPU6050 and stored on a SD (whithout connecting to a GPS) I noticed that the sampling time was not constant.
Thanks to the feedback obtained in this question on StackOverflow, I understood that the problem was my excessive use of strings, and now i understand why.
So I started to dig into this issue.
This was one of the first version of the code:
/******************************************************************************
CSV_Logger_TinyGPSPlus.ino
Log GPS data to a CSV file on a uSD card
By Jim Lindblom @ SparkFun Electronics
February 9, 2016
https://github.com/sparkfun/GPS_Shield
This example uses SoftwareSerial to communicate with the GPS module on
pins 8 and 9, then communicates over SPI to log that data to a uSD card.
It uses the TinyGPS++ library to parse the NMEA strings sent by the GPS module,
and prints interesting GPS information - comma separated - to a newly created
file on the SD card.
Resources:
TinyGPS++ Library - https://github.com/mikalhart/TinyGPSPlus/releases
SD Library (Built-in)
SoftwareSerial Library (Built-in)
Development/hardware environment specifics:
Arduino IDE 1.6.7
GPS Logger Shield v2.0 - Make sure the UART switch is set to SW-UART
Arduino Uno, RedBoard, Pro, Mega, etc.
******************************************************************************/
#include <SPI.h>
#include <SD.h>
#include <TinyGPS++.h>
#include<Wire.h>
#define ARDUINO_USD_CS 10 // uSD card CS pin (pin 10 on SparkFun GPS Logger Shield)
/////////////////////////
// Log File Defintions //
/////////////////////////
// Keep in mind, the SD library has max file name lengths of 8.3 - 8 char prefix,
// and a 3 char suffix.
// Our log files are called "gpslogXX.csv, so "gpslog99.csv" is our max file.
#define LOG_FILE_PREFIX "gpslog" // Name of the log file.
#define MAX_LOG_FILES 100 // Number of log files that can be made
#define LOG_FILE_SUFFIX "csv" // Suffix of the log file
char logFileName[13]; // Char string to store the log file name
// Data to be logged:
#define LOG_COLUMN_COUNT 15
char * log_col_names[LOG_COLUMN_COUNT] = {
"longitude", "latitude", "altitude", "speed", "course", "date", "time", "satellites","Acc.X","Acc.Y","Acc.Z","Gy.X","Gy.Y","Gy.Z","Temp"
}; // log_col_names is printed at the top of the file.
//////////////////////
// Log Rate Control //
//////////////////////
#define LOG_RATE 1000 // Log every 1 seconds
unsigned long lastLog = 0; // Global var to keep of last time we logged
/////////////////////////
// TinyGPS Definitions //
/////////////////////////
TinyGPSPlus tinyGPS; // tinyGPSPlus object to be used throughout
#define GPS_BAUD 9600 // GPS module's default baud rate
/////////////////////////////////
// GPS Serial Port Definitions //
/////////////////////////////////
// If you're using an Arduino Uno, Mega, RedBoard, or any board that uses the
// 0/1 UART for programming/Serial monitor-ing, use SoftwareSerial:
#include <SoftwareSerial.h>
#define ARDUINO_GPS_RX 9 // GPS TX, Arduino RX pin
#define ARDUINO_GPS_TX 8 // GPS RX, Arduino TX pin
SoftwareSerial ssGPS(ARDUINO_GPS_TX, ARDUINO_GPS_RX); // Create a SoftwareSerial
// Set gpsPort to either ssGPS if using SoftwareSerial or Serial1 if using an
// Arduino with a dedicated hardware serial port
#define gpsPort ssGPS // Alternatively, use Serial1 on the Leonardo
// Define the serial monitor port. On the Uno, Mega, and Leonardo this is 'Serial'
// on other boards this may be 'SerialUSB'
#define SerialMonitor Serial
const int MPU=0x68; // I2C address of the MPU-6050
int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
void setup()
{
Wire.begin();
Wire.beginTransmission(MPU);
Wire.write(0x6B); // PWR_MGMT_1 register
Wire.write(0); // set to zero (wakes up the MPU-6050)
Wire.endTransmission(true);
SerialMonitor.begin(9600);
gpsPort.begin(GPS_BAUD);
SerialMonitor.println("Setting up SD card.");
// see if the card is present and can be initialized:
if (!SD.begin(ARDUINO_USD_CS))
{
SerialMonitor.println("Error initializing SD card.");
}
updateFileName(); // Each time we start, create a new file, increment the number
printHeader(); // Print a header at the top of the new file
}
void loop()
{
Wire.beginTransmission(MPU);
Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(MPU,14,true); // request a total of 14 registers
AcX=Wire.read()<<8|Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
AcY=Wire.read()<<8|Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
AcZ=Wire.read()<<8|Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
Tmp=Wire.read()<<8|Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
GyX=Wire.read()<<8|Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
GyY=Wire.read()<<8|Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
GyZ=Wire.read()<<8|Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
{
if ((lastLog + LOG_RATE) <= millis())
{ // If it's been LOG_RATE milliseconds since the last log:
if (tinyGPS.location.isUpdated()) // If the GPS data is vaild
{
if (logGPSData()) // Log the GPS data
{
SerialMonitor.println("GPS logged."); // Print a debug message
lastLog = millis(); // Update the lastLog variable
}
else // If we failed to log GPS
{ // Print an error, don't update lastLog
SerialMonitor.println("Failed to log new GPS data.");
}
}
else // If GPS data isn't valid
{
// Print a debug message. Maybe we don't have enough satellites yet.
SerialMonitor.print("No GPS data. Sats: ");
SerialMonitor.println(tinyGPS.satellites.value());
}
}
// If we're not logging, continue to "feed" the tinyGPS object:
while (gpsPort.available())
tinyGPS.encode(gpsPort.read());
}
}
byte logGPSData()
{
File logFile = SD.open(logFileName, FILE_WRITE); // Open the log file
if (logFile)
{ // Print longitude, latitude, altitude (in feet), speed (in mph), course
// in (degrees), date, time, and number of satellites.
logFile.print(tinyGPS.location.lng(), 6);
logFile.print(',');
logFile.print(tinyGPS.location.lat(), 6);
logFile.print(',');
logFile.print(tinyGPS.altitude.feet(), 1);
logFile.print(',');
logFile.print(tinyGPS.speed.mph(), 1);
logFile.print(',');
logFile.print(tinyGPS.course.deg(), 1);
logFile.print(',');
logFile.print(tinyGPS.date.value());
logFile.print(',');
logFile.print(tinyGPS.time.value());
logFile.print(',');
logFile.print(tinyGPS.satellites.value());
logFile.print(AcX);
logFile.print(',');
logFile.print(AcY);
logFile.print(',');
logFile.print(AcZ);
logFile.print(',');
logFile.print(GyX);
logFile.print(',');
logFile.print(GyY);
logFile.print(',');
logFile.print(GyZ);
logFile.print(',');
logFile.print(Tmp);
logFile.println();
logFile.close();
return 1; // Return success
}
return 0; // If we failed to open the file, return fail
}
// printHeader() - prints our eight column names to the top of our log file
void printHeader()
{
File logFile = SD.open(logFileName, FILE_WRITE); // Open the log file
if (logFile) // If the log file opened, print our column names to the file
{
int i = 0;
for (; i < LOG_COLUMN_COUNT; i++)
{
logFile.print(log_col_names[i]);
if (i < LOG_COLUMN_COUNT - 1) // If it's anything but the last column
logFile.print(','); // print a comma
else // If it's the last column
logFile.println(); // print a new line
}
logFile.close(); // close the file
}
}
// updateFileName() - Looks through the log files already present on a card,
// and creates a new file with an incremented file index.
void updateFileName()
{
int i = 0;
for (; i < MAX_LOG_FILES; i++)
{
memset(logFileName, 0, strlen(logFileName)); // Clear logFileName string
// Set logFileName to "gpslogXX.csv":
sprintf(logFileName, "%s%d.%s", LOG_FILE_PREFIX, i, LOG_FILE_SUFFIX);
if (!SD.exists(logFileName)) // If a file doesn't exist
{
break; // Break out of this loop. We found our index
}
else // Otherwise:
{
SerialMonitor.print(logFileName);
SerialMonitor.println(" exists"); // Print a debug statement
}
}
SerialMonitor.print("File name: ");
SerialMonitor.println(logFileName); // Debug print the file name
}
Then I started:
- Studying how Arduino Memory works
- Breaking the problem, focusing only to learn how to read and write on a SD the accelerometer information (the device is not connected to the PC)
The following code is my latest version:
void Read_Write()
// function that reads the MPU and writes the data to the SD.
{
// Local variables:
int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ; // Variables read from MPU6050
// Read data:
Wire.beginTransmission(MPU);
Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(MPU,14,true); // request a total of 14 registers
AcX = Wire.read()<<8|Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
AcY = Wire.read()<<8|Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
AcZ = Wire.read()<<8|Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
GyX = Wire.read()<<8|Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
GyY = Wire.read()<<8|Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
GyZ = Wire.read()<<8|Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
// Data preparation for file saving:
String dataString = ""; // string for assembling the data to log:
// Add time tag:
dataString += String(Time0); dataString += ",";
// Append the MPU6050 data to the string:
dataString += String(AcX); dataString += ",";
dataString += String(AcY); dataString += ",";
dataString += String(AcZ); dataString += ",";
dataString += String(GyX); dataString += ",";
dataString += String(GyY); dataString += ",";
dataString += String(GyZ);
// Open the file in append mode:
File dataFile = SD.open("datalog.txt", FILE_WRITE);
// If the file is available, write to it:
if (dataFile)
{
dataFile.println(dataString);
dataFile.close();
if (Serial_plus_SD)
Serial.println(dataString);
}
// if the file does not open, pop up an error:
else
errorFW();
return;
}
I recived other suggestions such as "create a Buffer", but without a technical description and motivation I am having difficulties understand how to do it.
(I don’t want to simply copy&paste the code as i have done intitially with the first version)
I have to use C Strings instead of C++ Strings?
I have a civil engineer background, so is my first time with coding.
Thank you for your patience.