TinyCircuits

9 Axis IMU + MicroSD

9 Axis IMU + MicroSD
« on: February 18, 2018, 11:14:05 PM »
Hello. Can someone please give me an example of code to help me understand how to get the data taken by the sample 9 axis IMU code (from the TinyCircuits website) to be written on a microSD card? I'm having trouble understanding how to bridge the two. I am extremely new to coding and I would appreciate any help that I can get.
Thank you for your time.

Re: 9 Axis IMU + MicroSD
« Reply #1 on: September 18, 2018, 06:05:42 AM »
I'm currently developing a 9-axis data-logger. This uses a tinyscreen+, wifi, 9-axis imu and an sd-card. This currently works, but needs a change to manage file sizes.

The Arduino SD card writes to FAT32 only, and can only create files with 8 dot 3 filenames. File size is a concern to me as I'm trying to ensure I get at least 25 samples per second. This is going to create large file sizes, and the open-write data-close are going to get slower as the file get bigger.


#include <Wire.h>
#include <SPI.h>
#include <TinyScreen.h>
#include <WiFi101.h>
#include <RTCZero.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include "RTIMUSettings.h"
#include "RTIMU.h"
#include "RTFusionRTQF.h"
#include <SD.h>

//---------------------------------------------------------------

//Set up the WiFi
char ssid[] = "";        // your network SSID (Name)
char pass[] = "";          // your network password (use for WPA, or use as key for WEP)
int status = WL_IDLE_STATUS;       // the WiFi radio's status
bool WifiShieldPresent = false;
bool CalculatingGyro = false;
bool FirstTimeThrough = true;      //This is so we can do a bunch of stuff on the initial run after turning on

//Set up the TinyScreen+
//Library must be passed the board type
//TinyScreenDefault for TinyScreen shields
//TinyScreenAlternate for alternate address TinyScreen shields
//TinyScreenPlus for TinyScreen+
TinyScreen display = TinyScreen(TinyScreenPlus);

//Create a file object for writing out data to the SD card
File myFile;

//Tell the SD card library that the SD card controller is on pin 10 of the Arduino
const int Chipselect = 10;

//Set up the Filename for storing X/Y/Z degree data
String MyFileName = "";
String OutputVal = "";

//Set up the IMU (Inertial Motion Unit) 9-axis chip
RTIMU *imu;                                           // the IMU object
RTFusionRTQF fusion;                                  // the fusion object
RTIMUSettings settings;                               // the settings object

// NTP (Network Time Protocol) Setup
// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
// NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
WiFiUDP ntpUDP;
NTPClient NTPtimeClient(ntpUDP);
NTPClient timeClient(ntpUDP, "0.uk.pool.ntp.org", 3600, 60000); //Change this to local network NTP server IP address if neeeded

//Create an RTCZero object to talk to the onboard clock
RTCZero rtc;

//  DISPLAY_INTERVAL sets the rate at which results are displayed
#define DISPLAY_INTERVAL  35                         // interval between pose displays - default was 300
unsigned long lastDisplay;
unsigned long lastRate;

int sampleCount;
//---------------------------------------------------------------

void setup() {
  //Start the Wire object. We need this in order to talk with the IMU and other devices on the bus.
  Wire.begin();

  //Initialise the TinyScreen+ Display
  display.begin();                        //Start the display object
  display.setBrightness(5);                  //Turn down the brightness so we don't burn the OLED
  display.setFont(thinPixel7_10ptFontInfo);      //Nice, readable, font size
  display.fontColor(TS_8b_White, TS_8b_Black);  //Set the font color, font background
  display.on();                                 //Turns TinyScreen display on
  display.clearScreen();                  //Clear the screen
  display.setCursor(1, 1);                  //Put the cursor in the top left. A line is about 10 pixels setCursor(X,Y)

  //Start the SD-Card
  pinMode(10, OUTPUT);                     //Set pin 10 to output
  display.println("Initializing");
  display.setCursor(1, 10);                  //Move the cursor down 1 line
  display.println("SD card...");
  delay(2000);
  display.clearScreen();

  SD.begin(10);

  int errcode;

  //Make DAMN sure that the filename is blank.
  MyFileName = "";
 
  //Set up the Wifi
  //-------------------------------------
  //Set up the WiFi pins, as the tinyduino wifi shield are different from the defaults
  WiFi.setPins(8, 2, A3, -1); // VERY IMPORTANT FOR TINYDUINO
 
  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
     display.setCursor(1, 1);
     display.print("WiFi shield");
     display.setCursor(1, 10);
     display.print("not present");
     // don't continue:
     //while (true);
  }
  else {
     WifiShieldPresent = true;
  }
 
  if (WifiShieldPresent == true) {
     // attempt to connect to WiFi network:
     while (status != WL_CONNECTED) {
      display.clearScreen();
      display.setCursor(1,1);
      display.println("Attempting");
      display.setCursor(1,10);
      display.println("to connect WIFI: ");
      display.setCursor(1,20);
      display.println(ssid);
      // Connect to WPA/WPA2 network:
      status = WiFi.begin(ssid, pass);

      // wait 10 seconds for connection:
      delay(10000);
  }
 
  //Get the assigned IP Address
  IPAddress ip = WiFi.localIP();

  // you're connected now, so show a message and the IP Address:
  display.clearScreen();
  display.setCursor(1,1);
  display.println("Connected!");
  display.setCursor(1,10);
  display.print("IP: ");
  display.println(ip);

  //Start the Onboard Realtime Clock
  rtc.begin();

  //Start the Network Time Protocol (NTP) Client
  //We need the WiFi for this to work, but we assume it has
  NTPtimeClient.begin();
  NTPtimeClient.forceUpdate();
  display.setCursor(1, 20);

  //Sync the time between the NTP server and the onboard Realtime Clock (RTC)
  //If the RTC is already configured, don't bother. The small battery doesn't last long,
  //so we can 'assume' that if the RTC is set, then it sync'd recently.
  while ((rtc.isConfigured() == false) || (rtc.getYear() == 00)) {
     display.clearScreen();
     display.setCursor(1, 1);
     display.print("NTP Sync");
     rtc.setTime(NTPtimeClient.getHours(), NTPtimeClient.getMinutes(), NTPtimeClient.getSeconds());
     rtc.setEpoch(NTPtimeClient.getEpochTime());
  }

  //Quickly show the time on-screen before Getting into the tracking
  display.clearScreen();
  display.print(NTPtimeClient.getFormattedTime());
  display.setCursor(1, 30);
  display.print(rtc.getDay());
  display.print("/");
  display.print(rtc.getMonth());
  display.print("/");
  display.print(rtc.getYear());

  //Assemble the first filename to use
  CreateFilename();
  display.setCursor(1, 40);
  display.println(MyFileName);


  delay(10000); //Wait 10 seconds
  display.clearScreen();
  }
 
  //Initialise the IMU
  display.clearScreen();
  display.setCursor(1, 1);
  imu = RTIMU::createIMU(&settings);                        // create the imu object
  display.print("ArduinoIMU");
  display.setCursor(1, 10);
  display.print("starting");
  if ((errcode = imu->IMUInit()) < 0) {
     display.clearScreen();
     display.setCursor(1, 1);
     display.print("Failed to init");
     display.setCursor(1, 20);
     display.print("IMU: ");
     display.println(errcode);
  }

  display.clearScreen();
  display.setCursor(1, 1);
  if (imu->getCalibrationValid()) {
     display.print("Using compass");
     display.setCursor(1, 20);
     display.print("calibration");
  }
  else {
     display.setCursor(1, 1);
     display.print("No valid ");
     display.setCursor(1, 10);
     display.print("compass calibration");
     display.setCursor(1, 20);
     display.print("data");
  }
  lastDisplay = lastRate = millis();
  sampleCount = 0;
  delay(5000); //Wait 5 seconds
  display.clearScreen();

  // Slerp power controls the fusion and can be between 0 and 1
  // 0 means that only gyros are used, 1 means that only accels/compass are used
  // In-between gives the fusion mix.

  fusion.setSlerpPower(0.02);

  // use of sensors in the fusion algorithm can be controlled here
  // change any of these to false to disable that sensor

  fusion.setGyroEnable(true);
  fusion.setAccelEnable(true);
  fusion.setCompassEnable(true);

}

void loop() {
  // put your main code here, to run repeatedly:
   unsigned long now = millis();
   unsigned long delta;

   //Start reading the IMU (Inertial Motion Unit)
  if (imu->IMURead()) {                                // get the latest data if ready yet
     fusion.newIMUData(imu->getGyro(), imu->getAccel(), imu->getCompass(), imu->getTimestamp());
     sampleCount++;
     if ((delta = now - lastRate) >= 1000) {
        if (imu->IMUGyroBiasValid())
        {
           display.clearScreen();
           display.setCursor(1, 15);
           if (FirstTimeThrough == true) {
            display.print("Gyro Calibrated");
           }
           CalculatingGyro = false;
        }
        else
        {
           display.setCursor(1, 15);
           display.print("Calculating gyro");
           display.setCursor(1, 25);
           display.print("Don't move IMU!!");
           CalculatingGyro = true;
        }
        sampleCount = 0;
        lastRate = now;
     }
    
     if (CalculatingGyro == false) {
        if (FirstTimeThrough == true) {
         display.clearScreen();
         FirstTimeThrough = false;
        }
        if ((now - lastDisplay) >= DISPLAY_INTERVAL) {
           lastDisplay = now;
           RTVector3 accelData = imu->getAccel();
           RTVector3 gyroData = imu->getGyro();
           RTVector3 compassData = imu->getCompass();
           RTVector3 fusionData = fusion.getFusionPose();
           //displayAxis("Accel:", accelData.x(), accelData.y(), accelData.z());        // accel data
           //displayAxis("Gyro:", gyroData.x(), gyroData.y(), gyroData.z());            // gyro data
           //displayAxis("Mag:", compassData.x(), compassData.y(), compassData.z());    // compass data
           //displayDegrees("Fusion Position:", fusionData.x(), fusionData.y(), fusionData.z());   // fused output
           WriteToFile(fusionData.x(), fusionData.y(), fusionData.z(), accelData.x(), accelData.y(), accelData.z(), gyroData.x(), gyroData.y(), gyroData.z(), compassData.x(), compassData.y(), compassData.z());
        }
     }
  }


}

void displayAxis(const char *label, float x, float y, float z)
{
   OutputVal = "";
   OutputVal = label;
   OutputVal = OutputVal + (",X:");
   OutputVal = OutputVal + (x);
   OutputVal = OutputVal + (",Y:");
   OutputVal = OutputVal + (y);
   OutputVal = OutputVal + (",Z:");
   OutputVal = OutputVal + (z);
   OutputVal = OutputVal + (",T:" + NTPtimeClient.getFormattedTime());
   OutputVal = OutputVal + ":";
   OutputVal = OutputVal + ((int(millis() / 10) % 100));

   // open the file. note that only one file can be open at a time,
   // so you have to close this one before opening another.
   myFile = SD.open(MyFileName, FILE_WRITE);

   //If the file was opened properly
   if (myFile) {
      //Write the data out
      myFile.println(OutputVal);
      //Close the file
      myFile.close();
   }
   else {
      display.setCursor(30, 15);
      display.print("Error writing to file");
   }

}

void WriteToFile(const float Fusionx, float Fusiony, float Fusionz, float Accelx, float Accely, float Accelz, float Gyrox, float Gyroy, float Gyroz, float Compassx, float Compassy, float Compassz)
{
   OutputVal = "";
   OutputVal = "Fusion Position:";
   OutputVal = OutputVal + (",X:");
   OutputVal = OutputVal + (Fusionx * RTMATH_RAD_TO_DEGREE);
   OutputVal = OutputVal + (",Y:");
   OutputVal = OutputVal + (Fusiony * RTMATH_RAD_TO_DEGREE);
   OutputVal = OutputVal + (",Z:");
   OutputVal = OutputVal + (Fusionz * RTMATH_RAD_TO_DEGREE);
   OutputVal = OutputVal + ",Accelerometer:";
   OutputVal = OutputVal + (",X:");
   OutputVal = OutputVal + (Accelx);
   OutputVal = OutputVal + (",Y:");
   OutputVal = OutputVal + (Accely);
   OutputVal = OutputVal + (",Z:");
   OutputVal = OutputVal + (Accelz);
   OutputVal = OutputVal + ",Gyroscope:";
   OutputVal = OutputVal + (",X:");
   OutputVal = OutputVal + (Gyrox);
   OutputVal = OutputVal + (",Y:");
   OutputVal = OutputVal + (Gyroy);
   OutputVal = OutputVal + (",Z:");
   OutputVal = OutputVal + (Gyroz);
   OutputVal = OutputVal + ",Compass:";
   OutputVal = OutputVal + (",X:");
   OutputVal = OutputVal + (Compassx);
   OutputVal = OutputVal + (",Y:");
   OutputVal = OutputVal + (Compassy);
   OutputVal = OutputVal + (",Z:");
   OutputVal = OutputVal + (Compassz);
   OutputVal = OutputVal + (",Timestamp:" + NTPtimeClient.getFormattedTime());
   OutputVal = OutputVal + ":";
   OutputVal = OutputVal + ((int(millis() / 10) % 100));

   // open the file. note that only one file can be open at a time,
   // so you have to close this one before opening another.
   myFile = SD.open(MyFileName, FILE_WRITE);

   //If the file was opened properly
   if (myFile) {
      //Write the data out
      myFile.println(OutputVal);
      //Close the file
      myFile.close();
   }
   else {
      display.setCursor(30, 15);
      display.print("Error writing to file");
   }


}

void CreateFilename()
{
   int Day = rtc.getDay();
   int Hours = rtc.getHours();
   int Minutes = rtc.getAlarmMinutes();

   //Note. Filenames CANNOT be more than 8.3 characters
   MyFileName = "";
   //MyFileName = MyFileName + rtc.getMonth();
   MyFileName = MyFileName + Day;
   MyFileName = MyFileName + "_" + Hours;
   
   //We need to add a zero to bring it to 24hr format
   if (Minutes < 10) {
      MyFileName = MyFileName + "0";
   }

   MyFileName = MyFileName + Minutes;
   MyFileName = MyFileName + ".csv";
}