;; Load data by talking directly to the NEC765 floppy disc controller (FDC)
;;
;; Code assumes there is a drive 0 and there is a disc in it and the disc is formatted
;; to DATA format.
org &100
start:
;; turn on disc motor
ld bc,$fa7e
ld a,1
out (c),a
;; the motor on all connected drives will be turned on
;; and the motor will start to speed up.
;;
;; The drive must be "Ready" to accept commands from the FDC.
;; A drive will be ready if:
;; * the drive motor has reached a stable speed
;; * there is a disc in the drive
;;
;; The following code is a delay which will ait enough time for the motor
;; to reach a stable speed. (i.e. the motor speed is not increasing or decreasing)
;;
;; All drives are not the same, some 3" drives take longer to reach a stable
;; speed so we need a longer delay to be compatible with these.
;;
;; At this point interrupts must be enabled.
ld b,30 ;; 30/6 = 5 frames or 5/50 of a second.
w1:
;; there are 6 CPC interrupts per frame. This waits for one of them
halt
djnz w1
;; this is the drive we want to use
;; the code uses this variable.
ld a,0
ld (drive),a
;; recalibrate means to move the selected drive to track 0.
;;
;; track 0 is a physical signal from the drive that indicates when
;; the read/write head is at track 0 position.
;;
;; The drive itself doesn't know which track the read/write head is positioned over.
;; The FDC has an internal variable for each drive which holds the current track number.
;; This value is reset when the drive indicates the read/write head is over track 0.
;; The number is increased/decreased as the FDC issues step pulses to the drive to move the head
;; to the track we want.
;;
;; once a recalibrate has been done, both drive and fdc agree on the track.
;;
call fdc_recalibrate
;; now the drive is at a known position and is ready the fdc knows it is at a known position
;; we can read data..
call read_file
ret
read_file:
;; set variable for starting sector for our data (&C1 is first sector ID for
;; DATA format. Sector IDs are &C1, &C2, &C3, &C4, &C5, &C6, &C7, &C8 and &C9.
ld a,&c1
ld (sector),a
;; set variable for starting track for our data
;; Tracks are numbered 0 to 39 for 40 track drives and 0 to 79 for 80 track drives.
;; Some 3" drives can allow up to 42 tracks (0-41), some 80 track drives can allow up
;; to 83 tracks (0-82).
;;
;; Not all drives are the same however. The maximum that is compatible with all 3" drives
;; is 41 tracks.
ld a,1
ld (track),a
;; memory address to write data to (start)
ld de,file_buffer
ld (data_ptr),de
;; number of complete sectors to read for our data
;; 30 sectors, 512 bytes per sector. Total data to read is 30*512 = 15360 bytes.
ld (sector_count),a
read_sectors_new_track:
;; perform a seek (this means to move read/write head to track we want).
;; track is defined by the "track" variable.
;;
;; a recalibrate must be done on the drive before a seek is done.
;;
;; the fdc uses it's internal track value for the chosen drive to decide to seek up/down to
;; reach the desired track. The FDC issues "step pulses" which makes the read/write head move
;; 1 track at a time at the rate defined by the FDC specify command.
;;
;; e.g. if fdc thinks we are on track 10, and we ask it to move to track 5, it will step back 5 times
;; updating it's internal track number each time.
call fdc_seek
read_sectors:
;; Send Read data command to FDC to read 1 sector.
;; A track is layed out as follows:
;;
;; id field
;; data field
;;
;; id field
;; data field
;;
;; id field
;; data field
;; etc.
;;
;; we tell the FDC the values of the ID field we want. Once it finds a match it will then read
;; the data. If the ID field we want is not found, it will report an error.
ld a,%01000110 ;; read data command (mfm=double density reading mode)
;; not multi-track. See FDC data sheet for list of commands and the
;; number of bytes they need.
call fdc_write_command
ld a,(drive) ;; physical drive and side
;; bits 1,0 define drive, bit 2 defines side
call fdc_write_command
ld a,(track) ;; C value from id field of sector we want to read
call fdc_write_command
ld a,0 ;; H value from id field of sector we want to read
call fdc_write_command
ld a,(sector) ;; R value from id field of sector we want to read
call fdc_write_command
ld a,2 ;; N value from id field of sector we want to read
;; this also determines the amount of data in the sector.
;; 2 = 512 byte sector
call fdc_write_command
ld a,(sector) ;; EOT = Last sector ID to read. This is the same as the first to read 1 sector.
call fdc_write_command
ld a,&2a ;; Gap Length for read. Not important.
call fdc_write_command
ld a,&ff ;; DTL = Data length. Only valid when N is 0 it seems
call fdc_write_command
;; There will be a delay here before the first byte of a sector is ready and
;; interrupts can be active.
;;
;; The FDC is reading from the track. It is searching for an ID field that
;; matches the values we have sent in the command.
;;
;; When it finds the ID field, there is furthur time before the data field
;; of the sector is found and it starts to read.
;;
;; Once it has found the data, we must read it all and quickly.
;;
;; interrupts must be off now for data to be read successfully.
;;
;; The CPU constantly asks the FDC if there is data ready, if there is
;; it reads it from the FDC and stores it in RAM. There is a timing
;; constraint, the FDC gives the CPU a byte every 32microseconds.
;; If the CPU fails to read one of the bytes in time, the FDC will report
;; an overrun error and stop data transfer.
di
;; current address to write data too.
ld de,(data_ptr)
;; this is the main loop
;; which reads the data
;; The FDC will give us a byte every 32us (double density disc format).
;;
;; We must read it within this time.
fdc_data_read:
in a,(c) ;; FDC has data and the direction is from FDC to CPU
jp p,fdc_data_read ;;
and &20 ;; "Execution phase" i.e. indicates reading of sector data
jp z,fdc_read_end
inc c ;; BC = I/O address for FDC data register
in a,(c) ;; read from FDC data register
ld (de),a ;; write to memory
dec c ;; BC = I/O address for FDC main status register
inc de ;; increment memory pointer
jp fdc_data_read
fdc_read_end:
;; Interrupts can be enabled now we have completed the data transfer
ei
;; we will get here if we successfully read all the sector's data
;; OR if there was an error.
;; read the result
call fdc_result
;; check result
ld ix,result_data
ld c,&54
ld a,(ix+0)
cp &40
jp z,nerr
ld a,(ix+1)
cp &80
jp z,nerr
ld c,&40
nerr:
;; decrease number of sectors transferred
ld a,(sector_count)
dec a
jp z,read_done
ld (sector_count),a
;; update ram pointer for next sector
ld hl,(data_ptr)
ld bc,512
add hl,bc
ld (data_ptr),hl
;; update sector id (loops &C1-&C9).
ld a,(sector)
inc a
ld (sector),a
cp &ca ;; &C9+1 (last sector id on the track+1)
jp nz,read_sectors
;; we read sector &C9, the last on the track.
;; Update track variable so we seek to the next track before
;; reading the next sector
ld a,(track)
inc a
ld (track),a
ld a,&c1 ;; &C1 = first sector id on the track
ld (sector),a
jp read_sectors_new_track
read_done:
ret
;;===============================================
;; send command to fdc
;;
fdc_write_command:
ld bc,&fb7e ;; I/O address for FDC main status register
push af ;;
fwc1: in a,(c) ;;
add a,a ;;
jr nc,fwc1 ;;
add a,a ;;
jr nc,fwc2 ;;
pop af ;;
ret
fwc2:
pop af ;;
inc c ;;
out (c),a ;; write command byte
dec c ;;
;; some FDC documents say there must be a delay between each
;; command byte, but in practice it seems this isn't needed on CPC.
;; Here for compatiblity.
ld a,5 ;;
fwc3: dec a ;;
jr nz,fwc3 ;;
ret ;;
;;===============================================
;; get result phase of command
;;
;; timing is not important here
fdc_result:
ld hl,result_data
ld bc,&fb7e
fr1:
in a,(c)
cp &c0
jr c,fr1
inc c
in a,(c)
dec c
ld (hl),a
inc hl
ld a,5
fr2:
dec a
jr nz,fr2
in a,(c)
and &10
jr nz,fr1
ret
;;===============================================
;; physical drive
;; bit 1,0 are drive, bit 2 is side.
drive:
defb 0
;; physical track (updated during read)
track:
defb 0
;; id of sector we want to read (updated during read)
sector:
defb 0
;; number of sectors to read (updated during read)
sector_count:
defb 0
;; address to write data to (updated during read)
data_ptr:
defw 0
;;===============================================
fdc_seek:
ld a,%0000001111 ;; seek command
call fdc_write_command
ld a,(drive)
call fdc_write_command
ld a,(track)
call fdc_write_command
call fdc_seek_or_recalibrate
jp nz,fdc_seek
ret
;;===============================================
fdc_recalibrate:
;; seek to track 0
ld a,%111 ;; recalibrate
call fdc_write_command
ld a,(drive) ;; drive
call fdc_write_command
call fdc_seek_or_recalibrate
jp nz,fdc_recalibrate
ret
;;===============================================
;; NZ result means to retry seek/recalibrate.
fdc_seek_or_recalibrate:
ld a,%1000 ;; sense interrupt status
call fdc_write_command
call fdc_result
;; recalibrate completed?
ld ix,result_data
bit 5,(ix+0) ;; Bit 5 of Status register 0 is "Seek complete"
jr z,fdc_seek_or_recalibrate
bit 4,(ix+0) ;; Bit 4 of Status register 0 is "recalibrate/seek failed"
;;
;; Some FDCs will seek a maximum of 77 tracks at one time. This is a legacy/historical
;; thing when drives only had 77 tracks. 3.5" drives have 80 tracks.
;;
;; If the drive was at track 80 before the recalibrate/seek, then one recalibrate/seek
;; would not be enough to reach track 0 and the fdc will then report an error (meaning
;; it had seeked 77 tracks and failed to reach the track we wanted).
;; We repeat the recalibrate/seek to finish the movement of the read/write head.
;;
ret
;;===============================================
result_data:
defs 8
file_buffer:
defb 0
end