Cold Water

Retro

Restauring an old iRiver iHP-120, Part 2

In the first part, we have replaced the old Li-Po battery by a new one. In this part, we will replace the mechanical hard disk by a modern (and much larger) MicroSD card. Both parts are independent, this is, you could also replace the HDD but keep the old battery.

First of all, you need to install Rockbox on your player. This is an alternate firmware that will not only make your iRiver experience more enjoyable, but will also support larger hard disk sizes. The installation procedure is out of scope of this article, but Rockbox is providing a great installer tool that does most of the work for you.

You must install Rockbox before replacing the harddisk. The reason is that the original iRiver firmware won't boot from larger harddisks, but you need a running player to install a patched Rockbox bootloader via firmware update.

Your shopping list:

  • Toshiba 50-pin to CF card adapter – I use an HXSP-09CF69 adapter. I have tried an Adaptare 48012 adapater first, but always got ATA errors with that one. The adapter must be set to "Master" mode, usually by a switch or a jumper.
  • CF card to MicroSD card adapter – I use a DeLock adapter, but any other brand should work as well. You can also just use a CF memory card, but the combination of MicroSD cards and CF card adapaters is cheaper than CF cards of equal size.
  • MicroSD card – I use a 128 GB SanDisk Ultra MicroSDXC card, but any other brand should work as well. The card speed is not really a factor here. I couldn't find a maximum size that is supported by Rockbox, so maybe even larger cards will work.

As there are a lot of different adapters and MicroSD cards on the market, there is no guarantee that your combination will work. Remember that you use parts that have been totally utopian back in 2003. Also be warned that your hardware can be damaged by this modification. You do it at your own risk.

Before you move on, make sure that Rockbox is properly installed and started. Then connect your player to your computer via USB, and backup the .rockbox directory, and any other stuff on the old harddisk that you want to keep.

Also, please read the entire article first, before you start with the modification.

Prepare the MicroSD card on your PC. It must be FAT32 formatted and should be named like your player model (e.g. IHP-120). Copy the .rockbox directory from your backup to the MicroSD card. And while you're at it, use the chance and copy your music collection to the card. The access speed will be much slower once the card is in your player.

Now turn off your player, and open it again (as described in part 1). You only need to remove the back cover this time. No more disassembling is necessary.

Remove the harddisk by gently lifting it and pulling it out of the connector. Now try to insert the 50-pin adapter. For many adapters, a plastic nose on the connector will keep you from inserting it, so you may need to remove protruding parts of the adapter with a file.

It is normal that the connector has more holes than the header has pins. Just make sure that the position marked as "PIN 1" is properly aligned with the first pin of the header. This is what it looks like when the adapter and the CF card is correctly mounted.

The next step is important. The adapter might have a jumper for mode selection. If it sticks out, it may punctuate the battery when the casing is closed later, so bend it away (like in the photo) or just unsolder it and replace it with a wire bridge.

Now make sure that the CF card stays in place and won't touch the PCB even when the player is carried around. You can use some isolating tape, or a silicone mat cut to size. I constructed a small 3D printed piece that fills the space and keeps the CF card from loosening. You can put back the original silicone HDD frame on top of that, to keep the construction from vibrating or touching the battery.

It is a good idea to do a short test drive of your modified player now. It should boot up and start Rockbox. In the Rockbox file manager, you should see all the files on your MicroSD card.

You can close your player now. Be careful and don't use force if the back cover cannot be closed, but locate and remove the obstacle. Remember that the Li-Po battery on the back cover must not be damaged or punctuated.

Enjoy your new retro mp3 player!

Troubleshooting

If you should get ATA or "check HDD" errors, it can have a lot of different causes:

  • Make sure that your MicroSD card has a single primary partition, which must be FAT32 formatted. Other file systems (e.g. FAT16, NTFS) are not supported. Try a different formatter tool.
  • Check all the connections. Is the adapter properly aligned and connected to the header? Is it switched to "Master" mode? Is a header pin bent or broken? (A few pins may be a bit longer or shorter than the other pins though, that's normal.)
  • Remove the adapter and insert the original HDD. If your player still shows an error, the daugherboard may have been dislocated. Gently press it to the main board and try again.
  • Your combination of adapters and MicroSD card brands may be incompatible. Try to boot from a real CF memory card. Try other adapter or MicroSD card brands.

If you happen to have an iHP with a broken original hard disk, you can start with the replacement right away, but use a FAT32 formatted 16 GB or 32 GB MicroSD card first. The original firmware should start in this configuration, and allow you to install the Rockbox firmware, but this is untested. After the Rockbox firmware has been installed, you can use larger MicroSD cards.

Final words…

  • You do this modification at your own risk. It might not work, damage your hardware, and turn out to be an utter waste of money.
  • I cannot help you if your modification won't boot or won't run stable. I have already said all I know about it in this article. Maybe you can find help in the Rockbox forum.
  • I cannot print the 3D printed part for you. The stl file is available for free and can be printed by commercial print services.
Restauring an old iRiver iHP-120, Part 1

In a time before smartphones, people used so called "digital music players" for portable music. One of them was the iRiver iHP-100 series, which came to the market in October 2003. It had up to 40 GB of storage, which was really a lot these days. It had a playback time of up to 16 hours. It had a remote control with a separate display. And it is the only pocket-size player I know that is also equipped with an optical line-in and line-out.

I got my player in 2004, and I used it for many years, until the hard disk started to show first signs of failing. Then I stored it away to save it for "special occasions" that never came. Many years later, I did not dare to charge it again, as I distrust over-aged Li-Po batteries that have been discharged for a long time. So my player became a Sleeping Beauty, waiting for the day it would be rediscovered and properly restored. The day was now.

In this first part, I will replace the original battery. In a second part, I will replace the 20 GB hard disk with a modern 128 GB MicroSD card, which is a lot more than the size of my entire music collection. After more than 16 years, it will be a modern portable music player again. (Well, sort of… I know it's still inferior to a smartphone.)

Before we start: Li-Po batteries are delicate. A damaged battery can cause severe damage to your home and your health. Please be very careful. If you're not confident enough for the operation, please ask someone for help.

The player is opened by removing the eight screws from the top and the bottom cap with a T6 screwdriver. The caps are glued in place, but can be pulled off with a bit of force. After that, the back cover can just be lifted off. The attached battery cable is very short, so be careful when lifting.

This is a photo of the inside. To the left is the battery pack that we are going to replace. To the right, we see the 1.8" HDD. Yes, the iHP uses an actual hard disk, with spinning platters and arms and all. In part 2, it will be replaced by a MicroSD card.

The battery connector is on the other side of the PCB, so we have to disassemble more. First we remove the HDD, it just needs to be gently lifted and then pulled out of the connector. There is a screw on each of the two side panels, they need to be removed as well. Then we remove all visible screws on the PCB.

The display frame is glued to the front cover, so we need to use a bit of force to remove the PCB. Be careful, the display is very sensitive to scratches. Also we don't want to have hairs and dust caught between the display and the front cover when we reassemble the device, so make sure you work in a clean and dust-free room.

Now we can disconnect the battery from the main PCB. Sadly, the power connector is in the way, so we need to twiddle with the connector and use a bit of force to get it removed. If you use a screwdriver, take care not to slip off and damage the PCB. Also, take care not short circuit the battery cable.

In the next step, we can remove the old battery pack. It is glued to the back cover, so we need a lever tool (e.g. a plastic opening tool) and some patience to gently remove it.

Be very careful when you remove the old battery pack. Do not use blades or pointy tools, and do not use force. The battery pack may burn if bent, damaged, or punctured.

You got the old battery removed now? Please don't just throw it away, but make sure it is properly recycled.

Before we insert the new battery, we should have a look at the cable first. On my replacement, the cable was considerably longer than the original one, so I decided to align it with the other corner of the back cover. If your cable is shorter, or if you are not sure, use the same corner as the original battery. In any case make sure that the cable is at the bottom edge of the back cover. If there is some of the glue tape left, it will firmly hold the new battery in its place.

If you think it was difficult to disconnect the old battery, you will find out that it is even more difficult to connect the new one. Check that the polarity of the connector is correct, the black wire must be closer to the USB connector than the red wire. Take care not to cut or break the wires while inserting the connector. If there is absolutely no way to push the connector into the socket, you need to remove the USB daughterboard. It can be unplugged after unsoldering the wires on its four corners.

I was lucky. After a few attempts and some frustration, I finally got the new battery connected.

When reassembling, make sure the battery cable is correctly routed like in the next photo. It must not be pinched anywhere. Now the PCB can be placed back onto the top cover again.

This is the right moment to check if there are visible dust particles or hairs caught between the display and the front cover. If so, use a photo lens brush to gently brush them away. Do not use a cloth, as it may cause tiny scratches.

Now close the bottom cover for a test. The battery cable should fit properly and should be tension-free.

After that, you can reassemble the device in the opposite order. Congratulations, you have given a new life to this amazing piece of hardware!

In the next part, we will replace the HDD with a MicroSD card. It will not just conserve some battery power, make your player faster and keep it cooler, but also greatly extend hard disk space for your music.

R Tape loading error, Part 2

In the first part I showed how the Sinclair ZX Spectrum stored data on tape. This second part explains what is stored, and what causes a tape loading error.

The ZX Spectrum BASIC offers a SAVE command for saving all kind of data. It can be used to save a BASIC program, variable arrays, but also arbitrary parts of memory. These files are always saved in two separate blocks. The first block is called header. It contains the file name, data type, and other meta information. The second block follows about a second later and contains the data itself.

The internal structure of each block is identical. The first byte distinguishes between header ($00) and data blocks ($FF). The final byte is a parity checksum. Everything between these two bytes is the payload.

A header block always contains a payload of 17 bytes. The first byte identifies the file type, followed by the file name (10 characters), followed by the length of the data block, and closed by two optional parameters that have different meanings depending on the file type. The length and the two parameters consume two bytes each, with the lower byte coming first because the Z80 CPU is little endian.

This is an example header block of a screenshot:

00$00 = Header
0003$03 = Binary file (Code or SCREEN$)
0153S
0268h
0372r
0465e
0564d
062E.
077Az
086Fo
096En
1065e
11001BLength: 6912 bytes ($1B00)
130040Parameter 1, here: starting address ($4000)
150000Parameter 2, here: unused
20Parity

A screenshot is actually just a memory dump that starts at address $4000 (which is the starting address of the screen buffer) and is exactly 6912 bytes long (the ZX Spectrum has a resolution of 256×192 monochrome pixels plus 32×24 bytes color attributes, giving a screen buffer size of 6912 bytes).

For other file types, the two optional parameters have different meanings. For example, a BASIC program file stores the line number to start at after loading.

The final byte is the parity. It is used for error detection, and computed just by XOR-ing all the bytes that have been read. The result must be $00, otherwise a "R Tape loading error" is reported.

This kind of error detection is rather weak. Due to the nature of the XOR operation, two wrongs give a right. This means that when the block contains an even number of bad bits at the same position, they will be undetected. It is also not possible to correct reading errors, as the XOR operation only allows to identify the position of the bad bit, but not the actual byte that contained the error. More sophisticated error correction algorithms would have slowed down the loading process, though.

The parity is computed as a final step, after all the bytes have been read from the block on tape. For that reason, the loader can only decide at the end of the recording whether the loading was successful or not.

But then, why does the tape loading error sometimes appear while the block is still loading? Well, in the first part I have explained that the loading routine just reads an unknown number of bytes. It ends when waiting for a pulse change took to long. Now, if there is an audio gap on tape, the signal seems to end just in the middle of the block. It is then very likely that the parity checksum is wrong because there are still bytes missing.

Some simple copy protections made use of the way the Spectrum loads data from tape. A very common way were “headerless” files, where the header block was left out and only the data block was recorded on tape. The BASIC LOAD command was unable to read those files because of the missing header.

R Tape loading error

In the early time of home computers, at the beginning of the 1980's, hard disks and even floppy disks were too expensive for home use. The cheapest way for storing large amounts of data was the cassette tape. Cassettes and tape recorders were affordable and available in almost any household.

In this blog article, I'm going to explain how the Sinclair ZX Spectrum stored programs on cassette tapes. Other home computers of that time, like the Commodore 64 or Amstrad CPC, worked in a similar fashion.

Cassette tapes were designed to store audio signals like voice or music, so the inventors of the home computers had to find a way to convert data to audio signals. The easiest way is to serialize the data to a bit stream of 1's and 0's, and generate a long rectangular wave cycle for "1" and a short rectangular wave cycle for "0". This is what the ZX Spectrum actually does!

A short wave cycle is generated by giving power to the audio output for 855 so called T-states, and then turning off the power for another 855 T-states. A "T-state" is the time of a single clock pulse of the Z80-A CPU. As the CPU of a classic ZX Spectrum is clocked with 3.5 MHz, a T-state has a duration of 286 ns. The duration of a short wave cycle is thus 489 µs, giving an audio frequency of about 2,045 Hz. The long wave cycle is just twice as long.

Due to all kind of filters in the analog audio path, the rectangular signal is smoothed to a sinusoidal signal when played back. A Schmitt trigger inside the ZX Spectrum's hardware converts the audio signal back to a rectangular shape. Since the audio signal can have different amplitudes or could even be inverted, the hardware only cares for signal edges, not for levels. All that the loader routine now has to do is to measure the duration of the pulses, regenerate the bit stream, and put the bytes back together.

If you think that things cannot be that easy, you are right. 😄 The most difficult part for the loader is to find the start of the bit stream. If it is off by only one cycle (or even just a pulse), all the bytes are shifted by one bit, and the result is useless. All kind of noise on the tape makes it impossible to just wait for the signal to start, though.

For this reason, the recording starts with a leader signal, followed by a sync wave cycle, followed by the bit stream itself. The leader signal is just a continuous wave with a pulse length of 2,168 T-states, giving an 806 Hz tone that is displayed by red and cyan border colors on the TV. The sync wave cycle is a pulse of 667 T-States "on", followed by 735 T-states "off". After that, the actual data stream begins, which is displayed in blue and yellow border colors. When the last bit was transmitted, the data stream just ends.

So when the ZX Spectrum loads a file from tape, it first waits for the 806 Hz leader signal. If it was detected for at least 317 ms, it waits for the sync pulses, then it starts reading the bit sequence until there is a timeout while waiting for the next pulse.

It is a very simple way to store data on tape. And still, it is surprisingly reliable. After 30 years, I could recover almost all files from my old cassette tapes. Some of them were of the cheapest brands I could get my hands on back in 1987.

The only disadvantage is that this method is very slow. With 489 µs for a "0" and 978 µs for a "1", saving just 48 KBytes of data can take up to 6 minutes, giving an average bit rate of 1,363 bps (yes, bits per second). If we were to save a single 3 MBytes mp3 file that way, it would take almost 5 hours (and 5 cassettes with 60 minutes recording time each).

Some commercial games used speed loaders and copy protections. Speed loaders just reduced the number of T-states for the pulses, which increased the bit rate. Some copy protections used a "clicking" leader tone, where the leader signal was interrupted before the minimal detection time of 317 ms was reached. The original loader routine could not synchronize to these kind of signals, so it was impossible to read those files into copy programs. Those protection measures could still be circumvented by copying directly from tape to tape, but this only worked a few times due to increasing audio noise.

In the next article, I will take a deeper look at the bit stream contents, and I will also explain where the dreaded "R Tape loading error" comes from.

Z80 Disassembler

Some days ago, I was adding a Z80 disassembler to my tzxtools. I could not find one for Python, so I decided to write my own. The result fits into a single Python source file. This article is the Making-of…

The Zilog Z80 is an 8 bit processor. This means that (almost) all instructions only consume 1 byte. For example, the instruction ret (return from subroutine) has C9 as byte representation. Some commands are followed by another byte (as a constant to be used, or a relative jump displacement) or another two bytes (as a 16 bit constant or absolute address). Some examples:

C9------retReturn from subroutine
3E23----ld a,$23Load constant $23 into A register
C33412--jp $1234Jump to address $1234

Note that for 16 bit constants, the bytes seem to be reversed in memory. This is because the Z80 is a so-called little endian CPU, where the lower byte comes first. Some other processor families (like the 68000 ) are big endian and store the higher word first.

So there are 256 instructions only, which makes it pretty easy to disassemble them. I used an array of 256 entries, where each entry contains the instruction of the respective byte as a string. For constants, I have used placeholders like "##" or "$". If such a placeholder is found in the instruction string after decoding, the appropriate number of bytes are fetched, and the placeholder is replaced by the value that was found.

If we were to write a disassembler for the 8080 CPU, we were done now. However, the Z80 has some extensions that need to be covered, namely two extended instruction sets and two index registers.

One set of extended instructions is selected by an $ED prefix, and contains rarely used instructions. The other instruction set is selected by a $CB prefix and has bit manipulation and some rotation instructions.

EDB0----ldirCopy BC bytes from HL to DE
ED4B7856ld bc,($5678)Loads value from address $5678 into BC register pair
CBC7----set 0,aSet bit 0 in A register

For the $ED prefix, I used a separate array for decoding the instructions. The $CB instructions follow a simple bit scheme, so the instructions could be decoded by a few lines of Python code.

The Z80 provides two index registers, called IX and IY. They are used when the instruction is prefixed with a $DD or $FD byte, respectively. These prefixes basically use the selected index register instead of the HL register pair for the current instruction. However, if the (HL) addressing mode is used, an additional byte sized offset is provided. The index registers can be combined with the $CB prefix, which can make things complicated.

E5------push hlPush HL to stack
DDE5----push ixPush IX to stack (same opcode E5, but now with DD prefix)
FDE5----push iyPush IY to stack (now with FD prefix)
FD2180FFld iy,$FF80Load $FF80 constant into IY register
DD7E09--ld a,(ix+9)Load value at address IX+9 to A register (offset is after opcode)
CBC6----set 0,(hl)Set bit 0 at address in HL
FDCB03C6set 0,(iy+3)Set bit 0 at address IY+3 (offset is before opcode)

When the disassembler detects a $DD or $FD prefix, it sets a respective ix or iy flag. Later, when the instruction is decoded, every occurance of HL is replaced by either IX or IY. If (HL) was found, another byte is fetched from the byte stream and used as index offset for (IX+dd) or (IY+dd).

There is one exception. The examples above show that the index offset is always found at the third byte. This means that when the index register is combined with a $CB prefix, the actual instruction is located after the index. This is a case that needed special treatment in my disassembler. If this combination is detected, then the index offset is fetched and stored before the instruction is decoded.

Phew, this was complicated. Now we’re able to disassemble the official instruction set of the Z80 CPU. But we’re not done yet. There are a number of undocumented instructions. The manufacturer Zilog never documented them, they are not quite useful, but they still work on almost any Z80 CPU and are actually in use.

Most of them are covered just by extending the instruction arrays. Additionally, the $DD or $FD prefixes do not only affect the HL register pair, but also just the H and L registers, giving IXH/IYH and IXL/IYL registers. This is covered by the instruction post processing. A very special case is the $CB prefix in combination with index registers, giving a whole bunch of new instructions that store the result of a bit operation in another register. This actually needed special treatment by a separate $CB prefix instruction decoder.

Finally, the ZX Spectrum Next is going to bring some new instructions like multiplication or ZX Spectrum hardware related stuff. They were again covered by extending the instruction arrays. The only exceptions are the push [const] instruction where the constant is stored as big endian, and the nextreg [reg],[val] instruction that is (as the only instruction) followed by two constants.

And that’s it. 😄 This is how to write a Z80 disassembler in a single afternoon.