Skip to content

Arduino Examples

Example 1: Navigation Switch

This example demonstrates how to use the navigation switch and detect if the SD card is inserted. The !INT pin of the GPIO expander IC (PCA9554) triggers an interrupt any time a button is pressed or an SD card is inserted/removed.

The code for this example can be found in the Firmware folder of this repository. Alternatively, you can expand the link below and copy and paste the code into a shiny new Arduino sketch:

Example 1 Arduino Code
/*
  Read the 5-way switch and card detect on the Portability Shield
  By: Nathan Seidle
  SparkFun Electronics
  Date: November 24th, 2024
  License: This code is public domain but you buy me a drink if you use this
  and we meet someday (Drink license).
  Feel like supporting our work? Buy a board from SparkFun!
  https://www.sparkfun.com/products/27510

  This example demonstrates how to read the 5-way nav switch and detect if the SD card is inserted.
  The !INT pin of the GPIO expander IC (PCA9554) triggers an interrupt any time a button is pressed or
  an SD card is inserted/removed.

  Hardware Connections:
  Connect the Portability shield to the RTK Postcard or other controller board
  Open output window at 115200bps
  Press button on navigation switch
*/

#include <SparkFun_I2C_Expander_Arduino_Library.h> // Click here to get the library: http://librarymanager/All#SparkFun_I2C_Expander_Arduino_Library

SFE_PCA95XX io(PCA95XX_PCA9554); // Create an instance with the PCA9554 IC

int gpioExpander_up = 0;
int gpioExpander_down = 1;
int gpioExpander_right = 2;
int gpioExpander_left = 3;
int gpioExpander_center = 4;
int gpioExpander_cardDetect = 5;

int gpioExpander_interrupt = 14; //INT of PCA9554 is connected to ESP14. Goes low when button is pressed.

bool buttonPressed = false;

void IRAM_ATTR gpioExpanderISR()
{
  buttonPressed = true;
}

void setup()
{
  Serial.begin(115200);
  delay(250);
  Serial.println("Portability Shield Example");

  Wire.begin(7, 20); //SDA, SCL. I2C0 on Portability Shield

  // Initialize the PCA95xx with its default I2C address
  if (io.begin() == false)
  {
    Serial.println("PCA9554 not detected. Please check wiring. Freezing...");
    while (1)
      ;
  }
  Serial.println("Portability Shield online!");

  pinMode(gpioExpander_interrupt, INPUT_PULLUP);

  io.pinMode(gpioExpander_up, INPUT);
  io.pinMode(gpioExpander_down, INPUT);
  io.pinMode(gpioExpander_left, INPUT);
  io.pinMode(gpioExpander_right, INPUT);
  io.pinMode(gpioExpander_center, INPUT);
  io.pinMode(gpioExpander_cardDetect, INPUT);

  uint8_t currentState = io.getInputRegister() & 0b00111111; //Ignore unconnected GPIO6/7
  if (currentState & 0b00100000)
    Serial.println("SD Card detected");
  else
    Serial.println("No card detected");

  //Setup interrupt service routine (ISR)
  attachInterrupt(gpioExpander_interrupt, gpioExpanderISR, CHANGE);
}

void loop()
{
  if (buttonPressed == true)
  {
    //Get all the pins in one read
    uint8_t currentState = io.getInputRegister() & 0b00111111; //Ignore unconnected GPIO6/7

    // Pins are pulled high so when we see low, button is being pressed
    if ((currentState & (1 << gpioExpander_up)) == 0)
      Serial.print("Up");
    if ((currentState & (1 << gpioExpander_down)) == 0)
      Serial.print("Down");
    if ((currentState & (1 << gpioExpander_left)) == 0)
      Serial.print("Left");
    if ((currentState & (1 << gpioExpander_right)) == 0)
      Serial.print("Right");
    if ((currentState & (1 << gpioExpander_center)) == 0)
      Serial.print("Center");

    Serial.println();

    buttonPressed = false;
  }

  delay(100);
}

One thing to note is that you will also need to install the SparkFun I2C Expander Arduino Library if you haven't already. You can search the library from within the Arduino Library manager, download the zip from the GitHub Repository and install it manually, or you can click the link from within the code. Clicking the link will show something like the following:

SparkFun I2C Expander Arduino Library Install

SparkFun I2C Expander Arduino Library Install

Make sure you've selected the correct board (in this case, you will need to use ESP32 Dev Module from espressif) and port in the Tools menu and then hit the upload button. Once the code has finished uploading, go ahead and open a Serial Monitor. You should see something similar to the following.

Arduino Example 1 Output

Example 1 Output

Example 2: Fuel Gauge

This file demonstrates the simple API of the SparkFun MAX17043 Arduino library. Make sure you have a LiPo battery plugged into the JST port.

The code for this example can be found in the Firmware folder of this repository. Alternatively, you can expand the link below and copy and paste the code into a shiny new Arduino sketch:

Example 2 Arduino Code
/*
  Example1_Simple
  By: Paul Clark
  Date: October 23rd 2020

  Based extensively on:
  MAX17043_Simple_Serial.cpp
  SparkFun MAX17043 Example Code
  Jim Lindblom @ SparkFun Electronics
  Original Creation Date: June 22, 2015

  This file demonstrates the simple API of the SparkFun MAX17043 Arduino library.

  This example will print the gauge's voltage and state-of-charge (SOC) readings
  to Serial (115200 baud)

  This code is released under the MIT license.

  Distributed as-is; no warranty is given.
*/

#include <Wire.h> // Needed for I2C

#include <SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library.h> // Click here to get the library: http://librarymanager/All#SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library

SFE_MAX1704X lipo(MAX1704X_MAX17048); // Create a MAX17048

double voltage = 0; // Variable to keep track of LiPo voltage
double soc = 0; // Variable to keep track of LiPo state-of-charge (SOC)
bool alert; // Variable to keep track of whether alert has been triggered

void setup()
{
  Serial.begin(115200); // Start serial, to output debug data
  while (!Serial)
    ; //Wait for user to open terminal
  Serial.println(F("MAX17043 Example"));

  Wire.begin(7, 20); //SDA, SCL

  // Set up the fuel gauge:
  if (lipo.begin() == false)
  {
    Serial.println(F("MAX17043 not detected. Please check wiring. Freezing."));
    while (1)
      ;
  }

  // Quick start restarts the MAX17043 in hopes of getting a more accurate
  // guess for the SOC.
  lipo.quickStart();

  // We can set an interrupt to alert when the battery SoC gets too low.
  // We can alert at anywhere between 1% - 32%:
  lipo.setThreshold(20); // Set alert threshold to 20%.
}

void loop()
{
  // lipo.getVoltage() returns a voltage value (e.g. 3.93)
  voltage = lipo.getVoltage();
  // lipo.getSOC() returns the estimated state of charge (e.g. 79%)
  soc = lipo.getSOC();
  // lipo.getAlert() returns a 0 or 1 (0=alert not triggered)
  alert = lipo.getAlert();

  // Print the variables:
  Serial.print("Voltage: ");
  Serial.print(voltage);  // Print the battery voltage
  Serial.println(" V");

  Serial.print("Percentage: ");
  Serial.print(soc); // Print the battery state of charge
  Serial.println(" %");

  Serial.print("Alert: ");
  Serial.println(alert);
  Serial.println();

  delay(500);
}

You will also need to install the SparkFun MAX1704x Fuel Gauge Arduino Library if you haven't already. You can search the library from within the Arduino Library manager, download the zip from the GitHub Repository and install it manually, or you can click the link from within the code. Clicking the link will show something like the following:

SparkFun MAX1704x Fuel Gauge Arduino Library Install

SparkFun MAX1704x Fuel Gauge Arduino Library Install

Make sure you've selected the correct board and port in the Tools menu and then hit the upload button. Once the code has finished uploading, go ahead and open a Serial Monitor at 115200 baud. You should see the gauge's voltage and state-of-charge (SOC) readings, similar to the following:

Arduino Example 2 Output

Example 2 Output

Example 3: Display

This example demonstrates how to display on the 1.3" OLED and read the fuel gauge. You should have the Portability Shield connected to the RTK Postcard or other controller and a LiPo battery plugged into the JST port.

The code for this example can be found in the Firmware folder of this repository. Alternatively, you can expand the link below and copy and paste the code into a shiny new Arduino sketch:

Example 3 Arduino Code
/*
  Display a cube on the OLED at 400kHz. Query the fuel guage periodically.
  By: Nathan Seidle
  SparkFun Electronics
  Date: November 24th, 2024
  License: This code is public domain but you buy me a drink if you use this
  and we meet someday (Drink license).
  Feel like supporting our work? Buy a board from SparkFun!
  https://www.sparkfun.com/products/27510

  This example demonstrates how to display on the 1.3" OLED and read the fuel gauge.

  Hardware Connections:
  Connect the Portability shield to the RTK Postcard or other controller board
  Open output window at 115200bps
*/
#include <Wire.h>

#include <SparkFun_Qwiic_OLED.h> //http://librarymanager/All#SparkFun_Qwiic_OLED
Qwiic1in3OLED myOLED;

int width;
int height;

// For simple frame rate calculations
long drawTotalTime = 0;
int numberOfDraws = 0;

float d = 3;
float px[] = { -d, d, d, -d, -d, d, d, -d};
float py[] = { -d, -d, d, d, -d, -d, d, d};
float pz[] = { -d, -d, -d, -d, d, d, d, d};

float p2x[8] = {0};
float p2y[8] = {0};
float r[3] = {0};

#define SHAPE_SIZE 950
#define ROTATION_SPEED 00

#include <SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library.h> // Click here to get the library: http://librarymanager/All#SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library
SFE_MAX1704X lipo(MAX1704X_MAX17048);

double voltage = 0;
double soc = 0;
unsigned long lastLipoCheck;

void setup()
{
  Serial.begin(115200);
  delay(250);
  Serial.println("OLED + Fuel gauge test");

  Wire.begin(7, 20); //SDA, SCL
  Wire.setClock(400000); //Go to 400kHz for faster OLED response
  //Wire.setClock(1000000); //Moar! It's not 1MHz but it does work.

  if (myOLED.begin() == false)
  {
    Serial.println("Device begin failed. Freezing...");
    while (true);
  }

  if (lipo.begin() == false)
  {
    Serial.println("MAX17043 not detected. Please check wiring. Freezing...");
    while (1);
  }

  Serial.println("Begin success");

  width = myOLED.getWidth();
  height = myOLED.getHeight();

  // For frame rate calc
  drawTotalTime = 0;
  numberOfDraws = 0;
}

void loop()
{
  drawCube();

  if (millis() - lastLipoCheck > 1000)
  {
    lastLipoCheck = millis();
    voltage = lipo.getVoltage();
    soc = lipo.getSOC();

    Serial.print("Voltage: ");
    Serial.print(voltage);  // Print the battery voltage
    Serial.println(" V");

    Serial.print("Percentage: ");
    Serial.print(soc); // Print the battery state of charge
    Serial.println(" %");

    Serial.println();
  }
}

void drawCube()
{
  r[0] = r[0] + PI / 180.0; // Add a degree
  r[1] = r[1] + PI / 180.0; // Add a degree
  r[2] = r[2] + PI / 180.0; // Add a degree
  if (r[0] >= 360.0 * PI / 180.0)
    r[0] = 0;
  if (r[1] >= 360.0 * PI / 180.0)
    r[1] = 0;
  if (r[2] >= 360.0 * PI / 180.0)
    r[2] = 0;

  // This routine gets called often, so just make these statics
  static float px2, py2, pz2, px3, py3, pz3, ax, ay, az;

  for (int i = 0; i < 8; i++)
  {
    px2 = px[i];
    py2 = cos(r[0]) * py[i] - sin(r[0]) * pz[i];
    pz2 = sin(r[0]) * py[i] + cos(r[0]) * pz[i];

    px3 = cos(r[1]) * px2 + sin(r[1]) * pz2;
    py3 = py2;
    pz3 = -sin(r[1]) * px2 + cos(r[1]) * pz2;

    ax = cos(r[2]) * px3 - sin(r[2]) * py3;
    ay = sin(r[2]) * px3 + cos(r[2]) * py3;
    az = pz3 - 150;

    p2x[i] = width / 2 + ax * SHAPE_SIZE / az;
    p2y[i] = height / 2 + ay * SHAPE_SIZE / az;
  }

  // Calculate draw time
  uint32_t startTime = millis();

  myOLED.erase();
  for (int i = 0; i < 3; i++)
  {
    myOLED.line(p2x[i], p2y[i], p2x[i + 1], p2y[i + 1]);
    myOLED.line(p2x[i + 4], p2y[i + 4], p2x[i + 5], p2y[i + 5]);
    myOLED.line(p2x[i], p2y[i], p2x[i + 4], p2y[i + 4]);
  }

  myOLED.line(p2x[3], p2y[3], p2x[0], p2y[0]);
  myOLED.line(p2x[7], p2y[7], p2x[4], p2y[4]);
  myOLED.line(p2x[3], p2y[3], p2x[7], p2y[7]);
  myOLED.display();

  // Write out our frame rate
  drawTotalTime += millis() - startTime;
  numberOfDraws++;

  // Output framerate once every 120 frames
  if (numberOfDraws % 120 == 0)
  {
    Serial.print("Frame rate: ");
    Serial.println(numberOfDraws / (float)drawTotalTime * 1000.0);

    numberOfDraws = 0;
    drawTotalTime = 0;
  }
}

You will also need both the SparkFun Qwiic OLED Arduino Library as well as the SparkFun MAX1704x Fuel Gauge Arduino Library. If you haven't already installed these, you can search them from within the Arduino Library manager and install them from there. Alternatively, you can download the zips from their respective GitHub Repositories here(Fuel Gauge) and here(Qwiic OLED), and install them manually.

Make sure you've selected the correct board and port in the Tools menu and then hit the upload button. Once the code has finished uploading, you should see the gauge's voltage and state-of-charge (SOC) readings, similar to the the output for Example 2. The 1.3" OLED display should show a bouncing box like so:

Arduino Example 3 Output

Example 3

Example 4: SD Card

This example will mount an SD card, analyze the type of card, and output the analysis via the Serial Monitor.

Go ahead and connect the Portability Shield to the RTK Postcard or other controller board. Insert a microSD card into the socket. Cards up to 512GB should work.

The code for this example can be found in the Firmware folder of this repository. Alternatively, you can expand the link below and copy and paste the code into a shiny new Arduino sketch:

Example 4 Arduino Code
/*
  Attempt to mount an SD card and analyze the type of card
  By: Nathan Seidle
  SparkFun Electronics
  Date: November 24th, 2024
  License: This code is public domain but you buy me a drink if you use this
  and we meet someday (Drink license).
  Feel like supporting our work? Buy a board from SparkFun!
  https://www.sparkfun.com/products/27510

  Hardware Connections:
  Connect the Portability shield to the RTK Postcard or other controller board
  Insert a microSD card into the socket. Cards up to 512GB should work.
  Open output window at 115200bps
*/
#include "SdFat.h"  // Click here to get the library: http://librarymanager/All#SdFat_SDXC by Bill Greiman
#include "sdios.h"

int pin_spiSCK = 32;
int pin_spiPICO = 26; //microSD SDI
int pin_spiPOCI = 25; //microSD SDO
int pin_microSD_CS = 27;

#define SD_CONFIG SdSpiConfig(pin_microSD_CS, SHARED_SPI, SD_SCK_MHZ(16))
//#define SD_CONFIG SdSpiConfig(pin_microSD_CS, DEDICATED_SPI, SD_SCK_MHZ(16))

const int8_t DISABLE_CS_PIN = -1;

//------------------------------------------------------------------------------
SdFs sd;
cid_t cid;
csd_t csd;
scr_t scr;
uint8_t cmd6Data[64];
uint32_t eraseSize;
uint32_t ocr;
static ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
void cidDmp() {
  cout << F("\nManufacturer ID: ");
  cout << uppercase << showbase << hex << int(cid.mid) << dec << endl;
  cout << F("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl;
  cout << F("Product: ");
  for (uint8_t i = 0; i < 5; i++) {
    cout << cid.pnm[i];
  }
  cout << F("\nRevision: ") << cid.prvN() << '.' << cid.prvM() << endl;
  cout << F("Serial number: ") << hex << cid.psn() << dec << endl;
  cout << F("Manufacturing date: ");
  cout << cid.mdtMonth() << '/' << cid.mdtYear() << endl;
  cout << F("CID HEX: ");
  hexDmp(&cid, sizeof(cid));
}
//------------------------------------------------------------------------------
void clearSerialInput() {
  uint32_t m = micros();
  do {
    if (Serial.read() >= 0) {
      m = micros();
    }
  } while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
void csdDmp() {
  eraseSize = csd.eraseSize();
  cout << F("\ncardSize: ") << 0.000512 * csd.capacity();
  cout << F(" MB (MB = 1,000,000 bytes)\n");

  cout << F("flashEraseSize: ") << int(eraseSize) << F(" blocks\n");
  cout << F("eraseSingleBlock: ");
  if (csd.eraseSingleBlock()) {
    cout << F("true\n");
  } else {
    cout << F("false\n");
  }
  cout << F("dataAfterErase: ");
  if (scr.dataAfterErase()) {
    cout << F("ones\n");
  } else {
    cout << F("zeros\n");
  }
  cout << F("CSD HEX: ");
  hexDmp(&csd, sizeof(csd));
}
//------------------------------------------------------------------------------
void errorPrint() {
  if (sd.sdErrorCode()) {
    cout << F("SD errorCode: ") << hex << showbase;
    printSdErrorSymbol(&Serial, sd.sdErrorCode());
    cout << F(" = ") << int(sd.sdErrorCode()) << endl;
    cout << F("SD errorData = ") << int(sd.sdErrorData()) << dec << endl;
  }
}
//------------------------------------------------------------------------------
void hexDmp(void* reg, uint8_t size) {
  uint8_t* u8 = reinterpret_cast<uint8_t*>(reg);
  cout << hex << noshowbase;
  for (size_t i = 0; i < size; i++) {
    cout << setw(2) << setfill('0') << int(u8[i]);
  }
  cout << dec << endl;
}
//------------------------------------------------------------------------------
bool mbrDmp() {
  MbrSector_t mbr;
  bool valid = true;
  if (!sd.card()->readSector(0, (uint8_t*)&mbr)) {
    cout << F("\nread MBR failed.\n");
    errorPrint();
    return false;
  }
  cout << F("\nSD Partition Table\n");
  cout << F("part,boot,bgnCHS[3],type,endCHS[3],start,length\n");
  for (uint8_t ip = 1; ip < 5; ip++) {
    MbrPart_t* pt = &mbr.part[ip - 1];
    if ((pt->boot != 0 && pt->boot != 0X80) ||
        getLe32(pt->relativeSectors) > csd.capacity()) {
      valid = false;
    }
    cout << int(ip) << ',' << uppercase << showbase << hex;
    cout << int(pt->boot) << ',';
    for (int i = 0; i < 3; i++) {
      cout << int(pt->beginCHS[i]) << ',';
    }
    cout << int(pt->type) << ',';
    for (int i = 0; i < 3; i++) {
      cout << int(pt->endCHS[i]) << ',';
    }
    cout << dec << getLe32(pt->relativeSectors) << ',';
    cout << getLe32(pt->totalSectors) << endl;
  }
  if (!valid) {
    cout << F("\nMBR not valid, assuming Super Floppy format.\n");
  }
  return true;
}
//------------------------------------------------------------------------------
void dmpVol() {
  cout << F("\nScanning FAT, please wait.\n");
  int32_t freeClusterCount = sd.freeClusterCount();
  if (sd.fatType() <= 32) {
    cout << F("\nVolume is FAT") << int(sd.fatType()) << endl;
  } else {
    cout << F("\nVolume is exFAT\n");
  }
  cout << F("sectorsPerCluster: ") << sd.sectorsPerCluster() << endl;
  cout << F("fatStartSector:    ") << sd.fatStartSector() << endl;
  cout << F("dataStartSector:   ") << sd.dataStartSector() << endl;
  cout << F("clusterCount:      ") << sd.clusterCount() << endl;
  cout << F("freeClusterCount:  ");
  if (freeClusterCount >= 0) {
    cout << freeClusterCount << endl;
  } else {
    cout << F("failed\n");
    errorPrint();
  }
}
//------------------------------------------------------------------------------
void printCardType() {
  cout << F("\nCard type: ");

  switch (sd.card()->type()) {
    case SD_CARD_TYPE_SD1:
      cout << F("SD1\n");
      break;

    case SD_CARD_TYPE_SD2:
      cout << F("SD2\n");
      break;

    case SD_CARD_TYPE_SDHC:
      if (csd.capacity() < 70000000) {
        cout << F("SDHC\n");
      } else {
        cout << F("SDXC\n");
      }
      break;

    default:
      cout << F("Unknown\n");
  }
}
//------------------------------------------------------------------------------
void printConfig(SdSpiConfig config) {
  if (DISABLE_CS_PIN < 0) {
    cout << F(
        "\nAssuming the SD is the only SPI device.\n"
        "Edit DISABLE_CS_PIN to disable an SPI device.\n");
  } else {
    cout << F("\nDisabling SPI device on pin ");
    cout << int(DISABLE_CS_PIN) << endl;
    pinMode(DISABLE_CS_PIN, OUTPUT);
    digitalWrite(DISABLE_CS_PIN, HIGH);
  }
  cout << F("\nAssuming the SD chip select pin is: ") << int(config.csPin);
  cout << F("\nEdit SD_CS_PIN to change the SD chip select pin.\n");
}
//------------------------------------------------------------------------------
void printConfig(SdioConfig config) {
  (void)config;
  cout << F("Assuming an SDIO interface.\n");
}
//-----------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(250);
  Serial.println("SPI test");

  SPI.begin(pin_spiSCK, pin_spiPOCI, pin_spiPICO);

  cout << F("SdFat version: ") << SD_FAT_VERSION_STR << endl;
  printConfig(SD_CONFIG);
}
//------------------------------------------------------------------------------
void loop() {
  // Read any existing Serial data.
  clearSerialInput();

  // F stores strings in flash to save RAM
  cout << F("\ntype any character to start\n");
  while (!Serial.available()) {
    yield();
  }
  uint32_t t = millis();
  if (!sd.cardBegin(SD_CONFIG)) {
    cout << F(
        "\nSD initialization failed.\n"
        "Do not reformat the card!\n"
        "Is the card correctly inserted?\n"
        "Is there a wiring/soldering problem?\n");
    if (isSpi(SD_CONFIG)) {
      cout << F(
          "Is SD_CS_PIN set to the correct value?\n"
          "Does another SPI device need to be disabled?\n");
    }
    errorPrint();
    return;
  }
  t = millis() - t;
  cout << F("init time: ") << dec << t << " ms" << endl;

  if (!sd.card()->readCID(&cid) || !sd.card()->readCSD(&csd) ||
      !sd.card()->readOCR(&ocr) || !sd.card()->readSCR(&scr)) {
    cout << F("readInfo failed\n");
    errorPrint();
    return;
  }
  printCardType();
  cout << F("sdSpecVer: ") << 0.01 * scr.sdSpecVer() << endl;
  cout << F("HighSpeedMode: ");
  if (scr.sdSpecVer() > 101 && sd.card()->cardCMD6(0X00FFFFFF, cmd6Data) &&
      (2 & cmd6Data[13])) {
    cout << F("true\n");
  } else {
    cout << F("false\n");
  }
  cidDmp();
  csdDmp();
  cout << F("\nOCR: ") << uppercase << showbase;
  cout << hex << ocr << dec << endl;
  if (!mbrDmp()) {
    return;
  }
  if (!sd.volumeBegin()) {
    cout << F("\nvolumeBegin failed. Is the card formatted?\n");
    errorPrint();
    return;
  }
  dmpVol();
}

You will also need to install the SDFat Arduino Library if you haven't already. You can search the library from within the Arduino Library manager, download the zip from the GitHub Repository and install it manually, or you can click the link from within the code.

Make sure you've selected the correct board and port in the Tools menu and then hit the upload button. Once the code has finished uploading, go ahead and open a Serial Monitor at 115200 baud. Once you type in any key in the Message Field and send it, the code will analyze the SD card and output something similar to the following:

Arduino Example 4 Output

Example 4 Output