Skip to content

µPD765A Disc Controller – Primer

Back in the 80’s, Disk protection systems were starting to evolve at a rapid rate, presumably due to the use of IBM PC’s, Amiga/Atari STs on the market that could do a lot more with the controller than had been implemented on the target hardware. These machines were beasts in comparison, having more memory and could cross compile to multiple target machines with ease, making development of games and tools more efficient and streamlined. The PDS (Programmers Development System) was an expensive bit of kit, way out of my price range (Starting at around £2K). https://retro-hardware.com/2019/05/29/programmers-development-system-pds-by-andy-glaister/

Whilst using documented and undocumented AMSDOS ROM commands got you so far, understanding and fully controlling the µPD765A controller became necessary.

A friend back then “Dave” had access to all sorts of hardware documentation. One morning on his way to work, he dropped off a bundle of documents including hardware/circuit diagrams for the Amstrad CPC computer range, service documents and the µPD765A specification. Stuff I couldn’t get hold of myself.

I set to work reading the documentation, learning about Microelectronics at school helped enormously. From about the age 10, I was no stranger to a soldering iron and PCBs. The article below is the result of my own work back in the late 80s, using code from my original three inch disks. This will serve as a simple primer before I write up how I went about developing hacks for old school disk protections systems, some fo which went into commercial tools with Goldmark’s Nirvana+ and Nemesis Bonzo Pack for Sector #48, 6K Sectors and more.

Please Note

This article is a Primer only, to cover the basics. I will be following up with write ups of other FDC Commands such as Track Read which I took advantage of to create a Disk Protection System JaceLock, and more.

My Git Hub Repo containing my legacy disks and source code (Still to be sorted), is found here: https://github.com/muckypaws/AmstradCPC

The source code for this write up can be found: –https://github.com/muckypaws/AmstradCPC/blob/master/Assembly/DDFDC/DDFDC_MK2.asm

Thank you for taking the time to read my Blog.

If you would like to support me and my work, please check out my Online Store: www.muckypawslabs.com alternatively…

We all know developers and coders run on Coffee…

Choose an amount

£2.00
£3.00
£5.00

Your contribution is appreciated.

Donate

What do you need?

The article is technical in nature, and may be rough on the novice or beginner starting out to understand the history behind this… That said, you’re here so, please stick with it, it will make sense (Eventually), and I’m not the best at explaining tech stuff in layman terms, therefore constructive feedback is always welcome, or share your own experiences when coding for this tech. We all approach problems from differing angles.

  • You’ll need some Z80 Assembler Knowledge and Skills.
  • A development Environment
    • I use ADAM IDE for the Amstrad CPC, though JavaCPC contains a built in developer environment for assembly and disassembly to practice.
  • The µPD765A Specification.

AMSDOS ROM

The AMSDOS ROM, was quite advanced for the home computer market. Under the covers the developers enabled you to achieve a lot in terms of reading, writing and manipulating disk data. In fact, most of what I present here can be achieved with the correct manipulation of the eXtended Disk Parameter Block (XPDB), either used undocumented routines built into the ROM, or created their own. DiscSys Protection system, is a prime example of this. The Author used their indepth knowledge of the AMSDOS Rom to create a Disk that couldn’t be copied using the existing CP/M Tools of the time. It had the advantage of code re-use, reducing the footprint of the loader code required.

I wanted more control. Learning to interface directly with the controller had advantages achieving more, but also a steeper learning curve if you’re not going to use what the system developers already provided you.

First Steps

There are a number of stages to enable access to the Disk and the Disk Controller, some of which needs carefully calculated timing. In addition some logical steps need to be taken. Going forward, I’ll break some of these down. Most operations require the Disk Motor to be spun up, running at the correct speed, Some Operation/Command to execute, and Stop the Disk Motor when all operations are complete.

I’ll go through the following :-

MOTOR ON/MOTOR OFF

The Amstrad CPC implements access to the Disk Controller via Ports #FA7E and #FB7E.

Switching the Drive Motor ON/OFF is simply a case of using these BASIC commands :-

OUT &FA7E,1 : REM Switch the Drive Motor ON
OUT &FA7E,0 : REM Switch the Drive Motor OFF

Try it and see for yourself.

In assembler, i created the following in my DDFDC Library. It does a bit extra like preserve registers and adds a delay to wait for the drive to start spinning up to speed. You can achieve the same in much fewer bytes as you’ll see further down.

Motor On/Off Code Snippet

motoron
         DI                             ; All Reg. Preserved
         PUSH hl
         PUSH bc
         PUSH af
         LD   bc,#fa7e
         LD   a,1
         OUT  (c),a
         LD   hl,0
         LD   b,2
motoron1                                ; Pause Loop To Allow Motor To Pick Up 
         DEC  hl
         LD   a,h
         OR   l
         JR   nz,motoron1
         DJNZ motoron1
         POP  af
         POP  bc
         POP  hl
         RET                            ; Exit All Reg. Preserved
motoroff
         PUSH bc
         PUSH af                        ; All Reg. Preserved
         LD   bc,#fa7e
         XOR  a
         OUT  (c),a
         POP  af
         POP  bc
         RET                            ; Quit All Reg. Intact

A few more commands than BASIC, you can shorten it to :-

motoron
        LD A,1
motorctl
        LD BC,#FA7E
        OUT (C),A
        RET
motoroff
        XOR A
        JR motorctl

The first motor on routine sets a delay loop to give the drive to spin up to speed. Amstrad CPC Drives are expected to spin at approximately 300 rpm, though the controller IC allows some variable tolerance of around +/- 10%.

That’s the easy bit!

Drive Command Phases

The µPD765A Controller typically uses three phases of command to complete command execution. There are some commands that don’t have a results phase, or an execution phase. These are shown in the Specification, but I’ll cover the common scenario’s here.

  • Command Phase
  • Execution Phase
    • Read Data
    • Write Data
  • Results Phase

These phases are documented in the µPD765A Controller specification, though they’re not the most user friendly explanations… looking back I now realise how technical these documents are… As a teenager they seemed straight forward, nearly 40 years later I’m having to re-read/re-learn all over again.

Before going into each of the commands, I’ll describe the code I write in the 80’s to implement the interface to the controller.

Command Phase

Each of the phases of the controller are time critical, requiring 13µs pause between each command and parameter issued. You can jump to the code below which shows how I approached the problem.

Port #FB7E is used to Read the Main Register of the Disk Controller. #FB7F is used to READ/WRITE data to the Controller. To make it easier, The LD DE,command_table is a simple pointer to a table of commands and parameters.

The first byte in that table will be the number of commands needed to send. The flowchart below shows what’s going on in the code. For example, the Recalibrate command requires two parameters to be passed to the controller, #07, #00, where #07 is the command and #00 is the drive number and head we want to recalibrate. The table contains a length byte (#02) at the start, it’s used to tell our code we’re passing two bytes of data.

;
;                 **** COMMAND DATA FOR RECALIBRATE ****
;
RECALCOM DEFB 2                         ; 2 Parameters
         DEFB 7                         ; Command Code For RECALIBRATE
         DEFB 0                         ; Which Drive

It’s probably easier to show the flow of the Command Phase to help understand the code.

Flow Chart of the FDC Command Send Sequence

We need to ensure the controller is ready to receive our commands. To achieve this, we read the Main Status Register using A = INP(#FB7E), using the IN A,(C) assembler command.

We test the value of Bit 7 and Bit 6 using a simple ADD A,A command.

ddfdc1                                  ; Is Drive Ready To Accept Command ?
         IN   a,(c)
         ADD  a,a
         JR   nc,ddfdc1
         JP   m,ddfdc1                  ; If Not Then Wait

Why? We need to look at the Main Status Register from the controller.

  • Bit 7 (RQM – Request for Master) is set to 1 when ready to receive data.
  • Bit 6 (DIO – Data Input/Output) is set to 0 when in READ mode, 1 if Write mode.

We could just test bit 7 and bit 6 individually, or compare to a value #80… but remember we’re in tight time critical code, and this is a more elegant solution.

ADD A,A is the same as A = A * 2, the Carry Flag will be Set if an overflow condition occurs. For example #80 or 128 results in the answer 256. The accumulator will become #00 and the Carry Flag will be Set to indicate there’s more to the answer. In addition, Bit 7 is also used to indicate a negative number.

If the data read from the Main Status Register has Bit 7 = 0, then Carry will not be set regardless of the other bits, so we continue reading until Bit 7 is set. I.e. any original value less than 128 of #80 when doubled can not be greater than 254.

#80 * 2 = #100, A = #00 and Carry is Set.

We need the Controller to be in Ready for READ Mode, (Ready to READ OUR COMMANDS – Transfer from Processor to Data Register) hence Bit 6 must be 0 or reset. The ADD A,A also checks this for us? How?

The JP M,ddfdc1 command. This means the S – Sign Register is set, meaning Bit 7 is now set. Bit 6 = #40, #40 * 2 = #80.

Check this for your self… By experimenting with the code below in your assembler and monitor program.

        ORG #A000
start
        ENT $
        LD A,#80
        ADD A,A
        RET NC                ; Carry Always Set
        LD A,#40
        ADD A,A
        RET M                ; Always return as A=#80 Sign is Set
        LD A,#C0
        ADD A,A
        JP M,start        ; Won't be reached without changing A above

If the main status register = #Cx (RQM and DIO) set, then both Carry and Minus will be set, we continue to loop until the Controller is ready to Receive our Commands. Clever eh? and all in six bytes.

Section showing the Main Status Register

Code Fragment for the FDC Command Phase

;
;                        **** DDFDC COMMAND PHASE ****
;                        DE = Command Table Data
;
ddfdccom                                ; DDFDC Command Phase
         LD   bc,#fb7e
         PUSH bc
         LD   a,(de)                    ; Get Number Of Parameters
ddfdc
         PUSH af                        ; Preserve Counter
         INC  de
ddfdc1                                  ; Is Drive Ready To Accept Command ?
         IN   a,(c)
         ADD  a,a
         JR   nc,ddfdc1
         JP   m,ddfdc1                  ; If Not Then Wait
         LD   a,(de)
         INC  c
         OUT  (c),a                     ; Give DDFDC Command @ Port #FB7F
         DEC  c
         LD   a,5
ddfdcp                                  ; Wait 13 uS
         DEC  a
         JR   nz,ddfdcp
         POP  af
         DEC  a
         JR   nz,ddfdc
         POP  bc                        ; On Return BC=#FB7E
         RET                            ; Quit

Execution Phase (Read/Write)

The command phase was fairly straight forward, the code simply waits for the state of the controller to be ready to receive our commands, then we send the command and the command parameters in sequence 13µs apart.

Depending on the FDC Command we’re invoking, the execution phase is either :-

  • READ Data From the Controller. CALL ddfdcexc
  • WRITE Data to the Controller. CALL ddfdcwri

We READ Data if we’re scanning Sector IDs, Sector/Track Data, Validating Data etc.

WRITE Mode would be for Formatting Tracks, Writing Sector information and so forth.

The code for the execution phase is fairly straight forward, we pass the Drive we want to access in the Accumulator (0 = Drive A, 1 = Drive B), we then OR this with #20 (The RQM Execution Mode Bit). And use Self-Modifying code to check that the Drive is still in execution phase for that drive later in the loop. HL is set to point to a location in memory where the data will be stored.

The loop simply checks the Drive status, and reads data from the PORT until the Controller signals there’s no more data to be read.

Execution Phase when READing Data FROM the Controller

Equally when WRITING Data to the Controller, the logic is similar except we send data pointed to in HL until the controller signals to stop. It’s the easier phase.

Execution Phase when WRITING Data TO from the Controller

Code Fragment for the FDC Execution Phase

;
ddfdcexc                                ; DDFDC Execution Phase - DATA IN
         LD   a,(drive)
         OR   #20
         LD   (ddfdexc2-1),a
ddfdcexd
         LD   bc,#fb7e
         IN   a,(c)
         CP   #c0
         JR   c,ddfdexc1
ddfdexc0
         INC  c                         ; Point To #FB7F - DATA REGISTER
         IN   a,(c)                     ; Get byte from port
         LD   (hl),a                    ; Store it
         DEC  c                         ; Restore Port To Main Status Reg.
         INC  hl                        ; HL+1
ddfdexc1
         IN   a,(c)
         JP   p,ddfdexc1                ; Drive Not Finished Output So Wait
         AND  #20                       ; Main Status Reg=Execution Phase Start
ddfdexc2
         JR   nz,ddfdexc0               ; If Not Finished Loop ddfdexc
         RET                            ; Else Quit
;
;              **** DDFDC EXECUTION PHASE DATA TO SYSTEM ****
;
ddfdcwri                                ; DDFDC Write Into Data Register
         LD   bc,#fb7e                  ; Point To MAIN STATUS REG
         LD   a,(drive)
         OR   #20
         LD   (ddfdcw3-1),a
         JR   ddfdcw2                   ; Wait Till DDFDC Ready.
ddfdcw1
         INC  c                         ; Point To Data Port
         LD   a,(hl)                    ; Get Byte To Place
         INC  hl                        ; HL+1
         OUT  (c),a                     ; Output To Port #FB7F
         DEC  c                         ; Restore Port
ddfdcw2
         IN   a,(c)
         JP   p,ddfdcw2                 ; If Drive Not Ready Loop ddfdcw2
         AND  #20
ddfdcw3
         JR   nz,ddfdcw1                ; Is All Output Finished ?
         RET                            ; Quit 
;

The Results Phase

Finally we enter the results phase. The controller will send data informing us of the success or otherwise of the command we attempted to execute.

The code should start looking familiar now, as we add Bit 5 to the Drive number to check for readiness of the controller.

We also check the controller is both ready and the the DIO bit is set, a simple compare is all that’s needed here.

The results are written to a buffer pointed to in HL.

LD A,Drive
LD HL, Results_Buffer
CALL ddfdcres

The results buffer will contain information on the Drive Registers ST0, ST1, ST2, Track Number, Drive, Sector Number and Sector Size though these bytes are defined in the Specification.

Flowchart of the FDC RESULTS Phase

Code Fragment for the FDC Results Phase

;
;                       **** DDFDC RESULTS PHASE ****
;
ddfdcres                                ; DDFDC Result Phase
         LD   a,(drive)
         OR   #10
         LD   (ddfdresq-1),a
ddfdcret
         IN   a,(c)
         CP   #c0                       ; Is DDFDC Ready ?
         JR   c,ddfdcret                ; If Not Wait
         INC  c
         IN   a,(c)                     ; Get Byte From DATA REG
         LD   (hl),a                    ; Store it
         DEC  c                         ; Restore Data Reg.
         INC  hl                        ; HL+1
         LD   a,5
ddfdresp                                ; Wait 13 uS
         DEC  a
         JR   nz,ddfdresp
         IN   a,(c)
         AND  #10                       ; Has Results Finished ?
ddfdresq
         JR   nz,ddfdcret               ; If Not Loop ddfdcres
         RET                            ; Quit 

Putting it all together.

Now you have the assembly code basics for accessing the controller, it’s time to pull it all together and do something useful…

We need to implement some of the FDC Commands. Whilst many emulators do an awesome job of implementing Support, if you can try this on real hardware you will see some subtle differences. Mainly in the way the DSK File format masks some of the intricacies of Disk Formats and Status Codes, they will be predictable, physical and ageing disks won’t…

Recalibrate the Drive

Not quite what you may think here, this command doesn’t test speed or calibration of the stepper motors etc, it’s basically a way for the controller to know it’s start position after powering on your machine. Where is the drive head located now? Since the controller has no memory, it has no way of knowing which track it’s currently aligned to or if it moved after power off.

The recalibrate commands seeks to move the Drive Head to Track 0. A micro switch signals the controller the head is in the home position. From here on in, the drive will operate without the need to execute this command.

When Recalibrating the Drive Head, this is associated with the Sense Drive Status Command to ensure the operation is complete.

In likelihood, AMSDOS will have completed this task the moment you accessed a Disk in your Amstrad.

In all your disk operations you need to move the drive head up and down the disk to point to the correct track. There are a few steps you need to complete this.

  • Set the Drive ID (0 – Drive A, 1 – Drive B)
  • Set the Track you want to move to.

I’ve coded my library as follows.

  • Is requested Track same as Current Track?
    • If Yes, Quit no work to do.
  • If Requested Track = 0?
    • If Yes, issue the Recalibrate Command.
  • Else Send SEEK Command
  • Wait until the Sense Interrupt shows the operation is complete.
Flow Chart of the SEEK Command

Below are the commands in easier to read format, compared to the PDF that’s available.

RECALIBRATE Command

The Recalibration command doesn’t have an execution or status command phase.

OffsetCommandDefinition
0#07Recalibrate Command
1#00The Drive number to Recalibrate

SEEK Track Command

Whenever moving the Drive head to a different Track, you must always execute the SEEK command.

OffsetCommandDefinition
0#0FSeek Command
1#00Drive Number, 0 = A, 1 = B, Bit 2 can be set if you have dual heads.
2#00Destination Track, Usually 0 – 41, anything higher than this will likely damage the 3″ Disk Drive. If you have a 3.5″ Disk Connected, you can access higher tracks.

SENSE INTERRUPT STATUS COMMAND

OffsetCommandDefinition
0#08Sense Interrupt Status Command
OffsetResponseDefinition
0ST 0 Status Register 0 Data – See Appendix
1PCN – Present Cylinder NumberWhat is the current Track we’re on?

SENSE DRIVE STATUS COMMAND

OffsetCommandDefinition
0#04Sense Drive Status Command
1#00Drive Number, 0 = A, 1 = B, Bit 2 can be set if you have dual heads.
OffsetResponseDefinition
0xxStatus Register 3 Data – See Appendix

SEEK Track Code Usage

The snippet below shows how to invoke the movtrak command.

D = Track Number (0-41)
E = Drive Number (0 = A, 1 = B)

If Track 0 is requested the code will automatically perform a recalibration.

LD D,trackNumber
LD E,Drive
CALL movtrak

Seek, Recalibrate and Sense Drive Commands Code Snippet

;
;                        **** Move Drive Head To Track T ****
;
drivicl  DEFB 0
movtrak                                 ; On Entry D = Destination Track
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl                        ; Preserve Registers
         LD   a,e
         OR   #20
         LD   (drivicl),a
         CALL senseint                  ; Get Current Track Position
         LD   a,(sense+1)
         SUB  d                         ; Is Destination Track Current Track ?
         JR   z,movtrake                ; If So Quit
         LD   a,d                       ; A = Track
         OR   a                         ; Is Destination Track 0
         JR   z,trak0                   ; If So Then Trak0
         CALL seek                      ; Give DDFDC SEEK Command
movtrak1                                ; Use This Since No DMA
         CALL senseint                  ; Has Drive Reached Destination ?
         LD   hl,drivicl
         LD   a,(sense)
         CP   (hl)
         JR   nz,movtrak1               ; If Not Loop movtrak1
movtrake                                ; Quit Routine
         POP  hl
         POP  de
         POP  bc
         POP  af
         RET                            ; Preserved Registers
trak0
         CALL recalib                   ; Recalibrate (Move To Track 0)
trak01
         CALL senseds                   ; Call SENSE DRIVE STATUS
         LD   a,(hl)
         AND  %10000                    ; Has Drive Head Reached TRK 0
         JR   z,trak01                  ; If Not Loop
         JR   movtrake                  ; Restore Regs & Quit
;
;                       **** SENSE DRIVE STATUS ****
;
senseds
         LD   a,(drive)
         LD   (SenseCde+2),a
         LD   de,SenseCde
         CALL ddfdccom
         LD   hl,sendst                 ; Pointer To Put Resultant Data
         CALL ddfdcres
         DEC  hl                        ; On Exit HL=Address Of Status Reg. 3
         RET                            ; Quit
;
;                  **** RECALIBRATE DRIVE HEAD (TRACK 0) ****
;
recalib                                 ; Recalibrate
         LD   de,RECALCOM
         LD   a,(drive)
         LD   (RECALCOM+2),a
         JP   ddfdccom
;
;                       **** SENSE INTERUPT STATUS ****
;
senseint                                ; Sense Interupt Status
         PUSH de                        ; Preserve DE
         LD   de,SENSE_INT
         CALL ddfdccom
         LD   hl,sense
         CALL ddfdcres                  ; Call & Quit
         POP  de
         RET  
;
;                   **** SEEK COMMAND MOVE TRACK HEAD ****
;
seek                                    ; SEEK Entry D=Track
         PUSH de
         LD   (Seek_DT+3),a
         LD   a,(drive)
         LD   (Seek_DT+2),a
         LD   de,Seek_DT
         CALL ddfdccom
         POP  de
         RET  
;
;            **** COMMAND DATA FOR SENSE DRIVE STATUS ****
;
SenseCde
         DEFB 2                         ; 2 Parameters
         DEFB 4                         ; Code For SENSE DRIVE STATUS
         DEFB 0                         ; Drive
;
;                   **** COMMAND DATA FOR SEEK ****
;
Seek_DT                                 ; Seek Codes For Command
         DEFB 3                         ; 3 Parameters
         DEFB 15                        ; Command For Seek
         DEFB 0                         ; Drive
         DEFB 0                         ; Destination Track
;
;            **** COMMAND DATA FOR SENSE INTERUPT STATUS ****
;
SENSE_INT DEFB 1                         ; One Parameter
         DEFB 8                         ; Command Code For SENSE INTERRUPT STATE

Reading Sectors

Now we have the “Basics” out of the way, you’ll be wanting to do something useful like Reading Data …

Thankfully this and other commands are straight forward.

The table located at readcomd contains all the information needed to read a sector, you need to set these parameters accordingly, helper function patchrwd attempts to do this for you.

READ Command

OffsetCommandDefinition
0#46Read Sector Command from MFM Disk
1#00Drive Number, 0 = A, 1 = B, Bit 2 can be set if you have dual heads.
2#00 – #29Cylinder – Track Number to Read
3#00 – #01Head Number, 0 = Drive A, 1 = Drive B
4#xxSector ID to Read, i.e. #C1 for First Sector of DATA Format
5#00-#05N – Sector Size 2^(7+n)
6#xxLast Sector ID to Read Sequentially. For example you could set it up to Read all sectors from #C4 to #C7
7#xxGAP#3 Length
8#FFData Length, if less than 255 bytes, change this to the amount you want to Read.
OffsetResponseDefinition
0ST 0Status Register 0
1ST 1Status Register 1
2ST 2Status Register 2
3CCylinder/Track Number
4HHead Number
5RSector Number Read
6NSize of Sector Read 2^(7+N)

It’s important to check the status of the registers after a read operation to check if completed successfully.

The command response is stored in readcomr, check the status to ensure all has gone as expected.

  • Set HL to point to a memory location to load the sector.
  • E = Drive Number
  • D = Track Number
  • C = Sector ID to Read
  • CALL readsect

Read Sector Code Fragment

readsect                                ; ENTRY HL=BUFFER E=DRIVE D=TRK C=SECTR
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl                        ; Preserve Registers
         CALL movtrak                   ; Move To Appropriate Track
         PUSH hl
         LD   hl,readcomd+2
         CALL patchrwd                  ; Patch Read Data
         LD   de,readcomd
         CALL ddfdccom                  ; Set Up Command
         POP  hl
         CALL ddfdcexc                  ; Get Bytes From EXECUTION PHASE
         LD   hl,readcomr
         CALL ddfdcres                  ; Get Result From Read Command
         POP  hl
         POP  de
         POP  bc
         POP  af
         RET  
;
;             **** PATCH READ WRITE DATA FOR READ/WRITE COMMAND ****
;
patchrwd                                ; Patch Read Write Data
         PUSH hl
         PUSH af
         LD   a,e                       ; A=Drive
         LD   (hl),a
         INC  hl
         INC  hl                        ; Point HL To Head Number
         LD   (hl),a                    ; Store Head Number
         DEC  hl                        ; Point To Track Data
         LD   a,d                       ; A = Track
         LD   (hl),a                    ; Store Track Number
         INC  hl
         INC  hl                        ; Point To Sector
         LD   a,c                       ; A = Sector
         LD   (hl),a                    ; Store It
         INC  hl
         INC  hl                        ; Point To End Of Track
         LD   (hl),a                    ; Store Sector
         DEC  hl                        ; Point To Sector Size
         LD   a,(sectsize)              ; Get Sector Size From XDPB
         LD   (hl),a                    ; Store It
         INC  hl
         INC  hl                        ; Point To Gap Length
         LD   a,(gap3rw)                ; A = Gap #3 For READ/WRITE
         LD   (hl),a                    ; Store It
         INC  hl                        ; Point To Data Length
         LD   a,(dataleng)              ; A=Sector Length
         LD   (hl),a                    ; Store It
         POP  af
         POP  hl
         RET  
;
;                 **** COMMAND DATA FOR READ SECTOR ****
;
readcomd                                ; Read Command Data
         DEFB 9                         ; 9 Parameters
         DEFB #46                       ; READ DATA Command Alter To #4C Del.
         DEFB 0                         ; Drive
         DEFB 0                         ; Track
         DEFB 0                         ; Head Number
         DEFB #C1                       ; Sector To Read
         DEFB 2                         ; Number Of Data Bytes Per Sector
         DEFB #C1                         ; End Of Track
         DEFB 0                         ; Gap #3 - Generally #2a
         DEFB #FF                       ; Data Length - Sect Size < 256
;
;        **** READ DATA RESULTANT BYTES FROM RESULT PHASE ****
;
readcomr                                ; Read Command Result Table
         DEFB 0                         ; Status Register 0
         DEFB 0                         ; Status Register 1
         DEFB 0                         ; Status Register 2
         DEFB 0                         ; Track Number
         DEFB 0                         ; Head Number
         DEFB 0                         ; Sector Number
         DEFB 0                         ; Sector Size

Writing Sectors

If you followed through the READ Sector code above you won’t be surprised how similar these routines are.

The command response is stored in writcomr, check the status to ensure all has gone as expected.

  • Set HL to point to the memory location of the data to write.
  • E = Drive Number
  • D = Track Number
  • C = Sector ID to Write
  • CALL writsect

WRITE DATA COMMAND

OffsetCommandDefinition
0#45Write Sector Command from MFM Disk
1#00Drive Number, 0 = A, 1 = B, Bit 2 can be set if you have dual heads.
2#00 – #29Cylinder – Track Number to Read
3#00 – #01Head Number, 0 = Drive A, 1 = Drive B
4#xxSector ID to Write, i.e. #C1 for First Sector of DATA Format
5#00-#05N – Sector Size 2^(7+n)
6#xxLast Sector ID to Write Sequentially. For example you could set it up to Write all sectors from #C4 to #C7
7#xxGAP#3 Length
8#FFData Length, if less than 255 bytes, change this to the amount you want to write.
OffsetResponseDefinition
0ST 0Status Register 0
1ST 1Status Register 1
2ST 2Status Register 2
3CCylinder/Track Number
4HHead Number
5RSector Number Written
6NSize of Sector Written 2^(7+N)

Write Data to Sector Code Snippet

;
;                     **** WRITE DATA To TRACK & SECTOR ****
;
writsect                                ; Entry HL=DATA ADDR D=TRK E=DRV C=SECT
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl                        ; Preserve Registers
         CALL movtrak                   ; Move To Track D
         PUSH hl
         LD   hl,writcomd+2             ; Point To Command DATA Table
         CALL patchrwd                  ; Patch Write Data For TRACK & SECTOR
         LD   de,writcomd               ; Point To Command Data
         CALL ddfdccom                  ; Start COMMAND PHASE
         POP  hl                        ; Restore HL = DATA To Write
         CALL ddfdcwri                  ; Write It To Disk
         LD   hl,writcomr               ; HL=Write Command Return
         CALL ddfdcres                  ; Call Up Result Phase
         POP  hl
         POP  de
         POP  bc
         POP  af                        ; Restore Registers
         RET                            ; Exit Routine
;
;             **** COMMAND CODE FOR DDFDC WRITE SECTOR ****
;
writcomd                                ; Write Command Data
         DEFB 9                         ; 9 Parameters
         DEFB #45                       ; WRITE DATA Command Alter #49 Del. Dta
         DEFB 0                         ; Drive
         DEFB 0                         ; Track
         DEFB 0                         ; Head Number
         DEFB 0                         ; Sector To Read
         DEFB 0                         ; Number Of Data Bytes Per Sector
         DEFB 0                         ; End Of Track
         DEFB 0                         ; Gap #3 - Generally #2a
         DEFB #ff                       ; Data Length - Sect Size < 256
;
;         **** WRITE DATA RESULTANT BYTES FROM RESULT PHASE ****
;
writcomr                                ; Write Command Result Table
         DEFB 0                         ; Status Register 0
         DEFB 0                         ; Status Register 1
         DEFB 0                         ; Status Register 2
         DEFB 0                         ; Track Number
         DEFB 0                         ; Head Number
         DEFB 0                         ; Sector Number
         DEFB 0                         ; Sector Size

Formatting A Track

You’ve learned the basics of reading and writing sectors to disk, how about Formatting a Track? Again this is really easy, and where you can get creative too! Many a protection system designer started off messing around with the various parameters required to make disks that could be read by a bespoke program but copying them using CP/M tools was not possible. This helped boost a cottage industry of specialist copiers, and tools like Discology and Nirvana came into play. They were very good at analysing disks and creating a faithful copy for many of the nuanced protection systems designed out there.

  • LD HL,Format_Table
  • LD D,Track
  • CALL format

The Format_Table of code contains a data block consisting of Track, Head, Sector ID, Sector Size and would look like the snippet below for a DATA Format disk of 9 Sectors. Remember with physical hardware it was necessary to interlace the sectors for optimal performance through AMSDOS. However if you’re creating your own protection system, you can double the speed/performance of your program by sequentially numbering the sector IDs since you’re more likely to be reading data from disk in a tight loop.

;
;             **** Header Buffer For Formatting A Disk ****
;
fo                                      ; Format Disk
         DEFB 0,0,#c1,2                 ; Data For Sector Numbers To Format
         DEFB 0,0,#c6,2                 ; In This Case DATA Format
         DEFB 0,0,#c2,2
         DEFB 0,0,#c7,2
         DEFB 0,0,#c3,2
         DEFB 0,0,#c8,2
         DEFB 0,0,#c4,2
         DEFB 0,0,#c9,2
         DEFB 0,0,#c5,2

FORMAT TRACK COMMAND

OffsetCommandDefinition
0#4DFormat Track Command for MFM DIsk
1#00Drive Number, 0 = A, 1 = B, Bit 2 can be set if you have dual heads.
2#00-#05N – Sector Size 2^(7+n)
3#xxNumber of Sectors per Track
4#xxGAP#3 Length
5#xxFiller Byte (Usually #E5)
OffsetResponseDefinition
0ST 0Status Register 0
1ST 1Status Register 1
2ST 2Status Register 2
3CCylinder/Track Number
4HHead Number
5RSector Number
6NSize of Sector 2^(7+N)

FORMAT Track Code Snippet

;
;                        **** Format A Track Of Data ****
;
format                                  ; Entry:- HL=Header Info.Buff. D=Track
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl                        ; Preserve Registers
         LD   a,e
         LD   (drive),a                 ; Which Drive
         CALL movtrak                   ; E=Drive
         PUSH hl                        ; Locate Drive Head To Correct Track
;
         LD   a,(sectsize)
         LD   (formcode+3),a            ; Store For Command Phase
         LD   a,(sect_trk)
         LD   (formcode+4),a            ; Store Sectors Per Track
         LD   a,(gap3f)
         LD   (formcode+5),a            ; Store GAP#3 For Formatting
         LD   a,(filler)
         LD   (formcode+6),a            ; Store Filler Byte
         LD   de,formcode               ; Codes For Command Phase
format1
         CALL ddfdccom                  ; Get Bytes And Act On 'em
         POP  hl                        ; Restore Registers
         CALL ddfdcwri                  ; Write Bytes To Disk
         LD   hl,formret                ; Return Status
         CALL ddfdcres                  ; Get status
         POP  hl
         POP  de
         POP  bc
         POP  af
         RET                            ; Quit with all registers intact

;
;             **** Header Buffer For Formatting A Disk ****
;
fo                                      ; Format Disk
         DEFB 0,0,#c1,2                 ; Data For Sector Numbers To Format
         DEFB 0,0,#c6,2                 ; In This Case DATA Format
         DEFB 0,0,#c2,2
         DEFB 0,0,#c7,2
         DEFB 0,0,#c3,2
         DEFB 0,0,#c8,2
         DEFB 0,0,#c4,2
         DEFB 0,0,#c9,2
         DEFB 0,0,#c5,2
formcode                                ; Format Track Data
         DEFB 6                         ; 6 Paramters
         DEFB #4d                       ; Code For Formatting
drive    DEFB 0                         ; Drive
         DEFB 2                         ; Size of Sectors 2 ^ (N+7)
         DEFB 9                         ; No. of Sector Per Track
         DEFB #2a                       ; Gap Length
         DEFB #e5                       ; Data Pattern = Filler Byte
;
;                  **** FORMAT RESULTS PHASE TABLE ****
;
formret                                 ; Resultant Data From FORMAT TRACK
         DEFB 0                         ; ST0
         DEFB 0                         ; ST1
         DEFB 0                         ; ST2
         DEFB 0                         ; Track
         DEFB 0                         ; Head
         DEFB 0                         ; Sector
         DEFB 0                         ; Size Of Sector
;
;Default Numbers For 9 Sectors Per Track 512 Bytes Per Sector GAP #3=#2a
;
XDPB                                    ; For My Own Use (Alternative XPB)
;
filler   DEFB #e5                       ; Filler Byte When Formatting
sect_trk DEFB 9                         ; Number Of Sectors Per Track
sectsize DEFB 2                         ; Size Of Sectors
gap3rw   DEFB #2a                       ; GAP #3 For Read/Write
gap3f    DEFB #52                       ; GAP #3 For Formatting

Index Marker

How does the drive know where the start of track is?

If you pull back the protective cover of the disk you’ll spot close to the centre of the disk, a smaller hole, you may need to spin the disk around to line it up, like I have below. This method isn’t limited to the Three inch disks, many floppy’s, from the eight inch, five and quarter, three and half etc use this method. There are some disks that don’t have this indicator, those are out of scope from this primer.

As the disk spins, that little hole will appear very briefly (Around 300 times a minute), where an optical signal is sent to the controller to show the start of each disk revolution. When this light is detected (With the image below a slowed down animation, shows a green light), a signal is sent to the controller. This method ensures some reliability in reading the data from the start of each track.

The drive spins up and recognises start of track when the optical sensor receives a signal.

Full Source Code for Direct Access to the Disk Controller

Below is the complete source code from my assembler routines to access the Amstrad CPC Disk Controller directly. Firmware not required, which enabled me to easily manipulate disk data directly.

;
         ORG  #9d00                     ; Alternative DDFDC Commands
         ENT  $                         ; Version 9.89 - Jason Brooks
         DI   
         CALL motoron
         LD   hl,#100
         LD   d,0
         LD   e,1
         CALL readtrak
         CALL motoroff
         RET  
init
         PUSH bc
         LD   hl,fo
         LD   b,9
init1
         LD   (hl),d
         INC  hl
         INC  hl
         INC  hl
         INC  hl
         DJNZ init1
         POP  bc
         RET  
motoron
         DI                             ; All Reg. Preserved
         PUSH hl
         PUSH bc
         PUSH af
         LD   bc,#fa7e
         LD   a,1
         OUT  (c),a
         LD   hl,0
         LD   b,2
motoron1                                ; Pause Loop To Allow Motor To Pick Up 
         DEC  hl
         LD   a,h
         OR   l
         JR   nz,motoron1
         DJNZ motoron1
         POP  af
         POP  bc
         POP  hl
         RET                            ; Exit All Reg. Preserved
motoroff
         PUSH bc
         PUSH af                        ; All Reg. Preserved
         LD   bc,#fa7e
         XOR  a
         OUT  (c),a
         POP  af
         POP  bc
         RET                            ; Quit All Reg. Intact
readtrak
         DI   
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl
         LD   a,e
         LD   (drive),a
         CALL movtrak
         LD   a,e
         LD   (READTRAK+2),a
         LD   a,d
         LD   (READTRAK+3),a
         LD   de,readcomd
         CALL ddfdccom
         CALL ddfdcexc
         CALL ddfdcres
         POP  hl
         POP  de
         POP  bc
         POP  af
         RET  
;
rdelsect                                ; Read Deleted Sector
         LD   a,#4c                     ; Command Code For READ DELETED DATA
         LD   (readcomd+1),a            ; Store It
         CALL readsect                  ; Read In Sector
         LD   a,#46                     ; Command Code For READ DATA
         LD   (readcomd+1),a            ; Restore Command Code
         RET                            ; Exit 
readsect                                ; ENTRY HL=BUFFER E=DRIVE D=TRK C=SECTR
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl                        ; Preserve Registers
         CALL movtrak                   ; Move To Approprite Track
         PUSH hl
         LD   hl,readcomd+2
         CALL patchrwd                  ; Patch Read Data
         LD   de,readcomd
         CALL ddfdccom                  ; Set Up Command
         POP  hl
         CALL ddfdcexc                  ; Get Bytes From EXECUTION PHASE
         LD   hl,readcomr
         CALL ddfdcres                  ; Get Result From Read Command
         POP  hl
         POP  de
         POP  bc
         POP  af
         RET  
wdeldata                                ; Write Deleted Data
         PUSH af                        ; Preserve AF
         LD   a,%1001001                ; Comand For WRITE DELETED DATA
         LD   (writcomd+1),a            ; Store Command In Data String
         CALL writsect                  ; Call Up Normal WRITE SECTOR
         LD   a,%1000101                ; Command For WRITE DATA
         LD   (writcomd+1),a            ; Restore Command In Data String
         POP  AF                        ; Restore AF
         RET                            ; Quit 
;
;                     **** WRITE DATA To TRACK & SECTOR ****
;
writsect                                ; Entry HL=DATA ADDR D=TRK E=DRV C=SECT
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl                        ; Preserve Registers
         CALL movtrak                   ; Move To Track D
         PUSH hl
         LD   hl,writcomd+2             ; Point To Command DATA Table
         CALL patchrwd                  ; Patch Write Data For TRACK & SECTOR
         LD   de,writcomd               ; Point To Command Data
         CALL ddfdccom                  ; Start COMMAND PHASE
         POP  hl                        ; Restore HL = DATA To Write
         CALL ddfdcwri                  ; Write It To Disk
         LD   hl,writcomr               ; HL=Write Command Return
         CALL ddfdcres                  ; Call Up Result Phase
         POP  hl
         POP  de
         POP  bc
         POP  af                        ; Restore Registers
         RET                            ; Exit Routine
;
;             **** PATCH READ WRITE DATA FOR READ/WRITE COMMAND ****
;
patchrwd                                ; Patch Read Write Data
         PUSH hl
         PUSH af
         LD   a,e                       ; A=Drive
         LD   (hl),a
         INC  hl
         INC  hl                        ; Point HL To Head Number
         LD   (hl),a                    ; Store Head Number
         DEC  hl                        ; Point To Track Data
         LD   a,d                       ; A = Track
         LD   (hl),a                    ; Store Track Number
         INC  hl
         INC  hl                        ; Point To Sector
         LD   a,c                       ; A = Sector
         LD   (hl),a                    ; Store It
         INC  hl
         INC  hl                        ; Point To End Of Track
         LD   (hl),a                    ; Store Sector
         DEC  hl                        ; Point To Sector Size
         LD   a,(sectsize)              ; Get Sector Size From XDPB
         LD   (hl),a                    ; Store It
         INC  hl
         INC  hl                        ; Point To Gap Length
         LD   a,(gap3rw)                ; A = Gap #3 For READ/WRITE
         LD   (hl),a                    ; Store It
         INC  hl                        ; Point To Data Length
         LD   a,(dataleng)              ; A=Sector Length
         LD   (hl),a                    ; Store It
         POP  af
         POP  hl
         RET  
;
;                        **** Format A Track Of Data ****
;
format                                  ; Entry:- HL=Header Info.Buff. D=Track
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl                        ; Preserve Registers
         LD   a,e
         LD   (drive),a                 ; Which Drive
         CALL movtrak                   ; E=Drive
         PUSH hl                        ; Locate Drive Head To Correct Track
;
         LD   a,(sectsize)
         LD   (formcode+3),a            ; Store For Command Phase
         LD   a,(sect_trk)
         LD   (formcode+4),a            ; Store Sectors Per Track
         LD   a,(gap3f)
         LD   (formcode+5),a            ; Store GAP#3 For Formatting
         LD   a,(filler)
         LD   (formcode+6),a            ; Store Filler Byte
         LD   de,formcode               ; Codes For Command Phase
format1
         CALL ddfdccom                  ; Get Bytes And Act On 'em
         POP  hl                        ; Restore Registers
         CALL ddfdcwri                  ; Write Bytes To Disk
         LD   hl,formret                ; Return Status
         CALL ddfdcres                  ; Get status
         POP  hl
         POP  de
         POP  bc
         POP  af
         RET                            ; Quit with all registers intact
;
;                         **** Read Sector ID ****
;
readid
         PUSH af                        ; Entry E=drive 0=A 1=B
         PUSH bc
         PUSH de
         LD   a,e
         LD   (Read_ID+2),a             ; Store Drive
         LD   hl,diskid
         PUSH hl
         LD   de,Read_ID
         CALL ddfdccom
         CALL ddfdcres
         POP  hl                        ; Exit Cond. HL=Disk Id, All Reg. Pres.
         POP  de
         POP  bc
         POP  af
         RET  
;
;                        **** Move Drive Head To Track T ****
;
drivicl  DEFB 0
movtrak                                 ; On Entry D = Destination Track
         PUSH af
         PUSH bc
         PUSH de
         PUSH hl                        ; Preserve Registers
         LD   a,e
         OR   #20
         LD   (drivicl),a
         CALL senseint                  ; Get Current Track Position
         LD   a,(sense+1)
         SUB  d                         ; Is Destination Track Current Track ?
         JR   z,movtrake                ; If So Quit
         LD   a,d                       ; A = Track
         OR   a                         ; Is Destination Track 0
         JR   z,trak0                   ; If So Then Trak0
         CALL seek                      ; Give DDFDC SEEK Command
movtrak1                                ; Use This Since No DMA
         CALL senseint                  ; Has Drive Reached Destination ?
         LD   hl,drivicl
         LD   a,(sense)
         CP   (hl)
         JR   nz,movtrak1               ; If Not Loop movtrak1
movtrake                                ; Quit Routine
         POP  hl
         POP  de
         POP  bc
         POP  af
         RET                            ; Preserved Registers
trak0
         CALL recalib                   ; Recalibrate (Move To Track 0)
trak01
         CALL senseds                   ; Call SENSE DRIVE STATUS
         LD   a,(hl)
         AND  %10000                    ; Has Drive Head Reached TRK 0
         JR   z,trak01                  ; If Not Loop
         JR   movtrake                  ; Restore Regs & Quit
;
;                       **** SENSE DRIVE STATUS ****
;
senseds
         LD   a,(drive)
         LD   (SenseCde+2),a
         LD   de,SenseCde
         CALL ddfdccom
         LD   hl,sendst                 ; Pointer To Put Resultant Data
         CALL ddfdcres
         DEC  hl                        ; On Exit HL=Address Of Status Reg. 3
         RET                            ; Quit
;
;                  **** RECALIBRATE DRIVE HEAD (TRACK 0) ****
;
recalib                                 ; Recalibrate
         LD   de,RECALCOM
         LD   a,(drive)
         LD   (RECALCOM+2),a
         JP   ddfdccom
;
;                       **** SENSE INTERUPT STATUS ****
;
senseint                                ; Sense Interupt Status
         PUSH de                        ; Preserve DE
         LD   de,SENSEINT
         CALL ddfdccom
         LD   hl,sense
         CALL ddfdcres                  ; Call & Quit
         POP  de
         RET  
;
;                   **** SEEK COMMAND MOVE TRACK HEAD ****
;
seek                                    ; SEEK Entry D=Track
         PUSH de
         LD   (Seek_DT+3),a
         LD   a,(drive)
         LD   (Seek_DT+2),a
         LD   de,Seek_DT
         CALL ddfdccom
         POP  de
         RET  
;
;                        **** DDFDC COMMAND PHASE ****
;
ddfdccom                                ; DDFDC Command Phase
         LD   bc,#fb7e
         PUSH bc
         LD   a,(de)                    ; Get Number Of Parameters
ddfdc
         PUSH af                        ; Preserve Counter
         INC  de
ddfdc1                                  ; Is Drive Ready To Accept Command ?
         IN   a,(c)
         ADD  a,a
         JR   nc,ddfdc1
         JP   m,ddfdc1                  ; If Not Then Wait
         LD   a,(de)
         INC  c
         OUT  (c),a                     ; Give DDFDC Command @ Port #FB7F
         DEC  c
         LD   a,5
ddfdcp                                  ; Wait 13 uS
         DEC  a
         JR   nz,ddfdcp
         POP  af
         DEC  a
         JR   nz,ddfdc
         POP  bc                        ; On Return BC=#FB7E
         RET                            ; Quit
;
ddfdcexc                                ; DDFDC Execution Phase - DATA IN
         LD   a,(drive)
         OR   #20
         LD   (ddfdexc2-1),a
ddfdcexd
         LD   bc,#fb7e
         IN   a,(c)
         CP   #c0
         JR   c,ddfdexc1
ddfdexc0
         INC  c                         ; Point To #FB7F - DATA REGISTER
         IN   a,(c)                     ; Get byte from port
         LD   (hl),a                    ; Store it
         DEC  c                         ; Restore Port To Main Status Reg.
         INC  hl                        ; HL+1
ddfdexc1
         IN   a,(c)
         JP   p,ddfdexc1                ; Drive Not Finished Output So Wait
         AND  #20                       ; Main Status Reg=Execution Phase Start
ddfdexc2
         JR   nz,ddfdexc0               ; If Not Finished Loop ddfdexc
         RET                            ; Else Quit
;
;              **** DDFDC EXECUTION PHASE DATA TO SYSTEM ****
;
ddfdcwri                                ; DDFDC Write Into Data Register
         LD   bc,#fb7e                  ; Point To MAIN STATUS REG
         LD   a,(drive)
         OR   #20
         LD   (ddfdcw3-1),a
         JR   ddfdcw2                   ; Wait Till DDFDC Ready.
ddfdcw1
         INC  c                         ; Point To Data Port
         LD   a,(hl)                    ; Get Byte To Place
         INC  hl                        ; HL+1
         OUT  (c),a                     ; Output To Port #FB7F
         DEC  c                         ; Restore Port
ddfdcw2
         IN   a,(c)
         JP   p,ddfdcw2                 ; If Drive Not Ready Loop ddfdcw2
         AND  #20
ddfdcw3
         JR   nz,ddfdcw1                ; Is All Output Finished ?
         RET                            ; Quit 
;
;                       **** DDFDC RESULTS PHASE ****
;
ddfdcres                                ; DDFDC Result Phase
         LD   a,(drive)
         OR   #10
         LD   (ddfdresq-1),a
ddfdcret
         IN   a,(c)
         CP   #c0                       ; Is DDFDC Ready ?
         JR   c,ddfdcret                ; If Not Wait
         INC  c
         IN   a,(c)                     ; Get Byte From DATA REG
         LD   (hl),a                    ; Store it
         DEC  c                         ; Restore Data Reg.
         INC  hl                        ; HL+1
         LD   a,5
ddfdresp                                ; Wait 13 uS
         DEC  a
         JR   nz,ddfdresp
         IN   a,(c)
         AND  #10                       ; Has Results Finished ?
ddfdresq
         JR   nz,ddfdcret               ; If Not Loop ddfdcres
         RET                            ; Quit 
;
;                  **** DATA AREA BUFFERS, POINTERS Etc. ****
;
sense                                   ; Data from RESULT PHASE OF SNSE INT ST
         DEFB 0                         ; ST0
         DEFB 0                         ; Present Track Number
;
;             **** BYTE FOR SENSE DRIVE STATUS RESULT PHASE ****
;
sendst   DEFB 0                         ; Status Register 3
;
;             **** DATA FOR COMMAND PHASE OF FORMAT A TRACK ****
;
READTRAK
         DEFB 9
         DEFB %1100010
         DEFB 0
         DEFB 0
         DEFB 0
         DEFB 1
         DEFB 6
         DEFB 1
         DEFB #52
         DEFB #ff
formcode                                ; Format Track Data
         DEFB 6                         ; 6 Paramters
         DEFB #4d                       ; Code For Formatting
drive    DEFB 0                         ; Drive
         DEFB 2                         ; Size of Sectors 2 ^ (N+7)
         DEFB 9                         ; No. of Sector Per Track
         DEFB #2a                       ; Gap Length
         DEFB #e5                       ; Data Pattern = Filler Byte
;
;                  **** FORMAT RESULTS PHASE TABLE ****
;
formret                                 ; Resultant Data From FORMAT TRACK
         DEFB 0                         ; ST0
         DEFB 0                         ; ST1
         DEFB 0                         ; ST2
         DEFB 0                         ; Track
         DEFB 0                         ; Head
         DEFB 0                         ; Sector
         DEFB 0                         ; Size Of Sector
;
;Default Numbers For 9 Sectors Per Track 512 Bytes Per Sector GAP #3=#2a
;
XDPB                                    ; For My Own Use (Alternative XPB)
;
filler   DEFB #e5                       ; Filler Byte When Formatting
sect_trk DEFB 9                         ; Number Of Sectors Per Track
sectsize DEFB 2                         ; Size Of Sectors
gap3rw   DEFB #2a                       ; GAP #3 For Read/Write
gap3f    DEFB #52                       ; GAP #3 For Formatting
dataleng DEFB #ff
;
;            **** COMMAND DATA FOR SENSE DRIVE STATUS ****
;
SenseCde
         DEFB 2                         ; 2 Parameters
         DEFB 4                         ; Code For SENSE DRIVE STATUS
         DEFB 0                         ; Drive
;
;                   **** COMMAND DATA FOR SEEK ****
;
Seek_DT                                 ; Seek Codes For Command
         DEFB 3                         ; 3 Parameters
         DEFB 15                        ; Command For Seek
         DEFB 0                         ; Drive
         DEFB 0                         ; Destination Track
;
;            **** COMMAND DATA FOR SENSE INTERUPT STATUS ****
;
SENSEINT DEFB 1                         ; One Parameter
         DEFB 8                         ; Command Code For SENSE INTERUPT STATE
;
;                  **** COMMAND DATA FOR READ ID ****
;
Read_ID
         DEFB 2                         ; 2 Parameters
         DEFB #4a                       ; Command Number
         DEFB 0                         ; Drive
;
;                 **** RESULTANT DATA FROM READ ID ****
;
diskid                                  ; DISK ID Resultant Data
         DEFB 0                         ; ST0
         DEFB 0                         ; ST1
         DEFB 0                         ; ST2
         DEFB 0                         ; Track
         DEFB 0                         ; Head
         DEFB 0                         ; Sector
         DEFB 0                         ; Sector Size = 2 ^ N+7
;
;                 **** COMMAND DATA FOR RECALIBRATE ****
;
RECALCOM DEFB 2                         ; 2 Parameters
         DEFB 7                         ; Command Code For RECALIBRATE
         DEFB 0                         ; Which Drive
;
;             **** COMMAND CODE FOR DDFDC WRITE SECTOR ****
;
writcomd                                ; Write Command Data
         DEFB 9                         ; 9 Parameters
         DEFB #45                       ; WRITE DATA Command Alter #49 Del. Dta
         DEFB 0                         ; Drive
         DEFB 0                         ; Track
         DEFB 0                         ; Head Number
         DEFB 0                         ; Sector To Read
         DEFB 0                         ; Number Of Data Bytes Per Sector
         DEFB 0                         ; End Of Track
         DEFB 0                         ; Gap #3 - Generally #2a
         DEFB #ff                       ; Data Length - Sect Size < 256
;
;         **** WRITE DATA RESULTANT BYTES FROM RESULT PHASE ****
;
writcomr                                ; Write Command Result Table
         DEFB 0                         ; Status Register 0
         DEFB 0                         ; Status Register 1
         DEFB 0                         ; Status Register 2
         DEFB 0                         ; Track Number
         DEFB 0                         ; Head Number
         DEFB 0                         ; Sector Number
         DEFB 0                         ; Sector Size
;
;                 **** COMMAND DATA FOR READ SECTOR ****
;
readcomd                                ; Read Command Data
         DEFB 9                         ; 9 Parameters
         DEFB #46                       ; READ DATA Command Alter To #4C Del.
         DEFB 0                         ; Drive
         DEFB 0                         ; Track
         DEFB 0                         ; Head Number
         DEFB 0                         ; Sector To Read
         DEFB 0                         ; Number Of Data Bytes Per Sector
         DEFB 0                         ; End Of Track
         DEFB 0                         ; Gap #3 - Generally #2a
         DEFB #ff                       ; Data Length - Sect Size < 256
;
;        **** READ DATA RESULTANT BYTES FROM RESULT PHASE ****
;
readcomr                                ; Read Command Result Table
         DEFB 0                         ; Status Register 0
         DEFB 0                         ; Status Register 1
         DEFB 0                         ; Status Register 2
         DEFB 0                         ; Track Number
         DEFB 0                         ; Head Number
         DEFB 0                         ; Sector Number
         DEFB 0                         ; Sector Size
;
;             **** Header Buffer For Formatting A Disk ****
;
fo                                      ; Format Disk
         DEFB 0,0,#c1,2                 ; Data For Sector Numbers To Format
         DEFB 0,0,#c6,2                 ; In This Case DATA Format
         DEFB 0,0,#c2,2
         DEFB 0,0,#c7,2
         DEFB 0,0,#c3,2
         DEFB 0,0,#c8,2
         DEFB 0,0,#c4,2
         DEFB 0,0,#c9,2
         DEFB 0,0,#c5,2
;
dd       DEFS 20,0

Appendix

The following contains info of relevant registers, remember the Most Significant Bit (Bit 7) is to the Left, the Least Significant Bit (Bit 0) is to the right.

Bit to (Decimal/Hex) Mapping

Status Register 0 (ST0)

BITNameinfo
0US0 – Unit Select 0Drive A or B (If Bit Set).
1US1 – Unit Select 1Drive C or D (If Bit Set) – Not used on the Amstrad CPC.
2HD – Head AddressFlag to indicate that state of the Head at Interrupt.
3NR – Not ReadyIndicates the Drive isn’t ready to send or receive commands/data if you try to send/receive data.
4EC – Equipment CheckSet if the Drive is unable to recalibrate or there’s an error with the drive unit.
5SE – Seek EndSet when the SEEK Command has physically completed the move to Track X
6
7
IC – Interrupt Code 6 = 0, 7 = 0 – Normal Termination of the Command
6 = 1, 7 = 0 – Abnormal Termination of the Command
6 = 0, 7 = 1 – Invalid Command Issued
6 = 1, 7 = 1 – Abnormal Termination during command as Ready State Signal Received.

Status Register 1 (ST1)

BITNameInfo
0MA – Missing Address MarkIf the FDC can not find a valid IDAM before two revolutions of the disk (Index Pulsed).
1Not WriteableDuring a Write Operation, the Write Protect Signal was detected.
2ND – No DataIf during a READ, WRITE or SCAN operation the Specified Sector could not be found.
If during a READ ID command, the ID could not be read without an error.
If during READ Diagnostic Command, the start sector could not be found.
3Not UsedAlways 0
4OR – OverrunThe Controller wasn’t serviced (Data sent or received) from software within a set time interval.
5Data ErrorCRC Failure in either the ID Field or the DATA
6Not UsedAlways 0
7EN – End of CylinderSet when trying to access a sector not on this track.

Status Register 2

BITNameInfo
0MD – Missing Address MarkSet if the FDC cannot find a Data Address Mark or Deleted Data Address Mark.
1BC – Bad CylinderRelated to the ND Bit in ST1, when the contents of the Cylinder ID Stored on the medium differs from the from data stored in the IDR.
2SN – Scan Not SatisfiedDuring the SCAN command, if the Sector ID can not be found.
3SH – Scan Equal HitIf the Sector ID matched that of the SCAN Command, then this is set.
4WC – Wrong CylinderRelated to the ND Bit in ST1, when the contents of the Cylinder ID Stored on the medium differs from the from data stored in the IDR.
5DD – Data Error in Data FieldCRC Error in the data read.
6CM – Control MarkSet during a Read Data or Scan Command, set if a sector contains a deleted data address mark.
7Not UsedAlways 0

Status Register 3 (ST3)

BITNameInfo
0US0 – Unit Select 0Status of the Unit Select 0 = A, 1 = B
1US1 – Unit Select 1Status of the Unit Select 0 = C, 1 = D
2HD – Head AddressStatus of the Side Select (0 = Side A, 1 = Side B)
3TS – Two SidedTwo Sided Drive Status?
4T0 – Track ZeroTrack Zero Detected
5RY – ReadyThe FDC Is Ready
6WP – Write ProtectWrite Protect Signal, 1 = WP
7FT – FaultFault was detected.

4 Comments »

  1. Thanks a lot. I was looking for such code at some point.

    Note that patchrwd can be simplified, methinks :

    patchrwd                                ; Patch Read Write Data

             PUSH hl

             PUSH af

             LD   (hl),e

             INC  hl

             LD   (hl),d

             INC  hl

             LD   (hl),e

             INC  hl

             LD   (hl),c

             INC  hl

             LD   a,(sectsize)              ; Get Sector Size From XDPB

             LD   (hl),a                    ; Store It

             INC  hl                        ; Point To End Of Track

             LD   (hl),c                    ; Store Sector

             INC  hl                        ; Point To Gap Length

             LD   a,(gap3rw)                ; A = Gap #3 For READ/WRITE

             LD   (hl),a                    ; Store It

             INC  hl                        ; Point To Data Length

             LD   a,(dataleng)              ; A=Sector Length

             LD   (hl),a                    ; Store It

             POP  af

             POP  hl

             RET  

    Liked by 1 person

Leave a reply to Longshot Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.