A re-written SDFileSystem library with improved compatibility, CRC support, and card removal/replacement support.

Dependencies:   FATFileSystem

Dependents:   xadow_m0_SD_Hello roam_v1 roam_v2 Polytech_tours ... more

Revision:
11:67ddc53e3983
Parent:
10:395539a1481a
Child:
12:eebddab6eff2
--- a/SDFileSystem.cpp	Tue Aug 12 14:58:07 2014 +0000
+++ b/SDFileSystem.cpp	Thu Aug 14 22:27:07 2014 +0000
@@ -72,10 +72,10 @@
     if (enabled && !m_Crc) {
         //Send CMD59(0x00000001) to enable CRC
         m_Crc = true;
-        writeCommand(CMD59, 0x00000001);
+        commandTransaction(CMD59, 0x00000001);
     } else if (!enabled && m_Crc) {
         //Send CMD59(0x00000000) to disable CRC
-        writeCommand(CMD59, 0x00000000);
+        commandTransaction(CMD59, 0x00000000);
         m_Crc = false;
     }
 }
@@ -92,9 +92,23 @@
     m_LargeFrames = enabled;
 }
 
+int SDFileSystem::unmount()
+{
+    //Unmount the filesystem
+    FATFileSystem::unmount();
+
+    //Change the status to not initialized, and the card type to none
+    m_Status |= STA_NOINIT;
+    m_CardType = CARD_NONE;
+
+    //Always succeeds
+    return 0;
+}
+
 int SDFileSystem::disk_initialize()
 {
-    char resp;
+    char token;
+    unsigned int resp;
 
     //Make sure there's a card in the socket before proceeding
     checkSocket();
@@ -114,26 +128,32 @@
         m_Spi.write(0xFF);
 
     //Write CMD0(0x00000000) to reset the card
-    resp = writeCommand(CMD0, 0x00000000);
-    if (resp != 0x01) {
+    if (commandTransaction(CMD0, 0x00000000) != 0x01) {
         //Initialization failed
         m_CardType = CARD_UNKNOWN;
         return m_Status;
     }
 
+    //Send CMD59(0x00000001) to enable CRC if necessary
+    if (m_Crc) {
+        if (commandTransaction(CMD59, 0x00000001) != 0x01) {
+            //Initialization failed
+            m_CardType = CARD_UNKNOWN;
+            return m_Status;
+        }
+    }
+
     //Write CMD8(0x000001AA) to see if this is an SDCv2 card
-    resp = writeCommand(CMD8, 0x000001AA);
-    if (resp == 0x01) {
+    if (commandTransaction(CMD8, 0x000001AA, &resp) == 0x01) {
         //This is an SDCv2 card, get the 32-bit return value and verify the voltage range/check pattern
-        if ((readReturn() & 0xFFF) != 0x1AA) {
+        if ((resp & 0xFFF) != 0x1AA) {
             //Initialization failed
             m_CardType = CARD_UNKNOWN;
             return m_Status;
         }
 
         //Send CMD58(0x00000000) to read the OCR, and verify that the card supports 3.2-3.3V
-        resp = writeCommand(CMD58, 0x00000000);
-        if (resp != 0x01 || !(readReturn() & (1 << 20))) {
+        if (commandTransaction(CMD58, 0x00000000, &resp) != 0x01 || !(resp & (1 << 20))) {
             //Initialization failed
             m_CardType = CARD_UNKNOWN;
             return m_Status;
@@ -141,24 +161,23 @@
 
         //Send ACMD41(0x40100000) repeatedly for up to 1 second to initialize the card
         for (int i = 0; i < 1000; i++) {
-            resp = writeCommand(ACMD41, 0x40100000);
-            if (resp != 0x01)
+            token = commandTransaction(ACMD41, 0x40100000);
+            if (token != 0x01)
                 break;
             wait_ms(1);
         }
 
         //Check if the card initialized
-        if (resp != 0x00) {
+        if (token != 0x00) {
             //Initialization failed
             m_CardType = CARD_UNKNOWN;
             return m_Status;
         }
 
         //Send CMD58(0x00000000) to read the OCR
-        resp = writeCommand(CMD58, 0x00000000);
-        if (resp == 0x00) {
+        if (commandTransaction(CMD58, 0x00000000, &resp) == 0x00) {
             //Check the CCS bit to determine if this is a high capacity card
-            if (readReturn() & (1 << 30))
+            if (resp & (1 << 30))
                 m_CardType = CARD_SDHC;
             else
                 m_CardType = CARD_SD;
@@ -170,8 +189,7 @@
     } else {
         //Didn't respond or illegal command, this is either an SDCv1 or MMC card
         //Send CMD58(0x00000000) to read the OCR, and verify that the card supports 3.2-3.3V
-        resp = writeCommand(CMD58, 0x00000000);
-        if (resp != 0x01 || !(readReturn() & (1 << 20))) {
+        if (commandTransaction(CMD58, 0x00000000, &resp) != 0x01 || !(resp & (1 << 20))) {
             //Initialization failed
             m_CardType = CARD_UNKNOWN;
             return m_Status;
@@ -179,27 +197,27 @@
 
         //Try to initialize the card using ACMD41(0x00100000) for 1 second
         for (int i = 0; i < 1000; i++) {
-            resp = writeCommand(ACMD41, 0x00100000);
-            if (resp != 0x01)
+            token = commandTransaction(ACMD41, 0x00100000);
+            if (token != 0x01)
                 break;
             wait_ms(1);
         }
 
         //Check if the card initialized
-        if (resp == 0x00) {
+        if (token == 0x00) {
             //This is an SDCv1 standard capacity card
             m_CardType = CARD_SD;
         } else {
             //Try to initialize the card using CMD1(0x00100000) for 1 second
             for (int i = 0; i < 1000; i++) {
-                resp = writeCommand(CMD1, 0x00100000);
-                if (resp != 0x01)
+                token = commandTransaction(CMD1, 0x00100000);
+                if (token != 0x01)
                     break;
                 wait_ms(1);
             }
 
             //Check if the card initialized
-            if (resp == 0x00) {
+            if (token == 0x00) {
                 //This is an MMCv3 card
                 m_CardType = CARD_MMC;
             } else {
@@ -210,20 +228,9 @@
         }
     }
 
-    //Send CMD59(0x00000001) to enable CRC if necessary
-    if (m_Crc) {
-        resp = writeCommand(CMD59, 0x00000001);
-        if (resp != 0x00) {
-            //Initialization failed
-            m_CardType = CARD_UNKNOWN;
-            return m_Status;
-        }
-    }
-
     //Send CMD16(0x00000200) to force the block size to 512B if necessary
     if (m_CardType != CARD_SDHC) {
-        resp = writeCommand(CMD16, 0x00000200);
-        if (resp != 0x00) {
+        if (commandTransaction(CMD16, 0x00000200) != 0x00) {
             //Initialization failed
             m_CardType = CARD_UNKNOWN;
             return m_Status;
@@ -232,8 +239,7 @@
 
     //Send ACMD42(0x00000000) to disconnect the internal pull-up resistor on pin 1 if necessary
     if (m_CardType != CARD_MMC) {
-        resp = writeCommand(ACMD42, 0x00000000);
-        if (resp != 0x00) {
+        if (commandTransaction(ACMD42, 0x00000000) != 0x00) {
             //Initialization failed
             m_CardType = CARD_UNKNOWN;
             return m_Status;
@@ -264,34 +270,26 @@
     return m_Status;
 }
 
-int SDFileSystem::disk_read(uint8_t* buffer, uint64_t sector)
+int SDFileSystem::disk_read(uint8_t* buffer, uint64_t sector, uint8_t count)
 {
     //Make sure the card is initialized before proceeding
     if (m_Status & STA_NOINIT)
         return RES_NOTRDY;
 
-    //Convert from LBA to a byte address for standard capacity cards
-    if (m_CardType != CARD_SDHC)
-        sector *= 512;
-
-    //Try to read the block up to 3 times
-    for (int i = 0; i < 3; i++) {
-        //Send CMD17(sector) to read a single block
-        if (writeCommand(CMD17, sector) == 0x00) {
-            //Try to read the sector, and return if successful
-            if (readData((char*)buffer, 512))
-                return RES_OK;
-        } else {
-            //The command failed
-            return RES_ERROR;
-        }
+    //Read a single block, or multiple blocks
+    if (count > 1) {
+        if (readBlocks((char*)buffer, sector, count))
+            return RES_OK;
+    } else {
+        if (readBlock((char*)buffer, sector))
+            return RES_OK;
     }
 
-    //The read operation failed 3 times
+    //The read operation failed
     return RES_ERROR;
 }
 
-int SDFileSystem::disk_write(const uint8_t* buffer, uint64_t sector)
+int SDFileSystem::disk_write(const uint8_t* buffer, uint64_t sector, uint8_t count)
 {
     //Make sure the card is initialized before proceeding
     if (m_Status & STA_NOINIT)
@@ -301,24 +299,16 @@
     if (m_Status & STA_PROTECT)
         return RES_WRPRT;
 
-    //Convert from LBA to a byte address for older cards
-    if (m_CardType != CARD_SDHC)
-        sector *= 512;
-
-    //Try to write the block up to 3 times
-    for (int i = 0; i < 3; i++) {
-        //Send CMD24(sector) to write a single block
-        if (writeCommand(CMD24, sector) == 0x00) {
-            //Try to write the sector, and return if successful
-            if (writeData((char*)buffer))
-                return RES_OK;
-        } else {
-            //The command failed
-            return RES_ERROR;
-        }
+    //Write a single block, or multiple blocks
+    if (count > 1) {
+        if(writeBlocks((const char*)buffer, sector, count))
+            return RES_OK;
+    } else {
+        if(writeBlock((const char*)buffer, sector))
+            return RES_OK;
     }
 
-    //The write operation failed 3 times
+    //The write operation failed
     return RES_ERROR;
 }
 
@@ -340,12 +330,18 @@
         return 0;
 
     //Try to read the CSD register up to 3 times
-    for (int i = 0; i < 3; i++) {
+    for (int f = 0; f < 3; f++) {
+        //Select the card, and wait for ready
+        if (!select())
+            break;
+
         //Send CMD9(0x00000000) to read the CSD register
-        if (writeCommand(CMD9, 0x00000000) == 0x00) {
-            //Receive the 16B CSD data
+        if (command(CMD9, 0x00000000) == 0x00) {
+            //Read the 16B CSD data block
             char csd[16];
-            if (readData(csd, 16)) {
+            bool success = readData(csd, 16);
+            deselect();
+            if (success) {
                 //Calculate the sector count based on the card type
                 if ((csd[0] >> 6) == 0x01) {
                     //Calculate the sector count for a high capacity card
@@ -360,12 +356,13 @@
                 }
             }
         } else {
-            //The command failed
-            return 0;
+            //The command failed, get out
+            break;
         }
     }
 
     //The read operation failed 3 times
+    deselect();
     return 0;
 }
 
@@ -418,27 +415,40 @@
     //Deassert /CS
     m_Cs = 1;
 
-    //Send 8 dummy clocks with DI held high to disable DO (will also initiate any internal write process)
+    //Send 8 dummy clocks with DI held high to disable DO
     m_Spi.write(0xFF);
 }
 
-char SDFileSystem::writeCommand(char cmd, unsigned int arg)
+inline char SDFileSystem::commandTransaction(char cmd, unsigned int arg, unsigned int* resp)
 {
-    char resp;
+    //Select the card, and wait for ready
+    if (!select())
+        return 0xFF;
+
+    //Perform the command transaction
+    char token = command(cmd, arg, resp);
+
+    //Deselect the card, and return the R1 response token
+    deselect();
+    return token;
+}
+
+char SDFileSystem::command(char cmd, unsigned int arg, unsigned int* resp)
+{
+    char token;
 
     //Try to send the command up to 3 times
-    for (int i = 0; i < 3; i++) {
+    for (int f = 0; f < 3; f++) {
         //Send CMD55(0x00000000) prior to an application specific command
-        if (cmd == ACMD41 || cmd == ACMD42) {
-            resp = writeCommand(CMD55, 0x00000000);
-            if (resp > 0x01)
-                return resp;
+        if (cmd == ACMD23 || cmd == ACMD41 || cmd == ACMD42) {
+            token = command(CMD55, 0x00000000);
+            if (token > 0x01)
+                return token;
+
+            //Some cards need a dummy byte between CMD55 and an ACMD
+            m_Spi.write(0xFF);
         }
 
-        //Select the card, and wait for ready
-        if (!select())
-            return 0xFF;
-
         //Prepare the command packet
         char cmdPacket[6];
         cmdPacket[0] = cmd;
@@ -452,44 +462,50 @@
             cmdPacket[5] = 0x01;
 
         //Send the command packet
-        for (int b = 0; b < 6; b++)
-            m_Spi.write(cmdPacket[b]);
+        for (int i = 0; i < 6; i++)
+            m_Spi.write(cmdPacket[i]);
 
-        //Allow up to 8 bytes of delay for the command response
-        for (int b = 0; b < 9; b++) {
-            resp = m_Spi.write(0xFF);
-            if (!(resp & 0x80))
+        //Discard the stuff byte immediately following CMD12
+        if (cmd == CMD12)
+            m_Spi.write(0xFF);
+
+        //Allow up to 8 bytes of delay for the R1 response token
+        for (int i = 0; i < 9; i++) {
+            token = m_Spi.write(0xFF);
+            if (!(token & 0x80))
                 break;
         }
 
-        //Deselect the card on errors, or if the transaction is finished
-        if (resp > 0x01 || !(cmd == CMD8 || cmd == CMD9 || cmd == CMD17 || cmd == CMD24 || cmd == CMD55 || cmd == CMD58))
-            deselect();
+        //Verify the R1 response token
+        if (token == 0xFF) {
+            //No data was received, get out early
+            break;
+        } else if (token & (1 << 3)) {
+            //There was a CRC error, try again
+            continue;
+        } else if (token > 0x01) {
+            //An error occured, get out early
+            break;
+        }
 
-        //Return the response unless there was a CRC error
-        if (resp == 0xFF || !(resp & (1 << 3)))
-            return resp;
+        //Handle R2 and R3/R7 response tokens
+        if (cmd == CMD13 && resp != NULL) {
+            //Read the R2 response value
+            *resp = m_Spi.write(0xFF);
+        } else if ((cmd == CMD8 || cmd == CMD58) && resp != NULL) {
+            //Read the R3/R7 response value
+            *resp = (m_Spi.write(0xFF) << 24);
+            *resp |= (m_Spi.write(0xFF) << 16);
+            *resp |= (m_Spi.write(0xFF) << 8);
+            *resp |= m_Spi.write(0xFF);
+        }
+
+        //The command was successful
+        break;
     }
 
-    //The command failed 3 times
-    return 0xFF;
-}
-
-unsigned int SDFileSystem::readReturn()
-{
-    unsigned int ret;
-
-    //Read the 32-bit response value
-    ret = (m_Spi.write(0xFF) << 24);
-    ret |= (m_Spi.write(0xFF) << 16);
-    ret |= (m_Spi.write(0xFF) << 8);
-    ret |= m_Spi.write(0xFF);
-
-    //Deselect the card
-    deselect();
-
-    //Return the response value
-    return ret;
+    //Return the R1 response token
+    return token;
 }
 
 bool SDFileSystem::readData(char* buffer, int length)
@@ -497,7 +513,7 @@
     char token;
     unsigned short crc;
 
-    //Wait for up to 200ms for the data token to arrive
+    //Wait for up to 200ms for the start block token to arrive
     for (int i = 0; i < 200; i++) {
         token = m_Spi.write(0xFF);
         if (token != 0xFF)
@@ -506,10 +522,8 @@
     }
 
     //Make sure the token is valid
-    if (token != 0xFE) {
-        deselect();
+    if (token != 0xFE)
         return false;
-    }
 
     //Check if large frames are enabled or not
     if (m_LargeFrames) {
@@ -539,36 +553,29 @@
         crc |= m_Spi.write(0xFF);
     }
 
-    //Deselect the card
-    deselect();
-
     //Return the validity of the CRC16 checksum (if enabled)
     return (!m_Crc || crc == CRC16(buffer, length));
 }
 
-bool SDFileSystem::writeData(const char* buffer)
+char SDFileSystem::writeData(const char* buffer, char token)
 {
-    //Wait for up to 500ms for the card to become ready
-    if (!waitReady(500)) {
-        //We timed out, deselect and indicate failure
-        deselect();
-        return false;
-    }
-
-    //Send the data token
-    m_Spi.write(0xFE);
-
     //Calculate the CRC16 checksum for the data block (if enabled)
     unsigned short crc = (m_Crc) ? CRC16(buffer, 512) : 0xFFFF;
 
+    //Wait for the card to become ready
+    while (!m_Spi.write(0xFF));
+
+    //Send the start block token
+    m_Spi.write(token);
+
     //Check if large frames are enabled or not
     if (m_LargeFrames) {
         //Switch to 16-bit frames for better performance
         m_Spi.format(16, 0);
 
         //Write the data block from the buffer
-        for (int b = 0; b < 512; b += 2)
-            m_Spi.write((buffer[b] << 8) | buffer[b + 1]);
+        for (int i = 0; i < 512; i += 2)
+            m_Spi.write((buffer[i] << 8) | buffer[i + 1]);
 
         //Send the CRC16 checksum for the data block
         m_Spi.write(crc);
@@ -577,20 +584,247 @@
         m_Spi.format(8, 0);
     } else {
         //Write the data block from the buffer
-        for (int b = 0; b < 512; b++)
-            m_Spi.write(buffer[b]);
+        for (int i = 0; i < 512; i++)
+            m_Spi.write(buffer[i]);
 
         //Send the CRC16 checksum for the data block
         m_Spi.write(crc >> 8);
         m_Spi.write(crc);
     }
 
-    //Receive the data response
-    char resp = m_Spi.write(0xFF);
+    //Return the data response token
+    return (m_Spi.write(0xFF) & 0x1F);
+}
+
+inline bool SDFileSystem::readBlock(char* buffer, unsigned long long lba)
+{
+    //Try to read the block up to 3 times
+    for (int f = 0; f < 3; f++) {
+        //Select the card, and wait for ready
+        if (!select())
+            break;
+
+        //Send CMD17(block) to read a single block
+        if (command(CMD17, (m_CardType == CARD_SDHC) ? lba : lba * 512) == 0x00) {
+            //Try to read the block, and deselect the card
+            bool success = readData(buffer, 512);
+            deselect();
+
+            //Return if successful
+            if (success)
+                return true;
+        } else {
+            //The command failed, get out
+            break;
+        }
+    }
+
+    //The single block read failed
+    deselect();
+    return false;
+}
+
+inline bool SDFileSystem::readBlocks(char* buffer, unsigned long long lba, int count)
+{
+    //Try to read each block up to 3 times
+    for (int f = 0; f < 3;) {
+        //Select the card, and wait for ready
+        if (!select())
+            break;
 
-    //Deselect the card (this will initiate the internal write process)
+        //Send CMD18(block) to read multiple blocks
+        if (command(CMD18, (m_CardType == CARD_SDHC) ? lba : lba * 512) == 0x00) {
+            //Try to read all of the data blocks
+            do {
+                //Read the next block and break on errors
+                if (!readData(buffer, 512)) {
+                    f++;
+                    break;
+                }
+
+                //Update the variables
+                lba++;
+                buffer += 512;
+                f = 0;
+            } while (--count);
+
+            //Send CMD12(0x00000000) to stop the transmission
+            if (command(CMD12, 0x00000000) != 0x00) {
+                //The command failed, get out
+                break;
+            }
+
+            //Only wait for CMD12 if the read was unsuccessful
+            if (count)
+                while (!m_Spi.write(0xFF));
+
+            //Deselect the card
+            deselect();
+
+            //Return if successful
+            if (count == 0)
+                return true;
+        } else {
+            //The command failed, get out
+            break;
+        }
+    }
+
+    //The multiple block read failed
     deselect();
+    return false;
+}
 
-    //Return success/failure
-    return ((resp & 0x1F) == 0x05);
+inline bool SDFileSystem::writeBlock(const char* buffer, unsigned long long lba)
+{
+    //Try to write the block up to 3 times
+    for (int f = 0; f < 3; f++) {
+        //Select the card, and wait for ready
+        if (!select())
+            break;
+
+        //Send CMD24(block) to write a single block
+        if (command(CMD24, (m_CardType == CARD_SDHC) ? lba : lba * 512) == 0x00) {
+            //Try to write the block, and deselect the card
+            char token = writeData(buffer, 0xFE);
+            deselect();
+
+            //Check the data response token
+            if (token == 0x0A) {
+                //A CRC error occured, try again
+                continue;
+            } else if (token == 0x0C) {
+                //A write error occured, get out
+                break;
+            }
+
+            //Send CMD13(0x00000000) to verify that the programming was successful
+            unsigned int resp;
+            if (commandTransaction(CMD13, 0x00000000, &resp) != 0x00 || resp != 0x00) {
+                //Some manner of unrecoverable write error occured during programming, get out
+                break;
+            }
+
+            //The data was written successfully
+            return true;
+        } else {
+            //The command failed, get out
+            break;
+        }
+    }
+
+    //The single block write failed
+    deselect();
+    return false;
 }
+
+inline bool SDFileSystem::writeBlocks(const char* buffer, unsigned long long lba, int count)
+{
+    char token;
+    const char* currentBuffer = buffer;
+    unsigned long long currentLba = lba;
+    int currentCount = count;
+
+    //Try to write each block up to 3 times
+    for (int f = 0; f < 3;) {
+        //If this is an SD card, send ACMD23(count) to set the number of blocks to pre-erase
+        if (m_CardType != CARD_MMC) {
+            if (commandTransaction(ACMD23, currentCount) != 0x00) {
+                //The command failed, get out
+                break;
+            }
+        }
+
+        //Select the card, and wait for ready
+        if (!select())
+            break;
+
+        //Send CMD25(block) to write multiple blocks
+        if (command(CMD25, (m_CardType == CARD_SDHC) ? currentLba : currentLba * 512) == 0x00) {
+            //Try to write all of the data blocks
+            do {
+                //Write the next block and break on errors
+                token = writeData(currentBuffer, 0xFC);
+                if (token != 0x05) {
+                    f++;
+                    break;
+                }
+
+                //Update the variables
+                currentBuffer += 512;
+                f = 0;
+            } while (--currentCount);
+
+            //Wait for the card to finish processing the last block
+            while (!m_Spi.write(0xFF));
+
+            //Finalize the transmission
+            if (currentCount == 0) {
+                //Send the stop tran token
+                m_Spi.write(0xFD);
+
+                //Wait for the programming to complete, and deselect the card
+                while (!m_Spi.write(0xFF));
+                deselect();
+
+                //Send CMD13(0x00000000) to verify that the programming was successful
+                unsigned int resp;
+                if (commandTransaction(CMD13, 0x00000000, &resp) != 0x00 || resp != 0x00) {
+                    //Some manner of unrecoverable write error occured during programming, get out
+                    break;
+                }
+
+                //The data was written successfully
+                return true;
+            } else {
+                //Send CMD12(0x00000000) to abort the transmission
+                if (command(CMD12, 0x00000000) != 0x00) {
+                    //The command failed, get out
+                    break;
+                }
+
+                //Wait for CMD12 to complete, and deselect the card
+                while (!m_Spi.write(0xFF));
+                deselect();
+
+                //Check the error token
+                if (token == 0x0A) {
+                    //Determine the number of well written blocks if possible
+                    unsigned int writtenBlocks = 0;
+                    if (m_CardType != CARD_MMC) {
+                        //Send ACMD22(0x00000000) to get the number of well written blocks
+                        if (commandTransaction(ACMD22, 0x00000000) == 0x00) {
+                            //Read the data
+                            char acmdData[4];
+                            if (readData(acmdData, 4)) {
+                                //Extract the number of well written blocks
+                                writtenBlocks = acmdData[0] << 24;
+                                writtenBlocks |= acmdData[1] << 16;
+                                writtenBlocks |= acmdData[2] << 8;
+                                writtenBlocks |= acmdData[3];
+                            }
+                        }
+                    }
+
+                    //Roll back the variables based on the number of well written blocks
+                    currentBuffer = buffer + (writtenBlocks * 512);
+                    currentLba = lba + writtenBlocks;
+                    currentCount = count - writtenBlocks;
+
+                    //Try again
+                    continue;
+                } else {
+                    //A write error occured, get out
+                    break;
+                }
+            }
+        } else {
+            //The command failed, get out
+            break;
+        }
+    }
+
+    //The multiple block write failed
+    deselect();
+    return false;
+}