Published using Google Docs
( EASYFILE.FTH )
Updated automatically every 5 minutes

TACHYON

[~

IFNDEF SDCARD.fth

                 CR ." !!!  This module requires a memory driver such as SDCARD.fth !!!          "

!!!

}

IFDEF PROMPT   OFF PROMPT }

FORGET EASYFILE.fth

pub EASYFILE.fth    PRINT" FAT32 Virtual Memory Access File System Layer V1.1 150213-1530 " ;

{

CHANGELOG:

150201 Add CD and PWD operations

150121        renamed mount to mounted to avoid case confusion

141211        Improved reporting in MOUNT

141208        Added ?AUTOLOAD to check and verify new firmware on media

141108        Added FSECT@ to read the starting sector, needed to access files for cards > 4GB

140930        Added FPRINT$ word and cat command which takes a string as the filename and prints its contents if it exists. -

140924 Invalidate sector variable after a SAVEROM before FSTAMP'ing the file so that FS will read in the directory block before writing!

140919        Added FDATE! and FTIME! to update directory entry and flush. DIR! will flush the current entry

1409xx        

140626 Added QV for quick view of file header

          Added FC! word to access relative to a file and only in the file - more words to follow and methods to be updated

140425 Changed name to EASYFILE to keep this standard 8.3 naming plus emphasis on easy access

140213 ??

140123 Fixed bug in FLUSH which left flag on stack. Upgraded timers

131129 Changed the name from MULTIFAT to MULTIFILE with more emphasis on FILE vs FAT

DESCRIPTION

Provide access to FAT formatted SD cards allowing virtual memory access to the first 4GB of the card.

The driver is primarily written for datalogging applications and for loading source code files from SD but

may be expanded to include full FAT32 file access words. FAT16 is not deemed to be relevant anymore.

VMFS FEATURES

  • UP TO 4 FILES OPEN SIMULTANEOUSLY
  • VIRTUAL MEMORY ADDRESSING OF FIRST 4GB
  • PREFORMATTED FILES SIMPLIFIES RUNTIME ACCESS
  • MINIMIZES SD WEAR
  • FAIL-SAFE "EJECT ANYTIME"

DESIGN APPROACH

I favor preformatting the card with the names and sizes of the files that I would use for datalogging rather than creating a file that grows and requires messing with the FAT and cluster table and directory entries. This approach ensures that runtime operations are not complicated by file and FAT management and can simply find and open a file and read and write virtual memory rather than the archaic methods that developed from sequential tape access.

Consider this preformatted card with these files:

LOG0001 .TXT .....a 0000.3B28   04/11/2013 01:25:18   131,072

LOG0002 .TXT .....a 0000.3C28   04/11/2013 01:25:18   131,072

LOG0003 .TXT .....a 0000.3D28   04/11/2013 01:25:18   131,072

LOG0004 .TXT .....a 0000.3E28   04/11/2013 01:25:18   131,072

LOG0005 .TXT .....a 0000.3F28   04/11/2013 01:25:18   131,072

LOG0006 .TXT .....a 0000.4028   04/11/2013 01:25:18   131,072

LOG0007 .TXT .....a 0000.4128   04/11/2013 01:25:18   131,072

LOG0008 .TXT .....a 0000.4228   04/11/2013 01:25:18   131,072

SYSLOG  .TXT .....a 0000.4B28   04/11/2013 19:02:22   1,048,562

ROM     .BIN .....a 0000.5650   13/09/2013 13:07:42   32,768

The files may just be totally filled with blanks but they are created on the PC and copied to a fresh SD card. What results is that virtually all the FAT management has been taken care of by the very system that worries most about it, the PC. The files are also non-fragmented, we can access this is a linear fashion without trying to follow the FAT cluster chain.

For our embedded application we only need to worry about the data in or for the files. When a datalogging app opens the current log file which for instance is LOG0001.TXT then it returns with a simple pointer to the start of the file in virtual memory, addressing the first 4GB with a 32-bit address. Of course EASYFILE also knows the directory address and starting sector of the file which may also be outside the 4GB range but all we are concerned with is the first 4GB for now.

Look how simple it becomes to divert character output to a file, in this case we open the file, play with checking it's address and sector etc, look at the first 32 bytes of the file and then list MODULES to the file, finally examing some of the results.

FOPEN LOG0001.TXT...opened at 0000.3B28  ok

FILE@ . 765000 ok

3B28 9 SHL . 765000 ok

FILE@ 20 XDUMP

0076_5000:   20 20 20 20  20 20 20 20   20 20 20 20  20 20 20 20                       

0076_5010:   20 20 20 20  20 20 20 20   20 20 20 20  20 20 20 20                       

FOUTPUT MODULES CON  ok

FILE@ 40 XDUMP

0076_5000:   0D 0A 4D 4F  44 55 4C 45   53 20 4C 4F  41 44 45 44   ..MODULES LOADED

0076_5010:   3A 20 0D 0A  34 37 31 39   3A 20 4E 45  54 57 4F 52   : ..4719: NETWOR

0076_5020:   4B 2E 66 74  68 20 20 20   20 20 20 20  20 20 57 49   K.fth         WI

0076_5030:   5A 4E 45 54  20 4E 45 54   57 4F 52 4B  20 53 45 52   ZNET NETWORK SER

Of course the size of the file is still 131,072 bytes but what does this matter to a text viewer if the remaining lines are blank? We can also optionally mark the end of a text file with a null character. What happens if we power off and back on again and then want to append? There are a few approaches and as mentioned we could always write a null character at the end basically by writing a $00 after every character that's written (FOUTPUT is now updated to do this). Then all we need to do is find that null when we open the file and set the write pointer "fptr" to that. A binary search is implemented with APPEND to find the end of the file quickly but SDRD also scans for a match character too which is a mechanism that could be used.

So which files are active and which aren't? Before we go into that we will look at how the directory can be accessed and for ease of access I have just defined @ROOT which gives us the virtual address of the start of the root directory and then listed out the first 128 bytes (@ROOT added to source)

@ROOT 80 XDUMP

0076_4000:   50 42 4A 54  45 43 48 2D   53 44 4C 08  00 00 66 A0   PBJTECH-SDL...f.

0076_4010:   77 43 77 43  00 00 66 A0   77 43 00 00  00 00 00 00   wCwC..f.wC......

0076_4020:   4C 4F 47 30  30 30 31 20   54 58 54 20  00 64 78 A0   LOG0001 TXT .dx.

0076_4030:   77 43 77 43  00 00 29 0B   64 43 03 00  00 00 02 00   wCwC..).dC......

0076_4040:   4C 4F 47 30  30 30 32 20   54 58 54 20  00 64 78 A0   LOG0002 TXT .dx.

0076_4050:   77 43 77 43  00 00 29 0B   64 43 23 00  00 00 02 00   wCwC..).dC#.....

0076_4060:   4C 4F 47 30  30 30 33 20   54 58 54 20  00 64 78 A0   LOG0003 TXT .dx.

0076_4070:   77 43 77 43  00 00 29 0B   64 43 43 00  00 00 02 00   wCwC..).dCC..... ok

Notice that if we keep to standard 8.3 names (i.e. FILENAME.EXT)  in capitals when we create the files then they will have nice clean file names on 32 byte boundaries. The file names also include the media name (1st entry). Within each entry is the information needed to find the first cluster of the file, the creation and modified date, the file size, and attributes (archive, read-only etc). Although we could include code to handle long file names, I just detest the convoluted way they are encoded and there is simply no need for them, even a camera just uses standard 8.3 names for it's files.

If we were to delete LOG0002.TXT on the PC you would still see the directory entry except the first character would be replaced with a ? symbol and the PC will not display the file in it's directory listing because of this. Now the PC also releases the clusters that the file uses but we never want to do that, we never need to, there is so much memory available that we can have it all set aside. So if we simply replace the first character of a file with a ? then it will appear deleted to a PC even though we have not released the clusters that it uses. This is the way that we could mark the files that are active and those that aren't, and since we know all our log files begin with LOGxxxx then it's no problem to restore it back again.

When we open a file each file has it's own directory entry buffer and pointers so that makes it easy for us to look up and change information without having to shuffle sector buffers around. These buffers are in normal hub memory, here is the buffer for the file we opened:

@DIRBUF 20 DUMP

0000_3AC0:   4C 4F 47 30  30 30 31 20   54 58 54 20  00 64 78 A0   LOG0001 TXT .dx.

0000_3AD0:   77 43 77 43  00 00 29 0B   64 43 03 00  00 00 02 00   wCwC..).dC...... ok

.......more to come .............

( FILE CONTROL WORDS )

FOPEN$ ( name$ -- sector )

Process the name$ as an 8.3 file name and search the root directory for this entry. If found then copy the directory buffer and pointers and return with it's sector number which can be used as result flag. A result of zero indicates failure.

FOPEN <name>

Same as FOPEN$ except this is designed for interactive console use and a name follows

Usage

FOPEN LOG0001.TXT

FCLOSE

Flush any modifed sector to the media and reset directory buffers and pointers for the currently selected file

FILE ( n -- )

Set the current file index. Does not affect any files or pointers but the index is used by various file words.

Usage - open LOG0001.TXT as the 3rd file (indexed 0..3):

2 FILE " LOG0001.TXT" FOPEN$

FILE@ ( xaddr -- )

Return with the memory address (4GB) of the start of the file in virtual memory.

FSECT@ ( sector -- )

Return with the sector addess (2TB) of the start of the file.

FOUTPUT        Redirect all character output to the current file if opened else null out

>FILE                Alias for FOUTPUT

FPUT

FINPUT

FGET

TO DO:

Add file creation words while assigning enough clusters immediately so that no more need to be allocated during appends.

Update: Implement FCREATE which allows last file to grow as needed then FCLOSE will update size. If file is to be overwritten during FTP then delete and FCREATE again so that it allocates memory from the free end of the card. Cluster table needs to be updated to keep any PC read happy.

{ preallocating FCREATE# is then just FCREATE then increment the write pointer as required and do FCLOSE }

Offset FILE@ access from FSECT@ so that files up to 4GB may still be accessed as virtual memory anywhere even if the card is >4GB

MODULE INFORMATION

SMALL BUILD UNRECLAIMED

NAMES:  $5A12...74EB for 6873 (0865 bytes added)

CODE:   $0000...38BD for 7652 (2237 bytes added)

CALLS:  0388 vectors free

RAM:    8533 bytes free

SMALL BUILD RECLAIMED

NAMES:  $5D0C...74EB for 6111 (0103 bytes added)

CODE:   $0000...38BD for 8414 (2237 bytes added)

CALLS:  0388 vectors free

RAM:    9295 bytes free

NOTES:

Using DS to define variables rather than the LONG/WORD/BYTE means the storage space is not interrupted with

bytecodes, just purely data, so it's safe to block erase etc.

0 FILE        APPLICATION

1 FILE        DIR and SYSTEM (SAVEROM)

2 FILE

3 FILE

}

{ 150202 UPDATE NOTES

VIRTUAL FILE MEMORY

To date the virtual memory access words have served me well but they are limited to 4GB addressing which happens to be the smallest SD card I can now buy. When I have been accessing files whose virtual memory address is greater than 4GB I have chosen to use the sector methods instead although this has really only been for music files. The other problem with the virtual memory addressing methods is that you can just as easily write to system areas of the card as address 0 is the very first byte of the boot block on the card. So to address these concerns I am implementing new file access words which address a "file" as virtual memory so that address 0 is the first byte of the file, previously the first byte of the file was addressed with the starting address of the file in the 4GB virtual memory range. So now the address can only access the contents of the file, not beyond its last cluster as it is clipped, but that file can be anywhere on the card, even beyond the first 4GB.

The basic methods for virtual card memory are as you know XADR XC! XC@ X! etc and so the basic methods for virtual file memory are similar as FADR FC! FC@ F! etc. These words work just like the basic Forth memory operations.

FAT32 CLUSTER

Those of you who have used EASYFILE know that I essentially ignore the file allocation table and work with files as flat contiguous address space from the starting address using just the first cluster. As the card would have files created back on the PC I don't try to create files so I can get away without having to mess with the FAT or following cluster chains which is not at all conducive for flat virtual memory anyway. However there are several good reasons for needing to create files at runtime so I am in the middle of testing that except that when I create a file I do not try to use up any loose clusters but work from the end of the used area so that the new file may grow and remain contiguous which is handy for log files and copy operations etc. For other files it is advantageous to preallocate plenty of clusters at creation time.

DIRECTORIES

EASYFILE has up to this point been limited to accessing files only in the root directory but selecting another directory isn't really all that complicated, just a matter of "opening" the directory as you would a file and using its starting address/sector as the current working directory. So I've added methods for this to permit access from the shell in the same way we would on DOS or Linux systems using the "cd" command. A "cd /" will also reset to the root directory while the "cd .." will of course access the ".." entry as a directory to go back to a higher level directory. The "pwd" will work the same and I may make it an option that the path becomes the prompt as it does in PCs.

RETURN STACK

As a side note the kernel was tweaked last week due to uncovering a very strange bug in one of my applications which I found nested a lot of return addresses, more in fact than I had room for in the cog, which is normally 22 levels deep. The return stack is really only ever used for implicit calls as there is the loop stack which is handy for pushing and popping from the data stack. However the return stack size was previously 22 levels as I've never had any applications touch the bottom until recently, and it wasn't even networked, just filesystem stuff but with many layers. So this has been increased to 26 levels and thoroughly tested to see whether I needed to perhaps expand the return stack into hub RAM. Thankfully I didn't need to as the extra code to do this would have to come out of other cog resources and also impact the speed of call/return.

}

--- EASYFILE CODE ---

--- PARTITION TABLE ---

--- Partition table image - copied here

16 LONGS parts                         --- Room for 4 entries of 16 bytes

--- These are the offset constants compiled as absolute addresses (for 1st partition)

--- Many names have not been added to the dictionary to save space

parts        ORG

1        DS+ --- state                    --- 00 = inactive, $80 = active

1        DS+ --- head                   --- beginning of paritiion

2        DS+ --- cylsec                   --- beginning of partition cylinders/sector

1        DS+ --- partype                   --- type of partition

1        DS+ --- ehead                    --- end of partition head

2        DS+ --- ecylsec                   --- end of partitiion cylinder/sector

4        DS fatptr

4        DS+ --- size

--- BOOT RECORD ---

{

Boot Record

The boot record occupies one sector, and is always placed in logical sector number zero of the "partition". If the media is not divided into partitions, then this is the beginning of the media. This is the easiest sector on the partition for the computer to locate when it is loaded. If the storage media is partitioned (such as a hard disk), then the beginning of the actual media contains an MBR (x86) or other form of partition information. In this case each partition's first sector holds a Volume Boot Record.

}

Screenshot from 2015-02-02 10:49:08.png

Screenshot from 2015-02-02 10:45:17.png

#24 LONGS fat32 ( 90/96 used )

fat32        ORG        

3        DS+

8        DS oemname

2        DS byte/sect

1        DS sect/clust

2        DS rsvd

( 16 )

1        DS fats                        --- Copies of FAT

4        DS+                                --- root entry count (FAT16)

1        DS+                                --- media

2        DS+

2        DS+                                --- sect/trk        

2        DS+                                --- heads

4        DS+                                --- hidden sectors

( 32 )

4        DS sectors                        --- Number of sectors * byte/sect (512) = capacity

--- offset = 36

4        DS sect/fat                        --- Number of sectors per FAT table

2        DS+                                 --- fatflags

2        DS+                                --- fatver

4        DS rootcl                        --- Cluster Number of the Start of the Root Directory

2        DS+                                --- info = Sector Number of the FileSystem Information Sector  (from part start)

2        DS+                                --- boot = Sector Number of the Backup Boot Sector (from part start)

#12        DS+

3        DS+

4        DS serial                        --- #67 serial number of partition

#11        DS volname                        --- #71 volume name

8        DS fatname                        --- always FAT32 - (don't trust)

--- create room for some system variables in this table

1        DS mounted                         --- true flag if mounted (but also depends upon other checks)

1        DS clshift                        --- cluster shift (fast  multiplier)

4        DS rootdir                        --- sector address of root directory

--- DIR BUFFERS ---

--- A copy of the original virtual address of the directory entry is kept in this array (4 entries)

4 LONGS diradr                                                --- virtual memory address of file's directory entry

4 LONGS file                                                        --- table entry for 4 open files - holds sector address

--- Directory entry table

BL LONGS dirbuf                                          --- create a directory buffer for the opened file

LONG fboot                                                        --- boot signature - determines whether it needs to remount

--- FAT HANDLERS ---

pub @ROOT ( -- rootdiradr \ virtual memory address of the start of the root directory )

        rootdir @ 9 SHL

         ;

LONG cwdsect

pub @BOOT ( -- bootblkadr )

         fatptr @ 9 SHL

         ;

--- return with the starting address of the selected FAT (normally 0 or 1)

pub @FAT ( fat# -- sector )

        sect/fat @ *

         fatptr @ rsvd W@ +                                        --- look at FAT1 and read sector

         +

        ;

0        == FAT1

0        == FAT2

pub CLUST>SECT ( clust# -- sector )

        rootcl @ - clshift C@ SHL rootdir @ +

        ;

pri READFAT32

         fatptr @ SECTOR                                        --- Read the FAT32 boot record

         SDBUF fat32 #90 CMOVE                                --- backup the FAT32 entry for direct access

        sect/clust C@ >| clshift C!                        --- make a fast multiply using a shift left constant

         rsvd W@ sect/fat @ fats C@ *   

         rootcl @ 2 - clshift C@ << + + fatptr @ + rootdir !

         0 @FAT ' FAT1 1+ !

         1 @FAT ' FAT2 1+ !                        --- save time by precalculating FAT table addresses

        ;

pri .FAT

         CR PRINT" Mounted "

          fatptr @ SECTOR                                        --- read FAT32 boot record

         cid 9 + U@ .LONG                                        --- print SD card serial#

         "-" EMIT serial U@ .LONG

         SPACE oemname 8 CTYPE                                --- print OEM name

         SPACE

         volname #11 CTYPE                                        --- print volume name

         SPACE

         fatname 8 CTYPE                                        --- print FAT32 name

        sectors @ 512 1000000 */ 0 PRINTDEC PRINT" MB ("

        sect/clust C@ BLKSIZ * 0 PRINTDEC PRINT" /cluster)"

\\\         PRINT"  with " sectors @ 0 PRINTDEC PRINT"  sectors" 

        ;

HELP: MOUNT

Mount the currently selected storage device and init all 4 file handles

Read the FAT32 and set variables accordingly

}

pub MOUNT

        mounted C~ wrflgs 4 ERASE

         sector 16 ERASE

         file 16 ERASE

         dirbuf $80 ERASE

         diradr 16 ERASE                                        --- erase dir entry address pointers

         BUFFERS $800 ERASE                                        --- clear the buffers

         0 FILE

         !SD

      ON SDERRLED                                                --- turn on error LED - only cleared if FAT passes muster

         IF

           mounted C~~

           0 SECTOR                                                --- read partition info

           $01FE SDBUF + W@ $AA55 =                                --- Could it be a partition?

           IF

             $01BE SDBUF + parts $40 CMOVE                        --- buffer patitions

            READFAT32                                                --- read the FAT32 sector

            \ PRINT"  Mounted SD Card " 

             .FAT                                                --- display

            rootdir @ cwdsect !

             OFF SDERRLED                                        --- clear error indication

            boot @ fboot !                                        --- copy boot signature to validate mount status

          ELSE

          PRINT"  *Format Error* "

          THEN

        ELSE

           PRINT" *Card Error* "

        THEN

        ;

pub ?MOUNT

        CARD? NOT

         IF

           PRINT"  *No Card inserted!* " 

           ON SDERRLED MOUNT

         THEN

        boot @ fboot @ <> mounted C@ 0= OR IF MOUNT THEN

         ;

{HELP ?SDCARD

Make sure the card is inserted or mount it if it has just been inserted

}

pub ?SDCARD

        card? C@ 0= CARD? DUP card? C! AND                 --- check previous card state with now and update

          IF MOUNT THEN                                        --- just inserted so mount automatically

        CARD? NOT IF ON SDERRLED mounted C~ THEN                --- indicate an error if it's not inserted

        ;

--- FILE NAME HANDLERS ---

" FILENAME.TXT" 16 4 * STRING file$                        --- The actual name of the file requested

--- file$ stores 4 8.3 filenames at 16 byte boundaries

pub FILE$  ( -- addrOfFilenameString )        FILE# 4 SHL file$ + ;   

" FILENAMESTR " 0 STRING fname$                                --- The formatted name of the file requested for internal use

--- format friendly file name into directory format  

--- FILE.TXT --> FILE    TXT  

pri >F83 ( str1 -- str2 )

        fname$ #11 BL FILL                                        --- prep fname$ as all blanks

         fname$ #11 + C~                                        --- ensure terminator is set

        fname$ ( str1 fname$ )

         OVER C@ "." = IF OVER LEN$ CMOVE fname$ EXIT  THEN

        "." 3RD LOCATE$ ( str1 fname$ str1ext )                --- located the extension?

        ?DUP IF                                                 --- found an ext

           DUP >L

           3RD - ( str1 fname$ name8len ) CMOVE                --- copy across the filename part

           L> 1+ ( ext )                                        --- get the extension

          DUP LEN$          ( ext extLen )

          fname$ 8 + SWAP CMOVE                                   --- only move LEN$ chars  MJB so .JS becomes .JS(sp)

        ELSE                                                         --- no ext found
         
( str fname$ )

           OVER LEN$ CMOVE                                         --- just copy the string

          THEN

        fname$

        ;

--- DIRECTORY STRUCTURE ---

[PRIVATE

#00        == @FNAME                '' File name

#08        == @FEXT                '' File extension

#11        == @ATR                '' Attribute

#13        == @CMS                '' creation millisecond (2ms)

#14        == @CTIME                '' creation time

#16        == @CDATE                '' creation date

#18        == @ADATE                '' access data

#20        == @FCLSTH                '' First cluster (high word)

#22        == @FTIME                '' modification time

#24        == @FDATE                '' modification date

#26        == @FCLST                '' First cluster of file (low word)

PRIVATE]

#28        == @FSIZE                '' size of file

{

DIRECTORY ENTRY

F

I

L

E

N

A

M

E

E

X

T

ATR

CMS

CTIME

CTIME

CTIME

CDATE

CDATE

ADATE

ADATE

FCLSTH

FCLSTH

FTIME

FTIME

FDATE

FDATE

CLUST

CLUST

FSIZE

FSIZE

FSIZE

FSIZE

0080_0140:   57 45 4C 43  4F 4D 45 20   46 54 50 20  00 00 87 83   WELCOME FTP ....

0080_0150:   A2 44 A2 44  00 00 D9 BE   83 44 0F 00  90 00 00 00   .D.D.....D......

0080_0160:   45 58 54 45  4E 44 20 20   54 58 54 20  00 00 87 83   EXTEND  TXT ....

0080_0170:   A2 44 A2 44  00 00 1B 8F   82 44 10 00  DF 69 01 00   .D.D.....D...i..

0080_0180:   57 45 4C 43  4F 4D 45 20   54 45 4C 20  00 00 87 83   WELCOME TEL ....

0080_0190:   A2 44 A2 44  00 00 D6 68   6E 44 13 00  96 00 00 00   .D.D...hnD......

0080_01A0:   46 52 45 44  20 20 20 20   50 4E 47 20  00 00 87 83   FRED        PNG ....

0080_01B0:   A2 44 A2 44  00 00 FD 6D   65 44 14 00  FF 1E 00 00   .D.D...meD......

0080_01C0:   4C 4F 47 30  30 33 32 20   54 58 54 20  00 00 87 83   LOG0032 TXT ....

0080_01D0:   A2 44 A2 44  00 00 E6 73   58 44 15 00  00 00 10 00   .D.D...sXD......

WELCOME .FTP .....a 0000.4340        03/04/2014 23:54:50   0,144

EXTEND  .TXT .....a 0000.4380        02/04/2014 17:56:54   2,639

WELCOME .TEL .....a 0000.4440        14/03/2014 13:06:44   0,150

FRED           .PNG .....a 0000.4480        05/03/2014 13:47:58   7,935

LOG0032 .TXT .....a 0000.44C0        24/02/2014 14:31:12   8,576

ANALYSIS OF OPENING A FILE

Note: this method does not bother to index the FAT table but working from the starting cluster assumes

files are non-fragmented.

FOPEN WELCOME.FTP --- open a file (set to 1 FILE)

Each file has a directory entry buffer

@DIRBUF ( -- diradr )

diradr @FILE ( -- dirxptr ) --- virtual memory address of pointer to directory entry

diradr @FILE @ . 800140 ok

@DIRBUF 20 DUMP --- examine the buffered directory entry for this file

0000_418C:   57 45 4C 43  4F 4D 45 20   46 54 50 20  00 00 87 83   WELCOME FTP ....

0000_419C:   A2 44 A2 44  00 00 D9 BE   83 44 0F 00  90 00 00 00   .D.D.....D...... ok

StartCluster ( --- cluster ) returns the first cluster (32-bits) of the file

CLUST>SECT ( clust# -- sector )

  root @ --- offset to start of clusters

  clshift --- multiplier so that 6 = 64 * 512bytes = 32K/cluster

  rootdir @ + --- adds in the sector offset from the beginning of the root directory sector

file @FILE ! --- saves starting address of file

          StartCluster CLUST>SECT         

           DUP file @FILE !                         \ save address of file

           DUP 9 SHL @FREAD !

           0 @FWRITE !                                \ disable writing by specifying illegal write address = 0

 

file @FILE @ . 4340 ok

FILE@ 10 SD DUMP

0086_8000:   32 32 30 2D  57 45 4C 43   4F 4D 45 20  54 4F 20 54   220-WELCOME TO T

}

--- return with the address of the directory buffer  in RAM

pub @DIRBUF ( -- bufadr )                                        --- get dir buffer address for current file# - set with FILE                

         FILE# 5 SHL dirbuf +                                        --- index 1 of 4 32-byte directory buffers

         ;

#13 BYTES dir$                                                 --- make room for 8.3 filename inc dot + terminator

pub DIR?    ( str -- diraddr | FALSE )

        ?MOUNT

          mounted C@ IF

          >L                                                         --- save str ptr onto loop stack - ref as IX

           cwdsect @ 9 SHL                                        --- get virtual address of current working directory

           BEGIN ( dirptr )

             DUP XC@                                                 --- assume the file is in the root cluster

           WHILE

            DUP XADR dir$ #11 CMOVE dir$                         --- copy the name as a string

               0 OVER #11 + C!                                         --- ensure it's terminated correctly (previous bug)

            IX COMPARE$ IF L> DROP EXIT THEN                 --- Found it, exit here with address

             BL +                                                 --- skip to next dir entry (32 bytes)

           REPEAT

           L> 2DROP                                                 --- failed to find it - discard parameters

         ELSE

            DROP

         THEN

         FALSE                                                         --- Failed to find it

          ;

--- DIRECTORY ENTRY HANDLERS ---

--- update current directory entry from buffer

pub DIR!

         @DIRBUF diradr @FILE @ XADR BL CMOVE                 --- copy that to the virtual address

        WRSECT        DROP                                                --- Force an update

        ;

--- split 6 digit decimal number into 3 two digit groups

pri DECS ( #xxyyzz -- zz yy xx )

         #100 U/MOD #100 U/MOD

        ;

--- update file modification/create date in dir buf

pri FDATE! ( #yymmdd field -- )                                --- Date (7/4/5 bits, for year-since-1980/month/day)

         SWAP

        #200000 +                                         --- arrange as decimal YYMMDD from 1980 ( 2000.0000 + 1980.0000 - )

         DECS 9 SHL SWAP 5 SHL + +

        SWAP

pri +DIR! ( word field -- )

         @DIRBUF + W!                                          --- write to directory entry as new date

         ;

--- update file modification/create time in dir buf

pri FTIME! ( #hhmmss field -- )                                 --- Time (5/6/5 bits, for hour/minutes/doubleseconds)

         SWAP

         DECS #11 SHL SWAP 5 SHL + SWAP 2/ +

         SWAP +DIR!

         ;

--- DATE TIME STAMPING ---

IFNDEF DATE@

#150101        == DATE@

#120000        == TIME@

}

{HELP FSTAMP

Update the modified time and data of the current file

}

pub FSTAMP ( -- )

         DATE@ @FDATE FDATE! TIME@ @FTIME FTIME!                          --- update file date and time with current

         DIR!

         ;

pri .FYEAR ( fdate -- )

         9 SHR #1980 + $250A .NUM

         ;

IFNDEF .ASMONTH

pri .ASMONTH ( index -- )

         >N 1- 3 *

         " JanFebMarAprMayJunJulAugSepOctNovDec" + 3 CTYPE

         ;

}

pri .FDATE ( fdate -- ) --- print date in Unix format

         DUP 5 SHR .ASMONTH                                                        --- extract day of the month to index into name string

          ( date )

          $1F AND $230A .NUM                                                --- day of the month

         ;

pri .FTIME ( ftime -- )

          DUP #11 SHR $20A .NUM ":" EMIT

          5 SHR 6 BITS $20A .NUM

         ;

--- CLUSTER HANDLERS ---

{

--- display cluster usage from FAT

pub .FATMAP

         sect/fat 0 DO

           I $3F AND 0= IF .INDEX THEN

           I FAT1 + SECTOR

           sectcrc @FILE W@

           IF "*" ELSE "." THEN EMIT

           lastkey C@ $1B = IF LEAVE THEN

         LOOP

         ;

}

pri NextFreeDir ( -- xadr )

         0

         cwdsect @ 9 SHL sect/clust C@ BLKSIZ *

         ADO I XC@ 0= IF I OR LEAVE THEN BL +LOOP         

         ;

{ CLUSTER CHAIN CODES

If value => $0FFF.FFF8 then there are no more clusters in this chain.

$0FFF.FFF7 = bad

0 = free

}

pri @CLUSTER ( index -- xadr )

         2* 2* FAT1 9 SHL +

        ;

pri CLUSTER@ ( index -- cluster )

          @CLUSTER X@

         ;

IFNDEF ROUNDUP/

pub ROUNDUP/ SWAP OVER /ROUND SWAP / ;

}

{HELP CLUSTERS? ( size -- xadr )

Find free clusters for the file size in bytes - 0 = all

return with address of first free cluster

}

pri CLUSTERS? ( size -- xadr )

         9 SHR sect/clust C@ ROUNDUP/ ( clusters )                --- calculate clusters required

          0

         BEGIN

           BEGIN DUP @CLUSTER X@ WHILE 1+ REPEAT                        --- find a free cluster

           ( clusters index )

           0 OVER 4TH ADO I @CLUSTER X@ OR DUP IF NIP I SWAP LEAVE THEN LOOP                        --- check for sufficient contiguous clusters

           ( clusters chain flag )

         WHILE

           1+

        REPEAT

         NIP

         ;

pri StartCluster ( diradr -- cluster )                         --- get the file starting cluster for diradr

--- = virtual memory address of file's directory entry

        DUP @FCLST + W@ SWAP @FCLSTH + W@ W>L

        ;

pub FSIZE@                        @DIRBUF @FSIZE + @ ;

pub FSECT@ ( -- sect )        file @FILE @ ;

{HELP FILE@ ( -- xadr )

Fetch the virtual memory address of the currently open file

}

pub FILE@ (  -- xadr )        FSECT@ 9 SHL ;

--- VIRTUAL FILE ACCESS ---

{HELP FC! ( byte offset -- )

Store a character byte to the file with the offset

This write will only write to an open file and can address 4GB within that file which can be at any offset

in the media, even beyond 4GB.

}

pub FC! ( byte faddr -- )                                         --- store a byte in virtual memory relative to the start of the file with limits

\\\        FSECT@ ?DUP IF

         FILE@ ?DUP IF + 0 MAX FSIZE@ MIN XC! ELSE 2DROP THEN

         ;

pub FC@ ( faddr --- byte )

         FILE@ ?DUP IF + 0 MAX FSIZE@ MIN XC@ ELSE FALSE THEN

         ;

--- SEQUENTIAL FILE ACCESS ---

--- maintain read and write pointers for 4 files ---

4 LONGS fread            \ access via @FILE for current file:   fread @FILE   or    @FREAD 

4 LONGS fwrite        \ access via @FILE for current file:   fwrite @FILE  or    @FWRITE

BYTE fstat                                                        --- current status of file system

pub @FREAD ( -- readptr )

        fread @FILE

        ;

pub @FWRITE ( -- writeptr )

        fwrite @FILE

        ;

--- return with remaining bytes in file

pub FREM ( --- rem )         @FWRITE @ DUP IF FILE@ FSIZE@ + SWAP - THEN ;

WORD fkey                                                         --- backup for input device when input is switched to file

--- Read in the next character in the virtual buffer as part of the console input stream

pri FGET ( -- ch )

        @FREAD @ XC@                                                 --- Read a single character

        DUP 0=

           IF "X" fstat C! fkey W@ ukey W!                 --- stop when null encountered and return to previous input device

           ELSE @FREAD ++                                         --- update index otherwise

           THEN

        ;

--- Write a character into the logical end of the file and update the write pointer

\ 280us typical

pub FPUT ( ch -- )

        ?DUP IF                                                    --- ignore null characters (but will write nulls if another bit in long is set)

pub FPUTB ( byte -- )                                          --- write byte even if it is null

          FILE@ 0<> @FWRITE @ AND                                 --- check if file is opened and a valid write pointer is set (APPEND)

         IF                                                         --- don't write if a file is not opened

          @FWRITE @ FILE@ FSIZE@ BLKSIZ ALIGN + =>          --- file full?

             IF DROP "O" fstat C! EXIT THEN                 --- Don't write to beyond end of file - fstat = O for OVERFLOW

                                                                 --- still room in the file so just write it

          @FWRITE @ XC!                                         --- write the character to the file

          @FWRITE @ FILE@ FSIZE@ BLKSIZ ALIGN + 1- =          --- last byte in file?

             IF "F" fstat C! EXIT THEN                          --- Warn file is now full - fstat = F for FULL, exit now!

          @FWRITE ++

           0 @FWRITE @ XC!                                         --- Insert a null terminator (overwritten by the next character) 

         ELSE

           DROP "N" fstat C!                                        --- no file - fstat = N

         THEN

         THEN

         ;

--- Redirect the console input to read the file sequentially

pub FILE>

pub FINPUT

        FILE@ IF ukey W@ fkey W! ' FGET ukey W! THEN

         ;

{HELP >FILE ( -- )

Redirect character output via uemit to the open file using "fptr" which is set to the start of the file when opened

If the file is not opened and a valid write pointer set then output will be discarded

}

pub >FILE

pub FOUTPUT ( -- )

         FILE@ IF ' FPUT uemit W! ELSE NULLOUT THEN

        ;

pub FCLOSE                                                        --- Close the current file

        FLUSH                                                        --- make sure that this file has any modifications written back.

         _sector ~~                                                --- clear sector (-1)

         file @FILE ~                                                --- clear file pointer

         NULL$ FILE$ $!                                        --- blank out file name

         @DIRBUF BL ERASE                                        --- wipe dir entry

         ;

{HELP FOPEN# ( diradr -- sector )

Open the file pointed to by the virtual directory entry address

}

pub FOPEN# ( xdiradr --- sector )

           DUP diradr @FILE !                                 --- save RAM address of directory entry

            DUP $7FFF > IF XADR THEN                                --- only convert virtual addreses, pass physical addresses

           DUP @DIRBUF BL CMOVE                                 --- copy directory entry to local buffer

           StartCluster CLUST>SECT

           DUP file @FILE !                                         --- save address of file

           DUP 9 SHL @FREAD !                                        --- Set virtual memory FREAD address to start of file

           @FWRITE ~                                                --- disable writing by specifying illegal write address = 0

         ;

{HELP FOPEN$ ( namestr -- sector )

Open the file with the 8.3 name and return with its sector or 0 if failed

The variable fstat can be checked for more information if there was an error

Execution time ~ 10ms sample:

" FIRMWARE.ROM" LAP FOPEN$ DROP LAP .LAP 2.887ms ok

Usage: " SYSLOG.TXT" FOPEN$ IF etc etc

}

pub FOPEN$ ( namestr -- sector )

         FCLOSE

         DUP C@

         IF                                                                        --- skip null name

           DUP C@ "/" = IF 1+ THEN                                                --- ignore leading / in file name

           DUP LEN$ #12 > IF "!" fstat C! DROP FALSE EXIT THEN                 --- abort with ! status if name is too long

           DUP FILE$ $!                                                        --- set the name for this file

          >F83 DIR? DUP                                                         --- find the dir entry

             IF FOPEN#        ( sector )                                                --- open the file from the dir entry

            ELSE "?" fstat C!                                                --- else ? not found

          THEN

         ELSE

           DROP FALSE "?" fstat C!

         THEN

        ;

{HELP FOPEN ( <name> -- )

Open a file interactively with the name specified in the input stream

Report back to the console

}

pub FOPEN ( <name> -- ) IMMEDIATE

        GETWORD

         FOPEN$ ?DUP

           IF PRINT" ...opened at "  .LONG SPACE

           ELSE PRINT"  not found "

           THEN

        ;

HELP: FCREATE$ ( namestr -- flg )

--- Create a new file by name but if it already exists then delete the old one and reuse the dir entry.

--- preallocate 1M (hidden) per file in cluster table so only dir entry needs updating.

--- last file can increase beyond 1M if needed

}

pub FCREATE$ ( namestr -- flg )

         FCLOSE                                                        --- close any open file on this channel - clears dir entry buffer

        >F83 ( namestr -- f83str )                                --- make sure it's ready in the correct 8.3 format

         @DIRBUF #11 CMOVE                                        --- write the name of the file to the directory buffer

         $20 @DIRBUF @ATR + C!                                --- set archive attribute

         DATE@ @CDATE FDATE! TIME@ @CTIME FTIME!                --- stamp create date and time

         CLUSTERS? ( -- xadr )

\         NextFreeDir ( -- xadr ) XADR

         

         ;

--- FILE APPEND OPERATIONS ---

--- APPEND TO TEXT FILES USING AN EOF MARKER

#1                 == RCDSZ                \ Minimum record increment, normally 1 for text files, needs to be 2^n, n>=0

RCDSZ >|        == >|RCDSZ

pub @RCD        ( rcd -- xadr )        \ virtual memory address for rcd record number (for text files rcd = char pos)

         >|RCDSZ SHL FILE@ +

         ;

\ Erase the current file by overwriting with nulls

pub -FERASE

         FILE@ 0EXIT

         FILE@ FSIZE@ ADO 0 I X! 4 +LOOP

         FLUSH

         ;

BYTE eof                        \ end of file character

pri APPEND.BLK ( -- relblk )

        FILE@ 9 SHR FSIZE@ 9 SHR OVER + SWAP         ( max min )

         BEGIN

          2DUP - 2/ OVER +                ( max min mid )         --- Find new mid point

            SECTOR                                  ( max min )

         2DUP - 1 >

         WHILE

           SDBUF C@ eof C@ =                ( max min flg )         --- if true then too high

           IF 2DUP - 2/ OVER + ROT DROP SWAP

           ELSE 2DUP - 2/ OVER + NIP

           THEN

        REPEAT

          NIP

        ;

{HELP APPEND ( eof -- xptr )

Find the EOF marker (normally a null) and set the write pointer and result to this ready to append

return with null if failed.

}

pub APPEND ( eof -- ptr )

         eof C!

\\\         SDBUF BLKSIZ + DUP C@ >R C~                                --- ensure we have a null after the block (but save)

        FILE@ DUP 0EXIT DROP                                        --- exit with false if there is no file opened

         APPEND.BLK ( -- relblk )                                --- find the active block to use

         9 SHL SDBUF LEN$ BLKSIZ MIN +                         --- add in offset allowing for a full block as well

         FILE@ FSIZE@ + OVER = IF DROP FALSE THEN

         DUP @FWRITE !

\\\         R> SDBUF BLKSIZ + C!

         ;

{HELP RW

Make current file read/write

}

pub RW

         FILE@ DUP @FWRITE ! @FREAD !

         ;

--- FILE SHELL COMMANDS ---

         

pub RENAME$ ( new$ -- )

         FILE@ IF

           >F83 @DIRBUF #11 CMOVE                                 --- update local copy of directory entry

           DIR!

        THEN

          DROP

        ;

pub RENAME ( <oldname> <newname> ) IMMEDIATE

        GETWORD FOPEN$ IF PRINT"  to " GETWORD RENAME$ ELSE DROP PRINT"  No file opened " THEN

        ;

pub DELETE ( <name> -- ) IMMEDIATE

         GETWORD FOPEN$ IF  THEN

         ;

--- Load a file as console input - scripts or source code

pub FLOAD ( <name> -- ) IMMEDIATE

        GETWORD FOPEN$ 0EXIT FINPUT

        ;

--- exception handler - if word not found then run from file - point unum to this code

pub FRUN                word DUP C@ "." = IF 1+ THEN FOPEN$ 0EXIT FINPUT ;

pub FPRINT$ ( file$ -- ) --- Print the contents of the file specified by the string if it exists

         FOPEN$ 0EXIT

pub (cat) --- type out the currently open file else ignore

         FILE@ ?DUP 0EXIT 

         lastkey C~                                                        --- clear any preexisting console escape

         BEGIN 

           DUP XC@ DUP lastkey C@ $1B <> AND                        --- null end of file or escape key?

         WHILE                                                                 --- no, continue

           DUP $0A = IF $0D EMIT THEN EMIT 1+ 

         REPEAT

         2DROP

         ;

{HELP cat <name>

List the contents of the specified file

}

pub cat ( <name> -- ) IMMEDIATE         --- List the file as text 

        [COMPILE] FOPEN

        CR (cat)

        ;

--- DIRECTORY PATH CONTROL ---

" /" #11 STRING cwd$

pub cd         IMMEDIATE

         GETWORD

pub cd$ ( dirstr -- )

         DUP C@ "/" =                                                         --- from root directory?

         IF

           rootdir @ cwdsect ! " /" cwd$ $!

           1+                                                                --- point to remainder of string after preceding /

         THEN

         DUP FOPEN$ IF $10 @DIRBUF @ATR + SET?  IF cwd$ $! FSECT@ cwdsect ! EXIT THEN THEN

         DROP

        ;

pub pwd         cwd$ PRINT$ ;

IFDEF PCB$

pub .PATH          PCB$ PRINT$ PRINT" @" serial U@ .LONG SPACE pwd ."  $ " ;

}

IFNDEF PCB$

pub .PATH          PRINT" Tachyon@" serial U@ .LONG SPACE pwd ."  $ " ;

}

{HELP PROMPT ( on/off -- )

Turn on the path prompt and and suppress the ok response or revert back to normal Forth ok

}

pub PROMPT ( on/off -- )                DUP NOT OK ' .PATH AND prompt W! ;

--- DIRECTORY LISTING ---

{

DIRECTORY ENTRY

F

I

L

E

N

A

M

E

E

X

T

ATR

CMS

CTIME

CTIME

CTIME

CDATE

CDATE

ADATE

ADATE

FCLSTH

FCLSTH

FTIME

FTIME

FDATE

FDATE

CLUST

CLUST

FSIZE

FSIZE

FSIZE

FSIZE

0080_0140:   57 45 4C 43  4F 4D 45 20   46 54 50 20  00 00 87 83   WELCOME FTP ....

0080_0150:   A2 44 A2 44  00 00 D9 BE   83 44 0F 00  90 00 00 00   .D.D.....D......

LEGAL SYMBOLS

! # $ % & ' ( ) - @ ^ _ ` { } ~

Directory Attribute flags

0        Read Only

1        Hidden

2        System

3        Volume label

4        Directory

5        Archive

Directory types - first byte

  1. Normal record with short filename - Attrib is normal
  2. Long filename text - Attrib has all four type bits set
  3. Unused - First byte is 0xE5
  4. End of directory - First byte is zero

}

\ ]~

--- print the symbol(s) for each active directory name attribute

pri .ATR ( atr -- )

        " rhsvda" SWAP 6 

         FOR

           DUP 1 AND IF OVER C@ EMIT ELSE PRINT" ." THEN

           2/ SWAP 1+ SWAP

         NEXT 2DROP

        ;

\ [~

( FIRMWARE.ROM .....a 0000.620A        07/07/2014 11:58:14   5,536 )

pri .DIR ( addr -- )

         DUP C@                                                                --- ignore blank entries as closed

         IF

          DUP 8 CTYPE PRINT" ." DUP 8 + 3 CTYPE                        --- print the file name with spaces included (formatted listing)

           SPACE DUP @ATR + C@ .ATR SPACE                                --- print attributes as alphas

          DUP StartCluster CLUST>SECT .LONG                                --- print sector number

           4 SPACES DUP @FDATE + W@  DUP .FDATE                        --- print date

           SPACE .FYEAR

           SPACE DUP @FTIME + W@ .FTIME                                --- print time

           3 SPACES @FSIZE + @ .DECX                                --- print size

         ELSE

           DROP PRINT" *CLOSED*"

         THEN

        ;

pri (.DIR) I CR .DIR ;

{

DIR         PBJTECH.SDL

------------------------------------------------------------------------------

NAME    .EXT ATRS   SECTOR     DATE       TIME       SIZE

CE1372  .JPG .....a 0000.7C17    20/06/2013 02:36:52   736,486

}

WORD udir

pub DIR

\           CE1372  .JPG .....a 0000.7C17    20/06/2013 02:36:52   736,486

\        CR PRINT" NAME    .EXT ATRS   SECTOR       DATE       TIME       SIZE "

        ' (.DIR) --- use this method for listing the directory

pri (DIR) ( code -- )

        ?MOUNT

         CR volname #11 CTYPE

pub (SLIST) ( method -- )

        udir W!

         1 FILE FCLOSE                                        --- always use FILE#1 for directory and any temp read only

        cwdsect @ sect/clust C@                        --- scan and list rootdir by sector up to one cluster long

         ADO

           I SECTOR

           ACTIVE?

           IF

            SDBUF BLKSIZ                                 --- scan the buffer for dir entries

             ADO                                        --- up to 16 dir entries/sector

               I @ATR + C@ $0F >                        --- accept only valid entries

                 I C@ $80 < AND                        --- but ignore if 1st character has 8th bit set

               I C@ "?" <> AND                        --- ignore "deleted" entries

                IF udir W@ CALL THEN                --- all good, list this one using the directed method

            BL +LOOP                                        --- next dir entry 32 bytes (assumes standard 8.3)

           ELSE

             LEAVE                                        --- no more dir sectors - leave loop

          THEN

        LOOP

        ;

--- Print the file name at the current loop index I

pub .FNAME ( --- )        

         I C@ BL >                                                        --- skip invalid index/entry

         IF

           $10 I @ATR + SET? IF "[" EMIT THEN

           #11 0 DO

             J I + C@ DUP BL > IF I 8 = IF "." EMIT THEN DUP EMIT THEN DROP

           LOOP

           $10 I @ATR + SET? IF "]" EMIT THEN

         THEN

;

--- print the Unix time or year if the file is older than 6 months

pri .UTIME (  -- )

---  print time

          2 SPACES I @FTIME + W@ .FTIME                                        // read file modification time

        ;

\ ]~

BYTE lscnt PRIVATE

--- directory list method for ls

--- FORMAT: CE1372  .FTH   CE1372  .PDF   CHARLCD .JPG   IOT5500 .ROM   DRAGON  .JPG  

pri (ls)

         lscnt C@ 5 MOD 0= IF CR THEN                                --- 5 files per line

         $10 I @ATR + SET?                                                 --- format a directory name

         IF

           "[" EMIT I 8 CTYPE "]" EMIT 5 SPACES                         --- Use [ ] to enclose name

         ELSE

           I 8 CTYPE                                                         --- normal, print name and opt ext

           I 8 + C@ BL <>

             IF  PRINT" ." I 8 + 3 CTYPE 3 SPACES ELSE 7 SPACES THEN

         THEN

         1 lscnt C+!

        ;

--- List a single directory entry in FTP compatible format

pri (.LIST) ( <index> )

          PRINT" -rwxrwxrwx 1 502     500"                                // dummy permissions

          I @FSIZE + @ $2A0A .NUM                                                 // print file size "1048576"

          SPACE

          I @FDATE + W@ .FDATE                                                // DATE "24"

--- most systems expect either the time or the year if it is older than 6 months

           .UTIME

           SPACE .FNAME CR                                                // FILE NAME "LOG0001.TXT"

        ;

pub .LIST                ' (.LIST) (SLIST) ;                                        // set list method

--- list the directory in wide and simple format

--- ls -l in long format

pub ls IMMEDIATE

         delim 1+ C@ BL =

         IF

           GETWORD " -l" $=

           IF CR .LIST EXIT THEN

         THEN

         lscnt C~ ' (ls) (DIR)

        ;

\ [~

{ FILES

List the currently open files

Usage:

0 FILE   ok

FOPEN SYSLOG.TXT...opened at 0000.4B28  ok

1 FILE  ok

FOPEN SDCARD.FTH...opened at 0000.5620  ok

2 FILE   ok

FOPEN LOG0001.TXT...opened at 0000.3B28  ok

3 FILE  ok

FOPEN LOG0002.TXT...opened at 0000.3C28  ok

FILES

#0 SYSLOG  .TXT .....a 0000.4B28        04/11/2013 19:02:22   1,048,562

#1 SDCARD  .FTH .....a 0000.5620        20/11/2013 03:23:56   16,590

#2 LOG0001 .TXT .....a 0000.3B28        04/11/2013 01:25:18   131,072

#3 LOG0002 .TXT .....a 0000.3C28        04/11/2013 01:25:18   131,072 ok

}

pub .FILE         filesel C@

pub .FX         CR PRINT" #" DUP PRINT SPACE FILE @DIRBUF .DIR ;

pub .FILES

        FILE#         4 0 DO I .FX LOOP FILE

         ;

--- IFNDEF SMALL

--- FILE SYSTEM ADD-ONS ---

--- Return with the size of the "ROM", 64K if the EEPROM is larger > 32K and the file is too.

pri ROMSZ ( -- size )         0 E@ $8000 E@ <> FSIZE@ $1.0000 = AND IF $FF00 ELSE $8000 THEN ;

\ These are a couple of add-ons to the file system which allow the "ROM" to be saved to a file called default FIRMWARE.BIN

pub SAVEROM IMMEDIATE

         delim C@ BL = IF GETWORD ELSE " FIRMWARE.ROM" THEN

pub SAVEIMG ( filename$ -- )

        1 FILE FOPEN$ 0EXIT

         0 $8000 FIXBIN                                                --- fix up the image so checksum matches "binary" load

           PRINT"  Saving EEPROM image to " FILE$ PRINT$                --- prompt

           0 FILE@ FSIZE@ ( start size )                                --- 150108 Just save everything - only be selective during load

          ADO

             DUP SDBUF BLKSIZ ELOAD BLKSIZ +

             SDBUF I 9 SHR SDWR DROP                                        --- write one full sector

           BLKSIZ +LOOP

           DROP                                                                --- discard source address

           _sector ~~                                                        --- invalidate sector as we used raw methods to write image.

           FSTAMP

        ;

    }

pub LOADROM IMMEDIATE

        delim C@ BL = IF GETWORD ELSE " FIRMWARE.ROM" THEN

pub LOADIMG ( filename$ -- )

        1 FILE FOPEN$ 0EXIT 

pri (LOADROM)

         PRINT"  Loading EEPROM image " FILE$ PRINT$ PRINT"  to EEPROM "

         FILE@ 0 ROMSZ

         ADO DUP I + XADR I $40 ESAVE $40 +LOOP                --- EEPROM page size at a time.

         DROP

         ;

--- Generate a file CRC

pub FCRC ( -- crc )

         0 FILE@ FSIZE@

        ADO

              I XADR BLKSIZ

           ADO

             I C@ SWAP

                8 FOR

                   2DUP XOR 1 AND

             ROT 2/ ROT 2/ ROT

               IF $A001 XOR THEN

                NEXT

              NIP

           LOOP

         BLKSIZ +LOOP

         ;

HELP: ?AUTOLOAD ( -- )

Check if new firmware is available and if CRC is correct then load and reboot

}

pub ?AUTOLOAD

    MOUNT

    " FIRMWARE.ROM" FOPEN$ 0EXIT

    ."   ...CHECKING FOR NEW FIRMWARE...."

    0

    $8000 0 DO I FILE@ + X@ I E@ <> IF LEAVE 1+ THEN 4 +LOOP

    IF ." LOADING NEW FIRMWARE " (LOADROM) REBOOT ELSE ."  NONE FOUND " THEN

    ;

   

HELP: QV ( <filename> -- )

Quick view of file header in ASCII dump format

}

pub QV  GETWORD FOPEN$ 0EXIT FILE@ $100 SD DUMPA ; IMMEDIATE

DATE@ build.date E! TIME@ build.time E!

]~

\ RECLAIM

?BACKUP

END