Cartridges

SD-card cartridge

A SLOT2 cartridge that lets the P2000T launch cassette programs and preserve tapes through modern SD cards.

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.

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.

Summarizing, check that you have the following ready

  • 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.

Downloads

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.

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.

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.

Downloads

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) Opening the SD-card Flasher utility; press any key to start.
b
(b) Program checks whether a file called LAUNCHER.ROM is found.
c
(c) LAUNCHER.ROM file is copied to the internal ROM chip.
d
(d) CRC-16 checksum is being generated for verification purposes.

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 2, this corresponds to the Tetris program.

a
(a) Opening the SD-card launcher.
b
(b) The user went to subdirectory item 4 and runs ls to get a listing of the subdirectory contents.
c
(c) The user runs item 107 (Tetris) and the program is transferred from the SD-card to the external memory chip.
d
(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)

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.

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 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 3. 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 3, the process corresponding to manual mode is shown.

a
(a) Opening the Cassette Dump program. The user is asked to enter the operation mode.
b
(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
(c) The program has copied all 5 blocks corresponding to the file to the SD-card.
d
(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.

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 4, the schematic of a SLOT2 SD-card interface is shown. The schematic is fairly dense, so we will break it down into a few separate building blocks.

SD card cartridge schematic. <a href="/img/sdcartridge/port2-sdcard-interface.svg">Click here</a> to download the schematic.
Figure 4: 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 serial by nature, while the Z80 exposes a parallel data bus. One way to bridge that difference is bit banging, where the Z80 toggles the serial lines in software. That works, but it is rather inefficient: for each instruction we effectively use only 1 of the 8 available data bits. A nicer approach is to use the full 8-bit data bus whenever the Z80 performs an I/O operation. To do that, the cartridge uses 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 in roughly the time it would normally take the Z80 to complete one I/O instruction. The parallel-to-serial interface therefore has to run at a higher clock frequency than the Z80 itself. In this implementation, a timer circuit is driven by a 16 MHz oscillator. That frequency is divided by two using a 74HC163 counter, and the resulting clock drives both the shift registers and the SPI clock signal. The shift registers receive the inverted clock, which is delayed by half a clock cycle compared with the regular clock. The SD-card receives the regular clock, so the 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) at the same time. In this design, that exchange happens one byte at a time. To send a byte from the Z80 to the SD-card, the byte is first loaded into the PISO register. The timer circuit is then started and emits exactly 8 clock pulses, shifting the byte out to the SD-card. At the same time, the SIPO register receives 8 bits from the SD-card and stores them. The Z80 can then retrieve the received byte by reading from the SIPO register.

This also means that the Z80 must keep sending data even when it only wants to receive data from the SD-card, for example while reading a 512-byte block. The nice part of this design is that the Z80 does not need to bit-bang every clock edge itself. It can continue with other work, such as fetching the next instruction, while the SD-card circuitry performs the serial transfer in parallel.

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. Using two 3-to-8 line decoders (74HC138), the lower byte of the I/O address can select up to 16 devices. In practice, only 11 of these 16 lines are used. They are mapped to I/O ports 0x60-0x6F, while the upper byte is checked by a 74HC85 comparator. The complete port map is listed 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

The Z80 talks to these devices using the IN and OUT instructions. For example, to send 0xFF to the SD-card and read back the resulting byte, we could 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 stores the launcher, while the RAM chip is used as temporary storage for CAS programs. Because the Z80 I/O interface only gives us 8 bits at a time, the address for these memory chips is stored in two 8-bit registers (74HC273) and one 2-bit register (74HC74). The two 8-bit registers store the lower and upper address byte, and the 2-bit register is used for bank selection. Each bank therefore corresponds to 64kb of memory. Both the ROM and RAM chip share the same 16-bit address registers, which in practice does not cause any trouble.

Reading from the ROM and RAM chips works in much the same way. First, the bank is selected by writing 0 or 1 to 0x6A for the ROM chip, or to 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. Once the address is set, the Z80 can read from 0x6C for the ROM chip or from 0x6D for the RAM chip. For example, reading a byte at address 0xAA55 from the ROM chip looks like this.

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 equally straightforward: use an OUT instruction instead of an IN instruction. The example below writes 0x00 to address 0xAA55.

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 about that sequence can be found in the datasheet of the SST39SF0x0 family of chips.

SD-card

With the hardware described above, the Z80 can now communicate with the SD-card. Communication is command based: the host sends a command packet to the card, and the card returns a response. These command packets are sent through the I/O port.

The exact procedure is described in the official document “Physical Layer Simplified” which can be found via this page. Following Figure 4-2 in that document, the initialization sequence needed before reading blocks is roughly 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. Repeat steps 5 and 6 until a response of 0x00 is received. This typically takes about 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.

FAT32 file system

The SD-cards used with this cartridge are formatted as FAT32. To navigate folders and access files, the launcher needs to understand the basic structure of that filesystem. Luckily, FAT32 is relatively simple and well documented. We will not go into every detail of the storage format here; the goal is simply to give a brief overview of how files are found and retrieved.

  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 known, the launcher can read the metadata for the files and subdirectories in that directory. File entries point to the actual file data. Subdirectory entries point to the metadata for the contents of that subdirectory.
  5. Based on the file metadata, a sequential list of clusters can be constructed that contains the actual file data. These clusters are then traversed, and all sectors belonging to those clusters are read and transferred to the P2000T.

PCB lay-out

An image of the PCB is shown below. As you can see, there is not much free space left on this board. The SST39SF010 chip sits in the PLCC32 socket near the center, with the 128kb RAM chip directly above it. The 16 MHz oscillator is located in the upper-right area. In the upper-left area, an SD-card interface board is mounted. This small board performs the level shifting between the 5V signals on the cartridge PCB and the 3.3V signals used by the SD-card. Directly to the right of the PLCC32 socket, the 2x74HC273 buffer chips store the address bus for both the ROM and RAM chips.

PCB

References