SDFileSystemVersion2

This is an updated version of the SDFileSystem library module supplied with the compiler. It contains the following fixes and additions:

  • SPI routines changed to allow for silicon errors for some PICS - thanks to Tom Estes.
  • InitSequence altered to recognise inserted card more quickly - SeekResponse for Cmd 1 reduced To $FF.
  • SPI altered to use SSPIF rather than SSPStatus thanks to SteveB on forum - http://www.sfcompiler.co.uk/forum/viewtopic.php?t=158.
  • SSPSTAT.7 (SMP) changed to 0 - sample at middle.
  • Calculation of last cluster corrected - effects writing of files when disk full, as well as DiskSizeKB and FreeSpaceKB - previous version overestimated size and free space by one cluster for some cards.
  • Correction to AppendFile and CloseFile to avoid errors when appending to files of zero length and closing them.
  • Added OpenFileRW and ability to read and write at file pointer, when file opened with NewFile, OpenFileRW and AppendFile - can use new command FSeek to move file pointer and FilePtr to locate position of file pointer.
  • DiskMounted altered to return False only if disk not present or Init not previously called - a read/write error no longer causes a False return.

It is suggested that this library is placed in the UserLibrary directory. It should also be noted that the revisions are still at the BETA stage. If any problems are found, please raise them via the forum.

Steven Wright

Sample Code

Device = 18F452
Clock =  40
Config OSC = HSPLL

#option SD_SPI = MSSP
#option SD_SUPPORT_SUB_DIRECTORIES = False
Include "SDFileSystem.bas"
Include "USART.bas"
Include "Convert.bas"

Dim DoNotRemoveLED As PORTD.0

Dim XYSet As LongWord

Sub WriteFooter()
   SD.Write("    </Coordinates>", 13, 10)
   SD.Write("  </Document>", 13, 10)
   SD.Write("</kml>", 13, 10)
End Sub

Output(DoNotRemoveLED) 
DelayMS(100)

USART.SetBaudrate(br38400)

USART.Write("Insert SD/MMC:", 13, 10)
Repeat
Until SD.Init(spiOscDiv4)

DoNotRemoveLED = 1

USART.Write("Formatting:", 13, 10)
SD.QuickFormat

USART.Write("WRITING HEADER & FOOTER...", 13, 10)
SD.NewFile("TESTFILE.XML")
SD.Write("<?xml version=", 34, "1.0", 34, " encoding=", 34, "UTF-8", 34, "?>", 13, 10)
SD.Write("<kml>", 13, 10)
SD.Write("  <Document>", 13, 10)
SD.Write("    <Coordinates>", 13, 10)
WriteFooter()
USART.Write("HEADER & FOOTER WRITTEN", 13, 10)
USART.Write("CLOSING FILE...", 13, 10)
SD.CloseFile
USART.Write("FILE CLOSED", 13, 10)

USART.Write("OPENING FILE...", 13, 10)
SD.OpenFileRW("TESTFILE.XML")
USART.Write("FILE OPENED", 13, 10)

DoNotRemoveLED = 0

XYSet = 0

Repeat
   If XYSet Mod 100 = 0 Then
      DelayMS(3000)
   EndIf
   DoNotRemoveLED = 1
   SD.FSeek(SD.FileSize - 41) 
   SD.Write("      ", DecToStr(XYSet), ",", DecToStr(XYSet), 13, 10)
   WriteFooter()
   SD.SaveFile
   DoNotRemoveLED = 0
   Inc(XYSet)
Until False

SDFileSystem Version 2.0 Module

{
********************************************************************************
*  Name    : SDFileSystem.BAS                                                  *
*  Author  : S Wright                                                          *
*  Notice  : Copyright (c) 2006 S Wright                                       *
*  Date    : 01/11/2007                                                        *
*  Version : 2.0.1                                                             *
*  Notes   : Module for reading and writing from and to SD or MMC cards        *
*          : using the FAT16 file system                                       *
*          :                                                                   *
*  Uses    : FSR0, FSR1                                                        *
*          :                                                                   *
*  License : Permission is hereby granted, free of charge, To any person       *
*          : obtaining a copy of this library and associated documentation     *
*          : files (the "Software"), to deal in the Software without           *
*          : restriction, including without limitation the rights to use,      *
*          : copy, modify, merge, publish, distribute, sublicense, and/or sell *
*          : copies of the Software, and to permit persons to whom the         *
*          : Software is furnished to do so, subject to the following          *
*          : conditions:                                                       *
*          :                                                                   *
*          : The above copyright notice and this permission notice shall be    *
*          : included in all copies or substantial portions of the Software.   *
*          :                                                                   *
*          : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,   *
*          : EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES   *
*          : OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND          *
*          : NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT       *
*          : HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,      *
*          : WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      *
*          : FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR     *
*          : OTHER DEALINGS IN THE SOFTWARE.                                   *
*          :                                                                   *
*  Revs    : 1.0.2 13/04/07 - SPI routines changed to allow for silicon        *
*          : errors for some PICS - thanks to Tom Estes                        *
*          : 1.0.3 11/09/07 - InitSequence altered to recognise inserted card  *
*          : more quickly - SeekResponse for Cmd 1 reduced To $FF              *
*          : 1.0.4 22/09/07 - SPI altered to use SSPIF rather than SSPStatus - *
*          : thanks to SteveB on forum - www.sfcompiler.co.uk/forum/           *
*          : viewtopic.php?t=158                                               *
*          : 1.0.5 23/09/07 - SSPSTAT.7 (SMP) changed to 0 - sample at middle  *
*          : 1.0.6 21/10/07 - Calculation of last cluster corrected - effects  *
*          : writing of files when disk full, as well as DiskSizeKB and        *
*          : FreeSpaceKB - previous version overestimated size and free space  *
*          : by one cluster for some cards                                     *
*          : 1.0.7 23/10/07 - Correction to AppendFile and CloseFile to avoid  *
*          : errors when appending to files of zero length and closing them    *
*          : 2.0.0 24/10/07 - Added OpenFileRW and ability to read and write   *
*          : at file pointer, when file opened with NewFile, OpenFileRW and    *
*          : AppendFile - can use new command FSeek to move file pointer and   *
*          : FilePtr to locate position of file pointer                        *
*          : 2.0.1 27/10/07 - DiskMounted altered to return False only if disk *
*          : not present or Init not previously called - a read/write error    *
*          : no longer causes a False return                                   *
*          :                                                                   *
********************************************************************************
}
Module SD

Include "System.bas"

// Default module options - user options can override these values...
#option SD_CS = PORTC.2       // SPI CS To SD CS (SD pin 1)
#option SD_DI = PORTC.5       // SPI SDO to SD DI (SD Pin 2)
#option SD_CLK = PORTC.3      // SPI SCK To SD CLK (SD Pin 5)
#option SD_DO = PORTC.4       // SPI SDI To SD DO (SD Pin 7)
#option SD_SPI = MSSP
#option SD_SUPPORT_SUB_DIRECTORIES = TRUE

#if IsOption(SD_SPI) And Not (SD_SPI in (SW, MSSP))
   #error SD_SPI, "Invalid option. SPI type not recognized."
#endif

#if IsOption(SD_SUPPORT_SUB_DIRECTORIES) And Not (SD_SUPPORT_SUB_DIRECTORIES in (TRUE, FALSE))
   #error SD_SUPPORT_SUB_DIRECTORIES, "Invalid option. SD_SUPPORT_SUB_DIRECTORIES must be TRUE or FALSE."
#endif

#if IsOption(SD_SPI) And SD_SPI in (MSSP)
   // Map registers to SSP
   #if _mssp = 0
      #error _device + " does not support MSSP."
   // Single SSP...
   #elseif _mssp = 1
   Dim
      SSPControl1 As SSPCON1,
      SSPStatus   As SSPSTAT,
      SSPBuffer   As SSPBUF,
      SSPIF       As PIR1.3
   // Has more than one SSP module...   
   #else
   Dim                         
      SSPControl1 As SSP1CON1,   // Map to MSSP1
      SSPStatus   As SSP1STAT,
      SSPBuffer   As SSP1BUF
      SSPIF       As PIR1.3
   #endif
#endif

// Validate CS pin...
#if IsOption(SD_CS) And Not IsValidPortPin(SD_CS) 
   #error SD_CS, "Invalid option. CS must be a valid port pin."
#endif

// Validate DI pin...
#if IsOption(SD_DI) And Not IsValidPortPin(SD_DI) 
   #error SD_DI, "Invalid option. DI must be a valid port pin."
#endif

// Validate CLK pin...
#if IsOption(SD_CLK) And Not IsValidPortPin(SD_CLK) 
   #error SD_CLK, "Invalid option. CLK must be a valid port pin."
#endif

// Validate DO pin...
#if IsOption(SD_DO) And Not IsValidPortPin(SD_DO) 
   #error SD_DO, "Invalid option. DO must be a valid port pin."
#endif

Public Const                           
   // MSSP clock speeds...
   spiOscDiv64  = %10,                    // SPI master mode, clock = Fosc/64
   spiOscDiv16  = %01,                    // SPI master mode, clock = Fosc/16
   spiOscDiv4   = %00,                    // SPI master mode, clock = Fosc/04
   // Dir search & Rename types...                    
   sdFile      = 0,
   sdDirectory = 1,
   // Dir search directions...                    
   dirFirst     = 0,
   dirNext      = 1,
   dirPrevious  = 2,
   // ChDir commands...                    
   cdDotDot     = 0,                      // Change to parent directory
   cdUp         = 0,                      // Change to parent directory
   cdBS         = 1,                      // Change to Root Directory
   cdRoot       = 1,                      // Change to Root Directory
   // Error codes...                    
   errOK            = $00,
   errDiskFull      = $01,
   errNotFound      = $02,
   errRootDirFull   = $03,
   errDirNotEmpty   = $04,
   errExists        = $05,
   errInUse         = $06,
   errRWError       = $07,
   errFileNotOpen   = $08,
   errBeyondEOF     = $09

Dim        
   CS As SD_CS.SD_CS@,
   DI As SD_DI.SD_DI@,
   CLK As SD_CLK.SD_CLK@,
   DO As SD_DO.SD_DO@

Structure TName
   Name As String(9)
   Extension As String(4)
End Structure

Public Type TSDName = TName

Structure TFile
   SFR As Byte
   DirectoryStartSector As LongWord
   IsRootDirectory As SFR.Booleans(0)
   FileEntryDirectorySector As LongWord
   FileEntryDirectorySectorPos As Word
   FirstCluster As Word
   FATSector As LongWord
   FATSectorPos As Word
   ClusterSequenceIndex As Word
   ClusterSequenceNumber As Word
   CurrentSectorInCluster As Byte
   CurrentSector As LongWord
   CurrentSectorPos As Word
   CurrentSectorBuffer($200) As Byte
   Size As LongWord
   Number As Word
   BytesRead As LongWord
   EOF As SFR.Booleans(1)
   DirFileDir As SFR.Bits(2)
   IsOpen As SFR.Booleans(3)
   RW As SFR.Bits(4)   // 0 = Write, 1 = Read
End Structure   

Structure TDisk
   SectorsPerCluster As Byte
   FAT1 As LongWord
   FAT2 As LongWord
   SectorsPerFAT As Word
   RootDirectory As LongWord
   SectorsInRoot As Word
   Data As LongWord
   LastCluster As Word
   SFR As Byte
   DiskFull As SFR.Booleans(0)
   RWError As SFR.Booleans(1)
   IsDirty As SFR.Booleans(2)
End Structure

Dim Disk As TDisk
Dim File As TFile  


{
********************************************************************************
* Name    : SendByte (PRIVATE)                                                 *
* Purpose : Send a byte to card using SPI interface.                           *
*         : Either software Or hardware SPI can be used by selecting           *
*         : #option SD_SPI = SW or #option SD_SPI = MSSP                       *
********************************************************************************
}   
#if SD_SPI = SW                  // Software SPI version
Sub SendByte(pByte As Byte)
Dim Shift As Byte 
   Shift = 8                     // Write 8 bits...
   Repeat
      CLK = 0
      DI = pByte.Bits(7)
      pByte = pByte << 1
      Dec(Shift)
      CLK = 1
   Until Shift = 0
   CLK = 0                       // Clock idles low...
   ClrWDT
End Sub
#elseif SD_SPI = MSSP            // MSSP version
Sub SendByte(pByte As WREG)
   SSPIF = 0 
   SSPBuffer = pByte 
   Repeat 
      ClrWDT 
   Until SSPIF = 1 
End Sub
#endif
{
********************************************************************************
* Name    : SendCmd (PRIVATE)                                                  *
* Purpose : Send a command to card                                             *
********************************************************************************
}   
Compound Sub SendCmd(SendByte)
{
********************************************************************************
* Name    : ReceiveByte (PRIVATE)                                              *
* Purpose : Receive a byte from card using SPI interface.                      *
*         : Either software or hardware SPI can be used by selecting           *
*         : #option SD_SPI = SW Or #option SD_SPI = MSSP                       *
********************************************************************************
}   
#if SD_SPI = SW                        // Software SPI version
Function ReceiveByte() As Byte
Dim Shift As Byte 
   ReceiveByte = 0
   Shift = 8
   Repeat
      ReceiveByte = ReceiveByte << 1   // Shift in...
      ReceiveByte.0 = DO               // Sample before clock...
      CLK = 1
      CLK = 0
      Dec(Shift)
   Until Shift = 0
   ClrWDT
End Function
#elseif SD_SPI = MSSP                  // MSSP version
Function ReceiveByte() As SSPBuffer         
   SSPIF = 0 
   SSPBuffer = $FF 
   Repeat 
      ClrWDT 
   Until SSPIF = 1 
End Function
#endif
{
********************************************************************************
* Name    : SeekResponse (PRIVATE)                                             *
* Purpose : Wait for a specified response from card.                           *
*         : Return: 0 = Response obtained, 1 = Timeout                         *
********************************************************************************
}   
Function SeekResponse(pRequired As Byte, pIndex As Word = $FFFF) As Byte
Dim Response As Byte
   Repeat
      Response = ReceiveByte()
      Dec(pIndex)
   Until Response = pRequired Or pIndex = 0
   If pIndex = 0 Then
      SeekResponse = 1
   Else
      SeekResponse = 0
   EndIf
End Function                                                           
{
********************************************************************************
* Name    : ReadSector (PRIVATE)                                               *
* Purpose : Read a full sector from card to buffer.                            *
********************************************************************************
}   
Sub ReadSector(pSector As LongWord, pBuffer As Boolean = True)
Dim Address As LongWord
Dim Response, TimeOut, Error As Byte
Dim Index As Word
   If (Disk.RWError = True And pBuffer) Then     // If RW error has occurred, prevent further reading from 
                                                 // disk until reinitialised, unless not buffering
                                                 // (pBuffer = False, used for DiskMounted)
      Exit
   EndIf
   TimeOut = $00
   Error = 1
   Repeat
      Address = pSector << 9 
      CS = 0                                     // Send Cmd 17
      SendCmd($51, Address.Byte3, Address.Byte2, Address.Byte1, Address.Byte0, $FF)
      Response = SeekResponse($00)
      If Response = 0 Then
         Response = SeekResponse($FE)            // Read start token
         If Response = 0 Then
            Index = 0
            If pBuffer Then
               FSR0 = @File.CurrentSectorBuffer
               Repeat                            // Read data block
                  POSTINC0 = ReceiveByte()
                  Inc(Index)
               Until Index = $200
            Else
               Repeat                            // Don't read data block
                  ReceiveByte()
                  Inc(Index)
               Until Index = $200
            EndIf
            ReceiveByte()                        // Read dummy CRC to conclude data block
            ReceiveByte()
            Error = 0                            // Read completed successfully
         EndIf   
      EndIf      
      CS = 1
      SendByte($FF)                              // Clock SD/MMC to complete read
      Inc(TimeOut)
   Until Error = 0 Or TimeOut > $01
   If Error = 1 Then
      Disk.RWError = True                        // Set RWError flag - can only be cleared using Init()
   EndIf
End Sub                                                                
{
********************************************************************************
* Name    : SectorInitRead (PRIVATE)                                           *
* Purpose : Initialise sector for reading.                                     *
********************************************************************************
}   
Sub SectorInitRead(pSector As LongWord)
   File.CurrentSectorPos = 0               
   ReadSector(pSector)
End Sub         
{
********************************************************************************
* Name    : SectorInitWrite (PRIVATE)                                          *
* Purpose : Initialise sector for writing.                                     *
********************************************************************************
}   
Sub SectorInitWrite()
   File.CurrentSectorPos = 0               
   Clear(File.CurrentSectorBuffer)
End Sub         
{
********************************************************************************
* Name    : WriteSector (PRIVATE)                                              *
* Purpose : Write a full sector from buffer to card                            *
********************************************************************************
}   
Sub WriteSector(pSector As LongWord)
Dim Address As LongWord
Dim Response1 As Byte
Dim Response2 As Byte
Dim Index As Word 
Dim TimeOut As Byte
Dim Error As Byte
   If Disk.RWError = True Then       // If RW error has occurred, prevent further writing to disk until
                                     // reinitialised
      Exit
   EndIf
   TimeOut = $00
   Error = 1
   Repeat
      Address = pSector << 9 
      CS = 0                         // Send Cmd 24
      SendCmd($58, Address.Byte3, Address.Byte2, Address.Byte1, Address.Byte0, $FF)
      Response1 = SeekResponse($00)
      If Response1 = 0 Then
         SendByte($FE)               // Write start token 
         Index = 0
         FSR0 = @File.CurrentSectorBuffer
         Repeat                      // Write data block
            SendByte(POSTINC0)
            Inc(Index)
         Until Index = $200           
         SendByte($FF)               // Send dummy CRC to conclude data block
         SendByte($FF)                                                           
         Response1 = ReceiveByte()   // Data response token
         Response2 = SeekResponse($FF)
         Response1 = Response1 And $0F
         If Response1 = $05 Then
            If Response2 = 0 Then
               Error = 0
            EndIf
         EndIf
      EndIf
      CS = 1
      Inc(TimeOut)
   Until Error = 0 Or TimeOut > $01
   If Error = 1 Then
      Disk.RWError = True            // Set RWError flag - can only be cleared using Init()
   EndIf
End Sub
{
********************************************************************************
* Name    : WriteSectorByte (PRIVATE)                                          *
* Purpose : Write a byte to current sector.                                    *
*         : Can be used with either Sector or FA16 subroutines                 *
********************************************************************************
}   
Sub WriteSectorByte(pByte As Byte)
   If File.CurrentSectorPos = $200 Then
      File.CurrentSectorPos = $000
   EndIf
   File.CurrentSectorBuffer(File.CurrentSectorPos) = pByte
   Disk.IsDirty = True
   If File.CurrentSectorPos = $1FF Then
      WriteSector(File.CurrentSector)
      Disk.IsDirty = False
      Inc(File.CurrentSector)
   EndIf
   Inc(File.CurrentSectorPos)
End Sub
{
********************************************************************************
* Name    : ReadFATEntry (PRIVATE)                                             *
* Purpose : Read an entry from the FAT                                         *
********************************************************************************
}   
Function ReadFATEntry(pSectorPos As Word) As Word
   ReadFATEntry.Byte0 = File.CurrentSectorBuffer(pSectorPos)   // Read FAT entry as word
   ReadFATEntry.Byte1 = File.CurrentSectorBuffer(pSectorPos + 1)
End Function
{
********************************************************************************
* Name    : FATSectorToCluster (PRIVATE)                                       *
* Purpose : Calculate cluster number based on FAT sector/position              *
********************************************************************************
}   
Function FATSectorToCluster() As Word
   FATSectorToCluster = File.FATSectorPos / 2
   FATSectorToCluster = FATSectorToCluster + (File.FATSector * $100) 
End Function
{
********************************************************************************
* Name    : ClusterToFATSector (PRIVATE)                                       *
* Purpose : Calculate FAT Sector/position based on cluster Number              *
********************************************************************************
}   
Sub ClusterToFATSector(pCluster As Word)
   File.FATSector = pCluster / $100   // Find FAT position for existing cluster
   File.FATSectorPos = pCluster Mod $100
   File.FATSectorPos = File.FATSectorPos * 2
End Sub
{
********************************************************************************
* Name    : DataToCluster (PRIVATE)                                            *
* Purpose : Calculate cluster number based on data sector                      *
********************************************************************************
}   
Function DataToCluster() As Word
Dim TempCluster As LongWord
   TempCluster = File.CurrentSector - Disk.Data
   TempCluster = TempCluster / Disk.SectorsPerCluster
   DataToCluster = TempCluster + 2
End Function
{
********************************************************************************
* Name    : ClusterToData (PRIVATE)                                            *
* Purpose : Calculate data sector based on cluster number                      *
********************************************************************************
}   
Function ClusterToData(pCluster As Word) As LongWord
   ClusterToData = (pCluster - 2) * Disk.SectorsPerCluster
   ClusterToData = ClusterToData + Disk.Data
End Function
{
********************************************************************************
* Name    : FindFreeCluster (PRIVATE)                                          *
* Purpose : Find a free cluster in the FAT                                     *
********************************************************************************
}   
Function FindFreeCluster() As Byte
Dim ScanSector As LongWord
Dim ScanSectorPos As Word
Dim FATEntry As Word
Dim Cluster As Word
   FindFreeCluster = 1                          // Scan FAT1 for free cluster address
   ScanSector = (Disk.FAT1 + File.FATSector)
   Repeat
      SectorInitRead(ScanSector)                // Set sector & read sector to buffer
      If ScanSector = (Disk.FAT1 + File.FATSector) Then
         ScanSectorPos = File.FATSectorPos + (2 * File.ClusterSequenceNumber)
      Else
         ScanSectorPos = $000
      EndIf
      Repeat
         FATEntry = ReadFATEntry(ScanSectorPos)
         If FATEntry = $0000 Then
            FindFreeCluster = 0
            Break
         EndIf
         Inc(ScanSectorPos, 2)
      Until ScanSectorPos > $1FF
      Inc(ScanSector)
   Until ScanSector = (Disk.FAT1 + Disk.SectorsPerFAT) Or FindFreeCluster = 0
   Dec(ScanSector)
   If FindFreeCluster = 1 Then
      Exit
   EndIf
   Cluster = ScanSectorPos / 2
   Cluster = Cluster + ((ScanSector - Disk.FAT1) * $100) 
   If Cluster > Disk.LastCluster Then           // Check resulting cluster (hence sector)
                                                // not greater than number on card
      FindFreeCluster = 1
   Else
      File.FATSector = ScanSector - Disk.FAT1   // Store FAT sector
      File.FATSectorPos = ScanSectorPos         // Store buffer position
      File.ClusterSequenceNumber = 0
      ScanSectorPos = File.FATSectorPos
      Repeat
         FATEntry = ReadFATEntry(ScanSectorPos)
         If FATEntry = $0000 Then
            Inc(File.ClusterSequenceNumber)     // Number of empty clusters in sector
         Else
            Break
         EndIf
         Inc(ScanSectorPos, 2)
      Until ScanSectorPos > $1FF
      File.ClusterSequenceIndex = 1             // Initilise cluster sequence
      If (Cluster + File.ClusterSequenceNumber - 1) > Disk.LastCluster Then
         File.ClusterSequenceNumber = Disk.LastCluster - Cluster + 1
      EndIf
   EndIf
End Function
{
********************************************************************************
* Name    : MakeUpper (PRIVATE)                                                *
* Purpose : Converts single char to uppercase                                  *
********************************************************************************
}  
Function MakeUpper(pValue As Byte) As Byte
   If Char(pValue) >= "a" And Char(pValue) <= "z" Then
      Dec(pValue, 32)
   EndIf 
   Result = pValue
End Function
{
********************************************************************************
* Name    : ModifyFileName (PRIVATE)                                           *
* Purpose : Replace blank file name characters with $20                        *
********************************************************************************
}     
Sub ModifyFileName(ByRef pFileName As TSDName)
Dim RootDirFilePos As Byte
Dim NullFl As Byte
   NullFl = 0
   RootDirFilePos = $00
   Repeat
      If (NullFl = 1) Or (Byte(pFileName.Name(RootDirFilePos)) = 0) Then
         pFileName.Name(RootDirFilePos) = $20
         NullFl = 1
      Else
         pFileName.Name(RootDirFilePos) = MakeUpper(pFileName.Name(RootDirFilePos))
      EndIf
      Inc(RootDirFilePos)                            
   Until RootDirFilePos > $07
   pFileName.Name(8) = 0
   NullFl = 0
   RootDirFilePos = $00
   Repeat
      If (NullFl = 1) Or (Byte(pFileName.Extension(RootDirFilePos)) = 0) Then
         pFileName.Extension(RootDirFilePos) = $20
         NullFl = 1
      Else
         pFileName.Extension(RootDirFilePos) = MakeUpper(pFileName.Extension(RootDirFilePos))
      EndIf
      Inc(RootDirFilePos)                                                               
   Until RootDirFilePos > $02 
   pFileName.Extension(3) = 0
End Sub
{
********************************************************************************
* Name    : IncrementSector (PRIVATE)                                          *
* Purpose : Finds next sector in FAT chain.                                    *
*         : Return: 0 = Success, 1 = End of cluster chain reached              *
********************************************************************************
}   
Function IncrementSector() As Byte
Dim Cluster As Word
   Inc(File.CurrentSectorInCluster)
   If File.CurrentSectorInCluster > Disk.SectorsPerCluster Then
      File.CurrentSector = File.CurrentSector - (Disk.SectorsPerCluster - 1)
      Cluster = DataToCluster()
      ClusterToFATSector(Cluster)     // Find FAT position for existing cluster
      File.CurrentSector = Disk.FAT1 + File.FATSector
      ReadSector(File.CurrentSector)
      Cluster = ReadFATEntry(File.FATSectorPos)
      If Cluster = $FFFF Then 
         IncrementSector = 1
      Else
         File.CurrentSector = ClusterToData(Cluster)
         File.CurrentSectorInCluster = 1
         IncrementSector = 0
         ClusterToFATSector(Cluster)  // Update FAT positions   
      EndIf
   Else
      Inc(File.CurrentSector)
      IncrementSector = 0
   EndIf
End Function
{
********************************************************************************
* Name    : RootDirPosition (PRIVATE)                                          *
* Purpose : Returns a byte from position pPos in Root Directory entry          *
********************************************************************************
}   
Function RootDirPosition(pPos As Byte) As Byte
   RootDirPosition = File.CurrentSectorBuffer(File.CurrentSectorPos + pPos)
End Function
{
********************************************************************************
* Name    : CheckFileEntryStatus (PRIVATE)                                     *
* Purpose : Check type & status of entry in directory.                         *
*         : Returns true if entry is a file or directory, set by pFileDir, and *
*         : not a deleted file, long filename, system, hidden or volume entry  *
********************************************************************************
}   
Function CheckFileEntryStatus(pFileDir As Bit) As Boolean
Dim Byte00 As Byte
Dim Byte0B As Byte
   Byte00 = RootDirPosition($00)
   Byte0B = RootDirPosition($0B)
   CheckFileEntryStatus = False
   If (Byte0B Or %11110000) <> $FF Then                 // Not long filename entry
      If Byte00 <> $00 Then                             // Not empty entry
         If Byte00 <> $E5 Then                          // Not deleted entry
            If (Byte0B And %00001110) = 0 Then          // Not system, hidden or volume
                                                        // (File attributes (00ADVSHR)
                                                        // 0: unused bit, A: archive bit, D: Dir bit, 
                                                        // V: volume bit, S: system bit, R: read-only bit)
               If (Byte0B.Bit4 Xor pFileDir) = 0 Then   // Check if file or directory
                  CheckFileEntryStatus = True
               EndIf
            EndIf
         EndIf
      EndIf
   EndIf
End Function
{
********************************************************************************
* Name    : FindRootDirEntry (PRIVATE)                                         *
* Purpose : Search for free root directory entry, search for file,             *
*         : or search for files using Dir.                                     *
*         : pType: 0 = Free position, 1 = Existing File/Dir, 2 = Dir command   *
*         : pFileDir: 0 = Find file, 1 = Find directory                        *
*         : Return: True = Success, False = Fail                               *
********************************************************************************
}   
Function FindRootDirEntry(ByRef pFileName As TSDName, pType As Byte, pFileDir As Bit = 0) As Boolean
Dim RootDirFilePos As Byte
Dim RootDirFileNum As Word
Dim NullFl As Byte
Dim Cluster As Word
Dim Response As Byte
   ModifyFileName(pFileName)
   RootDirFileNum = 0
   FindRootDirEntry = False
   File.CurrentSector = File.DirectoryStartSector
   File.CurrentSectorInCluster = 1
   Repeat
      If Disk.RWError Then
         FindRootDirEntry = False
         Exit
      EndIf
      SectorInitRead(File.CurrentSector)                          // Read sector to buffer
      File.CurrentSectorPos = $000
      Repeat
         Select pType
         Case 0                                                   // Search for free position
            If RootDirPosition(0) = $00 Or RootDirPosition(0) = $E5 Then
               FindRootDirEntry = True
            EndIf
         Case 1                                                   // Search for file or directory by name
            NullFl = 0
            RootDirFilePos = $00
            Repeat                                                // Check file/dir name
               If RootDirPosition(RootDirFilePos) <> Byte(pFileName.Name(RootDirFilePos)) Then 
                  NullFl = 1
                  Break
               EndIf
               Inc(RootDirFilePos)
            Until RootDirFilePos > $07
            RootDirFilePos = $08
            Repeat                                                // Check file/dir extension
               If RootDirPosition(RootDirFilePos) <> Byte(pFileName.Extension(RootDirFilePos - $08)) Then 
                  NullFl = 1
                  Break
               EndIf
               Inc(RootDirFilePos)
            Until RootDirFilePos > $0A
            If NullFl = 0 Then
               If CheckFileEntryStatus(pFileDir) Then
                  Cluster.Byte0 = RootDirPosition($1A)            // Locate cluster number
                  Cluster.Byte1 = RootDirPosition($1B)
                  File.Size.Byte0 = RootDirPosition($1C)          // Obtain file size (bytes)
                  File.Size.Byte1 = RootDirPosition($1D)
                  File.Size.Byte2 = RootDirPosition($1E)
                  File.Size.Byte3 = RootDirPosition($1F)
                  FindRootDirEntry = True
               EndIf
            EndIf
         Case 2                                                   // Dir command
            If CheckFileEntryStatus(pFileDir) Then
               Inc(RootDirFileNum)
               If RootDirFileNum = File.Number Then               // Found required entry in directory
                  RootDirFilePos = $00
                  Repeat                                          // Transfer file name
                     pFileName.Name(RootDirFilePos) = RootDirPosition(RootDirFilePos)
                     Inc(RootDirFilePos)
                  Until RootDirFilePos > $07
                  pFileName.Name($08) = 0                         // String terminator
                  Repeat                                          // Transfer file extension
                     pFileName.Extension(RootDirFilePos - $08) = RootDirPosition(RootDirFilePos) 
                     Inc(RootDirFilePos)
                  Until RootDirFilePos > $0A
                  pFileName.Extension($03) = 0                    // String terminator
                  FindRootDirEntry = True
               EndIf
            EndIf
         EndSelect
         Inc(File.CurrentSectorPos,32)
      Until (File.CurrentSectorPos > $1FF) Or FindRootDirEntry
      Dec(File.CurrentSectorPos,32)
      If Not(FindRootDirEntry) Then
         If File.IsRootDirectory Then
            Inc(File.CurrentSector)
            If File.CurrentSector < (Disk.RootDirectory + Disk.SectorsInRoot) Then
               Response = 0
            Else
               Response = 1
            EndIf
         Else
            Response = IncrementSector()
         EndIf
      EndIf
   Until FindRootDirEntry Or (Response = 1)
   If Not(FindRootDirEntry) Then
      Exit
   EndIf
   File.FileEntryDirectorySector = File.CurrentSector             // Store root directory position
   File.FileEntryDirectorySectorPos = File.CurrentSectorPos       // Store buffer position
   If pType = 1 Then                                              // Only perform if searching for specific
                                                                  // entry
      If Cluster = 0 Then                                         // Only true when using ChDir to return 
                                                                  // to Root Directory using cdUp
         File.CurrentSector = Disk.RootDirectory
      Else
         File.CurrentSector = ClusterToData(Cluster)
      EndIf
   EndIf
End Function
{
********************************************************************************
* Name    : InsertByteIntoBuffer (PRIVATE)                                     *
* Purpose : Insert a byte into buffer                                          *
********************************************************************************
}   
Sub InsertByteIntoBuffer(pSectorPos As Word, pByte As Byte)
   File.CurrentSectorBuffer(pSectorPos) = pByte
End Sub
{
********************************************************************************
* Name    : AssembleFATSector (PRIVATE)                                        *
* Purpose : Update FAT sector after cluster sequence written.                  *
*         : pType: 0 = End continue, 1 = End close                             *
********************************************************************************
}   
Sub AssembleFATSector(pFATSectorPrevious As LongWord, pFATSectorPosPrevious As Word,
                      pClusterSequenceNumberPrevious As Word, pType As Byte)
Dim Cluster As Word
   Cluster = pFATSectorPrevious * $100   // Derive first cluster number
   Cluster = Cluster + (pFATSectorPosPrevious / 2)
   Inc(Cluster)
   While pClusterSequenceNumberPrevious > 1
      InsertByteIntoBuffer(pFATSectorPosPrevious, Cluster.Byte0)
      Inc(pFATSectorPosPrevious)
      InsertByteIntoBuffer(pFATSectorPosPrevious, Cluster.Byte1)
      Inc(pFATSectorPosPrevious)
      Inc(Cluster)
      Dec(pClusterSequenceNumberPrevious)
   Wend
   If pType = 0 Then
      Cluster = FATSectorToCluster() 
      InsertByteIntoBuffer(pFATSectorPosPrevious, Cluster.Byte0)
      Inc(pFATSectorPosPrevious)
      InsertByteIntoBuffer(pFATSectorPosPrevious, Cluster.Byte1)
   Else
      InsertByteIntoBuffer(pFATSectorPosPrevious, $FF)
      Inc(pFATSectorPosPrevious)
      InsertByteIntoBuffer(pFATSectorPosPrevious, $FF)
   EndIf
End Sub
{
********************************************************************************
* Name    : WriteByte (PRIVATE)                                                *
* Purpose : Write a single byte to file.                                       *
*         : Disk.DiskFull = True when disk full                                *
********************************************************************************
}   
Sub WriteByte(pByte As Byte)
Dim FATSectorPrevious As LongWord
Dim FATSectorPosPrevious As Word
Dim ClusterSequenceNumberPrevious As Word
Dim Cluster As Word
Dim Response As Byte
   If Disk.DiskFull Then                                    // Disk full
      Exit
   EndIf
   Inc(File.BytesRead)
   If File.EOF Or (File.BytesRead > File.Size) Then         // Extending file size
      Inc(File.Size)
      File.EOF = True
   EndIf
   If File.CurrentSectorPos > $1FF Then
      If File.EOF = False Then                              // Not extending file
         Dec(File.CurrentSector)                            // Put current sector back by one to undo
                                                            // inc on last WriteSectorByte
         IncrementSector()
      Else                                                  // Extending file
         Inc(File.CurrentSectorInCluster)
         If File.CurrentSectorInCluster > Disk.SectorsPerCluster Then
            Inc(File.ClusterSequenceIndex)
            If File.ClusterSequenceIndex > File.ClusterSequenceNumber Then
               FATSectorPrevious = File.FATSector
               FATSectorPosPrevious = File.FATSectorPos                      
               ClusterSequenceNumberPrevious = File.ClusterSequenceNumber
               Response = FindFreeCluster()
               If Response = 1 Then
                  Dec(File.ClusterSequenceIndex)
                  File.FATSector = FATSectorPrevious
                  File.FATSectorPos = FATSectorPosPrevious                     
                  File.ClusterSequenceNumber = ClusterSequenceNumberPrevious
                  Disk.DiskFull = True
                  Dec(File.Size)
                  Dec(File.BytesRead)
                  Exit
               EndIf
               ReadSector(Disk.FAT1 + FATSectorPrevious)
               AssembleFATSector(FATSectorPrevious, FATSectorPosPrevious, ClusterSequenceNumberPrevious, 0)
               WriteSector(Disk.FAT1 + FATSectorPrevious)   // Write Disk.FAT1
               WriteSector(Disk.FAT2 + FATSectorPrevious)   // Write Disk.FAT1
               Cluster = FATSectorToCluster()
               File.CurrentSector = ClusterToData(Cluster)
               SectorInitWrite()
            EndIf
            File.CurrentSectorInCluster = 1
         EndIf
      EndIf
      ReadSector(File.CurrentSector)
   EndIf
   WriteSectorByte(pByte)
End Sub        
{
********************************************************************************
* Name    : DiskFull (PUBLIC)                                                  *
* Purpose : Determines if a disk is full when writing to file.                 *
*         : Return: False = Disk not yet full, True = Disk is full             *
********************************************************************************
}   
Public Function DiskFull() As Boolean
   Result = Disk.DiskFull
End Function
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a Boolean to file.                                           *
*         : Check DiskFull for disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As Boolean)
   WriteByte(Byte(pValue))
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a Bit to file.                                               *
*         : Check DiskFull for disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As Bit)
   WriteByte(Byte(pValue))
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a Byte to file.                                              *
*         : Check DiskFull for disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As Byte)
   WriteByte(pValue)
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a Char to file.                                              *
*         : Check DiskFull for disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As Char)
   WriteByte(pValue)
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a ShortInt to file.                                          *
*         : Check DiskFull for disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As ShortInt)
   WriteByte(pValue)
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a Word to file.                                              *
*         : Check DiskFull For Disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As Word)
   WriteByte(pValue.Byte0)
   WriteByte(pValue.Byte1)
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write an Integer to file.                                          *
*         : Check DiskFull For Disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As Integer)
   WriteByte(pValue.Byte0)
   WriteByte(pValue.Byte1)
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a LongWord to file.                                          *
*         : Check DiskFull For Disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As LongWord)
   WriteByte(pValue.Byte0)
   WriteByte(pValue.Byte1)
   WriteByte(pValue.Byte2)
   WriteByte(pValue.Byte3)
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a LongInt to file.                                           *
*         : Check DiskFull For Disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As LongInt)
   WriteByte(pValue.Byte0)
   WriteByte(pValue.Byte1)
   WriteByte(pValue.Byte2)
   WriteByte(pValue.Byte3)
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a Float to file.                                             *
*         : Check DiskFull For Disk full                                       *
********************************************************************************
}  
Sub WriteItem(pValue As Float)
   WriteByte(pValue.Byte0)
   WriteByte(pValue.Byte1)
   WriteByte(pValue.Byte2)
   WriteByte(pValue.Byte3)
End Sub
{
********************************************************************************
* Name    : WriteItem (OVERLOAD)                                               *
* Purpose : Write a String to file.                                            *
*         : Check DiskFull For Disk full                                       *
********************************************************************************
}  
Sub WriteItem(pText As String)
Dim StringPos As Byte
   StringPos = 0
   While pText(StringPos) <> null
      WriteByte(pText(StringPos))
      Inc(StringPos)
   Wend
End Sub
{
********************************************************************************
* Name    : Write (COMPOUND)                                                   *
* Purpose : Write one or more items to file.                                   *
*         : Check DiskFull For Disk full                                       *
********************************************************************************
} 
Public Compound Sub Write(WriteItem)
{
********************************************************************************
* Name    : WriteString (PUBLIC)                                               *
* Purpose : Write a String to file, passing string ByRef.                      *
*         : Check DiskFull For Disk full                                       *
********************************************************************************
}  
Public Sub WriteString(ByRef pText As String)
Dim StringPos As Byte
   StringPos = 0
   While pText(StringPos) <> null
      WriteByte(pText(StringPos))
      Inc(StringPos)
   Wend
End Sub
{
********************************************************************************
* Name    : StrToFileDirName (PRIVATE)                                         *
* Purpose : Converts a file or directory string (e.g. FILE0001.TXT) to a       *
*         : TSDName type.                                                      *
*         : String may be upto 8 characters, a '.' and 3 characters, for a     *
*         : filename, or 8 characters for a directory                          *
********************************************************************************
}   
Function StrToFileDirName(pName As String) As TSDName
Const Period = $2E
Dim Index As Byte
   StrToFileDirName.Name = Null
   StrToFileDirName.Extension = Null
   Index = 0
   FSR0 = AddressOf(StrToFileDirName.Name)
   FSR1 = AddressOf(pName)  
   Index = 0                // Copy name...
   While (Index < 8) And (INDF1 <> 0) And (INDF1 <> Period)
      POSTINC0 = POSTINC1     
	   Inc(Index)
   Wend 
   INDF0 = 0       
   If INDF1 = Period Then   // Copy extension...
      Inc(FSR1)
      Index = 0
      FSR0 = AddressOf(StrToFileDirName.Extension)
      While (Index < 3) And (INDF1 <> 0)
         POSTINC0 = POSTINC1
         Inc(Index)
      Wend   
      INDF0 = 0
   EndIf
End Function
{
********************************************************************************
* Name    : FileExists (OVERLOAD)                                              *
* Purpose : Set pFileName.Name & pFileName.Extension, then check for file.     *
*         : Return: True = File found, False = File not found                  *
********************************************************************************
}   
Public Function FileExists(pFileName As TSDName) As Boolean
   FileExists = FindRootDirEntry(pFileName, 1, 0)
End Function
{
********************************************************************************
* Name    : FileExists (OVERLOAD)                                              *
* Purpose : Pass pFileName As a String of upto 8 characters, a '.' and 3       *
*         : characters.                                                        *
*         : Return: True = File found, False = File not found                  *
********************************************************************************
}   
Public Function FileExists(pFileName As String) As Boolean
   Result = FileExists(StrToFileDirName(pFileName))
End Function
{
********************************************************************************
* Name    : DirExists (OVERLOAD)                                               *
* Purpose : Set pDirName.Name, then check for directory.                       *
*         : Return: True = Dir found, False = Dir not found                    *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function DirExists(pDirName As TSDName) As Boolean
   pDirName.Extension = ""
   DirExists = FindRootDirEntry(pDirName, 1, 1)
End Function
#endif
{
********************************************************************************
* Name    : DirExists (OVERLOAD)                                               *
* Purpose : Pass pDirName as a string of 8 uppercase characters.               *
*         : Return: True = Dir found, False = Dir not found                    *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function DirExists(pDirName As String) As Boolean
   Result = DirExists(StrToFileDirName(pDirName))
End Function
#endif
{
********************************************************************************
* Name    : ReadSectorByte (PRIVATE)                                           *
* Purpose : Read a byte from current sector.                                   *
*         : Can be used with either Sector Or FA16 subroutines                 *
********************************************************************************
}   
Function ReadSectorByte() As Byte
   If File.CurrentSectorPos > $1FF Then   // Inc sector if gone past end of previous sector
      Inc(File.CurrentSector)
      SectorInitRead(File.CurrentSector)                                                     
   EndIf
   ReadSectorByte = File.CurrentSectorBuffer(File.CurrentSectorPos)
   Inc(File.CurrentSectorPos)
End Function
{
********************************************************************************
* Name    : SetFileTimeModified (PRIVATE)                                      *
* Purpose : Set modified time & date for file                                  *
********************************************************************************
}   
Sub SetFileTimeModified(pSectorPos As Word, pDay, pMonth, pYear, pHours, pMinutes, pSeconds As Byte)
Dim SetByte As Byte
   SetByte = (pSeconds / 2) Or (pMinutes << 5)      // Create time & date - Seconds/Minutes
   InsertByteIntoBuffer(pSectorPos, SetByte)
   Inc(pSectorPos) 
   SetByte = (pMinutes >> 3) Or (pHours << 3)       // Create time & date - Minutes/Hours
   InsertByteIntoBuffer(pSectorPos, SetByte)
   Inc(pSectorPos) 
   SetByte = pDay Or (pMonth << 5)                  // Create time & date - Day/Month
   InsertByteIntoBuffer(pSectorPos, SetByte)
   Inc(pSectorPos) 
   SetByte = (pMonth >> 3) Or ((pYear + 20) << 1)   // Create time & date - Month/Year
   InsertByteIntoBuffer(pSectorPos, SetByte)
End Sub     
{
********************************************************************************
* Name    : SetFileTimeCreate (PRIVATE)                                        *
* Purpose : Set create time & date for file                                    *
********************************************************************************
}   
Sub SetFileTimeCreate(pSectorPos As Word, pDay, pMonth, pYear,
                      pHours, pMinutes, pSeconds, pHSeconds As Byte)
Dim SetByte As Byte
   SetByte = pHSeconds + ((pSeconds Mod 2) * 100)   // Create time & date - Milliseconds/Minutes
   InsertByteIntoBuffer(pSectorPos, SetByte)
   Inc(pSectorPos) 
   SetFileTimeModified(pSectorPos, pDay, pMonth, pYear, pHours, pMinutes, pSeconds)       
End Sub
{
********************************************************************************
* Name    : CloseFile (PUBLIC)                                                 *
* Purpose : Close file                                                         *
********************************************************************************
}   
Public Sub CloseFile(pDay As Byte = 1, pMonth As Byte = 1, pYear As Byte = 1, 
                     pHours As Byte = 0, pMinutes As Byte = 0, pSeconds As Byte = 0)
Dim IsDirectory As Byte
   If File.RW = 1 Then
   ElseIf File.IsOpen Then                      // File must be open for write
      If File.EOF Then                          // If at eof, need to fill rest of sector with zeros,
                                                // then will automatically write sector at end
         While File.CurrentSectorPos < $200 
            WriteSectorByte($00)
         Wend
      Else
         WriteSector(File.CurrentSector)
      EndIf
      ReadSector(File.FileEntryDirectorySector)
      IsDirectory = File.CurrentSectorBuffer(File.FileEntryDirectorySectorPos + $0B)
      IsDirectory = IsDirectory And %00010000   // Check if it is a directory being closed
      InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $1C, File.Size.Byte0)   // File size
      InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $1D, File.Size.Byte1)
      InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $1E, File.Size.Byte2)
      InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $1F, File.Size.Byte3)
      If (File.Size > 0) Or (IsDirectory = %00010000) Then
         InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $1A, File.FirstCluster.Byte0)    
                                                                                      // Cluster number      
         InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $1B, File.FirstCluster.Byte1)    
      EndIf
      SetFileTimeModified(File.FileEntryDirectorySectorPos + $16, pDay, pMonth, pYear,
                          pHours, pMinutes, pSeconds)
                                                                                      // Set file modified
                                                                                      // time & date
      WriteSector(File.FileEntryDirectorySector)      
      If File.EOF Then
         If (File.Size > 0) Or (IsDirectory = %00010000) Then
            ReadSector(Disk.FAT1 + File.FATSector)
            AssembleFATSector(File.FATSector, File.FATSectorPos, File.ClusterSequenceIndex, 1)
            WriteSector(Disk.FAT1 + File.FATSector)                                   // Write FAT1
            WriteSector(Disk.FAT2 + File.FATSector)                                   // Write FAT2
         EndIf
      EndIf
   EndIf
   File.Size = 0
   File.IsOpen = False
End Sub        
{
********************************************************************************
* Name    : InitSequence (PRIVATE)                                             *
* Purpose : Init SD or MMC card.                                               *
*         : Sets Input/Output pins and switches card to SPI mode.              *
*         : Return: 0 = Success, 1 = Fail/card not present.                    *
*         : Either software or hardware SPI can be used by selecting           *
*         : #option SD_SPI = SW or #option SD_SPI = MSSP                       *
********************************************************************************
}   
#if SD_SPI = SW                                  // Software SPI version
Function InitSequence() As Boolean
#elseif SD_SPI = MSSP                            // MSSP version
Function InitSequence(pFOsc As Byte = spiOscDiv4) As Boolean
#endif
Dim Response As Byte
Dim Index As Byte
#if SD_SPI = MSSP                                // MSSP version
   SSPControl1 = pFOsc Or %00100000              // Setup MSSP module
   SSPStatus = %01000000                         
#endif
   Output(CS)                                    // Setup direction for SPI bus
   Output(DI)
   Output(CLK)
   Input(DO)
   InitSequence = False
   CS = 1                                        // Pull CS high
   SendCmd($FF, $FF, $FF, $FF, $FF, $FF)
   SendCmd($FF, $FF, $FF, $FF, $FF, $FF)
   DelayMS(100)
   CS = 0                                        // Pull CS low
   SendCmd($40, $00, $00, $00, $00, $95)         // Send Cmd 0 & CRC
   Response = SeekResponse($01, $FF)
   If Response = 0 Then
      Index = $FF
      Repeat
         SendCmd($41, $00, $00, $00, $00, $FF)   // Send Cmd 1 & CRC - init from idle
         Response = SeekResponse($00, $FF)
         Dec(Index)
      Until Response = 0 Or Index = 0
      If Index > 0 Then
         InitSequence = True                     // Init completed successfully
      EndIf
   EndIf
   CS = 1
   SendByte($FF)                                 // Clock SD/MMC to complete
End Function
{
********************************************************************************
* Name    : Init (PUBLIC)                                                      *
* Purpose : Init SD or MMC card and read FAT16 file system.                    *
*         : Sets Input/Output pins and switches card to SPI mode, then reads   *
*         : parameters of Master Boot Record & Boot Record.                    *
*         : Return: True = Success, False = Fail/card not present.             *
*         : Either software Or hardware SPI can be used by selecting           *
*         : #option SD_SPI = SW or #option SD_SPI = MSSP                       *
********************************************************************************
}   
#if SD_SPI = SW                                                    // Software SPI version
Public Function Init() As Boolean
#elseif SD_SPI = MSSP                                              // MSSP version
Public Function Init(pFOsc As Byte = spiOscDiv64) As Boolean
#endif
Dim ResponseB As Boolean
Dim BootRecord As LongWord
Dim BytesPerSector As Word
Dim ReservedSectors As Word
Dim NumberOfFATs As Byte
Dim NumberOfRootEntries As Word
Dim NumberOfSectors As LongWord
   Disk.RWError = False                                            // Clear read/write error flag to 
                                                                   // allow a re-initialise after failure
#if SD_SPI = SW
   ResponseB = InitSequence()                                      // Init SD/MMC
#elseif SD_SPI = MSSP                                              // MSSP version
   ResponseB = InitSequence(pFOsc)                                 // Init SD/MMC
#endif
   If ResponseB = False Then
      Disk.RWError = True
      Init = False
      Exit
   EndIf
   SectorInitRead($00)                                             // Read Master Boot Record               
   If File.CurrentSectorBuffer(0) = $EB And File.CurrentSectorBuffer(2) = $90 Then
      BootRecord = $0000                                           // Locate Boot Record - Sector 0
                                                                   // is Boot Record (no MBR)                
   ElseIf File.CurrentSectorBuffer(0) = $E9 Then
      BootRecord = $0000
   Else
      BootRecord.Byte0 = File.CurrentSectorBuffer($1C6)            // Locate Boot Record 
      BootRecord.Byte1 = File.CurrentSectorBuffer($1C7)
      BootRecord.Byte2 = File.CurrentSectorBuffer($1C8)
      BootRecord.Byte3 = File.CurrentSectorBuffer($1C9)
   EndIf                                                                                
   SectorInitRead(BootRecord)
   BytesPerSector.Byte0 = File.CurrentSectorBuffer($0B)            // Read bytes per sector           
   BytesPerSector.Byte1 = File.CurrentSectorBuffer($0C)                                
   Disk.SectorsPerCluster = File.CurrentSectorBuffer($0D)          // Read sectors per cluster                
   ReservedSectors.Byte0 = File.CurrentSectorBuffer($0E)           // Read number of reserved sectors
   ReservedSectors.Byte1 = File.CurrentSectorBuffer($0F)                                          
   NumberOfFATs = File.CurrentSectorBuffer($10)                    // Read number of FATs           
   NumberOfRootEntries.Byte0 = File.CurrentSectorBuffer($11)       // Read number of Root Entries      
   NumberOfRootEntries.Byte1 = File.CurrentSectorBuffer($12)      
   Disk.SectorsPerFAT.Byte0 = File.CurrentSectorBuffer($16)        // Read sectors Per FAT            
   Disk.SectorsPerFAT.Byte1 = File.CurrentSectorBuffer($17)                                
   NumberOfSectors.Byte0 = File.CurrentSectorBuffer($20)           // Read number of sectors                    
   NumberOfSectors.Byte1 = File.CurrentSectorBuffer($21)                      
   NumberOfSectors.Byte2 = File.CurrentSectorBuffer($22)                      
   NumberOfSectors.Byte3 = File.CurrentSectorBuffer($23)
   Disk.FAT1 = BootRecord + ReservedSectors                        // Locate FAT1
   Disk.FAT2 = BootRecord + ReservedSectors + Disk.SectorsPerFAT   // Locate FAT2
   Disk.RootDirectory = NumberOfFATs * Disk.SectorsPerFAT          // Locate Root Directory
   Disk.RootDirectory = Disk.RootDirectory + BootRecord + ReservedSectors  
   Disk.SectorsInRoot = NumberOfRootEntries * 32                   // Calculate sectors in Root Directory
   Disk.SectorsInRoot = Disk.SectorsInRoot / BytesPerSector
   Disk.Data = Disk.RootDirectory + Disk.SectorsInRoot             // Locate start of data @ cluster 2
   File.CurrentSector = BootRecord + NumberOfSectors               // Locate last sector 
                                                                   // (File.CurrentSector = 
                                                                   // BootRecord + NumberOfSectors - 1)
                                                                   // & add 1 to count full clusters 
                                                                   // correctly with DataToCluster
   Disk.LastCluster = DataToCluster() - 1                          // Subtract 1 to correct result 
                                                                   // from DataToCluster when used to
                                                                   // find number of complete clusters
   File.Number = 0                                                 // Set Dir to read first file
                                                                   // on first call
   File.DirFileDir = 0                                             // Set Dir to look for file,
                                                                   // not directory, by default 
   File.DirectoryStartSector = Disk.RootDirectory                  // Start off in Root Directory
   File.IsRootDirectory = True
   File.IsOpen = False
   Disk.IsDirty = False
   If Disk.RWError Then
      Init = False
   Else
      Init = True
   EndIf
End Function
{
********************************************************************************
* Name    : ChDir (OVERLOAD)                                                   *
* Purpose : Change to directory in current directory.                          *
*         : Return: True = Success, False = Directory not found                *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function ChDir(pDirName As TSDName) As Boolean
   If Not(DirExists(pDirName)) Then             // Check if directory exists
      ChDir = False                             // Error - directory does not exist
      Exit                                                                    
   EndIf
   File.DirectoryStartSector = File.CurrentSector
   If File.DirectoryStartSector = Disk.RootDirectory Then
      File.IsRootDirectory = True
   Else
      File.IsRootDirectory = False
   EndIf
   File.Number = 0                              // Reset file pointer for Dir
   ChDir = True                                 // Success - directory swapped
End Function                                                                        
#endif
{
********************************************************************************
* Name    : ChDir (OVERLOAD)                                                   *
* Purpose : Change to directory in current directory.                          *
*         : Return: True = Success, False = Directory not found                *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function ChDir(pDirName As String) As Boolean
   Result = ChDir(StrToFileDirName(pDirName))
End Function
#endif
{
********************************************************************************
* Name    : ChDir (OVERLOAD)                                                   *
* Purpose : Change to parent or root directory.                                *
*         : pCmd: cdDotDot/cdUp = parent directory, cdBS/cdRootDir = Root Dir  *
*         : Return: True = Success, False = Directory not found                *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function ChDir(pCmd As Byte) As Boolean
Dim TempName As TSDName
   If pCmd = cdUp Then
      TempName.Name = ".."
      Result = ChDir(TempName)
   Else
      File.DirectoryStartSector = Disk.RootDirectory
      File.IsRootDirectory = True
      File.Number = 0   // Reset file pointer for Dir
      Result = True
   EndIf
End Function
#endif
{
********************************************************************************
* Name    : IsRootDir (PUBLIC)                                                 *
* Purpose : Determines if current directory is Root Directory.                 *
*         : Return: False = Not Root Directory, True = Is Root Directory       *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function IsRootDir() As Boolean
   Result = File.IsRootDirectory
End Function   
#endif
{
********************************************************************************
* Name    : InsertFileNameIntoBuffer (PRIVATE)                                 *
* Purpose : Inserts a filename into the directory entry in the buffer          *
********************************************************************************
}   
Sub InsertFileNameIntoBuffer(ByRef pFileName As TSDName)
Dim RootDirFilePos As Byte
   File.CurrentSectorPos = File.FileEntryDirectorySectorPos
   RootDirFilePos = $00
   Repeat                                                                    // Write File Name
      InsertByteIntoBuffer(File.CurrentSectorPos, pFileName.Name(RootDirFilePos))      
      Inc(File.CurrentSectorPos)
      Inc(RootDirFilePos)
   Until RootDirFilePos > $07
   Repeat                                                                    // Write File Extension
      InsertByteIntoBuffer(File.CurrentSectorPos, pFileName.Extension(RootDirFilePos - $08))      
      Inc(File.CurrentSectorPos)
      Inc(RootDirFilePos)
   Until RootDirFilePos > $0A
End Sub
{
********************************************************************************
* Name    : New (PRIVATE)                                                      *
* Purpose : Create a new file or directory.                                    *
*         : pFileDir: 0 = file, 1 = directory                                  * 
*         : Return: errOK = Success, errExists = File already exists,          *
*         : errDiskFull = Disk full, errRootDirFull = Root Directory full,     *
*         : errInUse = File number in use
********************************************************************************
}   
Function New(pFileName As TSDName, pIsFile As Boolean,
             pDay As Byte = 1, pMonth As Byte = 1, pYear As Byte = 1,
             pHours As Byte = 0, pMinutes As Byte = 0, pSeconds As Byte = 0, pHSeconds As Byte = 0) As Byte
Dim Response As Byte
Dim ResponseB As Boolean
Dim RootDirFilePos As Byte
Dim Cluster As Word
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Dim NewDirSector As LongWord
Dim Index As Byte
#endif
   If Disk.RWError Then
      New = errRWError
      Exit
   EndIf
   If File.IsOpen Then
      New = errInUse
      Exit
   EndIf
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   If pIsFile Then
      ResponseB = FileExists(pFileName)                                   // Check if file exists
   Else
      ResponseB = DirExists(pFileName)                                    // Check if directory exists
   EndIf
#else
   ResponseB = FileExists(pFileName)                                      // Check if file exists
#endif
   If ResponseB Then
      New = errExists                                                     // Error - file already exists
      Exit                                                                    
   EndIf
   Repeat
      File.FATSector = 0
      File.FATSectorPos = 0
      File.ClusterSequenceNumber = 0
      Response = FindFreeCluster()
      If Response = 1 Then                                                // Disk full error
         New = errDiskFull
         Exit
      EndIf   
      Cluster = FATSectorToCluster()
      If Not(FindRootDirEntry(pFileName, 0)) Then                         // No free directory entry
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
         If File.IsRootDirectory Then                                     // Is Root Directory - no 
                                                                          // possibility of expansion
            New = errRootDirFull
            Exit
         Else                                                             // Expand directory with free 
                                                                          // cluster already found
            InsertByteIntoBuffer(File.FATSectorPos + 0, Cluster.Byte0)    // Point end of existing 
                                                                          // cluster chain to new cluster
                                                                          // FAT sector set & read by 
                                                                          // IncrementSector in 
                                                                          // FindRootDirEntry
            InsertByteIntoBuffer(File.FATSectorPos + 1, Cluster.Byte1)
            WriteSector(Disk.FAT1 + File.FATSector)                       // Write to FAT1
            WriteSector(Disk.FAT2 + File.FATSector)                       // Write to FAT2
            ClusterToFATSector(Cluster)                                   // Find position of new 
                                                                          // cluster entry
            ReadSector(Disk.FAT1 + File.FATSector)
            InsertByteIntoBuffer(File.FATSectorPos + 0, $FF)              // End cluster chain
            InsertByteIntoBuffer(File.FATSectorPos + 1, $FF)
            WriteSector(Disk.FAT1 + File.FATSector)                       // Write to FAT1
            WriteSector(Disk.FAT2 + File.FATSector)                       // Write to FAT2
            Clear(File.CurrentSectorBuffer)
            NewDirSector = ClusterToData(Cluster)
            Index = 0
            Repeat
               WriteSector(NewDirSector + Index)
               Inc(Index)
            Until Index = Disk.SectorsPerCluster
         EndIf
#else
         New = errRootDirFull
         Exit
#endif
      Else
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
         ClusterToFATSector(Cluster)                                  // Reset FAT sector parameters
                                                                      // to file parameters,
                                                                      // not directory parameters
#endif                                                               
      EndIf 
   Until Response = 0                                                 // Repeat finding free cluster
                                                                      // & directory entry if dir expanded
   ReadSector(File.FileEntryDirectorySector)
   InsertFileNameIntoBuffer(pFileName)
   File.CurrentSectorPos = File.FileEntryDirectorySectorPos + $0B
   RootDirFilePos = $0B
   Repeat                                                             // Erase Root Directory position
      InsertByteIntoBuffer(File.CurrentSectorPos, 0)      
      Inc(File.CurrentSectorPos)
      Inc(RootDirFilePos)
   Until RootDirFilePos > $1F
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   If pIsFile Then
      InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $0B, %00100000)
                                                                      // File attributes (00ADVSHR)
                                                                      // 0: unused bit, A: archive bit,
                                                                      // D: Dir bit, V: volume bit, 
                                                                      // S: system bit, R: read-only bit    
   Else
      InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $0B, %00010000) 
                                                                      // File attributes (00ADVSHR)
                                                                      // 0: unused bit, A: archive bit, 
                                                                      // D: Dir bit, V: volume bit, 
                                                                      // S: system bit, R: read-only bit    
   EndIf
#else
   InsertByteIntoBuffer(File.FileEntryDirectorySectorPos + $0B, %00100000)
                                                                      // File attributes (00ADVSHR)
                                                                      // 0: unused bit, A: archive bit, 
                                                                      // D: Dir bit, V: volume bit,
                                                                      // S: system bit, R: read-only bit    
#endif
   SetFileTimeCreate(File.FileEntryDirectorySectorPos + $0D, pDay, pMonth, pYear,
                     pHours, pMinutes, pSeconds, pHSeconds)
                                                                      // Set file create time & date
   File.FirstCluster = Cluster                                        // Store first cluster
   WriteSector(File.FileEntryDirectorySector)
   File.CurrentSector = ClusterToData(Cluster)                        // Set start sector for data
   SectorInitWrite()
   File.CurrentSectorInCluster = 1
   File.CurrentSectorPos = 0                                          // Set start pos for sector buffer
   File.Size = 0
   File.BytesRead = 0
   File.EOF = True
   Disk.DiskFull = False
   File.IsOpen = True
   File.RW = 0
   New = errOK
End Function
{
********************************************************************************
* Name    : NewFile (OVERLOAD)                                                 *
* Purpose : Create a new file.                                                 *
*         : Return: errOK = Success, errExists = File already exists,          *
*         : errDiskFull = Disk full, errRootDirFull = Root Directory full,     *
*         : errInUse = File number in use, errRWError = Read/write error       *
********************************************************************************
}   
Public Function NewFile(pFileName As TSDName, pDay As Byte = 1, pMonth As Byte = 1, pYear As Byte = 1,
                        pHours As Byte = 0, pMinutes As Byte = 0, pSeconds As Byte = 0,
                        pHSeconds As Byte = 0) As Byte
   Result = New(pFileName, True, pDay, pMonth, pYear, pHours, pMinutes, pSeconds, pHSeconds)
End Function
{
********************************************************************************
* Name    : NewFile (OVERLOAD)                                                 *
* Purpose : Create a new file.                                                 *
*         : Return: errOK = Success, errExists = File already exists,          *
*         : errDiskFull = Disk full, errRootDirFull = Root Directory full,     *
*         : errInUse = File Number in use, errRWError = Read/Write error       *
********************************************************************************
}   
Public Function NewFile(pFileName As String, pDay As Byte = 1, pMonth As Byte = 1, pYear As Byte = 1,
                        pHours As Byte = 0, pMinutes As Byte = 0, pSeconds As Byte = 0, 
                        pHSeconds As Byte = 0) As Byte
   Result = New(StrToFileDirName(pFileName), True, pDay, pMonth, pYear,
                pHours, pMinutes, pSeconds, pHSeconds)
End Function
{
********************************************************************************
* Name    : MkDir (OVERLOAD)                                                   *
* Purpose : Make a new directory.                                              *
*         : Return: errOK = Success, errExists = Directory already exists,     *
*         : errDiskFull = Disk full, errRootDirFull = Root Directory full,     *
*         : errInUse = File number in use, errRWError = Read/write error       *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function MkDir(pDirName As TSDName, pDay As Byte = 1, pMonth As Byte = 1, pYear As Byte = 1,
                      pHours As Byte = 0, pMinutes As Byte = 0, pSeconds As Byte = 0, 
                      pHSeconds As Byte = 0) As Byte
Dim Index As Byte
Dim Cluster As Word
Dim TempCurrentSector As LongWord
   pDirName.Extension = ""
   MkDir = New(pDirName, False, pDay, pMonth, pYear, pHours, pMinutes, pSeconds, pHSeconds)
   If MkDir > 0 Then
      Exit
   EndIf
   Clear(File.CurrentSectorBuffer)
   Index = $00
   Repeat
      InsertByteIntoBuffer(Index,$20)   // Write 11 spaces to clear first directory entries (for ".")
      Inc(Index)
   Until Index > $0A
   Index = $20
   Repeat
      InsertByteIntoBuffer(Index,$20)   // Write 11 spaces to clear second directory entries (for "..")
      Inc(Index)
   Until Index > $2A
   InsertByteIntoBuffer($00,$2E)   // "."
   InsertByteIntoBuffer($20,$2E)   // ".."
   InsertByteIntoBuffer($21,$2E)
   InsertByteIntoBuffer($0B,$10)   // Dir attribute for "."
   InsertByteIntoBuffer($2B,$10)   // Dir attribute for ".."
   SetFileTimeCreate($0D, pDay, pMonth, pYear, pHours, pMinutes, pSeconds, pHSeconds)   // Set file create
                                                                                        // time & date
                                                                                        // for "."
   SetFileTimeCreate($2D, pDay, pMonth, pYear, pHours, pMinutes, pSeconds, pHSeconds)   // Set file create
                                                                                        // time & date
                                                                                        // for ".."
   SetFileTimeModified($16, pDay, pMonth, pYear, pHours, pMinutes, pSeconds)   // Set file modified time
                                                                               // & date for "."
   SetFileTimeModified($36, pDay, pMonth, pYear, pHours, pMinutes, pSeconds)   // Set file modified time
                                                                               // & date for ".."
   Cluster = DataToCluster()
   InsertByteIntoBuffer($1A, Cluster.Byte0)   // Cluster number for "."      
   InsertByteIntoBuffer($1B, Cluster.Byte1)    
   TempCurrentSector = File.CurrentSector
   If File.DirectoryStartSector = Disk.RootDirectory Then
      Cluster = 0
   Else
      File.CurrentSector = File.DirectoryStartSector
      Cluster = DataToCluster()
      File.CurrentSector = TempCurrentSector     
   EndIf
   InsertByteIntoBuffer($3A, Cluster.Byte0)   // Cluster number for ".."     
   InsertByteIntoBuffer($3B, Cluster.Byte1)    
   File.CurrentSectorPos = $1FF               // Leave CurrentSectorPos as $1FF to force sector write
   CloseFile()
   Clear(File.CurrentSectorBuffer)
   Index = 1
   Repeat                                     // Clear remaining sectors of first cluster in directory
      WriteSector(TempCurrentSector + Index)
      Inc(Index)
   Until Index = Disk.SectorsPerCluster 
End Function
#endif
{
********************************************************************************
* Name    : MkDir (OVERLOAD)                                                   *
* Purpose : Make a new directory.                                              *
*         : Return: errOK = Success, errExists = Directory already exists,     *
*         : errDiskFull = Disk full, errRootDirFull = Root Directory full,     *
*         : errInUse = File Number in use, errRWError = Read/Write error       *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function MkDir(pDirName As String, pDay As Byte = 1, pMonth As Byte = 1, pYear As Byte = 1,
                      pHours As Byte = 0, pMinutes As Byte = 0, pSeconds As Byte = 0,
                      pHSeconds As Byte = 0) As Byte
   Result = MkDir(StrToFileDirName(pDirName), pDay, pMonth, pYear, pHours, pMinutes, pSeconds, pHSeconds)
End Function
#endif
{
********************************************************************************
* Name    : OpenFile (OVERLOAD)                                                *
* Purpose : Open an existing file.                                             *
*         : Return: errOK = Success, errNotFound = File not found,             *
*         : errInUse = File number in use, errRWError = Read/write error       *
********************************************************************************
}   
Public Function OpenFile(pFileName As TSDName) As Byte
   If Disk.RWError Then
      OpenFile = errRWError
      Exit
   EndIf
   If File.IsOpen Then
      OpenFile = errInUse
      Exit
   EndIf
   If Not(FileExists(pFileName)) Then           // Check if file exists
      OpenFile = errNotFound                    // Error - file does not exist
      Exit                                                                    
   EndIf
   SectorInitRead(File.CurrentSector)
   File.CurrentSectorInCluster = 1
   File.CurrentSectorPos = 0                    // Set start position for sector 
   File.BytesRead = 0                           // Zero sequential read marker
   If File.Size > 0 Then
      File.EOF = False
   Else
      File.EOF = True
   EndIf
   File.IsOpen = True
   File.RW = 1
   OpenFile = errOK                             // Success - file open for reading
End Function                                                                        
{
********************************************************************************
* Name    : OpenFile (OVERLOAD)                                                *
* Purpose : Open an existing file.                                             *
*         : Return: errOK = Success, errNotFound = File Not found,             *
*         : errInUse = File Number in use, errRWError = Read/Write error       *
********************************************************************************
}   
Public Function OpenFile(pFileName As String) As Byte
   Result = OpenFile(StrToFileDirName(pFileName))
End Function
{
********************************************************************************
* Name    : ReadByte (PUBLIC)                                                  *
* Purpose : Read a single byte from an existing file                           *
*         : File.EOF = True when EOF condition met                             *
********************************************************************************
}   
Public Function ReadByte() As Byte
   If File.EOF Then
      ReadByte = 0
      Exit
   EndIf
   Inc(File.BytesRead)
   If File.BytesRead >= File.Size Then
      File.EOF = True
   EndIf
   If File.CurrentSectorPos > $1FF Then
      If Disk.IsDirty Then                      // If bytes have been written in current sector,
                                                // write sector back to SD card and reset Disk.IsDirty
         WriteSector(File.CurrentSector)
         Disk.IsDirty = False
      EndIf
      IncrementSector()
      SectorInitRead(File.CurrentSector)
   EndIf
   ReadByte = File.CurrentSectorBuffer(File.CurrentSectorPos)
   Inc(File.CurrentSectorPos)
End Function
{
********************************************************************************
* Name    : EOF (PUBLIC)                                                       *
* Purpose : Determines if a file opened for reading has reached the end of     *
*         : file (EOF), or if RWError to prevent blocking loop when reading.   *
*         : Return: False = EOF not yet reached, True = EOF reached            *
********************************************************************************
}   
Public Function EOF() As Boolean
   Result = File.EOF Or Disk.RWError
End Function   
{
********************************************************************************
* Name    : ReadBoolean (PUBLIC)                                               *
* Purpose : Read a Boolean from file.                                          *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadBoolean() As Boolean
   ReadBoolean = Boolean(ReadByte())
End Function
{
********************************************************************************
* Name    : ReadBit (PUBLIC)                                                   *
* Purpose : Read a Bit from file.                                              *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadBit() As Bit
   ReadBit = Bit(ReadByte())
End Function
{
********************************************************************************
* Name    : ReadChar (PUBLIC)                                                  *
* Purpose : Read a Char from file.                                             *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadChar() As Char
   ReadChar = ReadByte()
End Function
{
********************************************************************************
* Name    : ReadShortInt (PUBLIC)                                              *
* Purpose : Read a ShortInt from file.                                         *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadShortInt() As ShortInt
   ReadShortInt = ReadByte()
End Function
{
********************************************************************************
* Name    : ReadWord (PUBLIC)                                                  *
* Purpose : Read a Word from file.                                             *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadWord() As Word
   ReadWord.Byte0 = ReadByte()
   ReadWord.Byte1 = ReadByte()
End Function
{
********************************************************************************
* Name    : ReadInteger (PUBLIC)                                               *
* Purpose : Read a Integer from file.                                          *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadInteger() As Integer
   ReadInteger.Byte0 = ReadByte()
   ReadInteger.Byte1 = ReadByte()
End Function
{
********************************************************************************
* Name    : ReadLongWord (PUBLIC)                                              *
* Purpose : Read a LongWord from file.                                         *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadLongWord() As LongWord
   ReadLongWord.Byte0 = ReadByte()
   ReadLongWord.Byte1 = ReadByte()
   ReadLongWord.Byte2 = ReadByte()
   ReadLongWord.Byte3 = ReadByte()
End Function
{
********************************************************************************
* Name    : ReadLongInt (PUBLIC)                                               *
* Purpose : Read a LongInt from file.                                          *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadLongInt() As LongInt
   ReadLongInt.Byte0 = ReadByte()
   ReadLongInt.Byte1 = ReadByte()
   ReadLongInt.Byte2 = ReadByte()
   ReadLongInt.Byte3 = ReadByte()
End Function
{
********************************************************************************
* Name    : ReadFloat (PUBLIC)                                                 *
* Purpose : Read a Float from file.                                            *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Public Function ReadFloat() As Float
   ReadFloat.Byte0 = ReadByte()
   ReadFloat.Byte1 = ReadByte()
   ReadFloat.Byte2 = ReadByte()
   ReadFloat.Byte3 = ReadByte()
End Function
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a Boolean from file.                                          *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As Boolean)
   pValue = Boolean(ReadByte())
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a Bit from file.                                              *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As Bit)
   pValue = Bit(ReadByte())
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a Byte from file.                                             *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As Byte)
   pValue = ReadByte()
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a Char from file.                                             *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As Char)
   pValue = ReadByte()
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a ShortInt from file.                                         *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As ShortInt)
   pValue = ReadByte()
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a Word from file.                                             *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As Word)
   pValue = ReadWord()
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a Integer from file.                                          *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As Integer)
   pValue = ReadWord()
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a LongWord from file.                                         *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As LongWord)
   pValue = ReadLongWord()
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a LongInt from file.                                          *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As LongInt)
   pValue = ReadLongWord()
End Sub
{
********************************************************************************
* Name    : ReadItem (OVERLOAD)                                                *
* Purpose : Read a Float from file.                                            *
*         : Check EOF for EOF condition                                        *
********************************************************************************
}  
Sub ReadItem(ByRef pValue As Float)
   pValue = ReadFloat()
End Sub
{
********************************************************************************
* Name    : Read (COMPOUND)                                                    *
* Purpose : Read one or more items from file.                                  *
*         : Check EOF for EOF condition                                        *
********************************************************************************
} 
Public Compound Sub Read(ReadItem)
{
********************************************************************************
* Name    : Dir (OVERLOAD)                                                     *
* Purpose : Read file names from SD/MMC card.                                  *
*         : pDirDirection: dirFirst = First, dirNext = Next,                   *
*         : dirPrevious = Previous.                                            *
*         : pFileDir: sdFile = Find file, sdDirectory = Find directory.        *
*         : Return: If file found, returned as pFileName, if no file found,    *
*         : pFileName.Name = Null, pFileName.Extension = Null                  *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Sub Dir(ByRef pName As TSDName, pDirDirection As Byte = dirNext, pFileDir As Bit = sdFile)
#else
Public Sub Dir(ByRef pName As TSDName, pDirDirection As Byte = dirNext)
#endif
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   If pFileDir <> File.DirFileDir Then
      File.Number = 0   // If swap from from searching for file to directory or vice versa,
                        // reset file number
      File.DirFileDir = pFileDir
   EndIf
#endif   
   Select pDirDirection
   Case 0
      File.Number = 1
   Case 1 
      Inc(File.Number)
   Case 2
      Dec(File.Number)
   EndSelect
   pName.Name = Null
   pName.Extension = Null
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   If Not(FindRootDirEntry(pName, 2, pFileDir)) Then
#else
   If Not(FindRootDirEntry(pName, 2, 0)) Then
#endif
      pName.Name = Null
      pName.Extension = Null
   EndIf
End Sub
{
********************************************************************************
* Name    : Dir (OVERLOAD)                                                     *
* Purpose : Read file names from SD/MMC card.                                  *
*         : pDirDirection: dirFirst = First, dirNext = Next,                   *
*         : dirPrevious = Previous.                                            *
*         : pFileDir: sdFile = Find File, sdDirectory = Find directory.        *
*         : Return: If file found, returned as string, e.g FILE0001.TXT,       *
*         : if no file found, return string is Null                            *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function Dir(pDirDirection As Byte = dirNext, pFileDir As Bit = sdFile) As String * 13
#else
Public Function Dir(pDirDirection As Byte = dirNext) As String * 13
#endif
Dim TempName As TSDName
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   Dir(TempName, pDirDirection, pFileDir)
#else
   Dir(TempName, pDirDirection)
#endif
   If TempName.Name <> Null Then
      Result = TempName.Name 
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
      If pFileDir = sdFile Then
         Result = Result + "." + TempName.Extension
      EndIf
#else
      Result = Result + "." + TempName.Extension
#endif
   Else
      Result = Null
   EndIf
End Function
{
********************************************************************************
* Name    : TraverseClusters (PRIVATE)                                         *
* Purpose : Traverse cluster chain in FAT                                      *
********************************************************************************
}   
Sub TraverseClusters(pNumBytesToTraverse As LongWord)
Dim Cluster As Word
   Cluster = File.FirstCluster
   File.CurrentSector = 0
   While pNumBytesToTraverse > ($200 * Disk.SectorsPerCluster)
      ClusterToFATSector(Cluster)                                // Find FAT position for cluster
      If File.CurrentSector <> (Disk.FAT1 + File.FATSector) Then
         File.CurrentSector = Disk.FAT1 + File.FATSector
         SectorInitRead(File.CurrentSector)
      EndIf     
      Cluster = ReadFATEntry(File.FATSectorPos)
      pNumBytesToTraverse = pNumBytesToTraverse - ($200 * Disk.SectorsPerCluster)
   Wend
   ClusterToFATSector(Cluster)                                   // Find FAT position for last cluster
   If pNumBytesToTraverse = 0 Then                               // Test for zero length file - others
                                                                 // will not meet this
                                                                 // condition, as 1 or more bytes
                                                                 // will be left after while - wend loop
      File.CurrentSectorInCluster = 0
   Else
      File.CurrentSectorInCluster = (pNumBytesToTraverse - 1) / $200
   EndIf
   File.CurrentSector = ClusterToData(Cluster)                   // Set append sector for data
   File.CurrentSector = File.CurrentSector + File.CurrentSectorInCluster 
   File.CurrentSectorInCluster = File.CurrentSectorInCluster + 1
   SectorInitRead(File.CurrentSector)
   File.CurrentSectorPos = pNumBytesToTraverse Mod $200          // Set append position for sector 
   If File.CurrentSectorPos = 0 Then                                              
      If pNumBytesToTraverse > 0 Then                            // Test not zero length file - others will 
                                                                 // meet this condition
         File.CurrentSectorPos = $200
         Inc(File.CurrentSector)
      EndIf
   EndIf
   File.ClusterSequenceIndex = 1
   File.ClusterSequenceNumber = 1
End Sub
{
********************************************************************************
* Name    : FSeek (PUBLIC)                                                     *
* Purpose : Seeks position in open file using pFilePtr.                        *
*         : Return: errOK = Success, errRWError = Read/Write error,            *
*         : errFileNotOpen = File not open, errBeyondEOF = Failed attemp to    *
*         : seek position beyond end of file                                   *
********************************************************************************
}   
Public Function FSeek(pFilePtr As LongWord) As Byte
Dim FileSizeHold As LongWord
   If Disk.RWError Then
      FSeek = errRWError
   ElseIf File.IsOpen = False Then
      FSeek = errFileNotOpen
   Else
      If Disk.IsDirty Then                                             // If bytes have been written in
                                                                       // current sector, write sector back
                                                                       // to SD card and reset IsDirty
         WriteSector(File.CurrentSector)
         Disk.IsDirty = False
      EndIf  
      If File.EOF Then                                                 // Are at end or have extended file
         FileSizeHold = File.Size
         CloseFile()
         File.Size = FileSizeHold
         File.IsOpen = True
      EndIf
      If pFilePtr > File.Size Then
         FSeek = errBeyondEOF
      Else
         TraverseClusters(pFilePtr)
         File.BytesRead = pFilePtr
         If pFilePtr = File.Size Then
            File.EOF = True
         Else
            File.EOF = False
         EndIf
         FSeek = errOK                                                 // Success - file seek OK    
      EndIf
   EndIf
End Function
{
********************************************************************************
* Name    : OpenFileRW (OVERLOAD)                                              *
* Purpose : Open an existing file for reading & writing.                       *
*         : Return: errOK = Success, errNotFound = File not found,             *
*         : errDiskFull = Disk full, errInUse = File in use,                   *
*         : errRWError = Read/write error                                      *
********************************************************************************
}   
Public Function OpenFileRW(pFileName As TSDName) As Byte
Dim Response As Byte
Dim Cluster As Word
   If Disk.RWError Then
      OpenFileRW = errRWError
   ElseIf File.IsOpen Then
      OpenFileRW = errInUse
   ElseIf Not(FileExists(pFileName)) Then                              // Check if file exists & loads info
                                                                       // r.e. file
      OpenFileRW = errNotFound                                         // Error - file does not exist
   Else
      If File.Size = 0 Then
         File.FATSector = 0
         File.FATSectorPos = 0
         File.ClusterSequenceNumber = 0
         Response = FindFreeCluster()
         If Response = 1 Then                                          // Disk full error
            OpenFileRW = errDiskFull
            Exit
         EndIf   
         Cluster = FATSectorToCluster() 
         File.FirstCluster = Cluster                                   // Store first cluster
         File.CurrentSector = ClusterToData(Cluster)                   // Set start sector for data
         SectorInitWrite()
         File.CurrentSectorInCluster = 1
         File.CurrentSectorPos = 0
         File.EOF = True     
      Else
         File.FirstCluster = DataToCluster()
         TraverseClusters(0)                                           // Move to start of file
         File.EOF = False
      EndIf
      Disk.DiskFull = False
      File.IsOpen = True
      File.RW = 0
      File.BytesRead = 0
      Disk.IsDirty = False
      OpenFileRW = errOK                                               // Success - file open for appending
   EndIf
End Function
{
********************************************************************************
* Name    : OpenFileRW (OVERLOAD)                                              *
* Purpose : Open an existing file for reading & writing.                       *
*         : Return: errOK = Success, errNotFound = File not found,             *
*         : errDiskFull = Disk full, errInUse = File in use,                   *
*         : errRWError = Read/write error                                      *
********************************************************************************
}   
Public Function OpenFileRW(pFileName As String) As Byte
   Result = OpenFileRW(StrToFileDirName(pFileName))
End Function
{
********************************************************************************
* Name    : AppendFile (OVERLOAD)                                              *
* Purpose : Open an existing file for writing to end of file.                  *
*         : Return: errOK = Success, errNotFound = File not found,             *
*         : errDiskFull = Disk full, errInUse = File in use,                   *
*         : errRWError = Read/write error                                      *
********************************************************************************
}   
Public Function AppendFile(pFileName As TSDName) As Byte
   Result = OpenFileRW(pFileName)
   FSeek(File.Size)
End Function
{
********************************************************************************
* Name    : AppendFile (OVERLOAD)                                              *
* Purpose : Open an existing file for writing to end of file.                  *
*         : Return: errOK = Success, errNotFound = File Not found,             *
*         : errDiskFull = Disk full, errInUse = File in use,                   *
*         : errRWError = Read/Write error                                      *
********************************************************************************
}   
Public Function AppendFile(pFileName As String) As Byte
   Result = OpenFileRW(pFileName)
   FSeek(File.Size)
End Function
{
********************************************************************************
* Name    : DiskSizeKB (PUBLIC)                                                *
* Purpose : Find size of SD/MMC card (in KB)                                   *
********************************************************************************
}   
Public Function DiskSizeKB() As LongWord
   DiskSizeKB = (Disk.LastCluster - 1) * Disk.SectorsPerCluster
   DiskSizeKB = DiskSizeKB / 2
End Function
{
********************************************************************************
* Name    : FreeSpaceKB (PUBLIC)                                               *
* Purpose : Find free space on SD/MMC card (in KB)                             *
********************************************************************************
}   
Public Function FreeSpaceKB() As LongWord
Dim FreeClusterNumber As Word
Dim ScanSector As LongWord
Dim ScanSectorPos As Word
Dim Correction As LongWord
   FreeClusterNumber = 0
   For ScanSector = Disk.FAT1 To (Disk.FAT1 + Disk.SectorsPerFAT - 1)   // Scan FAT1 for free clusters
      File.CurrentSector = ScanSector
      SectorInitRead(File.CurrentSector)
      For ScanSectorPos = $000 To $1FF Step 2
         If ReadFATEntry(ScanSectorPos) = $0000 Then
            Inc(FreeClusterNumber)
         EndIf
      Next
   Next
   Correction = Disk.SectorsPerFAT * $100                               
   Correction = Correction - Disk.LastCluster - 1                      
   FreeSpaceKB = FreeClusterNumber - Correction
   FreeSpaceKB = FreeSpaceKB * Disk.SectorsPerCluster
   FreeSpaceKB = FreeSpaceKB / 2
End Function
{
********************************************************************************
* Name    : Delete (PRIVATE)                                                   *
* Purpose : Delete an existing file or directory.                              *
*         : Return: True = Success, False = File not found                     *
********************************************************************************
}   
Function Delete(pFileName As TSDName, pIsFile As Boolean) As Boolean
Dim ResponseB As Boolean
Dim FirstPass As Boolean
Dim Cluster As Word
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   If pIsFile Then
      ResponseB = FileExists(pFileName)                            // Check if file exists
   Else
      ResponseB = DirExists(pFileName)                             // Check if directory exists
   EndIf                                                           
#else
   ResponseB = FileExists(pFileName)                               // Check if file exists
#endif
   If Not(ResponseB) Then
      Delete = False                                               // Error - file/directory does not exist
      Exit                                                                    
   EndIf
   InsertByteIntoBuffer(File.FileEntryDirectorySectorPos, $E5)    
   WriteSector(File.FileEntryDirectorySector)
   If File.Size = 0 And pIsfile Then                               // Zero-length file - no need to
                                                                   // modify FAT
      Delete = True
      Exit
   EndIf
   Cluster = DataToCluster()
   FirstPass = True                                                // Use as flag for first pass
                                                                   // through repeat-until loop
   Repeat
      ClusterToFATSector(Cluster)                                  // Find next FAT position
      If File.CurrentSector <> (Disk.FAT1 + File.FATSector) Then   // Accessing new sector
         If FirstPass = False Then
            WriteSector(File.CurrentSector)                        // Write FAT1 sector before read next 
                                                                   // sector, except on first pass
            File.CurrentSector = File.CurrentSector - Disk.FAT1    // Copy FAT1 sector to FAT2
            File.CurrentSector = File.CurrentSector + Disk.FAT2
            WriteSector(File.CurrentSector)                        // Write FAT2 sector before read next 
                                                                   // sector, except on first pass
         EndIf
         File.CurrentSector = Disk.FAT1 + File.FATSector
         FirstPass = False
         SectorInitRead(File.CurrentSector)
      EndIf
      Cluster = ReadFATEntry(File.FATSectorPos)
      InsertByteIntoBuffer(File.FATSectorPos, $00)                 // Clear first cluster byte in FAT
      InsertByteIntoBuffer(File.FATSectorPos + 1, $00)             // Clear first cluster byte in FAT
   Until Cluster = $FFFF
   WriteSector(File.CurrentSector)                                 // Write last FAT1 sector
   File.CurrentSector = File.CurrentSector - Disk.FAT1             // Copy FAT1 sector to FAT2
   File.CurrentSector = File.CurrentSector + Disk.FAT2
   WriteSector(File.CurrentSector)                                 // Write last FAT2 sector
   Delete = True                                                   // Success - file deleted
End Function
{
********************************************************************************
* Name    : DeleteFile (OVERLOAD)                                              *
* Purpose : Delete an existing file.                                           *
*         : Return: True = Success, False = File not found                     *
********************************************************************************
}   
Public Function DeleteFile(pFileName As TSDName) As Boolean
   Result = Delete(pFileName, True)
End Function
{
********************************************************************************
* Name    : DeleteFile (OVERLOAD)                                              *
* Purpose : Delete an existing file.                                           *
*         : Return: True = Success, False = File not found                     *
********************************************************************************
}   
Public Function DeleteFile(pFileName As String) As Boolean
   Result = Delete(StrToFileDirName(pFileName), True)
End Function
{
********************************************************************************
* Name    : QuickFormat (PUBLIC)                                               *
* Purpose : Quick formats SD/MMC card.                                         *
*         : Will delete all files, but does not rebuild a corrupt file         *
*         : structure                                                          *
*         : Return: True = Success, False = Read/write error                   *
********************************************************************************
}   
Public Function QuickFormat() As Boolean
   If Disk.RWError Then
      QuickFormat = False
      Exit
   EndIf
   Clear(File.CurrentSectorBuffer)
   For File.CurrentSector = Disk.RootDirectory To (Disk.RootDirectory + Disk.SectorsInRoot - 1)   
                                                                           // Scan Root Directory & delete
      WriteSector(File.CurrentSector)
   Next
   For File.CurrentSector = (Disk.FAT1 + 1) To (Disk.FAT1 + Disk.SectorsPerFAT - 1)  // Scan FAT1 & delete
      WriteSector(File.CurrentSector)
   Next
   For File.CurrentSector = (Disk.FAT2 + 1) To (Disk.FAT2 + Disk.SectorsPerFAT - 1)  // Scan FAT2 & delete
      WriteSector(File.CurrentSector)
   Next
   File.CurrentSectorBuffer(0) = $F8
   File.CurrentSectorBuffer(1) = $FF
   File.CurrentSectorBuffer(2) = $FF
   File.CurrentSectorBuffer(3) = $FF
   WriteSector(Disk.FAT1)
   WriteSector(Disk.FAT2)
   QuickFormat = True
End Function
{
********************************************************************************
* Name    : SaveFile(PUBLIC)                                                   *
* Purpose : Write all of buffer to file and update file size & FAT clusters.   *
*         : If disk removed, file should be safe. Has same effect as cloing    *
*         : file and re-opening with AppendFile, except works quicker          *
*         : Return: True = Success, False = Read/write error                   *
********************************************************************************
}   
Public Function SaveFile() As Boolean
Dim TempCurrentSectorPos As Word
Dim TempCurrentSector As LongWord
Dim TempFileSize As LongWord
   If Disk.RWError Then
      SaveFile = False
      Exit
   EndIf
   TempCurrentSectorPos = File.CurrentSectorPos   // Store pointers to temp pointers
   TempCurrentSector = File.CurrentSector
   TempFileSize = File.Size
   CloseFile()                                    // Save file size etc..
   File.IsOpen = True
   File.CurrentSector = TempCurrentSector         // Recover pointers from temp pointers
   ReadSector(File.CurrentSector)                 // Recover buffer contents
   File.CurrentSectorPos = TempCurrentSectorPos
   File.Size = TempFileSize
   SaveFile = True
End Function
{
********************************************************************************
* Name    : FileSize (PUBLIC)                                                  *
* Purpose : Find size of file (in Bytes)                                       *
********************************************************************************
}   
Public Function FileSize() As LongWord
   FileSize = File.Size
End Function
{
********************************************************************************
* Name    : RmDir (OVERLOAD)                                                   *
* Purpose : Remove an existing directory.                                      *
*         : Return: errOK = Success, errNotFound = Directory not found,        *
*         : errDirNotEmpty = Directory not empty, errRWError = Read/write      *
*         : error                                                              *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function RmDir(pDirName As TSDName) As Byte
Dim TempName As TSDName
   If Disk.RWError Then
      RmDir = errRWError
      Exit
   EndIf
   If Not(ChDir(pDirName)) Then                // Change into directory
      RmDir = errNotFound                      // Error - directory does not exist
      Exit                                                                    
   EndIf
   Dir(TempName, dirFirst, sdFile)
   If (TempName.Name <> Null) Or (TempName.Extension <> Null) Then
      RmDir = errDirNotEmpty                   // Error - directory is not empty - file(s) exists
      ChDir(cdUp)
      Exit                                                                    
   EndIf
   Dir(TempName, dirFirst, sdDirectory)
   Repeat
      If (TempName.Name <> Null) And (TempName.Name <> ".") And (TempName.Name <> "..") Then
         RmDir = errDirNotEmpty                // Error - directory is not empty - sub-directory(s) exists
         ChDir(cdUp)
         Exit                                                                    
      EndIf
      Dir(TempName, dirNext, sdDirectory)
   Until TempName.Name = Null
   ChDir(cdUp)
   Delete(pDirName, False)
   RmDir = errOK               // No need to check if directory not found on delete, since already checked
End Function
#endif
{
********************************************************************************
* Name    : RmDir (OVERLOAD)                                                   *
* Purpose : Remove an existing directory.                                      *
*         : Return: errOK = Success, errNotFound = Directory Not found,        *
*         : errDirNotEmpty = Directory Not empty, errRWError = Read/Write      *
*         : error                                                              *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function RmDir(pDirName As String) As Byte
   Result = RmDir(StrToFileDirName(pDirName))
End Function
#endif
{
********************************************************************************
* Name    : Rename (OVERLOAD)                                                  *
* Purpose : Rename a file from pOldName to pNewName.                           *
*         : pFileDir: sdFile = renaming file, sdDirectory = renaming directory *
*         : Return: errOK = Success, errExists = new file/dir name already     *
*         : exists, errNotFound = old file/dir name not found,                 *
*         : errRWError = Read/write error                                      *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function Rename(pOldName As TSDName, pNewName As TSDName, pFileDir As Bit = sdFile) As Byte
#else
Public Function Rename(pOldName As TSDName, pNewName As TSDName) As Byte
#endif
   If Disk.RWError Then
      Rename = errRWError
      Exit
   EndIf
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   If pFileDir = sdDirectory Then
      pOldName.Extension = ""                             // Clear extension if directory
      pNewName.Extension = ""
   EndIf
#endif
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   If FindRootDirEntry(pNewName, 1, pFileDir) Then        // Check new name does not already exist
#else
   If FindRootDirEntry(pNewName, 1, sdFile) Then
#endif
      Rename = errExists
      Exit
   EndIf
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   If Not(FindRootDirEntry(pOldName, 1, pFileDir)) Then   // Check old name does exist;
                                                          // also loads File.FileEntryDirectorySector & Pos
#else
   If Not(FindRootDirEntry(pOldName, 1, sdFile)) Then
#endif
      Rename = errNotFound
      Exit
   EndIf
   ReadSector(File.FileEntryDirectorySector)
   InsertFileNameIntoBuffer(pNewName)                     // Overwrite with new name
   WriteSector(File.FileEntryDirectorySector)
   Rename = errOK
End Function
{
********************************************************************************
* Name    : Rename (OVERLOAD)                                                  *
* Purpose : Rename a file from pOldName to pNewName.                           *
*         : pFileDir: sdFile = renaming file, sdDirectory = renaming directory *
*         : Return: errOK = Success, errExists = New file/dir name already     *
*         : exists, errNotFound = old File/Dir Name Not found,                 *
*         : errRWError = Read/Write error                                      *
********************************************************************************
}   
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
Public Function Rename(pOldName As String, pNewName As String, pFileDir As Bit = sdFile) As Byte
#else
Public Function Rename(pOldName As String, pNewName As String) As Byte
#endif
#if SD_SUPPORT_SUB_DIRECTORIES = TRUE Then
   Result = Rename(StrToFileDirName(pOldName), StrToFileDirName(pNewName), pFileDir)
#else
   Result = Rename(StrToFileDirName(pOldName), StrToFileDirName(pNewName))
#endif
End Function
{
********************************************************************************
* Name    : DiskMounted (PUBLIC)                                               *
* Purpose : Checks if disk is mounted - must initialise disk first with Init() *
*         : before disk will return DiskMounted as True; removing disk after   *
*         : this will return DiskMounted as False.                             *
*         : Return: True = Disk mounted, False = Disk not mounted              *
********************************************************************************
}
Public Function DiskMounted() As Boolean
   ReadSector(0, False)
   DiskMounted = Not(Disk.RWError)
End Function
{
********************************************************************************
* Name    : RWError (PUBLIC)                                                   *
* Purpose : Determines if a disk read/write error has occured.                 *
*         : Return: False = No error occurred, True = Error occurred, only     *
*         : cleared by Init()                                                  *
********************************************************************************
}   
Public Function RWError() As Boolean
   Result = Disk.RWError
End Function   
{
********************************************************************************
* Name    : IsOpen (PUBLIC)                                                    *
* Purpose : Determines if a file is in use.                                    *
*         : Return: True = File in use, False = File not in use                *
********************************************************************************
}   
Public Function IsOpen() As Boolean
   Result = File.IsOpen
End Function   
{
********************************************************************************
* Name    : FilePtr (PUBLIC)                                                   *
* Purpose : Returns the current file pointer position                          *
********************************************************************************
}   
Public Function FilePtr() As LongWord
   Result = File.BytesRead
End Function   


// module initialisation...
#if SD_SPI = MSSP
   SSPControl1 = %00100010  // Setup MSSP module at spiOscDiv64 initially
   SSPStatus = %11000000                         
#endif
Disk.RWError = True         // Start module with read/write error to prevent disk access until initialised