Single Density Disks

Back in May of 2007 Philip Pemberton asked on the classic computer mailing list for some diagnostic information from single density floppy disks. He said he was building custom hardware to read floppy disks and needed some data.

He was specifically looking for raw Catweasel output. The testhist command from cw2dmk can record this information to a file.

This presented a opportunity for me. Though I didn’t own any single density disks I did own a computer that could create, read and write them. The CoCo hardware supported it, but I needed to modify the software to achieve it.

The Color Computer floppy disk card by Tandy/Radio Shack (and third party compatible cards) did have a density select register that it passed to the flopper disk controller. But that bit also controlled a second function in the card: the NMI enable flag. The card used the MPU’s NMI interrupt to signal the end of a disk operation. This, used in combination with a separate halt flag, allowed the CoCo’s read/write software kernel to be very compact and fast. Fast enough to allow the CoCo to access double density disk’s reliably despite the 0.89 Mhz clock speed of the MPU.

In double density/Halt/NMI mode the CoCo’s MPU would pass a byte to/from the floppy disk controller and this would cause it to trigger the halt line on the MPU. When the controller was ready for another byte it would release the MPU from its halt mode and another byte would be passed. This would continue until the end of the transfer when an NMI interrupt was triggered. This is what would stop the MPU’s data passing loop.

In single density mode the NMI functionality in the floppy card is disabled. But there should be sufficient time to poll the controller for the interrupt request signal. I was not sure how to write such a loop. After a few unsuccessful attempts I gave up. I had heard the Flex operating system started life using a single density disk format and decided to try to find an example single density read/write loop from that operating system. It turns out the Flex users group has a lot of information on-line and it was relatively easy to find the loop I was looking for. I discovered the software loop polled for both the interrupt request and data request signals. So even the halt functionality of the card wasn’t needed in single density mode.

So now it was time to write a patch for RSDOS to support single density. Since this was only an experiment I decided to only patch the lower levels of RSDOS: DSKINI and DSKCON. This is the code that actually does data transfer. RSDOS also contains higher level code for reading/writing files (both executable and data). I did not modify them. Trying to read and write files after this patch will cause errors.

First I had to decide on the on disk sector layout. I choose 9 256 byte sectors, numbered 1 thru 9, on each track for 35 tracks. The I wrote the following program which patches RSDOS version 1.1:

	org	$7000
* patch DSKINI for SD

Change the loop constructs in DSKINI to end at sector 9 instead of 18.

	lda	#$09
	sta	$d5c5
	sta	$d5c9
	sta	$d5d2
	sta	$d67b
	sta	$d6bc

Change track start from 32 bytes of 0x4E to 32 bytes of 0xFF

	lda	#$ff
	sta	$d696

Change track end from 200 bytes of 0x4E to 200 bytes of 0xFF

	sta	$d6c1

New track table ends with 8 blocks not 9.

	lda	#$08
	sta	$d6b5

Insert the address of the new track table into DSKINI.

	ldd	#nt
	std	$d6a4

Change program to jump to our new DSKINI write routine.

	lda	#$7e
	sta	$d646
	ldd	#write
	std	$d647

Now we patch DSKCON.

OR in single density (instead of double density) mode

* patch DSKCON for SD
	lda	#$0
	sta	$d775

Don’t turn on halt mode

	lda	#$00
	sta	$d840

Patch DSKCON to jump to new write and read loops

	lda	#$7e
	sta	$d86b
	sta	$d881
	ldd	#wdsk
	std	$d86c
	ldd	#rdsk
	std	$d882
	rts

New writing routine for DSKINI

write	anda	#$4f
	sta	$ff40	Turn off halting, double density and NMI
w0	ldb	,x+	Read byte from track buffer
w1	lda	$ff48	Get FDC status
	rora		Roll busy bit into carry flag
	bcc	w3	If not busy, then branch out of loop
	rora		Roll data request bit into carry flag
	bcs	w2	If ready for data, branch to write instruction
	bra	w1	Branch back to test status again.
w2	stb	,y	Write data to FDC
	bra	w0	Branch back to get new byte
w3	jmp	$d64f done, jmp back to DSKINI

New writing routine for DSKCON

wdsk	ldb	,x+	Load byte from transfer buffer
	stb	$ff4b	Write it to FDC
wd1	lda	,u	Get status
	rora		Roll busy bit into carry flag
	lbcc	$d88b	If not busy, branch to end loop
	rora		Roll data request bit into carry flag
	bcs	wdsk	If data requested, get new byte from transfer buffer
	bra	wd1 Brach to check status again

New reading routine for DSKCON

rdsk	ldb	$ff4b	Load byte from FDC
	stb	,x+	Store byte to transfer buffer
rd1	lda	,u	Get status
	rora		Roll busy bit into carry flag
	lbcc	$d88b	If not busy, branch to end loop
	rora		Roll data request bit into carry flag
	bcs	rdsk	If data requested, get new byte from transfer buffer
	bra	rd1	Brach to check status again

New disk format table

nt	fcb 3, $0	Sync field
	fcb 3, $0
	fcb 1, $FE	ID Address Mark
* Track, side and sector numbers are inserted here
	fcb 1, 1	Sector Size (256 byte sectors)
	fcb 1, $f7	CRC Request
	fcb 11, $ff	GAP II (Post-ID Gap)
	fcb 6, $0	Sync Field
	fcb 1, $fb	Data Address Mark (AM2)
	fcb 0, $ff	Data Field (256 bytes)
	fcb 1, $f7	CRC Request
	fcb 27, $ff	Gap III (Post Gap Data)
	end

Running the above patch will allow you to use BASIC’s DSKINI command to format a single density disk. And use BASIC’s DSKI$ and DSKO$ to read and write sectors.