A beginners guide to IDE

This document is written to help people to start using IDE hard drives outside the PC environment.

It is based on my own findings when trying to connect a hard drive to my Atmel AVR Mega163 microcontroller. Therefore the direct I/O names and sample code instructions are specific to the AVR architecture and the Mega163 chip, but I'll try to write it in a way that makes it easy to convert to different controllers and architectures.

This document only covers PIO (Programmed Input/Output) data transfer, as it is probably all you are ever going to need, and it's fairly simple once you get then hang of it.

 
The hardware
Wires, Pins and Ports.
First, we'll have a look at the hardware we are going to use, and how to wire it all togheter.
Hard drive

To get started, It's a good idea to have one of these, that you are not too worried about destroying.
Most likely you are not going to break the drive, but if you follow this guide to the end, you'll be writing stuff to the disk, that makes it unreadable for the PC.

The picture shows a Western Digital "Caviar 136AA", and it's this drive I've been working whit.

Old style 40-lead IDE cable

You'll probably need one of these too...

If you have a µC developing board like the AVR STK500 that has 2x4 or 2x5 pin headers for the I/O ports, I have written a little guide on how to modify the IDE cable to easily connect it directly to the board.


Pinout table for the IDE connectors.
All lines are 5v TTL level.
Pin # Signal Function Pin # Signal Function
1 Reset 2 Ground
3 Data 7 4 Data 8
5 Data 6 6 Data 9
7 Data 5 8 Data 10
9 Data 4 10 Data 11
11 Data 3 12 Data 12
13 Data 2 14 Data 13
15 Data 1 16 Data 14
17 Data 0 18 Data 15
19 Ground 20 Key
21 DMARQ 22 Ground
23 DIOW- 24 Ground
25 DIOR- 26 Ground
27 IORDY 28 CSEL
29 DMACK- 30 Ground
31 INTRQ 32 IOCS16-
33 DA1 34 PDIAG-
35 DA0 36 DA2
37 CS1FX- 38 CS3FX-
39 DASP- 40 Ground

Let's have a look at what these pins are, which one we'll use, and where to connect them.
Reset This is the hardware reset line on the IDE/ATA bus. The signal is input to the IDE devices and is active low. This means that pulling it to logical "0" would reset the devices connected to the IDE bus.

I connected it to Pin7 on PORTA. Optionally, it can be connected to Vcc.

Data 0 -
Data 15
These are the actual data lines on the IDE/ATA bus. All these lines are bidirectional I/O and are used to transfer data, instructions, parameters and status information between the HDD and the host (in our case, the µC).
It seems that when these lines are configured as outputs, they need pull-up resistors provided by the host side to function properly.
It is possible to operate the HDD using only the lower 8 bits of this data bus, as all the commands and parameters are 8 bits. The downside is that you'll loose every 2nd byte of the space on the drive.

I have connected the lower 8 lines to PORTC and the upper 8 lines to PORTB on my µC.

DMARQ
DMACK
These are used for DMA transfer, wich is not covered in this document.
DMARQ is the DMA request sent from the IDE device to the host.
DMARK is the DMA acknowledge that the host sends to the IDE device.

I left the DMARQ line unconnected, and connected DMACK to Vcc.

CS1FX, CS3FX Chip select lines. Active Low.
For basic PIO operations, it's enough to know that CS1FX should be logical "0" and CS3FX should be logical "1".
Other combinations of these lines are used for DMA transfer, and other specialized tasks.

I connected CS3FX to Pin0 and CS1FX to Pin1 on PORTA.

DA0:DA2 Register Address lines.
These lines are used to select which register in the IDE device that is going to be "connected" to the data lines when the read or write strobe is activated.

I connected DA2 to Pin2, DA1 to Pin3 and DA0 to Pin4 on PORTA.

DIOR Read Strobe. Active Low.
Sending a logical "0" onto this line causes the IDE device to place the content of the register selected by A2:A0 onto the data lines.
Data is present on the data bus after the falling edge of DIOR.

This one, I connected to Pin5 on PORTA.

DIOW Write strobe. Active Low.
This line is used to signal the IDE device that there is data ready on the data bus.
Setting it to logical "0" makes the IDE device read the data on the data lines (8 or 16 bits) into the device register specified by the signals on the DA2:DA0 lines.
Data is read into the IDE device from the data bus after the falling edge of this line.

This one, I connected to Pin6 on PORTA.

DASP Device active.
Connecting a LED from Vcc (through resistor) to this line would work as an activity indicator.

Wiring it directly to any of the 8 LED pins on the STK500 also works fine, as they are active low, and have pull-up resistors built in.

GND Ground

I soldered them to the GND pins on the connectors I made in the Cable Hack guide.

All the lines that are not covered here, I left unconnected. Most of them have a purpose, but they are not needed for the simple operations described in this guide.
 

 
Inside the HDD
First glance at the registers.
Inside the electronics on the HDD, there are several registers. In the following table, I have included the ones that we'll be using, the data pattern to put on the A2:A0 and CS1FX&CS3FX lines if you have wired it to PORTA in the same way as I did, and a brief description of the function of each register.
We'll get back to them in more detail later.

Name Bit Pattern Description
Status 0b11111101 This is a read-only register that holds information about the state of the IDE device.
Data 0b11100001 This is the only 16 bit register. It's both read and write, and is used to read from or write to the data buffer in the IDE device.
Command 0b11111101 This is a write-only register, that is used to send commands to the IDE device.
Sector Count 0b11101001 Used for additional command parameters.
Sector Number 0b11111001 Used for additional command parameters.
Cylinder High 0b11110101 Used for additional command parameters.
Cylinder Low 0b11100101 Used for additional command parameters.
Device/Head 0b11101101 Used for additional command parameters.

At this point, it's time to start doing some "real world" stuff.
If you have modified and connected the IDE cable as described earlier, this is going to be a straightforward process, and you should be able to observe the results straight away.

What we are going to attempt, is to read the content of the Status register, and show the result on the STK500 LED's.
Using the 10pin jumper cables that came whit the STK500, connect all PORTD pins to the LED pins.

The first thing we need to do, is configure the I/O direction for the ports we are going to use.
PORTA is always going to be configured as output, so we can set the data direction register for PORTA (UDRA) to all "1"s.
The same is true for PORTD.

Because we are going to read from the data bus, we need to configure PORTC (and PORTB) as inputs. We also need to enable the built-in pull-up resistors on the input ports.
Writing "0"s to DDRC and DDRB configures them as inputs. Writing "1"s to PORTB and PORTC enables the pull-up resistors.

The next thing to do, is to activate the proper register address lines on the IDE bus.
For the status register, this is done by setting CS1FX to "0", CF3FX to "1" and all DA lines to "1".
RESET, DIOR and DIOW should all be deactivated by setting them to "1".
Outputting the value "0b11111101" to PORTA makes light work of that.

To read the status register data, we need to activate the Read Strobe, by writing a logical "0" to Pin5 on PORTA.
When the read strobe is active (logical "0"), the data in the Status register is available on the lower 8 bits of the IDE data bus. My experience shows that it is necessary to wait a few cycles before the data bus is read.
While the read strobe is "0", we can read the content of the Status register by reading PINC.
We'll put the content of PINC into r16.
After PINC have been read, we can deactivate the read strobe by setting it to logical "1".

The next thing to do is to invert the content of r16, and write it to PORTD.
The reason for inverting it, is that the LEDs on the STK500 are active low, resulting in a "0" being a lit LED, and a "1" being a dark LED.

The last thing to do is to put the inverted data from the status register onto the LEDs, and start over.

Get the complete ASM source code and paste it into a new project.

Here is a table of what the different bits in the status register are.
Bit number Name Description
7 BSY Busy flag. If this flag is "1" the device is busy, no other data is valid
6 DRDY Device ready flag. If this flag is "0" the device is not ready
5 WFT Obsolete. Indicates a write error.
4 SKC Oboslete. Indicates that the device performed a seek operation successfully
3 DRQ Data Request. This bit indicates that the device is ready to transfer data
2 ECC Obsolete. Indicates that an EEC correction was performed on the data.
1 INDEX Obsolete. This bit is pulsed to "1" each revolution of the disk.
0 ERR This bit is set to "1" if there has been any errors. All other data is then invalid.

If you tried the sample code provided, you should be able to see that when the drive is powering up, the BSY flag is set to "1" while all the other flags are "0".
After a short while, the BSY flag should be cleared, and the DRDY and the SKC flag should be set to "1". You should also be able to observe a faint glow in the LED indicating the INDEX flag. This is because this flag is pulsed to "1" as the heads pass the index marker on the disk, and this happens only once every disk revolution.
It should be noted that the state of the obsolete flags may not be indicated properly, as the device manufacturers are no longer required to implement them in new designs.
 

The ASM code samples provided are for the Atmega163 chip, running at 4.000MHz, using the latest AVR studio as the development environment.
You should be able easily port the code to any other device or platform.
Real work
Sending the first commands to the device
The next step in this guide, is to send a command to the IDE device.
The commands are the stuff that makes it all work.

The first command we are going to send, is the "Idle Immediate" command.
This command causes most hard drives to park the heads an spin down, which can be verified by simply listening to the hard drive.

To prepare to send the command, there is a few things we have to do first.
The first thing to do, is to place the I/O ports connected to the data lines into output mode.
In the AVR, writing "1"s to to the data direction registers takes care of that.

The next, not so obvious, step is to inform the devices (there can be two devices on each IDE bus, remember? One Slave and one Master) which one of them we want to perform the command.
This information is hiding inside the Device/Head register, bit number 4.
Setting this bit to "0" selects the Master device. Setting it to "1" selects the slave.
Assuming the HDD we are working on are set as Master, we need to write a "0" to this bit.
The easiest way to do this, is to write "0"s to all the bits in this register, as the other bits are ignored for this command.
To do this, we need to "connect" the Device/Head register to the data lines, by setting DA0 to "0", DA1 and DA2 to "1", CS1FX to "0" and CS3FX to "1".
Writing "0b11101101" to PORTA takes care of that.
Now that the data lines are connected to the Device/Head register, we can write all those "0"s to it.
First, we have to write the "0"s to the lower 8 bits of the data lines, by outputting them on PORTC.
Then we make the IDE device read the content on the data bus into the Device/Head register, by activating the Write Strobe. Activate the write strobe by setting Pin6 to "0".
Once the Write Strobe
have been activated, the IDE device reads the data on the lower 8 bits if the data lines into the Device/Head register. It is wise to wait a few cycles before disabling the write strobe again, by setting Pin6 in PORTA to "1"

Now that we have decided which devise is going to execute the command, we'll send it the actual command.
First we need to configure the DA and CSnFX lines to access the Command register.
Outputting "0b11111101" to PORTA takes care of that.

When the Command register is selected, the data we write onto the data lines is interpreted by the IDE device as a command.
The command we are going to send, is "$E0". We'll write "$E0" to PORTC, then activate the Write Strobe by setting Bit6 in PORTA to "0", wait a few cycles for the IDE device to receive the command, and then deactivate the Write Strobe by setting Pin6 in PORTA to "1".

Within a timeframe of 2 seconds after the command have been sent, the device should go into idle mode, and spin down.

For the following code sample to work, we need to disconnect the 10 Pin jumper cable going to the LEDs, and place it on the SWITCHES header.
The code is going to monitor the status register until the device is ready. Then it waits for someone to press the SW0 button on the STK.
The buttons on the STK500 is also inverted, so pressing the button gives a logical "0" to the controller, and releasing it gives a logical "1".

If everything went well, the HDD should now be in idle mode.
This is not a very productive command, but it works as an insurance that everything is working as it should.
In some rare cases, the HDD might not support this command, and would simply do nothing.
If nothing happens whit your HDD, you might try it on another, more modern one.
Nothing should happen to the data on the HDD, unless there's a freak accident and you happen to zap it whit ESD, or drop it on the floor.
 

 
Reading data
Getting some real data from the buffer on the hard drive
In this section we are going to read some real data from the HDD we are working whit. The data we are going to read, is the data that is generated by the "Identify Device" command.

This is also a fairly simple command, as then only additional parameter we need to specify is which of the two IDE devices that are going to execute it.

When the command is finished, the device have generated a data set of 512 bytes in it's internal data buffer. To read from this buffer, we have to read from the Data register. As mentioned before, this is the only 16 bits wide register, so we have to use both PORTB and PORTC to properly read it.
When data is read from the Data register, it comes 2 bytes at the time. This means that we have to read from the register 256 times to get all the data.

To pull this one off, we can examine the data in two ways.
One way is to store it in the µC EEPROM and from there, read it from the µC using the SPI programming interface.
The other way, is to use the built-in UART in the µC to send it directly to the PC, using the spare RS323 port on the STK500.
Using the EEPROM is fast going to eat away at the limited number of guaranteed write cycles of the EEPROM, but might be the best aproach if you don't have the STK500 board.

For the UART approach, the first thing to do is to connect one of the two-wire jumper cables that came whit the STK500 from PORTD Pin0 and Pin1 to the "RS232 SPARE" header on the STK500. "RXD" goes to "PD0" and "TXD" goes to "PD1".
If you have another serial cable, and two COM ports on your computer, you can just connect the "RS323 SPARE" connector on the STK500 to COM2 or whatever on your PC. If not, you can simply swap the one you got between the "RS232 CTRL" plug and the "RS232 SPARE" plug on the STK500.
If you are not familiar whit how the UART works, that's a disadvantage. You can still continue in this guide, as the UART settings are covered in the code sample.

After we have worked out the new connections regarding the UART, we are ready to go on.
The first thing we have to do, is to check if the HDD is busy. We already know how to do that, so I'll just go on to the next step.
When the device is ready, We'll write "0"s to the Device/Head register to select the Master device as the target for the following command.
The command we are going to send this time is "$EC".
The process of sending the command is the same as for sending the "Idle Immediate" command. To sum it up:
-Set PORTB and PORTC as outputs
-Output the address for the Device/Head register (0b11101101) on PORTA
-Write "0"s to PORTC
-Pulse the Write Strobe to "0"
-Output the address for the Command register (0b11111101) on PORTA
-Output "$EC" to PORTC, and optionally "0"s to PORTB. PORTB is ignored nonetheless.
-Pulse the Write Strobe to "0"

The command we just sent is going to take the drive some time to execute, so we have to check the BSY flag before we continue.
As long as the BSY flag is "1" we should just loop back and check it again and again until it changes to "0".
By the time the BSY flag changes from "1" to "0", the DRQ flag should have changed from "0" to "1" to signal that the device is ready to transfer data.

To read the data from the HDD data buffer, we have to repeatedly read from the Data register, and each time we read it, we get another two "new" bytes from the data buffer.

Reading from the Data register is just as easy as reading from the Status register. The only difference is what signals we put out on the DAn and CSnFX lines.
It is also a good idea to check the BSY flag before each new read from the DATA register. In most cases, it'll be cleared by the time we get about to checking it, but not checking it may result in false data readout, and we don't want that.
Here's a quick summary of the read process:
-Set PORTB and PORTC as inputs.
-Check the BSY flag until it's cleared.
-Output "0b11100001" on PORTA to select the Data register
-Activate the Read Strobe by setting PORTA Pin5 to "0"
-Wait for a few cycles (nop)
-Read all the 16 bits on the data lines, using both PORTB and PORTC.
-Deactivate the Read Strobe
-Put the data just read from the data lines somewhere "safe"
-Repeat 255 more times.

A nice place to put the data we get from each single read cycle would be either the UART or the EEPROM.
You could just place it in SRAM, but it would be hard to examine later on.
Whit the UART, you have the data right away, or by using the EEPROM, you can easily read it out using any ISP device.

This time, there are 2 almost similar code samples. One for UART, and one for EEPROM.

If you are not using the exact same hardware as I am, you propably have to modify the code a bit. In doing so, you are likeley to discover that both the UART and EEPROM code are more or less the same.
These samples are direct links to the ASM files, to preserve the proper formatting.

To use the UART sample, you need to run a properly configured terminal program on your PC.
A good such program is Br@y++ Terminal.

Here's how you make it work:
-If you use just one COM port, close the AVR programming window.
-Connect a serial cable to the "RS323 SPARE" plug on the STK500
-Start Br@y++ Terminal, and select the COM port you have connected to the "RS323 SPARE"
-Select 19200 as the baud rate
-Press the "Connect" button.
-In the long text-box in the lower part of the program window, type the number 1
-Press the " -> Send" button, located immediately to the right of the text-box.

If everything went well, you should see some data streaming into the upper frame of the program window.
There's going to be alot of <0>'s but you should also be able to make out the serial number (20 ASCII characters) and the model number (40 ASCII characters).

If you get nothing, you might have messed up the settings in the terminal program.
try to disconnect the wire going to the "RS323 SPARE" header TXD and RXD and short the two pins togheter.
While they are shorted, press the " -> Send" button again and observe the upper frame.
If you don't get a single "1" in there, something is very wrong.
Either you got the wrong COM port or you have some faulty wiring somewhere.

If you used the EEPROM solution, the µC immediately starts polling the BSY and RDY flag, and once the device is ready, it executes the command and saves the 512 byte long result to EEPROM, then stops.
AVRStudio is buggy when trying to read the EEPROM content directly into the simulator.
In most cases, you just get a bunch of "FF"s.
If you read the EEPROM to a file, and then uses the "Up/Download Memory" tool in the "Debug" menu to load the hex file into EEPROM debug memory, you should be able to view the results from the Identify Device command in the EEPROM section in the memory window.
 

 
More Reading
Reading from the first sector on the hard drive
By this time, we should have the basic principles of reading the HDD data buffer nailed down.
The next logical step from here, is to put some more fascinating data into this buffer.
One way to do that is to send the Read Sectors ($20) command to the drive.
Depending on what we put in the command parameter registers, we can read any sectors we like, from the entire disk, and put that data into the data buffer for reading.

The Read Sectors command is a bit more complicated, as it uses no less then five different parameters.
For the HDD to be able to give us the data we want, we have to tell it where it is.
More to the point; we have to tell it from which sector it's going to read and how many sectors to read in one go.

The sectors on the HDD can be accessed in at least two different ways. One way is to imagine all the sectors lined up in a vast table, where the sectors are simply numbered starting from 0 and going up to the total number of sectors on the drive. This is called Logical Block Addressing or LBA. The other way, is to specify which Sector, Head and Cylinder we want to read from. This is called CHS.
CHS is a pain to work whit, and has a capacity limit at 2048MB. Luckily, most hard drives today support LBA addressing, so that's what we are going to use.

The LBA address is a 28bit wide address, and knowing that each sector have 512 byte of data each, it's fairly easy to calulate that the limit is much higher for this methode. If you really want to know, then here it is: 2^28 x 512 = 137438953472 bytes, or roughly 128GB.

The LBA address starts in the Sector Number register, goes on to Cylinder Low and Cylinder High and has it's last 4 bits at the end of the Device/Head register.
The Device/Head also holds another secret in it's Bit6. This bit is used to select either CHS or LBA mode. Setting it to "1" enables LBA addressing.

On top of all this, we have the Sector Count register.
The Sector Count register is used to specify how many sectors we want to read, starting from the one specified by the LBA address.
This register is somewhat tricky. If we want just one sector, we write "$01" to it. If we want 5 sectors in one go, we write "$05" to it.
If we write "$FF" to it, we get 255 sectors. Writing "$00" on the other hand, doesn't give us 0 sectors, but 256.

To read sector 0, also known as the boot sector, first we need to write values to five different registers before we can issue the command.
The first register we'll write to is the Sector Count register. We only want one sector, so we'll write "$01" to it.
Next, we'll write "$00" to all the other registers (We want sector 0, remember?) except the Device/Head register.
To the Device/Head register, we'll write "$40", or "0b01000000", to set the Enable LBA (Bit6) flag. We still want the master device, so we'll put a "0" in the  DEV (Bit4) flag.

Sending the Read Sectors ($20) command is done in much the same way as when sending the Identify Device command, only we have set some more parameters before we execute the command. The whole process goes like this:
-Wait for the HDD to become ready
-Write "$01" to the Sector Count register
-Write "$00" to the Sector Number register
-Write "$00" to the Cylinder Low register
-Write "$00" to the Cylinder High register
-Write "$40" to the Device/Head register
-Write "$20" to the Command register
-Wait until the HDD is ready.
-Read from the Data register and store it in a "safe" place
-Repeat the two last steps 255 more times.

It is also possible to read more then just one sector at a time.
By putting a number other then "$01" into the sector count register is going to prepare more then just 512 bytes for reading.
In those cases, the DRQ flag, we haven't paid too much attention, is going to remain "1" even after we have finished reading the first 512 bytes from the data buffer.
To get the next 512 bytes, we can simply read from the Data register 256 more times. Over and over again, until we have got all the data we requested. Checking the DRQ flag after each 512 byte data block have been transferred might be a good idea.

If you have followed this guide from the start, these steps for reading a single sector should be quite easy by now.

There's one MAJOR difference from reading the "Identify Device" and reading data stored on the HDD, and it has to do whit big Indians and small Indians. No.. Endians. Big Endian and Small Endian.
The byte order are reversed when reading data generated by the Microsoft file system.
If the HDD you are using, used to have a FAT file system on it, reading sector 0 would show some Italian looking ASCII if you mix up the byte order: "
daptrtioi natlb<0>erEor roldani gporetani gystsmeM<0>siisgno eparitgns". If you get it right, you should be able to read: "Invalid partition table<0>Error loading operating system<0>Missing operating system".

Also for this section, I have included two code samples. One for sending the data to the UART and one for saving it in EEPROM.

 

 
Writing data
And messing up the hard drive
Writing data to the hard drive involves much the same procedures as reading from it.
There are a few important differences though. The command we are going to use is the Write Sectors ($30) command, and instead of reading from the Data register, we'll write to it.

Another thing to note is that the data sent to the HDD is not saved until the entire data buffer is filled.

The Write Sectors command also requires the five parameters we used for the Read Sectors command.
Setting up the LBA addressing is done exactly the same way. The difference is in writing the data to the HDD.
For this, it is preferable to set up a data buffer in the µC before we start writing to the HDD.
This is not required, but it could lead to some unpredictable results if the write sequence was to be interrupted before it was finished.

In the code sample for the Write Sectors command we are not going to use any buffers. We are simply going to write the data "On the fly".
We'll write to LBA address $000FFFF just to get a feel for the LBA addressing concept.
Later, we can modify the code for the Read Sectors command to read from LBA $000FFFF to verify that it worked.
For the UART sample, the µC waits for any data on the UART, and writes it to the HDD buffer each time it get another byte, until the write buffer is filled. When the DRQ flag is cleared, the program will stop.
The EEPROM sample is a bit simpler. It waits for the device to become ready. Then it writes the content of the EEPROM onto the drive and stops.

A typical Write Sectors operation would follow this pattern:
-Wait for BSY flag to be cleared
-Write the number of sectors to be written into the Sector Count register
-Write LBA address Bit00:Bit07 to the Sector Number register
-Write LBA address Bit08:Bit15 to the Cylinder Low register
-Write LBA address Bit16:Bit23 to the Cylinder High register
-Write LBA address Bit27:Bit24 to the low nibble of Device/Head register, "1" to the LBA enable flag and "0" to the DEV flag.
-Send the Write Sectors command to the Command register
-Wait for BSY flag to be cleared
-Write 16 bits of data to the Data register
-Repeat the last two items 255 more times
-Check the DRQ flag to see if the HDD is expecting more data
-If DRQ is still "1", then write 512 more bytes to the Data register.