2019-03-02 17:33:50 +00:00
|
|
|
package apple2
|
|
|
|
|
2019-05-17 21:28:20 +00:00
|
|
|
import (
|
|
|
|
"encoding/binary"
|
2019-10-20 22:00:42 +00:00
|
|
|
"fmt"
|
2019-05-17 21:28:20 +00:00
|
|
|
"io"
|
|
|
|
)
|
2019-03-02 19:41:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
https://applesaucefdc.com/woz/reference2/
|
2019-05-23 22:33:45 +00:00
|
|
|
|
2019-03-02 19:41:25 +00:00
|
|
|
Good explanation of the softswitches and the phases:
|
|
|
|
http://yesterbits.com/media/pubs/AppleOrchard/articles/disk-ii-part-1-1983-apr.pdf
|
2019-03-04 23:00:12 +00:00
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
"IMW Floppy Disk I/O Controller info" (https://www.brutaldeluxe.fr/documentation/iwm/apple2_IWM_INFO_19840510.pdf)
|
|
|
|
|
|
|
|
"Understanfing the Apple II, chapter 9"
|
|
|
|
|
2019-03-04 23:00:12 +00:00
|
|
|
35 tracks, 16 sectors, 256 bytes
|
|
|
|
NIB: 35 tracks 6656 bytes, 232960 bytes
|
|
|
|
|
2019-03-02 19:41:25 +00:00
|
|
|
*/
|
2019-03-04 23:00:12 +00:00
|
|
|
const maxHalfTrack = 68
|
2019-03-02 19:41:25 +00:00
|
|
|
|
|
|
|
type cardDisk2 struct {
|
|
|
|
cardBase
|
2019-05-23 22:33:45 +00:00
|
|
|
selected int // q5, Only 0 and 1 supported
|
2019-03-04 23:00:12 +00:00
|
|
|
drive [2]cardDisk2Drive
|
2019-05-23 22:33:45 +00:00
|
|
|
|
|
|
|
dataLatch uint8
|
|
|
|
q6 bool
|
|
|
|
q7 bool
|
2019-03-04 23:00:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type cardDisk2Drive struct {
|
2019-12-01 18:48:29 +00:00
|
|
|
diskette *diskette16sector
|
|
|
|
power bool // q4, not realy used for anything
|
|
|
|
position int
|
2019-12-02 21:43:40 +00:00
|
|
|
phases uint8 // q3, q2, q1 and q0 with q0 on the LSB. Magnets that are active on the stepper motor
|
2019-12-01 18:48:29 +00:00
|
|
|
tracksStep int // Stepmotor for tracks position. 4 steps per track
|
|
|
|
}
|
|
|
|
|
|
|
|
type diskette interface {
|
|
|
|
powerOn(cycle uint64)
|
|
|
|
powerOff(cycle uint64)
|
|
|
|
read(halfTrack int, cycle uint64) uint8
|
|
|
|
write(halfTrack int, value uint8, cycle uint64)
|
2019-03-02 17:33:50 +00:00
|
|
|
}
|
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
const (
|
|
|
|
diskBitCycle = 4 // There is a dataLatch bit transferred every 4 cycles
|
|
|
|
diskLatchReadCycles = 7 // Loaded data is available for a little more than 7ns
|
|
|
|
diskWriteByteCycle = 32 // Load data to write every 32 cycles
|
|
|
|
diskWriteSelfSyncCycle = 40 // Save $FF every 40 cycles. Self sync is 10 bits: 1111 1111 00
|
|
|
|
diskMotorStartMs = 150 // Time with the disk spinning to get full speed
|
|
|
|
|
|
|
|
)
|
|
|
|
|
2019-05-18 14:43:51 +00:00
|
|
|
func (c *cardDisk2) assign(a *Apple2, slot int) {
|
2019-05-23 22:33:45 +00:00
|
|
|
// Q1, Q2, Q3 and Q4 phase control soft switches,
|
2019-10-20 22:00:42 +00:00
|
|
|
for i := uint8(0); i < 4; i++ {
|
2019-05-23 22:33:45 +00:00
|
|
|
phase := i
|
2019-10-20 22:00:42 +00:00
|
|
|
c.addCardSoftSwitchR(phase<<1, func(_ *ioC0Page) uint8 {
|
2019-12-01 18:48:29 +00:00
|
|
|
// Update magnets and position
|
|
|
|
drive := &c.drive[c.selected]
|
2019-12-02 21:43:40 +00:00
|
|
|
drive.phases &^= (1 << phase)
|
|
|
|
drive.tracksStep = moveStep(drive.phases, drive.tracksStep)
|
2019-12-01 18:48:29 +00:00
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
return c.dataLatch // All even addresses return the last dataLatch
|
2019-10-20 22:00:42 +00:00
|
|
|
}, fmt.Sprintf("PHASE%vOFF", phase))
|
|
|
|
|
|
|
|
c.addCardSoftSwitchR((phase<<1)+1, func(_ *ioC0Page) uint8 {
|
2019-12-01 18:48:29 +00:00
|
|
|
// Update magnets and position
|
2019-05-23 22:33:45 +00:00
|
|
|
drive := &c.drive[c.selected]
|
2019-12-02 21:43:40 +00:00
|
|
|
drive.phases |= (1 << phase)
|
|
|
|
drive.tracksStep = moveStep(drive.phases, drive.tracksStep)
|
2019-12-01 18:48:29 +00:00
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
return 0
|
2019-10-20 22:00:42 +00:00
|
|
|
}, fmt.Sprintf("PHASE%vOFF", phase))
|
2019-03-02 19:41:25 +00:00
|
|
|
}
|
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
// Q4, power switch
|
2019-10-20 22:00:42 +00:00
|
|
|
c.addCardSoftSwitchR(0x8, func(_ *ioC0Page) uint8 {
|
2019-05-10 16:07:36 +00:00
|
|
|
if c.drive[c.selected].power {
|
|
|
|
c.drive[c.selected].power = false
|
|
|
|
c.a.releaseFastMode()
|
|
|
|
}
|
2019-05-23 22:33:45 +00:00
|
|
|
return c.dataLatch
|
2019-10-20 22:00:42 +00:00
|
|
|
}, "Q4DRIVEOFF")
|
|
|
|
c.addCardSoftSwitchR(0x9, func(_ *ioC0Page) uint8 {
|
2019-05-10 16:07:36 +00:00
|
|
|
if !c.drive[c.selected].power {
|
|
|
|
c.drive[c.selected].power = true
|
|
|
|
c.a.requestFastMode()
|
|
|
|
}
|
2019-03-02 19:41:25 +00:00
|
|
|
return 0
|
2019-10-20 22:00:42 +00:00
|
|
|
}, "")
|
2019-05-23 22:33:45 +00:00
|
|
|
|
|
|
|
// Q5, drive selecion
|
2019-10-20 22:00:42 +00:00
|
|
|
c.addCardSoftSwitchR(0xA, func(_ *ioC0Page) uint8 {
|
2019-03-02 19:41:25 +00:00
|
|
|
c.selected = 0
|
2019-05-23 22:33:45 +00:00
|
|
|
return c.dataLatch
|
2019-10-20 22:00:42 +00:00
|
|
|
}, "Q5SELECT1")
|
|
|
|
c.addCardSoftSwitchR(0xB, func(_ *ioC0Page) uint8 {
|
2019-03-02 19:41:25 +00:00
|
|
|
c.selected = 1
|
|
|
|
return 0
|
2019-10-20 22:00:42 +00:00
|
|
|
}, "Q5SELECT2")
|
2019-03-02 19:41:25 +00:00
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
// Q6, Q7
|
2019-10-20 22:00:42 +00:00
|
|
|
for i := uint8(0xC); i <= 0xF; i++ {
|
2019-05-23 22:33:45 +00:00
|
|
|
iCopy := i
|
2019-10-20 22:00:42 +00:00
|
|
|
c.addCardSoftSwitchR(iCopy, func(_ *ioC0Page) uint8 {
|
2019-05-23 22:33:45 +00:00
|
|
|
return c.softSwitchQ6Q7(iCopy, 0)
|
2019-10-20 22:00:42 +00:00
|
|
|
}, "Q6Q7")
|
|
|
|
c.addCardSoftSwitchW(iCopy, func(_ *ioC0Page, value uint8) {
|
2019-05-23 22:33:45 +00:00
|
|
|
c.softSwitchQ6Q7(iCopy, value)
|
2019-10-20 22:00:42 +00:00
|
|
|
}, "Q6Q7")
|
2019-03-03 22:54:43 +00:00
|
|
|
}
|
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
c.cardBase.assign(a, slot)
|
|
|
|
}
|
2019-03-03 22:54:43 +00:00
|
|
|
|
2019-12-02 21:43:40 +00:00
|
|
|
/*
|
|
|
|
Stepper motor to position the track.
|
|
|
|
|
|
|
|
There are a number of group of four magnets. The stepper motor can be thought as a long
|
|
|
|
line of groups of magnets, each group on the same configuration. We call phase each of those
|
|
|
|
magnets. The cog is attracted to the enabled magnets, and can stay aligned to a magnet or
|
|
|
|
between two.
|
|
|
|
|
|
|
|
Phases (magents): 3 2 1 0 3 2 1 0 3 2 1 0
|
|
|
|
Cog direction (step withn a group): 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
|
|
|
|
|
|
|
|
We will consider that the cog would go to the prefferred postion if there is one. Independenly
|
|
|
|
of the previous position. The previous position is only used to know if it goes up or down
|
|
|
|
a full group.
|
|
|
|
*/
|
|
|
|
|
2019-12-01 18:48:29 +00:00
|
|
|
const (
|
2019-12-02 21:43:40 +00:00
|
|
|
undefinedPosition = -1
|
|
|
|
maxStep = 68 * 2 // What is the maximum quarter tracks a DiskII can go?
|
|
|
|
stepsPerGroup = 8
|
|
|
|
stepsPerTrack = 4
|
2019-12-01 18:48:29 +00:00
|
|
|
)
|
|
|
|
|
2019-12-02 21:43:40 +00:00
|
|
|
var cogPositions = []int{
|
|
|
|
undefinedPosition, // 0000, phases active
|
|
|
|
0, // 0001
|
|
|
|
2, // 0010
|
|
|
|
1, // 0011
|
|
|
|
4, // 0100
|
|
|
|
undefinedPosition, // 0101
|
|
|
|
3, // 0110
|
|
|
|
2, // 0111
|
|
|
|
6, // 1000
|
|
|
|
7, // 1001
|
|
|
|
undefinedPosition, // 1010
|
|
|
|
0, // 1011
|
|
|
|
5, // 1100
|
|
|
|
6, // 1101
|
|
|
|
4, // 1110
|
|
|
|
undefinedPosition, // 1111
|
2019-12-01 18:48:29 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 21:43:40 +00:00
|
|
|
func moveStep(phases uint8, prevStep int) int {
|
2019-12-01 18:48:29 +00:00
|
|
|
|
2019-12-02 21:43:40 +00:00
|
|
|
//fmt.Printf("magnets: 0x%x\n", phases)
|
2019-12-01 18:48:29 +00:00
|
|
|
|
2019-12-02 21:43:40 +00:00
|
|
|
cogPosition := cogPositions[phases]
|
|
|
|
if cogPosition == undefinedPosition {
|
2019-12-01 18:48:29 +00:00
|
|
|
// Don't move if magnets don't push on a defined direction.
|
|
|
|
return prevStep
|
|
|
|
}
|
|
|
|
|
2019-12-02 21:43:40 +00:00
|
|
|
prevPosition := prevStep % stepsPerGroup // Direction, step in the current group of magnets.
|
|
|
|
delta := cogPosition - prevPosition
|
2019-12-01 18:48:29 +00:00
|
|
|
if delta < 0 {
|
2019-12-02 21:43:40 +00:00
|
|
|
delta = delta + stepsPerGroup
|
2019-12-01 18:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var nextStep int
|
|
|
|
if delta < 4 {
|
|
|
|
// Steps up
|
|
|
|
nextStep = prevStep + delta
|
|
|
|
if nextStep > maxStep {
|
|
|
|
nextStep = maxStep
|
|
|
|
}
|
|
|
|
} else if delta == 4 {
|
|
|
|
// Don't move if magnets push on the oposite direction
|
|
|
|
nextStep = prevStep
|
|
|
|
} else { // delta > 4
|
|
|
|
// Steps down
|
2019-12-02 21:43:40 +00:00
|
|
|
nextStep = prevStep + delta - stepsPerGroup
|
2019-12-01 18:48:29 +00:00
|
|
|
if nextStep < 0 {
|
|
|
|
nextStep = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nextStep
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:00:42 +00:00
|
|
|
func (c *cardDisk2) softSwitchQ6Q7(index uint8, in uint8) uint8 {
|
2019-05-23 22:33:45 +00:00
|
|
|
switch index {
|
|
|
|
case 0xC: // Q6L
|
|
|
|
c.q6 = false
|
|
|
|
case 0xD: // Q6H
|
|
|
|
c.q6 = true
|
|
|
|
case 0xE: // Q/L
|
|
|
|
c.q7 = false
|
|
|
|
case 0xF: // Q7H
|
|
|
|
c.q7 = true
|
2019-03-03 22:54:43 +00:00
|
|
|
}
|
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
c.processQ6Q7(in)
|
|
|
|
if index&1 == 0 {
|
|
|
|
// All even addresses return the last dataLatch
|
|
|
|
return c.dataLatch
|
2019-03-03 22:54:43 +00:00
|
|
|
}
|
2019-06-07 15:42:18 +00:00
|
|
|
return 0
|
2019-05-23 22:33:45 +00:00
|
|
|
}
|
2019-03-03 22:54:43 +00:00
|
|
|
|
2019-05-23 22:33:45 +00:00
|
|
|
func (c *cardDisk2) processQ6Q7(in uint8) {
|
|
|
|
d := &c.drive[c.selected]
|
|
|
|
if d.diskette == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !c.q6 {
|
|
|
|
if !c.q7 { // Q6L-Q7L: Read
|
2019-12-02 21:43:40 +00:00
|
|
|
track := d.tracksStep / stepsPerTrack
|
2019-05-23 22:33:45 +00:00
|
|
|
c.dataLatch, d.position = d.diskette.read(track, d.position)
|
|
|
|
} else { // Q6L-Q7H: Write the dataLatch value to disk. Shift data out
|
2019-12-02 21:43:40 +00:00
|
|
|
track := d.tracksStep / stepsPerTrack
|
2019-05-23 22:33:45 +00:00
|
|
|
d.position = d.diskette.write(track, d.position, c.dataLatch)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !c.q7 { // Q6H-Q7L: Sense write protect / prewrite state
|
|
|
|
// Bit 7 of the control status register means write protected
|
|
|
|
c.dataLatch = 0 // Never write protected
|
|
|
|
} else { // Q6H-Q7H: Load data into the controller
|
|
|
|
c.dataLatch = in
|
|
|
|
}
|
2019-03-03 22:54:43 +00:00
|
|
|
}
|
2019-03-02 17:33:50 +00:00
|
|
|
}
|
2019-03-04 23:00:12 +00:00
|
|
|
|
2019-03-10 23:12:34 +00:00
|
|
|
func (d *cardDisk2Drive) insertDiskette(dt *diskette16sector) {
|
|
|
|
d.diskette = dt
|
2019-03-04 23:00:12 +00:00
|
|
|
}
|
2019-05-17 21:28:20 +00:00
|
|
|
|
2019-10-05 23:26:00 +00:00
|
|
|
func (c *cardDisk2) save(w io.Writer) error {
|
|
|
|
err := binary.Write(w, binary.BigEndian, c.selected)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Write(w, binary.BigEndian, c.dataLatch)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Write(w, binary.BigEndian, c.q6)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Write(w, binary.BigEndian, c.q7)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = c.drive[0].save(w)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = c.drive[1].save(w)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return c.cardBase.save(w)
|
2019-05-17 21:28:20 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 23:26:00 +00:00
|
|
|
func (c *cardDisk2) load(r io.Reader) error {
|
|
|
|
err := binary.Read(r, binary.BigEndian, &c.selected)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Read(r, binary.BigEndian, &c.dataLatch)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Read(r, binary.BigEndian, &c.q6)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Read(r, binary.BigEndian, &c.q7)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = c.drive[0].load(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = c.drive[1].load(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return c.cardBase.load(r)
|
2019-05-17 21:28:20 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 23:26:00 +00:00
|
|
|
func (d *cardDisk2Drive) save(w io.Writer) error {
|
2019-12-01 18:48:29 +00:00
|
|
|
err := binary.Write(w, binary.BigEndian, d.power)
|
2019-10-05 23:26:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-02 21:43:40 +00:00
|
|
|
err = binary.Write(w, binary.BigEndian, d.phases)
|
2019-10-05 23:26:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-01 18:48:29 +00:00
|
|
|
err = binary.Write(w, binary.BigEndian, d.tracksStep)
|
2019-10-05 23:26:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Write(w, binary.BigEndian, d.position)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2019-05-17 21:28:20 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 23:26:00 +00:00
|
|
|
func (d *cardDisk2Drive) load(r io.Reader) error {
|
2019-12-01 18:48:29 +00:00
|
|
|
err := binary.Read(r, binary.BigEndian, &d.power)
|
2019-10-05 23:26:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-02 21:43:40 +00:00
|
|
|
err = binary.Read(r, binary.BigEndian, &d.phases)
|
2019-10-05 23:26:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-01 18:48:29 +00:00
|
|
|
err = binary.Read(r, binary.BigEndian, &d.tracksStep)
|
2019-10-05 23:26:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Read(r, binary.BigEndian, &d.position)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2019-05-17 21:28:20 +00:00
|
|
|
}
|