SD-card cartridge#

Introduction#

Perhaps the most elegant solution for loading CAS files into your P2000T is via a cartridge that hosts an SD-card slot. Even the smallest SD-cards have enough capacity to store the complete P2000T tape archive. Reading from such an SD-card is non-trivial and requires both a hardware solution to interface with the I/O port of the P2000T as well as a software solution to navigate through the files hosted on a FAT32 partition. This page explains both parts in more detail.

Note

The SD-card cartridge uses I/O port 0x60-0x6F and requires a P2000T which has this port available. There appear to be a number of hardware devices which also use this port and these devices will be mutually incompatible with the SD-card cartridge. For this reason, the newest variant of the SD-card (rev6 and newer) uses I/O port 0x40-0x4F.

Tip

This page contains a lot of technical details which can in principle be ignored by the non-interested reader. To get started working with the SD-card cartridge, the following sections are the most relevant:

There is also a manual available in Dutch which can be found via this link.

For a review on the SD-card cartridge, have a look at the video below:

Preparation#

The SD-card cartridge works very similar as compared to the data cartridge. A modified BASICNL cartridge is prepared that loads a LAUNCHER program from the ROM chip on the SD-card cartridge into memory. Next, this launcher program is executed which interfaces with the SD-card. To set-up this infrastructure, one needs to prepare a SLOT1 cartridge with the modified BASICNL rom and one needs to flash the launcher program on the ROM chip of the SD-card cartridge.

Note

If you have purchased an SD-card cartridge (kit), your cartridge set is most likely ready to use and this section is only relevant if you want to update the firmware.

Caution

The data cartridge also uses a modified BASICNL ROM, however this modified BASICNL ROM is a different one and the two types are not interchangeable.

Summarizing, the following is required to start working with the SD-card cartridge.

  • A SLOT1 cartridge with the modified BASICNL application.

  • A SLOT1 cartridge with the flasher application.

  • The SD-card cartridge with a FAT32-formatted SD-card and the launcher application flashed to its internal ROM.

Creating the SLOT1 cartridges#

First, we need to construct two SLOT1 cartridges, being the modified BASIC cartridge and the flasher. The BIN files for these two cartridges can be obtained via the links below.

Caution

Verify whether you have a 0x40 or a 0x60 version of the cartridge. The 0x40 cartridges are the newest and these cartridges have 0x40 written on the case.

Latest variant 0x40

Deprecated variant 0x60

Upload these two BIN files to different ROM slots on the ZIF cartridge. A detailed guide on the procedure can be found on the page describing the ZIF cartridge.

Tip

Make a note which slot you have used for which cartridge file.

Flashing the launcher onto the ROM chip#

Next, we need to prepare an SD-card and place the LAUNCHER.BIN on this SD-card in its root directory.

Important

  • If the SD-card is not yet formatted, format the SD-card using a FAT32 filesystem.

  • The SD-card cartridge only works with FAT32 formatted SD-cards. When formatting the SD-card, it is highly recommended to use default settings for the cluster size.

Place the LAUNCHER.BIN file in the root directory of the SD-card. The latest version of the LAUNCHER.BIN file can be downloaded via the link below.

Caution

  • Verify whether you have a 0x40 or a 0x60 version of the cartridge. The 0x40 cartridges are the newest and these cartridges have 0x40 written on the case.

  • After downloading the file, do not forget to change the name to LAUNCHER.BIN, otherwise the flasher program will not recognize (find) the file.

Latest variant 0x40

Deprecated variant 0x60

Insert the SLOT1 cartridge with the flasher and the SD-card cartridge in SLOT2 and turn on your P2000T. Hit any key to start interfacing with the SD-card and search for a file called LAUNCHER.BIN. If such a file is found, the file will be copied to the internal ROM chip of the SD-card cartridge. Any previous data corresponding to previous versions of the launcher will be automatically wiped. After copying the file to the ROM chip, a CRC-16 checksum is automatically generated and used for verification purposes.

a a
b b
c c
d d

Fig. 5 (a) Opening the SD-card Flasher utility; press any key to start. (b) Program checks whether a file called LAUNCHER.ROM is found. (c) LAUNCHER.ROM file is copied to the internal ROM chip. (d) CRC-16 checksum is being generated for verification purposes. ** Note that these images correspond to an earlier version of the Flasher, but the procedure remains very similar across different verisons of the Flasher utility.**#

Launching CAS programs from the SD-card cartridge#

Programs can be loaded from the SD-card and executed using the launcher. To load the launcher into memory, the P2000T requires a modified BASICNL cartridge (see the instructions under preparation on how to prepare such a cartridge) in SLOT1. This modified BASICNL cartridge will copy the launcher from the internal ROM chip on the SD-card cartridge to memory and run it.

Loading a program from the SD-card is fairly easy. There are only three commands one needs to be know: ls, cd and run.

  • cd is used to navigate to (sub)directories.

  • ls is used to show a listing.

  • run is used to start a program.

Per directory, all items inside the directory have a number (called an index) which starts at 1. On execution of a ls command, these indices are shown. To change directory, type cd followed by the index number. For example, cd 4 will go to the fourth entry. If that entry is a subdirectory, the launcher jumps to that folder. To run a program, one types run followed by the number. For example, run 107 will run the 107th entry in the folder. In the example shown in Figure 6, this corresponds to the Tetris program.

Note

  • Users familiar with Linux or MS-DOS operating systems are used to writing the actual filename or directory name when using commands such as cd and run. A different approach using indices was intentionally chosen to save upon typing but also to save upon more complex (and large in size) routines in the launcher application.

  • The images as shown below might differ with respect to the newest version, the procedure however remains the same.

a a
b b
c c
d d

Fig. 6 (a) Opening the SD-card launcher. (b) The user went to subdirectory item 4 and runs ls to get a listing of the subdirectory contents. (c) The user runs item 107 (Tetris) and the program is transferred from the SD-card to the external memory chip. (d) All sectors have been transferred and the program is ready to be executed or alternatively, its CRC-16 checksum can be validated (after which the program can still be executed)#

Tip

Instead of ls, one can also run lscas. The difference is that the latter command will also read the headers of the .CAS files and mention the name of the tape and the number of blocks.

Once the CAS file is loaded into memory and executed, one will not return back to the launcher once the BASIC program is terminated. To restart the launcher, simply reset the machine.

Running PRG files from the SD-card#

Besides running CAS files, it is also possible to execute special .PRG files. These files are loaded from the SD-card and placed into memory at position 0xA000-0xDCFF.

Important

.PRG files will only work if one has at least a 16 KiB memory expansion.

In contrast to CAS files, upon termination of these programs you will return to the launcher and one can open another .PRG file or a CAS file. Launching a .PRG file is completely synonymous to launching a CAS file, simply use run <index> where <index> refers to the index of a .PRG file. The launcher will automatically verify the integrity of the .PRG file by calculating its checksum. Only upon a valid checksum will the program start.

Currently, there are not many .PRG programs made, but one program that is potentially useful and relevant is CASDUMP.PRG. This program allows the user to copy files from the cassette tape to the SD-card. See this section <#creating-copies-of-cassettes-tapes>_ for more information.

Copying cassette data to the SD-card#

To copy the contents of a cassette tape to the SD card, first download the program CASDUMP.PRG via the link as shown below and place it on the SD-card.

Next, ensure a directory called DUMPS exists on the SD-card and is located in the ROOT directory of the SD-card. You will need to create this directory on your computer or laptop. Be sure it says DUMPS without any trailing spaces and preferentially all in capitals.

Insert the SD-card into the SD-card cartridge and execute the launcher application. Navigate to the folder containing CASDUMP.PRG and run it via run <index> where <index> refers to the index of CASDUMP.PRG.

Upon execution, the Cassette Dump program will verify whether it can find a folder called DUMPS and if so, the cassette dumping routines can start. A number of screenshots showcasing the process are provided in Figure 7. At the very start, the program will ask the user whether they want to perform cassette file extraction using automatic or manual mode. In the automatic mode, the program aims to read the whole cartridge and copy over any program it can find. In manual mode, for every file encountered, the user will be asked whether they want to store the file. In Figure 7, the process corresponding to manual mode is shown.

a a
b b
c c
d d

Fig. 7 (a) Opening the Cassette Dump program. The user is asked to enter the operation mode. (b) The cassette is automatically rewinded and the first program is encountered. The user is asked whether they want to copy the program to the SD-card. (c) The program has copied all 5 blocks corresponding to the file to the SD-card. (d) The program continues looking for the next file and has encountered one. The user is asked (again) whether they want to store this file.#

A CAS file has a 16 character description whereas the SD-card uses the old MS-DOS 8.3-style notation. As such, Cassette Dump will take the first 8 characters of the 16-character description and checks the SD-card whether a such a file already exists. If not, the cassette data is written to this file. If it does exists, the program tries to rename the file by replacing the last character by a 0. If this file also exists, it will replace the last character by a 1, and so forth, until all options have been tried. Any invalid characters, e.g. a question mark ? will be automatically replaced by valid 8.3-style characters.

Tip

After copying a number of files over from cassette to the SD-card, it is highly recommended to rename your files and reorganize them in a different set of folders.

Cartridge commands#

Below, an overview of the commands is provided.

Command

Description

ls

List contents of current folder

lscas

List contents of current folder, listing contents of CAS files

cd <number>

Change directory

run <number>

Run .CAS file

hexdump <number>

Performs a 120-byte hexdump of a file

fileinfo <number>

Provides location details of a file

ledtest

Performs a quick test on the read/write LEDs

stack

Show current position of the stack pointer

dump<XXXX>

Perform a 120-byte hexdump of main memory starting at 0xXXXX

romdump<XXXX>

Perform a 120-byte hexdump of cartridge ROM starting at 0xXXXX

ramdump<XXXX>

Perform a 120-byte hexdump of cartridge RAM starting at 0xXXXX

Technical details#

Cartridge features#

In Figure 8, the schematic of a SLOT2 SD-card interface is provided. This schematic is fairly complex and we will break it down into several independent components.

../_images/port2-sdcard-interface.svg

Fig. 8 SD card cartridge schematic. Click here to download the schematic.#

Parallel to serial conversion#

Communication with SD-cards proceeds via the SPI protocol. The SPI-protocol is inherently a serial protocol, yet the Z80 uses a parallel interface. One way for the Z80 processor to communicate with the SD card is via a process called bit banging. This is rather inefficient as we basically use only 1 out of the 8 bits every time the Z80 executes an instruction. More efficient would be to use the full 8 bit databus every time an instruction is executed. To accommodate this, we make use of one parallel-in-serial-out (PISO) and one serial-in-parallel-out (SIPO) shift register (74HC165 and 74HC595, respectively).

When communicating with the SD-card over the serial connection, we need to be able to transfer or receive 8 bits of information in the same time it would normally take to complete one instruction on the Z80. Thus, the parallel to serial interface needs to operate at a much higher clock frequency than the Z80. In this implementation, we make use of a timer circuit driven by a 16 MHz oscillator (which frequency is halved via a 74HC163 counter chip) to drive both the shift registers as well as the CLOCK signal of the SPI interface. Herein, the shift registers are fed the inverted CLOCK signal which is delayed by half a clock cycle with respect to the regular CLOCK signal. The SD-card receives the regular CLOCK signal, ensuring that the SD-card is half a clock cycle ahead of the shift registers.

The SPI protocol is a synchronous protocol, so essentially bits of data are exchanged between the host device (master) and the SD-card (slave). In our implementation, this becomes effectively 8 bits or 1 byte at a time. To transfer a byte from the Z80 to the SD-card, first the byte is transferred to the PISO register after which a timer circuit is started that exactly emits 8 clock pulses, transferring the byte from the PISO register to the SD-card. Simultaneously, the SIPO register receives 8 bits from the SD-card and stores them. The Z80 can then retrieve the byte of data by reading from the SIPO register. This synchronous protocol implies that when the Z80 only needs to receive data from the SD-card, e.g. when it receives a block of data, it still needs to keep sending new bits of data to the SD-card. The elegancy of this design is that the Z80 can keep on doing stuff (e.g. loading the next instruction) while the SD-card circuitry is running in parallel.

Note

The implementation used in the schematic above is based on this source. It should be mentioned that a 74HC123 multivibrator has been added to add a little delay to avoid ‘double-triggering’ of the timer circuit.

I/O selection#

The SD-card cartridge hosts a number of devices and chips which all require their own I/O address for efficient communication. By means of two 3-to-8 line decoders (74HC138), the lower byte of the I/O address is used to interface with at most 16 devices. Note that only 11 of the 16 lines are being used. All are wired to I/O ports 0x60-0x6F and the upper byte is being checked by means of a 74HC85 chip. An overview of all the port numbers is provided below.

Port number

Function

R/W

0x60

Serial read/write

R/W

0x61

Start clock circuit

W

0x62

Set MISO pull-up/down

R/W

0x63

Set/unset CS on SD-card

R/W

0x64

LEDs

R/W

0x65

not used

N/A

0x66

not used

N/A

0x67

not used

N/A

0x68

Lower address byte

W

0x69

Upper address byte

W

0x6A

Bank register ROM

W

0x6B

Bank register RAM

W

0x6C

ROM chip

R/W

0x6D

RAM chip

R/W

0x6E

not used

N/A

0x6F

not used

N/A

Interfacing with the devices proceeds via the IN and OUT instructions. For example, to send 0xFF to the SD-card and read back the resulting byte, one would use:

ld a,0xFF
out (0x60),a     ; set all ones
out (0x61),a     ; start timer circuit, value in register a is not used
in  a,(0x61)     ; receive result from SD-card

RAM and ROM chips#

The SD-card cartridge uses a SST39SF010 and a 62128 as the ROM and RAM chips, respectively. Both these chips have a capacity of 128kb. The ROM chip is used to store the launcher (more information on this later) and the RAM chip is used for intermittent storage of the CAS programs. As the I/O interface of the Z80 only allows us to use 8 bits at a time, the addresses for these chips are stored in two 8-bit registers (74HC273) and one 2-bit register (74HC74). The former two chips store the lower and upper byte of the address and the latter chip is used for banking. Each bank thus corresponds to 64kb of memory. Note that both the ROM and RAM chip share the 16-bit address registers which in practice does not result in any difficulties.

Reading from the ROM and RAM chips proceeds in a fairly equivalent fashion. The bank is set by writing a 0 or a 1 to address 0x6A for the ROM chip and 0x6B for the RAM chip. Next, the 16-bit address is set by writing the lower byte to 0x68 and the upper byte to 0x69. After setting the address, one can read from either 0x6C for the ROM chip or from 0x6D for the RAM chip. For example, reading a byte at address 0xAA55 from the ROM chip would look as follows.

ld a,0x00
out (0x6A), a    ; set first bank (bank 0)
ld a,0x55
out (0x68), a    ; set address lower byte (0x55)
ld a,0xAA
out (0x69), a    ; set address upper byte (0xAA)
in  a,(0x6C)     ; read from chip address 0xAA55

Writing to the RAM chip is fairly easy and only requires an OUT instead of an IN instruction. An example where the value 0x00 is written to address 0xAA55 is provided below.

ld a,0x00
out (0x6B), a    ; set first bank
ld a,0x55
out (0x68), a    ; set address lower byte (0x55)
ld a,0xAA
out (0x69), a    ; set address upper byte (0xAA)
ld a,0x00
out (0x6D), a    ; store byte in A into RAM chip at 0xAA55

Writing to the ROM chip is a bit more involved and requires a sequence of instructions. More details on this sequence can be found in the datasheet of the SST39SF0x0 family of chips.

SD-card#

Using the hardware set-up as explained above, we can interface with our SD-card. Communication with an SD-card proceeds via sending a series of command words to the SD-card upon which the SD-card responds. These command words are sent via the I/O port.

The exact procedure is provided in the (official) document “Physical Layer Simplified” which can be found via this page. Referring to Figure 4-2 in this document, the process of initializing an SD-card such that we can read blocks from it goes as follows.

  1. Set CS to low on the SD-card

  2. Send at least 74 pulses of 1 to the SD-card

  3. Send CMD0 to the SD-card ({0|0x40,0,0,0,0,0x94|0x01}) to put the card in SPI mode and check for a valid R1 response.

  4. Send CMD8 to the SD-card ({8|0x40,0,0,0x01,0xaa,0x86|0x01}) to send the interface condition (argument 0x1AA) and check for a valid R7 response.

  5. Send CMD55 to the SD-card ({55|0x40,0,0,0,0,0x00|0x01}) for an application specific command and check for a valid R1 response.

  6. Send ACMD41 to the SD-card ({41|0x40,0x40,0x00,0x00,0x00,0x00|0x01}) to send the host capacity support information (argument 0x40000000) and check for a valid R1 response.

  7. Keep on repeating steps 5 and 6 until a response of 0x00 is being received. This typically takes roughly 2-3 calls.

  8. Send CMD58 to the SD-card ({58|0x40,0,0,0,0,0x00|0x01}) to read the OCR register and read the R3 response.

  9. The card is now ready to receive CMD17 commands which request 512 byte blocks from the card.

Tip

The whole procedure is quite involved and I can warmly recommend to read upon this excellent guide when one is interested to implement such an interface yourself.

FAT32 file system#

The SD-cards used for this cartridge utilizes the FAT32 protocol for file storage. To navigate through the folders and access the files, we need to understand how this storage protocol works. Luckily, FAT32 is relatively simple and well-documented. We will not provide a detailed explanation of the storage format (see also the tip below if one is interested), yet suffice with a brief overview of how files are retrieved from a FAT32 filesystem.

  1. The first 512 bytes of the SD-card, i.e. the master boot record (MBR), is loaded. The MBR contains information on the partitions.

  2. From the MBR, the logical block address (LBA) of the first partition is read.

  3. From the partition table, the number of bytes per sector, the sectors per cluster, the reserved sectors, the number of FATs, the sectors per FAT, and the LBA of the root directory are read and stored for later use.

  4. Once the LBA of the root directory is established, we can start reading the metadata that describes the files and subdirectories that reside in the root directory. For files, these provide links to the actual file data. For subdirectories, these provide a reference to where the metadata for the content that is stored in that subdirectory.

  5. Based on the file metadata, a sequential list of clusters can be constructed which contain the actual file data. These clusters are traversed and all the sectors for each of these clusters is being read and transferred to the P2000T.

Tip

PCB lay-out#

Below, an image of the PCB (backside, unpopulated and populated) is shown. As can be readily seen, there is not much free space left on this PCB. At the center, one finds the SST39SF010 chip seated in a PLCC32 socket while directly above the 128kb RAM chip is found. On the right top, the 16 MHz oscillator is located. On the left top, a SD-card interface board is mounted. This board does the level shifting between the 5V lines on the PCB and the 3.3V lines on the SD-card. Directly to the right of the PLCC32 socket, the 2x74HC273 buffer chips are found which store the address bus for both the ROM and RAM chips.

PCB

References#