//<App !Start!>
// FILE: [sd_card_formatter.ino]
// Created by GUIslice Builder version: [0.15.b004]
//
// GUIslice Builder Generated File
//
// For the latest guides, updates and support view:
// https://github.com/ImpulseAdventure/GUIslice
//
//<App !End!>

// ------------------------------------------------
// Headers to include
// ------------------------------------------------
//<Includes !Start!>
// Varous system includes
#include <Arduino.h>

// Various library includes
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <BBQ10Keyboard.h>
#include <CircularBuffer.h>

// Various local includes
#include "sd_card_formatter_GSLC.h"

//<Includes !End!>

// ------------------------------------------------
// Program Globals
// ------------------------------------------------

// Battery level measurement
#define VBATPIN A6
float measuredVBat;
int batteryPercent;

// TFT Setup
#define TFT_CS 9
#define TFT_DC 10
Adafruit_ILI9341 tft(TFT_CS, TFT_DC);

// Keyboard
BBQ10Keyboard keyboard;
#define KBD_BTN_1 0x06
#define KBD_BTN_2 0x11
#define KBD_BTN_3 0x07
#define KBD_BTN_4 0x12
#define KBD_SW_UP 0x01
#define KBD_SW_DN 0x02
#define KBD_SW_LF 0x03
#define KBD_SW_RT 0x04
#define KBD_SW_OK 0x05
CircularBuffer<int16_t,8> uiKeyBuffer;

// NeoPixels
#define PIXELS_NUM_BOARD 1
#define PIXELS_NUM_WING 1
#define PIXELS_WING_PIN 11
Adafruit_NeoPixel pixels_board(PIXELS_NUM_BOARD, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixels_wing(PIXELS_NUM_WING, PIXELS_WING_PIN, NEO_GRB + NEO_KHZ800);

// GUI state globals
bool popupOnScreen = false;

// Various loop handlers
void handlerKeyboard();
void handlerBatteryLevel();
void eraseAndFormatCard();
void sdError(char*);

// SD Card related
#define USE_SDIO 0
const uint8_t chipSelect = 5;
#define SPI_SPEED SD_SCK_MHZ(50)
#include <SPI.h>
#include "src/SdFat/SdFat.h"
#include "src/SdFat/sdios.h"
#if USE_SDIO
// Use faster SdioCardEX
SdioCardEX card;
// SdioCard card;
#else  // USE_SDIO
Sd2Card card;
#endif  // USE_SDIO
uint32_t cardSizeBlocks;
uint32_t cardCapacityMB;
// cache for SD block
cache_t cache;
// MBR information
uint8_t partType;
uint32_t relSector;
uint32_t partSize;
// Fake disk geometry
uint8_t numberOfHeads;
uint8_t sectorsPerTrack;
// FAT parameters
uint16_t reservedSectors;
uint8_t sectorsPerCluster;
uint32_t fatStart;
uint32_t fatSize;
uint32_t dataStart;
// constants for file system structure
uint16_t const BU16 = 128;
uint16_t const BU32 = 8192;
//  strings needed in file system structures
char noName[] = "NO NAME    ";
char fat16str[] = "FAT16   ";
char fat32str[] = "FAT32   ";

// Save some element references for direct access
//<Save_References !Start!>
gslc_tsElemRef* m_pElemBatteryLevel= NULL;
gslc_tsElemRef* m_pElemBtFmt      = NULL;
gslc_tsElemRef* m_pElemBtSDNo     = NULL;
gslc_tsElemRef* m_pElemBtSDYes    = NULL;
gslc_tsElemRef* m_pElemSDInfo     = NULL;
gslc_tsElemRef* m_pElemSDStatus   = NULL;
gslc_tsElemRef* m_pElemStatusText = NULL;
//<Save_References !End!>

// Keyboard map related
#define MAX_INPUT_MAP       5
gslc_tsInputMap             m_asInputMap[MAX_INPUT_MAP];

// Define debug message function
static int16_t DebugOut(char ch) { if (ch == (char)'\n') Serial.println(""); else Serial.write(ch); return 0; }

// ------------------------------------------------
// Callback Methods
// ------------------------------------------------
// Common Button callback
bool CbBtnCommon(void* pvGui,void *pvElemRef,gslc_teTouch eTouch,int16_t nX,int16_t nY)
{
  // Typecast the parameters to match the GUI and element types
  gslc_tsGui*     pGui     = (gslc_tsGui*)(pvGui);
  gslc_tsElemRef* pElemRef = (gslc_tsElemRef*)(pvElemRef);
  gslc_tsElem*    pElem    = gslc_GetElemFromRef(pGui,pElemRef);

  if ( eTouch == GSLC_TOUCH_UP_IN ) {
    // From the element's ID we can determine which button was pressed.
    switch (pElem->nId) {
//<Button Enums !Start!>
      case E_ELEM_BTN_SD_FMT:
        gslc_PopupShow(&m_gui, E_PG_SD_CONFIRM, false);
        popupOnScreen = true;
        break;
      case E_ELEM_SD_NO:
        gslc_PopupHide(&m_gui);
        popupOnScreen = false;
        break;
      case E_ELEM_SD_YES:
        gslc_PopupHide(&m_gui);
        popupOnScreen = false;
        gslc_ElemXTextboxReset(&m_gui, m_pElemSDStatus);
        gslc_ElemSetVisible(&m_gui, m_pElemBtFmt, false);
        gslc_ElemSetVisible(&m_gui, m_pElemSDInfo, false);
        gslc_ElemSetVisible(&m_gui, m_pElemSDStatus, true);
        gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "Formatting SD Card\n");
        gslc_Update(&m_gui);
        eraseAndFormatCard();
        gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "Formatting Complete\n");
        gslc_Update(&m_gui);
        break;
//<Button Enums !End!>
      default:
        break;
    }
  }
  return true;
}
//<Checkbox Callback !Start!>
//<Checkbox Callback !End!>
//<Keypad Callback !Start!>
//<Keypad Callback !End!>
//<Spinner Callback !Start!>
//<Spinner Callback !End!>
//<Listbox Callback !Start!>
//<Listbox Callback !End!>
//<Draw Callback !Start!>
//<Draw Callback !End!>
//<Slider Enums !Start!>
//<Slider Enums !End!>
//<Tick Callback !Start!>
//<Tick Callback !End!>

// Keyboard Input polling callback function
bool CbKbdPoll(void* pvGui, int16_t* pnPinInd, int16_t* pnPinVal) {
  // Check for new keyboard events just in case
  handlerKeyboard();

  // If the key buffer is empty no events to process
  if (uiKeyBuffer.isEmpty()) {
    return false;
  }

  // Process a key that's on the key buffer for the UI event(s)
  *pnPinInd = uiKeyBuffer.pop();
  *pnPinVal = 1;

  return true;
}

// ------------------------------------------------
// Setup
// ------------------------------------------------
void setup() {

  Serial.begin(115200);

  // If GUI Slice debug of drivers is turned on, wait for serial so we get proper debug output
  #if defined(DBG_LOG) || defined(DBG_TOUCH) || defined(DBG_FRAME_RATE) || defined(DBG_DRAW_IMM) || defined(DBG_DRIVER)
  while (!Serial) {
    ;
  }
  #endif

  // GUI Slice debugging
  gslc_InitDebug(&DebugOut);

  // Setup red LED to indicate device is on (in case we disable NeoPixel battery level later)
  pinMode(3, OUTPUT);
  digitalWrite(3, HIGH);

  // Setup NeoPixels
  pixels_board.begin();
  pixels_wing.begin();
  pixels_board.clear();
  pixels_wing.clear();
  pixels_board.setBrightness(10);
  pixels_wing.setBrightness(5);

  // Setup neopixels to indicate we are initializing gui slice
  //    This can serve as an indication something has gone wrong like the sd card is missing
  pixels_wing.setPixelColor(0, pixels_board.Color(255, 0, 0));
  pixels_board.setPixelColor(0, pixels_board.Color(255, 0, 0));
  pixels_wing.show();
  pixels_board.show();

  // Setup BBQ10Keyboard
  Wire.begin();
  keyboard.begin();
  keyboard.setBacklight(0.5f);

  // Init GUI Slicle
  InitGUIslice_gen();

  // Set the pin poll callback function
  gslc_SetPinPollFunc(&m_gui, CbKbdPoll);

  // Create the GUI input mapping (pin event to GUI action)
  gslc_InitInputMap(&m_gui, m_asInputMap, MAX_INPUT_MAP);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_UP, GSLC_ACTION_FOCUS_NEXT, 0);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_DN,  GSLC_ACTION_FOCUS_PREV, 0);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_LF, GSLC_ACTION_FOCUS_NEXT, 0);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_RT, GSLC_ACTION_FOCUS_PREV, 0);
  gslc_InputMapAdd(&m_gui, GSLC_INPUT_PIN_ASSERT, KBD_SW_OK, GSLC_ACTION_SELECT, 0);

  // Flip to main screen
  // This wouldn't be necessary if we hadn't chopped off the formatter from the main _controller project and actually updated the main screen ;)
  gslc_SetPageCur(&m_gui, E_SD_CARD);
  gslc_ElemSetVisible(&m_gui, m_pElemSDStatus, false);

  // Set neopixels to standard start state
  pixels_wing.clear();
  pixels_wing.show();

  pixels_board.setPixelColor(0, pixels_board.Color(0, 0, 255));
  pixels_board.show();

#if USE_SDIO
  if (!card.begin()) {
    sdError("card.begin failed");  
  }
#else  // USE_SDIO
  if (!card.begin(chipSelect, SPI_SPEED)) {
    sdError("card.begin failed");
  }
#endif  
  cardSizeBlocks = card.cardSize();
  if (cardSizeBlocks == 0) {
    sdError("cardSize");
  }
  cardCapacityMB = (cardSizeBlocks + 2047)/2048;

  char textForDisplay[128];
  snprintf(textForDisplay, 128, "Card Size: %.0fMb\n", 1.048576*cardCapacityMB);
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);

  char* type;
  switch (card.type()) {
    case SD_CARD_TYPE_SD1:
      type = "SD1";
      break;
    case SD_CARD_TYPE_SD2:
      type = "SD2";
      break;
    case SD_CARD_TYPE_SDHC:
      if (cardSizeBlocks < 70000000) {
        type = "SDHC";
      } else {
        type = "SDXC";
      }
      break;
    default:
      type = "Unknown";
  }
  snprintf(textForDisplay, 128, "Type: %s\n", type);
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);

  cid_t cid;
  if (!card.readCID(&cid)) {
    sdError("readCID failed");
  }
  snprintf(textForDisplay, 128, "Serial Number: %x\n", cid.psn);
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);
  snprintf(textForDisplay, 128, "Manufacturing date: %04d-%02d\n", (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high), int(cid.mdt_month));
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDInfo, textForDisplay);

  gslc_Update(&m_gui);
}

// -----------------------------------
// Main event loop
// -----------------------------------
void loop() {
  // Handle keyboard events
  handlerKeyboard();

  // Update battery level as appropriate
  handlerBatteryLevel();

  gslc_Update(&m_gui);
}

// ------------------------------------------------
// Keyboard Handler
// ------------------------------------------------
void handlerKeyboard() {
  // Don't do anything if there are no key presses to process
  if (keyboard.keyCount() == 0) {
    return;
  }

  // Check if keys were pressed and drain queue of pressed keys
  while (keyboard.keyCount() > 0) {
    // Get keyboard event
    const BBQ10Keyboard::KeyEvent key = keyboard.keyEvent();

    // Process key press events
    if (key.state == BBQ10Keyboard::StatePress) {
      // Add keys to the key buffer that are mapped to UI functions (only for config screens)
      if (key.key == KBD_SW_UP
              || key.key == KBD_SW_DN
              || key.key == KBD_SW_LF
              || key.key == KBD_SW_RT
              || key.key == KBD_SW_OK) {
        uiKeyBuffer.push(key.key);
      }
    }

    // Pick color for signaling a key was pressed (short or long press)
    uint32_t keyboard_pixel_color = pixels_wing.Color(0, 0, 255);
    if (key.state == BBQ10Keyboard::StateLongPress) {
      keyboard_pixel_color = pixels_wing.Color(0, 255, 255);
    }

    // Display indicator accordingly
    if (key.state == BBQ10Keyboard::StatePress || key.state == BBQ10Keyboard::StateLongPress) {
      pixels_wing.setPixelColor(0, keyboard_pixel_color);
      pixels_wing.show(); 
    } 
    else {
      pixels_wing.clear();
      pixels_wing.show();
    }
  }
}

// ------------------------------------------------
// Battery level handler
// ------------------------------------------------
void handlerBatteryLevel() {
  measuredVBat = analogRead(VBATPIN);
  measuredVBat *= 2;    // we divided by 2, so multiply back
  measuredVBat *= 3.3;  // Multiply by 3.3V, our reference voltage
  measuredVBat /= 1024; // convert to voltage
  batteryPercent = (measuredVBat / 3.3) * 100;

  gslc_tsColor colorForHeaderElements;
  if (batteryPercent >= 75) {
    pixels_board.setPixelColor(0, pixels_board.Color(0, 255, 0));
    colorForHeaderElements = GSLC_COL_GREEN;
  }
  else if (batteryPercent >= 50) {
    colorForHeaderElements = GSLC_COL_YELLOW;
    pixels_board.setPixelColor(0, pixels_board.Color(255, 255, 0));
  }
  else if (batteryPercent >= 25) {
    colorForHeaderElements = GSLC_COL_ORANGE;
    pixels_board.setPixelColor(0, pixels_board.Color(255, 128, 0));
  }
  else {
    colorForHeaderElements = GSLC_COL_RED;
    pixels_board.setPixelColor(0, pixels_board.Color(255, 0, 0));
  }

  gslc_ElemXProgressSetVal(&m_gui, m_pElemBatteryLevel, batteryPercent);
  gslc_tsXProgress*  pGauge  = (gslc_tsXProgress*)gslc_GetXDataFromRef(&m_gui,m_pElemBatteryLevel,GSLC_TYPEX_PROGRESS,__LINE__);
  pGauge->colGauge = colorForHeaderElements;
  gslc_ElemSetTxtCol(&m_gui, m_pElemStatusText, colorForHeaderElements);

  // Update GUI and NeoPixel with battery status information
  pixels_board.show();
}

// ------------------------------------------------
// Format SD Card Methods
// ------------------------------------------------

//------------------------------------------------------------------------------
void sdError(char* msg) {
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "error: ");
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, msg);
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "\n");
  gslc_Update(&m_gui);
  
  sdErrorHalt();
}
//------------------------------------------------------------------------------
void sdErrorHalt() {
  if (card.errorCode()) {
    char textForDisplay[32];
    snprintf(textForDisplay, 32, "SD error: %d\n", int(card.errorCode()));
    gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, textForDisplay);
    gslc_Update(&m_gui);
  }
  SysCall::halt();
}
//------------------------------------------------------------------------------
// write cached block to the card
uint8_t writeCache(uint32_t lbn) {
  return card.writeBlock(lbn, cache.data);
}
//------------------------------------------------------------------------------
// initialize appropriate sizes for SD capacity
void initSizes() {
  if (cardCapacityMB <= 6) {
    sdError("Card is too small.");
  } else if (cardCapacityMB <= 16) {
    sectorsPerCluster = 2;
  } else if (cardCapacityMB <= 32) {
    sectorsPerCluster = 4;
  } else if (cardCapacityMB <= 64) {
    sectorsPerCluster = 8;
  } else if (cardCapacityMB <= 128) {
    sectorsPerCluster = 16;
  } else if (cardCapacityMB <= 1024) {
    sectorsPerCluster = 32;
  } else if (cardCapacityMB <= 32768) {
    sectorsPerCluster = 64;
  } else {
    // SDXC cards
    sectorsPerCluster = 128;
  }

  char textForDisplay[64];
  snprintf(textForDisplay, 64, "Blocks/Cluster: %d\n", int(sectorsPerCluster));
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, textForDisplay);
  gslc_Update(&m_gui);

  // set fake disk geometry
  sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63;

  if (cardCapacityMB <= 16) {
    numberOfHeads = 2;
  } else if (cardCapacityMB <= 32) {
    numberOfHeads = 4;
  } else if (cardCapacityMB <= 128) {
    numberOfHeads = 8;
  } else if (cardCapacityMB <= 504) {
    numberOfHeads = 16;
  } else if (cardCapacityMB <= 1008) {
    numberOfHeads = 32;
  } else if (cardCapacityMB <= 2016) {
    numberOfHeads = 64;
  } else if (cardCapacityMB <= 4032) {
    numberOfHeads = 128;
  } else {
    numberOfHeads = 255;
  }
}
//------------------------------------------------------------------------------
// zero cache and optionally set the sector signature
void clearCache(uint8_t addSig) {
  memset(&cache, 0, sizeof(cache));
  if (addSig) {
    cache.mbr.mbrSig0 = BOOTSIG0;
    cache.mbr.mbrSig1 = BOOTSIG1;
  }
}
//------------------------------------------------------------------------------
// zero FAT and root dir area on SD
void clearFatDir(uint32_t bgn, uint32_t count) {
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "Clear FAT/DIR\n    (This may take awhile)\n");
  gslc_Update(&m_gui);
  clearCache(false);
#if USE_SDIO
  for (uint32_t i = 0; i < count; i++) {
    if (!card.writeBlock(bgn + i, cache.data)) {
       sdError("Clear FAT/DIR writeBlock failed");
    }     
  }
#else  // USE_SDIO
  if (!card.writeStart(bgn, count)) {
    sdError("Clear FAT/DIR writeStart failed");
  }
  for (uint32_t i = 0; i < count; i++) {
    if (!card.writeData(cache.data)) {
      sdError("Clear FAT/DIR writeData failed");
    }
  }
  if (!card.writeStop()) {
    sdError("Clear FAT/DIR writeStop failed");
  }
#endif  // USE_SDIO
}
//------------------------------------------------------------------------------
// return cylinder number for a logical block number
uint16_t lbnToCylinder(uint32_t lbn) {
  return lbn / (numberOfHeads * sectorsPerTrack);
}
//------------------------------------------------------------------------------
// return head number for a logical block number
uint8_t lbnToHead(uint32_t lbn) {
  return (lbn % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack;
}
//------------------------------------------------------------------------------
// return sector number for a logical block number
uint8_t lbnToSector(uint32_t lbn) {
  return (lbn % sectorsPerTrack) + 1;
}
//------------------------------------------------------------------------------
// format and write the Master Boot Record
void writeMbr() {
  clearCache(true);
  part_t* p = cache.mbr.part;
  p->boot = 0;
  uint16_t c = lbnToCylinder(relSector);
  if (c > 1023) {
    sdError("MBR CHS");
  }
  p->beginCylinderHigh = c >> 8;
  p->beginCylinderLow = c & 0XFF;
  p->beginHead = lbnToHead(relSector);
  p->beginSector = lbnToSector(relSector);
  p->type = partType;
  uint32_t endLbn = relSector + partSize - 1;
  c = lbnToCylinder(endLbn);
  if (c <= 1023) {
    p->endCylinderHigh = c >> 8;
    p->endCylinderLow = c & 0XFF;
    p->endHead = lbnToHead(endLbn);
    p->endSector = lbnToSector(endLbn);
  } else {
    // Too big flag, c = 1023, h = 254, s = 63
    p->endCylinderHigh = 3;
    p->endCylinderLow = 255;
    p->endHead = 254;
    p->endSector = 63;
  }
  p->firstSector = relSector;
  p->totalSectors = partSize;
  if (!writeCache(0)) {
    sdError("write MBR");
  }
}
//------------------------------------------------------------------------------
// generate serial number from card size and micros since boot
uint32_t volSerialNumber() {
  return (cardSizeBlocks << 8) + micros();
}
//------------------------------------------------------------------------------
// format the SD as FAT16
void makeFat16() {
  uint32_t nc;
  for (dataStart = 2 * BU16;; dataStart += BU16) {
    nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
    fatSize = (nc + 2 + 255)/256;
    uint32_t r = BU16 + 1 + 2 * fatSize + 32;
    if (dataStart < r) {
      continue;
    }
    relSector = dataStart - r + BU16;
    break;
  }
  // check valid cluster count for FAT16 volume
  if (nc < 4085 || nc >= 65525) {
    sdError("Bad cluster count");
  }
  reservedSectors = 1;
  fatStart = relSector + reservedSectors;
  partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32;
  if (partSize < 32680) {
    partType = 0X01;
  } else if (partSize < 65536) {
    partType = 0X04;
  } else {
    partType = 0X06;
  }
  // write MBR
  writeMbr();
  clearCache(true);
  fat_boot_t* pb = &cache.fbs;
  pb->jump[0] = 0XEB;
  pb->jump[1] = 0X00;
  pb->jump[2] = 0X90;
  for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
    pb->oemId[i] = ' ';
  }
  pb->bytesPerSector = 512;
  pb->sectorsPerCluster = sectorsPerCluster;
  pb->reservedSectorCount = reservedSectors;
  pb->fatCount = 2;
  pb->rootDirEntryCount = 512;
  pb->mediaType = 0XF8;
  pb->sectorsPerFat16 = fatSize;
  pb->sectorsPerTrack = sectorsPerTrack;
  pb->headCount = numberOfHeads;
  pb->hidddenSectors = relSector;
  pb->totalSectors32 = partSize;
  pb->driveNumber = 0X80;
  pb->bootSignature = EXTENDED_BOOT_SIG;
  pb->volumeSerialNumber = volSerialNumber();
  memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
  memcpy(pb->fileSystemType, fat16str, sizeof(pb->fileSystemType));
  // write partition boot sector
  if (!writeCache(relSector)) {
    sdError("FAT16 write PBS failed");
  }
  // clear FAT and root directory
  clearFatDir(fatStart, dataStart - fatStart);
  clearCache(false);
  cache.fat16[0] = 0XFFF8;
  cache.fat16[1] = 0XFFFF;
  // write first block of FAT and backup for reserved clusters
  if (!writeCache(fatStart)
      || !writeCache(fatStart + fatSize)) {
    sdError("FAT16 reserve failed");
  }
}
//------------------------------------------------------------------------------
// format the SD as FAT32
void makeFat32() {
  uint32_t nc;
  relSector = BU32;
  for (dataStart = 2 * BU32;; dataStart += BU32) {
    nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
    fatSize = (nc + 2 + 127)/128;
    uint32_t r = relSector + 9 + 2 * fatSize;
    if (dataStart >= r) {
      break;
    }
  }
  // error if too few clusters in FAT32 volume
  if (nc < 65525) {
    sdError("Bad cluster count");
  }
  reservedSectors = dataStart - relSector - 2 * fatSize;
  fatStart = relSector + reservedSectors;
  partSize = nc * sectorsPerCluster + dataStart - relSector;
  // type depends on address of end sector
  // max CHS has lbn = 16450560 = 1024*255*63
  if ((relSector + partSize) <= 16450560) {
    // FAT32
    partType = 0X0B;
  } else {
    // FAT32 with INT 13
    partType = 0X0C;
  }
  writeMbr();
  clearCache(true);

  fat32_boot_t* pb = &cache.fbs32;
  pb->jump[0] = 0XEB;
  pb->jump[1] = 0X00;
  pb->jump[2] = 0X90;
  for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
    pb->oemId[i] = ' ';
  }
  pb->bytesPerSector = 512;
  pb->sectorsPerCluster = sectorsPerCluster;
  pb->reservedSectorCount = reservedSectors;
  pb->fatCount = 2;
  pb->mediaType = 0XF8;
  pb->sectorsPerTrack = sectorsPerTrack;
  pb->headCount = numberOfHeads;
  pb->hidddenSectors = relSector;
  pb->totalSectors32 = partSize;
  pb->sectorsPerFat32 = fatSize;
  pb->fat32RootCluster = 2;
  pb->fat32FSInfo = 1;
  pb->fat32BackBootBlock = 6;
  pb->driveNumber = 0X80;
  pb->bootSignature = EXTENDED_BOOT_SIG;
  pb->volumeSerialNumber = volSerialNumber();
  memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
  memcpy(pb->fileSystemType, fat32str, sizeof(pb->fileSystemType));
  // write partition boot sector and backup
  if (!writeCache(relSector)
      || !writeCache(relSector + 6)) {
    sdError("FAT32 write PBS failed");
  }
  clearCache(true);
  // write extra boot area and backup
  if (!writeCache(relSector + 2)
      || !writeCache(relSector + 8)) {
    sdError("FAT32 PBS ext failed");
  }
  fat32_fsinfo_t* pf = &cache.fsinfo;
  pf->leadSignature = FSINFO_LEAD_SIG;
  pf->structSignature = FSINFO_STRUCT_SIG;
  pf->freeCount = 0XFFFFFFFF;
  pf->nextFree = 0XFFFFFFFF;
  // write FSINFO sector and backup
  if (!writeCache(relSector + 1)
      || !writeCache(relSector + 7)) {
    sdError("FAT32 FSINFO failed");
  }
  clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster);
  clearCache(false);
  cache.fat32[0] = 0x0FFFFFF8;
  cache.fat32[1] = 0x0FFFFFFF;
  cache.fat32[2] = 0x0FFFFFFF;
  // write first block of FAT and backup for reserved clusters
  if (!writeCache(fatStart)
      || !writeCache(fatStart + fatSize)) {
    sdError("FAT32 reserve failed");
  }
}
//------------------------------------------------------------------------------
// flash erase all data
uint32_t const ERASE_SIZE = 262144L;
void eraseCard() {
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "Erasing\n");
  gslc_Update(&m_gui);
  uint32_t firstBlock = 0;
  uint32_t lastBlock;
  uint16_t n = 0;

  do {
    lastBlock = firstBlock + ERASE_SIZE - 1;
    if (lastBlock >= cardSizeBlocks) {
      lastBlock = cardSizeBlocks - 1;
    }
    if (!card.erase(firstBlock, lastBlock)) {
      sdError("erase failed");
    }
    gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, ".");
    gslc_Update(&m_gui);
    if ((n++)%32 == 31) {
      gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "\n");
      gslc_Update(&m_gui);
    }
    firstBlock += ERASE_SIZE;
  } while (firstBlock < cardSizeBlocks);
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "\n");
  gslc_Update(&m_gui);

  if (!card.readBlock(0, cache.data)) {
    sdError("readBlock");
  }
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "Erase done\n");
  gslc_Update(&m_gui);
}
//------------------------------------------------------------------------------
void formatCard() {
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "Formatting\n");
  gslc_Update(&m_gui);
  initSizes();
  if (card.type() != SD_CARD_TYPE_SDHC) {
    gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "FAT16\n");
    gslc_Update(&m_gui);
    makeFat16();
  } else {
    gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "FAT32\n");
    gslc_Update(&m_gui);
    makeFat32();
  }
#if DEBUG_PRINT
  debugPrint();
#endif  // DEBUG_PRINT
  gslc_ElemXTextboxAdd(&m_gui, m_pElemSDStatus, "Format done\n");
  gslc_Update(&m_gui);
}
//------------------------------------------------------------------------------
void eraseAndFormatCard() {
  eraseCard();
  formatCard();
}
//------------------------------------------------------------------------------
