refactor Disk ][ stepper motor emulation based on hardware teardown
This commit is contained in:
parent
66a4180747
commit
f310bae003
|
@ -14,6 +14,8 @@ epple2_SOURCES = a2colorsobserved.cpp addressbus.cpp analogtv.cpp apple2.cpp \
|
||||||
applentsc.cpp card.cpp cassette.cpp cassettein.cpp cassetteout.cpp \
|
applentsc.cpp card.cpp cassette.cpp cassettein.cpp cassetteout.cpp \
|
||||||
clipboardhandler.cpp clockcard.cpp \
|
clipboardhandler.cpp clockcard.cpp \
|
||||||
configep2.cpp cpu.cpp diskcontroller.cpp drive.cpp drivemotor.cpp \
|
configep2.cpp cpu.cpp diskcontroller.cpp drive.cpp drivemotor.cpp \
|
||||||
|
disk2readwritehead.cpp disk2steppermotor.cpp disk2steppermotorcan.cpp disk2steppermotorrotor.cpp \
|
||||||
|
magneticfield.cpp movable.cpp \
|
||||||
emptyslot.cpp emulator.cpp firmwarecard.cpp gui.cpp hypermode.cpp \
|
emptyslot.cpp emulator.cpp firmwarecard.cpp gui.cpp hypermode.cpp \
|
||||||
keyboard.cpp keyboardbuffermode.cpp languagecard.cpp filterchroma.cpp \
|
keyboard.cpp keyboardbuffermode.cpp languagecard.cpp filterchroma.cpp \
|
||||||
filterluma.cpp lss.cpp main.cpp memory.cpp \
|
filterluma.cpp lss.cpp main.cpp memory.cpp \
|
||||||
|
@ -21,7 +23,7 @@ memorychip.cpp memoryrow.cpp memorystrapping.cpp memoryrandomaccess.cpp \
|
||||||
paddlebuttonstates.cpp \
|
paddlebuttonstates.cpp \
|
||||||
paddles.cpp picturegenerator.cpp powerupreset.cpp raminitializer.cpp \
|
paddles.cpp picturegenerator.cpp powerupreset.cpp raminitializer.cpp \
|
||||||
screenimage.cpp slots.cpp speakerclicker.cpp standardin.cpp \
|
screenimage.cpp slots.cpp speakerclicker.cpp standardin.cpp \
|
||||||
standardinproducer.cpp standardout.cpp steppermotor.cpp textcharacters.cpp \
|
standardinproducer.cpp standardout.cpp textcharacters.cpp \
|
||||||
timable.cpp video.cpp videoaddressing.cpp videomode.cpp \
|
timable.cpp video.cpp videoaddressing.cpp videomode.cpp \
|
||||||
videostaticgenerator.cpp wozfile.cpp \
|
videostaticgenerator.cpp wozfile.cpp \
|
||||||
Circuit.cpp Common.cpp Cpu6502.cpp Cpu6502Helper.cpp Emu6502.cpp SegmentCache.cpp \
|
Circuit.cpp Common.cpp Cpu6502.cpp Cpu6502Helper.cpp Emu6502.cpp SegmentCache.cpp \
|
||||||
|
@ -31,13 +33,15 @@ tinyfiledialogs.cpp
|
||||||
noinst_HEADERS = a2colorsobserved.h addressbus.h analogtv.h apple2.h applentsc.h \
|
noinst_HEADERS = a2colorsobserved.h addressbus.h analogtv.h apple2.h applentsc.h \
|
||||||
card.h cassette.h cassettein.h cassetteout.h \
|
card.h cassette.h cassettein.h cassetteout.h \
|
||||||
clipboardhandler.h clockcard.h configep2.h cpu.h \
|
clipboardhandler.h clockcard.h configep2.h cpu.h \
|
||||||
|
disk2readwritehead.h disk2steppermotor.h disk2steppermotorcan.h disk2steppermotorrotor.h \
|
||||||
|
magneticfield.h movable.h \
|
||||||
diskcontroller.h drive.h drivemotor.h e2const.h emptyslot.h emulator.h firmwarecard.h font3x5.h gui.h \
|
diskcontroller.h drive.h drivemotor.h e2const.h emptyslot.h emulator.h firmwarecard.h font3x5.h gui.h \
|
||||||
hypermode.h keyboardbuffermode.h keyboard.h languagecard.h filterchroma.h \
|
hypermode.h keyboardbuffermode.h keyboard.h languagecard.h filterchroma.h \
|
||||||
filterluma.h lss.h memory.h \
|
filterluma.h lss.h memory.h \
|
||||||
memorychip.h memoryrow.h memorystrapping.h memoryrandomaccess.h \
|
memorychip.h memoryrow.h memorystrapping.h memoryrandomaccess.h \
|
||||||
paddlebuttonstates.h paddles.h picturegenerator.h \
|
paddlebuttonstates.h paddles.h picturegenerator.h \
|
||||||
powerupreset.h raminitializer.h screenimage.h slots.h speakerclicker.h \
|
powerupreset.h raminitializer.h screenimage.h slots.h speakerclicker.h \
|
||||||
standardin.h standardinproducer.h standardout.h steppermotor.h \
|
standardin.h standardinproducer.h standardout.h \
|
||||||
textcharacterimages.h textcharacters.h timable.h util.h \
|
textcharacterimages.h textcharacters.h timable.h util.h \
|
||||||
videoaddressing.h video.h videomode.h videostaticgenerator.h wozfile.h \
|
videoaddressing.h video.h videomode.h videostaticgenerator.h wozfile.h \
|
||||||
Circuit.h Common.h Cpu6502.h Cpu6502Helper.h Emu6502.h SegmentCache.h SegmentTypes.h \
|
Circuit.h Common.h Cpu6502.h Cpu6502Helper.h Emu6502.h SegmentCache.h SegmentTypes.h \
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
epple2
|
||||||
|
|
||||||
|
Copyright © 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "disk2readwritehead.h"
|
||||||
|
|
||||||
|
Disk2ReadWriteHead::~Disk2ReadWriteHead() = default;
|
||||||
|
|
||||||
|
int Disk2ReadWriteHead::position() const {
|
||||||
|
return this->current_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disk2ReadWriteHead::move_by(const int delta_position) {
|
||||||
|
const int candidate_position = this->current_position + delta_position;
|
||||||
|
|
||||||
|
const bool ok_position = (0 <= candidate_position && candidate_position < C_POSITION);
|
||||||
|
if (ok_position) {
|
||||||
|
this->current_position = candidate_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok_position;
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
epple2
|
||||||
|
|
||||||
|
Copyright © 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISK2READWRITEHEAD_H
|
||||||
|
#define DISK2READWRITEHEAD_H
|
||||||
|
|
||||||
|
#include "movable.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disk ][ read/write head assembly
|
||||||
|
*
|
||||||
|
* Represents the read/write head assembly of a Disk ][ floppy drive.
|
||||||
|
* The assembly includes a "sled" that can move back and forth, with a
|
||||||
|
* read/write head attached to it. The sled is moved by the rotor from
|
||||||
|
* the stepper motor.
|
||||||
|
*
|
||||||
|
* Rotation of the rotor will cause the sled to move back or forth, along
|
||||||
|
* with the read/write head, positioning it over a "track" on the floppy disk.
|
||||||
|
*
|
||||||
|
* If the rotor attempts to move the sled too far, the sled will resist and
|
||||||
|
* prevent the rotor from rotating.
|
||||||
|
*/
|
||||||
|
class Disk2ReadWriteHead : public Movable {
|
||||||
|
public:
|
||||||
|
virtual ~Disk2ReadWriteHead();
|
||||||
|
|
||||||
|
int position() const;
|
||||||
|
virtual bool move_by(int delta_position);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Total count of possible positions for this read/write head.
|
||||||
|
*
|
||||||
|
* A position will represent a track on a floppy disk.
|
||||||
|
* The number of tracks really depends on the floppy disk.
|
||||||
|
* The Disk ][ has a "stop" at track zero, but not at the high end,
|
||||||
|
* so this is where it can vary from disk to disk.
|
||||||
|
* Typical disks have 35 tracks, and allow quarter-track precision.
|
||||||
|
*
|
||||||
|
* In this emulator, we adhere to the WOZ v2 specification for floppy disks.
|
||||||
|
* It allows for up to 40 tracks (with quarter-track precision):
|
||||||
|
*
|
||||||
|
* 0=t0, 1=t0.25, 2=t0.5, 3=t0.75, 4=t1, ... 140=t35.00 ... 159=t39.75
|
||||||
|
*/
|
||||||
|
const static int C_POSITION = 40*4;
|
||||||
|
|
||||||
|
int current_position = C_POSITION/2; // random start
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DISK2READWRITEHEAD_H
|
|
@ -0,0 +1,396 @@
|
||||||
|
/*
|
||||||
|
epple2
|
||||||
|
|
||||||
|
Copyright © 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
See UA2, 9-7.
|
||||||
|
|
||||||
|
The following is based on research, experimentation, and other
|
||||||
|
help from John Morris, Tom Greene, Michael Guidero, and Lane Roathe.
|
||||||
|
|
||||||
|
Stepper motor has 2 cans each with a center-tapped coil, allowing
|
||||||
|
for current flow in one of either direction, causing a N/S or S/N
|
||||||
|
polarity fo the top/bottom of the can. Each top/bottom has fingers
|
||||||
|
bending down/up towards the center, the fingers from alternate surfaces
|
||||||
|
being interlased. These fingers transfer the polarity from the coil.
|
||||||
|
|
||||||
|
Example of coil-0, controlled by phases 0 and 2,
|
||||||
|
the two possible energized states, N/S and S/N,
|
||||||
|
generated by the signal from PH0 or PH2 driving
|
||||||
|
half the coil. Phase 0 drives top half of coil one way:
|
||||||
|
|
||||||
|
side view: top of can coil leads
|
||||||
|
----v-------v------NNN-----
|
||||||
|
N N N v-<-<-<-----PHASE-0 <--<-- ON
|
||||||
|
N S N S NNN@
|
||||||
|
N S N S SSS@>->->--center-tap--E
|
||||||
|
N S N S S @
|
||||||
|
S S S \-----------PHASE-2 OFF
|
||||||
|
--------^-------^--SSS-----
|
||||||
|
bottom of can
|
||||||
|
|
||||||
|
And to cause opposing polarity, phase 2 drives the bottom half
|
||||||
|
of the coil the other way:
|
||||||
|
|
||||||
|
side view: top of can coil leads
|
||||||
|
----v-------v------SSS-----
|
||||||
|
S S S /-----------PHASE-0 OFF
|
||||||
|
S N S N S @
|
||||||
|
S N S N SSS@>->->--center-tap--E
|
||||||
|
S N S N NNN@
|
||||||
|
N N N ^-<-<-<-----PHASE-2 <--<-- ON
|
||||||
|
--------^-------^--NNN-----
|
||||||
|
bottom of can
|
||||||
|
|
||||||
|
Only one coil at a time will (typically) have current,
|
||||||
|
one causing "upward" flow in the diagram, or the other
|
||||||
|
causing "downward" flow. The flow causes either N/S
|
||||||
|
or (reverse) S/N polarity of alternating fingers.
|
||||||
|
|
||||||
|
Inside the coils is a rotor with NSNSNS... fixed polarity magnets
|
||||||
|
(actually one cylindrical magnet with vertical stripes of polarity).
|
||||||
|
|
||||||
|
S on a finger will attract an N on the rotor, possibly causing it to move.
|
||||||
|
The rotor is attached to the track arm. Clockwise rotor movement causes the
|
||||||
|
arm to move in one direction, and counter-clockwise the other.
|
||||||
|
|
||||||
|
The two cans are offset rotationally from each other by one-half a finger width.
|
||||||
|
This offset is what allows the cans to alternately pull on the rotor (causing it
|
||||||
|
to rotate).
|
||||||
|
|
||||||
|
top of can 0 coil leads
|
||||||
|
----v-------v----------
|
||||||
|
v v @-----------PHASE-0
|
||||||
|
v ^ v ^ @--center-tap--E
|
||||||
|
^ ^ @-----------PHASE-2
|
||||||
|
--------^-------^------
|
||||||
|
bottom of can 0
|
||||||
|
|
||||||
|
top of can 1 coil leads
|
||||||
|
------v-------v--------
|
||||||
|
v v @-----------PHASE-1
|
||||||
|
v ^ v ^ @--center-tap--E
|
||||||
|
^ ^ @-----------PHASE-3
|
||||||
|
----------^-------^----
|
||||||
|
bottom of can 1
|
||||||
|
|
||||||
|
The combination of the two cans allows for 8 positions of the rotor (in 2 tracks),
|
||||||
|
yielding quarter-track precision.
|
||||||
|
Quarter tracks are achieved by activating two fingers (one in each can) adjacent
|
||||||
|
to one another, causing the rotor to be positioned BETWEEN the two fingers, rather
|
||||||
|
than ALIGNED with one finger (as is the case for on-track or half-track positions).
|
||||||
|
|
||||||
|
Example of two cans, with N/S charge in can 0 and no charge in can 1,
|
||||||
|
and resulting rotational position of the rotor (each one repeats 12 times around
|
||||||
|
the circle):
|
||||||
|
|
||||||
|
/v-------\ 7 N
|
||||||
|
|N S |
|
||||||
|
\----^---/ 3 S* <-- S charge of can is on finger position 3
|
||||||
|
|
||||||
|
/--v-----\ 5 O
|
||||||
|
| |
|
||||||
|
\------^-/ 1 O
|
||||||
|
|
||||||
|
|SS-NNN-S| <-- rotor, center of N pole (position 3) is aligned with
|
||||||
|
* the S (position 3) of the top can
|
||||||
|
76543210 <-- our (arbitrary) indices of the 8 rotor/finger positions
|
||||||
|
|
||||||
|
To calculate postion (of center of N pole), average positions of S poles in both cans,
|
||||||
|
but if only one can is on, use that one's position directly.
|
||||||
|
|
||||||
|
But 7&1 is a special case (calculation-wise): since it's a circle, they
|
||||||
|
"wrap around", position _0_ being between them:
|
||||||
|
|
||||||
|
/v-------\ 7 S* top of can 0
|
||||||
|
|S N |
|
||||||
|
\----^---/ 3 N bot of can 0
|
||||||
|
|
||||||
|
/--v-----\ 5 N top of can 1
|
||||||
|
| N S |
|
||||||
|
\------^-/ 1 S* bot of can 1
|
||||||
|
|
||||||
|
|N-SSS-NN| <-- rotor, center of N pole (position 0) is aligned between
|
||||||
|
* the two S poles in the two cans
|
||||||
|
76543210
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Adjacent fingers within a can are of reverse polarity, and are 4 positions apart.
|
||||||
|
Cans are offset from each other by 2 positions.
|
||||||
|
|
||||||
|
PH = phase 0-4 (on or off)
|
||||||
|
CAN = can 0-1 (north, south, or off)
|
||||||
|
|
||||||
|
CAN0 = PH0+PH2
|
||||||
|
CAN1 = PH1+PH3
|
||||||
|
|
||||||
|
PHASE CAN
|
||||||
|
3210 == 10
|
||||||
|
---- ---- ----
|
||||||
|
0000 OO. 0/ 0
|
||||||
|
0001 ON. 0/+1
|
||||||
|
0010 NO. +1/ 0
|
||||||
|
0011 NN. +1/+1
|
||||||
|
0100 OS. 0/-1
|
||||||
|
0101 0000
|
||||||
|
0110 NS. +1/-1
|
||||||
|
0111 0010
|
||||||
|
1000 SO. -1/ 0
|
||||||
|
1001 SN. -1/+1
|
||||||
|
1010 0000
|
||||||
|
1011 0001
|
||||||
|
1100 SS. -1/-1
|
||||||
|
1101 1000
|
||||||
|
1110 0100
|
||||||
|
1111 0000
|
||||||
|
|
||||||
|
PHASE 3 2 1 0
|
||||||
|
| | | |
|
||||||
|
/ X \
|
||||||
|
/ / \ \
|
||||||
|
/ / \ \
|
||||||
|
/ / \ \
|
||||||
|
/ E | | E \
|
||||||
|
| | | | | |
|
||||||
|
+oo+oo+ +oo+oo+
|
||||||
|
(CAN-1) (CAN-0)
|
||||||
|
|
||||||
|
PHASE to CAN translation algorithm:
|
||||||
|
can[0] = can[1] = 0
|
||||||
|
if phase 0, ++can[0]
|
||||||
|
if phase 2, --can[0]
|
||||||
|
if phase 1, ++can[1]
|
||||||
|
if phase 3, --can[1]
|
||||||
|
resulting can[i] value indicates:
|
||||||
|
+1, N north
|
||||||
|
0, O off
|
||||||
|
-1, S south
|
||||||
|
|
||||||
|
For each of the 8 on states, the rotor will (usually) be forced to one position.
|
||||||
|
(The one exception is if the rotor happens to be positioned in exactly the
|
||||||
|
opposing position, then the turning on of a magnet won't be able to move the
|
||||||
|
rotor into the correct position.)
|
||||||
|
|
||||||
|
If both coils are off (OO), then the rotor is free to rotate,
|
||||||
|
and could be in any possible position (and we can't determine where).
|
||||||
|
|
||||||
|
In terms of state transitions, we could go from/to:
|
||||||
|
from: to:
|
||||||
|
OFF any 8 ON (with random movement)
|
||||||
|
any 8 ON OFF (with no rotor movement)
|
||||||
|
any 8 ON any other 7 ON (with movement)
|
||||||
|
|
||||||
|
|
||||||
|
State transitions for on-states:
|
||||||
|
-+ -0 -- 0- +- +0 ++ 0+
|
||||||
|
\to | SN: SO: SS: OS: NS: NO: NN: ON:
|
||||||
|
from \ | N-SSS-NN NN-SSS-N NNN-SSS- -NNN-SSS S-NNN-SS SS-NNN-S SSS-NNN- -SSS-NNN
|
||||||
|
--------+-----------------------------------------------------------------------------------------------
|
||||||
|
SN: |
|
||||||
|
N-SSS-NN| 0 +1 +2 +3 0 -3 -2 -1
|
||||||
|
SO: |
|
||||||
|
NN-SSS-N| -1 0 +1 +2 +3 0 -3 -2
|
||||||
|
SS: |
|
||||||
|
NNN-SSS-| -2 -1 0 +1 +2 +3 0 -3
|
||||||
|
OS: |
|
||||||
|
-NNN-SSS| -3 -2 -1 0 +1 +2 +3 0
|
||||||
|
NS: |
|
||||||
|
S-NNN-SS| 0 -3 -2 -1 0 +1 +2 +3
|
||||||
|
NO: |
|
||||||
|
SS-NNN-S| +3 0 -3 -2 -1 0 +1 +2
|
||||||
|
NN: |
|
||||||
|
SSS-NNN-| +2 +3 0 -3 -2 -1 0 +1
|
||||||
|
ON: |
|
||||||
|
-SSS-NNN| +1 +2 +3 0 -3 -2 -1 0
|
||||||
|
|
||||||
|
0 == no movement
|
||||||
|
|
||||||
|
movement = (new_can[0]-old_can[0]) + (new_can[1]-old_can[1])
|
||||||
|
|
||||||
|
The concepts of track, half-track, and quarter-track are arbitrary, and are
|
||||||
|
of no significance to the stepper motor. As far as the motor is concerned, it's
|
||||||
|
output is just a positive or negative movement (rotation), by one or more
|
||||||
|
"steps". Also, positive and negative are arbitrary
|
||||||
|
designations; they simply indicate opposite directions of rotation.
|
||||||
|
|
||||||
|
Input is just the 4 phase switches.
|
||||||
|
|
||||||
|
Internal state is the position of the rotor.
|
||||||
|
We also keep the 4 phase settings in our internal state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "disk2steppermotor.h"
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
Disk2StepperMotor::Disk2StepperMotor(Movable& movable):
|
||||||
|
can_0(0, 2),
|
||||||
|
can_1(1, 2),
|
||||||
|
rotor(2, movable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2StepperMotor::set_phase(int p, const bool on) {
|
||||||
|
assert(0 <= p && p < 4);
|
||||||
|
|
||||||
|
Disk2StepperMotorCan *can;
|
||||||
|
if ((p&1) == 0) {
|
||||||
|
can = &this->can_0;
|
||||||
|
} else {
|
||||||
|
can = &this->can_1;
|
||||||
|
}
|
||||||
|
can->set_lead(p/2, on);
|
||||||
|
build_magnetic_field();
|
||||||
|
|
||||||
|
this->rotor.pend_rotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2StepperMotor::build_magnetic_field() {
|
||||||
|
if (magnetized()) {
|
||||||
|
this->field.turn_on_at(magnetic_position());
|
||||||
|
} else {
|
||||||
|
this->field.turn_off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disk2StepperMotor::magnetized() const {
|
||||||
|
return this->can_0.magnetized() || this->can_1.magnetized();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Disk2StepperMotor::magnetic_position() const {
|
||||||
|
assert(magnetized());
|
||||||
|
if (this->can_0.magnetized() && this->can_1.magnetized()) {
|
||||||
|
if (this->can_1.magnetic_position()==6 && this->can_0.magnetic_position()==0) { // special case at our "seam"
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
// average position between poles of both cans:
|
||||||
|
return (this->can_0.magnetic_position()+this->can_1.magnetic_position())/2;
|
||||||
|
}
|
||||||
|
if (this->can_0.magnetized()) {
|
||||||
|
return this->can_0.magnetic_position();
|
||||||
|
}
|
||||||
|
if (this->can_1.magnetized()) {
|
||||||
|
return this->can_1.magnetic_position();
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2StepperMotor::tick() {
|
||||||
|
this->rotor.tick(this->field);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SpyMovable : public Movable {
|
||||||
|
public:
|
||||||
|
virtual ~SpyMovable() = default;
|
||||||
|
int position() const { return last_delta; }
|
||||||
|
virtual bool move_by(int delta) { last_delta = delta; return true; }
|
||||||
|
int last_delta = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void advance(Disk2StepperMotor &d) {
|
||||||
|
for (int i = 0; i < 1007; ++i) {
|
||||||
|
d.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_phases_to(Disk2StepperMotor &d, int p) {
|
||||||
|
d.set_phase(0, (p&1) != 0);
|
||||||
|
d.tick();
|
||||||
|
d.set_phase(1, (p&2) != 0);
|
||||||
|
d.tick();
|
||||||
|
d.set_phase(2, (p&4) != 0);
|
||||||
|
d.tick();
|
||||||
|
d.set_phase(3, (p&8) != 0);
|
||||||
|
advance(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_move(SpyMovable &moved, int p_from, int p_to) {
|
||||||
|
Disk2StepperMotor d(moved);
|
||||||
|
|
||||||
|
if (p_from == 0x4 || p_from == 0xE) {
|
||||||
|
// oops, give it a nudge first
|
||||||
|
d.set_phase(0,true);
|
||||||
|
d.set_phase(1,true);
|
||||||
|
advance(d);
|
||||||
|
// and reset
|
||||||
|
d.set_phase(0,false);
|
||||||
|
d.set_phase(1,false);
|
||||||
|
advance(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up initial state
|
||||||
|
set_phases_to(d, p_from);
|
||||||
|
|
||||||
|
// clear any movement from setup
|
||||||
|
moved.move_by(0);
|
||||||
|
|
||||||
|
// test moving to the final state
|
||||||
|
set_phases_to(d, p_to);
|
||||||
|
|
||||||
|
return moved.position(); // return how far we actually were moved
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disk2StepperMotor::test() {
|
||||||
|
int phase[0x10] = {0x9,0x8,0xD,0xC,0x4,0xE,0x6,0x2,0x7,0x3,0x1,0xB,0x0,0x5,0xA,0xF};
|
||||||
|
int move[0x0C][0x10] = {
|
||||||
|
//fr\to 9 8 D C 4 E 6 2 7 3 1 B 0 5 A F
|
||||||
|
/*9*/ { 0,-1,-1,-2,-3,-3, 0,+3,+3,+2,+1,+1, 0, 0, 0, 0},
|
||||||
|
/*8*/ {+1, 0, 0,-1,-2,-2,-3, 0, 0,+3,+2,+2, 0, 0, 0, 0},
|
||||||
|
/*D*/ {+1, 0, 0,-1,-2,-2,-3, 0, 0,+3,+2,+2, 0, 0, 0, 0},
|
||||||
|
/*C*/ {+2,+1,+1, 0,-1,-1,-2,-3,-3, 0,+3,+3, 0, 0, 0, 0},
|
||||||
|
/*4*/ {+3,+2,+2,+1, 0, 0,-1,-2,-2,-3, 0, 0, 0, 0, 0, 0},
|
||||||
|
/*E*/ {+3,+2,+2,+1, 0, 0,-1,-2,-2,-3, 0, 0, 0, 0, 0, 0},
|
||||||
|
/*6*/ { 0,+3,+3,+2,+1,+1, 0,-1,-1,-2,-3,-3, 0, 0, 0, 0},
|
||||||
|
/*2*/ {-3, 0, 0,+3,+2,+2,+1, 0, 0,-1,-2,-2, 0, 0, 0, 0},
|
||||||
|
/*7*/ {-3, 0, 0,+3,+2,+2,+1, 0, 0,-1,-2,-2, 0, 0, 0, 0},
|
||||||
|
/*3*/ {-2,-3,-3, 0,+3,+3,+2,+1,+1, 0,-1,-1, 0, 0, 0, 0},
|
||||||
|
/*1*/ {-1,-2,-2,-3, 0, 0,+3,+2,+2,+1, 0, 0, 0, 0, 0, 0},
|
||||||
|
/*B*/ {-1,-2,-2,-3, 0, 0,+3,+2,+2,+1, 0, 0, 0, 0, 0, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::printf(" %s\n", "from_phase -> to_phase ? expected_move : rotor_before +/-actual_move = rotor_after [*=failed]");
|
||||||
|
std::printf(" %s\n", " 9 8 D C 4 E 6 2 7 3 1 B 0 5 A F");
|
||||||
|
|
||||||
|
SpyMovable moved;
|
||||||
|
|
||||||
|
bool bad = false;
|
||||||
|
for (int i_from = 0; i_from < 0x0C; ++i_from) {
|
||||||
|
std::printf("%1X: ", phase[i_from]);
|
||||||
|
for (int i_to = 0; i_to < 0x10; ++i_to) {
|
||||||
|
int expected = move[i_from][i_to];
|
||||||
|
int actual = test_move(moved,phase[i_from],phase[i_to]);
|
||||||
|
std::printf(
|
||||||
|
"%1X->%1X?%+2d:%+2d=%s ",
|
||||||
|
phase[i_from],phase[i_to],expected,actual,expected==actual?" ":"*");
|
||||||
|
if (actual != expected) {
|
||||||
|
bad = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return !bad;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
epple2
|
||||||
|
|
||||||
|
Copyright © 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISK2STEPPERMOTOR_H
|
||||||
|
#define DISK2STEPPERMOTOR_H
|
||||||
|
|
||||||
|
#include "disk2steppermotorcan.h"
|
||||||
|
#include "disk2steppermotorrotor.h"
|
||||||
|
#include "movable.h"
|
||||||
|
#include "magneticfield.h"
|
||||||
|
|
||||||
|
class Disk2StepperMotor {
|
||||||
|
public:
|
||||||
|
Disk2StepperMotor(Movable& movable);
|
||||||
|
|
||||||
|
void tick();
|
||||||
|
void set_phase(int i_phase_0_to_3, bool on);
|
||||||
|
|
||||||
|
static bool test();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void build_magnetic_field();
|
||||||
|
bool magnetized() const;
|
||||||
|
int magnetic_position() const;
|
||||||
|
|
||||||
|
Disk2StepperMotorCan can_0;
|
||||||
|
Disk2StepperMotorCan can_1;
|
||||||
|
|
||||||
|
MagneticField field;
|
||||||
|
|
||||||
|
Disk2StepperMotorRotor rotor;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DISK2STEPPERMOTOR_H
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
epple2
|
||||||
|
|
||||||
|
Copyright © 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "disk2steppermotorcan.h"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
Disk2StepperMotorCan::Disk2StepperMotorCan(int index, int total_stack):
|
||||||
|
index(index),
|
||||||
|
total_stack(total_stack) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2StepperMotorCan::set_lead(int i_lead, bool on) {
|
||||||
|
assert(0 <= i_lead && i_lead < 2);
|
||||||
|
if (i_lead == 0) {
|
||||||
|
this->lead_north = on;
|
||||||
|
} else {
|
||||||
|
this->lead_south = on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Disk2StepperMotorCan::charge() const {
|
||||||
|
int coil_charge = 0;
|
||||||
|
if (this->lead_north) {
|
||||||
|
++coil_charge;
|
||||||
|
}
|
||||||
|
if (this->lead_south) {
|
||||||
|
--coil_charge;
|
||||||
|
}
|
||||||
|
return coil_charge;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disk2StepperMotorCan::magnetized() const {
|
||||||
|
return this->charge() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Disk2StepperMotorCan::magnetic_position() const {
|
||||||
|
assert(magnetized());
|
||||||
|
return this->total_stack * (this->index + 2*(this->charge() < 0));
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
epple2
|
||||||
|
|
||||||
|
Copyright © 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISK2STEPPERMOTORCAN_H
|
||||||
|
#define DISK2STEPPERMOTORCAN_H
|
||||||
|
|
||||||
|
class Disk2StepperMotorCan {
|
||||||
|
public:
|
||||||
|
Disk2StepperMotorCan(int index, int total_stack);
|
||||||
|
|
||||||
|
bool magnetized() const;
|
||||||
|
int magnetic_position() const;
|
||||||
|
|
||||||
|
void set_lead(int index, bool on);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int charge() const;
|
||||||
|
|
||||||
|
const int index;
|
||||||
|
const int total_stack;
|
||||||
|
|
||||||
|
bool lead_north = false;
|
||||||
|
bool lead_south = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DISK2STEPPERMOTORCAN_H
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
epple2
|
||||||
|
|
||||||
|
Copyright © 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "disk2steppermotorrotor.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief positive_modulo
|
||||||
|
* @param x value to constrain
|
||||||
|
* @param n modulus
|
||||||
|
* @return constrained value
|
||||||
|
*/
|
||||||
|
static int positive_modulo(const int x, const int n) {
|
||||||
|
return (x%n + n) % n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
* Constrains x to within range (-n,n)
|
||||||
|
* representing movements around a circle
|
||||||
|
* @param x value to constrain
|
||||||
|
* @param n range
|
||||||
|
* @return constrained value
|
||||||
|
*/
|
||||||
|
static int cyclic_motion(int x, const int n) {
|
||||||
|
while (x <= -n) {
|
||||||
|
x += 2*n;
|
||||||
|
}
|
||||||
|
while (+n <= x) {
|
||||||
|
x -= 2*n;
|
||||||
|
}
|
||||||
|
return x%n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Disk2StepperMotorRotor::Disk2StepperMotorRotor(const int total_can_stack, Movable& movable):
|
||||||
|
total_can_stack(total_can_stack),
|
||||||
|
movable(movable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2StepperMotorRotor::pend_rotation() {
|
||||||
|
this->ticks_pending = 1000; // about 1 millisecond delay due to inertia
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2StepperMotorRotor::tick(const MagneticField &magnetic_field) {
|
||||||
|
if (this->ticks_pending) {
|
||||||
|
--this->ticks_pending;
|
||||||
|
if (!this->ticks_pending) {
|
||||||
|
if (magnetic_field.is_on()) {
|
||||||
|
rotate_to(magnetic_field.position());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2StepperMotorRotor::rotate_to(const int new_position) {
|
||||||
|
const int potential_movement = cyclic_motion(new_position-this->position, this->total_can_stack*2);
|
||||||
|
if (potential_movement && this->movable.move_by(potential_movement)) {
|
||||||
|
this->position = positive_modulo(this->position+potential_movement, this->total_can_stack*4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
epple2
|
||||||
|
|
||||||
|
Copyright © 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISK2STEPPERMOTORROTOR_H
|
||||||
|
#define DISK2STEPPERMOTORROTOR_H
|
||||||
|
|
||||||
|
#include "movable.h"
|
||||||
|
#include "magneticfield.h"
|
||||||
|
|
||||||
|
class Disk2StepperMotorRotor {
|
||||||
|
public:
|
||||||
|
Disk2StepperMotorRotor(const int total_can_stack, Movable& movable);
|
||||||
|
|
||||||
|
void pend_rotation();
|
||||||
|
void tick(const MagneticField &field);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void rotate_to(int new_position);
|
||||||
|
|
||||||
|
const int total_can_stack;
|
||||||
|
Movable& movable;
|
||||||
|
int position = 0;
|
||||||
|
int ticks_pending = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DISK2STEPPERMOTORROTOR_H
|
|
@ -20,8 +20,6 @@
|
||||||
DiskController::DiskController(ScreenImage& gui, int slot, bool lss13):
|
DiskController::DiskController(ScreenImage& gui, int slot, bool lss13):
|
||||||
gui(gui),
|
gui(gui),
|
||||||
slot(slot),
|
slot(slot),
|
||||||
drive1(diskBytes1,arm1),
|
|
||||||
drive2(diskBytes2,arm2),
|
|
||||||
currentDrive(&this->drive1),
|
currentDrive(&this->drive1),
|
||||||
load(false),
|
load(false),
|
||||||
write(false),
|
write(false),
|
||||||
|
@ -47,7 +45,7 @@ unsigned char DiskController::io(const unsigned short addr, const unsigned char
|
||||||
case 1: // TODO if phase-1 is on, it also acts as write-protect (UA2, 9-8)
|
case 1: // TODO if phase-1 is on, it also acts as write-protect (UA2, 9-8)
|
||||||
case 2:
|
case 2:
|
||||||
case 3:
|
case 3:
|
||||||
this->currentDrive->setMagnet(q,on);
|
this->currentDrive->set_phase(q,on);
|
||||||
this->gui.setTrack(this->slot, getCurrentDriveNumber(), getTrack());
|
this->gui.setTrack(this->slot, getCurrentDriveNumber(), getTrack());
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
|
@ -87,8 +85,8 @@ unsigned char DiskController::io(const unsigned short addr, const unsigned char
|
||||||
* (When the motor is on, that is.)
|
* (When the motor is on, that is.)
|
||||||
*/
|
*/
|
||||||
void DiskController::tick() {
|
void DiskController::tick() {
|
||||||
this->arm1.tick();
|
this->drive1.tick();
|
||||||
this->arm2.tick();
|
this->drive2.tick();
|
||||||
|
|
||||||
if (this->ioStepped) { // if we already ran it, above in io(), skip here
|
if (this->ioStepped) { // if we already ran it, above in io(), skip here
|
||||||
this->ioStepped = false;
|
this->ioStepped = false;
|
||||||
|
@ -101,6 +99,12 @@ void DiskController::tick() {
|
||||||
}
|
}
|
||||||
this->motor.tick(); // only need to send tick when motor is powered on
|
this->motor.tick(); // only need to send tick when motor is powered on
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO
|
||||||
|
* Every CPU clock, add 8 to your bit timing clock. If your bit timing clock is >= optimal bit timing,
|
||||||
|
* then inject the next bit and subtract the optimal bit timing from your bit timing clock.
|
||||||
|
* That will give you 125ns resolution on your bits being fed to the sequencer.
|
||||||
|
*/
|
||||||
rotateCurrentDisk();
|
rotateCurrentDisk();
|
||||||
|
|
||||||
// run two LSS cycles = 2MHz
|
// run two LSS cycles = 2MHz
|
||||||
|
|
|
@ -28,152 +28,128 @@
|
||||||
class DiskController : public Card
|
class DiskController : public Card
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
ScreenImage& gui;
|
ScreenImage& gui;
|
||||||
int slot;
|
int slot;
|
||||||
WozFile diskBytes1;
|
|
||||||
StepperMotor arm1;
|
|
||||||
Drive drive1;
|
|
||||||
|
|
||||||
WozFile diskBytes2;
|
Disk2Drive drive1;
|
||||||
StepperMotor arm2;
|
Disk2Drive drive2;
|
||||||
Drive drive2;
|
Disk2Drive* currentDrive;
|
||||||
|
|
||||||
Drive* currentDrive;
|
bool load; // Q6
|
||||||
|
bool write; // Q7
|
||||||
|
bool ioStepped;
|
||||||
|
|
||||||
bool load; // Q6
|
/*
|
||||||
bool write; // Q7
|
* Only one drive's motor can be on at a time,
|
||||||
bool ioStepped;
|
* so we only need one instance.
|
||||||
|
*/
|
||||||
|
DriveMotor motor;
|
||||||
|
|
||||||
/*
|
// Maintain a copy of the last thing on the data bus, so it can
|
||||||
* Only one drive's motor can be on at a time,
|
// be read by the LSS algorithm when needed.
|
||||||
* so we only need one instance.
|
std::uint8_t dataBusReadOnlyCopy;
|
||||||
*/
|
LSS lssp6rom; // the LSS PROM P6 chip (one command per sequence/state combination)
|
||||||
DriveMotor motor;
|
std::uint8_t dataRegister; // C3 the controller's LS323 data register
|
||||||
|
std::uint8_t seq; // A3 sequence control LS174 (current sequence number, 0-F)
|
||||||
|
// For ease of use, we store the 4-bit seq number in the _high order_ nibble here.
|
||||||
|
// On the real Apple the read pulse goes thru this LS174 too, but we don't emulate that here.
|
||||||
|
|
||||||
// Maintain a copy of the last thing on the data bus, so it can
|
std::uint8_t prev_seq; // remember previous seq, to determine if A7 changes (indicating write a 1 bit)
|
||||||
// be read by the LSS algorithm when needed.
|
std::uint8_t t; // used to keep track of 4 MPU cycles
|
||||||
std::uint8_t dataBusReadOnlyCopy;
|
|
||||||
LSS lssp6rom; // the LSS PROM P6 chip (one command per sequence/state combination)
|
|
||||||
std::uint8_t dataRegister; // C3 the controller's LS323 data register
|
|
||||||
std::uint8_t seq; // A3 sequence control LS174 (current sequence number, 0-F)
|
|
||||||
// For ease of use, we store the 4-bit seq number in the _high order_ nibble here.
|
|
||||||
// On the real Apple the read pulse goes thru this LS174 too, but we don't emulate that here.
|
|
||||||
|
|
||||||
std::uint8_t prev_seq; // remember previous seq, to determine if A7 changes (indicating write a 1 bit)
|
// TODO for a rev. 0 motherboard, the disk controller will auto reset the CPU (see UA2, 9-13)
|
||||||
std::uint8_t t; // used to keep track of 4 MPU cycles
|
|
||||||
|
|
||||||
// TODO for a rev. 0 motherboard, the disk controller will auto reset the CPU (see UA2, 9-13)
|
void writeBit(bool on) {
|
||||||
|
if (!this->motor.isOn()) {
|
||||||
void writeBit(bool on) {
|
return;
|
||||||
if (!this->motor.isOn()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->currentDrive->writeBit(on);
|
|
||||||
}
|
}
|
||||||
|
this->currentDrive->writeBit(on);
|
||||||
|
}
|
||||||
|
|
||||||
Drive& getDrive(const unsigned char drive)
|
Disk2Drive& getDrive(const unsigned char drive) {
|
||||||
{
|
return (drive == 0) ? this->drive1 : this->drive2;
|
||||||
return (drive == 0) ? this->drive1 : this->drive2;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Drive& getOtherDrive()
|
Disk2Drive& getOtherDrive() {
|
||||||
{
|
return (this->currentDrive == &this->drive1) ? this->drive2 : this->drive1;
|
||||||
return (this->currentDrive == &this->drive1) ? this->drive2 : this->drive1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void rotateCurrentDisk();
|
void rotateCurrentDisk();
|
||||||
void stepLss();
|
void stepLss();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DiskController(ScreenImage& gui, int slot, bool lss13);
|
DiskController(ScreenImage& gui, int slot, bool lss13);
|
||||||
~DiskController();
|
~DiskController();
|
||||||
|
|
||||||
void tick();
|
void tick();
|
||||||
virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing);
|
virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing);
|
||||||
|
|
||||||
void reset()
|
void reset() {
|
||||||
{
|
this->gui.setIO(this->slot,getCurrentDriveNumber(),false);
|
||||||
this->gui.setIO(this->slot,getCurrentDriveNumber(),false);
|
this->gui.clearCurrentDrive(this->slot,getCurrentDriveNumber());
|
||||||
this->gui.clearCurrentDrive(this->slot,getCurrentDriveNumber());
|
|
||||||
|
|
||||||
this->currentDrive = &this->drive1;
|
this->currentDrive = &this->drive1;
|
||||||
this->motor.reset();
|
this->motor.reset();
|
||||||
|
|
||||||
this->gui.setCurrentDrive(this->slot,getCurrentDriveNumber(),getTrack(),false);
|
this->gui.setCurrentDrive(this->slot,getCurrentDriveNumber(),getTrack(),false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadDisk(unsigned char drive, const std::string& fnib)
|
void loadDisk(unsigned char drive, const std::string& fnib) {
|
||||||
{
|
if (!this->getDrive(drive).loadDisk(fnib)) {
|
||||||
if (!this->getDrive(drive).loadDisk(fnib))
|
return;
|
||||||
{
|
}
|
||||||
return;
|
this->gui.setDiskFile(this->slot,drive,fnib);
|
||||||
}
|
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
|
||||||
this->gui.setDiskFile(this->slot,drive,fnib);
|
}
|
||||||
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void unloadDisk(unsigned char drive)
|
void unloadDisk(unsigned char drive) {
|
||||||
{
|
this->getDrive(drive).unloadDisk();
|
||||||
this->getDrive(drive).unloadDisk();
|
this->gui.setDiskFile(this->slot,drive,"");
|
||||||
this->gui.setDiskFile(this->slot,drive,"");
|
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
|
||||||
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void save(int drive)
|
void save(int drive) {
|
||||||
{
|
this->getDrive(drive).saveDisk();
|
||||||
this->getDrive(drive).saveDisk();
|
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
|
||||||
this->gui.setDirty(this->slot,getCurrentDriveNumber(),false);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool isMotorOn()
|
bool isMotorOn() {
|
||||||
{
|
return this->motor.isOn();
|
||||||
return this->motor.isOn();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// const WozFile& getDiskBytes(unsigned char disk)
|
unsigned char getTrack() {
|
||||||
// {
|
return this->currentDrive->getTrack();
|
||||||
// return this->getDrive(disk).getDiskBytes();
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
unsigned char getTrack()
|
bool isWriting() {
|
||||||
{
|
return this->write;
|
||||||
return this->currentDrive->getTrack();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool isWriting()
|
bool isModified() {
|
||||||
{
|
return this->currentDrive->isModified();
|
||||||
return this->write;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool isModified()
|
bool isModifiedOther() {
|
||||||
{
|
return getOtherDrive().isModified();
|
||||||
return this->currentDrive->isModified();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool isModifiedOther()
|
bool isWriteProtected() {
|
||||||
{
|
return this->currentDrive->isWriteProtected();
|
||||||
return getOtherDrive().isModified();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool isWriteProtected()
|
bool isDirty() {
|
||||||
{
|
return isModified() || isModifiedOther();
|
||||||
return this->currentDrive->isWriteProtected();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool isDirty()
|
unsigned char getCurrentDriveNumber() {
|
||||||
{
|
return this->currentDrive == &this->drive1 ? 0 : 1;
|
||||||
return isModified() || isModifiedOther();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char getCurrentDriveNumber()
|
unsigned char getOtherDriveNumber() {
|
||||||
{
|
return 1-getCurrentDriveNumber();
|
||||||
return this->currentDrive == &this->drive1 ? 0 : 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char getOtherDriveNumber()
|
virtual std::string getName() {
|
||||||
{
|
return "disk][ drive 1 drive 2 ";
|
||||||
return 1-getCurrentDriveNumber();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
virtual std::string getName() { return "disk][ drive 1 drive 2 "; }
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,3 +16,72 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include "drive.h"
|
#include "drive.h"
|
||||||
|
Disk2Drive::Disk2Drive():
|
||||||
|
stepper(head),
|
||||||
|
pulse(false),
|
||||||
|
bitBufferRead(0),
|
||||||
|
generator(std::chrono::system_clock::now().time_since_epoch().count()),
|
||||||
|
distribution(0,1) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disk2Drive::loadDisk(const std::string& fnib) {
|
||||||
|
return this->disk.load(fnib);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2Drive::unloadDisk() {
|
||||||
|
this->disk.unload();
|
||||||
|
}
|
||||||
|
bool Disk2Drive::isLoaded() const {
|
||||||
|
return this->disk.isLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2Drive::saveDisk() {
|
||||||
|
this->disk.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disk2Drive::isWriteProtected() const {
|
||||||
|
return this->disk.isWriteProtected();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disk2Drive::isModified() const {
|
||||||
|
return this->disk.isModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Disk2Drive::position() const {
|
||||||
|
return this->head.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2Drive::tick() {
|
||||||
|
this->stepper.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2Drive::set_phase(int i_phase_0_to_3, bool on) {
|
||||||
|
this->stepper.set_phase(i_phase_0_to_3, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Disk2Drive::getTrack() const {
|
||||||
|
return this->head.position() >> 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2Drive::rotateDiskOneBit() {
|
||||||
|
this->disk.rotateOneBit(this->head.position());
|
||||||
|
|
||||||
|
bitBufferRead <<= 1;
|
||||||
|
bitBufferRead |= this->disk.getBit(this->head.position());
|
||||||
|
if (bitBufferRead & 0x0Fu) {
|
||||||
|
this->pulse = (bitBufferRead & 0x02u) >> 1;
|
||||||
|
} else {
|
||||||
|
this->pulse = randomBit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disk2Drive::readPulse() const {
|
||||||
|
return this->pulse;
|
||||||
|
}
|
||||||
|
void Disk2Drive::clearPulse() {
|
||||||
|
this->pulse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disk2Drive::writeBit(bool on) {
|
||||||
|
this->disk.setBit(this->head.position(), on);
|
||||||
|
}
|
||||||
|
|
93
src/drive.h
93
src/drive.h
|
@ -23,13 +23,15 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include "disk2steppermotor.h"
|
||||||
|
#include "disk2readwritehead.h"
|
||||||
#include "wozfile.h"
|
#include "wozfile.h"
|
||||||
#include "steppermotor.h"
|
|
||||||
|
|
||||||
class Drive {
|
class Disk2Drive {
|
||||||
private:
|
private:
|
||||||
WozFile& disk;
|
Disk2StepperMotor stepper;
|
||||||
StepperMotor& arm;
|
Disk2ReadWriteHead head;
|
||||||
|
WozFile disk;
|
||||||
|
|
||||||
bool pulse;
|
bool pulse;
|
||||||
std::uint8_t bitBufferRead;
|
std::uint8_t bitBufferRead;
|
||||||
|
@ -44,74 +46,21 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Drive(WozFile& disk, StepperMotor& arm):
|
Disk2Drive();
|
||||||
disk(disk),
|
bool loadDisk(const std::string& fnib);
|
||||||
arm(arm),
|
void unloadDisk();
|
||||||
pulse(false),
|
bool isLoaded() const;
|
||||||
bitBufferRead(0),
|
void saveDisk();
|
||||||
generator(std::chrono::system_clock::now().time_since_epoch().count()),
|
bool isWriteProtected() const;
|
||||||
distribution(0,1) {
|
bool isModified() const;
|
||||||
}
|
int position() const;
|
||||||
|
void tick();
|
||||||
~Drive() {
|
void set_phase(int i_phase_0_to_3, bool on);
|
||||||
}
|
int getTrack() const;
|
||||||
|
void rotateDiskOneBit();
|
||||||
bool loadDisk(const std::string& fnib) {
|
bool readPulse() const;
|
||||||
return this->disk.load(fnib);
|
void clearPulse();
|
||||||
}
|
void writeBit(bool on);
|
||||||
|
|
||||||
void unloadDisk() {
|
|
||||||
this->disk.unload();
|
|
||||||
}
|
|
||||||
bool isLoaded() const {
|
|
||||||
return this->disk.isLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveDisk() {
|
|
||||||
this->disk.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isWriteProtected() const {
|
|
||||||
return this->disk.isWriteProtected();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isModified() const {
|
|
||||||
return this->disk.isModified();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void setMagnet(unsigned char q, bool on) {
|
|
||||||
this->arm.setMagnet(q,on);
|
|
||||||
}
|
|
||||||
|
|
||||||
int getTrack() const {
|
|
||||||
return this->arm.getTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void rotateDiskOneBit() {
|
|
||||||
this->disk.rotateOneBit(this->arm.getQuarterTrack());
|
|
||||||
|
|
||||||
bitBufferRead <<= 1;
|
|
||||||
bitBufferRead |= this->disk.getBit(this->arm.getQuarterTrack());
|
|
||||||
if (bitBufferRead & 0x0Fu) {
|
|
||||||
this->pulse = (bitBufferRead & 0x02u) >> 1;
|
|
||||||
} else {
|
|
||||||
this->pulse = randomBit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readPulse() const {
|
|
||||||
return this->pulse;
|
|
||||||
}
|
|
||||||
void clearPulse() {
|
|
||||||
this->pulse = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeBit(bool on) {
|
|
||||||
this->disk.setBit(this->arm.getQuarterTrack(), on);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#include "magneticfield.h"
|
||||||
|
|
||||||
|
bool MagneticField::is_on() const {
|
||||||
|
return this->on;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MagneticField::position() const {
|
||||||
|
return this->pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MagneticField::turn_off() {
|
||||||
|
this->on = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MagneticField::turn_on_at(const int position) {
|
||||||
|
this->on = true;
|
||||||
|
this->pos = position;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef MAGNETICFIELD_H
|
||||||
|
#define MAGNETICFIELD_H
|
||||||
|
|
||||||
|
class MagneticField {
|
||||||
|
public:
|
||||||
|
bool is_on() const;
|
||||||
|
int position() const;
|
||||||
|
|
||||||
|
void turn_off();
|
||||||
|
void turn_on_at(const int position);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool on = false;
|
||||||
|
int pos = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MAGNETICFIELD_H
|
|
@ -0,0 +1,3 @@
|
||||||
|
#include "movable.h"
|
||||||
|
|
||||||
|
Movable::~Movable() = default;
|
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef MOVABLE_H
|
||||||
|
#define MOVABLE_H
|
||||||
|
|
||||||
|
|
||||||
|
class Movable {
|
||||||
|
public:
|
||||||
|
virtual ~Movable();
|
||||||
|
virtual bool move_by(int delta_position) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MOVABLE_H
|
|
@ -1,136 +0,0 @@
|
||||||
/*
|
|
||||||
epple2
|
|
||||||
|
|
||||||
Copyright © 2008–2018, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emulates the arm stepper motor in the Disk ][.
|
|
||||||
*
|
|
||||||
* @author Chris Mosher
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
mags ps magval
|
|
||||||
3210
|
|
||||||
---- -- ------
|
|
||||||
|
|
||||||
Each postition is a quarter track.
|
|
||||||
One complete cycle through the 4 phases will
|
|
||||||
move the arm 2 tracks. (UA2, 9-7.)
|
|
||||||
0001 0 1
|
|
||||||
0011 1 3
|
|
||||||
0010 2 2
|
|
||||||
0110 3 6
|
|
||||||
0100 4 4
|
|
||||||
1100 5 C
|
|
||||||
1000 6 8
|
|
||||||
1001 7 9
|
|
||||||
|
|
||||||
strange, but still defined
|
|
||||||
1011 0 B
|
|
||||||
0111 2 7
|
|
||||||
1110 4 E
|
|
||||||
1101 6 D
|
|
||||||
|
|
||||||
all off (no movement)
|
|
||||||
0000 ? 0
|
|
||||||
|
|
||||||
undefined
|
|
||||||
0101 ? 5 // <-- TODO pick one at random?
|
|
||||||
1010 ? A // <-- TODO pick one at random?
|
|
||||||
1111 ? F // TODO what to do here?
|
|
||||||
*/
|
|
||||||
#include "steppermotor.h"
|
|
||||||
#include "util.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
StepperMotor::StepperMotor():
|
|
||||||
quarterTrack(QTRACKS >> 1), // start in the middle of the disk... just for fun
|
|
||||||
// TODO if we want to be extremely accurate, we should save each arm's position on shutdown and restore on startup
|
|
||||||
// (because in the real-life Apple ][, the arm stays in the same position when powered off).
|
|
||||||
pos(0),
|
|
||||||
mags(0),
|
|
||||||
pendingPos(0),
|
|
||||||
pendingTicks(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
StepperMotor::~StepperMotor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
std::int8_t StepperMotor::mapMagPos[] = {-1,0,2,1,4,-1,3,2,6,7,-1,0,5,6,4,-1};
|
|
||||||
|
|
||||||
void StepperMotor::calculateTrack(const std::int8_t delta) {
|
|
||||||
std::int16_t q = this->quarterTrack;
|
|
||||||
q += delta;
|
|
||||||
if (q < 0) {
|
|
||||||
q = 0;
|
|
||||||
} else if (QTRACKS <= q) {
|
|
||||||
q = QTRACKS-1;
|
|
||||||
}
|
|
||||||
this->quarterTrack = static_cast<std::uint8_t>(q);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StepperMotor::moveCog() {
|
|
||||||
if (this->pendingPos >= 0) {
|
|
||||||
calculateTrack(calcDeltaPos(this->pos,this->pendingPos));
|
|
||||||
this->pos = this->pendingPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StepperMotor::setMagnet(const std::uint8_t magnet, const bool on) {
|
|
||||||
const std::uint8_t mask = static_cast<std::uint8_t>(1u << magnet);
|
|
||||||
if (on) {
|
|
||||||
this->mags |= mask;
|
|
||||||
} else {
|
|
||||||
this->mags &= ~mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set magnets (above), but delay actual movement of the stepper
|
|
||||||
// motor cog, to emulate force of inertia while trying to move it
|
|
||||||
// This allows Locksmith to write on the quarter-track, for example.
|
|
||||||
|
|
||||||
this->pendingPos = mapMagPos[this->mags];
|
|
||||||
this->pendingTicks = 1000; // about 1 millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
void StepperMotor::tick() {
|
|
||||||
if (this->pendingTicks) {
|
|
||||||
--this->pendingTicks;
|
|
||||||
if (!this->pendingTicks) {
|
|
||||||
moveCog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// TODO fix logging (due to new delayed movement algorithm)
|
|
||||||
// const std::uint8_t oldQT = this->quarterTrack;
|
|
||||||
//...
|
|
||||||
// const std::uint8_t newQT = this->quarterTrack;
|
|
||||||
// const std::int8_t deltaQT = newQT - oldQT;
|
|
||||||
// printf("ARM: ph%d %s [%c%c%c%c] T$%02X.%02d %s %+0.2f\n",
|
|
||||||
// (std::uint8_t)magnet,
|
|
||||||
// on ? "+" : "-",
|
|
||||||
// (mags&1)?'*':'.',
|
|
||||||
// (mags&2)?'*':'.',
|
|
||||||
// (mags&4)?'*':'.',
|
|
||||||
// (mags&8)?'*':'.',
|
|
||||||
// this->quarterTrack / 4,
|
|
||||||
// (this->quarterTrack % 4) * 25,
|
|
||||||
// deltaQT>0 ? "-->" : deltaQT<0 ? "<--" : " ",
|
|
||||||
// (deltaQT % 4) / 4.0);
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
epple2
|
|
||||||
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#ifndef STEPPERMOTOR_H
|
|
||||||
#define STEPPERMOTOR_H
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
class StepperMotor {
|
|
||||||
private:
|
|
||||||
enum { QTRACKS = 160 };
|
|
||||||
// quarter track: 0=t0, 1=t0.25, 2=t0.5, 3=t0.75, 4=t1, ... 140=t35.00 ... 159=t39.75
|
|
||||||
// (see TMAP in WOZ2 file format spec)
|
|
||||||
std::uint8_t quarterTrack;
|
|
||||||
|
|
||||||
std::int8_t pos;
|
|
||||||
std::uint8_t mags;
|
|
||||||
|
|
||||||
std::int8_t pendingPos;
|
|
||||||
std::uint32_t pendingTicks;
|
|
||||||
|
|
||||||
static std::int8_t mapMagPos[];
|
|
||||||
|
|
||||||
void moveCog();
|
|
||||||
void calculateTrack(const std::int8_t delta);
|
|
||||||
|
|
||||||
static std::int8_t calcDeltaPos(const std::int8_t cur, const std::int8_t next) {
|
|
||||||
std::int8_t d = next-cur; // -7 to +7
|
|
||||||
|
|
||||||
if (d == 4 || d == -4) {
|
|
||||||
d = 0; // <--- TODO pick random direction?
|
|
||||||
} else if (4 < d) {
|
|
||||||
d -= 8;
|
|
||||||
} else if (d < -4) {
|
|
||||||
d += 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
StepperMotor();
|
|
||||||
~StepperMotor();
|
|
||||||
|
|
||||||
void setMagnet(const std::uint8_t magnet, const bool on);
|
|
||||||
std::uint8_t getTrack() {
|
|
||||||
return this->quarterTrack >> 2;
|
|
||||||
}
|
|
||||||
std::uint8_t getQuarterTrack() {
|
|
||||||
return this->quarterTrack;
|
|
||||||
}
|
|
||||||
void tick();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -112,21 +112,21 @@ public:
|
||||||
|
|
||||||
bool load(const std::string& filePath);
|
bool load(const std::string& filePath);
|
||||||
|
|
||||||
std::string getFileName() {
|
std::string getFileName() const {
|
||||||
return this->fileName;
|
return this->fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isLoaded() {
|
bool isLoaded() const {
|
||||||
return this->loaded;
|
return this->loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
void save();
|
void save();
|
||||||
void unload();
|
void unload();
|
||||||
bool isWriteProtected() {
|
bool isWriteProtected() const {
|
||||||
return !this->writable;
|
return !this->writable;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isModified() {
|
bool isModified() const {
|
||||||
return this->modified;
|
return this->modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue