Latest Altoids artwork

Medium: Altoids tin, Keyspan USA-19HS (modified), PS2 Multitap (modified), 74HCT04 inverting buffer, discrete passive components, solder, wire, epoxy.

Artist’s inspiration: Dorky boyfriend that crafts Final Fantasy Tactics characters with the precision of a meticulous dungeonmaster :)

(Yes, it’s a USB Playstation memory card reader/writer. Firmware and source code for Mac OS and Linux is in Subversion (navi-misc/psxcard). The software doesn’t yet support PS2 cards.)

/*
 * psxcard.c - Firmware for a USB Playstation card reader, implemented
 *             on a TUSB3410 microcontroller (Modified Keyspan USA19HS)
 *
 * Hardware:
 *
 *     All inputs and outputs routed through CMOS inverter. 4.7k
 *     pull-ups on inputs from memory card, 9.2k pull-ups on inputs
 *     from TUSB3410. 330 ohm current limiting resistor in series
 *     with TUSB3410 inputs / inverter outputs.
 *
 *     Memory card and inverter running off the same power supply,
 *     ~3.6v created by stringing a couple diodes off the 5V supply.
 *
 *     Signal mapping: (Note that all signals are inverted)
 *
 *     P3_0 -> CLK
 *     P3_1 <- DAT
 *     DTR  -> CMD
 *     RTS  -> SEL
 *     CTS  <- ACK
 *
 *     NB: Using CTS for ACK seems logical, since we have hardware
 *         change detection on the CTS and DSR pins. It would make
 *         more sense to use P3_2 and P3_3 for the other two signals,
 *         but those two GPIOs are problematic to access on the
 *         Keyspan board.
 *
 * USB Protocol:
 *     See psxcard-proto.h
 *
 * --Micah Dowty <micah@navi.cx>
 */

#include <stdio.h>
#include <string.h>
#include <usb_driver.h>
#include <uart_driver.h>
#include <util.h>
#include "psxcard-proto.h"

#define ARRAYSIZE(a) ((sizeof(a)/sizeof((a)[0])))
#define MIN(a, b)    ((a) < (b) ? (a) : (b))
#define TRUE         1
#define FALSE        0
typedef unsigned char uint8;
typedef unsigned short uint16;

/*
 * Hardware-specific definitions
 */
#define CLK_HIGH() P3_0 = 0
#define CLK_LOW()  P3_0 = 1
#define CMD_HIGH() MCR |= MCR_DTR
#define CMD_LOW()  MCR &= ~MCR_DTR
#define SEL_HIGH() MCR |= MCR_RTS
#define SEL_LOW()  MCR &= ~MCR_RTS
#define DAT_TEST() (!P3_1)
#define ACK_TEST() (MSR & MSR_LCTS)
#define ACK_CHANGE_RESET() MSR = MSR_DCTS
#define ACK_CHANGE_TEST()  (MSR & MSR_DCTS)

/*
 * We give SDCC from 0xF800 to 0xFCFF as general-purpose xdata,
 * the rest is available for buffers. Start at 0xFD00. Note that
 * DMA buffers must be aligned on an 8-byte boundary.
 *
 * XXX: We don't yet double-buffer, but that could increase
 *      speed quite a bit since this is so CPU-intensive.
 */
volatile xdata at 0xFD00 uint8 ep1_out_x[64];
volatile xdata at 0xFD40 uint8 ep1_in_x[64];

PsxcardErrors errors;

xdata struct {
    uint8 read[PSXCARD_FRAME_SIZE];
    uint8 write[PSXCARD_FRAME_SIZE];

    /* Amount of data currently buffered. Up to PSXCARD_FRAME_SIZE. */
    uint8 read_offset;
    uint8 write_size;

    /* Frame number of data currently in the buffer. */
    uint16 read_frame;
    uint16 write_frame;

    /* Nonzero if a read/write is currently in progress. */
    uint8 read_active;
    uint8 write_active;
} buffers;


/********************************************************************/
/************************************************* PSX Card Slot ****/
/********************************************************************/

void cardslot_init()
{
    /* Tristate all GPIOs */
    P3_0 = 1;
    P3_1 = 1;
    P3_2 = 1;
    P3_3 = 1;

    /* Outputs to their default states */
    CLK_HIGH();
    CMD_HIGH();
    SEL_HIGH();
}

void cardslot_begin()
{
    SEL_LOW();
    ACK_CHANGE_RESET();
}

void cardslot_end()
{
    SEL_HIGH();
}


/*
 * The Playstation bus is full-duplex, and we're always reading and
 * writing data simultaneously.  Often only the read data or only the
 * written data are significant, but sometimes both are. This function
 * writes and reads a single byte. Does not wait for ACK.
 *
 * For speed, this loop has been unrolled. The typical clock rate for
 * PSX cards is 250 KHz. This microcontroller is slow enough that we
 * shouldn't have to worry about exceeding that overall rate, but we
 * should be careful that the width of the clk low pulse isn't too
 * short. That's where we choose to reset the watchdog timer.
 */
uint8 cardslot_txrx_byte(uint8 outByte)
{
    uint8 inByte = 0;

#define TXRX_BIT(mask) \
    if (outByte & mask) { \
        CMD_HIGH(); \
    } else { \
        CMD_LOW(); \
    } \
    CLK_LOW(); \
    watchdog_reset(); \
    CLK_HIGH(); \
    if (DAT_TEST()) { \
       inByte |= mask; \
    }

    TXRX_BIT(0x01)
    TXRX_BIT(0x02)
    TXRX_BIT(0x04)
    TXRX_BIT(0x08)
    TXRX_BIT(0x10)
    TXRX_BIT(0x20)
    TXRX_BIT(0x40)
    TXRX_BIT(0x80)

#undef TXRX_BIT
    return inByte;
}

/*
 * Wait for an ACK. Returns TRUE if an ACK was received, FALSE if not.
 * This operates using the UART's change-detection circuitry.
 */
uint8 cardslot_wait_for_ack(void)
{
    uint16 i;
    for (i=0; i<1000; i++) {
	if (ACK_CHANGE_TEST()) {
	    ACK_CHANGE_RESET();
	    return TRUE;
	}
    }
    return FALSE;
}

/*
 * Read one frame from a Playstation memory card. Returns TRUE
 * if the read was successful, FALSE on any error.
 */
uint8 cardslot_read_frame(uint16 frame, xdata uint8 *buffer)
{
    uint8 i;
    uint8 frameHigh = frame >> 8;
    uint8 frameLow = (uint8) frame;
    uint8 checkbyte = frameHigh ^ frameLow;

    cardslot_begin();

    /* Select the memory card, slot 1 */
    cardslot_txrx_byte(0x81);
    if (!cardslot_wait_for_ack()) goto error;

    /* Memory card read */
    cardslot_txrx_byte(0x52);
    if (!cardslot_wait_for_ack()) goto error;

    /* Some magic 2-byte response... */
    if (cardslot_txrx_byte(0x00) != 0x5a) goto error;
    if (!cardslot_wait_for_ack()) goto error;
    if (cardslot_txrx_byte(0x00) != 0x5d) goto error;
    if (!cardslot_wait_for_ack()) goto error;

    /* Send address */
    cardslot_txrx_byte(frameHigh);
    if (!cardslot_wait_for_ack()) goto error;
    cardslot_txrx_byte(frameLow);
    if (!cardslot_wait_for_ack()) goto error;

    /* Magic 1-byte acknowledgment */
    if (cardslot_txrx_byte(0x00) != 0x5c) goto error;
    if (!cardslot_wait_for_ack()) goto error;
    
    /* Wait for 'start of data' byte */
    i = 100;
    while (cardslot_txrx_byte(0x00) != 0x5d) {
	if (!cardslot_wait_for_ack()) goto error;
	if (!--i) goto error;
    }
    if (!cardslot_wait_for_ack()) goto error;

    /* Read a confirmation copy of the frame address */
    if (cardslot_txrx_byte(0x00) != frameHigh) goto error;
    if (!cardslot_wait_for_ack()) goto error;
    if (cardslot_txrx_byte(0x00) != frameLow) goto error;
    if (!cardslot_wait_for_ack()) goto error;

    /*
     * Finally read the data itself. Along the way,
     * update our copy of the XOR check byte.
     */
    for (i=0; i<PSXCARD_FRAME_SIZE; i++) {
	checkbyte ^= buffer[i] = cardslot_txrx_byte(0x00);
	if (!cardslot_wait_for_ack()) goto error;
    }

    /* Read and verify the check byte */
    if (cardslot_txrx_byte(0x00) != checkbyte) goto error;
    if (!cardslot_wait_for_ack()) goto error;
    
    /* The last byte is an end-of-data flag, and isn't ACK'ed */
    if (cardslot_txrx_byte(0x00) != 0x47) goto error;

    cardslot_end();
    return TRUE;

 error:
    cardslot_end();
    return FALSE;
}


/*
 * Write one frame to a Playstation memory card. Returns TRUE if the
 * write was successful, FALSE on any error. Does not verify the
 * write- that should be the host software's job, so as to also
 * protect against any USB or firmware errors.
 */
uint8 cardslot_write_frame(uint16 frame, xdata uint8 *buffer)
{
    uint8 i;
    uint8 frameHigh = frame >> 8;
    uint8 frameLow = (uint8) frame;
    uint8 checkbyte = frameHigh ^ frameLow;

    cardslot_begin();

    /* Select the memory card, slot 1 */
    cardslot_txrx_byte(0x81);
    if (!cardslot_wait_for_ack()) goto error;

    /* Memory card write */
    cardslot_txrx_byte(0x57);
    if (!cardslot_wait_for_ack()) goto error;

    /* Some magic 2-byte response... */
    if (cardslot_txrx_byte(0x00) != 0x5a) goto error;
    if (!cardslot_wait_for_ack()) goto error;
    if (cardslot_txrx_byte(0x00) != 0x5d) goto error;
    if (!cardslot_wait_for_ack()) goto error;

    /* Send address */
    cardslot_txrx_byte(frameHigh);
    if (!cardslot_wait_for_ack()) goto error;
    cardslot_txrx_byte(frameLow);
    if (!cardslot_wait_for_ack()) goto error;

    /*
     * Send data, updating our copy of the XOR byte while we're here.
     */
    for (i=0; i<PSXCARD_FRAME_SIZE; i++) {
	uint8 byte = buffer[i];
	cardslot_txrx_byte(byte);
	checkbyte ^= byte;
	if (!cardslot_wait_for_ack()) goto error;
    }

    /* Send the XOR check byte */
    cardslot_txrx_byte(checkbyte);
    if (!cardslot_wait_for_ack()) goto error;
    
    /*
     * Receive a few magic end-of-something bytes..  The last of these
     * is the same end-of-data mark used for card reads, and it isn't
     * ACK'ed.
     */
    if (cardslot_txrx_byte(0x00) != 0x5c) goto error;
    if (!cardslot_wait_for_ack()) goto error;
    if (cardslot_txrx_byte(0x00) != 0x5d) goto error;
    if (!cardslot_wait_for_ack()) goto error;
    if (cardslot_txrx_byte(0x00) != 0x47) goto error;

    cardslot_end();
    return TRUE;

 error:
    cardslot_end();
    return FALSE;
}


/********************************************************************/
/************************************************* USB Interface ****/
/********************************************************************/

void usbreq_count_error(uint8 *ptr)
{
    if (*ptr != 0xFF) {
	(*ptr)++;
    }
}   

/* Reset the state associated with all USB read/write requests */
void usbreq_init()
{
    memset(&errors, 0, sizeof errors);
    memset(&buffers, 0, sizeof buffers);
}

/*
 * Begin processing a read on the specified frame. This immediately
 * loads this frame from the card, and updates error counters as
 * necessary.
 */
void usbreq_read(uint16 frame)
{
    uint8 retries = 10;
    uint8 blockSize;

    /*
     * If the frame is now out of range, end the read
     * without triggering an error.
     */
    if (frame >= 1024) {
	buffers.read_active = 0;
	return;
    }

    buffers.read_frame = frame;

    while (!cardslot_read_frame(frame, buffers.read)) {
	usbreq_count_error(&errors.soft);
	if (!--retries) {
	    usbreq_count_error(&errors.hard);
	    buffers.read_active = 0;
	    return;
	}
    }
    
    blockSize = MIN(sizeof ep1_in_x, PSXCARD_FRAME_SIZE);
    memcpy(ep1_in_x, buffers.read, blockSize);
    usb_dma_unstall(EDB_IEP1);
    usb_dma_setup(EDB_IEP1, ep1_in_x, blockSize);

    buffers.read_offset = blockSize;
    buffers.read_active = 1;
}


/*
 * Prepare for the next sequential block in a read
 */
void usbreq_read_next(void)
{
    uint8 blockSize;

    if (buffers.read_offset == PSXCARD_FRAME_SIZE) {
	usbreq_read(buffers.read_frame + 1);
	return;
    }

    blockSize = MIN(sizeof ep1_in_x, PSXCARD_FRAME_SIZE - buffers.read_offset);
    memcpy(ep1_in_x, buffers.read + buffers.read_offset, blockSize);
    usb_dma_setup(EDB_IEP1, ep1_in_x, blockSize);
    buffers.read_offset += blockSize;
}

void usbreq_write(uint16 frame)
{
    /*
     * If the frame is now out of range, end the write
     * without triggering an error.
     */
    if (frame >= 1024) {
	buffers.write_active = 0;
	return;
    }

    /* Prepare to receive the first writable frame */
    buffers.write_frame = frame;
    buffers.write_size = 0;
    buffers.write_active = 1;

    usb_dma_unstall(EDB_OEP1);
    usb_dma_setup(EDB_OEP1, ep1_out_x, sizeof ep1_out_x);
}

/*
 * We just received a write packet from the host.  Store it, request
 * the next packet, and if we have a complete frame start writing that
 * to the card.
 */
void usbreq_write_next(uint8 packetLen)
{
    /* Write this packet */
    packetLen = MIN(packetLen, PSXCARD_FRAME_SIZE - buffers.write_size);
    memcpy(buffers.write + buffers.write_size, ep1_out_x, packetLen);
    buffers.write_size += packetLen;

    /* Is this buffer full yet? */
    if (buffers.write_size == PSXCARD_FRAME_SIZE) {
	uint8 retries = 10;

	while (!cardslot_write_frame(buffers.write_frame, buffers.write)) {
	    usbreq_count_error(&errors.soft);
	    if (!--retries) {
		usbreq_count_error(&errors.hard);
		buffers.write_active = 0;
		return;
	    }
	}

	/* Our buffer is now empty. Start the next frame.. */
	usbreq_write(buffers.write_frame + 1);
	return;
    }

    /* Request the rest of this frame */
    usb_dma_setup(EDB_OEP1, ep1_out_x,
		  MIN(sizeof ep1_out_x, PSXCARD_FRAME_SIZE - buffers.write_size));
}

void main()
{
  uart_init();
  puts("\n---- Startup ----");
  cardslot_init();
  puts("Card slot initialized");
  usb_init();
  puts("USB initialized");

  /* Simulate an initial INIT request, to put us in a known state */
  usbreq_init();

  while (1) {
    watchdog_reset();
    usb_poll();

    if (buffers.read_active && usb_dma_status(EDB_IEP1)) {
	usbreq_read_next();
    }

    if (buffers.write_active) {
	int bytes = usb_dma_status(EDB_OEP1);
	if (bytes) {
	    usbreq_write_next(bytes);
	}
    }
  }
}


void usb_handle_vendor_request() {
  switch (usb_setup_buffer.bRequest) {

  case PSXCARD_REQ_INIT:
      usbreq_init();
      usb_write_ack();
      break;

  case PSXCARD_REQ_ERR_CHECK:
      usb_write_ep0_buffer(&errors, sizeof errors);
      memset(&errors, 0, sizeof errors);
      break;

  case PSXCARD_REQ_READ:
      usbreq_read(usb_setup_buffer.wValue);
      usb_write_ack();
      break;

  case PSXCARD_REQ_WRITE:
      usbreq_write(usb_setup_buffer.wValue);
      usb_write_ack();
      break;

  }
}

/* The End */