{-----------------------------------------------------------------------+ | | | CardBoot | | | | !!! Work in progress !!! | | | | The goal is to connect to a FAT32-formatted SDC/MMC card | | over the SPI interface, and then read its root directory. | | If there is a SYSTEM.GT1 file, load and execute it. | | | +-----------------------------------------------------------------------} gcl0x { XXX Positive card detection: Read MISO's Invert Write to ZP Read MIOS XXX Support loading of segments in zero page - Bricks 82.ef 110 bytes - Tetronis a0.ef 80 bytes - Frogstroll 82.ef 110 bytes Approach 1: Through page 1 Kludge but very simple. Visual artifacts are transient, and only with certain files We could also map it to a different page, e.g. one that will be scrolled out anyway Approach 2: Decoding after load Faster loading, but 2nd pass needed Can't hide visual artifacts, even for simple files Approach 3: Reduce footprint with CALLI and move variables to $c0 Lots of work, but this is a good idea anyway XXX 64K systems: put code in highest pages ($ffff and down) --> Conflicts with Apple-1 (WozMon) XXX 32K systems: put code in bottom screen memory ($7f9f and down) --> Conflicts with Apple-1. So how about $800.$1000 16*160 = 2560 bytes Con: It overwrites *** Gigatron 32K *** --> So be it We can always copy that to the bottom of screen memory (silly) Dynamically mak jump table at $f8: if you use it, ALLOC -8 $FE PrintChar, Newline, Open, LoadByte, ... XXX On the matter of scrolling: When to restore the video table? A: When not loading to $503 XXX DIR -> Builtin command. Only names XXX LIST -> External command: file size, date, volume label XXX CLS XXX STAT XXX TYPE command (consider passing of ARGS) XXX Device letters or not? A: B: C: XXX https://cdn.hackaday.io/images/1744261554112919559.png XXX LoadtGt1: better closing of sector XXX Compliancy: Byte addressing for V2 cards when CCS bit in CMD58 OCR is 0 XXX Compliancy: MMC v3 card detection with CMD1 XXX Compliancy: Don't set block size with CMD16 if CCS bit in OCS is 1 XXX Compliancy: Reconsider pull-down resistors on MISO lines Some cards [30%?] [MMC?] have MISO in open collector during init" But if this is only during CMD0, we can live with it... XXX Compliancy: Follow cluster chain for directories as well XXX Speed: Transfer 256 bytes at once XXX Think about card detect and monitoring it (inserted = low) Put we can always use the MOSI line for this, or check the init status when opening a file XXX Figure out why Apple formats card with type 0x0b References: http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf SPI and SD cards https://electronics.stackexchange.com/questions/303745/sd-card-initialization-problem-cmd8-wrong-response SD card initialization problem - CMD8 wrong response http://www.rjhcoding.com/avrc-sd-interface-1.php http://www.rjhcoding.com/avrc-sd-interface-2.php http://www.rjhcoding.com/avrc-sd-interface-3.php http://www.rjhcoding.com/avrc-sd-interface-4.php Interfacing an SD Card Part 1-4 https://www.pjrc.com/tech/8051/ide/fat32.html Understanding FAT32 Filesystems https://bits4device.wordpress.com/2017/12/16/sd-crc7-crc16-implementation/ CRC definition } {-----------------------------------------------------------------------+ | | | Variables | | | +-----------------------------------------------------------------------} { Variable Bytes Description -------- ----- ----------- CardType X Detected card version Address XX RAM for transfer buffer ValueL,H XXXX 32-bit accumulator OffsetL,H XXXX Offset to be added to ValueL,H SectorL,H XXXX Block number of last sector CurrentDirL,H ---- First cluster of current directory ClusterSize X Sectors per cluster: 1, 2, ... 128 ClusterMask X XXX ClusterSize - 1 ClusterBaseL,H ---- Cluster origin (hypothetical cluster 0) FatBaseL,H ---- XXX FileSizeL,H ???? File size from directory entry FilePosL,H --XX Current R/W position in file: 0..FileSize Checksum ---- CRC16 checksum over sector data Memory usage ------------ $500 $6ff Buffer for reading FAT sectors $58a0 $xxff Code not needed for GT1 reading Eg. detecting the card settings $xxa0 $78ff GT1 reading core $7fa0 ... Cluster list } {-----------------------------------------------------------------------+ | RAM page 2 | +-----------------------------------------------------------------------} { The version check below is a BUG of ROM v5a, and the reason that ROM v5a won't boot from an SD card } \romType, \romTypeValue_DEVROM- {Version check >= DEVROM} [if<0 do _frameCount _vPCH: loop] $500 Address= {512 bytes memory for one sector} {-----------------------------------------------------------------------+ |}$58a0 _vLR= [ret]{ RAM page $58 | +-----------------------------------------------------------------------} *=$58a0 {-----------------------------------------------------------------------+ | | | Memory card section | | | +-----------------------------------------------------------------------} { CMD0: GO_IDLE_STATE } [def { Resets the SD Memory Card } push [def `CMD0`` #0] PrintText! 0 CardType= { | """To communicate with the SD card, your program has to place the SD | card into the SPI mode. To do this, set the MOSI and CS lines to | logic value 1 and toggle SD CLK for at least 74 cycles.""" } 10 [do i= {Lets do 10 bytes for 80 cycles} SendOnesToCard! {Keep MOSI line high by only sending ones} i 1- if>0loop] { | """After the 74 cycles (or more) have occurred, your program | should set the CS line to 0 and send the command CMD0: | 01.000000 00000000 00000000 00000000 00000000 1001010.1 | This is the reset command, which puts the SD card into the SPI | mode if executed when the CS line is low.""" } EnableCard! {Sets /SS0 to 0} [def #$40 #0 #0 #0 #0 #$95] {CMD0 Reset} SendCommandToCard! { | """The SD card will respond to the reset command by sending a basic | 8-bit response on the MISO line. The structure of this response is | shown in Fig.6. The first bit is always a 0, while the other bits | specify any errors that may have occured when processing the last | message. If the command you sent was successfully received, then | you will receive the message (00000001)_2. | | 7 6 5 4 3 2 1 0 | +---+---+---+---+---+---+---+---+ | | 0 | | | | | | | | | +---+---+---+---+---+---+---+---+ | ^ ^ ^ ^ ^ ^ ^ | | | | | | | `--------in idle state | | | | | | `------------erase state | | | | | `----------------illegal command | | | | `--------------------CRC error | | | `------------------------erase sequence error | | `----------------------------address error | `--------------------------------parameter error | Figure 6 Format of a basic 8-bit Response to every command in SPI mode""" } WaitForCardReply! 1^ PrintResult! {Only 1 means success} pop ret ] CMD0= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $59 | +-----------------------------------------------------------------------} *=$59a0 { CMD8: SEND_IF_COND } [def { Send interface condition } push [def `CMD8`` #0] PrintText! { | """Following a successful reset, test if your system can successfully | communicate with the SD card by sending a different command. For | example, send one of the following commands, while keeping the CS | at value 0: (i) Command CMD8 | 01.001000 00000000 00000000 00000001 10101010 1000011.1 | (Fixed CRC -marcelk ^^^^^^^) | | This command is only available in the latest cards, compatible with | SD card Specifications version 2.0. For most older cards this command | should fail and cause the SD card to respond with a message that | this command is illegal. | (ii) Command CMD58 | 01.111010 00000000 00000000 00000000 00000000 0111010.1 | This command requests the contents of the operating conditions | register for the connected card. | | A response to these commands consists of 40 bits (see Fig.7), where | the first 8 bits are identical to the basic 8-bit response, while | the remaining 32 bits contain specific information about the SD | card. Although the actual contents of the remaining 32 bits are not | important for this discussion, a valid response indicates that your | command was transmitted and processed successfully. If successful, | the first 8 bits of the response will be either 00000001 or 00000101 | depending on the version of your SD card. | | 39 38 37 36 35 34 33 32 31...28 27 .. 12 11 .. 8 7 .. 0 | +--+--+--+--+--+--+--+--+-------+--------+-------+-------------+ | | 0| | | | | | | |Version|Reserved|Voltage|Check Pattern| | +--+--+--+--+--+--+--+--+-------+--------+-------+-------------+ | ^ ^ ^ ^ ^ ^ ^ | | | | | | | `-----in idle state | | | | | | `--------erase state | | | | | `-----------illegal command | | | | `--------------CRC error | | | `-----------------erase sequence error | | `--------------------address error | `-----------------------parameter error | Figure 7 The format of the 40-bit Response.""" } EnableCard! [def #$48 #0 #0 #1 #$aa #$87] {CMD8 Get Version, voltage 2.7-3.6V} SendCommandToCard! WaitForCardReply! $ff^ [if<>0 CardReply 4& [if<>0 1 CardType= {Version 1} else SendOnesToCard! {R7 response} SendOnesToCard! SendOnesToCard! SendOnesToCard! $aa^ [if=0 {If pattern response is correct} 2 CardType= {Version 2} ] ] ] CardReply 250& PrintResult! {Return result: 0, 1, 4 or 5 means success} pop ret ] CMD8= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5a | +-----------------------------------------------------------------------} *=$5aa0 { CMD58: READ_OCR } [def push [def `CMD58` #0] PrintText! EnableCard! [def #$7a #0 #0 #0 #0 #0] {CMD58} SendCommandToCard! WaitForCardReply! SendOnesToCard! {R3 response} SendOnesToCard! SendOnesToCard! $c0& $c0^ [if=0 4 CardType=] {CCS bit signals card is SDXC/SDHC} CardReply 254& PrintResult! {Only 0 and 1 mean success} pop ret ] CMD58= { CMD55: APP_CMD } [def { Defines to the card that the next command is an application specific command rather than a standard command } push [def `CMD55` #0] PrintText! [def #$77 #0 #0 #0 #0 #0] {CMD55} SendCommandToCard! WaitForCardReply! 254& PrintResult! {Only 0 and 1 mean success} pop ret ] CMD55= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5b | +-----------------------------------------------------------------------} *=$5ba0 { ACMD41 SD_SEND_OP_COND } [def { Sends hosts capacity support information and activates the card's initialization process. Reserved bits shall be set to '0' } push [def `ACMD41 #0] PrintText! CardType 1^ [if=0 [def #$69 #0 #0 #0 #0 #0] {ACMD41 for version 1.X} else [def #$69 #$40 #0 #0 #0 #0] {ACMD41 for version 2.0+} ] SendCommandToCard! WaitForCardReply! 254& PrintResult! {Only 0 and 1 mean success} pop ret ] ACMD41= { CMD16: SET_BLOCKLEN } [def { Set block size to 512 bytes } push [def `CMD16` #0] PrintText! [def #$50 #0 #0 #$02 #0 #0] {CMD16} SendCommandToCard! WaitForCardReply! 254& PrintResult! {Only 0 and 1 mean success} pop ret ] CMD16= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5c | +-----------------------------------------------------------------------} *=$5ca0 { CMD17: READ_SINGLE_BLOCK } [def { Reads a block of the size selected by the SET_BLOCKLEN command } push {[def `CMD17` #0] PrintText!} [def #$51 #0 #0 #0 #0 #0] {CMD17} p= q= >SectorH, SectorL, _vLR++ [ret] { RAM page $5d | +-----------------------------------------------------------------------} *=$5da0 { Bus ROM v4+ --- -------- A0 SCLK A1 (unused) A2 /SS0 A3 /SS1 A4 /SS2 A5 /SS3 A6 B0 A7 B1 A8-A14 (unused) A15 MOSI } { EnableCard } [def \SYS_ExpanderControl_v4_40 {SYS function} _sysFn= $8078 40!! {Enable SPI0, keep MOSI high, bank=1} ret ] EnableCard= { DisableCard } [def \SYS_ExpanderControl_v4_40 {SYS function} _sysFn= $807c 40!! {Disable SPI0, keep MOSI high, bank=1} ret ] DisableCard= { SendOnesToCard } [def 255 \sysArgs6. {Place byte in exchange buffer} \sysArgs6 _sysArgs0= {Begin} 1+ _sysArgs2= {End} \SYS_SpiExchangeBytes_v4_134 {SYS function} _sysFn= 134!! {Exchanges a single byte} \sysArgs6, {Reply byte} ret ] SendOnesToCard= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5e | +-----------------------------------------------------------------------} *=$5ea0 { UpdateCrc16 } [def { Update checksum for 1 byte in vACL. One caveat with SDC/MMC checksums is that reading all-zeroes still results in a valid check. } #\XORW #>Checksum {Inline assembly: XOR with Checksum's high byte} >Checksum. 8 [do i= {Loop for 8 bits} Checksum [if<0 {If bit 15 is 1} Checksum+ Checksum= {Shift left} $1021 Checksum^ {Apply CRC16-CCITT polynomial} else {Else bit 15 is 0} Checksum+] {Shift left} Checksum= {And store result} i 1- if>0loop] ret ] UpdateCrc16= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5f | +-----------------------------------------------------------------------} *=$5fa0 { SendCommandToCard } [def push { Setup command bytes } p= Buffer q= 255 q. 0loop] {Looping} { Send to SPI device } Buffer _sysArgs0= {Begin} 8+ _sysArgs2= {End and overwrite exchange buffer} \SYS_SpiExchangeBytes_v4_134 {SYS function} _sysFn= 134!! pop ret ] SendCommandToCard= { WaitForCardReply } [def push { | """To receive this message, your program should continuously toggle | the SD CLK signal and observe the MISO line for data, while keeping | the MOSI line high and the CS line low. Your program can detect the | message, because every message begins with a 0 bit, and when the | SD card sends no data it keeps the MISO line high.""" | | """Note that the response to each command is sent by the card a few | SD CLK cycles later. If the expected response is not received within | 16 clock cycles after sending the reset command, the reset command | has to be sent again.""" } 8 [do i= {Poll for upto 8 reply bytes} SendOnesToCard! {Keep MOSI line high by only sending ones} 128& {Note: communication is byte-aligned} if<>0 {Break out when valid message detected} i 1- if>0loop] {Or when loop counter exhausted} {32 PrintChar! {Space}} \sysArgs6, CardReply= {Store reply from card} PrintByte! {Print hex value} CardReply {As return value} pop ret ] WaitForCardReply= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $60 | +-----------------------------------------------------------------------} *=$60a0 { InitCard } [def { Put card in SPI mode and set block size } push [do CMD0! {Put card in SPI mode and enable card} if<>0 DisableCard! {Retry on failure} \frameCount, 60- if<0loop] {No more than 1 second} CMD8! {Detect version 1.X or later} [do { TODO: "In SPI mode CMD1 and ACMD41 have the same behavior" } CMD55! ACMD41! {Query condition} CardReply if<>0 {Until the card is initialised} \frameCount, 120- if<0loop] {No more than 2 seconds} CardType 2^ [if=0 CMD58!] {Detect standard capacity or SDHC/SDXC} CMD16! {Set block size to 512 bytes} DisableCard! {Initialisation completed} pop ret ] InitCard= {-----------------------------------------------------------------------+ | | | FAT32 section | | | +-----------------------------------------------------------------------} { ReadVolumeId } [def { Reads the first block of the partition. Mind that, despite it's name, this block doesn't hold the volume label: that is stored in the root directory instead. } push ReadSector! {SectorL,H as set by ReadMBR} [def `Vol.ID` #0] PrintText! $00b Address+ deek k= {Confirm expected sector length} PrintWord! {Print as little-endian word} 512 k^ PrintResult! pop ret ] ReadVolumeId= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $61 | +-----------------------------------------------------------------------} *=$61a0 { ReadMBR } [def { Reads MBR and finds the primary partition's start block Check if it is FAT32 } push 0 SectorL= SectorH= {First sector on device} ReadSector! {Read MBR} [def `MBR` #0] PrintText! $1fe Address+ deek k= {Fetch signature} $aa55 k^ {Check signature} [if=0 $1c6 Address+ deek SectorL= {Primary partition's first sector on disk} $1c8 Address+ deek SectorH= $1c2 Address+ peek k= {Filesystem type code} PrintByte! $0b k^ [if<>0 $07^] {Accepts $0b and $0c} {XXX In case of $0b: check for CHS vs LBA ???} ] pop ret ] ReadMBR= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $62 | +-----------------------------------------------------------------------} *=$62a0 { InitFat32 } [def push { Calculate cluster base and set current directory to root ClusterBaseL,H = PartitionL,H from MBR + Reserved Sectors from VolumeId + (Number of FATs * FAT size) from VolumeId - 2 * ClusterSize from VolumeId } $00d Address+ peek {Sectors per cluster} ClusterSize= SectorL ValueL= {Partition's first sector, from MBR} SectorH ValueH= $00e Address+ deek OffsetL= {Number of reserved sectors} 0 OffsetH= AddOffset! FatBaseL= ValueH FatBaseH= {Begin of primary FAT area} $024 Address+ deek OffsetL= {FAT size in sectors} $026 Address+ deek OffsetH= AddOffset! AddOffset! {Number of FATs is always 2} 0 ClusterSize- ClusterSize- {Subtract twice to arrive at ClusterBase} OffsetL= $ffff OffsetH= AddOffset! ClusterBaseL= ValueH ClusterBaseH= { First cluster of root directory } $02c Address+ deek CurrentDirL= $02e Address+ deek CurrentDirH= pop ret ] {InitFat32=} {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $63 | +-----------------------------------------------------------------------} *=$63a0 InitFat32= { OpenSector } [def { Tell card to read sector, but don't process the data yet } push EnableCard! CardType 2- [if<0 SectorToByte!] {Version 1.X cards do byte addressing} CMD17! {Request block of data} 0 Checksum= {Reset CRC16 checksum} pop ret ] OpenSector= { ReadSector } [def { Read sector from card into memory (clobbers ValueL,H and OffsetL,H) } push OpenSector! {Start data stream} Address q= {Setup write pointer} 0 Checksum= 512 [do k= {Number of bytes to read} SendOnesToCard! {XXX Read directly into buffer} q. {Store byte in buffer} UpdateCrc16! q 1+ q= {Advance write pointer, cross page boundaries} k 1- if>0loop] {Looping} SendOnesToCard! UpdateCrc16! {Read 16-bit checksum} SendOnesToCard! UpdateCrc16! DisableCard! Checksum {CRC16 will be zero on success} pop ret ] ReadSector= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $64 | +-----------------------------------------------------------------------} *=$64a0 { LoadByte } [def { Read next byte directly from file and advance position !!! XXX This is slow, but doesn't use buffer space !!! } push FilePosL FileSizeL^ {Check for EOF} [if=0 FilePosH FileSizeH^] [if<>0 {More to read} $1ff FilePosL& {Read new sector} [if=0 SendOnesToCard! {Ignore checksum} SendOnesToCard! OpenSector! {Open next sector} NextSector!] FilePosL 1+ FilePosL= {Increment position in file} [if=0 FilePosH 1+ FilePosH=] SendOnesToCard! {Returns the next data byte} else 1- {Negative value for EOF} ] pop ret ] LoadByte= { ReadDirectory } [def { Read first sector of current directory } push { Sector = ClusterBase + CurrentDir * ClusterSize } CurrentDirL ValueL= CurrentDirH ValueH= ClusterToSector! ReadSector! _vLR++ [ret] { RAM page $65 | +-----------------------------------------------------------------------} *=$65a0 { OpenFile } [def { Prepare for reading file whose directory entry is pointed at by p } push 0 FilePosL= FilePosH= {Reset position in file} p $1c+ deek FileSizeL= {Length of file in bytes} p $1e+ deek FileSizeH= p $1a+ deek ValueL= {First cluster for file} p $14+ deek ValueH= ReadClusterChain! ClusterToSector! pop ret ] OpenFile= {-----------------------------------------------------------------------+ | | | 32-bit arithmetic section | | | +-----------------------------------------------------------------------} { ClusterToSector } [def { Multiply ValueL,H by ClusterSize and add ClusterBase (clobbers OffsetL,H) } push >ValueH, 15& >ValueH. {Clear the top 4 reserved bits} 1 [do k= ClusterSize- if<0 ShiftLeft! k k+ loop] ClusterBaseL OffsetL= ClusterBaseH OffsetH= AddOffset! ValueL SectorL= {Set as next sector to read} ValueH SectorH= pop ret ] ClusterToSector= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $66 | +-----------------------------------------------------------------------} *=$66a0 { SectorToByte } [def { Multiply 32-bit ValueL,H by 512 (clobbers OffsetL,H) XXX Figure out what to do with this! This CAN'T be correct any longer } push 0 OffsetL. >ValueL, OffsetH. OffsetL ValueL= {Then double once} OffsetH ValueH= AddOffset! pop ret ] SectorToByte= { ShiftLeft } [def { Shift left ValueL,H by 1 bit (clobbers OffsetL,H) } push ValueL OffsetL= {Double value} ValueH OffsetH= AddOffset! OffsetH {Return old high word} pop ret ] ShiftLeft= { AddOffset } [def { Add 32-bit OffsetL,H to 32-bit ValueL,H and store result there } ValueL OffsetL^ [if<0 {Compare lower halves' most significant bits} ValueL {MSB unequal: carry taken from their sum} else $8000 {MSB equal: carry taken from either term} ] OffsetL+ {Carry now in MSB of vAC (inverted)} [if>=0 ValueH 1+ ValueH=] {Apply carry to upper half} ValueH OffsetH+ ValueH= {Sum upper half} ValueL OffsetL+ ValueL= {Sum lower half and return this as well} ret ] {AddOffset=} {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $67 | +-----------------------------------------------------------------------} *=$67a0 AddOffset= {-----------------------------------------------------------------------+ | | | Video terminal section | | | +-----------------------------------------------------------------------} { SafePrintChar } [def { Print any byte value as character, map non-ASCII to block symbol XXX Clear 6th slice XXX Insert Newline when wrapping the line } push 32- [if<0 127 else 96- {Map any non-ASCII to block symbol 127} if>=0 127] 127& PrintChar! {And print it} pop ret ] SafePrintChar= { PrintValue } [def { Print 32-bit ValueL,H in hexadecimal } push ValueH PrintWord! ValueL PrintWord! Newline! pop ret ] PrintValue= { PrintWord } [def { Print 16-bit word in hexadecimal } push k= >k, PrintByte! _vLR++ [ret] { RAM page $68 | +-----------------------------------------------------------------------} *=$68a0 { PrintByte } [def { Print byte value in vAC as hexadecimal number } push 2-- %0= 4<< \vACH, PrintHexDigit! {High nibble} %0 2++ PrintHexDigit! {Low nibble} pop ret ] PrintByte= { PrintResult } [def { Print OK or FAILED } push k= {Preserve vAC for caller} [if=0 [def ``OK #10 #0] else [def ``FAILED #10 #0] ] PrintText! k pop ret ] PrintResult= { PrintHexDigit } [def { Print lowest nibble from vAC as hex digit } push 15& 10- [if<0 $3a+ else $41+] PrintChar! pop ret ] PrintHexDigit= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $69 | +-----------------------------------------------------------------------} *=$69a0 { PrintText -- writes q } [def { Print text string, may include newline characters (#10) Returns 0 } push {Save vLR because this is not a leaf subroutine} q= [do {Loop over characters} q, if<>0 {Next character to be printed, unless 0} 10^ [if<>0 10^ PrintChar! {Print the character and advance cursor} else Newline!] {Or go to next line} 0loop] Newline! pop ret ] PrintVolumeLabel= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6a | +-----------------------------------------------------------------------} *=$6aa0 { PrintTwoDecimals } [def push Number= $30 k= {Do print leading zeroes} 10 PrintDigit! {Print tens} Number $30+ PrintChar! {Print ones} pop ret ] PrintTwoDecimals= { PrintDate -- reads p, writes i j k q Pos _sysFn _sysArgs[01245] } [def push \SYS_LSRW1_48 _sysFn= {Prepare 1-bit right shift} p 17+ peek {Select year, origin is 1980} 48!! 20- [if<0 100+] PrintTwoDecimals! \SYS_LSRW5_50 _sysFn= {Prepare 5-bit shift right} p 16+ deek 50!! 15& {Select month 1-12} PrintTwoDecimals! p 16+ peek 31& {Select day 1-31} PrintTwoDecimals! pop ret ] PrintDate= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6b | +-----------------------------------------------------------------------} *=$6ba0 { PrintDirEntry -- reads p k, writes i j k q Pos _sysFn _sysArgs[01245] } [def { Format directory entry as "YYMMDD FILENAME.EXT" } push PrintDate! {YYMMDD} 4 Pos+ Pos= {Small space} PrintSize! {File size or directory indicator} 4 Pos+ Pos= {Small space} PrintName! {FILENAME.EXT} Newline! {End of line} pop ret ] PrintDirEntry= { PrintName } [def { Print 8.3 filename } push p q= {Read pointer} 11 [do k= {Loop over 11 positions} 3^ [if=0 q, 32^ if<>0 {If there is an extension} $2e PrintChar!] {Period '.' before extension} q, 0 32^ {Skip spaces (padding)} SafePrintChar!] {Print the next character} k 1- if>0loop] pop ret ] PrintName= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6c | +-----------------------------------------------------------------------} *=$6ca0 { PrintDigit -- reads k Value, writes i j k Number Pos _sysFn _sysArgs[01245] } [def { Extract and print decimal digit or leading space } push i= {Radix as argument, keep in i} Number [do {Calculate Value/Radix} i- {Subtract i} if>=0 {As many times as fits} Number= _vLR++ [ret] { RAM page $6d | +-----------------------------------------------------------------------} *=$6da0 { Newline -- writes i Pos _sysFn _sysArgs[01245] } { !!! This version scrolls only pixel row 16 through 119 !!! } [def {Clear new line first} $3f20 _sysArgs0= {White on blue} 0 Pos. {Go to start of next line} Pos _sysArgs4= {Set screen position} \sysArgs2. {Set all-zero output pattern} [do \SYS_VDrawBits_134 {SYS call to draw 8 pixels vertically} _sysFn= 134!! <_sysArgs4++ {Step 1 pixel right} \sysArgs4, 160^ {Test for end of line} if<>0loop] {Then scroll up by modifying videoTable} $01 >i. 208 {Last entry at $100+238, minus 30} [do 30+ 0loop] {Move to previous entry in video table} ret ] Newline= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6e | +-----------------------------------------------------------------------} *=$6ea0 { PrintChar -- reads Pos, writes i j Pos _sysFn _sysArgs[01245] } [def { Print ASCII character (>=32) on screen using the 5x8 pixel built-in font. This is a bare bones version: 1. No handling of newline characters 2. No implicit linewrapping 3. No blanking of the 1 pixel horizontal space between characters } 82- {Map ASCII code to offset in font table} [if<0 50+ i= &_font32up {First page for ASCII 32..81} else i= &_font82up] j= {Second page is ASCII 82..127} i 2<< i+ {Multiply by 5} j+ j= {Add page address to reach bitmap data} $3f20 _sysArgs0= {White on blue} Pos _sysArgs4= {Position of character} 6+ Pos= {Advance position by 6 pixels for next call} \SYS_VDrawBits_134 _sysFn= {Prepare SYS calls} 5 [do i= {Loop to draw 5 vertical slices of 8 pixels} j 0? \sysArgs2. {Get byte from ROM using `LUP 0' instruction} 134!! {Invoke SYS function to draw 8 vertical pixels} 0loop] {Looping} ret ] PrintChar= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6f | +-----------------------------------------------------------------------} *=$6fa0 { PrintDirectory } [def { Print directory contents } push Address [do p= {Loop over all directory entries} $200 Address+ p- [if<=0 {If past the end of the segment} ReadSector! {Then read more bytes from device} 0 {Test for end of directory marker} $e5^ [if<>0 {Skip unused entries} p $0b+ peek k= {Fetch attributes} $0f^ if<>0 {Skip long filename information} 2& if<>0 {Skip hidden entries} k 8& [if<>0 PrintVolumeLabel! {Print label} else PrintDirEntry! {Print entry} IsBootGt1! {Are we looking for this file?} [if=0 {File found} LoadGt1! {Load file} Execute! {Execute otherwise} ] ] ] p 32+ loop] {Hop to next slot} pop ret ] PrintDirectory= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $70 | +-----------------------------------------------------------------------} *=$70a0 { PrintSize } [def { Print size in slot as 32-bit decimal ("%5lu") or as " " for directory slots } push p $0b+ peek 16& [if<>0 [def ``` #0] {Directory} else p 28+ deek ValueL= {File size in bytes} p 30+ deek ValueH= ValueToDecimal! {Base conversion} 0 j= 8 [do i= {Loop over no more than 9 digits} q, $30^ if=0 {Still a leading zero?} i 6- [if<0 32 q. {Convert last 5 into a space} else =0loop] Buffer j+ ] PrintText! pop ret ] PrintSize= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $71 | +-----------------------------------------------------------------------} *=$71a0 { ValueToDecimal } [def { Convert unsigned 32-bit ValueL,H to 10-digit decimal string } push 9 [do i= {For every position i} Buffer i+ q= $30 q. {Buffer[i] = '0'} i 1- if>=0loop] 31 [do j= {For every bit} ShiftLeft! {ValueL,H <<= 1} [if>=0 $6a {No carry} else $69] k= {Carry} 9 [do i= {Double the decimal result with carry} Buffer i+ q= {q = &Buffer[i]} q, 1<< k- {Double the next decimal with carry} [if<0 $3a+ q. $6a {No carry} else $30+ q. $69] k= {Carry} i 1- if>=0loop] j 1- if>=0loop] pop ret ] {ValueToDecimal=} {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $72 | +-----------------------------------------------------------------------} *=$72a0 ValueToDecimal= [def `---------- #0] Buffer= {String space for 32-bit unsigned decimal} {-----------------------------------------------------------------------+ | | | File reading section | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $73 | +-----------------------------------------------------------------------} *=$73a0 { LoadGt1 } [def { Load GT1 file into RAM and store start address in Address. See Docs/GT1-files.txt for details on GT1 XXX Support loading into audio channels } push [def `Loading` #0] PrintText! PrintName! OpenFile! {Prepare for reading} { Read file contents byte by byte } LoadByte! {Fetch first byte} [do {Chunk copy loop} >Address. {High-address comes first} LoadByte! 0loop] LoadByte! {Go to next block} if<>0loop] LoadByte! >Address. {Load execution address} LoadByte! =0 0 Address=] {Expect EOF, clear Address when missing} 512 [do i= {Read tail of sector} SendOnesToCard! i 1- if>0loop] DisableCard! pop ret ] LoadGt1= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $74 | +-----------------------------------------------------------------------} *=$74a0 { IsBootGt1 } [def { Check if directrory entry p is SYSTEM.GT1 Returns 0 when name matches, non-zero otherwise } [def `SYSTEM``GT1 #0] q= {String to match} p i= [do q, 0 j= {Compare until nul terminator} i, 0loop] Address [do if=0loop] {Stop if not executable} call {Run...} ] Execute= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $75 | +-----------------------------------------------------------------------} *=$75a0 {*** some FAT functions ***} { NextSector } [def push _vLR++ [ret] { RAM page $76 | +-----------------------------------------------------------------------} *=$76a0 { ReadClusterChain } [def { Traverse the FAT and collect the clusters used by this file XXX TODO Don't do this if the file is smaller than the cluster size } push \ClusterList List= {Reset} Newline! PrintValue! [do { Store in list } ValueL List: 0 {Optionally test low word, ignore bit 0:2} NextCluster! PrintValue! loop] \ClusterList {Reset} List= deek ValueL= List 2+ deek ValueH= pop ret ] ReadClusterChain= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $77 | +-----------------------------------------------------------------------} *=$77a0 { NextCluster } [def { Find next cluster in FAT } push >ValueL, > 8} OffsetL. >ValueH, OffsetH= > 7} FatBaseL OffsetL= {Value += First sector for FAT} FatBaseH OffsetH= AddOffset! SectorL^ {Compare sector number to what we already have} [if=0 ValueH SectorH^] [if<>0 ValueL SectorL= {Read new sector from FAT area} ValueH SectorH= ReadSector! ] m 127& 2<< Address+ {Pointer in FAT sector at 32-bit word i & $7f} m= deek ValueL= {Fetch next cluster number in the chain} m 2+ deek ValueH= pop ret ] NextCluster= {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $78 | +-----------------------------------------------------------------------} *=$78a0 {-----------------------------------------------------------------------+ | | | Main program | | | +-----------------------------------------------------------------------} [def #10 `***`Memory`card #10 #0] PrintText! 0 \frameCount. {Reset timer} InitCard! [def `CardType` #0] PrintText! CardType PrintByte! Newline! {Show detected card version} ReadMBR! PrintResult! {Master Boot Record} [if=0 ReadVolumeId!] {Read first block of FAT partition} InitFat32! ReadDirectory! {Read root directory XXX Move into PrintDir} [if=0 PrintDirectory!] {List directory, find SYSTEM.GT1 and execute it} ##\HALT {Halt if not found} {-----------------------------------------------------------------------+ | RAM page $7f | +-----------------------------------------------------------------------} _ClusterList=$7fa0 {Room for 96/4 = 24 clusters} {-----------------------------------------------------------------------+ | | +-----------------------------------------------------------------------}