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";
}