Reading disk faster in Amstrad CPC using firmware

When looking for a way to to read faster from Amstrad CPC floppy disks I had to take a decision: either I tried to read the disk not using CPC firmaware, what would most probably lead to incompatibilities with M4 and any other future external storage unit, or find a way to speed up reading using firmware.

When using firmware, we have two ways to read a file after opening using CAS_IN_OPEN: using CAS_IN_DIRECT or using CAS_IN_CHAR. First one is faster one, but implies reading the whole file at once, and so you may need a big buffer on CPC RAM. Second option is reading using CAS_IN_CHAR, which reads byte by byte, what is incredibly slow… or not.

The fact is CPC does not allow you to read blocks with a specific size, there is no CAS_IN_BLOCK, what makes a bit difficult reading a file in pieces, but fast, cause then the only way is CAS_IN_CHAR.

What does really CAS_IN_CHAR do?

Well, CAS_IN_CHAR is supposed to read a byte at a time, returning a byte in register A every time you call it, but CAS_IN_OPEN allways require a 2K buffer. Why? Cause CPC allways read in blocks of 2K.

What happens when you run the first CAS_IN_CHAR? Your computer actually reads first 2K of file (if avaliable) into that buffer, and second CAS_IN_CHAR does not read second byte from disk, it reads second byte from the buffer, and does that 2048 times until the buffer is empty, when next CAS_IN_CHAR will load another 2K in the buffer and return first byte again.

So, as you can imagine, for faster reading you should avoid calling CAS_IN_CHAR all the time, and read directly from buffer, specially if you have to move information from that buffer to some other point in large blocks using LDI, LDIR or whatever.

Ok, but if we read data from the buffer without calling CAS_IN_CHAR. How does CPC realize the buffer is empty and load next 2K?

Here comes the tricky part: when you open a file, you tell CPC where to create your 2K buffer, but also returns on HL a pointer to CPC header for that file. What firmare documentation doesn’t say is it also includes information about the buffer just below the header, so if you do this just after CAS_IN_OPEN you will have IY pointing to buffer control structure:

LD DE, -4

ADD HL, DE

PUSH HL

POP IY

At that point at address pointed by IY there a two byte value with the buffer address (the one we specified), and at IY+2, there is a two byte value with the current buffer position. So when the file has just been opened both values are equal, and when the buffer is over, value at IY+2 is value at IY+0 plus 2048.

Yes, you already know it, to tell firmware the buffer is over after reading it’s contents some other way you just have to change that value at IY+2 and make it be just as the value at IY, plus 2048.

Also, so CPC doesn’t get confused, you have to change the file size in the CPC header, so the firmware loads as much as possible. Do it this way:

XOR A
LD (IY+$17),A
LD (IY+$18), A

Once you do that , just call CAS_IN_CHAR again and another 2K will be loaded in the buffer.

Real life example

I’ve learnt and used this procedure while developing the CPC version of MALUVA:

Thanks to..

May thanks to rafag32, wilco, fer, augusto ruiz and many others who helped with this, either pointing to the final solution or giving ideas that finally bloomed into this.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store