XIAO Sense nRF52840 LittleFS on QSPI Flash with FreeRTOS

Hey all,

For some context, I need to store a configuration file within the 2MB onboard flash of the XIAO Sense nRF52840 board without the use of mbed os. I want to be able to use LittleFS to manage the config.json/config.txt file while being able to read/write (any alternatives would be nice as well).

As I said before, It is imperative that it is done without the use of mbed os in terms of power consumption. That being said, I’ve looked at the following page, but can’t get it to work due to it requiring mbed.

Here’s some code I had written for flash reads, but I’m stuck on properly writing to the flash in the way I need.

#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include <SdFat.h>
#include <Adafruit_SPIFlash.h>
#include <LSM6DS3.h>

// Flash Chip declerations, made up from Data Sheets.
// https://files.seeedstudio.com/wiki/github_weiruanexample/Flash_P25Q16H-UXH-IR_Datasheet.pdf
SPIFlash_Device_t const p25q16h{
.total_size = (1UL << 21), // 2MiB
.start_up_time_us = 10000,
.manufacturer_id = 0x85,
.memory_type = 0x60,
.capacity = 0x15,
.max_clock_speed_mhz = 55,
.quad_enable_bit_mask = 0x02,
.has_sector_protection = 1,
.supports_fast_read = 1,
.supports_qspi = 1,
.supports_qspi_writes = 1,
.write_status_register_split = 1,
.single_status_byte = 0,
.is_fram = 0,

};

//Create a instance of class LSM6DS3
LSM6DS3 myIMU(I2C_MODE, 0x6A);

#define int2Pin PIN_LSM6DS3TR_C_INT1
#define SS_SPI1 25 // Defaul SS or CS for the Onboard QSPI Flash Chip

SPIClass SPI_2(NRF_SPIM0, PIN_QSPI_IO1, PIN_QSPI_SCK, PIN_QSPI_IO0); // Onboard QSPI Flash chip
Adafruit_FlashTransport_SPI QflashTransport(PIN_QSPI_CS, SPI_2); // CS for QSPI Flash
Adafruit_SPIFlash Qflash(&QflashTransport);

void setup() {
Serial.begin(9600);

while (!Serial) {
delay(100); // waits for serial
}

Serial.println(“Starting up”);

if (!Qflash.begin(&p25q16h, 1)) {

Serial.println(F("Error, failed to initialize QSPI flash chip!"));

while (1)

  ;

}

Serial.println(Qflash.getJEDECID(), HEX);
Serial.print(Qflash.size() / 1024);
Serial.println(F(" KB"));
Serial.println(Qflash.read8(0x000500));
Serial.print("page size ");
Serial.println(Qflash.pageSize());
Serial.print("num page ");
Serial.println(Qflash.numPages());
Serial.print("sector count ");
Serial.print(Qflash.sectorCount());

}

/*

0x1FF000:

read8
Starting up
856015
2048 KB
255

read16
Starting up
856015
2048 KB
65535

read32
Starting up
856015
2048 KB
4294967295

*/

void loop() {
}

If there are any code examples using LittleFS on FreeRTOS with this chip, I would appreciate them!

Thank you so much in advance!

1 Like

Hi there ,
Have you looked at the built in littFS part of the core AFAIK.

Is that close, and You know that you can only write and erase Blocks or pages discreetly like the SPeed Test flash demo.
HTH
GL :slight_smile: PJ

Hi there,
BTW I used this to help understand the flow.

// The MIT License (MIT)
// Copyright (c) 2019 Ha Thach for Adafruit Industries
// Adaptation: Matthieu Charbonnier


#include <SPI.h>
#include "Adafruit_SPIFlash.h"
#include "flash_devices.h"

// Built from the P25Q16H datasheet.
SPIFlash_Device_t const P25Q16H {
  .total_size = (1UL << 21), // 2MiB
  .start_up_time_us = 10000, // Don't know where to find that value
  
  .manufacturer_id = 0x85,
  .memory_type = 0x60,
  .capacity = 0x15,

  .max_clock_speed_mhz = 55,
  .quad_enable_bit_mask = 0x02, // Datasheet p. 27
  .has_sector_protection = 1,   // Datasheet p. 27
  .supports_fast_read = 1,      // Datasheet p. 29
  .supports_qspi = 1,           // Obviously
  .supports_qspi_writes = 1,    // Datasheet p. 41
  .write_status_register_split = 1, // Datasheet p. 28
  .single_status_byte = 0,      // 2 bytes
  .is_fram = 0,                 // Flash Memory
};

// Use this constructor to tune the QSPI pins.
// Adafruit_FlashTransport_QSPI flashTransport(PIN_QSPI_SCK, PIN_QSPI_CS, PIN_QSPI_IO0, PIN_QSPI_IO1, PIN_QSPI_IO2, PIN_QSPI_IO3);
Adafruit_FlashTransport_QSPI flashTransport;

Adafruit_SPIFlash flash(&flashTransport);

#define BUFSIZE   4096

#define TEST_WHOLE_CHIP 1

#ifdef LED_BUILTIN
  uint8_t led_pin = LED_BUILTIN;
#else
  uint8_t led_pin = 0;
#endif

// 4 byte aligned buffer has best result with nRF QSPI
uint8_t bufwrite[BUFSIZE] __attribute__ ((aligned(4)));
uint8_t bufread[BUFSIZE] __attribute__ ((aligned(4)));

// the setup function runs once when you press reset or power the board
void setup()
{
  Serial.begin(115200);
  while ( !Serial ) delay(100);   // wait for native usb

  flash.begin(&P25Q16H, 1);

  pinMode(led_pin, OUTPUT);
  flash.setIndicator(led_pin, false);

  Serial.println("Adafruit Serial Flash Speed Test example");
  Serial.print("JEDEC ID: "); Serial.println(flash.getJEDECID(), HEX);
  Serial.print("Flash size: "); Serial.println(flash.size());
  Serial.flush();

  write_and_compare(0xAA);
  write_and_compare(0x55);

  Serial.println("Speed test is completed.");
  Serial.flush();
}

void print_speed(const char* text, uint32_t count, uint32_t ms)
{
  Serial.print(text);
  Serial.print(count);
  Serial.print(" bytes in ");
  Serial.print(ms / 1000.0F, 2);
  Serial.println(" seconds.");

  Serial.print("Speed: ");
  Serial.print( (count / 1000.0F) / (ms / 1000.0F), 2);
  Serial.println(" KB/s.\r\n");
}

bool write_and_compare(uint8_t pattern)
{
  uint32_t ms;

  Serial.println("Erase chip");
  Serial.flush();

#if TEST_WHOLE_CHIP
  uint32_t const flash_sz = flash.size();
  flash.eraseChip();
#else
  uint32_t const flash_sz = 4096;
  flash.eraseSector(0);
#endif

  flash.waitUntilReady();

  // write all
  memset(bufwrite, (int) pattern, sizeof(bufwrite));
  Serial.print("Write flash with 0x");
  Serial.println(pattern, HEX);
  Serial.flush();
  ms = millis();

  for(uint32_t addr = 0; addr < flash_sz; addr += sizeof(bufwrite))
  {
    flash.writeBuffer(addr, bufwrite, sizeof(bufwrite));
  }

  uint32_t ms_write = millis() - ms;
  print_speed("Write ", flash_sz, ms_write);
  Serial.flush();

  // read and compare
  Serial.println("Read flash and compare");
  Serial.flush();
  uint32_t ms_read = 0;
  for(uint32_t addr = 0; addr < flash_sz; addr += sizeof(bufread))
  {
    memset(bufread, 0, sizeof(bufread));

    ms = millis();
    flash.readBuffer(addr, bufread, sizeof(bufread));
    ms_read += millis() - ms;

    if ( memcmp(bufwrite, bufread, BUFSIZE) )
    {
      Serial.print("Error: flash contents mismatched at address 0x");
      Serial.println(addr, HEX);
      for(uint32_t i=0; i<sizeof(bufread); i++)
      {
        if ( i != 0 ) Serial.print(' ');
        if ( (i%16 == 0) )
        {
          Serial.println();
          if ( i < 0x100 ) Serial.print('0');
          if ( i < 0x010 ) Serial.print('0');
          Serial.print(i, HEX);
          Serial.print(": ");
        }

        if (bufread[i] < 0x10) Serial.print('0');
        Serial.print(bufread[i], HEX);
      }

      Serial.println();
      return false;
    }
  }

  print_speed("Read  ", flash_sz, ms_read);
  Serial.flush();

  return true;
}

void loop()
{
  // nothing to do
}

Erase read and write, You may want to consider a buffer write first then to the flash as a block or something, Padded if it’s short.

HTH
GL :slight_smile: PJ

Thank you for the sample code @PJ_Glasso ! I’ve been able to play around with it to get a better understanding of how the reading and writing works, but I’ve been having trouble making sense of the littlefs github link you provided. How would I go about instantiating the class for the QSPI rather than the internal flash? I’ve seen it done with the internal flash but not external (P25Q16H).

InternalFS

Thanks in advance!

Hi there,
Ok to test the QSPI and Insatiate the QSPI interface look here at this example

worth a read to see the Nrf52840’s interfaces Here is the MeAT :grinning: :+1:

#define SS_SPI0 1  // seems to default to Pin 7 if you don't declare it as the CS for the Flash Chip A1/D1 pin1 on the Xiao
#define SS_SPI1 25

SPIClass SPI_2(NRF_SPIM0, PIN_QSPI_IO1, PIN_QSPI_SCK, PIN_QSPI_IO0); // Onboard QSPI Flash chip
Adafruit_FlashTransport_SPI QflashTransport(PIN_QSPI_CS, SPI_2);     // CS for QSPI Flash

Adafruit_SPIFlash Qflash(&QflashTransport);

SPIClass SPI_1(NRF_SPIM3, D9, D8, D10);   // MISO, SCK, MOSI /EXPANSION FLASH// CS pin D1 (hardwired)
Adafruit_FlashTransport_SPI EflashTransport(SS_SPI0, SPI_1);  // Flash Type

Adafruit_SPIFlash Eflash(&EflashTransport); 

HTH
GL :slight_smile: PJ

I was a little too vague. Rather I mean how would I create the instance of littlefs using the the P25Q16H chip instead of using the internal nRF52840 flash?

Thanks!

Hi,

Were you able to figure this out? I’m also trying to write data to the 2MB onboard flash on the XIAO Sense nRF52840 without using the mbed library and struggling to get it to work.

Thanks!

I wasn’t able to figure out how to write to the 2 MB QSPI Flash and would still love to find out how, but I was able to figure out writing to the on-chip flash (with the help of @PJ_Glasso, of course!). Here’s the link to the GitHub repo with the InternalFS library. It acts as a wrapper for the AdafruitLittleFS library and writes to the 1 MB flash. Formatting doesn’t format the bootloader partition, but be mindful of the size of data you write to the memory. I’m not exactly sure if it can overwrite it, but it doesn’t sound fun finding out.

I’m pretty sure the on-chip flash has ~100,000 write cycles, but don’t put any formatting or writing functions in loop because it’ll wear it out pretty fast. I don’t think reading functions are included since it’s NOR flash.

In general the error messages are quite cryptic. From my experience with the library, when I would get an error message, the problem would usually be one of the following things.:

  • The file is open when you’re formatting
  • You’re trying to write when reading or read when writing
  • The file isn’t closed

If you use it with the SD library you’ll have to explicitly say where the File functions come from (since they both have a File class)

// Prefix with Adafruit_LittleFS_Namespace::
File.open(FILENAME, Adafruit_LittleFS_Namespace::FILE_O_READ);

They have some examples on the GitHub already, but if you can explain your use case I can try to create an example for you as well.

Let me know if you have any questions and good luck!

Hi there,
You can look at the Speed test and Diags with the Adafruit Flash examples
Writing and testing Both QSPI flash and External “grove flash”
Instantiate them both for Littfs or as SD card. The Adafruit examples do Work with minimum tweaks.
HTH
GL :slight_smile: PJ
see how they read and write, erase…

/*
  ----------------------------------------------------------------------------------------------------------------------------------
  |                                                            Winbond Flash                                                         |
  |                                                      SPIMemory library test v3.0.1                                                |
  |----------------------------------------------------------------------------------------------------------------------------------|
  |                                                                Marzogh                                                           |
  |                                                              16.11.2016                                                          |
  |                                                          Modified: hanyazou                                                      |
  |                                                              19.11.2017                                                          |
  |----------------------------------------------------------------------------------------------------------------------------------|
  |                                     (Please make sure your Serial monitor is set to 'No Line Ending')                            |
  |                                     *****************************************************************                            |
  |                                                                                                                                  |
  |                     # Please pick from the following commands and type the command number into the Serial console #              |
  |    For example - to write a byte of data, you would have to use the write_byte function - so type '3' into the serial console.   |
  |                                                    --------------------------------                                              |
  |                                                                                                                                  |
  |  1. getID                                                                                                                        |
  |   '1' gets the JEDEC ID of the chip                                                                                              |
  |                                                                                                                                  |
  |  2. writeByte [address] [byte]                                                                                                   |
  |   '2' followed by '2435' and then by '224' writes the byte 224 to address 2435                                                   |
  |                                                                                                                                  |
  |  3. readByte [address]                                                                                                           |
  |   '3' followed by '2435' returns the byte from address '2435'                                                                    |
  |                                                                                                                                  |
  |  4. writeWord [address] [word]                                                                                                   |
  |   '4' followed by '5948' and then by '633' writes the int 633 to address 5948                                                    |
  |                                                                                                                                  |
  |  5. readWord [address]                                                                                                           |
  |   '5' followed by '5948' returns the int from address 5948                                                                       |
  |                                                                                                                                  |
  |  6. writeStr [address] [inputString]                                                                                             |
  |   '6' followed by '345736' and then by 'Test String 1!' writes the String 'Test String 1! to address 345736                      |
  |                                                                                                                                  |
  |  7. readStr [address] [outputString]                                                                                             |
  |   '7' followed by '345736' reads the String from address 345736 into the outputString                                            |
  |                                                                                                                                  |
  |  8. writePage [page]                                                                                                             |
  |   '8' followed by '33' writes bytes from 0 to 255 sequentially to fill a page (256 bytes) starting with address 33               |
  |                                                                                                                                  |
  |  9. printPage [page]                                                                                                             |
  |   '9' followed by 33 reads & prints a page (256 bytes) starting with address 33. To just read a page to a data buffer, refer     |
  |    to 'ReadMe.md' in the library folder.                                                                                         |
  |                                                                                                                                  |
  |  10. printAllData                                                                                                                |
  |   '10' reads the entire chip and outputs the data as a byte array to the serial console                                          |
  |   This function is to extract data from a flash chip onto a computer as a text file.                                             |
  |   Refer to 'Read me.md' in the library for details.                                                                              |
  |                                                                                                                                  |
  |  11. Erase 4KB sector                                                                                                            |
  |   '11'  followed by 2 erases a 4KB sector containing the address be erased                                                       |
  |   Page 0-15 --> Sector 0; Page 16-31 --> Sector 1;......Page 4080-4095 --> Sector 255                                            |
  |                                                                                                                                  |
  |  12. Erase 32KB block                                                                                                            |
  |   '12'  followed by 2 erases a 32KB block containing the address to be erased                                                    |
  |   Page 0-15 --> Sector 0; Page 16-31 --> Sector 1;......Page 4080-4095 --> Sector 255                                            |
  |                                                                                                                                  |
  |  13. Erase 64KB block                                                                                                            |
  |   '13'  followed by 2 erases a 64KB block containing the address to be erased                                                    |
  |   Page 0-15 --> Sector 0; Page 16-31 --> Sector 1;......Page 4080-4095 --> Sector 255                                            |
  |                                                                                                                                  |
  |  14. Erase Chip                                                                                                                  |
  |   '14' erases the entire chip                                                                                                    |
  |                                                                                                                                  |
  ^----------------------------------------------------------------------------------------------------------------------------------^
*/
//#include <SPI.h>
#include<SPIMemory.h>
uint8_t pageBuffer[256];
String serialCommand;
char printBuffer[128];
uint32_t addr;
uint8_t dataByte;
uint16_t dataInt;
String inputString, outputString;

//Define a flash memory size (if using non-Winbond memory) according to the list in defines.h
//#define CHIPSIZE MB64

#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
// Required for Serial on Zero based boards
#define Serial SERIAL_PORT_USBVIRTUAL
#endif

#if defined (SIMBLEE)
#define BAUD_RATE 250000
#define RANDPIN 0
#else
#define BAUD_RATE 9600
#if defined(ARCH_STM32)
#define RANDPIN PA0
#else
#define RANDPIN A0
#endif
#endif
#define SS 3
//SPIFlash flash;                   
SPIFlash flash(SS, &SPI);       //Use this constructor if using an SPI bus other than the default SPI. Only works with chips with more than one hardware SPI bus

void setup() {
  
  Serial.begin(BAUD_RATE);
#if defined (ARDUINO_ARCH_SAMD) || (__AVR_ATmega32U4__) || defined(ARCH_STM32) || defined(NRF5)
  while (!Serial) ; // Wait for Serial monitor to open
#endif
  delay(250); //Time to terminal get connected
  Serial.print(F("Initialising"));
  for (uint8_t i = 0; i < 10; ++i)
  {
    Serial.print(F("."));
  }
  Serial.println();
  randomSeed(analogRead(RANDPIN));
  Serial.println("--------------");
 Serial.print("MOSI: ");
  Serial.println(MOSI);
  Serial.print("MISO: ");
  Serial.println(MISO);
  Serial.print("SCK: ");
  Serial.println(SCK);
  Serial.print("SS: ");
  Serial.println(SS);  
 Serial.println("--------------");
  flash.begin();
  //To use a custom flash memory size (if using memory from manufacturers not officially supported by the library) - declare a size variable according to the list in defines.h
  //flash.begin(MB(1));
  
  Serial.println();
  Serial.println();
  commandList();
}

void loop() {
  while (Serial.available() > 0) {
    uint8_t commandNo = Serial.parseInt();
    if (commandNo == 0) {
      commandList();
    }
    else if (commandNo == 1) {
      printLine();
      Serial.println(F("                                                      Function 1 : Get JEDEC ID                                                   "));
      printLine();
      printLine();
      uint8_t b1, b2, b3;
      uint32_t JEDEC = flash.getJEDECID();
      //uint16_t ManID = flash.getManID();
      b1 = (JEDEC >> 16);
      b2 = (JEDEC >> 8);
      b3 = (JEDEC >> 0);
      clearprintBuffer();
      sprintf(printBuffer, "Manufacturer ID: %02xh\nMemory Type: %02xh\nCapacity: %02xh", b1, b2, b3);
      Serial.println(printBuffer);
      clearprintBuffer();
      sprintf(printBuffer, "JEDEC ID: %04xh", JEDEC);
      Serial.println(printBuffer);
      printLine();
      printNextCMD();
    }
    else if (commandNo == 2) {
      printLine();
      Serial.println(F("                                                       Function 2 : Write Byte                                                    "));
      printSplash();
      printLine();
      inputAddress();
      Serial.print(F("Please enter the value of the byte (0-255) you wish to save: "));
      while (!Serial.available()) {
      }
      dataByte = Serial.parseInt();
      Serial.println(dataByte);
      if (flash.writeByte(addr, dataByte)) {
        clearprintBuffer();
        sprintf(printBuffer, "%d has been written to address %d", dataByte, addr);
        Serial.println(printBuffer);
      }
      else {
        writeFail();
      }
      printLine();
      printNextCMD();
    }
    else if (commandNo == 3) {
      printLine();
      Serial.println(F("                                                       Function 3 : Read Byte                                                     "));
      printSplash();
      printLine();
      inputAddress();
      clearprintBuffer();
      sprintf(printBuffer, "The byte at address %d is: ", addr);
      Serial.print(printBuffer);
      Serial.println(flash.readByte(addr));
      printLine();
      printNextCMD();
    }
    else if (commandNo == 4) {
      printLine();
      Serial.println(F("                                                       Function 4 : Write Word                                                    "));
      printSplash();
      printLine();
      inputAddress();
      Serial.print(F("Please enter the value of the word (>255) you wish to save: "));
      while (!Serial.available()) {
      }
      dataInt = Serial.parseInt();
      Serial.println(dataInt);
      if (flash.writeWord(addr, dataInt)) {
        clearprintBuffer();
        sprintf(printBuffer, "%d has been written to address %d", dataInt, addr);
        Serial.println(printBuffer);
      }
      else {
        writeFail();
      }
      printLine();
      printNextCMD();
    }
    else if (commandNo == 5) {
      printLine();
      Serial.println(F("                                                       Function 5 : Read Word                                                     "));
      printSplash();
      printLine();
      inputAddress();
      clearprintBuffer();
      sprintf(printBuffer, "The unsigned int at address %d is: ", addr);
      Serial.print(printBuffer);
      Serial.println(flash.readWord(addr));
      printLine();
      printNextCMD();
    }
    else if (commandNo == 6) {
      printLine();
      Serial.println(F("                                                      Function 6 : Write String                                                   "));
      printSplash();
      printLine();
      Serial.println(F("This function will write a String of your choice to the page selected."));
      inputAddress();
      Serial.println(F("Please enter the String you wish to save: "));
      while (!Serial.available()) {
      }
      readSerialStr(inputString);
      if (flash.writeStr(addr, inputString)) {
        clearprintBuffer();
        Serial.print(F("String '"));
        Serial.print(inputString);
        sprintf(printBuffer, "' has been written to address %d", addr);
        Serial.println(printBuffer);
      }
      else {
        writeFail();
      }
      printLine();
      printNextCMD();
    }
    else if (commandNo == 7) {
      printLine();
      Serial.println(F("                                                      Function 7 : Read String                                                    "));
      printSplash();
      printLine();
      Serial.print(F("This function will read a string from your address of choice: "));
      inputAddress();
      clearprintBuffer();
      sprintf(printBuffer, "The String at address %d is: ", addr);
      Serial.print(printBuffer);
      flash.readStr(addr, outputString);
      Serial.println(outputString);
      printLine();
      printNextCMD();
    }
    else if (commandNo == 8) {
      printLine();
      Serial.println(F("                                                       Function 8 : Write Page                                                    "));
      printSplash();
      printLine();
      Serial.println(F("This function will write a sequence of bytes (0-255) starting from your address of choice"));
      Serial.print(F("Please enter the address you wish to write to: "));
      while (!Serial.available()) {
      }
      addr = Serial.parseInt();
      Serial.println(addr);
      for (uint16_t i = 0; i < SPI_PAGESIZE; ++i) {
        pageBuffer[i] = i;
      }
      if (flash.writeByteArray(addr, &pageBuffer[0], SPI_PAGESIZE)) {
        clearprintBuffer();
        sprintf(printBuffer, "Values from 0 to 255 have been written starting from the address %d", addr);
        Serial.println(printBuffer);
        printReadChoice();
        while (!Serial.available()) {
        }
        uint8_t choice = Serial.parseInt();
        Serial.println(choice);
        if (choice == 1) {
          printOutputChoice();
          while (!Serial.available()) {
          }
          uint8_t outputType = Serial.parseInt();
          Serial.println(outputType);
          printPage(addr, outputType);
        }
      }
      else {
        writeFail();
      }
      printLine();
      printNextCMD();
    }
    else if (commandNo == 9) {
      printLine();
      Serial.println(F("                                                       Function 9 : Read Page                                                    "));
      printSplash();
      printLine();
      Serial.println(F("This function will read 256 bytes from the address selected."));
      Serial.print(F("Please enter the address you wish to read: "));
      while (!Serial.available()) {
      }
      addr = Serial.parseInt();
      Serial.println(addr);
      printOutputChoice();
      while (!Serial.available()) {
      }
      uint8_t outputType = Serial.parseInt();
      Serial.println(outputType);
      printPage(addr, outputType);
      printLine();
      printNextCMD();
    }
    else if (commandNo == 10) {
      printLine();
      Serial.println(F("                                                     Function 10 : Read All Pages                                                  "));
      printSplash();
      printLine();
      Serial.println(F("This function will read the entire flash memory."));
      Serial.println(F("This will take a long time and might result in memory issues. Do you wish to continue? (Y/N)"));
      char c;
      while (!Serial.available()) {
      }
      c = (char)Serial.read();
      if (c == 'Y' || c == 'y') {
        printOutputChoice();
        while (!Serial.available()) {
        }
        uint8_t outputType = Serial.parseInt();
        Serial.println(outputType);
        printAllPages(outputType);
      }
      printLine();
      printNextCMD();
    }
    else if (commandNo == 11) {
      printLine();
      Serial.println(F("                                                       Function 11 : Erase 4KB sector                                               "));
      printSplash();
      printLine();
      Serial.println(F("This function will erase a 4KB sector."));
      Serial.print(F("Please enter the address you wish to erase: "));
      while (!Serial.available()) {
      }
      addr = Serial.parseInt();
      Serial.println(addr);
      if (flash.eraseSector(addr)) {
        clearprintBuffer();
        sprintf(printBuffer, "A 4KB sector containing address %d has been erased", addr);
        Serial.println(printBuffer);
        printReadChoice();
        while (!Serial.available()) {
        }
        uint8_t choice = Serial.parseInt();
        Serial.println(choice);
        if (choice == 1) {
          printOutputChoice();
          while (!Serial.available()) {
          }
          uint8_t outputType = Serial.parseInt();
          Serial.println(outputType);
          printPage(addr, outputType);
        }
      } else {
        Serial.println("Erasing sector failed");
      }
      printLine();
      printNextCMD();
    }
    else if (commandNo == 12) {
      printLine();
      Serial.println(F("                                                       Function 12 : Erase 32KB Block                                              "));
      printSplash();
      printLine();
      Serial.println(F("This function will erase a 32KB block."));
      Serial.print(F("Please enter the address you wish to erase: "));
      while (!Serial.available()) {
      }
      addr = Serial.parseInt();
      Serial.println(addr);
      if (flash.eraseBlock32K(addr)) {
        clearprintBuffer();
        sprintf(printBuffer, "A 32KB block containing address %d has been erased", addr);
        Serial.println(printBuffer);
        printReadChoice();
        while (!Serial.available()) {
        }
        uint8_t choice = Serial.parseInt();
        Serial.println(choice);
        if (choice == 1) {
          printOutputChoice();
          while (!Serial.available()) {
          }
          uint8_t outputType = Serial.parseInt();
          Serial.println(outputType);
          printPage(addr, outputType);
        }
      } else {
        Serial.println("Erasing block 32K failed");
      }
      printLine();
      printNextCMD();
    }
    else if (commandNo == 13) {
      printLine();
      Serial.println(F("                                                       Function 13 : Erase 64KB Block                                              "));
      printSplash();
      printLine();
      Serial.println(F("This function will erase a 64KB block."));
      Serial.print(F("Please enter the address you wish to erase: "));
      while (!Serial.available()) {
      }
      addr = Serial.parseInt();
      Serial.println(addr);
      if (flash.eraseBlock64K(addr)) {
        clearprintBuffer();
        sprintf(printBuffer, "A 64KB block containing address %d has been erased", addr);
        Serial.println(printBuffer);
        printReadChoice();
        while (!Serial.available()) {
        }
        uint8_t choice = Serial.parseInt();
        Serial.println(choice);
        if (choice == 1) {
          printOutputChoice();
          while (!Serial.available()) {
          }
          uint8_t outputType = Serial.parseInt();
          Serial.println(outputType);
          printPage(addr, outputType);
        }
      } else {
        Serial.println("Erasing block 64K failed");
      }
      printLine();
      printNextCMD();
    }
    else if (commandNo == 14) {
      printLine();
      Serial.println(F("                                                      Function 14 : Erase Chip                                                    "));
      printSplash();
      printLine();
      Serial.println(F("This function will erase the entire flash memory."));
      Serial.println(F("Do you wish to continue? (Y/N)"));
      char c;
      while (!Serial.available()) {
      }
      c = (char)Serial.read();
      if (c == 'Y' || c == 'y') {
        if (flash.eraseChip())
          Serial.println(F("Chip erased"));
        else
          Serial.println(F("Error erasing chip"));
      }
      printLine();
      printNextCMD();
    }
  }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

void clearprintBuffer()
{
  for (uint8_t i = 0; i < 128; i++) {
    printBuffer[i] = 0;
  }
}

//Reads a string from Serial
bool readSerialStr(String &inputStr) {
  if (!Serial)
    Serial.begin(115200);
  while (Serial.available()) {
    inputStr = Serial.readStringUntil('\n');
    return true;
  }
  return false;
}

//Prints hex/dec formatted data from page reads - for debugging
void _printPageBytes(uint8_t *data_buffer, uint8_t outputType) {
  char buffer[10];
  for (int a = 0; a < 16; ++a) {
    for (int b = 0; b < 16; ++b) {
      if (outputType == 1) {
        sprintf(buffer, "%02x", data_buffer[a * 16 + b]);
        Serial.print(buffer);
      }
      else if (outputType == 2) {
        uint8_t x = data_buffer[a * 16 + b];
        if (x < 10) Serial.print("0");
        if (x < 100) Serial.print("0");
        Serial.print(x);
        Serial.print(',');
      }
    }
    Serial.println();
  }
}

//Reads a page of data and prints it to Serial stream. Make sure the sizeOf(uint8_t data_buffer[]) == 256.
void printPage(uint32_t _address, uint8_t outputType) {
  if (!Serial)
    Serial.begin(115200);

  char buffer[24];
  sprintf(buffer, "Reading address 0x(%04x)", _address);
  Serial.println(buffer);

  uint8_t data_buffer[SPI_PAGESIZE];
  flash.readByteArray(_address, &data_buffer[0], SPI_PAGESIZE);
  _printPageBytes(data_buffer, outputType);
}

//Reads all pages on Flash chip and dumps it to Serial stream.
//This function is useful when extracting data from a flash chip onto a computer as a text file.
void printAllPages(uint8_t outputType) {
  if (!Serial)
    Serial.begin(115200);

  Serial.println("Reading all pages");
  uint8_t data_buffer[256];

  uint32_t maxAddr = flash.getCapacity();
  for (int a = 0; a < maxAddr; a++) {
    flash.readByteArray(a, &data_buffer[0], 256);
    _printPageBytes(data_buffer, outputType);
    delay(100);
  }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Print commands~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

void printLine()
{
  Serial.println(F("----------------------------------------------------------------------------------------------------------------------------------"));
}

void printSplash()
{
  Serial.println(F("                                                        SPIFlash library test                                                     "));
}

void printNextCMD()
{
  Serial.println(F("Please type the next command. Type 0 to get the list of commands"));
}

void printOutputChoice()
{
  Serial.print("Would you like your output in decimal or hexadecimal? Please indicate with '1' for HEX or '2' for DEC: ");
}

void printReadChoice()
{
  Serial.print("Type 1 to read the page you have just modified. Type 0 to continue: ");
}

void writeSuccess()
{
  Serial.println("Data write successful");
}

void writeFail()
{
  Serial.println("Data write failed");
}

void inputAddress(void) {
  Serial.print(F("Please enter the address (0 - CAPACITY) you wish to access: "));
  while (!Serial.available()) {
  }
  addr = Serial.parseInt();
  Serial.println(addr);
}