Showing posts with label SPI. Show all posts
Showing posts with label SPI. Show all posts

Sunday, 13 October 2013

Building a Board

Building a Board

The initial prototype was hand-wired on a piece of prototyping board. The next version was built on a Adafruit Prototyping Pi Plate and wired up with jumpers. I chose jumpers because I wanted the flexibility to use the same board as a floppy drive controller like the Catweasel project eventually. I will describe the Adafruit board as it's likely to be easier to build than the hand-wired version.

Step 1

Drive Cable Connector
Remove (or if you buy as a kit, don't install) the four pin connector at the end of the board beside the adafruit logo. and install a 34-pin male connector. e.g. Digikey OR903-ND or equivalent. The original TRS-80 had card edge connectors on the drive cable but a pin edge connector is much more convenient for connecting to the Adafruit plate. The slot at the is used to orient the ribbon cable, but since there is only space for a ribbon exiting upwards it is almost impossible to plug in the ribbon cable incorrectly.




I chose to solder the connector to the board. I don't think there would be room underneath the plate to wire wrap this connector. I put a single row female connector beside the header as I wanted to use jumpers. You may want to hard-wire the board if you want a permanent solution.

Cable Connector Soldering


The row of pins nearest the end of the plate contains only ground signals so every pin on that row is connected together and then attached to the nearby ground pin. The other row is connected to the female header on my board but you could choose to solder it directly to the 74LS06 pins instead.







Step 2

The project only requires two 74LS06 ICs and fortunately there is just enough room for two 14-pin IC sockets on the plate. I have put pin 1 of the IC sockets at the end of the board furthest away from the cable socket. The sockets must be positioned so that the pins on the left and right go into the rows of interconnected holes on the right and left sides of the prototype area. As there is a bit more clearance underneath it might be possible to use wire-wrap sockets, but check first before trying this. I chose to solder the sockets to the plate and mount some female headers beside the sockets.

Back of Board
This picture of the back of the board shows the sockets and pin headers soldered in as well as some of the ground and +5 volt power supply lines. (One of the power supply lines is on the other side of the board.) I put two female pin headers on each side of the ICs to allow connecting pull-up resistors and the logic analyzer.






74LS06
The 74LS06 has ground on pin 7 and 5 volts on pin 14. These need to be connected to the ground and +5 volt supply on the Adafruit board. Each 74LS06 has 6 gates which input on 'A' and output on 'B'. The gates invert the input signals but the software is already written with this in mind. Every signal from the TRS-80 to the Pi is passed through a gate on the 74LS06. This protects the Pi from the 5 volt TTL signal as we use the internal Pi pull-up resistors to provide a suitable input voltage and the 74LS06 is also able to generate a suitable TTL output from the Pi GPIO output signals.

Pull-up Resistors

Pull-up Resistors

As mentioned in a previous post, the TRS-80 Expansion Interface has 150 ohm pull-up resistors on the Read Data, Write Protect, Index Pulse and Track Zero lines. All the other lines must be terminated on the floppy drive or, in this case, but the emulator board. Only one set of termination resistors should be attached to external devices. I suggest you put a jumper between the termination resistors on the Adafruit board and the +5 volt connector so you can enable/disable termination. The picture shows a suitable location for pull-up resistors between the cable connector and the header which connects to the Pi. I use jumper wires to connect each termination resistor but you may choose to hard-wire them and add a single jumper to +5 volts to enable/disable them all.

Schematic?

There is no schematic yet, but a dedicated hobbyist should be able to wire it up from the information provided in these blog posts. Every signal coming from the TRS-80 Expansion Interface goes to an input (labelled 'A') on one of the 74LS06 ICs. Each input should also be connected to a 150 ohm pull-up resistor unless you have an actual floppy drive connected to the cable which is already terminated. The other side of the gate is connected to the corresponding GPIO pin.

Each of the four outputs (Read Data, Write Protect, Index Pulse and Track Zero) to the TRS-80 is connected to an output (labelled 'B') on one of the 74LS06 ICs. The other side of the gate (labelled 'A') for the outputs is connected to the corresponding GPIO pin which is configured as an input. The Expansion Interface already has pull-up resistors for those lines so no additional pull-up is required.

Programming the Pi GPIO and SPI

GPIO Software

The original software ran under RISC OS. I chose this OS because it is single-user with cooperative multitasking and because I am not an experienced Linux programmer. The cooperative multitasking would hopefully avoid having the program interrupted during time-sensitive operations. The single-user environment allows software very free access to hardware and memory with few security constraints. I have since ported it to Rasbpian as Linux has better documentation and more people working on the kernel.

I used the bcm2835 C library for GPIO for the project. I modified the original library, which was written for Linux, to work under RISC OS. It's a very simple, bare-bones library that directly accesses the various hardware registers using Memory-mapped I/O. Since it runs under both Linux and RISCO OS (with my modifications) this made porting to Linux easier.

When I ported to Linux I found that simple edge detection did not work. I'm fairly sure that's because edge detect interrupts are now supported on the newer kernels and the interrupt service routine is clearing the edge detect bit before my code can detect it. Therefore I switched to using the /sys/class/gpio sysfs interface as described here (see paragraph 'Sysfs interface for Userspace) for the step signal.

The program starts by initializing the library with the call bcm2835_init(). This function memory-maps the registers into shared memory so that the library can access them directly. Under Linux this calls mmap. Under RISC OS this calls OS_Memory 13 to map in the same memory regions as under Linux.

To accommodate any differences in the GPIO pins selected for various function I define which pins are assigned to which function in a header "pin.h" shown below. The definitions are based on bcm2835.h. Since I have a Model B, revision 2.0 Raspberry Pi I use the revision 2.0 definitions.

// input_pins
#define DS0_IN   RPI_V2_GPIO_P1_03 // GPIO 2 connects (through 74LS06) to cable 10
#define DS1_IN   RPI_V2_GPIO_P1_22 // GPIO 25 connects (through 74LS06) to cable 12
#define DS2_IN   RPI_V2_GPIO_P1_15 // GPIO 22 connects (through 74LS06) to cable 14
#define MOTOR_ON RPI_V2_GPIO_P1_07 // GPIO 4 connects (through 74LS06) to cable 16
#define DIR_SEL  RPI_V2_GPIO_P1_11 // GPIO 17 connects (through 74LS06) to cable 18
#define DIR_STEP RPI_V2_GPIO_P1_13 // GPIO 27 connects (through 74LS06) to cable 20
#define WRITE_GATE  RPI_V2_GPIO_P1_10 // not currently implemented
#define WRITE_DATA  RPI_V2_GPIO_P1_21 // not currently implemented

// output pins

#define TRACK_0  RPI_V2_GPIO_P1_16 // GPIO 23 connects (through 74LS06) to cable 26
#define WRITE_PROTECT RPI_V2_GPIO_P1_12 // GPIO 18 connects (through 74LS06) to cable 28
#define READ_DATA     RPI_V2_GPIO_P1_19 // GPIO 10 connects (through 74LS06) to cable 30
#define INDEX_PULSE  RPI_V2_GPIO_P1_18 // GPIO 24 connects (through 74LS06) to cable 8

I also define a few other macros to make typing faster. They are shown below.

#define GPIO_IN   BCM2835_GPIO_FSEL_INPT
#define GPIO_OUT  BCM2835_GPIO_FSEL_OUTP
#define PULL_UP   BCM2835_GPIO_PUD_UP

The next step after initialization is to configure the various GPIO pins as inputs and outputs according to which floppy disk signal is being handled. The first group of library calls set the input signals as GPIO inputs and enable the pull-up resistors for all the inputs. You must enable pull-up resistors for all inputs because the ICs are open collector.

    bcm2835_gpio_fsel(DS0_IN,GPIO_IN);
    bcm2835_gpio_set_pud(DS0_IN,PULL_UP);
    bcm2835_gpio_fsel(DS1_IN,GPIO_IN);
    bcm2835_gpio_set_pud(DS1_IN,PULL_UP);
    bcm2835_gpio_fsel(DS2_IN,GPIO_IN);
    bcm2835_gpio_set_pud(DS2_IN,PULL_UP);
    bcm2835_gpio_fsel(MOTOR_ON,GPIO_IN);
    bcm2835_gpio_set_pud(MOTOR_ON,PULL_UP);
    bcm2835_gpio_fsel(DIR_SEL,GPIO_IN);
    bcm2835_gpio_set_pud(DIR_SEL,PULL_UP);
    bcm2835_gpio_fsel(DIR_STEP,GPIO_IN);
    bcm2835_gpio_set_pud(DIR_STEP,PULL_UP);

Though they are not used currently, we can also enable the floppy disk Write Gate and Write data signals if they are connected to the board.

    bcm2835_gpio_fsel(WRITE_GATE,GPIO_IN);
    bcm2835_gpio_set_pud(WRITE_GATE,PULL_UP);
    bcm2835_gpio_fsel(WRITE_DATA,GPIO_IN);
    bcm2835_gpio_set_pud(WRITE_DATA,PULL_UP);

Next the SPI interface must be initialized through the library call bcm2835_spi_begin() and the appropriate clock rate set. Finally the 'step' signal must be configured through the /sys/class/gpio interface for rising edge detection. I wrote my own small class to do this.

Virtual Disk Image

I currently support DMK virtual disk images as described here. This format is comprehensive enough to hopefully allow supporting of copy-protected disk images as well as normal formats. Since I don't have a double-density controller I only implemented single-density. The Pi has enough memory available that the entire disk image can be loaded into memory into a suitable C++ class. Since the floppy disk controller expects an actual drive to take several milliseconds to step the read head from one track to the next we have sufficient time to convert the active track to SPI output format during that time.

Converting the raw track to SPI output format is done by a lookup table. Currently I support two different SPI clock rates. The 'single' rate outputs one clock and data bit per byte so a single track byte is sent as eight SPI output bytes for a total single-density track size of 25,000 bytes. The 'double' rate outputs either a clock or a data bit per byte so a single track byte is sent as sixteen SPI output bytes for a total single-density track size of 50,000 bytes. After the initial translation we then have to fix up the various special index and data address marks which have special clock and data patterns. Fortunately there are only seven of these special marks and the location of these special bytes are stored as part of the disk format. Another lookup table is used to set the index and data address marks to the correct pattern.

SPI Output

The SPI output is time critical since we are trying to fill the FIFO in a very short period of time as well as toggle the index pulse up and down in synchronization with the SPI output. I created a TransmitTrack class to handle this class. It uses a separate thread which runs at high priority and directly accesses the SPI registers. It runs continuously until a flag is set (and a condition signaled) to exit. At the beginning of the track the index signal is set to HIGH and the current time is recorded. The index pulse should remain high for around 4-5 milliseconds so we add that to the current time so we know when to set the index pulse LOW. Next we write to the SPI FIFO until the SPI register indicates the input FIFO is full. Then we enter a loop where we call pthread_cond_timedwait for 100 microseconds or until the exit condition is signaled. Each time pthread_cond_timedwait returns we either exit (because the condition was signaled) or we continue to fill the FIFO. We also check to see if the index pulse needs to be set low. A real floppy disk spins continuously so when we reach the end of the track we wait until the last byte has been transmitted and start all over again.

The SPI interface reads and writes data at the same time and expects the input FIFO to be emptied as data arrives. Currently we read the data from the input FIFO but discard it as virtual write has not been implemented. I suspect the write access will be require much higher data rates and will probably have to use DMA for SPI input/output.

Source Code
Source code on Google Drive includes a Geany project and a simple makefile. The binary file ldos.dmk is a disk image of a bootable LDOS single-density disk. Note that you must run the program using sudo or from a root terminal.

Note: current code doesn't use the kernel spi driver so you might need to blacklist spi-bcm2708.

Thursday, 26 September 2013

SPI - the Key to the Project

Using SPI to Output Track Data

I looked at several floppy disk projects for ideas on how to make the Raspberry Pi work as a virtual floppy disk. On in particular has been quite useful - the SVD. It was based on a MicroChip PIC controller and bit-banged the output line representing the floppy disk track data. The difficulty with this approach is that it relies on assembler code and very tight timing where each instruction time is measured and accounted for. This is not compatible with an operating system where interrupts could stop the flow of execution and cause gaps in the output data.

Let the Hardware do the Heavy Lifting


After a bit of investigation I decided to try the SPI bus instead. This would let the specialized hardware of the Raspberry Pi do a lot of the work for me. Put simply, I'm ignoring the SPI protocol and asking the SPI hardware to perform parallel to serial conversions and to do it according to a specified clock rate. The logic analyzer screen shot below shows the SPI clock on channel 1 and the SPI output on channel 0. The first two output bytes are 0xFF and the third byte is 0x00.


Note that the SPI clock is not continuous. There are eight clock pulses for each output byte followed by a one clock cycle pause. So what we actually have on the output are eight bits that can be toggled on and off followed by a ninth 'spacer' bit which is always zero. The challenge is to use this capability to output a signal that emulates the floppy drive data output.

The initial attempt set the SPI clock so that the total time for the eight bits plus the ninth 'spacer' bit equaled 8 microseconds. A value of 0x80 was transmitted for a data bit 0 which only has the clock bit set and a value of 0x88 was transmitted for a data bit 1 which has both clock and data bits set. Therefore each byte of track data was translated into eight bytes of SPI data where each byte of SPI data represents a single bit of track data. The screenshot below shows the timing.


The output signal deviated from the ideal signal because of the spacer bit, but the floppy disk controller was still able to decode this correctly as the bits appeared within the expected timing window.

A more accurate approach doubled the SPI clock speed and represented each clock or data bit with a single SPI byte using a bit pattern of 11000000b or 0xC0 in hexadecimal for a '1' and zero for a '0'. (Clock bits are always '1' except for the special address marks noted in the previous blog post.) Note that because the clock speed is doubled we have to set two bits instead of one for each output '1'. Therefore each byte of track data was translated into sixteen bytes of SPI data where the even bytes of SPI data represented the clock bits and the odd bytes represented the data bits. The screenshot below shoes the timing. The clock and data bits are still .888 µs but the output pulses and gaps are now much closer to the ideal signal.


With the double-rate scheme a single-density track becomes 50,000 bytes of SPI data which must be transmitted every 200 milliseconds or one byte every four microseconds. You may wonder what happens if the software is interrupted for a period greater than four microseconds. Will there be a gap in the SPI output signal? Fortunately the Raspberry Pi SPI has a 64 byte input buffer which will take 256 microseconds to empty using the double-rate scheme. The current software runs on Risc OS where the recommended maximum time to disable interrupts is 100 microseconds so even in the worst case situation the buffer is unlikely to be emptied while the operating system is handling an interrupt. If I ever figure out how to enable DMA for the SPI even faster data rates would be possible.

More on SPI and GPIO programming in later blog posts.