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.
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