;; 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 start