[Electronics]  [Files]   [Links]

 
Electronics » the Z80 project » ide interface

Note: There's a newer version of my IDE interface / article here

My Z80 Project was sorely lacking in the local non-volatile storage dept. I thought about adding a flash memory IC (almost) directly to CPU address/data bus as I had some old BIOS chips recycled from PC motherboards that would have done the job. However they wouldn't hold much data - half a meg - and the only practical "file system" would have been "console memory card" style block save approach.


Compact Flash to IDE Adapter & Card. Ta eBay :)
Thinking of flash RAM in general, I noticed how ridiculously cheap camera memory cards were getting, especially on ebay (seeing how people only really want the highest capacity cards nowadays). Looking at the different types available (MMC, Secure Digital, Compact Flash, Memory Stick et al) and scanning the net for interface specs, I found the most commonly used cards in hobbiest's projects were the Compact Flash type. This is because - as I soon found out - they can run in "True IDE mode", ie: they can use the same interface as hard drives, CDROMs etc. All that's needed is an adapter (check out the Hong Kong based suppliers on ebay) and you can attach 'em to a normal IDE connector.

I figured the IDE / Compact flash route would be cheap, offer large capacity and allow me to do all my testing with an old harddrive, rather than risk spooning up a memory card. (It'd still require a file system for anything more than random access to thousands of 512 byte sectors, but that could come later..) Also the IDE interface specs are well documented on the net - people have made IDE interfaces for the CBM64, data loggers, PIC chips etc so I theres lots of 'inspiration' out there...

Firstly, here's the IDE pin-outs (not worried about the CF card pin-outs, the adapter takes care of that. Beware though that Compact Flash cards are not TTL level compliant and require a 4.0V logic high according to the spec sheet.)

/Reset	-  1  2 - Ground 
Data7	-  3  4 - Data 8 
Data6 	-  5  6 - Data 9 
Data5 	-  7  8 - Data 10 
Data4 	-  9 10 - Data 11 
Data3 	- 11 12 - Data 12 
Data2 	- 13 14 - Data 13 
Data1 	- 15 16 - Data 14 
Data0 	- 17 18 - Data 15 
Ground 	- 19 20 - No pin (Key) 
DMARQ 	- 21 22 - Ground 
/Write 	- 23 24 - Ground 
/Read 	- 25 26 - Ground 
IORDY 	- 27 28 - CSEL (Cable select pull-up) 
DMACK 	- 29 30 - Ground 
IRQ 	- 31 32 - Obsolete: no connection (formerly "IOCS16") 
Addr1	- 33 34 - PDIAG / CBLID (UDMA detect) 
Addr0 	- 35 36 - Addr2 
/CS0	- 37 38 - /CS1 
DASP	- 39 40 - Ground

Electrically, it's just a 16-bit data bus, with a 3-bit address bus, 2 chip select signals, a read & a write signal plus a drive reset line. (Compact Flash cards will work in an 8-bit mode too, but I havent looked into that.) Most home-brew IDE circuits I have seen leave the DMARQ, DMACK, IORDY, PDIAG, CSEL and IRQ lines unconnected, however the following may be of interest:

DMARQ is an output from the drive used in high speed DMA transfers so presumably can be ignored. DMACK is an input at the drive used to acknowledge DMA mode - I noted that the Compact Flash ATA mode spec sheets say if there is no DMA-mode ability in the host (as there wouldn't be for a project like this) DMACK should be driven high.

IORDY is an output from the drive which can be used to slow down read/write cycles if they are too quick (ie: insert wait states) I have not used this signal. The ATA spec sheet suggests it be pulled high with a 1k resistor min (presumably, that's only if it is to be used by the host circuit.)

"CSEL" is generally pulled high via a 10K resitor. It can be used for cable select of master and slave when using a special cable. These cables ground CSEL, and have the conductor cut beyond the first connector, this way the first device sees itself as the master (pulled down) and the second sees itself as slave (not pulled down).

The "DASP" pin just an open collector drive activity indicator and can be connected to a LED from Vcc via a resistor. All other pins, (except the ground lines of course:) can be ignored and left unconnected.

On the software side the interface protocol has been expanded to very complicated levels over the years but I didnt need super-fast interrupt-based DMA modes etc. Fortunately its all pretty straightforward: Basically you tell the drive the sector number you want to access, give it a command and then transfer the data. Communicating with the drive is done via 16 IDE registers, the IDE address lines A0-A2 and chip select lines /CS0 and /CS1 dictating which IDE register is being accessed. Data is written to (and appears in) these registers depending on what the drive is doing (more info later).

Onto the actual interface electronics: An IDE interface would be a direct bus connection to the Z80 if wasnt for those meddling kids the 16-bit data bus. As it is, you need a couple of latch ICs, a buffer and some control logic (or a microcontroller with a lot of ports) to handle the 8-to-16bit conversion. (I used 74HC574 latch chips and a 74HC245 buffer. Another latch was used to hold the IDE address bus and chip select signals but in other projects these could come direct from the host perhaps.)


Bytes from the 'Get ID' command. Note the drive name.
Here's a schematic (hand scribbled - sorry:) of my interface - some of the input lines are specific to my project - for example "/IOWR3", "/IORD3" and "/IOW2" are the outputs of a 74HC139 port/address demultiplexor on my CPU board that were going begging. There's also a couple of doubled up inverter gates whose purpose is nothing more than to make absolutely sure that certain signals arrive at the correct time via propagation delays.

In my implementation of the interface, in order to send a 16bit word to the interface I write the high byte to (my Z80 board's) port 7, then the low byte to it's port 3 - the circuit presents to the full 16 bit value to the IDE pins when the second byte is written. To read a 16bit word from the drive I read the low byte from (my Z80 board's) port 3 (which also clocks the high byte in a latch) and then read the high byte from its port 7. Writing to (my Z80 board's) port 2 simply sets the IDE's A0,A1,A2,/CS0 and /CS1 lines (IDE register select etc) - I could've controlled /RESET from this latch too but just made a resistor/ capacitor timer instead. Note that only the sector data transfers (to reg 0) are 16 bit, commands and reads to/from the other registers are 8 bit so the high byte can be ignored.

Here's a run down of the IDE registers accessed by A0-A2 and /CS0 and /CS1 - some simplifications have been made. EG: I'm ignoring all the old head/cylinders/sectors rubbish as that's ancient history - using the LBA (Logical Block Address) mode is the way to go. Nowadays each sector is simply addressed sequentially, via the 28 bit address held in registers $3-$6 (Incidentally, this is where the pre-LBA48 137GB drive size limit comes from in Windows: 2^28 sectors * 512 bytes = ~137GB. I've not looked into the way LBA48 works as I'm not going to be needing anything like that amount of capacity!).

IDE reg: A0-A2: /CS0: /CS1: Use:

  $0	000    0    1     IDE Data Port 
  $1	001    0    1     Read: Error code
  $2	010    0    1     Number Of Sectors To Transfer 
  $3	011    0    1     Sector address LBA 0 (low byte)
  $4	100    0    1     Sector address LBA 1 
  $5	101    0    1     Sector address LBA 2 
  $6	110    0    1     Sector address LBA 3 (high nybble in 0:3) **
  $7	111    0    1     Read: "Status", Write: Issue command to drive
  $8	000    1    0     Not Important 
  $9	001    1    0     Not Important 
  $A	010    1    0     Not Important 
  $B	011    1    0     Not Important 
  $C	100    1    0     Not Important 
  $D	101    1    0     Not Important 
  $E	110    1    0     Not Important 
  $F	111    1    0     Not Important 

** Other bits in register $6:

Bit 4= Select Master (0) or Slave (1) drive
Bit 5= Always set to 1
Bit 6= Always Set to 1 for LBA Mode Access
Bit 7= Always set to 1

Some detail: The most important register is IDE register $7 which accepts commands when written to and reports the drive status when read.
IDE Register $7 WHEN WRITTEN = Send command to drive.

Key commands (there's loads more besides) :-

$20 - Read sectors with retry 
$30 - Write sectors with retry 
$EC - Identify drive


IDE Register $7 WHEN READ = drive status: 

Bit: Name: Condition:

 0   ERR    1 = Previous command ended in an error 
 1   IDX        (not important)
 2   CORR       (not important) 
 3   DRQ    1 = Data Request Ready (Sector buffer ready for transfer)
 4   DSC        (not important) 
 5   DF     1 = Write Fault 
 6   RDY    1 = Ready for command 
 7   BUSY   1 = Controller is executing a command.
IDE Reg $1: Can be read for more detail when the ERR (bit 0) in STATUS (reg $7) above is set:
Bit: Condition:

 0    1 = DAM not found 
 1    1 = Track 000 not found 
 2    1 = Command aborted 
 3        Reserved
 4    1 = ID not found 
 5        Reserved
 6    1 = Uncorrectable ECC error 
 7    1 = Bad block detected 

Sending commands

The "Identify drive" command is useful as it tells you your interface is working (without having to read anything from the disk itself). The command returns 512 bytes of data containing a lot of info about the connected/selected drive including the device name in ASCII (40 chars @ byte offset $36) and the capacity of the drive in sectors (2 words @ byte offset $72, least significant first).

The procedure for getting a drive's ID is:

1. Make sure BUSY=0 and RDY=1 before proceding.
2. Select the drive master/slave with appropriate write to IDE reg $6.
3. Send "Drive ID Command" ($EC) to Command Register $7
4. Wait until BUSY=0 and DRQ=1
5. Check error flags.
6. Read 256 words from data port 0.

The procedure for reading a sector is:

1. Make sure BUSY = 0 and RDY =1 before proceding.
2. Tell the drive what sector is required (fill in the LBA address regs)
3. Set number of sectors to transfer to 1 (reg $2)
4. Issue read sector command ($20) to reg $7
5. Wait until BUSY=0 and DRQ=1
6. Check error flags.
7. Read 256 words from data port 0.

The procedure for writing a sector is:

1. Make sure BUSY = 0 and RDY = 1 before proceding.
2. Tell the drive what sector is to be written (fill in the LBA address regs)
3. Set number of sectors to transfer to 1 (reg $2)
4. Issue write sector command ($30) to reg $7
5. Wait until BUSY=0 and DRQ=1
6. Write 256 words to data port 0.
7. Wait for BUSY to become clear and RDY to become set.
8. Check error flags.

And that's about for the basics of reading and writing to an IDE drive a sector at a time. Here's the Z80 source code I programmed to operate my Z80 interface - as usual its The Z80 Project-specific, not well optimized, poorly commented and may have bugs. "Educational use only" as they say.


The finished board (includes keyboard interface)

Oh yes, file systems. Small memory cards (and hard drives) tend to use the FAT file system and this is reasonably well documented on the net. It is however overkill for what I need (what with its variable cluster sizes, partition tables and so on) so.. I made my own. Info here.

Update: The IDE interface circuit was (largely) integrated into a CPLD when I did the V3 Z80 Project. A newer schematic is here

Further Reading:

CompactFlash.Org - Specs available for download etc
Peter Faasse's 8255 to IDE interface
Compact Flash to IDE adapters on ebay UK
The Alya Project (more CF->IDE stuff)
Hans Summer's IDE interface
(Old) ATA spec text file
Official ATA specs - very detailed (too much so in fact)