1566 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			1566 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|       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, <q++ q.                    {Put SectorL,H in argument, big-endian order}
 | 
						|
  <ValueH, <q++ q.
 | 
						|
  >ValueL, <q++ q.
 | 
						|
  <ValueL, <q++ q.
 | 
						|
  p \SendCommandToCard!
 | 
						|
 | 
						|
  \WaitForCardReply!
 | 
						|
  254& {\PrintResult!}           {Only 0 and 1 mean success}
 | 
						|
 | 
						|
  [if=0
 | 
						|
    {Wait for first data byte}
 | 
						|
    {[def `Wait``` #0] \PrintText!}
 | 
						|
    [do
 | 
						|
      \SendOnesToCard!          {XXX Can we use WaitForCardReply here?}
 | 
						|
      $ff^ if=0loop]            {XXX Loop needs a timeout}
 | 
						|
      {\sysArgs6, \PrintByte!}
 | 
						|
      {\sysArgs6, $fe^ \PrintResult!} {Only $fe is OK}
 | 
						|
  ]
 | 
						|
 | 
						|
  pop ret
 | 
						|
] 
 | 
						|
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|} >_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, <q++ k= [if=0 1 ret]
 | 
						|
     i, <i++ k^ if=0loop]
 | 
						|
  $1fe Address+ deek k=
 | 
						|
  $aa55 k^
 | 
						|
  ret
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|} >_vLR++ [ret] {      RAM page $5f                                    |
 | 
						|
+-----------------------------------------------------------------------}
 | 
						|
*=$5fa0
 | 
						|
 | 
						|
{ \SendCommandToCard }
 | 
						|
[def _SendCommandToCard=*
 | 
						|
  push
 | 
						|
 | 
						|
  { Setup command bytes }
 | 
						|
  p= Buffer q=
 | 
						|
  255 q. <q++ q. <q++           {Start with two dummy bytes}
 | 
						|
  6 [do i=                      {Copy 6 command bytes to exchange buffer}
 | 
						|
    p, <p++                     {Fetch byte and advance read pointer}
 | 
						|
    q. <q++                     {Store byte and advance write pointer}
 | 
						|
    i 1- if>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 <Pos. FilePosH \PrintWord! FilePosL \PrintWord!}
 | 
						|
        Address
 | 
						|
       else                      {error}
 | 
						|
        FilePosH FileSizeH= FilePosL FileSizeL=
 | 
						|
        0 ] ] ]
 | 
						|
  Ptr= Len
 | 
						|
  pop ret 
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|} >_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, >ValueH.            {First shift left by one byte}
 | 
						|
  >ValueL, <ValueH.
 | 
						|
  <ValueL, >ValueL.
 | 
						|
  0        <ValueL.
 | 
						|
 | 
						|
  _ShiftLeft=*
 | 
						|
  {
 | 
						|
    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
 | 
						|
] 
 | 
						|
 | 
						|
{ \AddSmallOffset \AddOffset }
 | 
						|
[def
 | 
						|
  _AddSmallOffset=*
 | 
						|
  {
 | 
						|
    Add 16-bit OffsetL to 32-bit ValueL,H and store result there
 | 
						|
  }
 | 
						|
  0 OffsetH=
 | 
						|
 | 
						|
  _AddOffset=*
 | 
						|
  {
 | 
						|
    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
 | 
						|
]
 | 
						|
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|} >_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!
 | 
						|
  <k, \PrintByte!
 | 
						|
  pop ret
 | 
						|
]
 | 
						|
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|} >_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}
 | 
						|
    <q++ loop]                  {Advance text pointer and loop}
 | 
						|
  pop ret
 | 
						|
] 
 | 
						|
 | 
						|
{ \PrintVolumeLabel }
 | 
						|
[def _PrintVolumeLabel=*
 | 
						|
  push
 | 
						|
 | 
						|
  [def `Volume` #0] \PrintText!
 | 
						|
 | 
						|
  { Volume label 11 characters }
 | 
						|
  p q=                          {Read pointer}
 | 
						|
  11 [do k=                     {Loop over 12 positions}
 | 
						|
    q, <q++                     {Print the next character}
 | 
						|
    \SafePrintChar!
 | 
						|
    k 1- if>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 <size> 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, <q++
 | 
						|
    32^ [if<>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=
 | 
						|
    <k++                        {Increment 0..9 times}
 | 
						|
    loop]
 | 
						|
  k [if=0                       {If leading zero digit}
 | 
						|
    32 \PrintChar!               {Space}
 | 
						|
  else
 | 
						|
    $30| \PrintChar!             {Map to $30..$39 range and print digit}
 | 
						|
    $30 k=                      {And mark all further zeroes as non-leading}
 | 
						|
  ]
 | 
						|
  pop ret
 | 
						|
] 
 | 
						|
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|} >_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. $120 peek >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+ <i.
 | 
						|
    i, 112- [if<0 120+
 | 
						|
             else 24+] i.       {Rotate by 8 in 24..119 range}
 | 
						|
    <i, 32- if>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}
 | 
						|
    <j++ <_sysArgs4++           {Advance to next slice in ROM and on screen}
 | 
						|
    i 1- if>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 "  <DIR>" for directory slots
 | 
						|
  }
 | 
						|
  push
 | 
						|
  p $0b+ peek 16& [if<>0
 | 
						|
    [def ```<DIR> #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 <j++]          {But drop all earlier zeroes}
 | 
						|
      <q++
 | 
						|
      i 1- if>=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! <caddr.        {Then the low address}
 | 
						|
    {caddr \PrintWord!}
 | 
						|
    \LoadByte! 1- 255& 1+ clen= 
 | 
						|
    {\PrintByte! \Newline!}
 | 
						|
    \CopyGt1Segment! 
 | 
						|
    clen if=0                  {Check we did it}          
 | 
						|
    \LoadByte!                 {Go to next block}
 | 
						|
    if<>0loop]
 | 
						|
  \LoadByte! >caddr.           {Load execution address}
 | 
						|
  \LoadByte! <caddr.
 | 
						|
 | 
						|
  \LoadByte! [if>=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, <q++ if<>0 j=            {Compare until nul terminator}
 | 
						|
    i, <i++ j^ if=0loop]        {Loop while equal}
 | 
						|
  ret
 | 
						|
]
 | 
						|
 | 
						|
{ \Execute }
 | 
						|
[def _Execute=*
 | 
						|
  $20 \ResetScreen!             {Restore video table}
 | 
						|
  caddr
 | 
						|
  [do if=0loop]                 {Stop if not executable}
 | 
						|
  call                          {Run...}
 | 
						|
] 
 | 
						|
 | 
						|
{ \IncSector }
 | 
						|
[def _IncSector=*
 | 
						|
  1 SectorL+ SectorL=           {To next sector}
 | 
						|
  [if=0 1 SectorH+ SectorH=]
 | 
						|
  ret
 | 
						|
]
 | 
						|
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|} >_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: <List++ <List++
 | 
						|
    ValueH List: <List++ <List++
 | 
						|
 | 
						|
    { Break at End Of Cluster marker }
 | 
						|
    $f000 ValueH| 1+ [if=0      {Test high word first, ignore bit 28:31}
 | 
						|
    $0007 ValueL| 1+] if<>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, <OffsetL.            {Offset = Cluster >> 8}
 | 
						|
  <ValueH, >OffsetL.
 | 
						|
  >ValueH,  OffsetH=
 | 
						|
 | 
						|
  <ValueL, m=                   {Park the lowest 8 bits in m}
 | 
						|
  128& peek ValueL=             {Value = 1 if bit7 else 0}
 | 
						|
  0         ValueH=
 | 
						|
  \AddOffset! \AddOffset!         {Value += 2 * Offset: now we have Cluster >> 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
 | 
						|
 | 
						|
{-----------------------------------------------------------------------+
 | 
						|
|                                                                       |
 | 
						|
+-----------------------------------------------------------------------}
 |