September 2021

Feather 1 kHz Event Datalogger


This Event Datalogger is based on the Adafruit 3.5" 480/320 Touchscreen with SD Card FeatherWing coupled with the Feather M0 Proto Basic (ATSAMD21 Cortex M0).   Using the touchscreen, you start and stop the datalogging of an event to the SD card at 1 kHz, and then that data is plotted to the display.  

This display is fantastic looking, and has great performance.   The HX8357, and GFX libraries developed by Adafruit, along with the standard Arduino SD card library make it easy to create a fully functional event data logger.   Button (areas) are drawn on the screen and when touched by the user, this starts and stops the recording of data.   After the data is X-Y plotted to the screen, the user clicks in the center of the screen to begin the recording of another event.   Although only one signal is plotted to the screen, multiple signals could be recorded to the SD card.  

The 1 kHz (1 ms sample period) data sampling rate is possible when using a fast microcontroller and writing to the SD card without a flush() until the end of the event.   Nearly all mechanical systems can be monitored effectively by a sensor with a sample rate of 1 kHz.   For example, this event data logger was developed to support an application where an impact event was to be measured with an accelerometer.  

The plotting of the data to the screen is accomplished with a modified version of the plot function created by Kris Kasprzak and posted on the arduino forum and on github at KrisKasprzak/GraphingFunction.   I modified Kris's plotting function to format the x & y axis numerical labels, and to fit them to the x/y chart plotted to the screen.   My code also demonstrates how to get the proper values for the arguments that are passed to his charting function from any type of data.  

Reading the data back from the card and then plotting it to the screen does take some time, depending on the duration of the event.   The benefit is that you can record and then plot a lot of data, without using memory on the microcontroller.   Sampling at 1 kHz records a lot of data!   You can easily adapt the code to record data at a slower rate to match your specific application.  

A function GetSdCardWriteSpeed() measures the SD card write speed and sets a global variable SdSampleIntervalMs that is used by the data recording function SdRecordByMaxSampleInterval (a timer) to record the data.   So if you use the display with a different microcontroller or slower SD card, then the appropriate sample rate will be chosen.   I'm not sure if the SD card speed will matter, but I mention it just in case.   I do know that the SD card write speed is much slower with the 32u4 Basic Proto.   Note that you can adapt this to read any type of sensor, such as a I2C or SPI based sensor, or from one or more of the microcontroller analog inputs.  

The video below demonstrates the user interface using the display touchscreen.  

Code:



/*
  Event Data Logger
  
  Adafruit FeatherWing 3.5" 480x320 TFT

  Adafruit Feather M0 Basic
 
 */

// In Arduino IDE, select 'Tools','Board','Adafruit Boards','Adafruit Feather ...'
// Find USB port in Windows by looking at ...

const String codeVersion = "v0.0.1";

const boolean bDebug = true;

/////////////////////////////////////////////////////////////////////////
// Built in LED(s) & LiPoly Battery

//  #0 ESP8266
// #13 ESP32, STM32F2, Teensy, 32u4, 382P, M0, M4
const byte pinBuiltInLED = 13;  

// A6 328P,
// A7 M0
//const byte pinLiPoly = A6

void blinkLED(byte ledPIN){
  //  consumes 300 ms.
  for(int i = 5; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(30);
    digitalWrite(ledPIN, LOW);
    delay(30);
  }    
} //blinkLED()


void blinkERR(byte ledPIN){
  // S-O-S
  const int S = 150, O = 300;
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(O);
    digitalWrite(ledPIN, LOW);
    delay(O);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
} // blinkERR()


/////////////////////////////////////////////////////////////////////////
//  3.5" 480x320 TFT FeatherWing - AF Product #3651

// Adafruit HX8357 Library, Adafruit GFX Library, Adafruit ImageReader Library
//  Adafruit HX8357 Library   (search for "HX8357")
//  Adafruit GFX Library
//  https://github.com/adafruit/Adafruit_HX8357_Library
//  https://github.com/adafruit/Adafruit-GFX-Library

#include <SPI.h>
#include "Adafruit_GFX.h"
#include "Adafruit_HX8357.h"

#ifdef ESP8266
   #define STMPE_CS 16
   #define TFT_CS   0
   #define TFT_DC   15
   #define SD_CS    2
#endif
#ifdef ESP32
   #define STMPE_CS 32
   #define TFT_CS   15
   #define TFT_DC   33
   #define SD_CS    14
#endif
#ifdef TEENSYDUINO
   #define TFT_DC   10
   #define TFT_CS   4
   #define STMPE_CS 3
   #define SD_CS    8
#endif
#ifdef ARDUINO_STM32_FEATHER
   #define TFT_DC   PB4
   #define TFT_CS   PA15
   #define STMPE_CS PC7
   #define SD_CS    PC5
#endif
#ifdef ARDUINO_NRF52832_FEATHER
   #define STMPE_CS 30
   #define TFT_CS   31
   #define TFT_DC   11
   #define SD_CS    27
#endif
#if defined(ARDUINO_MAX32620FTHR) || defined(ARDUINO_MAX32630FTHR)
   #define TFT_DC   P5_4
   #define TFT_CS   P5_3
   #define STMPE_CS P3_3
   #define SD_CS    P3_2
#endif

// Anything else!
#if defined (__AVR_ATmega32U4__) || defined(ARDUINO_SAMD_FEATHER_M0) || defined (__AVR_ATmega328P__) || \
defined(ARDUINO_SAMD_ZERO) || defined(__SAMD51__) || defined(__SAM3X8E__) || defined(ARDUINO_NRF52840_FEATHER)
   #define STMPE_CS 6
   #define TFT_CS   9
   #define TFT_DC   10
   #define SD_CS    5
#endif

#define TFT_RST -1

// Use hardware SPI and the above for CS/DC
Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, TFT_RST);


// Micro SD card mounted to the 3.5" 480x320 TFT FeatherWing
// Arduino standard SD library: https://github.com/arduino-libraries/SD
// NOTE: Typical SD write & flush takes 20 to 30 ms
// Write without flush can achieve 1 kHz with Feather M0 for faster microcontroller. 
#include <SPI.h>
#include <SD.h>
// SDCS (chip select) pin is #10 on 32u4
// SDCS (chip select) pin is #5 on M0 mounted to 480x320 TFT 3651 FeatherWing
const int SDchipSelect = 5;
File sdfile;


// Initialize touchscreen driver
#include <Adafruit_STMPE610.h>
Adafruit_STMPE610 ts = Adafruit_STMPE610(STMPE_CS);

// Calibration data for the raw touch data to the screen coordinates
#define TS_MINX 3800
#define TS_MAXX 100
#define TS_MINY 100
#define TS_MAXY 3750

const unsigned long touchDelayMs = 1000;
unsigned long lastTouchTFT = 0;

/////////////////////////////////////////////////////////////////////////

float fGetLgSignedRandFloat() {
  //long myLong = random(100, 999);
  long myLong = random(100000000, 999999999);
  long divisor;
  int expnt = random(2,6);
  switch (expnt) {
    case 2:
      divisor = 100;
      break;
    case 3:
      divisor = 1000;
      break;
    case 4:
      divisor = 10000;
      break;
    case 5:
      divisor = 100000;
    default:
      divisor = 10;
  }
  float f = (float)myLong / divisor;
  int sign = random(10);
  if (sign <= 5) {
    f = f * (-1.0);
  }
  
  return f;
}

/////////////////////////////////////////////////////////////////////////
// ChtXYplot
// 
// Derived from:
//   Data visualization library based on Adafruit GFX
//   by Kris Kasprzak
// 
//   https://forum.arduino.cc/t/another-free-graph-function-for-plotting-in-cartesian-space/354751
//   https://github.com/KrisKasprzak/GraphingFunction/blob/master/Graph.ino

#define LTBLUE    0xB6DF
#define LTTEAL    0xBF5F
#define LTGREEN   0xBFF7
#define LTCYAN    0xC7FF
#define LTRED     0xFD34
#define LTMAGENTA 0xFD5F
#define LTYELLOW  0xFFF8
#define LTORANGE  0xFE73
#define LTPINK    0xFDDF
#define LTPURPLE  0xCCFF
#define LTGREY    0xE71C

#define BLUE      0x001F
#define TEAL      0x0438
#define GREEN     0x07E0
#define CYAN      0x07FF
#define RED       0xF800
#define MAGENTA   0xF81F
#define YELLOW    0xFFE0
#define ORANGE    0xFC00
#define PINK      0xF81F
#define PURPLE    0x8010
#define GREY      0xC618
#define WHITE     0xFFFF
#define BLACK     0x0000

#define DKBLUE    0x000D
#define DKTEAL    0x020C
#define DKGREEN   0x03E0
#define DKCYAN    0x03EF
#define DKRED     0x6000
#define DKMAGENTA 0x8008
#define DKYELLOW  0x8400
#define DKORANGE  0x8200
#define DKPINK    0x9009
#define DKPURPLE  0x4010
#define DKGREY    0x4A49

// these are the only external variables used by the graph function
// it's a flag to draw the coordinate system only on the first call to the ChtXYplot() function
// and will mimize flicker
// also create some variables to store the old x and y, if you draw 2 graphs on the same display
// you will need to store ox and oy per each display
boolean display1 = true;
double ox, oy ;

void ChtXYplot(Adafruit_HX8357 &d, double x, double y, double gx, double gy, double w, double h, double xlo, double xhi, double xinc, double ylo, double yhi, double yinc, String title, String xlabel, String ylabel, unsigned int gcolor, unsigned int acolor, unsigned int pcolor, unsigned int tcolor, unsigned int bcolor, boolean &redraw);


double y, xlo, xhi, xinc, ylo, yhi, yinc;
unsigned int x = 0;
/////////////////////////////////////////////////////////////////////////
//  SD Card

char filename[15];

unsigned long samples = 0;
// Below set as default based on results from GetSdCardWriteSpeed()
// and used by SdRecordByMaxSampleInterval()
unsigned long SdSampleIntervalMs = 1;   // 1 ms = 1 kHz
unsigned long SdSampleIntervalLap = millis();  // timer
byte stateSdRecord = LOW;

void DeleteAllFilesOnSdCard(void) {
  // Get the files on the SD card..
  sdfile = SD.open("/");
  while (true) {
    File f = sdfile.openNextFile();
    if (! f) {
      Serial.println("No files on SD card to delete");
      break;
    } else {
      Serial.println(f.name());
      if (SD.exists(f.name())) SD.remove(f.name());
    }
    f.close();
  } // while()
  sdfile.close();  
} // DeleteAllFilesOnSdCard()


void GetSdCardWriteSpeed(void) {
  // Measures the SD Card write speed without flush() between
  // writes and updates the global variable SdSampleIntervalMs.
  // Requires global variables:  filename, pinBuiltInLED, samples,
  strcpy(filename, "/SPEEDTST.CSV");
  if (SD.exists(filename)) SD.remove(filename);
  // Open file on SD Card for writing
  sdfile = SD.open(filename, FILE_WRITE);
  if (! sdfile) {
    if (bDebug == true)  Serial.print("ERROR - unable to create '");
    if (bDebug == true)  Serial.print(filename); Serial.println("'");
    DisplayError();
    while (1) {
      blinkERR(pinBuiltInLED);
    }
  }
  if (bDebug == true)  Serial.println("");

  unsigned long timerIntervalUs = 2000000; // 2 sec
  samples = 0;
  digitalWrite(pinBuiltInLED, HIGH);

  unsigned long sdTimerStart = micros();
  while (micros() - sdTimerStart < timerIntervalUs) {
    float f = fGetLgSignedRandFloat();
    sdfile.print(millis());
    sdfile.print(";");
    sdfile.println(f);
    samples++;
  } // while
  unsigned long sdTimeElapsed = micros() - sdTimerStart;
  
  // Execute a flush() to insure it is written since no sdfile.close() will be issued.
  sdfile.flush();
  sdfile.close();

  SdSampleIntervalMs = ceil(float(sdTimeElapsed)/1000.0/float(samples));
  if (bDebug == true) {
    Serial.print(samples); Serial.print(" samples in "); Serial.print(sdTimeElapsed); Serial.print(" us"); Serial.print("\t"); Serial.print(float(sdTimeElapsed)/float(samples)); Serial.println(" us/sample");
    Serial.print(samples); Serial.print(" samples in "); Serial.print(sdTimeElapsed/1000); Serial.print(" ms"); Serial.print("\t"); Serial.print(float(sdTimeElapsed)/1000.0/float(samples)); Serial.println(" ms/sample");
    Serial.print(samples); Serial.print(" samples in "); Serial.print(sdTimeElapsed/1000/1000); Serial.print(" s"); 
    Serial.print("\t"); Serial.print(1.0/(float(sdTimeElapsed)/1000.0/1000.0/float(samples))); Serial.println(" Hz");
    Serial.print("SdSampleIntervalMs = "); Serial.print(SdSampleIntervalMs); Serial.println(" ms");
    Serial.println(" ");
  }
  
  if (SD.exists(filename)) SD.remove(filename);
  digitalWrite(pinBuiltInLED, LOW);

} // GetSdCardWriteSpeed()


void OpenNewFileOnSdCard(void) {
  // Create a filename reference to a file that doesn't exist 'ANALOG00.TXT'..'ANALOG99.TXT'
  //char filename[15];
  // /DTALOG00.TXT
  // 012345678
  strcpy(filename, "/DTALOG00.CSV");
  for (uint8_t i = 0; i < 100; i++) {
    filename[7] = '0' + i/10;
    filename[8] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (SD.exists(filename)){
      if (bDebug == true) {
        Serial.print("File '");
        Serial.print(filename);
        Serial.println("' already exists");
      }
    } else {
      if (bDebug == true) {
        Serial.print("New file will be '");
        Serial.print(filename);
        Serial.println("'");
      }
      break;
    }
  }
  // Open file on SD Card for writing
  sdfile = SD.open(filename, FILE_WRITE);
  if (! sdfile) {
    if (bDebug == true)  {
      Serial.print("ERROR - unable to create '");
      Serial.print(filename); Serial.println("'");
    }
    DisplayError();
    while (1) {
      blinkERR(pinBuiltInLED);
    }
  }
  if (bDebug == true) Serial.println("");

} // OpenNewFileOnSdCard()


void SdRecordByMaxSampleInterval() {
  // Records only if stateSdRecord == HIGH
  if (SdSampleIntervalLap > millis())  SdSampleIntervalLap = millis();  
  if (stateSdRecord == HIGH && millis() - SdSampleIntervalLap > SdSampleIntervalMs) { 
    // Write to SD card without flush()
    y = fGetLgSignedRandFloat();
    x = millis();
    sdfile.print(x);
    sdfile.print(";");
    sdfile.println(y);
    if (samples == 0) {
      xlo = x; xhi = x; ylo=y; yhi=y; 
    } else {
      if (fabs(x) < fabs(xlo))  xlo = x;
      if (fabs(x) > fabs(xhi))  xhi = x;
      if (y < ylo)  ylo = y;
      if (y > yhi)  yhi = y;
    }
    samples++;
    SdSampleIntervalLap = millis(); // reset the timer
  }
} // SdRecordByMaxSampleInterval()

/////////////////////////////////////////////////////////////////////////


// activePage identifies the TFT screen page
//  0 = splash screen     SplashScreen()
//  1 = Record Data       RecordData()
//  2 = Recording Data    RecordingData()
//  3 = Plot data   PlotData()
byte activePage = 0;

unsigned long recordStartMs = 0;
unsigned long recordEndMs = 0;


void SplashScreen(void) {
  activePage = 0;
  
  // Fill screen with 16 bit SmartCost Inc. blue 0x04193d 4,25,61
  // The argument is a 16-bit 5-6-5 Color value.
  // http://greekgeeks.net/#maker-tools_convertColor
  tft.fillScreen(0x00C7);

  tft.setCursor(int(480.0/2.0-24.0/2.0*18.5),320/3); // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(3); // 26 chars wide (18.5 px/char)
  tft.setTextWrap(false);
  //tft.print("012345678901234567890123456");
  tft.print("Mechatronic Solutions LLC");
  
  tft.setCursor(int(480.0/2.0-6.0/2.0*12),290); // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(2); // 40 chars wide (12 px/char)
  tft.setTextWrap(false);
  tft.print(codeVersion);

} // SplashScreen()


/////////////////////////////////////////////////////////////////////////

void DrawScreenConstants(void) {
  // Fill screen with 16 bit SmartCost Inc. blue 0x04193d 4,25,61
  // The argument is a 16-bit 5-6-5 Color value.
  // http://greekgeeks.net/#maker-tools_convertColor
  tft.fillScreen(0x00C7);

  tft.setCursor(int(480.0/2.0-19.0/2.0*18.5),10); // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(3); // 26 chars wide (18.5 px/char)
  tft.setTextWrap(false);
  //tft.print("012345678901234567890123456");
  tft.print("Event Data Recorder");
  
  tft.setCursor(int(480-13*6),300); // right justify
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(1); // 80 chars wide (6 px/char)
  tft.setTextWrap(false);
  //tft.print("01234567890123456789012345678901234567890123456789012345678901234567890123456789");
  tft.print("SmartCost Inc");

} // DrawScreenConstants()


void RecordData(void) {
  activePage = 1;
  //Serial.println("RecordData()..");
  
  DrawScreenConstants();

  tft.setCursor(int(480.0/2.0-11.0/2.0*12.0),150);  // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(2);
  tft.setTextWrap(false);
  tft.print("Record Data");
  tft.setCursor(int(480.0/2.0-16.0/2.0*12.0),190);  // center horizontally
  tft.print("(touch to start)");
  // Draw a rectangle with no fill color..
  //  drawRect (x top left, y top left, width, height, color)
  tft.drawRect(100, 170-40, 480-100-100, (170-(170-40)+8)*2, 0xFFFF);
  
} // RecordData()


void RecordingData(void) {
  activePage = 2;
  //Serial.println("RecordingData()..");

  OpenNewFileOnSdCard();

  DrawScreenConstants();

  // Write the SD filename to the TFT display
  tft.setCursor(10,70);
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(2); // 40 chars wide (12 px/char)
  tft.setTextWrap(false);
  tft.print("File: ");
  for (uint8_t i = 1; i < 13; i++) {
    tft.print(filename[i]);
  }
  
  tft.setCursor(int(480.0/2.0-14.0/2.0*12.0),150);  // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(2);
  tft.setTextWrap(false);
  tft.print("Recording Data");
  tft.setCursor(int(480.0/2.0-15.0/2.0*12.0),190);  // center horizontally
  tft.print("(touch to stop)");
  // Draw a rectangle with no fill color..
  //  drawRect (x top left, y top left, width, height, color)
  tft.drawRect(100, 170-40, 480-100-100, (170-(170-40)+8)*2, 0xFFFF);

  // trigger recording to SD card via RecordDataToSdCard()
  stateSdRecord = HIGH; 
  recordStartMs = millis();
  samples = 0;

} // RecordingData()


void PlotData(void) {
  activePage = 3;

  DrawScreenConstants();

  // Write the SD filename to the TFT display
  tft.setCursor(10,70);
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(2); // 40 chars wide (12 px/char)
  tft.setTextWrap(false);
  tft.print("File: ");
  for (uint8_t i = 1; i < 13; i++) {
    tft.print(filename[i]);
  }
  
  tft.setCursor(int(480.0/2.0-17.0/2.0*12.0),140);  // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(2);
  tft.setTextWrap(false);
  tft.print("Processing Data..");

  //SdSampleIntervalMs
  tft.setCursor(int(480.0/2.0-25.0/2.0*12.0),200);  // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(2);
  tft.setTextWrap(false);
  tft.print((recordEndMs - recordStartMs)/1000);
  tft.print(" sec recorded at ");
  // Calculate the sample rate in Hz..
  float r = 1.0/(float(SdSampleIntervalMs)/1000.0);
  if (r >= 1000.0) {
    tft.print(r/1000.0);
    tft.print(" kHz");
  } else {
    tft.print(r);
    tft.print(" Hz");
  }

  if (bDebug == true) {
    Serial.print(samples); 
    Serial.print(" samples recorded over ");
    Serial.print((millis() - recordStartMs)/1000);
    Serial.println(" sec");
  }

  // Stop data recording & execute a flush() since no sdfile.close() will be issued.
  stateSdRecord = LOW;
  digitalWrite(pinBuiltInLED, LOW);
  sdfile.flush();  
  sdfile.close();
  if (bDebug == true) Serial.println("SD file flushed and closed");

  double gx, gy, w, h;
  unsigned long lines = 0;
  
  // Calculate the grid lines..
  xinc = (xhi-xlo)/5.0;
  yinc = (yhi-ylo)/5.0;
  if (bDebug == true) {
    Serial.print("x: "); Serial.print(xlo); Serial.print(" to "); Serial.println(xhi);
    Serial.print("y: "); Serial.print(ylo); Serial.print(" to "); Serial.println(yhi);
    Serial.print("xinc: "); Serial.println(xinc);
    Serial.print("yinc: "); Serial.println(yinc);
  }
  
  // Plot the data
  tft.fillScreen(BLACK);
  lines = 0;
  sdfile = SD.open(filename, FILE_READ);
  if (! sdfile) {
    if (bDebug == true) {
      Serial.print("ERROR opening SD file ");
      Serial.println(filename);
    }
    DisplayError();
    while (1) {
      blinkERR(pinBuiltInLED);
    }
  } else {
    display1 = true; 
    xhi = xhi - xlo;
    gx=60; w=460-gx; gy=290; h=260;
    if (bDebug == true) Serial.print("Plotting "); Serial.println(filename);
    //String title = "012345678901234567890123456789012456789";
    String title = "Acceleration     " + String(filename).substring(1,strlen(filename));
    while (sdfile.available()) {
      x = sdfile.parseInt() - xlo;  // subtract out the X offset
      y = double(sdfile.parseFloat());
      if (lines < samples) {
        //Serial.print(samples); Serial.print(";"); Serial.print(x); Serial.print(";"); Serial.println(y);
        //ChtXYplot(display, x, y, gx, gy, w, h, xlo, xhi, xinc, ylo, yhi, yinc, title, xlabel, ylabel, gcolor, acolor, pcolor, tcolor, bcolor, boolean &redraw)
        //ChtXYplot(tft, x, y, gx, gy, w, h, xlo, xhi, xinc, ylo, yhi, yinc, title, "ms", "y", DKBLUE, RED, YELLOW, WHITE, BLACK, display1);
        ChtXYplot(tft, x, y, gx, gy, w, h, 0.0, xhi, xinc, ylo, yhi, yinc, title, "ms", "m/s^2", DKBLUE, RED, YELLOW, WHITE, BLACK, display1);
      }
      lines++;
    }
    sdfile.close();
  }  
  
  // clear out the touchscreen buffer..
  TS_Point p = ts.getPoint();
  lastTouchTFT = millis();
  
} // PlotData()



void DisplayError(void) {
  // Fill screen with FireBrick  #0x8800 rgb(139, 0, 0)
  // The argument is a 16-bit 5-6-5 Color value.
  // http://greekgeeks.net/#maker-tools_convertColor
  tft.fillScreen(0x8800);

  tft.setCursor(int(480.0/2.0-5.0/2.0*24.0),320/2); // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(4); // 20 chars wide (24 px/char)
  tft.setTextWrap(false);
  //tft.print("012345678901234567890");
  tft.print("ERROR");

  while (1) {
    blinkERR(pinBuiltInLED);
  }
  
} // DisplayError()



void touchScreen() {
  // ts.touched()
  if (! ts.bufferEmpty()) {
    TS_Point p = ts.getPoint();
    // Scale from ~0->4000 to tft.width using the calibration #'s
    //  tft.width() = 480;  tft.height() = 320
    p.x = map(p.x, TS_MINX, TS_MAXX, tft.width(), 0);
    p.y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height());
    // tft.width() =320, tft.height() = 480 before rotation
    // tft.width() =480, tft.height() = 320 after rotation
    // 3800,0       3800,3800
    // 0,0          0,3800
    //Serial.print("Touch ");  Serial.print(p.x);  Serial.print(", ");  Serial.println(p.y);
    // Drawn button:
    //  300,75    300,275
    //  150,75    150,275
    // Check if drawn button has been touched..
    if (lastTouchTFT > millis())  lastTouchTFT = millis();
    //Serial.println(millis() - lastTouchTFT);
    if (millis() - lastTouchTFT > touchDelayMs && p.x > 150 && p.x < 300 && p.y >75 && p.y < 275) {
      lastTouchTFT = millis();
      if (bDebug == true) Serial.print("Button Touch ");  Serial.print(p.x);  Serial.print(", ");  Serial.println(p.y);
      switch (activePage) {
        case 0: // splash screen
          break; 
        case 1: // record data
          RecordingData();
          break;
        case 2: // recording data
          recordEndMs = millis();
          PlotData();
          break;
        case 3: // plot data
          //OpenNewFileOnSdCard();
          RecordData();
          break;
        default:
          DisplayError();
          break;          
      } // switch (activePage)
    } // button touch
  } // ts.bufferEmpty()
  
} // touchScreen()

/////////////////////////////////////////////////////////////////////////


void setup() {

  if (bDebug == true) {
    Serial.begin(115200);
    while (!Serial) {
      delay(1);
    }
    Serial.println("\nSerial ready\n");
  }

  pinMode(pinBuiltInLED, OUTPUT);
  digitalWrite(pinBuiltInLED, LOW);

  //480x320 TFT FeatherWing #3651
  if (bDebug == true) Serial.println("480x320 TFT FeatherWing #3651");
  tft.begin();
  /*
  Serial.println("480x320 TFT FeatherWing #3651 diagnostics..");
  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(HX8357_RDPOWMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(HX8357_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(HX8357_RDCOLMOD);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(HX8357_RDDIM);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(HX8357_RDDSDR);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); 
  Serial.println("");
  */

  // Set rotation of the screen
  // rotation 1 is landscape mode with USB jack at bottom right
  // rotation 3 is landscape mode with USB jack at left
  tft.setRotation(1);

  // Fill screen with 16 bit SmartCost Inc. blue 0x04193d 4,25,61
  // The argument is a 16-bit 5-6-5 Color value.
  // http://greekgeeks.net/#maker-tools_convertColor
  //tft.fillScreen(0x00C7);

  SplashScreen();

  // Initialize Micro SD Card
  if (!SD.begin(SDchipSelect)) {
    if (bDebug == true) Serial.println("SD card failed, or not present");
    while (1) {
      blinkERR(pinBuiltInLED);
    }
  }
  if (bDebug == true) Serial.println("\nSD card initialized.");

  DeleteAllFilesOnSdCard();
  
  GetSdCardWriteSpeed();

  stateSdRecord = HIGH;
  samples = 0;

  // Initialize other items now (while splash screen is shown)..
  // Initialize the touchscreen
  if (!ts.begin()) {
    if (bDebug == true) Serial.println("Touchscreen initialization error!");
    DisplayError();
    while (1) {
      blinkERR(pinBuiltInLED);
    }
  }
  if (bDebug == true) Serial.println("\nTouchscreen started\n"); 

  /*
  // Write text to the TFT display (in landscape mode)
  // For .setTextSize(2): 40 chars wide (12 char/px) x 30 px vertical spacing
  tft.setCursor(int(480.0/2.0-19.0/2.0*12.0),10); // center horizontally
  tft.setTextColor(0xFFFF); // white
  tft.setTextSize(2);
  tft.setTextWrap(false);
  //tft.print("0123456789012345678901234567890123456789");
  tft.print("Event Data Recorder");
  */
  
  RecordData();

  /*
  // Record an event to the SD Card and measure the average sample rate
  // (sample rate is limited by how fast the data can be written to the SD Card).
  // 10000000 us = 10000 ms = 10 sec
  unsigned long sampleDurationUs = 10000000;
  RecordEventToSdCard(sampleDurationUs);
  */

  lastTouchTFT = millis();
  if (bDebug == true) Serial.println("\nSetup finished");
} // setup()


void loop() {

  touchScreen();

  SdRecordByMaxSampleInterval();

} // loop()

void ChtXYplot(Adafruit_HX8357 &d, double x, double y, double gx, double gy, double w, double h, double xlo, double xhi, double xinc, double ylo, double yhi, double yinc, String title, String xlabel, String ylabel, unsigned int gcolor, unsigned int acolor, unsigned int pcolor, unsigned int tcolor, unsigned int bcolor, boolean &redraw) {
/*

  function to draw a cartesian coordinate system and plot whatever data you want
  just pass x and y and the graph will be drawn

  huge arguement list
  &d name of your display object
  x = x data point
  y = y datapont
  gx = x graph location (lower left)
  gy = y graph location (lower left)
  w = width of graph
  h = height of graph
  xlo = lower bound of x axis
  xhi = upper bound of x asis
  xinc = division of x axis (distance not count)
  ylo = lower bound of y axis
  yhi = upper bound of y asis
  yinc = division of y axis (distance not count)
  title = title of graph
  xlabel = x asis label
  ylabel = y asis label
  gcolor = graph line colors
  acolor = axi ine colors
  pcolor = color of your plotted data
  tcolor = text color
  bcolor = background color
  &redraw = flag to redraw graph on fist call only
*/
  double ydiv, xdiv;
  double i;
  double temp;
  int rot, newrot;

  if (redraw == true) {

    redraw = false;
    // initialize old x and old y in order to draw the first point of the graph
    // but save the transformed value
    // note my transform funcition is the same as the map function, except the map uses long and we need doubles
    ox = (x - xlo) * ( w) / (xhi - xlo) + gx;
    oy = (y - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
    // draw y scale
    for ( i = ylo; i <= yhi; i += yinc) {
      // compute the transform
      temp =  (i - ylo) * (gy - h - gy) / (yhi - ylo) + gy;

      if (i == 0) {
        d.drawLine(gx, temp, gx + w, temp, acolor);
      }
      else {
        d.drawLine(gx, temp, gx + w, temp, gcolor);
      }
      // draw the axis labels
      d.setTextSize(1);
      d.setTextColor(tcolor, bcolor);
      d.setCursor(gx - 55, temp); // 55 offsets axis numeric label to left of axis
      // +0.0E+00
      d.printf("%+.1E",i);
    }
    // draw x scale
    for (i = xlo; i <= xhi; i += xinc) {

      // compute the transform
      temp =  (i - xlo) * ( w) / (xhi - xlo) + gx;
      if (i == 0) {
        d.drawLine(temp, gy, temp, gy - h, acolor);
      }
      else {
        d.drawLine(temp, gy, temp, gy - h, gcolor);
      }
      // draw the axis labels
      d.setTextSize(1);
      d.setTextColor(tcolor, bcolor);
      d.setCursor(temp, gy + 10);
      // +4.1E+06
      d.printf("%+.1E",i);
    }

    //now draw the graph labels
    d.setTextSize(2);
    d.setTextColor(tcolor, bcolor);
    d.setCursor(gx , gy - h - 30);
    d.println(title);

    d.setTextSize(1);
    d.setTextColor(acolor, bcolor);
    d.setCursor(gx , gy + 20);
    d.println(xlabel);

    d.setTextSize(1);
    d.setTextColor(acolor, bcolor);
    d.setCursor(gx - 30, gy - h - 10);
    d.println(ylabel);

  }

  // the coordinates are now drawn, plot the data
  // the entire plotting code are these few lines...
  // recall that ox and oy are initialized above
  x =  (x - xlo) * ( w) / (xhi - xlo) + gx;
  y =  (y - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
  d.drawLine(ox, oy, x, y, pcolor);
  // it's up to you but drawing 2 more lines to give the graph some thickness
  d.drawLine(ox, oy + 1, x, y + 1, pcolor);
  d.drawLine(ox, oy - 1, x, y - 1, pcolor);
  ox = x;
  oy = y;

} // ChtXYplot()

 


Do you need help developing or customizing a IoT product for your needs?   Send me an email requesting a free one hour phone / web share consultation.  

 

The information presented on this website is for the author's use only.   Use of this information by anyone other than the author is offered as guidelines and non-professional advice only.   No liability is assumed by the author or this web site.