Scott McNabb
Published © GPL3+

Photon 2 Sump Water Level Monitor and Email & Logging

If my sump pump fails I want an immediate email.

AdvancedFull instructions providedOver 4 days55
Photon 2 Sump Water Level Monitor and Email & Logging

Things used in this project

Hardware components

Photon 2
Particle Photon 2
×1
Feather Click Shield
MIKROE Feather Click Shield
×1
MIKROE 4-20 mA R Click
×1
MIKROE Barometer Click
×1
MIKROE Terminal Click
×1
MIKROE microSD Click
×1
MIKROE Wire Jumpers Female to Female 15 cm 10 pcs
×1
Liquid Level Differential Pressure Sensor, 4-20 mA for 0-1 m
×1
SparkFun HC-SR04-33 Ultrasonic Sensor
×1
32 GB microSD card
×1
USB Type A Power Supply, 5 Vdc, 3 A
×1
USB A to C Cable 6 feet
×1
USB A to Micro B Cable 6 feet
×1
Plastic Enclosure 6x4x2 inches
×1
Telephone Cable 4 conductor 22 AWG 8 fe
×1
Raspberry Pi 5
Raspberry Pi 5
×1

Story

Read more

Schematics

SumpLevel

SumpLevel

Exploded view

Code

SumpLevel.cpp

C/C++
This is the main program.
/* 
 * Project SumpLevel
 * Author: Scott McNabb
 * Start Date: 10-Jan-2026
 * Last Edit Date: 12-Apr-2026
 */

// Include Particle Device OS APIs
#include "Particle.h" //automatically included
#include "barometer.h"
#include "c420mar.h"
#include "sd-card-library-photon-compat.h"
#include "sd-fat.h"
// Median filter from GitHub tmick0
// Readings are placed in a circular buffer. The buffer content is copied to a second array,
// where the median is computed using the quickselect algorithm.
#include "MedianFilter.h"

// Let Device OS manage the connection to the Particle Cloud
SYSTEM_MODE(AUTOMATIC);

// Show system, cloud connectivity, and application logs over USB
// View logs with CLI using 'particle serial monitor --follow'
//SerialLogHandler logHandler(LOG_LEVEL_INFO);

// MedianFilter size (odd numbers only) NOTE that large numbers cause a delay, e.g. 99 causes 48 s delay, but very good at smoothing the data
#define MEDIANFILTERSIZE 25
// barometer config for I2C bus 1, I2C address = 0x5D (0x5C if jumper moved) (use terminal click on left feather wing)
// MOTE: Make SURE that D18/CS and D19/INT/EN are disabled, as they will conflict with 420mar SPI 1
#define BAROMBUS MIKROBUS_1
// c420mar config for SPI 1 on left feather wing (NOTE: As of 2026/03/14, c420mar is way too noisy to be used!)
#define C420MARBUS MIKROBUS_1
// So 4-20 mA relative H20 sensor is 0.0 cm when not in any water (i.e. calibration constant)
#define FMACOMPENSATION 0.06
// The following definitions are based on the assessment of lots of data graphed in Excel
// Use UT Height (median 25) values to count sump operations (or sump not working)
// NOTE: reduce CMHIGH from 38 to 36 due to sometimes sump motor wire limits low water detection
#define CMERRORLOW 5.0
#define CMTOOLOW 26.0
#define CMZERO 29.0
#define CMLOW 32.0
#define CMHIGH 36.0
#define CM100 42.0
#define CMERRORHIGH 60.0
// Use raw Vref, Vin+ to calculate Depth1 (median 25) of water from 4-20 mA probe, and count sump operations (or sump not working)
#define D1ERRORLOW 5.0
#define D1ZERO 13.0
#define D1LOW 15.0
#define D1HIGH 21.0
#define D1100 26.0
#define D1TOOHIGH 28.0
#define D1ERRORHIGH 60.0
// Interrupt routine delay in ms between each operation, where all data is collected (1000 = 1 s)
#define DATACOLLECTIONTIMER 1000
// TCP logging default port (default 23 for telnet, but can be changed to something else if needed)
#define TCPLOGGINGPORT 23
// Use any number above 1024
#define TCPEMAILPORT 10000

static c420mar_t c420mar;
static barometer_t barometer; // I2C 0x5D
MedianFilter <float, MEDIANFILTERSIZE> fCmMedFilter; // always an odd number for median filter
MedianFilter <float, MEDIANFILTERSIZE> fD1MedFilter; // always an odd number for median filter
TCPClient client, clientEmail;
byte server[] = {192, 168, 1, 99}; // last digit needs to be set
byte serverEmail[] = {192, 168, 1, 100}; // Raspberry Pi (port 10000)
String sEmail = ""; // used for message to Raspberry Pi to Email
char cEmail[100]; // used for message to Raspberry Pi to Email
int iEmailSent = 0; // used to keep track of whether email was sent successfully (1 = True, 0 = False), possibly >1 for multiple emails
bool bHighWaterAlertSent = FALSE; // used to keep track of whether high water alert email was sent, so that it doesn't send multiple emails during the same high water event
bool bDailySumpOperationsSent = FALSE; // used to keep track of whether daily sump operations email was sent, so that it doesn't send multiple emails during the same day
String sLastDate = ""; // used to keep track of the last date, for daily sump pump operations email alert

// c420mar config for soldered connections to raw Vref, Vin+ on left feather wing (NOTE: Use this instead of c420mar)
const uint8_t VrefPin = A0;
const uint8_t VinPin = A2;
int boardLed = D7; // blue board LED
// HC-SR04-33 Ultrasonic Sensor on left feather wing
int trig_pin = D8; // TX pin
int echo_pin = D9; // RX pin
// microSD config for SPI 2 on right feather wing (SPI 1: CS = D18 on left feather wing)
const uint8_t chipSelect = D5;    // Also used for HARDWARE SPI setup on right feather wing
const uint8_t mosiPin = D15;
const uint8_t misoPin = D16;
const uint8_t clockPin = D17;
bool bSDEnabled; // flag used to indicate whether to write data to microSD card
int iNewDataAvailable; // count to indicate new data is available for processing
long lTimeLoopStart; // used to time total loop time, from data Interrupt start, to file data saved
int iLastDayTimeSynced; // used to initiate Particle.syncTime() once per day
long lmsGetData; // used to measure time (ms) for getting all data
String smsGetData; // used for cloud variable
long lmsWriteFile; // used to measure (ms) to save data to SD file
String smsWriteFile; // used for cloud variable
long lmsTotalLoop; // used to measure (ms) for entire loop to get data and save it to SD file
String smsTotalLoop; // used for cloud variable
float fCm; // HC-SR04-33 Ultrasonic Sensor
String sCm; // used for cloud variable
float fBTemperature_c; // Baromteric sensor temperature
float fBPressure; // Barometric sensor pressure
String sBar; // used for cloud variable
float fDepth; // 4-20 mA pressure sensor for 0-100 cm water depth
//String sD; // used for cloud variable
float fmA; // 4-20 mA pressure sensor raw data
//String smA; // used for cloud variable
int iVref; // A/D 4-20 mA raw voltage reference (2.048V)
String sVref; // used for cloud variable
int iVin; // A/D 4-20 mA raw voltage input (nominal 0.4 V to 2.0 V, corresponding to 4-20 mA input)
String sVin; // used for cloud variable
float fD1; // water depth calculated from raw voltage inputs from 4-20 mA board
String sD1; // used for cloud variable
int iSumpCmDailyOps; // count of how many times the sump pump operated each day
float fCmMedNowTooHigh; // keep track of the value that triggered the Too High flag
int iSumpD1DailyOps; // count of how many times the sump pump operated each day
float fD1MedNowTooHigh; // keep track of the value that triggered the Too High flag
String sSumpTodayOperations; // used for cloud variable
String sSumpYesterdayOperations; // used for cloud variable
String sStatus; // used for cloud variable to indicate value(s) that caused high water
bool bCmLow, bCmHigh, bCmTooLow; //  flags to keep track of water level changing during sump pump operation (low cm = high water level)
bool bD1Low, bD1High, bD1TooHigh; //  flags to keep track of water level changing during sump pump operation
int iYear, iMonth, iDay, iHour, iMinute, iSecond;
bool bNewDay; // flag to indicate that header to be added to a new file, since date has changed
bool bHeaderDataSaved; // flag to indicate that header needs to be saved to file, but not yet done
char cDataString[140]; // used for data string to be added to log file on microSD card (limit of 200??)
char cData[15]; // used for individual data type to be added to log file data
String sDisplayTime = "15:45:01"; //example
String sDisplayDate = "2026/02/28"; // example
String sLogFileName = "20260221.txt"; // use String so easy to create file name, then convert to char array
char cFileName[13]; // make sure file name is 8.3 format, including string Nul char at end
float fVref, fVin; // raw 4-20 mA input (ref = 2.048V, nominal 0.4V - 2.0V, corresponding to 4-20 mA input)
float fCmMedNow; // use a median 3 filter
String sCmMedAll; // used for cloud variables
float fDMedNow; // // use a median 5 filter
String sDMedAll; // used for cloud variables
float fD1MedNow; // use a median 5 filter
String sD1MedAll; // used for cloud variables
bool bTCPLoggingEnabled; // used to indicate whether to (try to) send data to TCP

// Create a file name for the log file on the microSD card, based on the date (e.g. 20260219.txt)
void CreateFilename() {
  sLogFileName = String(iYear);
  if (iMonth < 10) sLogFileName += "0"; //make sure 2 digits for month
  sLogFileName += String(iMonth);
  if (iDay < 10) sLogFileName += "0"; //make sure 2 digits for day
  sLogFileName += String(iDay) + ".txt"; //e.g. 20260219.txt (max 8.3 digits)
  // Now, convert String file name (sLogFileName1) to an array (sFileName), needed to open file
  strncpy(cFileName, sLogFileName, 12);
  cFileName[12] = 0; // Null char to indicate end of characters
}

// Update the date and time variables (iYear, iMonth, iDay, iHour, iMinute, iSecond) based on the Particle Cloud time, if available, or by incrementing the local time variables if not.
void UpdateDateAndTime() {
  if (Time.isValid()) { // update local time variables from cloud
    Time.zone(-4); // DST, else -5
    iYear = Time.year(); // 4 digit year
    iMonth = Time.month(); // 1-12 month
    iDay = Time.day(); // 1-31 day
    iHour = Time.hour(); // 0-23
    iMinute = Time.minute(); // 0-59
    iSecond = Time.second(); // 0-59
    if (iDay != iLastDayTimeSynced) {
      Particle.syncTime(); // do this only once per day
      iLastDayTimeSynced = iDay;
    }
  }
  else { // increment local time variables (change this if DATACOLLECTIONTIMER is changed from 1000 ms)
    iSecond += (DATACOLLECTIONTIMER/1000); // convert ms to s, only works without error if data is collected on multiples of seconds
    if (iSecond >= 60) {
      iSecond -= 60; // e.g. if updating time every 2 s, then iSecond coud be 59+2 = 61
      iMinute += 1;
      if (iMinute >= 60) {
        iMinute = 0; // starts at 0
        iHour += 1;
        if (iHour >= 24) {
          iHour = 0; // starts at 0
          iDay += 1; // Note that this could produce an incorrect date, but data can still be fixed later
          if (iDay >= 32) { // Note that this could produce incorrect dates
            iDay = 1; // starts at 1
            iMonth += 1;
            if (iMonth >= 13) {
              iMonth = 1; // starts at 1
              iYear += 1;
            }
          }
        }
      }
    }
  }

  sDisplayDate = String(iYear) + "/";
  if (iMonth < 10) sDisplayDate += "0"; // make sure 2 digits for month
  sDisplayDate += String(iMonth) + "/";
  if (iDay < 10) sDisplayDate += "0"; // make sure 2 digits for day
  sDisplayDate += String(iDay); //e.g. "2026/02/28"

  sDisplayTime = "";
  if (iHour < 10) sDisplayTime += "0"; // make sure 2 digits for hour
  sDisplayTime += String(iHour) + ":";
  if (iMinute < 10) sDisplayTime += "0"; // make sure 2 digits for minuute
  sDisplayTime += String(iMinute) + ":";
  if (iSecond < 10) sDisplayTime += "0"; // make sure 2 digits for second
  sDisplayTime += String(iSecond); // e.g. "15:45:01"

  if ((iHour == 0) && (iMinute == 0) && (iSecond < 10)) {
    bNewDay = TRUE; // flag to indicate a header is to be added to new file name (once per day)
  }
  else {
    bNewDay = FALSE;
    sLastDate = sDisplayDate; // save the last date, for use in daily operations email alert at the next new day (since the date will have changed by then)
  }
}

// Save the collected data to a file on the microSD card.
void SaveInfoToFile() { // Save info to microSD Click
  long lTimeSDStart;

  if (bSDEnabled) { //only save to SD if available (and logging not turned off by Particle StopDataLogging function)
    lTimeSDStart = millis(); // START TIME for the duration of saving data to SD card

    // open the file. note that only one file can be open at a time,
    // so you have to close this one before opening another.
    // SM - changed sd-fat.cpp, SdFile::open() and sync() to put timestamps with todays date and time,
    // FAT_DATE(Time.year(), Time.month(), Time.day());
    // FAT_TIME(Time.hour(), Time.minute(), Time.second());
    File dataFile = SD.open(cFileName, FILE_WRITE);

    // if the file is available, write to it:
    if (dataFile) {
      dataFile.println(cDataString);
      dataFile.close();
      lmsWriteFile = millis() - lTimeSDStart; // END TIME for the duration of saving data to SD card (7-62 ms)
      if (lmsWriteFile > 1000) {
        lmsWriteFile = 999; // cap at 999 ms (in case millis() overflows after ~49 days, or some other issue causes a really long loop time, so that it doesn't mess up the data formatting in the file and remote TCP)
      }
      // print to the serial port too:
      //Serial.println(dataString);
    }
    // if the file isn't open, pop up an error:
    else {
//      Serial.printf("Error opening %s on SD card", cFileName); // comment out when not needed for testing
//      Serial.println(); // comment out when not needed for testing
      bSDEnabled = FALSE; // don't bother trying to save to SD file again
      lmsWriteFile = 0;
    }
  }
  else {
    lmsWriteFile = 0;
  }
}

// Collect data from the sensors (HC-SR04-33 Ultrasonic Sensor, 4-20 mA pressure sensor, and Barometer). This function is called by a software timer interrupt at a defined interval (DATACOLLECTIONTIMER, typically 1000 ms).
void GetDataInput() { // takes 7-11 ms, typically 8 ms
  unsigned long lDuration;
  float fTempD;
  long lTimeStart;

  lTimeLoopStart = millis(); // NOTE millis() overflows after ~49 days, so only use during software testing phase

  lTimeStart = lTimeLoopStart; // START TIME for the duration of getting all input data

  digitalWrite(boardLed,HIGH);  // pulse the board blue LED on for the duration of the data sampling AND processing time

  // Get top of water height using UT sensor (typically 29 - 42 cm)
  /* Trigger the UT sensor by sending a HIGH pulse of 10 or more microseconds */
  digitalWriteFast(trig_pin, HIGH);
  delayMicroseconds(1); // delay of 1 results in a pulse of 15-17 usec (10 => 22-23 usec !)
  digitalWriteFast(trig_pin, LOW);
  lDuration = pulseIn(echo_pin, HIGH);
  /* Convert the time into a distance */
  // Sound travels at 340 m/s (29 us/cm), out and back so divide by 2
  // Ref: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  // cm = duration / 29 / 2
  fCm = lDuration / 58.3;
  if (fCm < 0.0) {fCm = 0.0;} // lower limit
  if (fCm > 400.0) {fCm = 400.0;} // upper limit

  // get 4-20 mA current for depth of water (0-1 m), typically 2 - 34 cm ???
  // not sure if the following two lines are needed, or if they help ???
  pinMode(D19, OUTPUT);
  digitalWrite(D19, HIGH);
  // since 4-20 mA R click data is way too noisy, use Vref (2.048 V) and Vin+ (0.4-2.0 V, for 4-20 mA) to calculate mA
  iVref = analogRead(VrefPin); // 0-4095, for 0-3.3V
  iVin = analogRead(VinPin); // 0-4095, for 0-3.3V
  fVref = 3.3 * (iVref / 4095.0); // convert to voltage
  fVin = 3.3 * (iVin / 4095.0); // convert to voltage
  fD1 = 100.0 * ((((C420MARVREF / fVref) * fVin) - 0.4) / 1.6); // compensate based on Vref, convert to cm of depth
  if (fD1 < 0.0) {fD1 = 0.0;} // lower limit, 0 m
  if (fD1 > 100.0) {fD1 = 100.0;} // upper limit, 1 m
  // now, get 4-20 mA values (just log the values to assess sometime later, since way too noisy for use at present)
  fmA = c420mar_read_data( &c420mar );
  if (fmA < 4.0) {fmA = 4.0;} // lower limit
  if (fmA > 20.0) {fmA = 20.0;} // upeer limit
  fTempD = (fmA - 4.0) / 16.0; // convert 4-20 mA to m of water
  fDepth = 100.0 * ((fTempD + FMACOMPENSATION)); // correct for present probe's relative offset, and convert to cm (i.e. can ignore Barom.)

  // Get Barometer info (not sure if this might interfere with 4-20 mA, so do last ???)
  fBTemperature_c = barometer_get_temperature_c( &barometer );
  //Delay_100ms( );
  fBPressure = barometer_get_pressure( &barometer ); // hPa (typically 1000.0)

  lmsGetData = millis() - lTimeStart; // END TIME for the duration of getting all input data (7-11, typically 7 ms)
  if (lmsGetData > 1000) {
    lmsGetData = 999; // cap at 999 ms (in case millis() overflows after ~49 days, or some other issue causes a really long loop time, so that it doesn't mess up the data formatting in the file and remote TCP)
  }

  iNewDataAvailable += 1; // used to indicate new data is available from interrupt routine
  //bWaitForData = FALSE; // used to indicate interrupt routine has started 1st time, so can exit setup() and run loop()
}

// Create a software timer to call the GetDataInput() function at a defined interval (DATACOLLECTIONTIMER, typically 1000 ms) to collect data from all input sensors.
Timer timer1(DATACOLLECTIONTIMER, GetDataInput); // set data input interrupt routine rate, typically 1000 ms

// Update the data string (cDataString) that will be saved to the log file on the microSD card. This function formats the collected data (date, time, sensor readings) into a comma-separated string that can be easily saved to a CSV file for later analysis.
void UpdateDataForFileOutput() { // make sure cDataString[] is big enough to hold all data below!!
    cDataString[0]='\0'; // also used by PrepareHeaderForFile
    
    snprintf(cData, sizeof(cData), "%d", iYear);
    strcat(cDataString, cData);
    strcat(cDataString, ",");    
    snprintf(cData, sizeof(cData), "%d", iMonth);
    strcat(cDataString, cData);
    strcat(cDataString, ",");    
    snprintf(cData, sizeof(cData), "%d", iDay);
    strcat(cDataString, cData);
    strcat(cDataString, ",");    
    snprintf(cData, sizeof(cData), "%d", iHour);
    strcat(cDataString, cData);
    strcat(cDataString, ",");    
    snprintf(cData, sizeof(cData), "%d", iMinute);
    strcat(cDataString, cData);
    strcat(cDataString, ",");    
    snprintf(cData, sizeof(cData), "%d", iSecond);
    strcat(cDataString, cData);
    strcat(cDataString, ",");    // 20 bytes (typical)

    snprintf(cData, sizeof(cData), "%.1f", fCm);
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%.1f", fBTemperature_c);
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%.1f", fBPressure);
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%.1f", fDepth);
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%.3f", fmA);
    strcat(cDataString, cData);
    strcat(cDataString, ",");   // 28-29 bytes

    snprintf(cData, sizeof(cData), "%.3f", fVref);
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%.3f", fVin);
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%.1f", fD1);
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%d", int(lmsGetData));
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%d", int(lmsWriteFile));
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%d", int(lmsTotalLoop));
    strcat(cDataString, cData);
    strcat(cDataString, ",");     // 25-28 bytes

    snprintf(cData, sizeof(cData), "%.1f", fCmMedNow); // changed from med 3 to med 25 2026-03-30
    strcat(cDataString, cData);
    strcat(cDataString, ",");
    snprintf(cData, sizeof(cData), "%.1f", fD1MedNow); // changed from med 5 to med 25 2026-03-30
    strcat(cDataString, cData);
    //strcat(cDataString, ",");
    //snprintf(cData, sizeof(cData), "%.1f", fD1MxNow);
    //strcat(cDataString, cData);   // 14+1 bytes, total 88-92 bytes
    //strcat(cDataString, ",");
}

// Prepare the header string (cDataString) that will be saved as the first line of the log file on the microSD card.
void PrepareHeaderForFile() { // make sure cDataString[] is big enough to hold all data below!!
    cDataString[0]='\0'; // also used by UpdateDataForFileOutput
    
    strcat(cDataString, "Year,");    
    strcat(cDataString, "Month,");    
    strcat(cDataString, "Day,");    
    strcat(cDataString, "Hour,");    
    strcat(cDataString, "Min,");    
    strcat(cDataString, "Sec,");   // 28 bytes
    strcat(cDataString, "H (cm),");
    strcat(cDataString, "T (C),");
    strcat(cDataString, "B (hPa),");
    strcat(cDataString, "D (cm),");
    strcat(cDataString, "D (mA),");   // 35 bytes
    strcat(cDataString, "Vref (V),");
    strcat(cDataString, "V1 (V),");
    strcat(cDataString, "D1 (cm),");
    strcat(cDataString, "T1 (ms),");
    strcat(cDataString, "T2 (ms),");
    strcat(cDataString, "TT (ms),");  // 48 bytes
    strcat(cDataString, "Hm25,");
    strcat(cDataString, "D1m25,");
    //strcat(cDataString, "D1m99");     // 16+1 bytes, total 128 bytes
}

// Print the collected data to the serial port for debugging and assessment purposes.
//void PrintOutputToSerial() {
  // print outputs to assess what is working
//  Serial.printf("cm = %i", iCm);
//  Serial.printf(", T = %.1f", fBTemperature_c); //floats default to 6 decimal places
//  Serial.printf(", Barom = %.1f", fBPressure);
//  Serial.printf(", Depth = %.1f", fDepth);
//  Serial.printf(", mA = %.3f", fmA);
//  Serial.printf(", Vref = %.3f", fVref);
//  Serial.printf(", Vin = %.3f", fVin);
//  Serial.printf(", D1 = %.1f", fD1);
//  Serial.printf(", Present time: %4i:%2i:%2i %2i:%2i:%2i", iYear, iMonth, iDay, iHour, iMinute, iSecond);
//  Serial.printf(", msData = %i, msFile = %i, msTotal = %i", int(lmsGetData), int(lmsWriteFile), int(lmsTotalLoop));
//  Serial.println();
//}

// Update the cloud variables that are displayed on the Particle Cloud and can be accessed through the Particle API.
void UpdateCloudVariables() { // NOTE: UpdateDateAndTime() used to update sDisplayDate, sDisplayTime
  float fPercent;

  sCm = String(fCm,1);                    // raw data, distance from UT sensor to top of water, cm
  fPercent = ((CM100 - fCmMedNow) / (CM100 - CMZERO)) * 100.0;
  sCmMedAll = String(CM100,0) + ", " + String(fCmMedNow,1) + "(" + String(fPercent,0) + "%), " + String(CMZERO,0);      // median UT cm, Now

  sD1 = String(fD1,1);                  // raw Vref from 4-20 mA input (2.048V)
  fPercent = ((fD1MedNow - D1ZERO) / (D1100 - D1ZERO)) * 100.0;
  sD1MedAll = String(D1ZERO,0) + ", " + String(fD1MedNow,1) + "(" + String(fPercent,0) + "%), " + String(D1100,0);      // median depth1 of water, cm, Now

  sBar = String(fBTemperature_c,1) + ", " + String(fBPressure,1); // temperature and barometric pressure

  sSumpTodayOperations = String(iSumpCmDailyOps) + ", " + String(iSumpD1DailyOps); // number of times the sump pump has operated today

  sStatus = "Normal";
  if ((bCmTooLow) || (bD1TooHigh)) { // used to indicate value(s) that caused high water, if any
    sStatus = "High Water ";
  }
  if (bCmTooLow) {
    sStatus = sStatus + "H: " + String(fCmMedNowTooHigh,1); // add the value that triggered the high water flag
  }
  if (bD1TooHigh) {
    if (bCmTooLow) {
      sStatus = sStatus + ", ";
    }
    sStatus = sStatus + "D1: " + String(fD1MedNowTooHigh,1); // add the value that triggered the high water flag
  }

  smsTotalLoop = String(lmsGetData) + ", " + String(lmsWriteFile) + ", " + String(lmsTotalLoop);  // time (ms) for total loop processing of data
}

// Set up the cloud variables that will be displayed on the Particle Cloud dashboard and can be accessed through the Particle API.
void SetupCloudVariables() {
  //Particle.variable("d_Time_ms", dTime); //text description must NOT have any spaces
  //items are listed in alphabetical order on iPhone App (max 20)
  Particle.variable("1 Date",sDisplayDate);
  Particle.variable("2 Time", sDisplayTime);
  Particle.variable("3 Height (cm)", sCm);
  Particle.variable("4 Hm25", sCmMedAll);
  Particle.variable("5 Depth1 (cm)",sD1);
  Particle.variable("6 D1m25", sD1MedAll);
  Particle.variable("7 C, hPa", sBar);
  Particle.variable("81 Sump # Now", sSumpTodayOperations);
  Particle.variable("82 Sump # Yesterday", sSumpYesterdayOperations); // gets updated at midnight, so shows yesterday's total operations
  Particle.variable("83 Status",sStatus);
  Particle.variable("9 Timer ms",smsTotalLoop);
}

// send information to Raspberry Pi for email alerts
void SendTCPemail() {
  strncpy(cEmail, sEmail.c_str(), sizeof(cEmail) - 1); // fills rest of cEmail with null chars if sTestEmail is shorter than cEmail, and ensures no buffer overflow if sTestEmail is longer than cEmail
  cEmail[sizeof(cEmail) - 1] = '\0'; // Ensure null-termination
  if (clientEmail.connect(serverEmail, TCPEMAILPORT)) {
    clientEmail.println(cEmail);
    clientEmail.stop();
    iEmailSent = 1; // use as binary for now, but might consider changing to a count of emails sent, if want to track that at some point
  } else {
    iEmailSent = 0;
  }
}





// Particle Functions
int SendTestEmail (String command) {
  sEmail = "Sump1 test email from Particle Sump at " + sDisplayDate + " " + sDisplayTime;
  SendTCPemail();
  return iEmailSent;
}
// Set last digit of IP for Raspberry Pi for email alerts
int SetEmailTCPAddr (String sAddr) {
  int Addr = 100;
  if (sscanf(sAddr, "%d", &Addr) == 1) {
    serverEmail[3] = Addr;
  } else {
    serverEmail[3] = 100; // default
  }
  return Addr;
}
// Stop data logging, so power can be removed to take out SD card
int StopDataLogging (String command) { //allow any text to stop data logging to SD file
  bSDEnabled = FALSE;
  //Serial.println("Data Logging to SD file stopped!");
  return 1;
}
// Restart system, such as if the system Time is incorrect
int ResartSystem (String command) { // allow any text to reset the values
  System.reset();
  return 1;
}
// Reset high water flags due to false trigger (sump is working, so reset flags)
int ResetHighWaterFlags(String command) { // allow any text to reset the values
  bCmTooLow = FALSE;
  bD1TooHigh = FALSE;
  bHighWaterAlertSent = FALSE; // reset flag to allow another high water email alert to be sent if needed
  return 1;
}
// Enable remote TCP logging to port TCPLOGGINGPORT (23 for Telnet by default)
int EnableTCPLogging (String command) {
  if (client.connect(server, TCPLOGGINGPORT)) {
    bTCPLoggingEnabled = TRUE;
    return 0;
  }
  return 1;
}
// Disable remote TCP logging to port 23 (Telnet)
int DisableTCPLogging (String command) {
  bTCPLoggingEnabled = FALSE;
  client.stop();
  return 0;
}
// Set last digit of IP for TCP logging to port 23 (Telnet)
int SetTCPAddr (String sAddr) {
  int Addr = 99;
  if (sscanf(sAddr, "%d", &Addr) == 1) {
    server[3] = Addr;
  } else {
    server[3] = 99; // default
  }
  return Addr;
}





// setup() runs once, when the device is first turned on
void setup() {
  // Register Particle.variable() within 30 s of connecting to the cloud so that we can access the values from the cloud.
  SetupCloudVariables();
  Particle.function("StopDataLogging", StopDataLogging); //so that data logging can be stopped, for power off and SD card removal
  Particle.function("ResartSystem", ResartSystem); // restart system, such as when Date/Time is incorrect
  Particle.function("ResetHighWaterFlags", ResetHighWaterFlags); // reset the flags that indicate water level is too high (sump off)
  Particle.function("EnableTCPLogging", EnableTCPLogging);
  Particle.function("DisableTCPLogging", DisableTCPLogging);
  Particle.function("SetTCPAddr", SetTCPAddr);
  Particle.function("SetEmailTCPAddr", SetEmailTCPAddr);
  Particle.function("SendTestEmail", SendTestEmail);

  // Declare variables needed for initialization
  c420mar_cfg_t c420mar_cfg;
  barometer_cfg_t barometer_cfg;

  pinMode(boardLed, OUTPUT);  // use blue board LED to blink ON whenever data sampling is occurring
  digitalWrite(boardLed, HIGH); // turn on while initializing

  // set up USB serial printing to computer terminal, comment out when not needed for testing
  //Serial.begin(9600);   // open serial over USB (COM3 on PuTTY, on my computer USB)
  //waitFor(Serial.isConnected, 30000); // Wait for a USB serial connection for up to 30 seconds
  //Serial.println("Hello there!");

  // setup for HC-SR04-33 Ultrasonic Sensor on Terminal Click on left feather wing
  //Serial.println("HC-SR04-33 UT initializing"); // comment out when not needed for testing
  pinMode(trig_pin, OUTPUT);
  digitalWriteFast(trig_pin, LOW);
  pinMode(echo_pin, INPUT);

  // since 4-20 mA R click data is way too noisy, read Vref and Vin+ directly, to calculate mA
  //Serial.println("c420mar raw connections initializing"); // comment out when not needed for testing
  pinMode(VrefPin, AN_INPUT);
  pinMode(VinPin, AN_INPUT);
  // SM - force EN high for c420mar on left feather wing for now, but check if this is done in code somewhere
  //pinMode(D19, OUTPUT);
  //digitalWrite(D19, HIGH);

  //  4-20 mA Input Click initialization
  //Serial.println("4-20 mA initializing"); // comment out when not needed for testing
  c420mar_cfg_setup( &c420mar_cfg );
  c420MAR_MAP_MIKROBUS( c420mar_cfg, C420MARBUS ); // uses SPI 1 on left feather wing
  c420mar_init( &c420mar, &c420mar_cfg );

  // Initialize microSD using HARDWARE SPI
  if (!SD.begin(chipSelect)) {                    // uses SPI 1 on right feather wing
    bSDEnabled = FALSE; // microSD card is not available for data logging
  //  Serial.println("SD Card failed, or is not present"); // comment out when not needed for testing
  }
  else {
    //Serial.println("SD card initialized"); // comment out when not needed for testing
    bSDEnabled = TRUE; // microSD card is available for data logging
  }

  // Barometer Click initialization (I2C-1) SM NOTE: needed to disable cs and rdy, since they conflict with 420mar
  //Serial.println("Barometer initializing"); // comment out when not needed for testing
  barometer_cfg_setup( &barometer_cfg ); // initialize mainly I2C and SPI stuff
  BAROMETER_MAP_MIKROBUS( barometer_cfg, BAROMBUS ); // initialize remaining stuff for using SPI or I2C bus 1
  // scl=D1, sda=D0, miso=S1/D16, mosi=S0/D15, sck=S2/D17, cs=S3/D18, rdy/int=S4/D19, I2C Address = 0x5D
  barometer_init( &barometer, &barometer_cfg );
  barometer_default_cfg( &barometer );
  // Check sensor ID, comment out when not needed for testing
  if ( barometer_check_id( &barometer ) != BAROMETER_DEVICE_ID ) {
    //  Serial.println("    ERROR when initializing Barometer" );
    delay(1);
  }
  
  // The date and time is not valid for power up and the first few reboots, so see if waiting a bit helps
  delay(30000); // delay for 30 s so cloud connection can be made for getting correct date and time
  //Serial.println("Waiting for up to 60 s for date & time from cloud"); // comment out when not needed for testing
  waitFor(Time.isValid, 60000); // wait up to 60 s for time from cloud (not sure if the delay works !??)
  if (!(Time.isValid())) { // initialize to some dummy date and time
    iYear = 2000;
    iMonth = 1;
    iDay = 1;
    iHour = 0;
    iMinute = 0;
    iSecond = 10;
  //  Serial.println("Cloud not presently available, so setting date and time manually"); // comment out when not needed for testing
  }
  else {
    //  Serial.println("Date and time set from Cloud"); // comment out when not needed for testing
    delay(1); // not needed?
  }

  iNewDataAvailable = 0; // counter to indicate how many data sets are available for processing (typically every data set is processed immediatiely)
  timer1.start(); // start getting input data every DATACOLLECTIONTIMER ms (typically 1000 ms = 1 s)
  while (iNewDataAvailable < 1) { // get data for iCm, fDepth, fD1, etc.
    delay(1); // do nothing while waiting for Interrupt routine to start (so data is available for loop() to run)
  }
  iNewDataAvailable = 0; // reset re Interrupt routine data available

  // initialize variables used in loop()
  for (int i = 0; i < MEDIANFILTERSIZE; i++) {
    fCmMedFilter.addSample(34.0); // NOTE: Need MEDIANFILTERSIZE samples before median is calculated
    fD1MedFilter.addSample(19.0); // NOTE: Need MEDIANFILTERSIZE samples before median is calculated
  }
  
  EEPROM.get(20, iSumpCmDailyOps); // yesterday's count sump pump operations based on UT Cm
  if ((iSumpCmDailyOps > 100) || (iSumpCmDailyOps < 0)) { // if the value was not initialized
    iSumpCmDailyOps = 0;
    EEPROM.put(20, iSumpCmDailyOps); // save the initialized value to EEPROM
  }
  EEPROM.get(30, iSumpD1DailyOps); // yesterday's count sump pump operations based on D1 (D is too noisy to be useful for this)
  if ((iSumpD1DailyOps > 100) || (iSumpD1DailyOps < 0)) { // if the value was not initialized
    iSumpD1DailyOps = 0;
    EEPROM.put(30, iSumpD1DailyOps); // save the initialized value to EEPROM
  }
  sSumpYesterdayOperations = String(iSumpCmDailyOps) + ", " + String(iSumpD1DailyOps); // number of times the sump pump has operated yesterday

  EEPROM.get(0, iSumpCmDailyOps); // count sump pump operations based on UT Cm
  if ((iSumpCmDailyOps > 100) || (iSumpCmDailyOps < 0)) { // if the value was not initialized
    iSumpCmDailyOps = 0;
    EEPROM.put(0, iSumpCmDailyOps); // save the initialized value to EEPROM
  }
  EEPROM.get(10, iSumpD1DailyOps); // count sump pump operations based on D1 (D is too noisy to be useful for this)
  if ((iSumpD1DailyOps > 100) || (iSumpD1DailyOps < 0)) { // if the value was not initialized
    iSumpD1DailyOps = 0;
    EEPROM.put(10, iSumpD1DailyOps); // save the initialized value to EEPROM
  }
  //sSumpTodayOperations = String(iSumpCmDailyOps) + ", " + String(iSumpD1DailyOps); // number of times the sump pump has operated today
  
  bCmLow = FALSE;
  bCmHigh = FALSE;
  bCmTooLow = FALSE;
  bD1Low = FALSE;
  bD1High = FALSE;
  bD1TooHigh = FALSE;
  lmsWriteFile = 0;
  fCmMedNowTooHigh = 0.0; // This will be set when the high water flag bCmTooLow is triggered
  fD1MedNowTooHigh = 0.0; // This will be set when the high water flag bD1TooHigh is triggered

  iLastDayTimeSynced = 100; // invalid vaue, to force Particle.syncTime() in UpdateDateAndTime()
  UpdateDateAndTime();
  CreateFilename();
  PrepareHeaderForFile(); // always save file data header info when first booting up
  SaveInfoToFile(); // save header to SD file
  // the following two flags are used to perform a finite 4-state machine operation
  bNewDay = FALSE; // only add header once
  bHeaderDataSaved = FALSE; // only add header once per day
  bTCPLoggingEnabled = FALSE; // disable TCP logging by default

  digitalWrite(boardLed, LOW); // turn off since initializing finished
}







// the OS runs loop() over and over again, as quickly as it can execute, typically 1000 per s
void loop() {

  if (iNewDataAvailable > 0) { // data is available from interrupt routine, typically every 1 s

    iNewDataAvailable = 0; // reset re Interrupt routine data available

    // update Median 25 filters data (MEDIANFILTERSIZE)
    fCmMedFilter.addSample(fCm); // the data will automatically get sorted when new data is added
    fCmMedNow = fCmMedFilter.getMedian();
    fD1MedFilter.addSample(fD1); // the data will automatically get sorted when new data is added
    fD1MedNow = fD1MedFilter.getMedian();

    // update sump operations counter for UT cm, as needed, and sump not operating status
    if (fCmMedNow < CMLOW) { bCmLow = TRUE; } // a low point (high water) has been reached
    if (fCmMedNow > CMHIGH) { bCmHigh = TRUE; } // presently a high point (low water)
    else { bCmHigh = FALSE; } // not presently a high point (not low water)
    if (bCmLow && bCmHigh) { // a high point after a low point, so pump must have operated
      iSumpCmDailyOps++; // record that the sump pump operated once
      EEPROM.put(0, iSumpCmDailyOps); // save the updated value to EEPROM
      bCmLow = FALSE; // reset, so can monitor for another pump operation
    }
    if (fCmMedNow < CMTOOLOW) {
      bCmTooLow = TRUE; // monitor if sump pump didn't operate
      fCmMedNowTooHigh = fCmMedNow; // save the median value that was too low (high water)
      if (!bHighWaterAlertSent) { // only send alert once, until reset by user
        sEmail = "Sump1 high water alert at " + sDisplayDate + " " + sDisplayTime + ", H = " + String(fCmMedNowTooHigh,1) + " cm"; // add the value that triggered the high water flag
        SendTCPemail(); // send email alert via Raspberry Pi, if water is too high (i.e. cm is too low)
        if (iEmailSent == 1) { // if iEmailSend is used as other than binary, then need to compare old to new value
          bHighWaterAlertSent = TRUE; // email alert was sent successfully
        }
      }
    }

    // update sump operations counter for D1, as needed, and sump not operating status
    if (fD1MedNow > D1HIGH) { bD1High = TRUE; } // a high water point has been reached
    if (fD1MedNow < D1LOW) { bD1Low = TRUE; } // presently a low water point
    else { bD1Low = FALSE; } // not presently a low water point
    if (bD1Low && bD1High) { // a low point after a high point, so pump must have operated
      iSumpD1DailyOps++; // record that the sump pump operated once
      EEPROM.put(10, iSumpD1DailyOps); // save the updated value to EEPROM
      bD1High = FALSE; // reset, so can monitor for another pump operation
    }
    if (fD1MedNow > D1TOOHIGH) {
      bD1TooHigh = TRUE; // monitor if sump pump didn't operate
      fD1MedNowTooHigh = fD1MedNow; // save the median value that was too high
      if (!bHighWaterAlertSent) { // only send alert once, until reset by user
        sEmail = "Sump1 high water alert at " + sDisplayDate + " " + sDisplayTime + ", D1 = " + String(fD1MedNow,1) + " cm"; // add the value that triggered the high water flag
        SendTCPemail(); // send email alert via Raspberry Pi, if water is too high
        if (iEmailSent == 1) { // if iEmailSend is used as other than binary, then need to compare old to new value
          bHighWaterAlertSent = TRUE; // email alert was sent successfully
        }
      }
    }

    // save data to SD file
    UpdateDateAndTime();
    CreateFilename();
    // the following two flags are used to perform a finite 4-state machine operation
    if (bNewDay) {
      if (!bHeaderDataSaved) {
        sEmail = "Sump1 daily pump operations for " + sLastDate + ": " + String(iSumpCmDailyOps) + " based on UT Cm, " + String(iSumpD1DailyOps) + " based on D1"; // add yesterday's count of sump pump operations to email alert
        PrepareHeaderForFile(); // add file data header to the daily new SD file
        SaveInfoToFile(); // save header to SD file
        bHeaderDataSaved = TRUE; // only add header once per day
        EEPROM.put(20, iSumpCmDailyOps); // save today's count to yesterday's count, based on UT Cm
        EEPROM.put(30, iSumpD1DailyOps); // save today's count to yesterday's count, based on D1
        sSumpYesterdayOperations = String(iSumpCmDailyOps) + ", " + String(iSumpD1DailyOps); // number of times the sump pump has operated yesterday
        iSumpCmDailyOps = 0; // reset counter for a new day
        EEPROM.put(0, iSumpCmDailyOps); // save the initialized value to EEPROM
        iSumpD1DailyOps = 0; // reset counter for a new day
        EEPROM.put(10, iSumpD1DailyOps); // save the initialized value to EEPROM
      }
      if (!bDailySumpOperationsSent) { // only check during bNewDay (1st 10 s of a day), else email won't get sent
        SendTCPemail(); // send email alert via Raspberry Pi, with daily sump pump operations (see above for email content)
        if (iEmailSent == 1) { // if iEmailSend is used as other than binary, then need to compare old to new value
          bDailySumpOperationsSent = TRUE; // email with daily sump pump operations was sent successfully
        }
      }
    }
    else {
      bHeaderDataSaved = FALSE;
      bDailySumpOperationsSent = FALSE;
    }
    UpdateDataForFileOutput();
    SaveInfoToFile(); // save data to SD file

    // update cloud variables
    UpdateCloudVariables();

    digitalWrite(boardLed,LOW); // pulse the board blue LED on for the duration of the data sampling AND processing time (not serial or remote TCP time)

    //PrintOutputToSerial(); // comment out when not needed for testing
    
    // send data to remote TCP
    if (bTCPLoggingEnabled) {
      client.println(cDataString);
    }

    lmsTotalLoop = millis() - lTimeLoopStart; // typically 17-70 ms (if no serial or TCP Logging)
    if (lmsTotalLoop > 1000) {
      lmsTotalLoop = 999; // cap at 999 ms (in case millis() overflows after ~49 days, or some other issue causes a really long loop time, so that it doesn't mess up the data formatting in the file and remote TCP)
    }
  }
} // if no data from interrupt routine, exit loop() so other stuff can be done (i.e. non blocking by doing this)

drv_digital_in.h

C/C++
Interface to Mikroe hardware
/*
 * Copyright (c) 2023 Particle Industries, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _DRV_DIGITAL_IN_H_
#define _DRV_DIGITAL_IN_H_

#include "mikro_port.h"

//enum for status codes, added for library compatibility
typedef enum
{
    DIGITAL_IN_SUCCESS = 0,                 //Success
    DIGITAL_IN_UNSUPPORTED_PIN = (-1)       //Error
} digital_in_err_t;

//struc for the various mikroBUS pins, added for library port compatibility
typedef struct
{
    uint8_t pin;     //structure defining pin base and mask
} digital_in_t;

//digital out functions
int8_t digital_in_init(digital_in_t *in, uint8_t mode);
int32_t digital_in_read(digital_in_t *in);

#endif // _DRV_DIGITAL_IN_H_

drv_digital_out.cpp

C/C++
Interface to Mikroe hardware
No preview (download only).

drv_digital_out.h

C/C++
Interface to Mikroe hardware
/*
 * Copyright (c) 2023 Particle Industries, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _DRV_DIGITAL_OUT_H_
#define _DRV_DIGITAL_OUT_H_

#include "mikro_port.h"

//enum for status codes, added for library compatibility
typedef enum
{
    DIGITAL_OUT_SUCCESS = 0,                //Success
    DIGITAL_OUT_UNSUPPORTED_PIN = (-1)      //Error
} digital_out_err_t;

//struc for the various mikroBUS pins, added for library port compatibility
typedef struct
{
    uint16_t pin;    //Structure defining pin base and mask
} digital_out_t;

//digital out functions
int8_t digital_out_init(digital_out_t *out, uint8_t mode);
void digital_out_high(digital_out_t *out);
void digital_out_low(digital_out_t *out);
void digital_out_toggle(digital_out_t *out);
void digital_out_write(digital_out_t *out, uint8_t value);

#endif // _DRV_DIGITAL_OUT_H_

drv_i2c_master.cpp

C/C++
Interface to Mikroe hardware
No preview (download only).

drv_i2c_master.h

C/C++
Interface to Mikroe hardware
/*
 * Copyright (c) 2023 Particle Industries, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _DRV_I2C_MASTER_H_
#define _DRV_I2C_MASTER_H_

#include "mikro_port.h"

//enum for status codes, added for library compatibility
typedef enum
{
    I2C_MASTER_SUCCESS = 0,     //Success
    I2C_MASTER_ERROR = (-1)     //Error
} i2c_master_err_t;

//enum for various clock frequencies, added for library compatibility
typedef enum
{
    I2C_MASTER_SPEED_STANDARD = 0,      //standard clock frequency, 100kHz
    I2C_MASTER_SPEED_FULL = 1,          //fast mode clock frequency, 400kHz
    I2C_MASTER_SPEED_FAST = 2           //fast mode Plus clock frequency, 1MHz
} i2c_master_speed_t;

//struct for various I2C function parameters, added for library compatibility
typedef struct
{
    uint8_t addr;                       //client address
    pin_name_t sda;                     //SDA pin
    pin_name_t scl;                     //SCL pin
    uint32_t speed;                     //SCL clock rate
    uint16_t timeout_pass_count;        //timeout value , # of retries
} i2c_master_config_t;

//struc for i2c master configuration, added for library compatibility
typedef struct
{
    i2c_master_config_t config;     //I2C configuration structure
} i2c_master_t;

//i2c functions
void i2c_master_configure_default(i2c_master_config_t *config);
int8_t i2c_master_open(i2c_master_t *obj, i2c_master_config_t *config);
int8_t i2c_master_set_speed(i2c_master_t *obj, uint32_t speed);
int8_t i2c_master_set_timeout(i2c_master_t *obj, uint16_t timeout_pass_count);
int8_t i2c_master_set_slave_address(i2c_master_t *obj, uint8_t address);
int8_t i2c_master_write(i2c_master_t *obj, uint8_t *write_data_buf, size_t len_write_data);
int8_t i2c_master_read(i2c_master_t *obj, uint8_t *read_data_buf, size_t len_read_data);
int8_t i2c_master_write_then_read(i2c_master_t *obj, uint8_t *write_data_buf, size_t len_write_data, uint8_t *read_data_buf, size_t len_read_data);
void i2c_master_close(i2c_master_t *obj);

#endif // _DRV_I2C_MASTER_H_

drv_spi_master.cpp

C/C++
Interface to Mikroe hardware
/*
 * Copyright (c) 2023 Particle Industries, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "drv_spi_master.h"

//global variable
static uint8_t cs_polarity;        //CS polarity variable, static as previous value should be retained

//Configures spi_master_config_t struct to default initialization values
void spi_master_configure_default(spi_master_config_t *config) 
{
    //set default values for pins (CS)
    pinMode(MIKROBUS_1_CS_PIN, OUTPUT);         //set CS sginal mikroBUS1 as output
    pinMode(MIKROBUS_2_CS_PIN, OUTPUT);         //set CS sginal mikroBUS2 as output
    digitalWrite(MIKROBUS_1_CS_PIN, HIGH);      //set CS HIGH
    digitalWrite(MIKROBUS_2_CS_PIN, HIGH);      //set CS HIGH
    cs_polarity = LOW;              //set CS active low          

    //set default values for spi mode and speed
    SPI.setDataMode(SPI_MASTER_MODE_0);     //set default spi mode
    SPI.setClockSpeed(100, KHZ);            //set default speed, 100kHz

}

//open SPI peripheral
int8_t spi_master_open(spi_master_t *obj, spi_master_config_t *config) 
{   
//    #warning spi_master_open() assumes SPI peripheral will not get stolen by other threads
    pinMode(MIKROBUS_1_CS_PIN, OUTPUT);             //set CS sginal mikroBUS1 as output
    pinMode(MIKROBUS_2_CS_PIN, OUTPUT);             //set CS sginal mikroBUS2 as output
    digitalWrite(MIKROBUS_1_CS_PIN, HIGH);          //set CS HIGH
    digitalWrite(MIKROBUS_2_CS_PIN, HIGH);          //set CS HIGH
    SPI.begin(SPI_MODE_MASTER, PIN_INVALID);        //begin SPI transaction, not selecting CS pin
    return SPI_MASTER_SUCCESS;                      //return status
}

//assert chip select
void spi_master_select_device(uint8_t chip_select)
{   
    pinMode(chip_select, OUTPUT);               //set function parameter as output
    digitalWrite(chip_select, cs_polarity);     //assert CS 
}

//de-assert chip select
void spi_master_deselect_device(uint8_t chip_select) 
{  
    pinMode(chip_select, OUTPUT);                   //set function parameter as output
    digitalWrite(chip_select, !cs_polarity);        //de-assert CS 
}

//sets chip select polarity
void spi_master_set_chip_select_polarity(uint8_t polarity)
{   
    if ((polarity == LOW) || (polarity == HIGH))        //check for valid function parameter
    {
        cs_polarity = polarity;                         //set new CS polarity
    }
}

//set SPI clock speed
int8_t spi_master_set_speed(spi_master_t *obj, uint32_t speed)  
{
    if(SPI.setClockSpeed(speed) == speed)       //set clock speed to function parameter and check to confirm it is set
    {
        return SPI_MASTER_SUCCESS;              //return status
    }
    return SPI_MASTER_ERROR;                    //return status
}

//Sets SPI Mode
int8_t spi_master_set_mode(spi_master_t *obj, uint8_t mode)
{
    if((mode >= SPI_MODE0) || (mode <= SPI_MODE3))      //check that paramter input is valid
    {
        SPI.setDataMode(mode);                          //set spi mode to function parameter
        return SPI_MASTER_SUCCESS;                      //return status
    }
    return SPI_MASTER_ERROR;                            //return status
}

//sets SPI default value, not implemented
int8_t spi_master_set_default_write_data(spi_master_t *obj, uint8_t  default_write_data) 
{
//    #warning spi_master_set_default_write_data() function is not implemented
    return SPI_MASTER_SUCCESS;      //return status
}

//write byte(s) to SPI bus
int8_t spi_master_write(spi_master_t *obj, uint8_t *write_data_buffer, size_t write_data_length)
{
//    #warning spi_master_write() assumes the fist element of write_data_buffer is the first byte to be transmitted, IE the opcode/register if required
    
    //create dummy array size of write fill with zeros
    uint8_t write_dummy[write_data_length] = {0};       //required for SPI.transfer read/write arrays must be of same size
    if(SPI.beginTransaction() == SPI_MASTER_ERROR)      //setup fail
    {
        return SPI_MASTER_ERROR;                        //return status
    }
    
    SPI.transfer(write_data_buffer, write_dummy, write_data_length, NULL);      //spi transaction for write (read = NULL)
    SPI.endTransaction();                                                       //end transaction and release spi peripheral lock
    return SPI_MASTER_SUCCESS;                                                  //return status
}

//Reads byte(s) from SPI bus
int8_t spi_master_read(spi_master_t *obj, uint8_t *read_data_buffer, size_t read_data_length)  
{   
//    #warning spi_master_read() assumes the read immediately occurs on first SCK. If data needs to be transmitted before read, use spi_master_write_then_read() instead
    
    //create dummy array size of read fill with zeros
    uint8_t read_dummy[read_data_length] = {0};     //required for SPI.transfer read/write arrays must be of same size  

    if(SPI.beginTransaction() == SPI_MASTER_ERROR)      //setup fail
    {
        return SPI_MASTER_ERROR;                        //return status
    }

    SPI.transfer(read_dummy, read_data_buffer, read_data_length, NULL);     //spi transaction for read (write = NULL)
    SPI.endTransaction();                                                   //end transaction and release spi peripheral lock
    return SPI_MASTER_SUCCESS;                                              //return status
}

//write a sequence of byte(s) to SPI bus followed by read
int8_t spi_master_write_then_read(spi_master_t *obj, uint8_t *write_data_buffer, size_t length_write_data, uint8_t *read_data_buffer, size_t length_read_data)   
{
//    #warning spi_master_write_then_read() assumes the fist element of write_data_buffer is the first byte to be transmitted, IE the opcode/register if required

    //create dummy array size write/read and fill with zeros
    uint8_t write_dummy[length_write_data] = {0};       //required for SPI.transfer read/write arrays must be of same size  
    uint8_t read_dummy[length_read_data] = {0};         //required for SPI.transfer read/write arrays must be of same size  

    if(SPI.beginTransaction() == SPI_MASTER_ERROR)      //setup fail
    {
        return SPI_MASTER_ERROR;                        //return status
    }
    
    SPI.transfer(write_data_buffer, write_dummy, length_write_data, NULL);      //spi transaction for write (read = NULL)
    SPI.transfer(read_dummy, read_data_buffer, length_read_data, NULL);         //spi transaction for read (write = NULL)
    SPI.endTransaction();                                                       //end transaction and release spi peripheral lock
    return SPI_MASTER_SUCCESS;                                                  //return status                                         
}                                                           
                                                             
//close SPI peripheral
void spi_master_close(spi_master_t *obj) 
{   
    SPI.end();      //close spi peripheral
}

drv_spi_master.h

C/C++
Interface to Mikroe hardware
/*
 * Copyright (c) 2023 Particle Industries, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _DRV_SPI_MASTER_H_
#define _DRV_SPI_MASTER_H_

#include "mikro_port.h"

//struct for SPI configuration, added for library port compatibility
typedef struct
{
    uint8_t         default_write_data;     //SPI Master default write data
    uint8_t         sck;                    //SCK pin
    uint8_t         miso;                   //MISO pin
    uint8_t         mosi;                   //MOSI pin
    uint32_t        speed;                  //SPI clock speed
    uint8_t         mode;                   //SPI mode
} spi_master_config_t;

//struc for spi master configuration, added for library port compatibility
typedef struct
{
    spi_master_config_t config;     //SPI Master Driver configuration structure
} spi_master_t;

//enum for various SPI modes, added for library port compatibility
typedef enum {
    SPI_MASTER_MODE_0 = 0,                          //SPI MODE 0
    SPI_MASTER_MODE_1 = 1,                          //SPI MODE 1
    SPI_MASTER_MODE_2 = 2,                          //SPI MODE 2
    SPI_MASTER_MODE_3 = 3,                          //SPI MODE 3
    SPI_MASTER_MODE_DEFAULT = SPI_MASTER_MODE_0     //Default SPI mode - MODE 0
} spi_master_mode_t;

//enum chip select polarity, added for library port compatibility
typedef enum
{
    SPI_MASTER_CHIP_SELECT_POLARITY_ACTIVE_LOW = LOW,       //Chip Select Polarity - Active Low
    SPI_MASTER_CHIP_SELECT_POLARITY_ACTIVE_HIGH = HIGH,     //Chip Select Polarity - Active High
    SPI_MASTER_CHIP_SELECT_DEFAULT_POLARITY = LOW           //Chip Select Default Polarity - Active Low
} spi_master_chip_select_polarity_t;

//enum for status codes, added for library compatibility
typedef enum
{
    SPI_MASTER_SUCCESS = 0,     //Success
    SPI_MASTER_ERROR = (-1)     //Error
} spi_master_err_t;

//spi functions
void spi_master_configure_default(spi_master_config_t *config);                                                                                                     //Configures #spi_master_config_t structure to default initialization values
int8_t spi_master_open(spi_master_t *obj, spi_master_config_t *config);                                                                                             //Opens the SPI Master driver object on selected pins
void spi_master_select_device(uint8_t chip_select);                                                                                                                 //Sets digital output individual slave pin to logic 0
void spi_master_deselect_device(uint8_t chip_select);                                                                                                               //Sets digital output individual slave pin to logic 1
void spi_master_set_chip_select_polarity(uint8_t polarity);                                                                                                         //sets SPI Master chip select polarity either to active low or active high
int8_t spi_master_set_speed(spi_master_t *obj, uint32_t speed);                                                                                                     //Sets SPI Master module speed to passed value if possible
int8_t spi_master_set_mode(spi_master_t *obj, uint8_t mode);                                                                                                        //Sets SPI Master module mode to passed value if possible
int8_t spi_master_set_default_write_data(spi_master_t *obj, uint8_t default_write_data);                                                                            //Default write data is sent by driver when the data transmit buffer is shorter than data receive buffer
int8_t spi_master_write(spi_master_t *obj, uint8_t *write_data_buffer, size_t write_data_length);                                                                   //Writes byte to SPI bus in blocking mode                                        
int8_t spi_master_read(spi_master_t *obj, uint8_t *read_data_buffer, size_t read_data_length);                                                                      //Reads byte from SPI bus in blocking mode                                             
int8_t spi_master_write_then_read(spi_master_t *obj, uint8_t *write_data_buffer, size_t length_write_data, uint8_t *read_data_buffer, size_t length_read_data);     //Writes a sequence of bytes to SPI bus, followed by a corresponding read                                                                                      
void spi_master_close(spi_master_t *obj);                                                                                                                           //Closes SPI Master Driver context object

#endif // _DRV_SPI_MASTER_H_

fat-structs.h

C/C++
Interface to Mikroe hardware
/* Arduino SdFat Library
 * Copyright (C) 2009 by William Greiman
 *
 * This file is part of the Arduino SdFat Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino SdFat Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
#ifndef _FAT_STRUCTS
#define _FAT_STRUCTS

/**
 * \file
 * FAT file structures
 */
/*
 * mostly from Microsoft document fatgen103.doc
 * http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx
 */
//------------------------------------------------------------------------------
/** Value for byte 510 of boot block or MBR */
uint8_t const BOOTSIG0 = 0X55;
/** Value for byte 511 of boot block or MBR */
uint8_t const BOOTSIG1 = 0XAA;
//------------------------------------------------------------------------------
/**
 * \struct partitionTable
 * \brief MBR partition table entry
 *
 * A partition table entry for a MBR formatted storage device.
 * The MBR partition table has four entries.
 */
struct partitionTable {
          /**
           * Boot Indicator . Indicates whether the volume is the active
           * partition.  Legal values include: 0X00. Do not use for booting.
           * 0X80 Active partition.
           */
  uint8_t  boot;
          /**
            * Head part of Cylinder-head-sector address of the first block in
            * the partition. Legal values are 0-255. Only used in old PC BIOS.
            */
  uint8_t  beginHead;
          /**
           * Sector part of Cylinder-head-sector address of the first block in
           * the partition. Legal values are 1-63. Only used in old PC BIOS.
           */

  uint16_t beginSector : 6;
           /** High bits cylinder for first block in partition. */
  uint16_t beginCylinderHigh : 2;
          /**
           * Combine beginCylinderLow with beginCylinderHigh. Legal values
           * are 0-1023.  Only used in old PC BIOS.
           */
  uint8_t  beginCylinderLow;
			/**
           * Partition type. See defines that begin with PART_TYPE_ for
           * some Microsoft partition types.
           */
  uint8_t  type;
          /**
           * head part of cylinder-head-sector address of the last sector in the
           * partition.  Legal values are 0-255. Only used in old PC BIOS.
           */
  uint8_t  endHead;
          /**
           * Sector part of cylinder-head-sector address of the last sector in
           * the partition.  Legal values are 1-63. Only used in old PC BIOS.
           */
  uint16_t endSector : 6;
           /** High bits of end cylinder */
  uint16_t endCylinderHigh : 2;
          /**
           * Combine endCylinderLow with endCylinderHigh. Legal values
           * are 0-1023.  Only used in old PC BIOS.
           */
  uint8_t  endCylinderLow;
           /** Logical block address of the first block in the partition. */
  uint32_t firstSector;
           /** Length of the partition, in blocks. */
  uint32_t totalSectors;
}__attribute__ ((packed));
/** Type name for partitionTable */
typedef struct partitionTable part_t;
//------------------------------------------------------------------------------
/**
 * \struct masterBootRecord
 *
 * \brief Master Boot Record
 *
 * The first block of a storage device that is formatted with a MBR.
 */
struct masterBootRecord {
           /** Code Area for master boot program. */
  uint8_t  codeArea[440];
           /** Optional WindowsNT disk signature. May contain more boot code. */
  uint32_t diskSignature;
           /** Usually zero but may be more boot code. */
  uint16_t usuallyZero;
           /** Partition tables. */
  part_t   part[4];
           /** First MBR signature byte. Must be 0X55 */
  uint8_t  mbrSig0;
           /** Second MBR signature byte. Must be 0XAA */
  uint8_t  mbrSig1;
}__attribute__ ((packed));
/** Type name for masterBootRecord */
typedef struct masterBootRecord mbr_t;
//------------------------------------------------------------------------------
/** 
 * \struct biosParmBlock
 *
 * \brief BIOS parameter block
 * 
 *  The BIOS parameter block describes the physical layout of a FAT volume.
 */
struct biosParmBlock {
          /**
           * Count of bytes per sector. This value may take on only the
           * following values: 512, 1024, 2048 or 4096
           */
  uint16_t bytesPerSector;
          /**
           * Number of sectors per allocation unit. This value must be a
           * power of 2 that is greater than 0. The legal values are
           * 1, 2, 4, 8, 16, 32, 64, and 128.
           */
  uint8_t  sectorsPerCluster;
          /**
           * Number of sectors before the first FAT.
           * This value must not be zero.
           */
  uint16_t reservedSectorCount;
          /** The count of FAT data structures on the volume. This field should
           *  always contain the value 2 for any FAT volume of any type.
           */
  uint8_t  fatCount;
          /**
          * For FAT12 and FAT16 volumes, this field contains the count of
          * 32-byte directory entries in the root directory. For FAT32 volumes,
          * this field must be set to 0. For FAT12 and FAT16 volumes, this
          * value should always specify a count that when multiplied by 32
          * results in a multiple of bytesPerSector.  FAT16 volumes should
          * use the value 512.
          */
  uint16_t rootDirEntryCount;
          /**
           * This field is the old 16-bit total count of sectors on the volume.
           * This count includes the count of all sectors in all four regions
           * of the volume. This field can be 0; if it is 0, then totalSectors32
           * must be non-zero.  For FAT32 volumes, this field must be 0. For
           * FAT12 and FAT16 volumes, this field contains the sector count, and
           * totalSectors32 is 0 if the total sector count fits
           * (is less than 0x10000).
           */
  uint16_t totalSectors16;
          /**
           * This dates back to the old MS-DOS 1.x media determination and is
           * no longer usually used for anything.  0xF8 is the standard value
           * for fixed (non-removable) media. For removable media, 0xF0 is
           * frequently used. Legal values are 0xF0 or 0xF8-0xFF.
           */
  uint8_t  mediaType;
          /**
           * Count of sectors occupied by one FAT on FAT12/FAT16 volumes.
           * On FAT32 volumes this field must be 0, and sectorsPerFat32
           * contains the FAT size count.
           */
  uint16_t sectorsPerFat16;
           /** Sectors per track for interrupt 0x13. Not used otherwise. */
  uint16_t sectorsPerTrtack;
           /** Number of heads for interrupt 0x13.  Not used otherwise. */
  uint16_t headCount;
          /**
           * Count of hidden sectors preceding the partition that contains this
           * FAT volume. This field is generally only relevant for media
           *  visible on interrupt 0x13.
           */
  uint32_t hidddenSectors;
          /**
           * This field is the new 32-bit total count of sectors on the volume.
           * This count includes the count of all sectors in all four regions
           * of the volume.  This field can be 0; if it is 0, then
           * totalSectors16 must be non-zero.
           */
  uint32_t totalSectors32;
          /**
           * Count of sectors occupied by one FAT on FAT32 volumes.
           */
  uint32_t sectorsPerFat32;
          /**
           * This field is only defined for FAT32 media and does not exist on
           * FAT12 and FAT16 media.
           * Bits 0-3 -- Zero-based number of active FAT.
           *             Only valid if mirroring is disabled.
           * Bits 4-6 -- Reserved.
           * Bit 7	-- 0 means the FAT is mirrored at runtime into all FATs.
	         *        -- 1 means only one FAT is active; it is the one referenced in bits 0-3.
           * Bits 8-15 	-- Reserved.
           */
  uint16_t fat32Flags;
          /**
           * FAT32 version. High byte is major revision number.
           * Low byte is minor revision number. Only 0.0 define.
           */
  uint16_t fat32Version;
          /**
           * Cluster number of the first cluster of the root directory for FAT32.
           * This usually 2 but not required to be 2.
           */
  uint32_t fat32RootCluster;
          /**
           * Sector number of FSINFO structure in the reserved area of the
           * FAT32 volume. Usually 1.
           */
  uint16_t fat32FSInfo;
          /**
           * If non-zero, indicates the sector number in the reserved area
           * of the volume of a copy of the boot record. Usually 6.
           * No value other than 6 is recommended.
           */
  uint16_t fat32BackBootBlock;
          /**
           * Reserved for future expansion. Code that formats FAT32 volumes
           * should always set all of the bytes of this field to 0.
           */
  uint8_t  fat32Reserved[12];
}__attribute__ ((packed));
/** Type name for biosParmBlock */
typedef struct biosParmBlock bpb_t;
//------------------------------------------------------------------------------
/**
 * \struct fat32BootSector
 *
 * \brief Boot sector for a FAT16 or FAT32 volume.
 * 
 */  
struct fat32BootSector {
           /** X86 jmp to boot program */
  uint8_t  jmpToBootCode[3];
           /** informational only - don't depend on it */
  uint8_t     oemName[8];
           /** BIOS Parameter Block */
  bpb_t    bpb;
           /** for int0x13 use value 0X80 for hard drive */
  uint8_t  driveNumber;
           /** used by Windows NT - should be zero for FAT */
  uint8_t  reserved1;
           /** 0X29 if next three fields are valid */
  uint8_t  bootSignature;
           /** usually generated by combining date and time */
  uint32_t volumeSerialNumber;
           /** should match volume label in root dir */
  uint8_t     volumeLabel[11];
           /** informational only - don't depend on it */
  uint8_t     fileSystemType[8];
           /** X86 boot code */
  uint8_t  bootCode[420];
           /** must be 0X55 */
  uint8_t  bootSectorSig0;
           /** must be 0XAA */
  uint8_t  bootSectorSig1;
}__attribute__ ((packed));
//------------------------------------------------------------------------------
// End Of Chain values for FAT entries
/** FAT16 end of chain value used by Microsoft. */
uint16_t const FAT16EOC = 0XFFFF;
/** Minimum value for FAT16 EOC.  Use to test for EOC. */
uint16_t const FAT16EOC_MIN = 0XFFF8;
/** FAT32 end of chain value used by Microsoft. */
uint32_t const FAT32EOC = 0X0FFFFFFF;
/** Minimum value for FAT32 EOC.  Use to test for EOC. */
uint32_t const FAT32EOC_MIN = 0X0FFFFFF8;
/** Mask a for FAT32 entry. Entries are 28 bits. */
uint32_t const FAT32MASK = 0X0FFFFFFF;

/** Type name for fat32BootSector */
typedef struct fat32BootSector fbs_t;
//------------------------------------------------------------------------------
/**
 * \struct directoryEntry
 * \brief FAT short directory entry
 *
 * Short means short 8.3 name, not the entry size.
 *  
 * Date Format. A FAT directory entry date stamp is a 16-bit field that is 
 * basically a date relative to the MS-DOS epoch of 01/01/1980. Here is the
 * format (bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the 
 * 16-bit word):
 *   
 * Bits 9-15: Count of years from 1980, valid value range 0-127 
 * inclusive (1980-2107).
 *   
 * Bits 5-8: Month of year, 1 = January, valid value range 1-12 inclusive.
 *
 * Bits 0-4: Day of month, valid value range 1-31 inclusive.
 *
 * Time Format. A FAT directory entry time stamp is a 16-bit field that has
 * a granularity of 2 seconds. Here is the format (bit 0 is the LSB of the 
 * 16-bit word, bit 15 is the MSB of the 16-bit word).
 *   
 * Bits 11-15: Hours, valid value range 0-23 inclusive.
 * 
 * Bits 5-10: Minutes, valid value range 0-59 inclusive.
 *      
 * Bits 0-4: 2-second count, valid value range 0-29 inclusive (0 - 58 seconds).
 *   
 * The valid time range is from Midnight 00:00:00 to 23:59:58.
 */
struct directoryEntry {
           /**
            * Short 8.3 name.
            * The first eight bytes contain the file name with blank fill.
            * The last three bytes contain the file extension with blank fill.
            */
  uint8_t  name[11];
          /** Entry attributes.
           *
           * The upper two bits of the attribute byte are reserved and should
           * always be set to 0 when a file is created and never modified or
           * looked at after that.  See defines that begin with DIR_ATT_.
           */
  uint8_t  attributes;
          /**
           * Reserved for use by Windows NT. Set value to 0 when a file is
           * created and never modify or look at it after that.
           */
  uint8_t  reservedNT;
          /**
           * The granularity of the seconds part of creationTime is 2 seconds
           * so this field is a count of tenths of a second and its valid
           * value range is 0-199 inclusive. (WHG note - seems to be hundredths)
           */
  uint8_t  creationTimeTenths;
           /** Time file was created. */
  uint16_t creationTime;
           /** Date file was created. */
  uint16_t creationDate;
          /**
           * Last access date. Note that there is no last access time, only
           * a date.  This is the date of last read or write. In the case of
           * a write, this should be set to the same date as lastWriteDate.
           */
  uint16_t lastAccessDate;
          /**
           * High word of this entry's first cluster number (always 0 for a
           * FAT12 or FAT16 volume).
           */
  uint16_t firstClusterHigh;
           /** Time of last write. File creation is considered a write. */
  uint16_t lastWriteTime;
           /** Date of last write. File creation is considered a write. */
  uint16_t lastWriteDate;
           /** Low word of this entry's first cluster number. */
  uint16_t firstClusterLow;
           /** 32-bit unsigned holding this file's size in bytes. */
  uint32_t fileSize;
}__attribute__ ((packed));
//------------------------------------------------------------------------------
// Definitions for directory entries
//
/** Type name for directoryEntry */
typedef struct directoryEntry dir_t;
/** escape for name[0] = 0XE5 */
uint8_t const DIR_NAME_0XE5 = 0X05;
/** name[0] value for entry that is free after being "deleted" */
uint8_t const DIR_NAME_DELETED = 0XE5;
/** name[0] value for entry that is free and no allocated entries follow */
uint8_t const DIR_NAME_FREE = 0X00;
/** file is read-only */
uint8_t const DIR_ATT_READ_ONLY = 0X01;
/** File should hidden in directory listings */
uint8_t const DIR_ATT_HIDDEN = 0X02;
/** Entry is for a system file */
uint8_t const DIR_ATT_SYSTEM = 0X04;
/** Directory entry contains the volume label */
uint8_t const DIR_ATT_VOLUME_ID = 0X08;
/** Entry is for a directory */
uint8_t const DIR_ATT_DIRECTORY = 0X10;
/** Old DOS archive bit for backup support */
uint8_t const DIR_ATT_ARCHIVE = 0X20;
/** Test value for long name entry.  Test is
  (d->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME. */
uint8_t const DIR_ATT_LONG_NAME = 0X0F;
/** Test mask for long name entry */
uint8_t const DIR_ATT_LONG_NAME_MASK = 0X3F;
/** defined attribute bits */
uint8_t const DIR_ATT_DEFINED_BITS = 0X3F;
/** Directory entry is part of a long name */
static inline uint8_t DIR_IS_LONG_NAME(const dir_t* dir) {
  return (dir->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME;
}
/** Mask for file/subdirectory tests */
uint8_t const DIR_ATT_FILE_TYPE_MASK = (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY);
/** Directory entry is for a file */
static inline uint8_t DIR_IS_FILE(const dir_t* dir) {
  return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == 0;
}
/** Directory entry is for a subdirectory */
static inline uint8_t DIR_IS_SUBDIR(const dir_t* dir) {
  return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY;
}
/** Directory entry is for a file or subdirectory */
static inline uint8_t DIR_IS_FILE_OR_SUBDIR(const dir_t* dir) {
  return (dir->attributes & DIR_ATT_VOLUME_ID) == 0;
}
#endif  // FatStructs_h

File.cpp

C/C++
Interface to Mikroe hardware
/*

 SD - a slightly more friendly wrapper for sdfatlib

 This library aims to expose a subset of SD card functionality
 in the form of a higher level "wrapper" object.

 License: GNU General Public License V3
          (Because sdfatlib is licensed with this.)

 (C) Copyright 2010 SparkFun Electronics

 */

#include "sd-card-library-photon-compat.h"
#include <malloc.h>
#include <string.h> // required for memcpy?

/* for debugging file open/close leaks
   uint8_t nfilecount=0;
*/

File::File(SdFile f, const char *n) {
  // oh man you are kidding me, new() doesnt exist? Ok we do it by hand!
  _file = (SdFile *)malloc(sizeof(SdFile)); 
  if (_file) {
    memcpy(_file, &f, sizeof(SdFile));
    
    strncpy(_name, n, 12);
    _name[12] = 0;
    
    /* for debugging file open/close leaks
       nfilecount++;
       Serial.print("Created \"");
       Serial.print(n);
       Serial.print("\": ");
       Serial.println(nfilecount, DEC);
    */
  }
}

File::File(void) {
  _file = 0;
  _name[0] = 0;
  //Serial.print("Created empty file object");
}

File::~File(void) {
  //  Serial.print("Deleted file object");
}

// returns a pointer to the file name
char *File::name(void) {
  return _name;
}

// a directory is a special type of file
boolean File::isDirectory(void) {
  return (_file && _file->isDir());
}


size_t File::write(uint8_t val) {
  return write(&val, 1);
}

size_t File::write(const uint8_t *buf, size_t size) {
  size_t t;
  if (!_file) {
    setWriteError();
    return 0;
  }
  _file->clearWriteError();
  t = _file->write(buf, size);
  if (_file->getWriteError()) {
    setWriteError();
    return 0;
  }
  return t;
}

int File::peek() {
  if (! _file) 
    return 0;

  int c = _file->read();
  if (c != -1) _file->seekCur(-1);
  return c;
}

int File::read() {
  if (_file) 
    return _file->read();
  return -1;
}

// buffered read for more efficient, high speed reading
int File::read(void *buf, uint16_t nbyte) {
  if (_file) 
    return _file->read(buf, nbyte);
  return 0;
}

int File::available() {
  if (! _file) return 0;

  uint32_t n = size() - position();

  return n > 0X7FFF ? 0X7FFF : n;
}

void File::flush() {
  if (_file)
    _file->sync();
}

boolean File::seek(uint32_t pos) {
  if (! _file) return false;

  return _file->seekSet(pos);
}

uint32_t File::position() {
  if (! _file) return -1;
  return _file->curPosition();
}

uint32_t File::size() {
  if (! _file) return 0;
  return _file->fileSize();
}

void File::close() {
  if (_file) {
    _file->close();
    free(_file); 
    _file = 0;

    /* for debugging file open/close leaks
    nfilecount--;
    Serial.print("Deleted ");
    Serial.println(nfilecount, DEC);
    */
  }
}

File::operator bool() {
  if (_file) 
    return  _file->isOpen();
  return false;
}

MedianFilter.h

C/C++
This code provides a median filter of data
#ifndef MedianFilter_h
#define MedianFilter_h
// Median filter from GitHub tmick0
// Readings are placed in a circular buffer. The buffer content is copied to a second array,
// where the median is computed using the quickselect algorithm.

template <typename T, int S>
class MedianFilter {
public:

  /* Constructor
   */
  MedianFilter()
  : m_idx(0), m_cnt(0), m_med(0)
  {
  }
  
  /* addSample(s): adds the sample S to the window and computes the median
   * if enough samples have been gathered
   */
  void addSample(T s)
  {
    m_buf[m_idx] = s;
    m_idx = (m_idx + 1) % S;

    if(m_cnt == S){
      p_calcMedian();
    } else {
      m_cnt++;
      addSample(s);
    }
  }
  
  /* getMedian(): returns the median computed when the last sample was
   * added. Does not return anything meaningful if not enough samples
   * have been gathered; check isReady() first.
   */
  T getMedian()
  {
    return m_med;
  }
  
  
private:

  int m_idx, m_cnt;
  T m_buf[S], m_tmp[S], m_med;
  
  /* p_calcMedian(): helper to calculate the median. Copies
   * the buffer into the temp area, then calls Hoare's in-place
   * selection algorithm to obtain the median.
   */
  void p_calcMedian()
  {
    for(int i = 0; i < S; i++){
      m_tmp[i] = m_buf[i];
    }
    m_med = p_select(0, S - 1, S / 2);
  }
  
  /* p_partition(l, r, p): partition function, like from quicksort.
   * l and r are the left and right bounds of the array (m_tmp),
   * respectively, and p is the pivot index.
   */
  int p_partition(int l, int r, int p)
  {
    T tmp;
    T pv = m_tmp[p];
    m_tmp[p] = m_tmp[r];
    m_tmp[r] = pv;
    int s = l;
    for(int i = l; i < r; i++){
      if(m_tmp[i] < pv){
        tmp = m_tmp[s];
        m_tmp[s] = m_tmp[i];
        m_tmp[i] = tmp;
        s++;
      }
    }
    tmp = m_tmp[s];
    m_tmp[s] = m_tmp[r];
    m_tmp[r] = tmp;
    return s;
  }
  
  /* p_select(l, r, k): Hoare's quickselect. l and r are the
   * array bounds, and k conveys that we want to return
   * the k-th value
   */
  T p_select(int l, int r, int k)
  {
    if(l == r){
      return m_tmp[l];
    }
    int p = (l + r) / 2;
    p = p_partition(l, r, p);
    if(p == k){
      return m_tmp[k];
    }
    else if(k < p){
      return p_select(l, p - 1, k);
    }
    else {
      return p_select(p + 1, r, k);
    }
  }
  
};

#endif

mikroe_port.h

C/C++
Interface to Mikroe hardware
/*
 * Copyright (c) 2023 Particle Industries, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _MIKRO_PINS_H_
#define _MIKRO_PINS_H_

//#include <stdint.h>
//#include <stddef.h>
//#include <math.h>
#include "Particle.h"

//used in libraries for pin selection
#define HAL_MODULE_ERROR (uint8_t)(0xFFFFFFFF)         //general module error
#define HAL_CHANNEL_ERROR (uint8_t)(0xFFFFFFFF)        //channel error, (timer, ADC, etc.)
#define HAL_PIN_NC (uint8_t)(0xFFFFFFFF)               //pin error, wrong pin selected
#define HAL_PORT_NC (uint8_t)(0xFFFFFFFF)              //port error, wrong port selected

//us delays functions for mikroE delays for libray port compability
inline void Delay_1us( ) {delayMicroseconds(1);}
inline void Delay_10us( ) {delayMicroseconds(10);}
inline void Delay_50us( ) {delayMicroseconds(50);}
inline void Delay_100us( ) {delayMicroseconds(100);}
inline void Delay_1000us( ) {delayMicroseconds(1000);}
inline void Delay_us(unsigned long us) {delayMicroseconds(us);}

//ms delays functions for mikroE delays for libray port compability
inline void Delay_1ms( ) {delay(1);}
inline void Delay_10ms( ) {delay(10);}
inline void Delay_100ms( ) {delay(100);}
inline void Delay_1000ms( ) {delay(1000);}
inline void Delay_ms(unsigned long ms) {delay(ms);}

//sec delays functions for mikroE delays for libray port compability
inline void Delay_1sec( ) {delay(1000);}

//1-wire HAL delays, TBD on whether these are needed
inline void Delay_6us( ) {delayMicroseconds(6);}
inline void Delay_9us( ) {delayMicroseconds(9);}
inline void Delay_22us( ) {delayMicroseconds(22);}
inline void Delay_55us( ) {delayMicroseconds(55);}
inline void Delay_64us( ) {delayMicroseconds(64);}
inline void Delay_70us( ) {delayMicroseconds(70);}
inline void Delay_410us( ) {delayMicroseconds(410);}
inline void Delay_480us( ) {delayMicroseconds(480);}

//mikroBUS pin mapping first lookup
#define MIKROBUS_AN   AN_PIN
#define MIKROBUS_RST  RST_PIN
#define MIKROBUS_CS   CS_PIN
#define MIKROBUS_SCK  SCK_PIN
#define MIKROBUS_MISO MISO_PIN
#define MIKROBUS_MOSI MOSI_PIN
#define MIKROBUS_PWM  PWM_PIN
#define MIKROBUS_INT  INT_PIN
#define MIKROBUS_RX   RX_PIN
#define MIKROBUS_TX   TX_PIN
#define MIKROBUS_SCL  SCL_PIN
#define MIKROBUS_SDA  SDA_PIN

//M.2 format SKUs, same pinout for B404/402, B404X, and B524
#if PLATFORM_ID == PLATFORM_BSOM || PLATFORM_ID == PLATFORM_B5SOM        
    //mikroBUS pin mapping for actual pin lookup using BUS1
    #define MIKROBUS_1          1
    #define MIKROBUS_1_AN_PIN   A1          //D18
    #define MIKROBUS_1_RST_PIN  D7
    #define MIKROBUS_1_CS_PIN   SS          //D8
    #define MIKROBUS_1_SCK_PIN  SCK         //D13
    #define MIKROBUS_1_MISO_PIN MISO        //D11
    #define MIKROBUS_1_MOSI_PIN MOSI        //D12
    #define MIKROBUS_1_PWM_PIN  D5
    #define MIKROBUS_1_INT_PIN  D22
    #define MIKROBUS_1_RX_PIN   RX          //D9
    #define MIKROBUS_1_TX_PIN   TX          //D10
    #define MIKROBUS_1_SCL_PIN  SCL         //D1
    #define MIKROBUS_1_SDA_PIN  SDA         //D0

    //mikroBUS pin mapping for actual pin lookup using BUS2
    #define MIKROBUS_2          2
    #define MIKROBUS_2_AN_PIN   A2          //D17
    #define MIKROBUS_2_RST_PIN  D19
    #define MIKROBUS_2_CS_PIN   D4
    #define MIKROBUS_2_SCK_PIN  SCK         //D13
    #define MIKROBUS_2_MISO_PIN MISO        //D11
    #define MIKROBUS_2_MOSI_PIN MOSI        //D12
    #define MIKROBUS_2_PWM_PIN  D6
    #define MIKROBUS_2_INT_PIN  D23
    #define MIKROBUS_2_RX_PIN   RX          //D9
    #define MIKROBUS_2_TX_PIN   TX          //D10
    #define MIKROBUS_2_SCL_PIN  SCL         //D1
    #define MIKROBUS_2_SDA_PIN  SDA         //D0

//Feather format SKUs, Argon and Boron, same pinout for Argon, BRN404/402, and BRN404X
#elif PLATFORM_ID == PLATFORM_ARGON || PLATFORM_ID == PLATFORM_BORON
     //mikroBUS pin mapping for actual pin lookup using BUS1
    #define MIKROBUS_1          1
    #define MIKROBUS_1_AN_PIN   A0          //D19
    #define MIKROBUS_1_RST_PIN  A2          //D17
    #define MIKROBUS_1_CS_PIN   SS          //A5/D14
    #define MIKROBUS_1_SCK_PIN  SCK         //D17  
    #define MIKROBUS_1_MISO_PIN MISO        //D16
    #define MIKROBUS_1_MOSI_PIN MOSI        //D15
    #define MIKROBUS_1_PWM_PIN  A3          //D16
    #define MIKROBUS_1_INT_PIN  A4          //D15
    #define MIKROBUS_1_RX_PIN   RX          //D9
    #define MIKROBUS_1_TX_PIN   TX          //D8
    #define MIKROBUS_1_SCL_PIN  SCL         //D1
    #define MIKROBUS_1_SDA_PIN  SDA         //D0

    //mikroBUS pin mapping for actual pin lookup using BUS2
    #define MIKROBUS_2          2
    #define MIKROBUS_2_AN_PIN   A1          //D18
    #define MIKROBUS_2_RST_PIN  D7
    #define MIKROBUS_2_CS_PIN   D5
    #define MIKROBUS_2_SCK_PIN  SCK         //D17  
    #define MIKROBUS_2_MISO_PIN MISO        //D16
    #define MIKROBUS_2_MOSI_PIN MOSI        //D15
    #define MIKROBUS_2_PWM_PIN  D8
    #define MIKROBUS_2_INT_PIN  D6
    #define MIKROBUS_2_RX_PIN   RX          //D9
    #define MIKROBUS_2_TX_PIN   TX          //D8
    #define MIKROBUS_2_SCL_PIN  SCL         //D1
    #define MIKROBUS_2_SDA_PIN  SDA         //D0

//Feather format SKUs, Photon 2 pinout
#elif PLATFORM_ID == PLATFORM_P2    
    //mikroBUS pin mapping for actual pin lookup using BUS1
    #define MIKROBUS_1          1
    #define MIKROBUS_1_AN_PIN   A0          //D11
    #define MIKROBUS_1_RST_PIN  A2          //D13
    #define MIKROBUS_1_CS_PIN   SS          //S3(D18)
    #define MIKROBUS_1_SCK_PIN  SCK         //S2(D17)
    #define MIKROBUS_1_MISO_PIN MISO        //S1(D16)
    #define MIKROBUS_1_MOSI_PIN MOSI        //S0(D15)
    #define MIKROBUS_1_PWM_PIN  A5          //D14
    #define MIKROBUS_1_INT_PIN  S4          //D19
    #define MIKROBUS_1_RX_PIN   RX          //D9
    #define MIKROBUS_1_TX_PIN   TX          //D8
    #define MIKROBUS_1_SCL_PIN  SCL         //D1
    #define MIKROBUS_1_SDA_PIN  SDA         //D0

    //mikroBUS pin mapping for actual pin lookup using BUS2
    #define MIKROBUS_2          2
    #define MIKROBUS_2_AN_PIN   A1          //D12
    #define MIKROBUS_2_RST_PIN  D7
    #define MIKROBUS_2_CS_PIN   SS1         //D5
    #define MIKROBUS_2_SCK_PIN  SCK         //D17  
    #define MIKROBUS_2_MISO_PIN MISO        //D16
    #define MIKROBUS_2_MOSI_PIN MOSI        //D15
    #define MIKROBUS_2_PWM_PIN  D10         //warning, not a valid PWM pin
    #define MIKROBUS_2_INT_PIN  D6
    #define MIKROBUS_2_RX_PIN   RX          //D9
    #define MIKROBUS_2_TX_PIN   TX          //D8
    #define MIKROBUS_2_SCL_PIN  SCL         //D1
    #define MIKROBUS_2_SDA_PIN  SDA         //D0

//undefined, need to add pinout for platform
#else
    //INSERT PLATFORM PINOUT HERE

#endif

//used to define which mikroBUS is used
#define MIKROBUS(index, pinout) MIKROBUS_IMPL(index, pinout)
#define MIKROBUS_IMPL(index, pinout) MIKROBUS_##index##_##pinout

//typedef for various parameters passed through libraries, added for library compatibility
typedef uint8_t pin_name_t;             //GPIO pin name
typedef uint8_t port_name_t;            //GPIO port name
typedef uint8_t port_size_t;            //GPIO port size
typedef uint16_t hal_base_addr_t;       //Base address, which is size dependant on the architecture
typedef uint16_t hal_channel_t;         //Channel type, which is size dependant on the architecture
typedef uint16_t hal_pin_name_t;        //Pin type, which is size dependant on the architecture
typedef uint16_t hal_port_name_t;       //Port type, which is size dependant on the architecture
typedef uint16_t hal_port_size_t;       //Port width, which is size dependant on the architecture

#endif // _MIKRO_PINS_H_

sd2-card.cpp

C/C++
Interface to Mikroe hardware
/* Arduino Sd2Card Library
 * Copyright (C) 2009 by William Greiman
 *
 * This file is part of the Arduino Sd2Card Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino Sd2Card Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include "application.h"
#include "sd2-card.h"

//#include "spark_wiring_spi.h"
//#include "spark_wiring_usbserial.h"

/*#include "dma.h"
#ifdef SPI_DMA
bool dmaActive;
uint8_t mysink[1];

inline void DMAEvent(){
    dma_irq_cause event = dma_get_irq_cause(DMA1, DMA_CH3);
    switch(event) {
        case DMA_TRANSFER_COMPLETE:
            dma_detach_interrupt(DMA1, DMA_CH2);
            dma_detach_interrupt(DMA1, DMA_CH3);
            dmaActive = false;
            break;
        case DMA_TRANSFER_ERROR:
            SerialUSB.println("DMA Error - read/write data might be corrupted");
            break;
    }
}
#endif*/

//pointer to spi object
//HardwareSPI SD_SPI(SD_SPI_NUMBER);

//------------------------------------------------------------------------------
// functions for hardware SPI

#ifdef SPI_SPEED_UP
#	if SD_SPI_NUMBER   == 1
#		define __spi_dev__		SPI1
#	elif SD_SPI_NUMBER == 2
#		define __spi_dev__		SPI2
#	elif SD_SPI_NUMBER == 3
#		define __spi_dev__		SPI3
#	endif
#	define _RXNE_BIT	0x01
#	define _TXE_BIT		0x02
	extern "C" {
		inline uint8_t _spiSend(uint8_t b){
			while( !( __spi_dev__->regs->SR & _TXE_BIT) ) ;
			__spi_dev__->regs->DR = b;
			while( !( __spi_dev__->regs->SR & _RXNE_BIT) ) ;
			return __spi_dev__->regs->DR;
		}
		#define spiRec()	_spiSend(0xff)
		#define spiSend(b)	_spiSend(b)
	} /* extern C */

#else /* not SPI_SPEED_UP */
	//#ifdef SPI_DMA
		//#define spiSend(b) SPI.transfer(b)
		#define spiSend(b) sparkSPISend(b)
		/** Receive a byte from the card */
		//#define spiRec() SPI.transfer(0XFF)
		#define spiRec() sparkSPISend(0XFF)
	//#else
		/** Send a byte to the card */
		//#define spiSend(b) SPI.send(b)
		/** Receive a byte from the card */
		//#define spiRec() SPI.send(0XFF)
	//#endif
#endif	/* SPI_SPEED_UP */

//------------------------------------------------------------------------------
// send command and return error code.  Return zero for OK
uint8_t Sd2Card::cardCommand(uint8_t cmd, uint32_t arg) {
  // end read if in partialBlockRead mode
  readEnd();

  // select card
  chipSelectLow();

  // wait up to 300 ms if busy
  waitNotBusy(300);

  // send command
  spiSend(cmd | 0x40);

  // send argument
  for (int8_t s = 24; s >= 0; s -= 8) spiSend(arg >> s);

  // send CRC
  uint8_t crc = 0XFF;
  if (cmd == CMD0) crc = 0X95;  // correct crc for CMD0 with arg 0
  if (cmd == CMD8) crc = 0X87;  // correct crc for CMD8 with arg 0X1AA
  spiSend(crc);

  // wait for response
  for (uint8_t i = 0; ((status_ = spiRec()) & 0X80) && i != 0XFF; i++);
  return status_;
}
//------------------------------------------------------------------------------
/**
 * Determine the size of an SD flash memory card.
 *
 * \return The number of 512 byte data blocks in the card
 *         or zero if an error occurs.
 */
uint32_t Sd2Card::cardSize(void) {
  csd_t csd;
  if (!readCSD(&csd)) return 0;
  if (csd.v1.csd_ver == 0) {
    uint8_t read_bl_len = csd.v1.read_bl_len;
    uint16_t c_size = (csd.v1.c_size_high << 10)
                      | (csd.v1.c_size_mid << 2) | csd.v1.c_size_low;
    uint8_t c_size_mult = (csd.v1.c_size_mult_high << 1)
                          | csd.v1.c_size_mult_low;
    return (uint32_t)(c_size + 1) << (c_size_mult + read_bl_len - 7);
  } else if (csd.v2.csd_ver == 1) {
    uint32_t c_size = ((uint32_t)csd.v2.c_size_high << 16)
                      | (csd.v2.c_size_mid << 8) | csd.v2.c_size_low;
    return (c_size + 1) << 10;
  } else {
    error(SD_CARD_ERROR_BAD_CSD);
    return 0;
  }
}

//------------------------------------------------------------------------------
inline void Sd2Card::chipSelectHigh(void) {
  digitalWrite(chipSelectPin_, HIGH);
}

//------------------------------------------------------------------------------
inline void Sd2Card::chipSelectLow(void) {
  digitalWrite(chipSelectPin_, LOW);
}

//------------------------------------------------------------------------------
/** Erase a range of blocks.
 *
 * \param[in] firstBlock The address of the first block in the range.
 * \param[in] lastBlock The address of the last block in the range.
 *
 * \note This function requests the SD card to do a flash erase for a
 * range of blocks.  The data on the card after an erase operation is
 * either 0 or 1, depends on the card vendor.  The card must support
 * single block erase.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t Sd2Card::erase(uint32_t firstBlock, uint32_t lastBlock) {
  if (!eraseSingleBlockEnable()) {
    error(SD_CARD_ERROR_ERASE_SINGLE_BLOCK);
	Serial.println("Error: Erase Single Block");
    goto fail;
  }
  if (type_ != SD_CARD_TYPE_SDHC) {
    firstBlock <<= 9;
    lastBlock <<= 9;
  }
  if (cardCommand(CMD32, firstBlock)
    || cardCommand(CMD33, lastBlock)
    || cardCommand(CMD38, 0)) {
      error(SD_CARD_ERROR_ERASE);
	  Serial.println("Error: Erase");
	  goto fail;
  }
  if (!waitNotBusy(SD_ERASE_TIMEOUT)) {
    error(SD_CARD_ERROR_ERASE_TIMEOUT);
	Serial.println("Error: Erase timeout");
	goto fail;
  }
  chipSelectHigh();
  return true;

 fail:
  chipSelectHigh();
  Serial.println("Error: Sd2Card::Erase()");
  return false;
}
//------------------------------------------------------------------------------
/** Determine if card supports single block erase.
 *
 * \return The value one, true, is returned if single block erase is supported.
 * The value zero, false, is returned if single block erase is not supported.
 */
uint8_t Sd2Card::eraseSingleBlockEnable(void) {
  csd_t csd;
  return readCSD(&csd) ? csd.v1.erase_blk_en : 0;
}
//------------------------------------------------------------------------------
/**
 * Initialize an SD flash memory card.
 *
 * \param[in] sckRateID SPI clock rate selector. See setSckRate().
 * \param[in] chipSelectPin SD chip select pin number.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.  The reason for failure
 * can be determined by calling errorCode() and errorData().
 */
uint8_t Sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin) {
  chipSelectPin_ = chipSelectPin;
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  
  if( sckRateID == SPI_FULL_SPEED ){
    SPI.setClockDivider(SPI_CLOCK_DIV4);
  }
  else{
    SPI.setClockDivider(SPI_CLOCK_DIV8);
  }
  
  SPI.begin(chipSelectPin_);
  
  SPImode_ = 1;		// Set hardware SPI mode
  
  //return init(SD_SPI);
  return init();
}

uint8_t Sd2Card::init(uint8_t mosiPin, uint8_t misoPin, uint8_t clockPin, uint8_t chipSelectPin) {
  mosiPin_ = mosiPin;
  misoPin_ = misoPin;
  clockPin_ = clockPin;
  chipSelectPin_ = chipSelectPin;
  
  pinMode(clockPin_, OUTPUT);
  pinMode(mosiPin_, OUTPUT);
  pinMode(misoPin_, INPUT);
  pinMode(chipSelectPin_, OUTPUT);

  SPImode_ = 0;		// Set software SPI mode

  return init();
}

uint8_t Sd2Card::init() {
//  chipSelectPin_ = SS;
//  SPI.begin();

  errorCode_ = inBlock_ = partialBlockRead_ = type_ = 0;
#ifdef SPI_DMA
    //init DMA
    dma_init(DMA1);
    //enable SPI over DMA
    spi_rx_dma_enable(SPI1);
    spi_tx_dma_enable(SPI1);
    //DMA activity control
    dmaActive = false;
    //Acknowledgment array
    for(int i=0; i<SPI_BUFF_SIZE; i++) 
        ack[i] = 0xFF;
#endif
	
  //chipSelectPin_ = chipSelectPin;
  // 16-bit init start time allows over a minute

  uint16_t t0 = (uint16_t)millis();
  uint32_t arg;


//  SPIn = s;
  // set pin modes
/*  pinMode(chipSelectPin_, OUTPUT);

  chipSelectHigh();
  pinMode(SPI_MISO_PIN, INPUT);
  pinMode(SPI_MOSI_PIN, OUTPUT);
  pinMode(SPI_SCK_PIN, OUTPUT);
*/

  // SS must be in output mode even it is not chip select
//  pinMode(SS_PIN, OUTPUT);
  // Enable SPI, Master, clock rate f_osc/128
//  SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
  // clear double speed
//  SPSR &= ~(1 << SPI2X);

  // must supply min of 74 clock cycles with CS high.
  
  chipSelectHigh();
  for (uint8_t i = 0; i < 10; i++) spiSend(0XFF);
  chipSelectLow();

  // command to go idle in SPI mode
  while ((status_ = cardCommand(CMD0, 0)) != R1_IDLE_STATE) {
    if (((uint16_t)millis() - t0) > SD_INIT_TIMEOUT) {
		Serial.println("Error: CMD0");
		error(SD_CARD_ERROR_CMD0);
		goto fail;
    }
  }
  // check SD version
  if ((cardCommand(CMD8, 0x1AA) & R1_ILLEGAL_COMMAND)) {
    type(SD_CARD_TYPE_SD1);
  } else {
    // only need last byte of r7 response
    for (uint8_t i = 0; i < 4; i++) status_ = spiRec();
    if (status_ != 0XAA) {
      error(SD_CARD_ERROR_CMD8);
	  Serial.println("Error: CMD8");
	  goto fail;
    }
    type(SD_CARD_TYPE_SD2);
  }
  // initialize card and send host supports SDHC if SD2
  arg = (type() == SD_CARD_TYPE_SD2) ? 0X40000000 : 0;

  while ((status_ = cardAcmd(ACMD41, arg)) != R1_READY_STATE) {
    // check for timeout
    if (((uint16_t)millis() - t0) > SD_INIT_TIMEOUT) {
		Serial.println("Error: ACMD41");
		error(SD_CARD_ERROR_ACMD41);
		goto fail;
    }
  }
  // if SD2 read OCR register to check for SDHC card
  if (type() == SD_CARD_TYPE_SD2) {
    if (cardCommand(CMD58, 0)) {
		Serial.println("Error: CMD58");
		error(SD_CARD_ERROR_CMD58);
		goto fail;
    }
    if ((spiRec() & 0XC0) == 0XC0) type(SD_CARD_TYPE_SDHC);
    // discard rest of ocr - contains allowed voltage range
    for (uint8_t i = 0; i < 3; i++) spiRec();
  }
  chipSelectHigh();

//  return setSckRate(sckRateID);
  return true;

 fail:
  chipSelectHigh();
  Serial.println("Error: Sd2Card::init()");
  return false;
}
//------------------------------------------------------------------------------
/**
 * Enable or disable partial block reads.
 *
 * Enabling partial block reads improves performance by allowing a block
 * to be read over the SPI bus as several sub-blocks.  Errors may occur
 * if the time between reads is too long since the SD card may timeout.
 * The SPI SS line will be held low until the entire block is read or
 * readEnd() is called.
 *
 * Use this for applications like the Adafruit Wave Shield.
 *
 * \param[in] value The value TRUE (non-zero) or FALSE (zero).)
 */
void Sd2Card::partialBlockRead(uint8_t value) {
  readEnd();
  partialBlockRead_ = value;
}
//------------------------------------------------------------------------------
/**
 * Read a 512 byte block from an SD card device.
 *
 * \param[in] block Logical block to be read.
 * \param[out] dst Pointer to the location that will receive the data.

 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t Sd2Card::readBlock(uint32_t block, uint8_t* dst) {
  return readData(block, 0, 512, dst);
}
//------------------------------------------------------------------------------
/**
 * Read part of a 512 byte block from an SD card.
 *
 * \param[in] block Logical block to be read.
 * \param[in] offset Number of bytes to skip at start of block
 * \param[out] dst Pointer to the location that will receive the data.
 * \param[in] count Number of bytes to read
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t Sd2Card::readData(uint32_t block,
        uint16_t offset, uint16_t count, uint8_t* dst) {
  //uint16_t n;
  if (count == 0) return true;
  if ((count + offset) > 512) {
    goto fail;
  }
  if (!inBlock_ || block != block_ || offset < offset_) {
    block_ = block;
    // use address if not SDHC card
    if (type()!= SD_CARD_TYPE_SDHC) block <<= 9;
    if (cardCommand(CMD17, block)) {
      error(SD_CARD_ERROR_CMD17);
	  Serial.println("Error: CMD17");
      goto fail;
    }
    if (!waitStartBlock()) {
      goto fail;
    }
    offset_ = 0;
    inBlock_ = 1;
  }

#ifdef SPI_DMA
    // skip data before offset
    if(offset_ < offset){
        dma_setup_transfer(DMA1, 
				DMA_CH3, 
				&SPI1->regs->DR, 
				DMA_SIZE_8BITS, 
				ack, 
				DMA_SIZE_8BITS,
                           (/*DMA_MINC_MODE | DMA_CIRC_MODE  |*/ DMA_FROM_MEM | DMA_TRNS_CMPLT | DMA_TRNS_ERR));
        dma_attach_interrupt(DMA1, DMA_CH3, DMAEvent);
        dma_set_priority(DMA1, DMA_CH3, DMA_PRIORITY_VERY_HIGH);
        dma_set_num_transfers(DMA1, DMA_CH3, offset - offset_);
        
        dmaActive = true;
        dma_enable(DMA1, DMA_CH3);
        
        while(dmaActive) delayMicroseconds(1);
        dma_disable(DMA1, DMA_CH3);
    }
    offset_ = offset;
    
    // transfer data
    dma_setup_transfer(DMA1, DMA_CH2, &SPI1->regs->DR, DMA_SIZE_8BITS, dst, DMA_SIZE_8BITS,
                       (DMA_MINC_MODE | DMA_TRNS_CMPLT | DMA_TRNS_ERR));
    dma_attach_interrupt(DMA1, DMA_CH2, DMAEvent);
    dma_setup_transfer(DMA1, DMA_CH3, &SPI1->regs->DR, DMA_SIZE_8BITS, ack, DMA_SIZE_8BITS,
                       (/*DMA_MINC_MODE | DMA_CIRC_MODE |*/ DMA_FROM_MEM));             
    dma_set_priority(DMA1, DMA_CH2, DMA_PRIORITY_VERY_HIGH);
    dma_set_priority(DMA1, DMA_CH3, DMA_PRIORITY_VERY_HIGH);
    dma_set_num_transfers(DMA1, DMA_CH2, count);
    dma_set_num_transfers(DMA1, DMA_CH3, count);
    
    dmaActive = true;
    dma_enable(DMA1, DMA_CH3);
    dma_enable(DMA1, DMA_CH2);
    
    while(dmaActive) delayMicroseconds(1);
    dma_disable(DMA1, DMA_CH3);
    dma_disable(DMA1, DMA_CH2);
    
    offset_ += count;
    if (!partialBlockRead_ || offset_ >= SPI_BUFF_SIZE) {
        readEnd();
    }

#else
  // skip data before offset
  for (;offset_ < offset; offset_++) {
    spiRec();
  }
  // transfer data
  for (uint16_t i = 0; i < count; i++) {
    dst[i] = spiRec();
  }
  offset_ += count;
  if (!partialBlockRead_ || offset_ >= 512) {
    // read rest of data, checksum and set chip select high
    readEnd();
  }
#endif

  return true;

 fail:
  chipSelectHigh();
  Serial.println("Error: Sd2Card::readData()");
  return false;
}
//------------------------------------------------------------------------------
/** Skip remaining data in a block when in partial block read mode. */
void Sd2Card::readEnd(void) {
  if (inBlock_) {
      // skip data and crc
#ifdef SPI_DMA
        dma_setup_transfer(DMA1, DMA_CH3, &SPI1->regs->DR, DMA_SIZE_8BITS, ack, DMA_SIZE_8BITS,
                           (/*DMA_MINC_MODE | DMA_CIRC_MODE |*/ DMA_FROM_MEM | DMA_TRNS_CMPLT | DMA_TRNS_ERR));  
        dma_attach_interrupt(DMA1, DMA_CH3, DMAEvent);
        dma_set_priority(DMA1, DMA_CH3, DMA_PRIORITY_VERY_HIGH);
        dma_set_num_transfers(DMA1, DMA_CH3, SPI_BUFF_SIZE + 1 - offset_);
        
        dmaActive = true;
        dma_enable(DMA1, DMA_CH3);
        
        while(dmaActive)delayMicroseconds(1);
        dma_disable(DMA1, DMA_CH3);
#else  // SPI_DMA
    while (offset_++ < 514) spiRec();
#endif  // SPI_DMA
    chipSelectHigh();
    inBlock_ = 0;
  }
}
//------------------------------------------------------------------------------
/** read CID or CSR register */
uint8_t Sd2Card::readRegister(uint8_t cmd, void* buf) {
  uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
  if (cardCommand(cmd, 0)) {
	  error(SD_CARD_ERROR_READ_REG);
	  Serial.println("Error: Read reg");
	  goto fail;
  }
  if (!waitStartBlock()) goto fail;
  // transfer data
  for (uint16_t i = 0; i < 16; i++) dst[i] = spiRec();
  spiRec();  // get first crc byte
  spiRec();  // get second crc byte
  chipSelectHigh();
  return true;

 fail:
  Serial.println("Error: Sd2Card::readRegister()");
  chipSelectHigh();
  return false;
}
//------------------------------------------------------------------------------
/**
 * Set the SPI clock rate.
 *
 * \param[in] sckRateID A value in the range [0, 6].
 *
 * The SPI clock will be set to F_CPU/pow(2, 1 + sckRateID). The maximum
 * SPI rate is F_CPU/2 for \a sckRateID = 0 and the minimum rate is F_CPU/128
 * for \a scsRateID = 6.
 *
 * \return The value one, true, is returned for success and the value zero,
 * false, is returned for an invalid value of \a sckRateID.
 */
uint8_t Sd2Card::setSckRate(uint8_t sckRateID) {
/*  if (sckRateID > 6) {
    error(SD_CARD_ERROR_SCK_RATE);
    return false;
  }
  // see avr processor datasheet for SPI register bit definitions
  if ((sckRateID & 1) || sckRateID == 6) {
    SPSR &= ~(1 << SPI2X);
  } else {
    SPSR |= (1 << SPI2X);
  }
  SPCR &= ~((1 <<SPR1) | (1 << SPR0));
  SPCR |= (sckRateID & 4 ? (1 << SPR1) : 0)
    | (sckRateID & 2 ? (1 << SPR0) : 0);
*/
	return true;
}
//------------------------------------------------------------------------------
// wait for card to go not busy
uint8_t Sd2Card::waitNotBusy(uint16_t timeoutMillis) {
  uint16_t t0 = millis();
  do {
    if (spiRec() == 0XFF) return true;
  }
  while (((uint16_t)millis() - t0) < timeoutMillis);
  return false;
}
//------------------------------------------------------------------------------
/** Wait for start block token */
uint8_t Sd2Card::waitStartBlock(void) {
  uint16_t t0 = millis();
  while ((status_ = spiRec()) == 0XFF) {
    if (((uint16_t)millis() - t0) > SD_READ_TIMEOUT) {
      error(SD_CARD_ERROR_READ_TIMEOUT);
	  Serial.println("Error: Read timeout");
      goto fail;
    }
  }
  if (status_ != DATA_START_BLOCK) {
    error(SD_CARD_ERROR_READ);
	Serial.println("Error: Read");
    goto fail;
  }
  return true;

 fail:
  chipSelectHigh();
  Serial.println("Error: Sd2Card::waitStartBlock()");
  return false;
}
//------------------------------------------------------------------------------
/**
 * Writes a 512 byte block to an SD card.
 *
 * \param[in] blockNumber Logical block to be written.
 * \param[in] src Pointer to the location of the data to be written.
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src) {
#if SD_PROTECT_BLOCK_ZERO
  // don't allow write to first block
  if (blockNumber == 0) {
    error(SD_CARD_ERROR_WRITE_BLOCK_ZERO);
    Serial.println("Error: Write block zero");
    goto fail;
  }
#endif  // SD_PROTECT_BLOCK_ZERO

  // use address if not SDHC card
  if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9;
  if (cardCommand(CMD24, blockNumber)) {
	Serial.println("Error: CMD24");
	error(SD_CARD_ERROR_CMD24);
	goto fail;
  }
  if (!writeData(DATA_START_BLOCK, src)) goto fail;

  // wait for flash programming to complete
  if (!waitNotBusy(SD_WRITE_TIMEOUT)) {
    error(SD_CARD_ERROR_WRITE_TIMEOUT);
    Serial.println("Error: Write timeout");
    goto fail;
  }
  // response is r2 so get and check two bytes for nonzero
  if (cardCommand(CMD13, 0) || spiRec()) {
    error(SD_CARD_ERROR_WRITE_PROGRAMMING);
    Serial.println("Error: Write programming");
    goto fail;
  }
  chipSelectHigh();
  return true;

 fail:
  chipSelectHigh();
  Serial.println("Error: Sd2Card::writeBlock");
  return false;
}
//------------------------------------------------------------------------------
/** Write one data block in a multiple block write sequence */
uint8_t Sd2Card::writeData(const uint8_t* src) {
  // wait for previous write to finish
  if (!waitNotBusy(SD_WRITE_TIMEOUT)) {
    error(SD_CARD_ERROR_WRITE_MULTIPLE);
	Serial.println("Error: writeData");
    chipSelectHigh();
    return false;
  }
  return writeData(WRITE_MULTIPLE_TOKEN, src);
}
//------------------------------------------------------------------------------
// send one block of data for write block or write multiple blocks
uint8_t Sd2Card::writeData(uint8_t token, const uint8_t* src) {
#ifdef SPI_DMA
        dma_setup_transfer(DMA1, DMA_CH3, &SPI1->regs->DR, DMA_SIZE_8BITS, (uint8_t *)src, DMA_SIZE_8BITS, (DMA_MINC_MODE |  DMA_FROM_MEM | DMA_TRNS_CMPLT | DMA_TRNS_ERR));
        dma_attach_interrupt(DMA1, DMA_CH3, DMAEvent);
        dma_set_priority(DMA1, DMA_CH3, DMA_PRIORITY_VERY_HIGH);
        dma_set_num_transfers(DMA1, DMA_CH3, 512);

        dmaActive = true;
        dma_enable(DMA1, DMA_CH3);

        while(dmaActive) delayMicroseconds(1);
        dma_disable(DMA1, DMA_CH3);

#else  // SPI_DMA
  spiSend(token);
  for (uint16_t i = 0; i < 512; i++) {
    spiSend(src[i]);
  }
#endif  // OPTIMIZE_HARDWARE_SPI
  spiSend(0xff);  // dummy crc
  spiSend(0xff);  // dummy crc

  status_ = spiRec();
  if ((status_ & DATA_RES_MASK) != DATA_RES_ACCEPTED) {
    error(SD_CARD_ERROR_WRITE);
    chipSelectHigh();
	Serial.println("Error: Write");
    Serial.println("Error: Sd2Card::writeData()");
    return false;
  }
  return true;
}
//------------------------------------------------------------------------------
/** Start a write multiple blocks sequence.
 *
 * \param[in] blockNumber Address of first block in sequence.
 * \param[in] eraseCount The number of blocks to be pre-erased.
 *
 * \note This function is used with writeData() and writeStop()
 * for optimized multiple block writes.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t Sd2Card::writeStart(uint32_t blockNumber, uint32_t eraseCount) {
#if SD_PROTECT_BLOCK_ZERO
  // don't allow write to first block
  if (blockNumber == 0) {
    error(SD_CARD_ERROR_WRITE_BLOCK_ZERO);
	Serial.println("Error: Write block zero");
    goto fail;
  }
#endif  // SD_PROTECT_BLOCK_ZERO
  // send pre-erase count
  if (cardAcmd(ACMD23, eraseCount)) {
	Serial.println("Error: ACMD23");
    error(SD_CARD_ERROR_ACMD23);
    goto fail;
  }
  // use address if not SDHC card
  if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9;
  if (cardCommand(CMD25, blockNumber)) {
    error(SD_CARD_ERROR_CMD25);
	Serial.println("Error: CMD25");
    goto fail;
  }
  return true;

 fail:
  chipSelectHigh();
    Serial.println("Error: Sd2Card::writeStart()");
  return false;
}
//------------------------------------------------------------------------------
/** End a write multiple blocks sequence.
 *
* \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t Sd2Card::writeStop(void) {
  if (!waitNotBusy(SD_WRITE_TIMEOUT)) goto fail;
  spiSend(STOP_TRAN_TOKEN);
  if (!waitNotBusy(SD_WRITE_TIMEOUT)) goto fail;
  chipSelectHigh();
  return true;

 fail:
  error(SD_CARD_ERROR_STOP_TRAN);
  chipSelectHigh();
    Serial.println("Error: Sd2Card::writeStop()");
  return false;
}

inline __attribute__((always_inline))
uint8_t Sd2Card::sparkSPISend(uint8_t data) {
	uint8_t b=0;

	if (SPImode_) {				// SPI Mode is Hardware so use Spark SPI function
		b = SPI.transfer(data);
	}
	else {						// SPI Mode is Software so use bit bang method
		for (uint8_t bit = 0; bit < 8; bit++)  {
			if (data & (1 << (7-bit)))		// walks down mask from bit 7 to bit 0
				pinSetFast(mosiPin_); // Data High
			else
				pinResetFast(mosiPin_); // Data Low
			
			pinSetFast(clockPin_); // Clock High

			b <<= 1;
			if (pinReadFast(misoPin_))
				b |= 1;

			pinResetFast(clockPin_); // Clock Low
		}
	}
	return b;
}

sd2-card.h

C/C++
Interface to Mikroe hardware
/* Arduino Sd2Card Library
 * Copyright (C) 2009 by William Greiman
 *
 * This file is part of the Arduino Sd2Card Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino Sd2Card Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
#ifndef _SD2_CARD
#define _SD2_CARD
/**
 * \file
 * Sd2Card class
 */
#include "sd2-card-config.h"
#include "sd-info.h"

#define SPI_BUFF_SIZE 512

/** Set SCK to max rate of F_CPU/2. See Sd2Card::setSckRate(). */
uint8_t const SPI_FULL_SPEED = 0;
/** Set SCK rate to F_CPU/4. See Sd2Card::setSckRate(). */
uint8_t const SPI_HALF_SPEED = 1;
/** Set SCK rate to F_CPU/8. Sd2Card::setSckRate(). */
uint8_t const SPI_QUARTER_SPEED = 2;
//------------------------------------------------------------------------------
/** Protect block zero from write if nonzero */
#define SD_PROTECT_BLOCK_ZERO 1
/** init timeout ms */
uint16_t const SD_INIT_TIMEOUT = 2000;
/** erase timeout ms */
uint16_t const SD_ERASE_TIMEOUT = 10000;
/** read timeout ms */
uint16_t const SD_READ_TIMEOUT = 300;
/** write time out ms */
uint16_t const SD_WRITE_TIMEOUT = 600;
//------------------------------------------------------------------------------
// SD card errors
/** timeout error for command CMD0 */
uint8_t const SD_CARD_ERROR_CMD0 = 0X1;
/** CMD8 was not accepted - not a valid SD card*/
uint8_t const SD_CARD_ERROR_CMD8 = 0X2;
/** card returned an error response for CMD17 (read block) */
uint8_t const SD_CARD_ERROR_CMD17 = 0X3;
/** card returned an error response for CMD24 (write block) */
uint8_t const SD_CARD_ERROR_CMD24 = 0X4;
/**  WRITE_MULTIPLE_BLOCKS command failed */
uint8_t const SD_CARD_ERROR_CMD25 = 0X05;
/** card returned an error response for CMD58 (read OCR) */
uint8_t const SD_CARD_ERROR_CMD58 = 0X06;
/** SET_WR_BLK_ERASE_COUNT failed */
uint8_t const SD_CARD_ERROR_ACMD23 = 0X07;
/** card's ACMD41 initialization process timeout */
uint8_t const SD_CARD_ERROR_ACMD41 = 0X08;
/** card returned a bad CSR version field */
uint8_t const SD_CARD_ERROR_BAD_CSD = 0X09;
/** erase block group command failed */
uint8_t const SD_CARD_ERROR_ERASE = 0X0A;
/** card not capable of single block erase */
uint8_t const SD_CARD_ERROR_ERASE_SINGLE_BLOCK = 0X0B;
/** Erase sequence timed out */
uint8_t const SD_CARD_ERROR_ERASE_TIMEOUT = 0X0C;
/** card returned an error token instead of read data */
uint8_t const SD_CARD_ERROR_READ = 0X0D;
/** read CID or CSD failed */
uint8_t const SD_CARD_ERROR_READ_REG = 0X0E;
/** timeout while waiting for start of read data */
uint8_t const SD_CARD_ERROR_READ_TIMEOUT = 0X0F;
/** card did not accept STOP_TRAN_TOKEN */
uint8_t const SD_CARD_ERROR_STOP_TRAN = 0X10;
/** card returned an error token as a response to a write operation */
uint8_t const SD_CARD_ERROR_WRITE = 0X11;
/** attempt to write protected block zero */
uint8_t const SD_CARD_ERROR_WRITE_BLOCK_ZERO = 0X12;
/** card did not go ready for a multiple block write */
uint8_t const SD_CARD_ERROR_WRITE_MULTIPLE = 0X13;
/** card returned an error to a CMD13 status check after a write */
uint8_t const SD_CARD_ERROR_WRITE_PROGRAMMING = 0X14;
/** timeout occurred during write programming */
uint8_t const SD_CARD_ERROR_WRITE_TIMEOUT = 0X15;
/** incorrect rate selected */
uint8_t const SD_CARD_ERROR_SCK_RATE = 0X16;
//------------------------------------------------------------------------------
// card types
/** Standard capacity V1 SD card */
uint8_t const SD_CARD_TYPE_SD1 = 1;
/** Standard capacity V2 SD card */
uint8_t const SD_CARD_TYPE_SD2 = 2;
/** High Capacity SD card */
uint8_t const SD_CARD_TYPE_SDHC = 3;
//------------------------------------------------------------------------------

/**
 * \class Sd2Card
 * \brief Raw access to SD and SDHC flash memory cards.
 */
class Sd2Card {
 public:
  /** Construct an instance of Sd2Card. */
  Sd2Card(void) : errorCode_(0), inBlock_(0) , partialBlockRead_(0), type_(0) {}
  uint32_t cardSize(void);
  uint8_t erase(uint32_t firstBlock, uint32_t lastBlock);
  uint8_t eraseSingleBlockEnable(void);
  /**
   * \return error code for last error. See Sd2Card.h for a list of error codes.
   */
  uint8_t errorCode(void) const {return errorCode_;}
  /** \return error data for last error. */
  uint8_t errorData(void) const {return status_;}
  /**
   * Initialize an SD flash memory card with default clock rate and chip
   * select pin.  See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin).
   */
  //uint8_t init();

  /**
   * Initialize an SD flash memory card with the selected SPI clock rate
   * and the default SD chip select pin.
   * See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin).
   */
  // Hardware SPI inits
  uint8_t init(uint8_t sckRateID) {
    //return init(sckRateID, SD_CHIP_SELECT_PIN);
    return init(sckRateID, D5); // SM - change to microSD on right feather wing
  }
  uint8_t init(uint8_t sckRateID, uint8_t chipSelectPin);

  // Software SPI init
  uint8_t init(uint8_t mosiPin, uint8_t misoPin, uint8_t clockPin, uint8_t chipSelectPin);
  
  void partialBlockRead(uint8_t value);
  /** Returns the current value, true or false, for partial block read. */
  uint8_t partialBlockRead(void) const {return partialBlockRead_;}
  uint8_t readBlock(uint32_t block, uint8_t* dst);
  uint8_t readData(uint32_t block,
          uint16_t offset, uint16_t count, uint8_t* dst);
  /**
   * Read a cards CID register. The CID contains card identification
   * information such as Manufacturer ID, Product name, Product serial
   * number and Manufacturing date. */
  uint8_t readCID(cid_t* cid) {
    return readRegister(CMD10, cid);
  }
  /**
   * Read a cards CSD register. The CSD contains Card-Specific Data that
   * provides information regarding access to the card's contents. */
  uint8_t readCSD(csd_t* csd) {
    return readRegister(CMD9, csd);
  }
  void readEnd(void);
  uint8_t setSckRate(uint8_t sckRateID);
  /** Return the card type: SD V1, SD V2 or SDHC */
  uint8_t type(void) const {return type_;}
  uint8_t writeBlock(uint32_t blockNumber, const uint8_t* src);
  uint8_t writeData(const uint8_t* src);
  uint8_t writeStart(uint32_t blockNumber, uint32_t eraseCount);
  uint8_t writeStop(void);
 private:
  uint32_t block_;
  uint8_t SPImode_;
  uint8_t chipSelectPin_;
  uint8_t mosiPin_;
  uint8_t misoPin_;
  uint8_t clockPin_;
  uint8_t errorCode_;
  uint8_t inBlock_;
  uint16_t offset_;
  uint8_t partialBlockRead_;
  uint8_t status_;
  uint8_t type_;
  #ifdef SPI_DMA
        //pol
        uint8_t ack[SPI_BUFF_SIZE];
  #endif
  // private functions
  uint8_t cardAcmd(uint8_t cmd, uint32_t arg) {
    cardCommand(CMD55, 0);
    return cardCommand(cmd, arg);
  }
  uint8_t sparkSPISend(uint8_t data);
  uint8_t init(void);
  uint8_t cardCommand(uint8_t cmd, uint32_t arg);
  void error(uint8_t code) {errorCode_ = code;}
  uint8_t readRegister(uint8_t cmd, void* buf);
  uint8_t sendWriteCommand(uint32_t blockNumber, uint32_t eraseCount);
  void chipSelectHigh(void);
  void chipSelectLow(void);
  void type(uint8_t value) {type_ = value;}
  uint8_t waitNotBusy(uint16_t timeoutMillis);
  uint8_t writeData(uint8_t token, const uint8_t* src);
  uint8_t waitStartBlock(void);
};
#endif  // Sd2Card_h

sd2-card-config.h

C/C++
Interface to Mikroe hardware
#ifndef _SD2_CARD_CONFIG
#define _SD2_CARD_CONFIG
//#include "application.h"
//#include "spark_wiring.h"

#define SD_SPI_NUMBER	1					/* Specify HardwareSPI number */

// For Feather Click Shield
#	define SD_CHIP_SELECT_PIN	S3			/* for Mikroe left SPI CS1 pin using Photon 2*/
	//#define SPI_LOW_CLOCK	SPI_281_250KHZ	/* = SPI_HALF_SPEED */
#	define SPI_LOW_CLOCK	SPI_4_5MHZ		/* = SPI_HALFL_SPEED */

/* High speed */
//#	define SPI_HIGH_CLOCK	SPI_4_5MHZ		/* = SPI_FULL_SPEED */
#	define SPI_HIGH_CLOCK	SPI_9MHZ		/* = SPI_FULL_SPEED */
//#	define SPI_HIGH_CLOCK	SPI_18MHZ		/* = SPI_FULL_SPEED */

/* Select output device for print() function */
//#	define SERIAL_DEVICE	0				/* 0:SerialUSB  1=: Serial UART device */
//#	if SERIAL_DEVICE == 0  	
//#		define Serial Serial				/* Use USB CDC port */
//#		define BPS_9600		/* Nothing */ 
//#		define BPS_115200 	/* Nothing */ 
//#	else
//#		define Serial Serial1				/* Specify serial device, "Serial1" or "Serial2" or "Serial3" */
//#		define BPS_9600		9600
//#		define BPS_115200 	115200
//#	endif

//#define SPI_SPEED_UP						/* Enable read/write speed up option */


//define SPI_DMA		/* Do not enable this line, no checked, experiment, dangerous */


#endif /* __SD2CARD_CONFIG_H__ */

sd-card-library-photon-compat.cpp

C/C++
Interface to Mikroe hardware
/*

 SD - a slightly more friendly wrapper for sdfatlib

 This library aims to expose a subset of SD card functionality
 in the form of a higher level "wrapper" object.

 License: GNU General Public License V3
          (Because sdfatlib is licensed with this.)

 (C) Copyright 2010 SparkFun Electronics


 This library provides four key benefits:

   * Including `SD.h` automatically creates a global
     `SD` object which can be interacted with in a similar
     manner to other standard global objects like `Serial` and `Ethernet`.

   * Boilerplate initialisation code is contained in one method named 
     `begin` and no further objects need to be created in order to access
     the SD card.

   * Calls to `open` can supply a full path name including parent 
     directories which simplifies interacting with files in subdirectories.

   * Utility methods are provided to determine whether a file exists
     and to create a directory heirarchy.


  Note however that not all functionality provided by the underlying
  sdfatlib library is exposed.

 */

/*

  Implementation Notes

  In order to handle multi-directory path traversal, functionality that 
  requires this ability is implemented as callback functions.

  Individual methods call the `walkPath` function which performs the actual
  directory traversal (swapping between two different directory/file handles
  along the way) and at each level calls the supplied callback function.

  Some types of functionality will take an action at each level (e.g. exists
  or make directory) which others will only take an action at the bottom
  level (e.g. open).

 */

#include "sd-card-library-photon-compat.h"

// Used by `getNextPathComponent`
#define MAX_COMPONENT_LEN 12 // What is max length?
#define PATH_COMPONENT_BUFFER_LEN MAX_COMPONENT_LEN+1

bool getNextPathComponent(char *path, unsigned int *p_offset,
			  char *buffer) {
  /*

    Parse individual path components from a path.

      e.g. after repeated calls '/foo/bar/baz' will be split
           into 'foo', 'bar', 'baz'.

    This is similar to `strtok()` but copies the component into the
    supplied buffer rather than modifying the original string.


    `buffer` needs to be PATH_COMPONENT_BUFFER_LEN in size.

    `p_offset` needs to point to an integer of the offset at
    which the previous path component finished.

    Returns `true` if more components remain.

    Returns `false` if this is the last component.
      (This means path ended with 'foo' or 'foo/'.)

   */

  // TODO: Have buffer local to this function, so we know it's the
  //       correct length?

  int bufferOffset = 0;

  int offset = *p_offset;

  // Skip root or other separator
  if (path[offset] == '/') {
    offset++;
  }
  
  // Copy the next next path segment
  while (bufferOffset < MAX_COMPONENT_LEN
	 && (path[offset] != '/')
	 && (path[offset] != '\0')) {
    buffer[bufferOffset++] = path[offset++];
  }

  buffer[bufferOffset] = '\0';

  // Skip trailing separator so we can determine if this
  // is the last component in the path or not.
  if (path[offset] == '/') {
    offset++;
  }

  *p_offset = offset;

  return (path[offset] != '\0');
}



boolean walkPath(char *filepath, SdFile& parentDir,
		 boolean (*callback)(SdFile& parentDir,
				     char *filePathComponent,
				     boolean isLastComponent,
				     void *object),
		 void *object = NULL) {
  /*
     
     When given a file path (and parent directory--normally root),
     this function traverses the directories in the path and at each
     level calls the supplied callback function while also providing
     the supplied object for context if required.

       e.g. given the path '/foo/bar/baz'
            the callback would be called at the equivalent of
	    '/foo', '/foo/bar' and '/foo/bar/baz'.

     The implementation swaps between two different directory/file
     handles as it traverses the directories and does not use recursion
     in an attempt to use memory efficiently.

     If a callback wishes to stop the directory traversal it should
     return false--in this case the function will stop the traversal,
     tidy up and return false.

     If a directory path doesn't exist at some point this function will
     also return false and not subsequently call the callback.

     If a directory path specified is complete, valid and the callback
     did not indicate the traversal should be interrupted then this
     function will return true.

   */


  SdFile subfile1;
  SdFile subfile2;

  char buffer[PATH_COMPONENT_BUFFER_LEN]; 

  unsigned int offset = 0;

  SdFile *p_parent;
  SdFile *p_child;

  SdFile *p_tmp_sdfile;  
  
  p_child = &subfile1;
  
  p_parent = &parentDir;

  while (true) {

    boolean moreComponents = getNextPathComponent(filepath, &offset, buffer);

    boolean shouldContinue = callback((*p_parent), buffer, !moreComponents, object);

    if (!shouldContinue) {
      // TODO: Don't repeat this code?
      // If it's one we've created then we
      // don't need the parent handle anymore.
      if (p_parent != &parentDir) {
        (*p_parent).close();
      }
      return false;
    }
    
    if (!moreComponents) {
      break;
    }
    
    boolean exists = (*p_child).open(*p_parent, buffer, O_RDONLY);

    // If it's one we've created then we
    // don't need the parent handle anymore.
    if (p_parent != &parentDir) {
      (*p_parent).close();
    }
    
    // Handle case when it doesn't exist and we can't continue...
    if (exists) {
      // We alternate between two file handles as we go down
      // the path.
      if (p_parent == &parentDir) {
        p_parent = &subfile2;
      }

      p_tmp_sdfile = p_parent;
      p_parent = p_child;
      p_child = p_tmp_sdfile;
    } else {
      return false;
    }
  }
  
  if (p_parent != &parentDir) {
    (*p_parent).close(); // TODO: Return/ handle different?
  }

  return true;
}



/*

   The callbacks used to implement various functionality follow.

   Each callback is supplied with a parent directory handle,
   character string with the name of the current file path component,
   a flag indicating if this component is the last in the path and
   a pointer to an arbitrary object used for context.

 */

boolean callback_pathExists(SdFile& parentDir, char *filePathComponent, 
			    boolean isLastComponent, void *object) {
  /*

    Callback used to determine if a file/directory exists in parent
    directory.

    Returns true if file path exists.

  */
  SdFile child;

  boolean exists = child.open(parentDir, filePathComponent, O_RDONLY);
  
  if (exists) {
     child.close(); 
  }
  
  return exists;
}



boolean callback_makeDirPath(SdFile& parentDir, char *filePathComponent, 
			     boolean isLastComponent, void *object) {
  /*

    Callback used to create a directory in the parent directory if
    it does not already exist.

    Returns true if a directory was created or it already existed.

  */
  boolean result = false;
  SdFile child;
  
  result = callback_pathExists(parentDir, filePathComponent, isLastComponent, object);
  if (!result) {
    result = child.makeDir(parentDir, filePathComponent);
  } 
  
  return result;
}


  /*

boolean callback_openPath(SdFile& parentDir, char *filePathComponent, 
			  boolean isLastComponent, void *object) {

    Callback used to open a file specified by a filepath that may
    specify one or more directories above it.

    Expects the context object to be an instance of `SDClass` and
    will use the `file` property of the instance to open the requested
    file/directory with the associated file open mode property.

    Always returns true if the directory traversal hasn't reached the
    bottom of the directory heirarchy.

    Returns false once the file has been opened--to prevent the traversal
    from descending further. (This may be unnecessary.)

  if (isLastComponent) {
    SDClass *p_SD = static_cast<SDClass*>(object);
    p_SD->file.open(parentDir, filePathComponent, p_SD->fileOpenMode);
    if (p_SD->fileOpenMode == FILE_WRITE) {
      p_SD->file.seekSet(p_SD->file.fileSize());
    }
    // TODO: Return file open result?
    return false;
  }
  return true;
}
  */



boolean callback_remove(SdFile& parentDir, char *filePathComponent, 
			boolean isLastComponent, void *object) {
  if (isLastComponent) {
    return SdFile::remove(parentDir, filePathComponent);
  }
  return true;
}

boolean callback_rmdir(SdFile& parentDir, char *filePathComponent, 
			boolean isLastComponent, void *object) {
  if (isLastComponent) {
    SdFile f;
    if (!f.open(parentDir, filePathComponent, O_READ)) return false;
    return f.rmDir();
  }
  return true;
}



/* Implementation of class used to create `SDCard` object. */

// HARDWARE SPI
boolean SDClass::begin(uint8_t csPin) {
  /*
    Performs the initialisation required by the sdfatlib library.
    Return true if initialization succeeds, false otherwise.
   */
  return card.init(SPI_FULL_SPEED, csPin) &&
         volume.init(card) &&
         root.openRoot(volume);
}

// SOFTWARE SPI
boolean SDClass::begin(uint8_t mosiPin, uint8_t misoPin, uint8_t sclkPin, uint8_t csPin) {
  /*
    Performs the initialisation required by the sdfatlib library.
    Return true if initialization succeeds, false otherwise.
   */
  return card.init(mosiPin, misoPin, sclkPin, csPin) &&
         volume.init(card) &&
         root.openRoot(volume);
}

// this little helper is used to traverse paths
SdFile SDClass::getParentDir(const char *filepath, int *index) {
  // get parent directory
  SdFile d1 = root; // start with the mostparent, root!
  SdFile d2;

  // we'll use the pointers to swap between the two objects
  SdFile *parent = &d1;
  SdFile *subdir = &d2;
  
  const char *origpath = filepath;

  while (strchr(filepath, '/')) {

    // get rid of leading /'s
    if (filepath[0] == '/') {
      filepath++;
      continue;
    }
    
    if (! strchr(filepath, '/')) {
      // it was in the root directory, so leave now
      break;
    }

    // extract just the name of the next subdirectory
    uint8_t idx = strchr(filepath, '/') - filepath;
    if (idx > 12)
      idx = 12;    // dont let them specify long names
    char subdirname[13];
    strncpy(subdirname, filepath, idx);
    subdirname[idx] = 0;

    // close the subdir (we reuse them) if open
    subdir->close();
    if (! subdir->open(parent, subdirname, O_READ)) {
      // failed to open one of the subdirectories
      return SdFile();
    }
    // move forward to the next subdirectory
    filepath += idx;

    // we reuse the objects, close it.
    parent->close();

    // swap the pointers
    SdFile *t = parent;
    parent = subdir;
    subdir = t;
  }

  *index = (int)(filepath - origpath);
  // parent is now the parent diretory of the file!
  return *parent;
}


File SDClass::open(const char *filepath, uint8_t mode) {
  /*

     Open the supplied file path for reading or writing.

     The file content can be accessed via the `file` property of
     the `SDClass` object--this property is currently
     a standard `SdFile` object from `sdfatlib`.

     Defaults to read only.

     If `write` is true, default action (when `append` is true) is to
     append data to the end of the file.

     If `append` is false then the file will be truncated first.

     If the file does not exist and it is opened for writing the file
     will be created.

     An attempt to open a file for reading that does not exist is an
     error.

   */

  int pathidx;

  // do the interative search
  SdFile parentdir = getParentDir(filepath, &pathidx);
  // no more subdirs!

  filepath += pathidx;

  if (! filepath[0]) {
    // it was the directory itself!
    return File(parentdir, "/");
  }

  // Open the file itself
  SdFile file;

  // failed to open a subdir!
  if (!parentdir.isOpen())
    return File();

  // there is a special case for the Root directory since its a static dir
  if (parentdir.isRoot()) {
    if ( ! file.open(SD.root, filepath, mode)) {
      // failed to open the file :(
      return File();
    }
    // dont close the root!
  } else {
    if ( ! file.open(parentdir, filepath, mode)) {
      return File();
    }
    // close the parent
    parentdir.close();
  }

  if (mode & (O_APPEND | O_WRITE)) 
    file.seekSet(file.fileSize());
  return File(file, filepath);
}


/*
File SDClass::open(char *filepath, uint8_t mode) {
  //

     Open the supplied file path for reading or writing.

     The file content can be accessed via the `file` property of
     the `SDClass` object--this property is currently
     a standard `SdFile` object from `sdfatlib`.

     Defaults to read only.

     If `write` is true, default action (when `append` is true) is to
     append data to the end of the file.

     If `append` is false then the file will be truncated first.

     If the file does not exist and it is opened for writing the file
     will be created.

     An attempt to open a file for reading that does not exist is an
     error.

   //

  // TODO: Allow for read&write? (Possibly not, as it requires seek.)

  fileOpenMode = mode;
  walkPath(filepath, root, callback_openPath, this);

  return File();

}
*/


//boolean SDClass::close() {
//  /*
//
//    Closes the file opened by the `open` method.
//
//   */
//  file.close();
//}


boolean SDClass::exists(char *filepath) {
  /*

     Returns true if the supplied file path exists.

   */
  return walkPath(filepath, root, callback_pathExists);
}


//boolean SDClass::exists(char *filepath, SdFile& parentDir) {
//  /*
//
//     Returns true if the supplied file path rooted at `parentDir`
//     exists.
//
//   */
//  return walkPath(filepath, parentDir, callback_pathExists);
//}


boolean SDClass::mkdir(char *filepath) {
  /*
  
    Makes a single directory or a heirarchy of directories.

    A rough equivalent to `mkdir -p`.
  
   */
  return walkPath(filepath, root, callback_makeDirPath);
}

boolean SDClass::rmdir(char *filepath) {
  /*
  
    Makes a single directory or a heirarchy of directories.

    A rough equivalent to `mkdir -p`.
  
   */
  return walkPath(filepath, root, callback_rmdir);
}

boolean SDClass::remove(char *filepath) {
  return walkPath(filepath, root, callback_remove);
}


// allows you to recurse into a directory
File File::openNextFile(uint8_t mode) {
  dir_t p;

  //Serial.print("\t\treading dir...");
  while (_file->readDir(&p) > 0) {

    // done if past last used entry
    if (p.name[0] == DIR_NAME_FREE) {
      //Serial.println("end");
      return File();
    }

    // skip deleted entry and entries for . and  ..
    if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') {
      //Serial.println("dots");
      continue;
    }

    // only list subdirectories and files
    if (!DIR_IS_FILE_OR_SUBDIR(&p)) {
      //Serial.println("notafile");
      continue;
    }

    // print file name with possible blank fill
    SdFile f;
    char name[13];
    _file->dirName(p, name);
    //Serial.print("try to open file ");
    //Serial.println(name);

    if (f.open(_file, name, mode)) {
      //Serial.println("OK!");
      return File(f, name);    
    } else {
      //Serial.println("ugh");
      return File();
    }
  }

  //Serial.println("nothing");
  return File();
}

void File::rewindDirectory(void) {  
  if (isDirectory())
    _file->rewind();
}

SDClass SD;

sd-card-library-photon-compat.h

C/C++
Interface to Mikroe hardware
/*

 SD - a slightly more friendly wrapper for sdfatlib

 This library aims to expose a subset of SD card functionality
 in the form of a higher level "wrapper" object.

 License: GNU General Public License V3
          (Because sdfatlib is licensed with this.)

 (C) Copyright 2010 SparkFun Electronics

 */
#ifndef _SD_CARD_LIBRARY
#define _SD_CARD_LIBRARY

#include "application.h"
#include "sd-fat.h"
#include "sd-fat-util.h"


#define FILE_READ O_READ
#define FILE_WRITE (O_READ | O_WRITE | O_CREAT)

class File : public Stream {
 private:
  char _name[13]; // our name
  SdFile *_file;  // underlying file pointer

public:
  File(SdFile f, const char *name);     // wraps an underlying SdFile
  File(void);      // 'empty' constructor
  ~File(void);     // destructor
  virtual size_t write(uint8_t);
  virtual size_t write(const uint8_t *buf, size_t size);
  virtual int read();
  virtual int peek();
  virtual int available();
  virtual void flush();
  int read(void *buf, uint16_t nbyte);
  boolean seek(uint32_t pos);
  uint32_t position();
  uint32_t size();
  void close();
  operator bool();
  char * name();

  boolean isDirectory(void);
  File openNextFile(uint8_t mode = O_RDONLY);
  void rewindDirectory(void);
  
  using Print::write;
};

class SDClass {

private:
  // These are required for initialisation and use of sdfatlib
  Sd2Card card;
  SdVolume volume;
  SdFile root;
  
  // my quick&dirty iterator, should be replaced
  SdFile getParentDir(const char *filepath, int *indx);
public:
  // This needs to be called to set up the connection to the SD card
  // before other methods are used.
  //boolean begin(uint8_t csPin = SS);	// HARDWARE SPI
  boolean begin(uint8_t csPin = D5);	// HARDWARE SPI SM - on right feather wing

  boolean begin(uint8_t mosiPin, uint8_t misoPin, uint8_t sclkPin, uint8_t csPin);	// SOFTWARE SPI
  
  // Open the specified file/directory with the supplied mode (e.g. read or
  // write, etc). Returns a File object for interacting with the file.
  // Note that currently only one file can be open at a time.
  File open(const char *filename, uint8_t mode = FILE_READ);

  // Methods to determine if the requested file path exists.
  boolean exists(char *filepath);

  // Create the requested directory heirarchy--if intermediate directories
  // do not exist they will be created.
  boolean mkdir(char *filepath);
  
  // Delete the file.
  boolean remove(char *filepath);
  
  // Remove the directory
  boolean rmdir(char *filepath);

private:

  // This is used to determine the mode used to open a file
  // it's here because it's the easiest place to pass the 
  // information through the directory walking function. But
  // it's probably not the best place for it.
  // It shouldn't be set directly--it is set via the parameters to `open`.
  int fileOpenMode;
  
  friend class File;
  friend boolean callback_openPath(SdFile&, char *, boolean, void *); 
};

extern SDClass SD;

#endif

sd-fat.cpp

C/C++
Interface to Mikroe hardware
/* Arduino SdFat Library
 * Copyright (C) 2009 by William Greiman
 *
 * This file is part of the Arduino SdFat Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino SdFat Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
 
#include "application.h"
#include "sd-fat.h"
#include "sd-fat-util.h"

//#include "spark_wiring_usbserial.h"

//------------------------------------------------------------------------------
// callback function for date/time
void (*SdFile::dateTime_)(uint16_t* date, uint16_t* time) = NULL;

#if ALLOW_DEPRECATED_FUNCTIONS
// suppress cpplint warnings with NOLINT comment
void (*SdFile::oldDateTime_)(uint16_t& date, uint16_t& time) = NULL;  // NOLINT
#endif  // ALLOW_DEPRECATED_FUNCTIONS
//------------------------------------------------------------------------------
// add a cluster to a file
uint8_t SdFile::addCluster() {
  if (!vol_->allocContiguous(1, &curCluster_)) return false;

  // if first cluster of file link to directory entry
  if (firstCluster_ == 0) {
    firstCluster_ = curCluster_;
    flags_ |= F_FILE_DIR_DIRTY;
  }
  return true;
}
//------------------------------------------------------------------------------
// Add a cluster to a directory file and zero the cluster.
// return with first block of cluster in the cache
uint8_t SdFile::addDirCluster(void) {
  if (!addCluster()) return false;

  // zero data in cluster insure first cluster is in cache
  uint32_t block = vol_->clusterStartBlock(curCluster_);
  for (uint8_t i = vol_->blocksPerCluster_; i != 0; i--) {
    if (!SdVolume::cacheZeroBlock(block + i - 1)) return false;
  }
  // Increase directory file size by cluster size
  fileSize_ += 512UL << vol_->clusterSizeShift_;
  return true;
}
//------------------------------------------------------------------------------
// cache a file's directory entry
// return pointer to cached entry or null for failure
dir_t* SdFile::cacheDirEntry(uint8_t action) {
  if (!SdVolume::cacheRawBlock(dirBlock_, action)) return NULL;
  return SdVolume::cacheBuffer_.dir + dirIndex_;
}
//------------------------------------------------------------------------------
/**
 *  Close a file and force cached data and directory information
 *  to be written to the storage device.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include no file is open or an I/O error.
 */
uint8_t SdFile::close(void) {
  if (!sync())return false;
  type_ = FAT_FILE_TYPE_CLOSED;
  return true;
}
//------------------------------------------------------------------------------
/**
 * Check for contiguous file and return its raw block range.
 *
 * \param[out] bgnBlock the first block address for the file.
 * \param[out] endBlock the last  block address for the file.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include file is not contiguous, file has zero length
 * or an I/O error occurred.
 */
uint8_t SdFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock) {
  // error if no blocks
  if (firstCluster_ == 0) return false;

  for (uint32_t c = firstCluster_; ; c++) {
    uint32_t next;
    if (!vol_->fatGet(c, &next)) return false;

    // check for contiguous
    if (next != (c + 1)) {
      // error if not end of chain
      if (!vol_->isEOC(next)) return false;
      *bgnBlock = vol_->clusterStartBlock(firstCluster_);
      *endBlock = vol_->clusterStartBlock(c)
                  + vol_->blocksPerCluster_ - 1;
      return true;
    }
  }
}
//------------------------------------------------------------------------------
/**
 * Create and open a new contiguous file of a specified size.
 *
 * \note This function only supports short DOS 8.3 names.
 * See open() for more information.
 *
 * \param[in] dirFile The directory where the file will be created.
 * \param[in] fileName A valid DOS 8.3 file name.
 * \param[in] size The desired file size.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include \a fileName contains
 * an invalid DOS 8.3 file name, the FAT volume has not been initialized,
 * a file is already open, the file already exists, the root
 * directory is full or an I/O error.
 *
 */
uint8_t SdFile::createContiguous(SdFile* dirFile,
        const char* fileName, uint32_t size) {
  // don't allow zero length file
  if (size == 0) return false;
  if (!open(dirFile, fileName, O_CREAT | O_EXCL | O_RDWR1)) return false;

  // calculate number of clusters needed
  uint32_t count = ((size - 1) >> (vol_->clusterSizeShift_ + 9)) + 1;

  // allocate clusters
  if (!vol_->allocContiguous(count, &firstCluster_)) {
    remove();
    return false;
  }
  fileSize_ = size;

  // insure sync() will update dir entry
  flags_ |= F_FILE_DIR_DIRTY;
  return sync();
}
//------------------------------------------------------------------------------
/**
 * Return a files directory entry
 *
 * \param[out] dir Location for return of the files directory entry.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t SdFile::dirEntry(dir_t* dir) {
  // make sure fields on SD are correct
  if (!sync()) return false;

  // read entry
  dir_t* p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
  if (!p) return false;

  // copy to caller's struct
  memcpy(dir, p, sizeof(dir_t));
  return true;
}
//------------------------------------------------------------------------------
/**
 * Format the name field of \a dir into the 13 byte array
 * \a name in standard 8.3 short name format.
 *
 * \param[in] dir The directory structure containing the name.
 * \param[out] name A 13 byte char array for the formatted name.
 */
void SdFile::dirName(const dir_t& dir, char* name) {
  uint8_t j = 0;
  for (uint8_t i = 0; i < 11; i++) {
    if (dir.name[i] == ' ')continue;
    if (i == 8) name[j++] = '.';
    name[j++] = dir.name[i];
  }
  name[j] = 0;
}
//------------------------------------------------------------------------------
/** List directory contents to Serial (only used while testing).
 *
 * \param[in] flags The inclusive OR of
 *
 * LS_DATE - %Print file modification date
 *
 * LS_SIZE - %Print file size.
 *
 * LS_R - Recursive list of subdirectories.
 *
 * \param[in] indent Amount of space before file name. Used for recursive
 * list to indicate subdirectory level.
 */
void SdFile::ls(uint8_t flags, uint8_t indent) {
  dir_t* p;

  rewind();
  while ((p = readDirCache())) {
    // done if past last used entry
    if (p->name[0] == DIR_NAME_FREE) break;

    // skip deleted entry and entries for . and  ..
    if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue;

    // only list subdirectories and files
    if (!DIR_IS_FILE_OR_SUBDIR(p)) continue;

    // print any indent spaces
    for (int8_t i = 0; i < indent; i++) Serial.print(' ');

    // print file name with possible blank fill
    printDirName(*p, flags & (LS_DATE | LS_SIZE) ? 14 : 0);

    // print modify date/time if requested
    if (flags & LS_DATE) {
       printFatDate(p->lastWriteDate);
       Serial.print(' ');
       printFatTime(p->lastWriteTime);
    }
    // print size if requested
    if (!DIR_IS_SUBDIR(p) && (flags & LS_SIZE)) {
      Serial.print(' ');
      Serial.print(p->fileSize);
    }
    Serial.println();

    // list subdirectory content if requested
    if ((flags & LS_R) && DIR_IS_SUBDIR(p)) {
      uint16_t index = curPosition()/32 - 1;
      SdFile s;
      if (s.open(this, index, O_READ)) s.ls(flags, indent + 2);
      seekSet(32 * (index + 1));
    }
  }
}
//------------------------------------------------------------------------------
// format directory name field from a 8.3 name string
uint8_t SdFile::make83Name(const char* str, uint8_t* name) {
  uint8_t c;
  uint8_t n = 7;  // max index for part before dot
  uint8_t i = 0;
  // blank fill name and extension
  while (i < 11) name[i++] = ' ';
  i = 0;
  while ((c = *str++) != '\0') {
    if (c == '.') {
      if (n == 10) return false;  // only one dot allowed
      n = 10;  // max index for full 8.3 name
      i = 8;   // place for extension
    } else {
      // illegal FAT characters
//      PGM_P p = PSTR("|<>^+=?/[];,*\"\\");
	  char p[] = {"|<>^+=?/[];,*\"\\"};
	  char *ptr = p;
	  uint8_t b;
      while ((b = *(ptr++))) {if (b == c) return false; }
      // check size and only allow ASCII printable characters
      if (i > n || c < 0X21 || c > 0X7E)return false;
      // only upper case allowed in 8.3 names - convert lower to upper
      name[i++] = c < 'a' || c > 'z' ?  c : c + ('A' - 'a');
    }
  }
  // must have a file name, extension is optional
  return name[0] != ' ';
}
//------------------------------------------------------------------------------
/** Make a new directory.
 *
 * \param[in] dir An open SdFat instance for the directory that will containing
 * the new directory.
 *
 * \param[in] dirName A valid 8.3 DOS name for the new directory.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include this SdFile is already open, \a dir is not a
 * directory, \a dirName is invalid or already exists in \a dir.
 */
uint8_t SdFile::makeDir(SdFile* dir, const char* dirName) {
  dir_t d;

  // create a normal file
  if (!open(dir, dirName, O_CREAT | O_EXCL | O_RDWR1)) return false;

  // convert SdFile to directory
  flags_ = O_READ;
  type_ = FAT_FILE_TYPE_SUBDIR;

  // allocate and zero first cluster
  if (!addDirCluster())return false;

  // force entry to SD
  if (!sync()) return false;

  // cache entry - should already be in cache due to sync() call
  dir_t* p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
  if (!p) return false;

  // change directory entry  attribute
  p->attributes = DIR_ATT_DIRECTORY;

  // make entry for '.'
  memcpy(&d, p, sizeof(d));
  for (uint8_t i = 1; i < 11; i++) d.name[i] = ' ';
  d.name[0] = '.';

  // cache block for '.'  and '..'
  uint32_t block = vol_->clusterStartBlock(firstCluster_);
  if (!SdVolume::cacheRawBlock(block, SdVolume::CACHE_FOR_WRITE)) return false;

  // copy '.' to block
  memcpy(&SdVolume::cacheBuffer_.dir[0], &d, sizeof(d));

  // make entry for '..'
  d.name[1] = '.';
  if (dir->isRoot()) {
    d.firstClusterLow = 0;
    d.firstClusterHigh = 0;
  } else {
    d.firstClusterLow = dir->firstCluster_ & 0XFFFF;
    d.firstClusterHigh = dir->firstCluster_ >> 16;
  }
  // copy '..' to block
  memcpy(&SdVolume::cacheBuffer_.dir[1], &d, sizeof(d));

  // set position after '..'
  curPosition_ = 2 * sizeof(d);

  // write first block
  return SdVolume::cacheFlush();
}
//------------------------------------------------------------------------------
/**
 * Open a file or directory by name.
 *
 * \param[in] dirFile An open SdFat instance for the directory containing the
 * file to be opened.
 *
 * \param[in] fileName A valid 8.3 DOS name for a file to be opened.
 *
 * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
 * OR of flags from the following list
 *
 * O_READ - Open for reading.
 *
 * O_RDONLY - Same as O_READ.
 *
 * O_WRITE - Open for writing.
 *
 * O_WRONLY - Same as O_WRITE.
 *
 * O_RDWR1 - Open for reading and writing.
 *
 * O_APPEND - If set, the file offset shall be set to the end of the
 * file prior to each write.
 *
 * O_CREAT - If the file exists, this flag has no effect except as noted
 * under O_EXCL below. Otherwise, the file shall be created
 *
 * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists.
 *
 * O_SYNC - Call sync() after each write.  This flag should not be used with
 * write(uint8_t), write_P(PGM_P), writeln_P(PGM_P), or the Arduino Print class.
 * These functions do character at a time writes so sync() will be called
 * after each byte.
 *
 * O_TRUNC - If the file exists and is a regular file, and the file is
 * successfully opened and is not read only, its length shall be truncated to 0.
 *
 * \note Directory files must be opened read only.  Write and truncation is
 * not allowed for directory files.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include this SdFile is already open, \a difFile is not
 * a directory, \a fileName is invalid, the file does not exist
 * or can't be opened in the access mode specified by oflag.
 */
uint8_t SdFile::open(SdFile* dirFile, const char* fileName, uint8_t oflag) {
  uint8_t dname[11];
  dir_t* p;

  // error if already open
  if (isOpen())return false;

  if (!make83Name(fileName, dname)) return false;
  vol_ = dirFile->vol_;
  dirFile->rewind();

  // bool for empty entry found
  uint8_t emptyFound = false;

  // search for file
  while (dirFile->curPosition_ < dirFile->fileSize_) {
    uint8_t index = 0XF & (dirFile->curPosition_ >> 5);
    p = dirFile->readDirCache();
    if (p == NULL) return false;

    if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) {
      // remember first empty slot
      if (!emptyFound) {
        emptyFound = true;
        dirIndex_ = index;
        dirBlock_ = SdVolume::cacheBlockNumber_;
      }
      // done if no entries follow
      if (p->name[0] == DIR_NAME_FREE) break;
    } else if (!memcmp(dname, p->name, 11)) {
      // don't open existing file if O_CREAT and O_EXCL
      if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) return false;

      // open found file
      return openCachedEntry(0XF & index, oflag);
    }
  }
  // only create file if O_CREAT and O_WRITE
  if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) return false;

  // cache found slot or add cluster if end of file
  if (emptyFound) {
    p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
    if (!p) return false;
  } else {
    if (dirFile->type_ == FAT_FILE_TYPE_ROOT16) return false;

    // add and zero cluster for dirFile - first cluster is in cache for write
    if (!dirFile->addDirCluster()) return false;

    // use first entry in cluster
    dirIndex_ = 0;
    p = SdVolume::cacheBuffer_.dir;
  }
  // initialize as empty file
  memset(p, 0, sizeof(dir_t));
  memcpy(p->name, dname, 11);

  // set timestamps - SM - removed some stuff
  //if (dateTime_) {
    // call user function
  //  dateTime_(&p->creationDate, &p->creationTime);
  //} else {
    // use default date/time
  //  p->creationDate = FAT_DEFAULT_DATE;
  //  p->creationTime = FAT_DEFAULT_TIME;
  //}
  // set timestamps - SM changes added here (used present date, time)
  p->creationDate = FAT_DATE(Time.year(), Time.month(), Time.day());
  p->creationTime = FAT_TIME(Time.hour(), Time.minute(), Time.second());
  p->lastAccessDate = p->creationDate;
  p->lastWriteDate = p->creationDate;
  p->lastWriteTime = p->creationTime;

  // force write of entry to SD
  if (!SdVolume::cacheFlush()) return false;

  // open entry in cache
  return openCachedEntry(dirIndex_, oflag);
}
//------------------------------------------------------------------------------
/**
 * Open a file by index.
 *
 * \param[in] dirFile An open SdFat instance for the directory.
 *
 * \param[in] index The \a index of the directory entry for the file to be
 * opened.  The value for \a index is (directory file position)/32.
 *
 * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
 * OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC.
 *
 * See open() by fileName for definition of flags and return values.
 *
 */
uint8_t SdFile::open(SdFile* dirFile, uint16_t index, uint8_t oflag) {
  // error if already open
  if (isOpen())return false;

  // don't open existing file if O_CREAT and O_EXCL - user call error
  if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) return false;

  vol_ = dirFile->vol_;

  // seek to location of entry
  if (!dirFile->seekSet(32 * index)) return false;

  // read entry into cache
  dir_t* p = dirFile->readDirCache();
  if (p == NULL) return false;

  // error if empty slot or '.' or '..'
  if (p->name[0] == DIR_NAME_FREE ||
      p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') {
    return false;
  }
  // open cached entry
  return openCachedEntry(index & 0XF, oflag);
}
//------------------------------------------------------------------------------
// open a cached directory entry. Assumes vol_ is initializes
uint8_t SdFile::openCachedEntry(uint8_t dirIndex, uint8_t oflag) {
  // location of entry in cache
  dir_t* p = SdVolume::cacheBuffer_.dir + dirIndex;

  // write or truncate is an error for a directory or read-only file
  if (p->attributes & (DIR_ATT_READ_ONLY | DIR_ATT_DIRECTORY)) {
    if (oflag & (O_WRITE | O_TRUNC)) return false;
  }
  // remember location of directory entry on SD
  dirIndex_ = dirIndex;
  dirBlock_ = SdVolume::cacheBlockNumber_;

  // copy first cluster number for directory fields
  firstCluster_ = (uint32_t)p->firstClusterHigh << 16;
  firstCluster_ |= p->firstClusterLow;

  // make sure it is a normal file or subdirectory
  if (DIR_IS_FILE(p)) {
    fileSize_ = p->fileSize;
    type_ = FAT_FILE_TYPE_NORMAL;
  } else if (DIR_IS_SUBDIR(p)) {
    if (!vol_->chainSize(firstCluster_, &fileSize_)) return false;
    type_ = FAT_FILE_TYPE_SUBDIR;
  } else {
    return false;
  }
  // save open flags for read/write
  flags_ = oflag & (O_ACCMODE | O_SYNC | O_APPEND);

  // set to start of file
  curCluster_ = 0;
  curPosition_ = 0;

  // truncate file to zero length if requested
  if (oflag & O_TRUNC) return truncate(0);
  return true;
}
//------------------------------------------------------------------------------
/**
 * Open a volume's root directory.
 *
 * \param[in] vol The FAT volume containing the root directory to be opened.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include the FAT volume has not been initialized
 * or it a FAT12 volume.
 */
uint8_t SdFile::openRoot(SdVolume* vol) {
  // error if file is already open
  if (isOpen()) return false;

  if (vol->fatType() == 16) {
    type_ = FAT_FILE_TYPE_ROOT16;
    firstCluster_ = 0;
    fileSize_ = 32 * vol->rootDirEntryCount();
  } else if (vol->fatType() == 32) {
    type_ = FAT_FILE_TYPE_ROOT32;
    firstCluster_ = vol->rootDirStart();
    if (!vol->chainSize(firstCluster_, &fileSize_)) return false;
  } else {
    // volume is not initialized or FAT12
    return false;
  }
  vol_ = vol;
  // read only
  flags_ = O_READ;

  // set to start of file
  curCluster_ = 0;
  curPosition_ = 0;

  // root has no directory entry
  dirBlock_ = 0;
  dirIndex_ = 0;
  return true;
}
//------------------------------------------------------------------------------
/** %Print the name field of a directory entry in 8.3 format to Serial.
 * (for testing)
 * \param[in] dir The directory structure containing the name.
 * \param[in] width Blank fill name if length is less than \a width.
 */
void SdFile::printDirName(const dir_t& dir, uint8_t width) {
  uint8_t w = 0;
  for (uint8_t i = 0; i < 11; i++) {
    if (dir.name[i] == ' ')continue;
    if (i == 8) {
      Serial.print('.');
      w++;
    }
    Serial.print((char)dir.name[i]);
    w++;
  }
  if (DIR_IS_SUBDIR(&dir)) {
    Serial.print('/');
    w++;
  }
  while (w < width) {
    Serial.print(' ');
    w++;
  }
}
//------------------------------------------------------------------------------
/** %Print a directory date field to Serial.
 *
 *  Format is yyyy-mm-dd.
 *
 * \param[in] fatDate The date field from a directory entry.
 */
void SdFile::printFatDate(uint16_t fatDate) {
  Serial.print(FAT_YEAR(fatDate));
  Serial.print('-');
  printTwoDigits(FAT_MONTH(fatDate));
  Serial.print('-');
  printTwoDigits(FAT_DAY(fatDate));
}
//------------------------------------------------------------------------------
/** %Print a directory time field to Serial.
 *
 * Format is hh:mm:ss.
 *
 * \param[in] fatTime The time field from a directory entry.
 */
void SdFile::printFatTime(uint16_t fatTime) {
  printTwoDigits(FAT_HOUR(fatTime));
  Serial.print(':');
  printTwoDigits(FAT_MINUTE(fatTime));
  Serial.print(':');
  printTwoDigits(FAT_SECOND(fatTime));
}
//------------------------------------------------------------------------------
/** %Print a value as two digits to Serial.
 *
 * \param[in] v Value to be printed, 0 <= \a v <= 99
 */
void SdFile::printTwoDigits(uint8_t v) {
  char str[3];
  str[0] = '0' + v/10;
  str[1] = '0' + v % 10;
  str[2] = 0;
  Serial.print(str);
}
//------------------------------------------------------------------------------
/**
 * Read data from a file starting at the current position.
 *
 * \param[out] buf Pointer to the location that will receive the data.
 *
 * \param[in] nbyte Maximum number of bytes to read.
 *
 * \return For success read() returns the number of bytes read.
 * A value less than \a nbyte, including zero, will be returned
 * if end of file is reached.
 * If an error occurs, read() returns -1.  Possible errors include
 * read() called before a file has been opened, corrupt file system
 * or an I/O error occurred.
 */
int16_t SdFile::read(void* buf, uint16_t nbyte) {
  uint8_t* dst = reinterpret_cast<uint8_t*>(buf);

  // error if not open or write only
  if (!isOpen() || !(flags_ & O_READ)) return -1;

  // max bytes left in file
  if (nbyte > (fileSize_ - curPosition_)) nbyte = fileSize_ - curPosition_;

  // amount left to read
  uint16_t toRead = nbyte;
  while (toRead > 0) {
    uint32_t block;  // raw device block number
    uint16_t offset = curPosition_ & 0X1FF;  // offset in block
    if (type_ == FAT_FILE_TYPE_ROOT16) {
      block = vol_->rootDirStart() + (curPosition_ >> 9);
    } else {
      uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_);
      if (offset == 0 && blockOfCluster == 0) {
        // start of new cluster
        if (curPosition_ == 0) {
          // use first cluster in file
          curCluster_ = firstCluster_;
        } else {
          // get next cluster from FAT
          if (!vol_->fatGet(curCluster_, &curCluster_)) return -1;
        }
      }
      block = vol_->clusterStartBlock(curCluster_) + blockOfCluster;
    }
    uint16_t n = toRead;

    // amount to be read from current block
    if (n > (512 - offset)) n = 512 - offset;

    // no buffering needed if n == 512 or user requests no buffering
    if ((unbufferedRead() || n == 512) &&
      block != SdVolume::cacheBlockNumber_) {
      if (!vol_->readData(block, offset, n, dst)) return -1;
      dst += n;
    } else {
      // read block to cache and copy data to caller
      if (!SdVolume::cacheRawBlock(block, SdVolume::CACHE_FOR_READ)) return -1;
      uint8_t* src = SdVolume::cacheBuffer_.data + offset;
      uint8_t* end = src + n;
      while (src != end) *dst++ = *src++;
    }
    curPosition_ += n;
    toRead -= n;
  }
  return nbyte;
}
//------------------------------------------------------------------------------
/**
 * Read the next directory entry from a directory file.
 *
 * \param[out] dir The dir_t struct that will receive the data.
 *
 * \return For success readDir() returns the number of bytes read.
 * A value of zero will be returned if end of file is reached.
 * If an error occurs, readDir() returns -1.  Possible errors include
 * readDir() called before a directory has been opened, this is not
 * a directory file or an I/O error occurred.
 */
int8_t SdFile::readDir(dir_t* dir) {
  int8_t n;
  // if not a directory file or miss-positioned return an error
  if (!isDir() || (0X1F & curPosition_)) return -1;

  while ((n = read(dir, sizeof(dir_t))) == sizeof(dir_t)) {
    // last entry if DIR_NAME_FREE
    if (dir->name[0] == DIR_NAME_FREE) break;
    // skip empty entries and entry for .  and ..
    if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') continue;
    // return if normal file or subdirectory
    if (DIR_IS_FILE_OR_SUBDIR(dir)) return n;
  }
  // error, end of file, or past last entry
  return n < 0 ? -1 : 0;
}
//------------------------------------------------------------------------------
// Read next directory entry into the cache
// Assumes file is correctly positioned
dir_t* SdFile::readDirCache(void) {
  // error if not directory
  if (!isDir()) return NULL;

  // index of entry in cache
  uint8_t i = (curPosition_ >> 5) & 0XF;

  // use read to locate and cache block
  if (read() < 0) return NULL;

  // advance to next entry
  curPosition_ += 31;

  // return pointer to entry
  return (SdVolume::cacheBuffer_.dir + i);
}
//------------------------------------------------------------------------------
/**
 * Remove a file.
 *
 * The directory entry and all data for the file are deleted.
 *
 * \note This function should not be used to delete the 8.3 version of a
 * file that has a long name. For example if a file has the long name
 * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include the file read-only, is a directory,
 * or an I/O error occurred.
 */
uint8_t SdFile::remove(void) {
  // free any clusters - will fail if read-only or directory
  if (!truncate(0)) return false;

  // cache directory entry
  dir_t* d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
  if (!d) return false;

  // mark entry deleted
  d->name[0] = DIR_NAME_DELETED;

  // set this SdFile closed
  type_ = FAT_FILE_TYPE_CLOSED;

  // write entry to SD
  return SdVolume::cacheFlush();
}
//------------------------------------------------------------------------------
/**
 * Remove a file.
 *
 * The directory entry and all data for the file are deleted.
 *
 * \param[in] dirFile The directory that contains the file.
 * \param[in] fileName The name of the file to be removed.
 *
 * \note This function should not be used to delete the 8.3 version of a
 * file that has a long name. For example if a file has the long name
 * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include the file is a directory, is read only,
 * \a dirFile is not a directory, \a fileName is not found
 * or an I/O error occurred.
 */
uint8_t SdFile::remove(SdFile* dirFile, const char* fileName) {
  SdFile file;
  if (!file.open(dirFile, fileName, O_WRITE)) return false;
  return file.remove();
}
//------------------------------------------------------------------------------
/** Remove a directory file.
 *
 * The directory file will be removed only if it is empty and is not the
 * root directory.  rmDir() follows DOS and Windows and ignores the
 * read-only attribute for the directory.
 *
 * \note This function should not be used to delete the 8.3 version of a
 * directory that has a long name. For example if a directory has the
 * long name "New folder" you should not delete the 8.3 name "NEWFOL~1".
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include the file is not a directory, is the root
 * directory, is not empty, or an I/O error occurred.
 */
uint8_t SdFile::rmDir(void) {
  // must be open subdirectory
  if (!isSubDir()) return false;

  rewind();

  // make sure directory is empty
  while (curPosition_ < fileSize_) {
    dir_t* p = readDirCache();
    if (p == NULL) return false;
    // done if past last used entry
    if (p->name[0] == DIR_NAME_FREE) break;
    // skip empty slot or '.' or '..'
    if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue;
    // error not empty
    if (DIR_IS_FILE_OR_SUBDIR(p)) return false;
  }
  // convert empty directory to normal file for remove
  type_ = FAT_FILE_TYPE_NORMAL;
  flags_ |= O_WRITE;
  return remove();
}
//------------------------------------------------------------------------------
/** Recursively delete a directory and all contained files.
 *
 * This is like the Unix/Linux 'rm -rf *' if called with the root directory
 * hence the name.
 *
 * Warning - This will remove all contents of the directory including
 * subdirectories.  The directory will then be removed if it is not root.
 * The read-only attribute for files will be ignored.
 *
 * \note This function should not be used to delete the 8.3 version of
 * a directory that has a long name.  See remove() and rmDir().
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t SdFile::rmRfStar(void) {
  rewind();
  while (curPosition_ < fileSize_) {
    SdFile f;

    // remember position
    uint16_t index = curPosition_/32;

    dir_t* p = readDirCache();
    if (!p) return false;

    // done if past last entry
    if (p->name[0] == DIR_NAME_FREE) break;

    // skip empty slot or '.' or '..'
    if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue;

    // skip if part of long file name or volume label in root
    if (!DIR_IS_FILE_OR_SUBDIR(p)) continue;

    if (!f.open(this, index, O_READ)) return false;
    if (f.isSubDir()) {
      // recursively delete
      if (!f.rmRfStar()) return false;
    } else {
      // ignore read-only
      f.flags_ |= O_WRITE;
      if (!f.remove()) return false;
    }
    // position to next entry if required
    if (curPosition_ != (uint32_t)(32*(index + 1))) {
      if (!seekSet(32*(index + 1))) return false;
    }
  }
  // don't try to delete root
  if (isRoot()) return true;
  return rmDir();
}
//------------------------------------------------------------------------------
/**
 * Sets a file's position.
 *
 * \param[in] pos The new position in bytes from the beginning of the file.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t SdFile::seekSet(uint32_t pos) {
  // error if file not open or seek past end of file
  if (!isOpen() || pos > fileSize_) return false;

  if (type_ == FAT_FILE_TYPE_ROOT16) {
    curPosition_ = pos;
    return true;
  }
  if (pos == 0) {
    // set position to start of file
    curCluster_ = 0;
    curPosition_ = 0;
    return true;
  }
  // calculate cluster index for cur and new position
  uint32_t nCur = (curPosition_ - 1) >> (vol_->clusterSizeShift_ + 9);
  uint32_t nNew = (pos - 1) >> (vol_->clusterSizeShift_ + 9);

  if (nNew < nCur || curPosition_ == 0) {
    // must follow chain from first cluster
    curCluster_ = firstCluster_;
  } else {
    // advance from curPosition
    nNew -= nCur;
  }
  while (nNew--) {
    if (!vol_->fatGet(curCluster_, &curCluster_)) return false;
  }
  curPosition_ = pos;
  return true;
}
//------------------------------------------------------------------------------
/**
 * The sync() call causes all modified data and directory fields
 * to be written to the storage device.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 * Reasons for failure include a call to sync() before a file has been
 * opened or an I/O error.
 */
uint8_t SdFile::sync(void) {
  // only allow open files and directories
  if (!isOpen()) return false;

  if (flags_ & F_FILE_DIR_DIRTY) {
    dir_t* d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
    if (!d) return false;

    // do not set filesize for dir files
    if (!isDir()) d->fileSize = fileSize_;

    // update first cluster fields
    d->firstClusterLow = firstCluster_ & 0XFFFF;
    d->firstClusterHigh = firstCluster_ >> 16;

    // set modify time if user supplied a callback date/time function
    if (dateTime_) {
      dateTime_(&d->lastWriteDate, &d->lastWriteTime);
      d->lastAccessDate = d->lastWriteDate;
    } else { // SM - added this
      // set timestamps - SM changes added here (used present date, time)
      d->lastWriteDate = FAT_DATE(Time.year(), Time.month(), Time.day());
      d->lastWriteTime = FAT_TIME(Time.hour(), Time.minute(), Time.second());
      d->lastAccessDate = d->lastWriteDate;
    }
    // clear directory dirty
    flags_ &= ~F_FILE_DIR_DIRTY;
  }
  return SdVolume::cacheFlush();
}
//------------------------------------------------------------------------------
/**
 * Set a file's timestamps in its directory entry.
 *
 * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive
...

This file has been truncated, please download it to see its full contents.

sd-fat.h

C/C++
Interface to Mikroe hardware
/* Arduino SdFat Library
 * Copyright (C) 2009 by William Greiman
 *
 * This file is part of the Arduino SdFat Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino SdFat Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
#ifndef _SD_FAT
#define _SD_FAT
/**
 * \file
 * SdFile and SdVolume classes
 */
#include "application.h"
#include "sd-fat-util.h"
#include "sd2-card.h"
#include "fat-structs.h"
//#include "spark_wiring_print.h"

//------------------------------------------------------------------------------
/**
 * Allow use of deprecated functions if non-zero
 */
#define ALLOW_DEPRECATED_FUNCTIONS 1
//------------------------------------------------------------------------------
// forward declaration since SdVolume is used in SdFile
class SdVolume;
//==============================================================================
// SdFile class

// flags for ls()
/** ls() flag to print modify date */
uint8_t const LS_DATE = 1;
/** ls() flag to print file size */
uint8_t const LS_SIZE = 2;
/** ls() flag for recursive list of subdirectories */
uint8_t const LS_R = 4;

// use the gnu style oflag in open() SM - NOTE that many are already defined elsewhere
/** open() oflag for reading */
uint8_t const O_READ = 0X01;
/** open() oflag - same as O_READ */
//uint8_t const O_RDONLY = O_READ; // SM presently not used
/** open() oflag for write */
uint8_t const O_WRITE = 0X02;
/** open() oflag - same as O_WRITE */
//uint8_t const O_WRONLY = O_WRITE; // SM presently not used
/** open() oflag for reading and writing */
uint8_t const O_RDWR1 = (O_READ | O_WRITE); // SM changed
/** open() oflag mask for access modes */
uint8_t const O_ACCMODE = (O_READ | O_WRITE);
/** The file offset shall be set to the end of the file prior to each write. */
uint8_t const O_APPEND = 0X04;
/** synchronous writes - call sync() after each write */
uint8_t const O_SYNC = 0X08;
/** create the file if nonexistent */
uint8_t const O_CREAT = 0X10;
/** If O_CREAT and O_EXCL are set, open() shall fail if the file exists */
uint8_t const O_EXCL = 0X20;
/** truncate the file to zero length */
uint8_t const O_TRUNC = 0X40;

// flags for timestamp
/** set the file's last access date */
uint8_t const T_ACCESS = 1;
/** set the file's creation date and time */
uint8_t const T_CREATE = 2;
/** Set the file's write date and time */
uint8_t const T_WRITE = 4;
// values for type_
/** This SdFile has not been opened. */
uint8_t const FAT_FILE_TYPE_CLOSED = 0;
/** SdFile for a file */
uint8_t const FAT_FILE_TYPE_NORMAL = 1;
/** SdFile for a FAT16 root directory */
uint8_t const FAT_FILE_TYPE_ROOT16 = 2;
/** SdFile for a FAT32 root directory */
uint8_t const FAT_FILE_TYPE_ROOT32 = 3;
/** SdFile for a subdirectory */
uint8_t const FAT_FILE_TYPE_SUBDIR = 4;
/** Test value for directory type */
uint8_t const FAT_FILE_TYPE_MIN_DIR = FAT_FILE_TYPE_ROOT16;

/** date field for FAT directory entry */
static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) {
  return (year - 1980) << 9 | month << 5 | day;
}
/** year part of FAT directory date field */
static inline uint16_t FAT_YEAR(uint16_t fatDate) {
  return 1980 + (fatDate >> 9);
}
/** month part of FAT directory date field */
static inline uint8_t FAT_MONTH(uint16_t fatDate) {
  return (fatDate >> 5) & 0XF;
}
/** day part of FAT directory date field */
static inline uint8_t FAT_DAY(uint16_t fatDate) {
  return fatDate & 0X1F;
}
/** time field for FAT directory entry */
static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
  return hour << 11 | minute << 5 | second >> 1;
}
/** hour part of FAT directory time field */
static inline uint8_t FAT_HOUR(uint16_t fatTime) {
  return fatTime >> 11;
}
/** minute part of FAT directory time field */
static inline uint8_t FAT_MINUTE(uint16_t fatTime) {
  return(fatTime >> 5) & 0X3F;
}
/** second part of FAT directory time field */
static inline uint8_t FAT_SECOND(uint16_t fatTime) {
  return 2*(fatTime & 0X1F);
}
/** Default date for file timestamps is 1 Jan 2000 */
uint16_t const FAT_DEFAULT_DATE = ((2000 - 1980) << 9) | (1 << 5) | 1;
/** Default time for file timestamp is 1 am */
uint16_t const FAT_DEFAULT_TIME = (1 << 11);
//------------------------------------------------------------------------------
/**
 * \class SdFile
 * \brief Access FAT16 and FAT32 files on SD and SDHC cards.
 */
class SdFile : public Print {
 public:
  /** Create an instance of SdFile. */
  SdFile(void) : type_(FAT_FILE_TYPE_CLOSED) {}
  /**
   * writeError is set to true if an error occurs during a write().
   * Set writeError to false before calling print() and/or write() and check
   * for true after calls to print() and/or write().
   */
  bool writeError;
  /**
   * Cancel unbuffered reads for this file.
   * See setUnbufferedRead()
   */
  void clearUnbufferedRead(void) {
    flags_ &= ~F_FILE_UNBUFFERED_READ;
  }
  uint8_t close(void);
  uint8_t contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock);
  uint8_t createContiguous(SdFile* dirFile,
          const char* fileName, uint32_t size);
  /** \return The current cluster number for a file or directory. */
  uint32_t curCluster(void) const {return curCluster_;}
  /** \return The current position for a file or directory. */
  uint32_t curPosition(void) const {return curPosition_;}
  /**
   * Set the date/time callback function
   *
   * \param[in] dateTime The user's call back function.  The callback
   * function is of the form:
   *
   * \code
   * void dateTime(uint16_t* date, uint16_t* time) {
   *   uint16_t year;
   *   uint8_t month, day, hour, minute, second;
   *
   *   // User gets date and time from GPS or real-time clock here
   *
   *   // return date using FAT_DATE macro to format fields
   *   *date = FAT_DATE(year, month, day);
   *
   *   // return time using FAT_TIME macro to format fields
   *   *time = FAT_TIME(hour, minute, second);
   * }
   * \endcode
   *
   * Sets the function that is called when a file is created or when
   * a file's directory entry is modified by sync(). All timestamps,
   * access, creation, and modify, are set when a file is created.
   * sync() maintains the last access date and last modify date/time.
   *
   * See the timestamp() function.
   */

  static void dateTimeCallback(
    void (*dateTime)(uint16_t* date, uint16_t* time)) {
      dateTime_ = dateTime;
  }
  /**
   * Cancel the date/time callback function.
   */
  static void dateTimeCallbackCancel(void) {
    // use explicit zero since NULL is not defined for Sanguino
    dateTime_ = 0;
  }
  /** \return Address of the block that contains this file's directory. */
  uint32_t dirBlock(void) const {return dirBlock_;}
  uint8_t dirEntry(dir_t* dir);
  /** \return Index of this file's directory in the block dirBlock. */
  uint8_t dirIndex(void) const {return dirIndex_;}
  static void dirName(const dir_t& dir, char* name);
  /** \return The total number of bytes in a file or directory. */
  uint32_t fileSize(void) const {return fileSize_;}
  /** \return The first cluster number for a file or directory. */
  uint32_t firstCluster(void) const {return firstCluster_;}
  /** \return True if this is a SdFile for a directory else false. */
  uint8_t isDir(void) const {return type_ >= FAT_FILE_TYPE_MIN_DIR;}
  /** \return True if this is a SdFile for a file else false. */
  uint8_t isFile(void) const {return type_ == FAT_FILE_TYPE_NORMAL;}
  /** \return True if this is a SdFile for an open file/directory else false. */
  uint8_t isOpen(void) const {return type_ != FAT_FILE_TYPE_CLOSED;}
  /** \return True if this is a SdFile for a subdirectory else false. */
  uint8_t isSubDir(void) const {return type_ == FAT_FILE_TYPE_SUBDIR;}
  /** \return True if this is a SdFile for the root directory. */
  uint8_t isRoot(void) const {
    return type_ == FAT_FILE_TYPE_ROOT16 || type_ == FAT_FILE_TYPE_ROOT32;
  }
  void ls(uint8_t flags = 0, uint8_t indent = 0);
  uint8_t makeDir(SdFile* dir, const char* dirName);
  uint8_t open(SdFile* dirFile, uint16_t index, uint8_t oflag);
  uint8_t open(SdFile* dirFile, const char* fileName, uint8_t oflag);

  uint8_t openRoot(SdVolume* vol);
  static void printDirName(const dir_t& dir, uint8_t width);
  static void printFatDate(uint16_t fatDate);
  static void printFatTime(uint16_t fatTime);
  static void printTwoDigits(uint8_t v);
  /**
   * Read the next byte from a file.
   *
   * \return For success read returns the next byte in the file as an int.
   * If an error occurs or end of file is reached -1 is returned.
   */
  int16_t read(void) {
    uint8_t b;
    return read(&b, 1) == 1 ? b : -1;
  }
  int16_t read(void* buf, uint16_t nbyte);
  int8_t readDir(dir_t* dir);
  static uint8_t remove(SdFile* dirFile, const char* fileName);
  uint8_t remove(void);
  /** Set the file's current position to zero. */
  void rewind(void) {
    curPosition_ = curCluster_ = 0;
  }
  uint8_t rmDir(void);
  uint8_t rmRfStar(void);
  /** Set the files position to current position + \a pos. See seekSet(). */
  uint8_t seekCur(uint32_t pos) {
    return seekSet(curPosition_ + pos);
  }
  /**
   *  Set the files current position to end of file.  Useful to position
   *  a file for append. See seekSet().
   */
  uint8_t seekEnd(void) {return seekSet(fileSize_);}
  uint8_t seekSet(uint32_t pos);
  /**
   * Use unbuffered reads to access this file.  Used with Wave
   * Shield ISR.  Used with Sd2Card::partialBlockRead() in WaveRP.
   *
   * Not recommended for normal applications.
   */
  void setUnbufferedRead(void) {
    if (isFile()) flags_ |= F_FILE_UNBUFFERED_READ;
  }
  uint8_t timestamp(uint8_t flag, uint16_t year, uint8_t month, uint8_t day,
          uint8_t hour, uint8_t minute, uint8_t second);
  uint8_t sync(void);
  /** Type of this SdFile.  You should use isFile() or isDir() instead of type()
   * if possible.
   *
   * \return The file or directory type.
   */
  uint8_t type(void) const {return type_;}
  uint8_t truncate(uint32_t size);
  /** \return Unbuffered read flag. */
  uint8_t unbufferedRead(void) const {
    return flags_ & F_FILE_UNBUFFERED_READ;
  }
  /** \return SdVolume that contains this file. */
  SdVolume* volume(void) const {return vol_;}
  size_t write(uint8_t b);
  size_t write(const void* buf, uint16_t nbyte);
  size_t write(const char* str);
  void write_P(PGM_P str);
  void writeln_P(PGM_P str);
//------------------------------------------------------------------------------
#if ALLOW_DEPRECATED_FUNCTIONS
// Deprecated functions  - suppress cpplint warnings with NOLINT comment
  /** \deprecated Use:
   * uint8_t SdFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock);
   */
  uint8_t contiguousRange(uint32_t& bgnBlock, uint32_t& endBlock) {  // NOLINT
    return contiguousRange(&bgnBlock, &endBlock);
  }
 /** \deprecated Use:
   * uint8_t SdFile::createContiguous(SdFile* dirFile,
   *   const char* fileName, uint32_t size)
   */
  uint8_t createContiguous(SdFile& dirFile,  // NOLINT
    const char* fileName, uint32_t size) {
    return createContiguous(&dirFile, fileName, size);
  }

  /**
   * \deprecated Use:
   * static void SdFile::dateTimeCallback(
   *   void (*dateTime)(uint16_t* date, uint16_t* time));
   */
  static void dateTimeCallback(
    void (*dateTime)(uint16_t& date, uint16_t& time)) {  // NOLINT
    oldDateTime_ = dateTime;
    dateTime_ = dateTime ? oldToNew : 0;
  }
  /** \deprecated Use: uint8_t SdFile::dirEntry(dir_t* dir); */
  uint8_t dirEntry(dir_t& dir) {return dirEntry(&dir);}  // NOLINT
  /** \deprecated Use:
   * uint8_t SdFile::makeDir(SdFile* dir, const char* dirName);
   */
  uint8_t makeDir(SdFile& dir, const char* dirName) {  // NOLINT
    return makeDir(&dir, dirName);
  }
  /** \deprecated Use:
   * uint8_t SdFile::open(SdFile* dirFile, const char* fileName, uint8_t oflag);
   */
  uint8_t open(SdFile& dirFile, // NOLINT
    const char* fileName, uint8_t oflag) {
    return open(&dirFile, fileName, oflag);
  }
  /** \deprecated  Do not use in new apps */
  uint8_t open(SdFile& dirFile, const char* fileName) {  // NOLINT
    return open(dirFile, fileName, O_RDWR);
  }
  /** \deprecated Use:
   * uint8_t SdFile::open(SdFile* dirFile, uint16_t index, uint8_t oflag);
   */
  uint8_t open(SdFile& dirFile, uint16_t index, uint8_t oflag) {  // NOLINT
    return open(&dirFile, index, oflag);
  }
  /** \deprecated Use: uint8_t SdFile::openRoot(SdVolume* vol); */
  uint8_t openRoot(SdVolume& vol) {return openRoot(&vol);}  // NOLINT

  /** \deprecated Use: int8_t SdFile::readDir(dir_t* dir); */
  int8_t readDir(dir_t& dir) {return readDir(&dir);}  // NOLINT
  /** \deprecated Use:
   * static uint8_t SdFile::remove(SdFile* dirFile, const char* fileName);
   */
  static uint8_t remove(SdFile& dirFile, const char* fileName) {  // NOLINT
    return remove(&dirFile, fileName);
  }
//------------------------------------------------------------------------------
// rest are private
 private:
  static void (*oldDateTime_)(uint16_t& date, uint16_t& time);  // NOLINT
  static void oldToNew(uint16_t* date, uint16_t* time) {
    uint16_t d;
    uint16_t t;
    oldDateTime_(d, t);
    *date = d;
    *time = t;
  }
#endif  // ALLOW_DEPRECATED_FUNCTIONS
 private:
  // bits defined in flags_
  // should be 0XF
  static uint8_t const F_OFLAG = (O_ACCMODE | O_APPEND | O_SYNC); // SM changed
  // available bits
  static uint8_t const F_UNUSED = 0X30; // SM changed
  // use unbuffered SD read
  static uint8_t const F_FILE_UNBUFFERED_READ = 0X40; // SM changed
  // sync of directory entry required
  static uint8_t const F_FILE_DIR_DIRTY = 0X80; // SM changed

// make sure F_OFLAG is ok
//#if ((F_UNUSED | F_FILE_UNBUFFERED_READ | F_FILE_DIR_DIRTY) & F_OFLAG) // SM changed
//#error flags_ bits conflict // SM changed
//#endif  // flags_ bits // SM changed

  // private data
  uint8_t   flags_;         // See above for definition of flags_ bits
  uint8_t   type_;          // type of file see above for values
  uint32_t  curCluster_;    // cluster for current file position
  uint32_t  curPosition_;   // current file position in bytes from beginning
  uint32_t  dirBlock_;      // SD block that contains directory entry for file
  uint8_t   dirIndex_;      // index of entry in dirBlock 0 <= dirIndex_ <= 0XF
  uint32_t  fileSize_;      // file size in bytes
  uint32_t  firstCluster_;  // first cluster of file
  SdVolume* vol_;           // volume where file is located

  // private functions
  uint8_t addCluster(void);
  uint8_t addDirCluster(void);
  dir_t* cacheDirEntry(uint8_t action);
  static void (*dateTime_)(uint16_t* date, uint16_t* time);
  static uint8_t make83Name(const char* str, uint8_t* name);
  uint8_t openCachedEntry(uint8_t cacheIndex, uint8_t oflags);
  dir_t* readDirCache(void);
};
//==============================================================================
// SdVolume class
/**
 * \brief Cache for an SD data block
 */
union cache_t {
           /** Used to access cached file data blocks. */
  uint8_t  data[512];
           /** Used to access cached FAT16 entries. */
  uint16_t fat16[256];
           /** Used to access cached FAT32 entries. */
  uint32_t fat32[128];
           /** Used to access cached directory entries. */
  dir_t    dir[16];
           /** Used to access a cached MasterBoot Record. */
  mbr_t    mbr;
           /** Used to access to a cached FAT boot sector. */
  fbs_t    fbs;
};
//------------------------------------------------------------------------------
/**
 * \class SdVolume
 * \brief Access FAT16 and FAT32 volumes on SD and SDHC cards.
 */
class SdVolume {
 public:
  /** Create an instance of SdVolume */
  SdVolume(void) :allocSearchStart_(2), fatType_(0) {}
  /** Clear the cache and returns a pointer to the cache.  Used by the WaveRP
   *  recorder to do raw write to the SD card.  Not for normal apps.
   */
  static uint8_t* cacheClear(void) {
    cacheFlush();
    cacheBlockNumber_ = 0XFFFFFFFF;
    return cacheBuffer_.data;
  }
  /**
   * Initialize a FAT volume.  Try partition one first then try super
   * floppy format.
   *
   * \param[in] dev The Sd2Card where the volume is located.
   *
   * \return The value one, true, is returned for success and
   * the value zero, false, is returned for failure.  Reasons for
   * failure include not finding a valid partition, not finding a valid
   * FAT file system or an I/O error.
   */
  uint8_t init(Sd2Card* dev) { return init(dev, 1) ? true : init(dev, 0);}
  uint8_t init(Sd2Card* dev, uint8_t part);

  // inline functions that return volume info
  /** \return The volume's cluster size in blocks. */
  uint8_t blocksPerCluster(void) const {return blocksPerCluster_;}
  /** \return The number of blocks in one FAT. */
  uint32_t blocksPerFat(void)  const {return blocksPerFat_;}
  /** \return The total number of clusters in the volume. */
  uint32_t clusterCount(void) const {return clusterCount_;}
  /** \return The shift count required to multiply by blocksPerCluster. */
  uint8_t clusterSizeShift(void) const {return clusterSizeShift_;}
  /** \return The logical block number for the start of file data. */
  uint32_t dataStartBlock(void) const {return dataStartBlock_;}
  /** \return The number of FAT structures on the volume. */
  uint8_t fatCount(void) const {return fatCount_;}
  /** \return The logical block number for the start of the first FAT. */
  uint32_t fatStartBlock(void) const {return fatStartBlock_;}
  /** \return The FAT type of the volume. Values are 12, 16 or 32. */
  uint8_t fatType(void) const {return fatType_;}
  /** \return The number of entries in the root directory for FAT16 volumes. */
  uint32_t rootDirEntryCount(void) const {return rootDirEntryCount_;}
  /** \return The logical block number for the start of the root directory
       on FAT16 volumes or the first cluster number on FAT32 volumes. */
  uint32_t rootDirStart(void) const {return rootDirStart_;}
  /** return a pointer to the Sd2Card object for this volume */
  static Sd2Card* sdCard(void) {return sdCard_;}
//------------------------------------------------------------------------------
#if ALLOW_DEPRECATED_FUNCTIONS
  // Deprecated functions  - suppress cpplint warnings with NOLINT comment
  /** \deprecated Use: uint8_t SdVolume::init(Sd2Card* dev); */
  uint8_t init(Sd2Card& dev) {return init(&dev);}  // NOLINT

  /** \deprecated Use: uint8_t SdVolume::init(Sd2Card* dev, uint8_t vol); */
  uint8_t init(Sd2Card& dev, uint8_t part) {  // NOLINT
    return init(&dev, part);
  }
#endif  // ALLOW_DEPRECATED_FUNCTIONS
//------------------------------------------------------------------------------
  private:
  // Allow SdFile access to SdVolume private data.
  friend class SdFile;

  // value for action argument in cacheRawBlock to indicate read from cache
  static uint8_t const CACHE_FOR_READ = 0;
  // value for action argument in cacheRawBlock to indicate cache dirty
  static uint8_t const CACHE_FOR_WRITE = 1;

  static cache_t cacheBuffer_;        // 512 byte cache for device blocks
  static uint32_t cacheBlockNumber_;  // Logical number of block in the cache
  static Sd2Card* sdCard_;            // Sd2Card object for cache
  static uint8_t cacheDirty_;         // cacheFlush() will write block if true
  static uint32_t cacheMirrorBlock_;  // block number for mirror FAT
//
  uint32_t allocSearchStart_;   // start cluster for alloc search
  uint8_t blocksPerCluster_;    // cluster size in blocks
  uint32_t blocksPerFat_;       // FAT size in blocks
  uint32_t clusterCount_;       // clusters in one FAT
  uint8_t clusterSizeShift_;    // shift to convert cluster count to block count
  uint32_t dataStartBlock_;     // first data block number
  uint8_t fatCount_;            // number of FATs on volume
  uint32_t fatStartBlock_;      // start block for first FAT
  uint8_t fatType_;             // volume type (12, 16, OR 32)
  uint16_t rootDirEntryCount_;  // number of entries in FAT16 root dir
  uint32_t rootDirStart_;       // root start block for FAT16, cluster for FAT32
  //----------------------------------------------------------------------------
  uint8_t allocContiguous(uint32_t count, uint32_t* curCluster);
  uint8_t blockOfCluster(uint32_t position) const {
          return (position >> 9) & (blocksPerCluster_ - 1);}
  uint32_t clusterStartBlock(uint32_t cluster) const {
           return dataStartBlock_ + ((cluster - 2) << clusterSizeShift_);}
  uint32_t blockNumber(uint32_t cluster, uint32_t position) const {
           return clusterStartBlock(cluster) + blockOfCluster(position);}
  static uint8_t cacheFlush(void);
  static uint8_t cacheRawBlock(uint32_t blockNumber, uint8_t action);
  static void cacheSetDirty(void) {cacheDirty_ |= CACHE_FOR_WRITE;}
  static uint8_t cacheZeroBlock(uint32_t blockNumber);
  uint8_t chainSize(uint32_t beginCluster, uint32_t* size) const;
  uint8_t fatGet(uint32_t cluster, uint32_t* value) const;
  uint8_t fatPut(uint32_t cluster, uint32_t value);
  uint8_t fatPutEOC(uint32_t cluster) {
    return fatPut(cluster, 0x0FFFFFFF);
  }
  uint8_t freeChain(uint32_t cluster);
  uint8_t isEOC(uint32_t cluster) const {
    return  cluster >= (fatType_ == 16 ? FAT16EOC_MIN : FAT32EOC_MIN);
  }
  uint8_t readBlock(uint32_t block, uint8_t* dst) {
    return sdCard_->readBlock(block, dst);}
  uint8_t readData(uint32_t block, uint16_t offset,
    uint16_t count, uint8_t* dst) {
      return sdCard_->readData(block, offset, count, dst);
  }
  uint8_t writeBlock(uint32_t block, const uint8_t* dst) {
    return sdCard_->writeBlock(block, dst);
  }
};
#endif  // SdFat_h

sd-fat-main-page.h

C/C++
Interface to Mikroe hardware - SdFat Library Info from Arduino
/* Arduino SdFat Library
 * Copyright (C) 2009 by William Greiman
 *  
 * This file is part of the Arduino SdFat Library
 *  
 * This Library is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino SdFat Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

/**
\mainpage Arduino SdFat Library
<CENTER>Copyright &copy; 2009 by William Greiman
</CENTER>

\section Intro Introduction
The Arduino SdFat Library is a minimal implementation of FAT16 and FAT32
file systems on SD flash memory cards.  Standard SD and high capacity
SDHC cards are supported.

The SdFat only supports short 8.3 names.

The main classes in SdFat are Sd2Card, SdVolume, and SdFile.

The Sd2Card class supports access to standard SD cards and SDHC cards.  Most
applications will only need to call the Sd2Card::init() member function.

The SdVolume class supports FAT16 and FAT32 partitions.  Most applications
will only need to call the SdVolume::init() member function.

The SdFile class provides file access functions such as open(), read(),
remove(), write(), close() and sync(). This class supports access to the root
directory and subdirectories.

A number of example are provided in the SdFat/examples folder.  These were
developed to test SdFat and illustrate its use.

SdFat was developed for high speed data recording.  SdFat was used to implement
an audio record/play class, WaveRP, for the Adafruit Wave Shield.  This
application uses special Sd2Card calls to write to contiguous files in raw mode.
These functions reduce write latency so that audio can be recorded with the
small amount of RAM in the Arduino.

\section SDcard SD\SDHC Cards

Arduinos access SD cards using the cards SPI protocol.  PCs, Macs, and
most consumer devices use the 4-bit parallel SD protocol.  A card that
functions well on A PC or Mac may not work well on the Arduino.

Most cards have good SPI read performance but cards vary widely in SPI
write performance.  Write performance is limited by how efficiently the
card manages internal erase/remapping operations.  The Arduino cannot
optimize writes to reduce erase operations because of its limit RAM.

SanDisk cards generally have good write performance.  They seem to have
more internal RAM buffering than other cards and therefore can limit
the number of flash erase operations that the Arduino forces due to its
limited RAM.

\section Hardware Hardware Configuration

SdFat was developed using an
<A HREF = "http://www.adafruit.com/"> Adafruit Industries</A> 
<A HREF = "http://www.ladyada.net/make/waveshield/"> Wave Shield</A>.

The hardware interface to the SD card should not use a resistor based level
shifter.  SdFat sets the SPI bus frequency to 8 MHz which results in signal
rise times that are too slow for the edge detectors in many newer SD card
controllers when resistor voltage dividers are used.

The 5 to 3.3 V level shifter for 5 V Arduinos should be IC based like the
74HC4050N based circuit shown in the file SdLevel.png.  The Adafruit Wave Shield
uses a 74AHC125N.  Gravitech sells SD and MicroSD Card Adapters based on the
74LCX245.

If you are using a resistor based level shifter and are having problems try
setting the SPI bus frequency to 4 MHz.  This can be done by using 
card.init(SPI_HALF_SPEED) to initialize the SD card.

\section comment Bugs and Comments

If you wish to report bugs or have comments, send email to fat16lib@sbcglobal.net.

\section SdFatClass SdFat Usage

SdFat uses a slightly restricted form of short names.
Only printable ASCII characters are supported. No characters with code point
values greater than 127 are allowed.  Space is not allowed even though space
was allowed in the API of early versions of DOS.

Short names are limited to 8 characters followed by an optional period (.)
and extension of up to 3 characters.  The characters may be any combination
of letters and digits.  The following special characters are also allowed:

$ % ' - _ @ ~ ` ! ( ) { } ^ # &

Short names are always converted to upper case and their original case
value is lost.

\note
  The Arduino Print class uses character
at a time writes so it was necessary to use a \link SdFile::sync() sync() \endlink
function to control when data is written to the SD card.

\par
An application which writes to a file using \link Print::print() print()\endlink,
\link Print::println() println() \endlink
or \link SdFile::write write() \endlink must call \link SdFile::sync() sync() \endlink
at the appropriate time to force data and directory information to be written
to the SD Card.  Data and directory information are also written to the SD card
when \link SdFile::close() close() \endlink is called.

\par
Applications must use care calling \link SdFile::sync() sync() \endlink
since 2048 bytes of I/O is required to update file and
directory information.  This includes writing the current data block, reading
the block that contains the directory entry for update, writing the directory
block back and reading back the current data block.

It is possible to open a file with two or more instances of SdFile.  A file may
be corrupted if data is written to the file by more than one instance of SdFile.

\section HowTo How to format SD Cards as FAT Volumes

You should use a freshly formatted SD card for best performance.  FAT
file systems become slower if many files have been created and deleted.
This is because the directory entry for a deleted file is marked as deleted,
but is not deleted.  When a new file is created, these entries must be scanned
before creating the file, a flaw in the FAT design.  Also files can become
fragmented which causes reads and writes to be slower.

Microsoft operating systems support removable media formatted with a
Master Boot Record, MBR, or formatted as a super floppy with a FAT Boot Sector
in block zero.

Microsoft operating systems expect MBR formatted removable media
to have only one partition. The first partition should be used.

Microsoft operating systems do not support partitioning SD flash cards.
If you erase an SD card with a program like KillDisk, Most versions of
Windows will format the card as a super floppy.

The best way to restore an SD card's format is to use SDFormatter
which can be downloaded from:

http://www.sdcard.org/consumers/formatter/

SDFormatter aligns flash erase boundaries with file
system structures which reduces write latency and file system overhead.

SDFormatter does not have an option for FAT type so it may format
small cards as FAT12.

After the MBR is restored by SDFormatter you may need to reformat small
cards that have been formatted FAT12 to force the volume type to be FAT16.

If you reformat the SD card with an OS utility, choose a cluster size that
will result in:

4084 < CountOfClusters && CountOfClusters < 65525

The volume will then be FAT16.

If you are formatting an SD card on OS X or Linux, be sure to use the first
partition. Format this partition with a cluster count in above range.

\section  References References

Adafruit Industries:

http://www.adafruit.com/

http://www.ladyada.net/make/waveshield/

The Arduino site:

http://www.arduino.cc/

For more information about FAT file systems see:

http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx

For information about using SD cards as SPI devices see:

http://www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf

The ATmega328 datasheet:

http://www.atmel.com/dyn/resources/prod_documents/doc8161.pdf
 

 */  

sd-fat-util.h

C/C++
Interface to Mikroe hardware
/* Arduino SdFat Library
 * Copyright (C) 2008 by William Greiman
 *
 * This file is part of the Arduino SdFat Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with the Arduino SdFat Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
#ifndef _SD_FAT_UTIL
#define _SD_FAT_UTIL
/**
 * \file
 * Useful utility functions.
 */
//#include "application.h"
#include "sd2-card.h"
//#include <string.h>
/*
#ifndef PSTR
#define PSTR(x) (x)
#endif
#ifndef PROGMEM
#define PROGMEM         // N/A
#endif
*/
#ifndef PGM_P
#define PGM_P const char*
#endif
#ifndef prog_uchar
typedef const unsigned char  prog_uchar;
#endif
#ifndef prog_short
typedef const unsigned short prog_short;
#endif
#ifndef pgm_read_byte_near
#define pgm_read_byte_near(x) (*(prog_uchar*)x)
#endif
#ifndef pgm_read_byte
#define pgm_read_byte(x)      (*(prog_uchar*)x)
#endif
#ifndef pgm_read_word
#define pgm_read_word(x)      (*(prog_short*)x)
#endif
#ifndef strcpy_P
#define strcpy_P strcpy
#endif
#ifndef strncpy_P
#define strncpy_P strncpy
#endif

//#include <avr/pgmspace.h>
/** Store and print a string in flash memory.*/
/*
#ifndef PgmPrint
#define PgmPrint(x) Serial.print(PSTR(x))
#endif
*/
/** Store and print a string in flash memory followed by a CR/LF.*/
/*
#ifndef PgmPrintln
#define PgmPrintln(x) Serial.println(PSTR(x))
#endif
*/
/** Defined so doxygen works for function definitions. */
#define NOINLINE __attribute__((noinline))
//------------------------------------------------------------------------------
/** Return the number of bytes currently free in RAM. */
#if 0
static int FreeRam(void) {
  extern int  __bss_end;
  extern int* __brkval;
  int free_memory;
  if (reinterpret_cast<int>(__brkval) == 0) {
    // if no heap use from end of bss section
    free_memory = reinterpret_cast<int>(&free_memory)
                  - reinterpret_cast<int>(&__bss_end);
  } else {
    // use from top of stack to heap
    free_memory = reinterpret_cast<int>(&free_memory)
                  - reinterpret_cast<int>(__brkval);
  }
  return free_memory;
}
#endif
//------------------------------------------------------------------------------
/**
 * %Print a string in flash memory to the serial port.
 *
 * \param[in] str Pointer to string stored in flash memory.
*/ 
/*
#ifndef SerialPrint_P
#define SerialPrint_P(str) { \
	const char *p=str; \
  for (uint8_t c; (c = pgm_read_byte(p)); p++) Serial.print((char)c); \
}
#endif
------------------------------------------------------------------------------
*/
/**
 * %Print a string in flash memory followed by a CR/LF.
 *
 * \param[in] str Pointer to string stored in flash memory.
*/
/* 
#ifndef SerialPrintln_P
#define SerialPrintln_P(str) { \
  SerialPrint_P(str); \
  Serial.println();   \
}
#endif
*/
#endif  // #define SdFatUtil_h

sd-info.h

C/C++
Interface to Mikroe hardware
/* Arduino Sd2Card Library
 * Copyright (C) 2009 by William Greiman
 *
 * This file is part of the Arduino Sd2Card Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino Sd2Card Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
#ifndef _SD_INFO
#define _SD_INFO


//#include "application.h"
//#include <stdint.h>

// Based on the document:
//
// SD Specifications
// Part 1
// Physical Layer
// Simplified Specification
// Version 2.00
// September 25, 2006
//
// www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf
//------------------------------------------------------------------------------
// SD card commands
/** GO_IDLE_STATE - init card in spi mode if CS low */
uint8_t const CMD0 = 0X00;
/** SEND_IF_COND - verify SD Memory Card interface operating condition.*/
uint8_t const CMD8 = 0X08;
/** SEND_CSD - read the Card Specific Data (CSD register) */
uint8_t const CMD9 = 0X09;
/** SEND_CID - read the card identification information (CID register) */
uint8_t const CMD10 = 0X0A;
/** SEND_STATUS - read the card status register */
uint8_t const CMD13 = 0X0D;
/** READ_BLOCK - read a single data block from the card */
uint8_t const CMD17 = 0X11;
/** WRITE_BLOCK - write a single data block to the card */
uint8_t const CMD24 = 0X18;
/** WRITE_MULTIPLE_BLOCK - write blocks of data until a STOP_TRANSMISSION */
uint8_t const CMD25 = 0X19;
/** ERASE_WR_BLK_START - sets the address of the first block to be erased */
uint8_t const CMD32 = 0X20;
/** ERASE_WR_BLK_END - sets the address of the last block of the continuous
    range to be erased*/
uint8_t const CMD33 = 0X21;
/** ERASE - erase all previously selected blocks */
uint8_t const CMD38 = 0X26;
/** APP_CMD - escape for application specific command */
uint8_t const CMD55 = 0X37;
/** READ_OCR - read the OCR register of a card */
uint8_t const CMD58 = 0X3A;
/** SET_WR_BLK_ERASE_COUNT - Set the number of write blocks to be
     pre-erased before writing */
uint8_t const ACMD23 = 0X17;
/** SD_SEND_OP_COMD - Sends host capacity support information and
    activates the card's initialization process */
uint8_t const ACMD41 = 0X29;
//------------------------------------------------------------------------------
/** status for card in the ready state */
uint8_t const R1_READY_STATE = 0X00;
/** status for card in the idle state */
uint8_t const R1_IDLE_STATE = 0X01;
/** status bit for illegal command */
uint8_t const R1_ILLEGAL_COMMAND = 0X04;
/** start data token for read or write single block*/
uint8_t const DATA_START_BLOCK = 0XFE;
/** stop token for write multiple blocks*/
uint8_t const STOP_TRAN_TOKEN = 0XFD;
/** start data token for write multiple blocks*/
uint8_t const WRITE_MULTIPLE_TOKEN = 0XFC;
/** mask for data response tokens after a write block operation */
uint8_t const DATA_RES_MASK = 0X1F;
/** write data accepted token */
uint8_t const DATA_RES_ACCEPTED = 0X05;
//------------------------------------------------------------------------------
struct CID {
  // byte 0
  uint8_t mid;  // Manufacturer ID
  // byte 1-2
  char oid[2];  // OEM/Application ID
  // byte 3-7
  char pnm[5];  // Product name
  // byte 8
  uint16_t prv_m : 4;  // Product revision n.m
  uint16_t prv_n : 4;
  // byte 9-12
  uint32_t psn;  // Product serial number
  // byte 13
  uint16_t mdt_year_high : 4;  // Manufacturing date
  uint16_t reserved : 4;
  // byte 14
  uint16_t mdt_month : 4;
  uint16_t mdt_year_low :4;
  // byte 15
  uint16_t always1 : 1;
  uint16_t crc : 7;
}__attribute__ ((packed));
typedef struct CID cid_t; 
//------------------------------------------------------------------------------
// CSD for version 1.00 cards
struct CSDV1 {
  // byte 0
  uint16_t reserved1 : 6;
  uint16_t csd_ver : 2;
  // byte 1
  uint8_t taac;
  // byte 2
  uint8_t nsac;
  // byte 3
  uint8_t tran_speed;
  // byte 4
  uint8_t ccc_high;
  // byte 5
  uint16_t read_bl_len : 4;
  uint16_t ccc_low : 4;
  // byte 6
  uint16_t c_size_high : 2;
  uint16_t reserved2 : 2;
  uint16_t dsr_imp : 1;
  uint16_t read_blk_misalign :1;
  uint16_t write_blk_misalign : 1;
  uint16_t read_bl_partial : 1;
  // byte 7
  uint8_t c_size_mid;
  // byte 8
  uint16_t vdd_r_curr_max : 3;
  uint16_t vdd_r_curr_min : 3;
  uint16_t c_size_low :2;
  // byte 9
  uint16_t c_size_mult_high : 2;
  uint16_t vdd_w_cur_max : 3;
  uint16_t vdd_w_curr_min : 3;
  // byte 10
  uint16_t sector_size_high : 6;
  uint16_t erase_blk_en : 1;
  uint16_t c_size_mult_low : 1;
  // byte 11
  uint16_t wp_grp_size : 7;
  uint16_t sector_size_low : 1;
  // byte 12
  uint16_t write_bl_len_high : 2;
  uint16_t r2w_factor : 3;
  uint16_t reserved3 : 2;
  uint16_t wp_grp_enable : 1;
  // byte 13
  uint16_t reserved4 : 5;
  uint16_t write_partial : 1;
  uint16_t write_bl_len_low : 2;
  // byte 14
  uint16_t reserved5: 2;
  uint16_t file_format : 2;
  uint16_t tmp_write_protect : 1;
  uint16_t perm_write_protect : 1;
  uint16_t copy : 1;
  uint16_t file_format_grp : 1;
  // byte 15
  uint16_t always1 : 1;
  uint16_t crc : 7;
}__attribute__ ((packed));
typedef CSDV1 csd1_t;
//------------------------------------------------------------------------------
// CSD for version 2.00 cards
struct CSDV2 {
  // byte 0
  uint16_t reserved1 : 6;
  uint16_t csd_ver : 2;
  // byte 1
  uint8_t taac;
  // byte 2
  uint8_t nsac;
  // byte 3
  uint8_t tran_speed;
  // byte 4
  uint8_t ccc_high;
  // byte 5
  uint16_t read_bl_len : 4;
  uint16_t ccc_low : 4;
  // byte 6
  uint16_t reserved2 : 4;
  uint16_t dsr_imp : 1;
  uint16_t read_blk_misalign :1;
  uint16_t write_blk_misalign : 1;
  uint16_t read_bl_partial : 1;
  // byte 7
  uint16_t reserved3 : 2;
  uint16_t c_size_high : 6;
  // byte 8
  uint8_t c_size_mid;
  // byte 9
  uint8_t c_size_low;
  // byte 10
  uint16_t sector_size_high : 6;
  uint16_t erase_blk_en : 1;
  uint16_t reserved4 : 1;
  // byte 11
  uint16_t wp_grp_size : 7;
  uint16_t sector_size_low : 1;
  // byte 12
  uint16_t write_bl_len_high : 2;
  uint16_t r2w_factor : 3;
  uint16_t reserved5 : 2;
  uint16_t wp_grp_enable : 1;
  // byte 13
  uint16_t reserved6 : 5;
  uint16_t write_partial : 1;
  uint16_t write_bl_len_low : 2;
  // byte 14
  uint16_t reserved7: 2;
  uint16_t file_format : 2;
  uint16_t tmp_write_protect : 1;
  uint16_t perm_write_protect : 1;
  uint16_t copy : 1;
  uint16_t file_format_grp : 1;
  // byte 15
  uint16_t always1 : 1;
  uint16_t crc : 7;
}__attribute__ ((packed));
typedef CSDV2 csd2_t;
//------------------------------------------------------------------------------
// union of old and new style CSD register
union csd_t {
  csd1_t v1;
  csd2_t v2;
};
#endif  // SdInfo_h

sd-volume.cpp

C/C++
Interface to Mikroe hardware
/* Arduino SdFat Library
 * Copyright (C) 2009 by William Greiman
 *
 * This file is part of the Arduino SdFat Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino SdFat Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
#include "application.h"
#include "sd-fat.h"

//#include "spark_wiring_usbserial.h"

//------------------------------------------------------------------------------
// raw block cache
// init cacheBlockNumber_to invalid SD block number
uint32_t SdVolume::cacheBlockNumber_ = 0XFFFFFFFF;
cache_t  SdVolume::cacheBuffer_;     // 512 byte cache for Sd2Card
Sd2Card* SdVolume::sdCard_;          // pointer to SD card object
uint8_t  SdVolume::cacheDirty_ = 0;  // cacheFlush() will write block if true
uint32_t SdVolume::cacheMirrorBlock_ = 0;  // mirror  block for second FAT
//------------------------------------------------------------------------------
// find a contiguous group of clusters
uint8_t SdVolume::allocContiguous(uint32_t count, uint32_t* curCluster) {
  // start of group
  uint32_t bgnCluster;

  // flag to save place to start next search
  uint8_t setStart;

  // set search start cluster
  if (*curCluster) {
    // try to make file contiguous
    bgnCluster = *curCluster + 1;

    // don't save new start location
    setStart = false;
  } else {
    // start at likely place for free cluster
    bgnCluster = allocSearchStart_;

    // save next search start if one cluster
    setStart = 1 == count;
  }
  // end of group
  uint32_t endCluster = bgnCluster;

  // last cluster of FAT
  uint32_t fatEnd = clusterCount_ + 1;

  // search the FAT for free clusters
  for (uint32_t n = 0;; n++, endCluster++) {
    // can't find space checked all clusters
    if (n >= clusterCount_) return false;

    // past end - start from beginning of FAT
    if (endCluster > fatEnd) {
      bgnCluster = endCluster = 2;
    }
    uint32_t f;
    if (!fatGet(endCluster, &f)) return false;

    if (f != 0) {
      // cluster in use try next cluster as bgnCluster
      bgnCluster = endCluster + 1;
    } else if ((endCluster - bgnCluster + 1) == count) {
      // done - found space
      break;
    }
  }
  // mark end of chain
  if (!fatPutEOC(endCluster)) return false;

  // link clusters
  while (endCluster > bgnCluster) {
    if (!fatPut(endCluster - 1, endCluster)) return false;
    endCluster--;
  }
  if (*curCluster != 0) {
    // connect chains
    if (!fatPut(*curCluster, bgnCluster)) return false;
  }
  // return first cluster number to caller
  *curCluster = bgnCluster;

  // remember possible next free cluster
  if (setStart) allocSearchStart_ = bgnCluster + 1;

  return true;
}
//------------------------------------------------------------------------------
uint8_t SdVolume::cacheFlush(void) {
  if (cacheDirty_) {
    if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data)) {
      return false;
    }
    // mirror FAT tables
    if (cacheMirrorBlock_) {
      if (!sdCard_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data)) {
        return false;
      }
      cacheMirrorBlock_ = 0;
    }
    cacheDirty_ = 0;
  }
  return true;
}
//------------------------------------------------------------------------------
uint8_t SdVolume::cacheRawBlock(uint32_t blockNumber, uint8_t action) {
  if (cacheBlockNumber_ != blockNumber) {
    if (!cacheFlush()) return false;
    if (!sdCard_->readBlock(blockNumber, cacheBuffer_.data)) return false;
    cacheBlockNumber_ = blockNumber;
  }
  cacheDirty_ |= action;
  return true;
}
//------------------------------------------------------------------------------
// cache a zero block for blockNumber
uint8_t SdVolume::cacheZeroBlock(uint32_t blockNumber) {
  if (!cacheFlush()) return false;

  // loop take less flash than memset(cacheBuffer_.data, 0, 512);
  for (uint16_t i = 0; i < 512; i++) {
    cacheBuffer_.data[i] = 0;
  }
  cacheBlockNumber_ = blockNumber;
  cacheSetDirty();
  return true;
}
//------------------------------------------------------------------------------
// return the size in bytes of a cluster chain
uint8_t SdVolume::chainSize(uint32_t cluster, uint32_t* size) const {
  uint32_t s = 0;
  do {
    if (!fatGet(cluster, &cluster)) return false;
    s += 512UL << clusterSizeShift_;
  } while (!isEOC(cluster));
  *size = s;
  return true;
}
//------------------------------------------------------------------------------
// Fetch a FAT entry
uint8_t SdVolume::fatGet(uint32_t cluster, uint32_t* value) const {
  if (cluster > (clusterCount_ + 1)) return false;
  uint32_t lba = fatStartBlock_;
  lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7;
  if (lba != cacheBlockNumber_) {
    if (!cacheRawBlock(lba, CACHE_FOR_READ)) return false;
  }
  if (fatType_ == 16) {
    *value = cacheBuffer_.fat16[cluster & 0XFF];
  } else {
    *value = cacheBuffer_.fat32[cluster & 0X7F] & FAT32MASK;
  }
  return true;
}
//------------------------------------------------------------------------------
// Store a FAT entry
uint8_t SdVolume::fatPut(uint32_t cluster, uint32_t value) {
  // error if reserved cluster
  if (cluster < 2) return false;

  // error if not in FAT
  if (cluster > (clusterCount_ + 1)) return false;

  // calculate block address for entry
  uint32_t lba = fatStartBlock_;
  lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7;

  if (lba != cacheBlockNumber_) {
    if (!cacheRawBlock(lba, CACHE_FOR_READ)) return false;
  }
  // store entry
  if (fatType_ == 16) {
    cacheBuffer_.fat16[cluster & 0XFF] = value;
  } else {
    cacheBuffer_.fat32[cluster & 0X7F] = value;
  }
  cacheSetDirty();

  // mirror second FAT
  if (fatCount_ > 1) cacheMirrorBlock_ = lba + blocksPerFat_;
  return true;
}
//------------------------------------------------------------------------------
// free a cluster chain
uint8_t SdVolume::freeChain(uint32_t cluster) {
  // clear free cluster location
  allocSearchStart_ = 2;

  do {
    uint32_t next;
    if (!fatGet(cluster, &next)) return false;

    // free cluster
    if (!fatPut(cluster, 0)) return false;

    cluster = next;
  } while (!isEOC(cluster));

  return true;
}
//------------------------------------------------------------------------------
/**
 * Initialize a FAT volume.
 *
 * \param[in] dev The SD card where the volume is located.
 *
 * \param[in] part The partition to be used.  Legal values for \a part are
 * 1-4 to use the corresponding partition on a device formatted with
 * a MBR, Master Boot Record, or zero if the device is formatted as
 * a super floppy with the FAT boot sector in block zero.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.  Reasons for
 * failure include not finding a valid partition, not finding a valid
 * FAT file system in the specified partition or an I/O error.
 */
uint8_t SdVolume::init(Sd2Card* dev, uint8_t part) {
  uint32_t volumeStartBlock = 0;
  sdCard_ = dev;
  // if part == 0 assume super floppy with FAT boot sector in block zero
  // if part > 0 assume mbr volume with partition table
  if (part) {
    if (part > 4){
		Serial.println("Error: SdVolume::init() MBR");
		return false;
	}

    if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ)) {
		Serial.println("Error: SdVolume::init() Cache for read");
		return false;
	}

    part_t* p = &cacheBuffer_.mbr.part[part-1];
	if ((p->boot & 0X7F) !=0  ||
      p->totalSectors < 100 ||
      p->firstSector == 0) {
      // not a valid partition
	  Serial.println("Error: SdVolume::init() Invalid partition");
	  return false;
    }
	volumeStartBlock = p->firstSector;
  }
  if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ)) {
	  Serial.println("Error: SdVolume::init() Cache for read2");
	  return false;
  }

  bpb_t* bpb = &cacheBuffer_.fbs.bpb;
  if (bpb->bytesPerSector != 512 ||
    bpb->fatCount == 0 ||
    bpb->reservedSectorCount == 0 ||
    bpb->sectorsPerCluster == 0) {
       // not valid FAT volume
      Serial.println("Error: SdVolume::init() invalid FAT volume");
      return false;
  }
  fatCount_ = bpb->fatCount;
  blocksPerCluster_ = bpb->sectorsPerCluster;

  // determine shift that is same as multiply by blocksPerCluster_
  clusterSizeShift_ = 0;
  while (blocksPerCluster_ != (1 << clusterSizeShift_)) {
    // error if not power of 2
    if (clusterSizeShift_++ > 7) {
		Serial.println("Error: SdVolume::init() not power of 2");
		return false;
	}
  }
  blocksPerFat_ = bpb->sectorsPerFat16 ?
                    bpb->sectorsPerFat16 : bpb->sectorsPerFat32;

  fatStartBlock_ = volumeStartBlock + bpb->reservedSectorCount;

  // count for FAT16 zero for FAT32
  rootDirEntryCount_ = bpb->rootDirEntryCount;

  // directory start for FAT16 dataStart for FAT32
  rootDirStart_ = fatStartBlock_ + bpb->fatCount * blocksPerFat_;

  // data start for FAT16 and FAT32
  dataStartBlock_ = rootDirStart_ + ((32 * bpb->rootDirEntryCount + 511)/512);

  // total blocks for FAT16 or FAT32
  uint32_t totalBlocks = bpb->totalSectors16 ?
                           bpb->totalSectors16 : bpb->totalSectors32;
  // total data blocks
  clusterCount_ = totalBlocks - (dataStartBlock_ - volumeStartBlock);

  // divide by cluster size to get cluster count
  clusterCount_ >>= clusterSizeShift_;

  // FAT type is determined by cluster count
  if (clusterCount_ < 4085) {
    fatType_ = 12;
  } else if (clusterCount_ < 65525) {
    fatType_ = 16;
  } else {
    rootDirStart_ = bpb->fat32RootCluster;
    fatType_ = 32;
  }
  return true;
}

TCPtoEmail

Python
Code on Raspberry Pi 5 to run a TCP Server, and forward messages received to my email.
import socket
import time
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

# Configuration
smtp_server = "smtp.xxx.com"
smtp_port = xxx
sender = "xxxxxxxxxx@xxx.com"
receiver = "xxxxxxxxxx@xxx.com"
pwd = "xxxxxxxxxxxxxxxx" # Generated App Password, typically 16 characters long

def sendmsg(message):
    msg = MIMEMultipart()
    msg["From"] = 'xxxxxxxxxx@xxx.com'
    msg["To"] = 'xxxxxxxxxx@xxx.com'
    msg["Subject"] = 'Email from Pi about Sump Operation ' + time.ctime()
    msg.attach(MIMEText(message, 'plain')) # plain for standard text
    try:
        with smtplib.SMTP(smtp_server, smtp_port) as server:
            server.starttls()
            server.login(sender, pwd)
            server.send_message(msg)
            server.quit()
        print('Email sent successfully!')
    except Exception as e:
        print(f'Error: {e}')
    print(' ') # new line, for separation of dispayed information

def start_server():
    # '0.0.0.0' listens on all available network interfaces (WiFi and Ethernet)
    host = '192.168.1.100'
    port = 10000  # Choose a non-privileged port (>1024, but valid int)
    error_email_sent = False

    # Create a socket object using IPv4 and TCP
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        # Avoid "Address already in use" errors on restart
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        
        s.bind((host, port))
        while True: # use Stop to exit this loop
            s.listen()
            print(f'Server started. Listening on {host}:{port}...')

            conn, addr = s.accept() # Blocking call: waits for a client
            with conn:
                print(f'Connected by {addr}')
                while True:
                    data = conn.recv(1024)
                    if not data:
                        break
                    print(f'Received: {data.decode()}')
                    #conn.sendall(data) # Echo data back
                    mymsg = f'{data.decode()}'
                    sendmsg(mymsg)
                    break

if __name__ == "__main__":
    start_server()

Barometer.cpp

C/C++
Interface to Mikroe Barometer Click
/*
 * MikroSDK - MikroE Software Development Kit
 * Copyright 2020 MikroElektronika d.o.o.
 * 
 * Permission is hereby granted, free of charge, to any person 
 * obtaining a copy of this software and associated documentation 
 * files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, 
 * publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 
 * OR OTHER DEALINGS IN THE SOFTWARE. 
 */

/*!
 * \file
 *
 */

#include "barometer.h"

// ------------------------------------------------------------- PRIVATE MACROS 

#define BAROMETER_DUMMY 0

// ---------------------------------------------- PRIVATE FUNCTION DECLARATIONS 

static void barometer_i2c_write ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len );

static void barometer_i2c_read ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len );

static void barometer_spi_write ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len );

static void barometer_spi_read ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len );

// ------------------------------------------------ PUBLIC FUNCTION DEFINITIONS

void barometer_cfg_setup ( barometer_cfg_t *cfg )
{
    // Communication gpio pins 

    cfg->scl = HAL_PIN_NC;
    cfg->sda = HAL_PIN_NC;
    cfg->sck = HAL_PIN_NC;
    cfg->miso = HAL_PIN_NC;
    cfg->mosi = HAL_PIN_NC;
    cfg->cs = HAL_PIN_NC;
    
    // Additional gpio pins

    cfg->rdy = HAL_PIN_NC;

    cfg->i2c_speed = I2C_MASTER_SPEED_STANDARD; 
    cfg->i2c_address = BAROMETER_I2C_ADDRESS_1;
    
    cfg->spi_speed = 100000; 
    cfg->spi_mode = SPI_MASTER_MODE_0;
    cfg->cs_polarity = SPI_MASTER_CHIP_SELECT_POLARITY_ACTIVE_LOW;
    
    cfg->sel = BAROMETER_MASTER_I2C;
}

BAROMETER_RETVAL barometer_init ( barometer_t *ctx, barometer_cfg_t *cfg )
{
    // Only in case it is necessary to check somewhere which communication is set
    ctx->master_sel = cfg->sel;

    if ( ctx->master_sel == BAROMETER_MASTER_I2C )
    {
        i2c_master_config_t i2c_cfg;

        i2c_master_configure_default( &i2c_cfg );
        i2c_cfg.speed    = cfg->i2c_speed;
        i2c_cfg.scl = cfg->scl;
        i2c_cfg.sda = cfg->sda;

        ctx->slave_address = cfg->i2c_address;

        if (  i2c_master_open( &ctx->i2c, &i2c_cfg ) == I2C_MASTER_ERROR )
        {
            return BAROMETER_INIT_ERROR;
        }

        i2c_master_set_slave_address( &ctx->i2c, ctx->slave_address );
        i2c_master_set_speed( &ctx->i2c, cfg->i2c_speed );
        i2c_master_set_timeout( &ctx->i2c, 0 );
        
        // SM - commented out the two lines below, since conflicts with 420mar SPI
        //digital_out_init( &ctx->cs, cfg->cs );
        //digital_out_high( &ctx->cs );

        ctx->read_f = barometer_i2c_read;
        ctx->write_f = barometer_i2c_write;
    }
    else
    {
        spi_master_config_t spi_cfg;

        spi_master_configure_default( &spi_cfg );
        spi_cfg.mode   = cfg->spi_mode;
        spi_cfg.speed  = cfg->spi_speed;
        spi_cfg.sck    = cfg->sck;
        spi_cfg.miso   = cfg->miso;
        spi_cfg.mosi   = cfg->mosi;
        
        spi_cfg.default_write_data = BAROMETER_DUMMY;
        digital_out_init( &ctx->cs, cfg->cs );
        ctx->chip_select = cfg->cs;
        

        if (  spi_master_open( &ctx->spi, &spi_cfg ) == SPI_MASTER_ERROR )
        {
            return  BAROMETER_INIT_ERROR;
        }

        spi_master_set_default_write_data( &ctx->spi, BAROMETER_DUMMY );
        spi_master_set_mode( &ctx->spi, spi_cfg.mode );
        spi_master_set_speed( &ctx->spi, spi_cfg.speed );
        spi_master_set_chip_select_polarity( cfg->cs_polarity );
        spi_master_deselect_device( ctx->chip_select ); 

        ctx->read_f = barometer_spi_read;
        ctx->write_f = barometer_spi_write;

        // SM - move this up from below, to inside else, since conflicts with 420mar SPI
        // Input pins
        digital_in_init( &ctx->rdy, cfg->rdy );
    }

    return BAROMETER_OK;
}

void barometer_default_cfg ( barometer_t *ctx )
{
    uint8_t tmp;
    tmp = BAROMETER_DEFAULT_CONFIG;
    barometer_generic_write( ctx, BAROMETER_CTRL_REG1, &tmp, 1 );
}

void barometer_generic_write ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len )
{
    ctx->write_f( ctx, reg, data_buf, len ); 
}

void barometer_generic_read ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len )
{
    ctx->read_f( ctx, reg, data_buf, len );
}

// Generic read data function
uint8_t barometer_read_data( barometer_t *ctx, uint8_t address )
{
    uint8_t read_reg;

    barometer_i2c_read( ctx, address, &read_reg, 1 );

    return read_reg;
}

// Sets the module active function 
void barometer_set_active( barometer_t *ctx )
{
    uint8_t temp;
    barometer_generic_read( ctx, BAROMETER_CTRL_REG1, &temp, 1 );
    temp |= BAROMETER_CONFIG_ACTIVE_MODE;
    barometer_generic_write( ctx, BAROMETER_CTRL_REG1, &temp, 1 );
}

// Read temperature in degrees of Celsius function
float barometer_get_temperature_c ( barometer_t *ctx )
{
    uint8_t buffer[ 2 ];
    int16_t result;
    float temperature_c;

    buffer[ 0 ] = barometer_read_data( ctx, BAROMETER_TEMP_OUT_H );
    buffer[ 1 ] = barometer_read_data( ctx, BAROMETER_TEMP_OUT_L );
    
    result = buffer[ 0 ];
    result <<= 8;
    result |= buffer[ 1 ];
    
    temperature_c = ( ( float ) result / 480.0 ) + 42.5;

    return temperature_c;
}

//Read temperature in degrees of Fahrenheit function
float barometer_get_temperature_f( barometer_t *ctx )
{
    uint8_t buffer[ 2 ];
    int16_t result;
    float temperature_f;

    buffer[ 0 ] = barometer_read_data( ctx, BAROMETER_TEMP_OUT_H );
    buffer[ 1 ] = barometer_read_data( ctx, BAROMETER_TEMP_OUT_L );

    result = buffer[ 0 ];
    result <<= 8;
    result |= buffer[ 1 ];

    temperature_f = ( ( float ) result / 480.0 * 1.8 ) + 108.5;

    return temperature_f;
}

/* Read pressure in milibars function */
float barometer_get_pressure( barometer_t *ctx )
{
    uint8_t buffer[ 3 ];
    uint32_t result;
    float pressure;
  
    buffer[ 0 ] = barometer_read_data( ctx, BAROMETER_PRESS_OUT_H );
    buffer[ 1 ] = barometer_read_data( ctx, BAROMETER_PRESS_OUT_L );
    buffer[ 2 ] = barometer_read_data( ctx, BAROMETER_PRESS_OUT_XL );
    
    result = buffer[ 0 ];
    result <<= 8;
    result |= buffer[ 1 ];
    result <<= 8;
    result |= buffer[ 2 ];

    pressure = ( float ) result / 4096.00;

    return pressure;
}

// Check sensor id - who am I function
uint8_t barometer_check_id ( barometer_t *ctx )
{
    return barometer_read_data( ctx, BAROMETER_WHO_AM_I );
}

// Check sensor status function
uint8_t barometer_check_status ( barometer_t *ctx )
{
    return barometer_read_data( ctx, BAROMETER_STATUS_REG );
}

// State of interrupt pin function
uint8_t barometer_check_interrupt ( barometer_t *ctx )
{
    return digital_in_read( &ctx->rdy );
}

// ----------------------------------------------- PRIVATE FUNCTION DEFINITIONS

static void barometer_i2c_write ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len )
{
    uint8_t tx_buf[ 256 ];
    uint8_t cnt;
    
    tx_buf[ 0 ] = reg;

    for ( cnt = 1; cnt <= len; cnt++ )
    {
        tx_buf[ cnt ] = data_buf[ cnt - 1 ]; 
    }

    i2c_master_write( &ctx->i2c, tx_buf, len + 1 );    
}

static void barometer_i2c_read ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len )
{
    i2c_master_write_then_read( &ctx->i2c, &reg, 1, data_buf, len );
}

static void barometer_spi_write ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len )
{
    uint8_t tx_buf[ 265 ];
    uint8_t cnt;

    tx_buf[ 0 ] = reg;
    for ( cnt = 1; cnt <= len; cnt++ )
    {
        tx_buf[ cnt ] = data_buf[ cnt - 1 ]; 
    }
    
    spi_master_select_device( ctx->chip_select );
    spi_master_write( &ctx->spi, tx_buf, len + 1 );
    spi_master_deselect_device( ctx->chip_select );  
}

static void barometer_spi_read ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len )
{
    uint8_t tx_buf[ 1 ];
    //uint8_t rx_buf[ 265 ];
    //uint8_t cnt;

    tx_buf[ 0 ] = reg | 0x80;
    
    spi_master_select_device( ctx->chip_select );
    spi_master_write_then_read( &ctx->spi, tx_buf, 1, data_buf, len );
    spi_master_deselect_device( ctx->chip_select );  
}

// ------------------------------------------------------------------------- END

Barometer.h

C/C++
Interface to Mikroe Barometer Click
/*
 * MikroSDK - MikroE Software Development Kit
 * Copyright 2020 MikroElektronika d.o.o.
 * 
 * Permission is hereby granted, free of charge, to any person 
 * obtaining a copy of this software and associated documentation 
 * files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, 
 * publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 
 * OR OTHER DEALINGS IN THE SOFTWARE. 
 */

/*!
 * \file
 *
 * \brief This file contains API for Barometer Click driver.
 *
 * \addtogroup barometer Barometer Click Driver
 * @{
 */
// ----------------------------------------------------------------------------

#ifndef BAROMETER_H
#define BAROMETER_H

/**
 * Any initialization code needed for MCU to function properly.
 * Do not remove this line or clock might not be set correctly.
 */
#ifdef PREINIT_SUPPORTED
#include "preinit.h"
#endif

#ifdef MikroCCoreVersion
    #if MikroCCoreVersion >= 1
        #include "delays.h"
    #endif
#endif

#include "drv_digital_out.h"
#include "drv_digital_in.h"
#include "drv_i2c_master.h"
#include "drv_spi_master.h"

// -------------------------------------------------------------- PUBLIC MACROS 
/**
 * \defgroup macros Macros
 * \{
 */

/**
 * \defgroup map_mikrobus MikroBUS
 * \{
 */

#define BAROMETER_MAP_MIKROBUS( cfg, mikrobus ) \
   cfg.scl   = MIKROBUS( mikrobus, MIKROBUS_SCL ); \
   cfg.sda   = MIKROBUS( mikrobus, MIKROBUS_SDA ); \
   cfg.miso  = MIKROBUS( mikrobus, MIKROBUS_MISO ); \
   cfg.mosi  = MIKROBUS( mikrobus, MIKROBUS_MOSI ); \
   cfg.sck   = MIKROBUS( mikrobus, MIKROBUS_SCK ); \
   cfg.cs    = MIKROBUS( mikrobus, MIKROBUS_CS ); \
   cfg.rdy = MIKROBUS( mikrobus, MIKROBUS_INT )
/** \} */

/**
 * \defgroup communication Select communication
 * \{
 */
#define BAROMETER_MASTER_I2C 0
#define BAROMETER_MASTER_SPI 1
/** \} */

/**
 * \defgroup error_code Error Code
 * \{
 */
#define BAROMETER_RETVAL  uint8_t

#define BAROMETER_OK           0x00
#define BAROMETER_INIT_ERROR   0xFF
/** \} */

/**
 *  \defgroup barometer_address  Barometer Address
 * \{
 */
 #define BAROMETER_I2C_ADDRESS_0                     0x5C
 /** \} */
 
 /**
  *  \defgroup barometer_address_1  Barometer Address 1
  * \{
  */
 #define BAROMETER_I2C_ADDRESS_1                      0x5D
 /** \} */
 
 /**
  *  \defgroup barometer_ref  Barometer Ref 
  * \{
  */
 #define BAROMETER_REF_P_XL                           0x08
 #define BAROMETER_REF_P_L                            0x09
 #define BAROMETER_REF_P_H                            0x0A
 /** \} */
 
 /**
  *  \defgroup Barometer_who  Barometer Who
  * \{
  */
 #define BAROMETER_WHO_AM_I                           0x0F
 /** \} */
 
 /**
  *  \defgroup barometer_data  Barometer Data
  * \{
  */
 #define BAROMETER_RES_CONF                           0x10
 #define BAROMETER_CTRL_REG1                          0x20
 #define BAROMETER_CTRL_REG2                          0x21
 #define BAROMETER_CTRL_REG3                          0x22
 #define BAROMETER_CTRL_REG4                          0x23
 #define BAROMETER_INTERRUPT_CFG                      0x24
 #define BAROMETER_INT_SOURCE                         0x25
 #define BAROMETER_STATUS_REG                         0x27
 #define BAROMETER_PRESS_OUT_XL                       0x28
 #define BAROMETER_PRESS_OUT_L                        0x29
 #define BAROMETER_PRESS_OUT_H                        0x2A
 #define BAROMETER_TEMP_OUT_L                         0x2B
 #define BAROMETER_TEMP_OUT_H                         0x2C
 #define BAROMETER_FIFO_CTRL                          0x2E
 #define BAROMETER_FIFO_STATUS                        0x2F
 #define BAROMETER_THS_P_L                            0x30
 #define BAROMETER_THS_P_H                            0x31
 #define BAROMETER_RPDS_L                             0x39
 #define BAROMETER_RPDS_H                             0x3A
 #define BAROMETER_DEVICE_ID                          0xBD
 /** \} */
 
 /**
  *  \defgroup  default  Default
  * \{
  */
 #define BAROMETER_DEFAULT_CONFIG                     0xB0
 #define BAROMETER_CONFIG_ACTIVE_MODE                 0x80
 /** \} */

/** \} */ // End group macro 

// --------------------------------------------------------------- PUBLIC TYPES

/**
 * \defgroup type Types
 * \{
 */

/**
 * @brief Communication type.
 */
typedef uint8_t  barometer_select_t;

/**
 * @brief Master Input/Output type.
 */
typedef void ( *barometer_master_io_t )( struct barometer_s*, uint8_t, uint8_t*, uint8_t );

/**
 * @brief Click ctx object definition.
 */
typedef struct barometer_s
{
    digital_out_t cs;

    // Input pins 

    digital_in_t rdy;
    
    // Modules 

    i2c_master_t i2c;
    spi_master_t spi;

    // ctx variable 

    uint8_t slave_address;
    pin_name_t chip_select;
   barometer_master_io_t  write_f;
   barometer_master_io_t  read_f;
   barometer_select_t master_sel;

} barometer_t;

/**
 * @brief Click configuration structure definition.
 */
typedef struct
{
    // Communication gpio pins 

    pin_name_t scl;
    pin_name_t sda;
    pin_name_t miso;
    pin_name_t mosi;
    pin_name_t sck;
    pin_name_t cs;
    
    // Additional gpio pins 

    pin_name_t rdy;

    // static variable 

   uint32_t i2c_speed;
   uint8_t  i2c_address;

   uint32_t spi_speed;
   spi_master_mode_t  spi_mode;
   spi_master_chip_select_polarity_t cs_polarity;

    barometer_select_t sel;

} barometer_cfg_t;

/** \} */ // End types group
// ----------------------------------------------- PUBLIC FUNCTION DECLARATIONS
/**
 * \defgroup public_function Public function
 * \{
 */

//#ifdef __cplusplus COMMENTED OUT BY S.M.
//extern "C"{
//#endif

/**
 * @brief Config Object Initialization function.
 *
 * @param cfg  Click configuration structure.
 *
 * @description This function initializes Click configuration structure to init state.
 * @note All used pins will be set to unconnected state.
 */
void barometer_cfg_setup ( barometer_cfg_t *cfg );

/**
 * @brief Initialization function.
 * @param barometer Click object.
 * @param cfg Click configuration structure.
 * 
 * @description This function initializes all necessary pins and peripherals used for this Click.
 */
BAROMETER_RETVAL barometer_init ( barometer_t *ctx, barometer_cfg_t *cfg );

/**
 * @brief Click Default Configuration function.
 *
 * @param ctx  Click object.
 *
 * @note
 *<pre>
 *       set: BAROMETER_CTRL_REG1
 *              - BAROMETER_DEFAULT_CONFIG
 *</pre>
 * @description This function executes default configuration for Barometer Click.
 */
void barometer_default_cfg ( barometer_t *ctx );

/**
 * @brief Generic write function.
 *
 * @param ctx        Click object.
 * @param reg        Register address.
 * @param data_buf   Output data buf
 * @param len        Number of the bytes to be read
 *
 * @description This function writes data to the desired register.
 */
void barometer_generic_write ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len );

/**
 * @brief Generic read function.
 *
 * @param ctx      Click object.
 * @param reg          Register address.
 * @param data_buf  Data buf to be written.
 * @param len          Number of the bytes in data buf.
 *
 * @description This function reads data from the desired register.
 */
void barometer_generic_read ( barometer_t *ctx, uint8_t reg, uint8_t *data_buf, uint8_t len );

/**
 * @brief Generic read 8-bit data function
 *
 * @param ctx             Click object.
 * @param address         Register address
 *
 * @return
 * 8-bit data from addressed register from the LPS25HB
 *
 * Function read 8-bit data from register address 
 * of the LPS25HB sensor.
 */
uint8_t barometer_read_data( barometer_t *ctx, uint8_t address );

/**
 * @brief Sets the module active function
 *
 * @param ctx          Click object.
 * Function set active mode
 * by writing configuration register from the LPS25HB sensor.
 */
void barometer_set_active( barometer_t *ctx );

/**
 * @brief Read temperature in degrees Celsius function
 *
 * @param ctx          Click object.
 * @return
 * float temperature data degrees Celsius [ C ]
 *
 * Function read and calculate temperature
 * in degrees Celsius from the LPS25HB sensor.
 */
float barometer_get_temperature_c ( barometer_t *ctx );

/**
 * @brief Read temperature in degrees of Fahrenheit function
 *
 * @param ctx          Click object.
 * @return
 * float temperature data degrees Fahrenheit [ F ]
 *
 * Function read and calculate temperature
 * in degrees Fahrenheit from the LPS25HB sensor.
 */
float barometer_get_temperature_f( barometer_t *ctx );

/**
 * @brief Read pressure in milibars function
 *
 * @param ctx          Click object.
 * @return
 * float pressure data in milibars [ mbar ]
 *
 * Function read and calculate pressure
 * in milibars from the LPS25HB sensor.
 */
float barometer_get_pressure( barometer_t *ctx );

/**
 * @brief Check sensor id - Who Am I function
 *
 * @param ctx          Click object.
 * @return
 * 8-bit id data from Who Am I register
 *
 * Function read sensor id ( Who Am I register )
 * from the LPS25HB sensor.
 */
uint8_t barometer_check_id ( barometer_t *ctx );

/**
 * @brief Check sensor status function
 *
 * @param ctx          Click object.
 * @return
 * 8-bit id data from status register
 *
 * Function read sensor status ( status register )
 * from the LPS25HB sensor.
 */
uint8_t barometer_check_status ( barometer_t *ctx );

/**
 * @brief State of interrupt pin function
 *
 * @param ctx          Click object.
 * @return State of INT pin - 0 or 1
 *
 * Function checks is interrupt occurred and returns the state of the INT pin.
 * INT pin can be configured to show are data registers updated with the new values or not.
 */
uint8_t barometer_check_interrupt ( barometer_t *ctx );

//#ifdef __cplusplus COMMENTED OUT BY S.M.
//}
//#endif
#endif  // _BAROMETER_H_

/** \} */ // End public_function group
/// \}    // End Click Driver group  
/*! @} */
// ------------------------------------------------------------------------- END

c420mar.cpp

C/C++
Interface to Mikroe 4-20 mA R Click
/*
 * MikroSDK - MikroE Software Development Kit
 * Copyright 2020 MikroElektronika d.o.o.
 * 
 * Permission is hereby granted, free of charge, to any person 
 * obtaining a copy of this software and associated documentation 
 * files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, 
 * publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 
 * OR OTHER DEALINGS IN THE SOFTWARE. 
 */

/*!
 * \file
 *
 */

#include "c420mar.h"

// ------------------------------------------------------------- PRIVATE MACROS 

#define c420MAR_DUMMY 0

// ------------------------------------------------ PUBLIC FUNCTION DEFINITIONS

void c420mar_cfg_setup ( c420mar_cfg_t *cfg )
{
    // Communication gpio pins 

    cfg->sck = HAL_PIN_NC;
    cfg->miso = HAL_PIN_NC;
    cfg->mosi = HAL_PIN_NC;
    cfg->cs = HAL_PIN_NC;

    // Additional gpio pins

    cfg->en = HAL_PIN_NC;

    //cfg->spi_mode = SPI_MASTER_MODE_3; // SM - actual board measurements show SCK is low when idle, so this is ignored and actually set to Mode 0,0 ???
    cfg->spi_mode = SPI_MASTER_MODE_0; // SM - changed to Mode 0,0 based on MCP3201 data sheet
    cfg->cs_polarity = SPI_MASTER_CHIP_SELECT_POLARITY_ACTIVE_LOW;
    cfg->spi_speed = 100000; 
}

c420MAR_RETVAL c420mar_init ( c420mar_t *ctx, c420mar_cfg_t *cfg )
{
    spi_master_config_t spi_cfg;

    // SM - need to initialize ctx variables (cs, en, spi, chip_select) ??
    //ctx->cs = x; // ???
    //ctx->en = x; // ???
    //ctx->spi = x; // ??? ()
    ctx->chip_select = cfg->cs; // pin D18
    
    spi_master_configure_default( &spi_cfg );
    spi_cfg.speed     = cfg->spi_speed;
    spi_cfg.mode      = cfg->spi_mode;
    spi_cfg.sck       = cfg->sck; // pin
    spi_cfg.miso      = cfg->miso; // pin
    spi_cfg.mosi      = cfg->mosi; // pin
    spi_cfg.default_write_data = c420MAR_DUMMY;

    //digital_out_init( &ctx->cs, cfg->cs ); // SM - wrong???, replace with pinMode
    pinMode( cfg->cs, OUTPUT ); // pin D18 for left feather wing

    if ( spi_master_open( &ctx->spi, &spi_cfg ) == SPI_MASTER_ERROR ) // ctx not used
    {
        return c420MAR_INIT_ERROR;
    }

    spi_master_set_default_write_data( &ctx->spi, c420MAR_DUMMY ); // not implemented yet
    spi_master_set_mode( &ctx->spi, spi_cfg.mode ); // ctx not used
    spi_master_set_speed( &ctx->spi, spi_cfg.speed ); // ctx not used
    spi_master_set_chip_select_polarity( cfg->cs_polarity );
    spi_master_deselect_device( ctx->chip_select );

    // Output pins 

    //digital_out_init( &ctx->en, cfg->en ); // SM
    //digital_out_high( &ctx->en ); // SM
    //digital_out_high( &ctx->cs ); // SM
    pinMode(cfg->en, OUTPUT); // SM - D19 for left feather wing
    digitalWrite(cfg->en, HIGH); // SM - D19 high, so 420mar is enabled
    digitalWrite(cfg->cs, HIGH); // SM - D18 high, so 420mar is not presently selected for communication

    return c420MAR_OK;
}

void c420mar_generic_transfer ( c420mar_t *ctx, uint8_t *wr_buf, uint16_t wr_len, uint8_t *rd_buf, uint16_t rd_len )
{
    spi_master_select_device( ctx->chip_select );
    spi_master_write_then_read( &ctx->spi, wr_buf, wr_len, rd_buf, rd_len );
    spi_master_deselect_device( ctx->chip_select );   
}

float c420mar_read_data ( c420mar_t *ctx )
{
    uint8_t tmp;
    uint16_t value;

    spi_master_select_device( ctx->chip_select );
    spi_master_read( &ctx->spi, &tmp, 1 ); // SM - ctx not used
    value = tmp & 0x0F; // SM - change from 0x1F to ignore Null bit too
    value <<= 8;
    //Serial.printf("c420mar: 1st byte = %i, value = %i", tmp, value);
    //Serial.println();
    spi_master_read( &ctx->spi, &tmp, 1 ); // SM - ctx not used
    value |= tmp;
    //value >>= 1; // SM - this is not needed ???
    //Serial.printf("c420mar: 2nd byte = %i, value = %i", tmp, value);
    //Serial.println();
    spi_master_deselect_device( ctx->chip_select );

    //return ( float ) value / ( 20 * 10 ); // SM - not correct???
    // SM - On-board reference is 2.048 V, input of 0.4-2.0 V for 4-20 mA (i.e. x 10)
    return ( float ) (value * (C420MARVREF * 10.0 / 4095.0 )); // SM - 4096 in manual, but should probably be 4095
}

// ------------------------------------------------------------------------- END

c420mar.h

C/C++
Interface to Mikroe c420 mA R Click
/*
 * MikroSDK - MikroE Software Development Kit
 * Copyright 2020 MikroElektronika d.o.o.
 * 
 * Permission is hereby granted, free of charge, to any person 
 * obtaining a copy of this software and associated documentation 
 * files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, 
 * publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 
 * OR OTHER DEALINGS IN THE SOFTWARE. 
 */

/*!
 * \file
 *
 * \brief This file contains API for 4-20 mA R Click driver.
 *
 * \addtogroup c420mar 4-20 mA R Click Driver
 * @{
 */
// ----------------------------------------------------------------------------

#ifndef c420MAR_H
#define c420MAR_H

// SM - c420mar uses an on-board reference voltage of 2.048 V, for comparison to the Vin+
#define C420MARVREF 2.048

/**
 * Any initialization code needed for MCU to function properly.
 * Do not remove this line or clock might not be set correctly.
 */
#ifdef PREINIT_SUPPORTED
#include "preinit.h"
#endif

#ifdef MikroCCoreVersion
    #if MikroCCoreVersion >= 1
        #include "delays.h"
    #endif
#endif

#include "drv_digital_out.h"
#include "drv_spi_master.h"

// -------------------------------------------------------------- PUBLIC MACROS 
/**
 * \defgroup macros Macros
 * \{
 */

/**
 * \defgroup map_mikrobus MikroBUS
 * \{
 */

#define c420MAR_MAP_MIKROBUS( cfg, mikrobus ) \
   cfg.miso  = MIKROBUS( mikrobus, MIKROBUS_MISO ); \
   cfg.mosi  = MIKROBUS( mikrobus, MIKROBUS_MOSI ); \
   cfg.sck   = MIKROBUS( mikrobus, MIKROBUS_SCK ); \
   cfg.cs    = MIKROBUS( mikrobus, MIKROBUS_CS ); \
   cfg.en    = MIKROBUS( mikrobus, MIKROBUS_INT )
/** \} */

/**
 * \defgroup error_code Error Code
 * \{
 */
#define c420MAR_RETVAL  uint8_t

#define c420MAR_OK           0x00
#define c420MAR_INIT_ERROR   0xFF
/** \} */

/** \} */ // End group macro 
// --------------------------------------------------------------- PUBLIC TYPES
/**
 * \defgroup type Types
 * \{
 */

/**
 * @brief Click ctx object definition.
 */
typedef struct
{
    digital_out_t cs;

    // Output pins 

    digital_out_t en;
    
    // Modules 

    spi_master_t spi;
    pin_name_t chip_select;

} c420mar_t;

/**
 * @brief Click configuration structure definition.
 */
typedef struct
{
    // Communication gpio pins 

    pin_name_t miso;
    pin_name_t mosi;
    pin_name_t sck;
    pin_name_t cs;

    // Additional gpio pins 

    pin_name_t en;

    // static variable 

    uint32_t spi_speed;
    spi_master_mode_t   spi_mode;
    spi_master_chip_select_polarity_t cs_polarity;

} c420mar_cfg_t;

/** \} */ // End types group
// ----------------------------------------------- PUBLIC FUNCTION DECLARATIONS

/**
 * \defgroup public_function Public function
 * \{
 */

/**
 * @brief Config Object Initialization function.
 *
 * @param cfg  Click configuration structure.
 *
 * @description This function initializes Click configuration structure to init state.
 * @note All used pins will be set to unconnected state.
 */
void c420mar_cfg_setup ( c420mar_cfg_t *cfg );

/**
 * @brief Initialization function.
 *
 * @param ctx Click object.
 * @param cfg Click configuration structure.
 * 
 * @description This function initializes all necessary pins and peripherals used for this Click.
 */
c420MAR_RETVAL c420mar_init ( c420mar_t *ctx, c420mar_cfg_t *cfg );

/**
 * @brief Generic transfer function.
 *
 * @param ctx          Click object.
 * @param wr_buf       Write data buffer
 * @param wr_len       Number of byte in write data buffer
 * @param rd_buf       Read data buffer
 * @param rd_len       Number of byte in read data buffer
 *
 * @description Generic SPI transfer, for sending and receiving packages
 */
void c420mar_generic_transfer ( c420mar_t *ctx, uint8_t *wr_buf, uint16_t wr_len, uint8_t *rd_buf, uint16_t rd_len );

/**
 * @brief Read data function.
 *
 * @param ctx  Click object.
 *
 * @description This function reads the 16-bit current value from the SPI data register, 
 *              and then normalizes and converts it to float.
 */
float c420mar_read_data( c420mar_t *ctx );

#endif  // _c420MAR_H_

/** \} */ // End public_function group
/// \}    // End Click Driver group  
/*! @} */
// ------------------------------------------------------------------------- END

drv_digital_in.cpp

C/C++
Interface to Mikroe hardware
/*
 * Copyright (c) 2023 Particle Industries, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "drv_digital_in.h"

//function for setting pin as input
int8_t digital_in_init(digital_in_t *in, uint8_t mode)
{
    if (mode == INPUT)
    {
        pinMode(in->pin, static_cast<PinMode>(mode));
        return DIGITAL_IN_SUCCESS;      //return status
    }
    else        //mode other than input selected
    {
        return DIGITAL_IN_UNSUPPORTED_PIN;      //return status
    }
}

//function for reading pin state
int32_t digital_in_read(digital_in_t *in)
{
    return digitalRead(in->pin);
}

Credits

Scott McNabb
2 projects • 0 followers
Retired Electrical Engineer

Comments