{-----------------------------------------------------------------------+ | CardBoot | | | | This has been modified by lb3361 to make the boot process faster | | and more reliable. No attempt was made to make this code usable | | for anything but booting from SD with minimal footprint. | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ | | | 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 { ??? Positive card detection: ( Read MISO's/Invert/Write to ZP/Read MIOS ) WONT Support loading of segments in zero page WONT 64K systems: put code in highest pages ($ffff and down) WONT 32K systems: put code in bottom screen memory ($7f9f and down) OK On the matter of scrolling: When to restore the video table? OK LoadtGt1: better closing of sector OK Compliancy: Byte addressing for V2 cards when CCS bit in CMD58 OCR is 0 NO Compliancy: MMC v3 card detection with CMD1 OK Compliancy: Don't set block size with CMD16 if CCS bit in OCS is 1 OK 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... OK Compliancy: Follow cluster chain for directories as well (LB3361: DONE) OK Speed: Transfer 256 bytes at once WONT Think about card detect and monitoring it (inserted = low) NA Figure out why Apple formats card with type 0x0b References: http://elm-chan.org/docs/mmc/mmc_e.html http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf 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 Ptr XX Pointer to the current byte in an open file Len 0X Number of bytes valid at Ptr in current page Memory usage ------------ $58a0 $7bff Cardboot code $7c00 $7cff Page filled with $FF for SpiExchangeBytes $7d00 $7eff Sector buffer $7f00 $7fff Cluster list } {-----------------------------------------------------------------------+ | RAM page 2 | +-----------------------------------------------------------------------} \romType, \romTypeValue_ROMv5- {Version check >= ROMv5} [if<0 do _frameCount _vPCH: loop] \SectorBuffer Address= {512 bytes memory for one sector} { Make a black screen. - Set videoTop to hide the top two lines. - Clear the rest of the screen with a black background. } \videoTop_v5 pVideoTop= $0 \ResetScreen! $20 pVideoTop. { Uncomment this line to hide the buffers $7c00-$7fff } $1e8 i= [do $7b i. i 2+ i= $1f0 i^ if<>0loop] { Fill page $7c with $ff for SYS_SpiExchangeBytes } \FFPage \sysArgs3. $ff \sysArgs1: 0 \sysArgs0. 54!! {-----------------------------------------------------------------------+ |}$58a0 _vLR= [ret]{ RAM page $58 | +-----------------------------------------------------------------------} *=$58a0 {-----------------------------------------------------------------------+ | | | Memory card section | | | +-----------------------------------------------------------------------} { CMD0: GO_IDLE_STATE } [def _CMD0=* { 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 ] [def _CMD0Retries=* 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} pop ret] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $59 | +-----------------------------------------------------------------------} *=$59a0 { CMD8: SEND_IF_COND } [def _CMD8=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5a | +-----------------------------------------------------------------------} *=$5aa0 { CMD58: READ_OCR } [def _CMD58=* push {[def `CMD58` #0] \PrintText!} \EnableCard! [def #$7a #0 #0 #0 #0 #0] {CMD58} \SendCommandToCard! \WaitForCardReply! \SendOnesToCard! {R3 response} $40& [if<>0 4 CardType=] {CCS bit signals card is SDXC/SDHC} \SendOnesToCard! \SendOnesToCard! CardReply 254& {\PrintResult!} {Only 0 and 1 mean success} pop ret ] { CMD55: APP_CMD } [def _CMD55=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5b | +-----------------------------------------------------------------------} *=$5ba0 { ACMD41 SD_SEND_OP_COND } [def _ACMD41=* { 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 ] [def _ACMD41Retries=* push [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} pop ret ] { CMD16: SET_BLOCKLEN } [def _CMD16=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5c | +-----------------------------------------------------------------------} *=$5ca0 { CMD17: READ_SINGLE_BLOCK } [def _CMD17=* { 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= SectorL ValueL= SectorH ValueH= CardType 2- [if<=0 \SectorToByte!] {Version 1.X cards do byte addressing} >ValueH, ValueL, _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 _EnableCard=* \SYS_ExpanderControl_v4_40 {SYS function} _sysFn= $8078 40!! {Enable SPI0, keep MOSI high, bank=1} ret ] { \DisableCard } [def _DisableCard=* \SYS_ExpanderControl_v4_40 {SYS function} _sysFn= $807c 40!! {Disable SPI0, keep MOSI high, bank=1} ret ] { \SendOnesToCard } [def _SendOnesToCard=* 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $5e | +-----------------------------------------------------------------------} *=$5ea0 { \UpdateCrc16 -- COMMENTED OUT [def _UpdateCrc16=* { Update checksum for 1 byte in vACL. One caveat with SDC/MMC checksums is that reading all-zeroes still results in a valid check. This is no longer used. } #\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 ] } { \CheckBootBlock } [def _CheckBootBlock=* { Return 0 for potential MBR, 1 for potential FAT32 boot block } [def `FAT32``` #0 ] q= $52 Address+ i= [do q, _vLR++ [ret] { RAM page $5f | +-----------------------------------------------------------------------} *=$5fa0 { \SendCommandToCard } [def _SendCommandToCard=* 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 ] { \WaitForCardReply } [def _WaitForCardReply=* 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $60 | +-----------------------------------------------------------------------} *=$60a0 { \InitCard } [def _InitCard=* { Put card in SPI mode and set block size } push \CMD0Retries! {No more than 1 second} \CMD8! {Detect version 1.X or later} \ACMD41Retries! {No more than 2 seconds} CardType 2- [if=0 \CMD58!] {Detect standard capacity or SDHC/SDXC} CardType 2- [if<=0 \CMD16!] {Set block size to 512 bytes} \DisableCard! {Initialisation completed} pop ret ] {-----------------------------------------------------------------------+ | | | FAT32 section | | | +-----------------------------------------------------------------------} { \ReadVolumeId } [def _ReadVolumeId=* { 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. (LB) In fact this routine just checks that we have a good looking FAT32 boot block with an appropriate logical sector size. } push \ReadSector! {SectorL,H as set by ReadMBR} [def `FAT32` #0] \PrintText! {Check FAT32 block} \CheckBootBlock! 1^ [if=0 $00b Address+ deek k= 512 k^ {Confirm expected sector length} ] \PrintResult! pop ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $61 | +-----------------------------------------------------------------------} *=$61a0 { \ReadMBR } [def _ReadMBR=* { 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! \CheckBootBlock! {Return 0 if potential MBR} [if=0 $1c6 Address+ deek SectorL= {Primary partition's first sector on disk} $1c8 Address+ deek SectorH= $1c2 Address+ peek k= {Filesystem type code} else $ff {FF when not a MBR} ] \PrintByte! $0b k^ [if<>0 $07^] {Accepts $0b and $0c} pop ret ] { \CompCluster } [def _CompCluster=* ClusterSize= ClusterSize+ 1- \vACH. $ff| ClusterMask= ret] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $62 | +-----------------------------------------------------------------------} *=$62a0 { \InitFat32 } [def _InitFat32=* 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 \CompCluster! {Sectors per cluster} SectorL ValueL= {Partition's first sector, from MBR} SectorH ValueH= $00e Address+ deek OffsetL= {Number of reserved sectors} \AddSmallOffset! 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $63 | +-----------------------------------------------------------------------} *=$63a0 { \ReadSector } [def _ReadSector=* { Read sector from card into memory (clobbers ValueL,H and OffsetL,H) } push \EnableCard! \CMD17! {Request block of data} \sysArgs6, $fe^ [if=0 \SYS_SpiExchangeBytes_v4_134 \sysFn: $7c00 \sysArgs0: Address \sysArgs2: 134!! { Read first 256 bytes} <\sysArgs3++ 134!! \SendOnesToCard! {Skip 16-bit checksum} \SendOnesToCard! \DisableCard! 0 {Success} ] pop ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $64 | +-----------------------------------------------------------------------} *=$64a0 { \LoadData } [def _LoadData=* { Makes Ptr point to available data. Returns number of bytes available at this address before reaching the end of the page } push FilePosH FileSizeH^ [if=0 FilePosL FileSizeL^ \vACH,] [if=0 FileSizeL else FilePosL $ff| 1+] FilePosL- Len= [if<>0 $1ff FilePosL& {Read new sector} [if<>0 Address+ {Pointer position} else \ReadSector! {Reads next sector into buffer} [if=0 \NextSector! {and determines next sector} {0 _vLR++ [ret] { RAM page $65 | +-----------------------------------------------------------------------} *=$65a0 { \OpenFile } [def _OpenFile=* { 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 ] {-----------------------------------------------------------------------+ | | | 32-bit arithmetic section | | | +-----------------------------------------------------------------------} { \ClusterToSector } [def _ClusterToSector=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $66 | +-----------------------------------------------------------------------} *=$66a0 { \SectorToByte, \ShiftLeft } [def _SectorToByte=* { Multiply 32-bit ValueL,H by 512 (clobbers OffsetL,H) } push ValueH. {First shift left by one byte} >ValueL, ValueL. 0 =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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $67 | +-----------------------------------------------------------------------} *=$67a0 {-----------------------------------------------------------------------+ | | | Video terminal section | | | +-----------------------------------------------------------------------} { \SafePrintChar } [def _SafePrintChar=* { 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 ] { \PrintValue } [def _PrintValue=* { Print 32-bit ValueL,H in hexadecimal } push ValueH \PrintWord! ValueL \PrintWord! \Newline! pop ret ] { \PrintWord } [def _PrintWord=* { Print 16-bit word in hexadecimal } push k= >k, \PrintByte! _vLR++ [ret] { RAM page $68 | +-----------------------------------------------------------------------} *=$68a0 { \PrintByte } [def _PrintByte=* { Print byte value in vAC as hexadecimal number } push 2-- %0= 4<< \vACH, \PrintHexDigit! {High nibble} %0 2++ \PrintHexDigit! {Low nibble} pop ret ] { \PrintResult } [def _PrintResult=* { 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 ] { \PrintHexDigit } [def _PrintHexDigit=* { Print lowest nibble from vAC as hex digit } push 15& 10- [if<0 $3a+ else $41+] \PrintChar! pop ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $69 | +-----------------------------------------------------------------------} *=$69a0 { \PrintText -- writes q } [def _PrintText=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6a | +-----------------------------------------------------------------------} *=$6aa0 { \PrintTwoDecimals } [def _PrintTwoDecimals=* push Number= $30 k= {Do print leading zeroes} 10 \PrintDigit! {Print tens} Number $30+ \PrintChar! {Print ones} pop ret ] { \PrintDate -- reads p, writes i j k q Pos _sysFn _sysArgs[01245] } [def _PrintDate=* 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6b | +-----------------------------------------------------------------------} *=$6ba0 { \PrintDirEntry -- reads p k, writes i j k q Pos _sysFn _sysArgs[01245] } [def _PrintDirEntry=* { 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 ] { \PrintName } [def _PrintName=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6c | +-----------------------------------------------------------------------} *=$6ca0 { \PrintDigit -- reads k Value, writes i j k Number Pos _sysFn _sysArgs[01245] } [def _PrintDigit=* { 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 111 !!! } [def _Newline=* {Clear new line first} $3f00 _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. 192 {Last entry at $100+238, minus 30, minus 16} [do 30+ 0loop] {Move to previous entry in video table} ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6e | +-----------------------------------------------------------------------} *=$6ea0 { \PrintChar -- reads Pos, writes i j Pos _sysFn _sysArgs[01245] } [def _PrintChar=* { 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} $3f00 _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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $6f | +-----------------------------------------------------------------------} *=$6fa0 { \PrintDirectory } [def _PrintDirectory=* { Print directory contents Execute SYSTEM.GT1 if found } push [do \LoadData! Ptr p= peek if<>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} 208 pVideoTop. {Blank many lines for speed} \LoadGt1! {Load file} \Execute! {Execute otherwise} ] ] ] 32 \IncFilePos! {Next slot} loop] pop ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $70 | +-----------------------------------------------------------------------} *=$70a0 { \PrintSize } [def _PrintSize=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $71 | +-----------------------------------------------------------------------} *=$71a0 { \ValueToDecimal } [def _ValueToDecimal=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $72 | +-----------------------------------------------------------------------} *=$72a0 [def `---------- #0] Buffer= {String space for 32-bit unsigned decimal} { ResetScreen } [def _ResetScreen=* { Reset screen with vAC color and regular videoTable Still save the top two lines } push \sysArgs1. \SYS_SetMemory_v2_54 \sysFn: $18 i= $120 k= [do i k. \sysArgs3. 0 \sysArgs2. $A0 \sysArgs0. 54!! 1 i+ i= 2 k+ k= $1f0 k^ if<>0loop] 0 pVideoTop. pop ret ] {-----------------------------------------------------------------------+ | | | File reading section | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $73 | +-----------------------------------------------------------------------} *=$73a0 { \LoadGt1 } [def _LoadGt1=* { 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} >caddr. {High-address comes first} \LoadByte! 0loop] \LoadByte! >caddr. {Load execution address} \LoadByte! =0 0 caddr=] {Expect EOF, clear caddr when missing} \DisableCard! pop ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $74 | +-----------------------------------------------------------------------} *=$74a0 { \IsBootGt1 } [def _IsBootGt1=* { 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, _vLR++ [ret] { RAM page $75 | +-----------------------------------------------------------------------} *=$75a0 { \NextSector } [def _NextSector=* push \IncSector! {Next sector} $200 FilePosL+ ClusterMask& [if=0 List 4+ List= deek ValueL= {Get next cluster from ClusterList} List 2+ deek ValueH= \ClusterToSector! ] pop ret ] { \IncFilePos } [def _IncFilePos=* { Increment file position by vAC bytes (strong carry assumptions here) Side effect: store vAC in i } i= FilePosL+ FilePosL= [if=0 1 FilePosH+ FilePosH= ] Len i- Len= ret ] { \LoadByte } [def _LoadByte=* push { Returns next byte or -1 if EOF } \LoadData! [if>0 1 \IncFilePos! Ptr, else -1] pop ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $76 | +-----------------------------------------------------------------------} *=$76a0 { \ReadClusterChain } [def _ReadClusterChain=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $77 | +-----------------------------------------------------------------------} *=$77a0 { \NextCluster } [def _NextCluster=* { 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 ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $78 | +-----------------------------------------------------------------------} *=$78a0 { \CopyGt1Segment } [def _CopyGt1Segment=* { Copy data for segment caddr,caddr+clen } push [do \LoadData! if>0 clen- [if>0 0] clen+ \IncFilePos! Ptr \sysArgs2: caddr \sysArgs0: i+ caddr= \SYS_CopyMemory_v6_80 \sysFn: i 80!! clen i- clen= if>0loop] pop ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $79 | +-----------------------------------------------------------------------} *=$79a0 { \OpenMainDirectory } [def _OpenMainDirectory=* { Prepare for reading main directory } push 0 FilePosL= FilePosH= {Reset position in file} 0 FileSizeL= 1 FileSizeH= {Excessive length} CurrentDirL ValueL= CurrentDirH ValueH= \ReadClusterChain! \ClusterToSector! pop ret ] { \PrintCardType } [def _PrintCardType=* { Show detected card version } push [def `CardType` #0] \PrintText! CardType \PrintByte! \Newline! pop ret ] {-----------------------------------------------------------------------+ |} >_vLR++ [ret] { RAM page $79 | +-----------------------------------------------------------------------} *=$7aa0 {-----------------------------------------------------------------------+ | | | Main program | | | +-----------------------------------------------------------------------} [def #10 `***`CardBoot #10 #0] \PrintText! 0 \frameCount. {Reset timer} \InitCard! \PrintCardType! \ReadMBR! \PrintResult! {Master Boot Record} \ReadVolumeId! {Read first block of FAT partition} [if=0 \InitFat32! \OpenMainDirectory! {Read root directory XXX Move into PrintDir} \PrintDirectory! {List directory, find SYSTEM.GT1 and execute it} ] 32 pVideoTop. {Not found!} \Newline! [def `SYSTEM.GT1`not`found! #0 ] \PrintText! ##\HALT {Halt if not found} {-----------------------------------------------------------------------+ | RAM page $7c...7f | +-----------------------------------------------------------------------} _FFPage=$7c _SectorBuffer=$7d00 {512 bytes} _ClusterList=$7f00 {Room for 256/4 = 64 clusters = 32KB} _SYS_CopyMemory_v6_80=$e9 {-----------------------------------------------------------------------+ | | +-----------------------------------------------------------------------}