1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-06 01:28:57 +00:00

Merge pull request #726 from TomHarte/BD-DOS

Implements highly-provisional Byte Drive 500 support for the Oric.
This commit is contained in:
Thomas Harte 2020-01-16 00:09:34 -05:00 committed by GitHub
commit 9bb294a023
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 513 additions and 220 deletions

View File

@ -20,7 +20,9 @@
using namespace Analyser::Static::Oric;
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
namespace {
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
int score = 0;
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
@ -30,7 +32,7 @@ static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, cons
return score;
}
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
@ -51,10 +53,10 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
};
return Score(disassembly, rom_functions, variable_locations);
return score(disassembly, rom_functions, variable_locations);
}
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
@ -76,10 +78,10 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
};
return Score(disassembly, rom_functions, variable_locations);
return score(disassembly, rom_functions, variable_locations);
}
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
@ -100,17 +102,22 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
}
static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
/*
The Jasmin boot sector is sector 1 of track 0 and is loaded at $400;
disassemble it to test it for validity.
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
use disassembly to test for likely matches.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
if(!sector) return false;
if(sector->samples.empty()) return false;
const std::vector<uint8_t> &first_sample = sector->samples[0];
if(first_sample.size() != 256) return false;
// Take a copy of the first sampling, and keep only the final 256 bytes (assuming at least that many were found).
std::vector<uint8_t> first_sample = sector->samples[0];
if(first_sample.size() < 256) return false;
if(first_sample.size() > 256) {
first_sample.erase(first_sample.end() - 256, first_sample.end());
}
// Grab a disassembly.
const auto disassembly =
@ -120,14 +127,24 @@ static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
int register_hits = 0;
for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
for(auto address : list) {
register_hits += (address >= 0x3f4 && address <= 0x3ff);
register_hits += (address >= range_start && address <= range_end);
}
}
// Arbitrary, sure, but as long as at least two accesses to Jasmin registers are found, accept this.
// Arbitrary, sure, but as long as at least two accesses to the requested register range are found, accept this.
return register_hits >= 2;
}
bool is_jasmin(Storage::Encodings::MFM::Parser &parser) {
return is_400_loader(parser, 0x3f4, 0x3ff);
}
bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
return is_400_loader(parser, 0x310, 0x323);
}
}
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
auto target = std::make_unique<Target>();
target->machine = Machine::Oric;
@ -146,9 +163,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
const Analyser::Static::MOS6502::Disassembly disassembly =
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
int basic10_score = Basic10Score(disassembly);
int basic11_score = Basic11Score(disassembly);
if(basic10_score > basic11_score) basic10_votes++; else basic11_votes++;
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
}
}
@ -158,17 +173,22 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
}
if(!media.disks.empty()) {
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc and
// Jasmin formats here.
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
// Jasmin and BD-DOS formats here.
for(auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
if(IsMicrodisc(parser)) {
if(is_microdisc(parser)) {
target->disk_interface = Target::DiskInterface::Microdisc;
target->media.disks.push_back(disk);
} else if(IsJasmin(parser)) {
} else if(is_jasmin(parser)) {
target->disk_interface = Target::DiskInterface::Jasmin;
target->should_start_jasmin = true;
target->media.disks.push_back(disk);
} else if(is_bd500(parser)) {
target->disk_interface = Target::DiskInterface::BD500;
target->media.disks.push_back(disk);
target->rom = Target::ROM::BASIC10;
}
}
}

View File

@ -27,6 +27,7 @@ struct Target: public ::Analyser::Static::Target {
Microdisc,
Pravetz,
Jasmin,
BD500,
None
};

View File

@ -824,6 +824,10 @@ void WD1770::set_head_loaded(bool head_loaded) {
if(head_loaded) posit_event(int(Event1770::HeadLoad));
}
bool WD1770::get_head_loaded() {
return head_is_loaded_;
}
ClockingHint::Preference WD1770::preferred_clocking() {
if(status_.busy) return ClockingHint::Preference::RealTime;
return Storage::Disk::MFMController::preferred_clocking();

View File

@ -80,6 +80,9 @@ class WD1770: public Storage::Disk::MFMController {
virtual void set_motor_on(bool motor_on);
void set_head_loaded(bool head_loaded);
/// @returns The last value posted to @c set_head_loaded.
bool get_head_loaded();
private:
Personality personality_;
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }

146
Machines/Oric/BD500.cpp Normal file
View File

@ -0,0 +1,146 @@
//
// BD500.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/01/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "BD500.hpp"
using namespace Oric;
BD500::BD500() : DiskController(P1793, 9000000) {
disable_basic_rom_ = true;
select_paged_item();
set_is_double_density(true);
}
void BD500::write(int address, uint8_t value) {
access(address);
if(address >= 0x0320 && address <= 0x0323) {
// if(address == 0x320) printf("Command %02x\n", value);
WD::WD1770::write(address, value);
}
}
uint8_t BD500::read(int address) {
access(address);
switch(address) {
default: return 0xff;
case 0x0320: case 0x0321: case 0x0322: case 0x0323:
return WD::WD1770::read(address);
case 0x312: return (get_data_request_line() ? 0x80 : 0x00) | (get_interrupt_request_line() ? 0x40 : 0x00);
}
}
void BD500::access(int address) {
// Determine whether to perform a command.
switch(address) {
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
return;
case 0x310: enable_overlay_ram_ = true; break;
case 0x313: enable_overlay_ram_ = false; break;
case 0x317: disable_basic_rom_ = false; break; // Could be 0x311.
default:
// printf("Switch %04x???\n", address);
break;
}
select_paged_item();
}
/*
The following was used when trying to find appropriate soft switch locations. It is preserved
as the values I have above are unlikely to be wholly correct and further research might be
desirable.
void BD500::access(int address) {
// 0,1,4,5,10,11 -> 64kb Atmos
// 2,3,9 -> 56kb Atmos.
// Broken: 6, 7, 8
int order = 5;
int commands[4];
std::vector<int> available_commands = {0, 1, 2, 3};
const int modulos[] = {6, 2, 1, 1};
for(int c = 0; c < 4; ++c) {
const int index = order / modulos[c];
commands[c] = available_commands[size_t(index)];
available_commands.erase(available_commands.begin() + index);
order %= modulos[c];
}
// Determine whether to perform a command.
int index = -1;
switch(address) {
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
return;
case 0x310: index = 0; break;
case 0x313: index = 1; break;
case 0x314: index = 2; break;
case 0x317: index = 3; break;
default:
printf("Switch %04x???\n", address);
break;
}
select_paged_item();
if(index >= 0) {
switch(commands[index]) {
case 0: enable_overlay_ram_ = true; break; // +RAM
case 1: disable_basic_rom_ = false; break; // -rom
case 2: disable_basic_rom_ = true; break; // +rom
case 3: enable_overlay_ram_ = false; break; // -RAM
}
select_paged_item();
}
}
*/
void BD500::set_head_load_request(bool head_load) {
// Turn all motors on or off; if off then unload the head instantly.
is_loading_head_ |= head_load;
for(auto &drive : drives_) {
if(drive) drive->set_motor_on(head_load);
}
if(!head_load) set_head_loaded(false);
}
void BD500::run_for(const Cycles cycles) {
// If a head load is in progress and the selected drive is now ready,
// declare head loaded.
if(is_loading_head_ && drives_[selected_drive_] && drives_[selected_drive_]->get_is_ready()) {
set_head_loaded(true);
is_loading_head_ = false;
}
WD::WD1770::run_for(cycles);
}
void BD500::set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led("BD-500");
observer_->set_led_status("BD-500", get_head_loaded());
}
}
void BD500::set_head_loaded(bool loaded) {
WD::WD1770::set_head_loaded(loaded);
if(observer_) {
observer_->set_led_status("BD-500", loaded);
}
}

43
Machines/Oric/BD500.hpp Normal file
View File

@ -0,0 +1,43 @@
//
// BD500.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/01/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef BD500_hpp
#define BD500_hpp
#include "../../Components/1770/1770.hpp"
#include "../../Activity/Observer.hpp"
#include "DiskController.hpp"
#include <array>
#include <memory>
namespace Oric {
class BD500: public DiskController {
public:
BD500();
void write(int address, uint8_t value);
uint8_t read(int address);
void run_for(const Cycles cycles);
void set_activity_observer(Activity::Observer *observer);
private:
void set_head_load_request(bool head_load) final;
bool is_loading_head_ = false;
Activity::Observer *observer_ = nullptr;
void access(int address);
void set_head_loaded(bool loaded);
};
};
#endif /* BD500_hpp */

View File

@ -0,0 +1,84 @@
//
// DiskController.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/01/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef DiskController_h
#define DiskController_h
namespace Oric {
class DiskController: public WD::WD1770 {
public:
DiskController(WD::WD1770::Personality personality, int clock_rate) :
WD::WD1770(personality), clock_rate_(clock_rate) {}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
const size_t drive = size_t(d);
if(!drives_[drive]) {
drives_[drive] = std::make_unique<Storage::Disk::Drive>(clock_rate_, 300, 2);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
}
enum class PagedItem {
DiskROM,
BASIC,
RAM
};
struct Delegate: public WD1770::Delegate {
virtual void disk_controller_did_change_paged_item(DiskController *controller) = 0;
};
inline void set_delegate(Delegate *delegate) {
delegate_ = delegate;
WD1770::set_delegate(delegate);
if(delegate) delegate->disk_controller_did_change_paged_item(this);
}
inline PagedItem get_paged_item() {
return paged_item_;
}
protected:
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
size_t selected_drive_ = 0;
void select_drive(size_t drive) {
if(drive != selected_drive_) {
selected_drive_ = drive;
set_drive(drives_[selected_drive_]);
}
}
Delegate *delegate_ = nullptr;
bool enable_overlay_ram_ = false;
bool disable_basic_rom_ = false;
void select_paged_item() {
PagedItem item = PagedItem::RAM;
if(!enable_overlay_ram_) {
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
}
set_paged_item(item);
}
private:
PagedItem paged_item_ = PagedItem::DiskROM;
int clock_rate_;
inline void set_paged_item(PagedItem item) {
if(paged_item_ == item) return;
paged_item_ = item;
if(delegate_) {
delegate_->disk_controller_did_change_paged_item(this);
}
}
};
};
#endif /* DiskController_h */

View File

@ -12,17 +12,9 @@ using namespace Oric;
// NB: there's some controversy here on WD1770 versus WD1772, but between those two I think
// the only difference is stepping rates, and it says 1770 on the schematic I'm looking at.
Jasmin::Jasmin() : WD1770(P1770) {
Jasmin::Jasmin() : DiskController(P1770, 8000000) {
set_is_double_density(true);
}
void Jasmin::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
const size_t drive = size_t(d);
if(!drives_[drive]) {
drives_[drive] = std::make_unique<Storage::Disk::Drive>(8000000, 300, 2);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
select_paged_item();
}
void Jasmin::write(int address, uint8_t value) {
@ -40,23 +32,20 @@ void Jasmin::write(int address, uint8_t value) {
case 0x3fa: {
// If b0, enable overlay RAM.
posit_paging_flags((paging_flags_ & BASICDisable) | ((value & 1) ? OverlayRAMEnable : 0));
enable_overlay_ram_ = value & 1;
select_paged_item();
} break;
case 0x3fb:
// If b0, disable BASIC ROM.
posit_paging_flags((paging_flags_ & OverlayRAMEnable) | ((value & 1) ? BASICDisable : 0));
disable_basic_rom_ = value & 1;
select_paged_item();
break;
case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff: {
const size_t new_selected_drive = size_t(address - 0x3fc);
if(new_selected_drive != selected_drive_) {
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(false);
selected_drive_ = new_selected_drive;
set_drive(drives_[selected_drive_]);
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
}
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(false);
select_drive(size_t(address - 0x3fc));
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
} break;
default:
@ -67,4 +56,15 @@ void Jasmin::write(int address, uint8_t value) {
void Jasmin::set_motor_on(bool on) {
motor_on_ = on;
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
if(observer_) {
observer_->set_led_status("Microdisc", on);
}
}
void Jasmin::set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led("Jasmin");
observer_->set_led_status("Jasmin", motor_on_);
}
}

View File

@ -11,48 +11,23 @@
#include "../../Components/1770/1770.hpp"
#include "../../Activity/Observer.hpp"
#include <array>
#include <memory>
#include "DiskController.hpp"
namespace Oric {
class Jasmin: public WD::WD1770 {
class Jasmin: public DiskController {
public:
Jasmin();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
void write(int address, uint8_t value);
enum PagingFlags {
/// Indicates that overlay RAM is enabled, implying no ROM is visible.
OverlayRAMEnable = (1 << 0),
/// Indicates that the BASIC ROM is disabled, implying that the JASMIN ROM
/// fills its space.
BASICDisable = (1 << 1)
};
struct Delegate: public WD1770::Delegate {
virtual void jasmin_did_change_paging_flags(Jasmin *jasmin) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; WD1770::set_delegate(delegate); }
inline int get_paging_flags() { return paging_flags_; }
void set_activity_observer(Activity::Observer *observer);
private:
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
size_t selected_drive_;
int paging_flags_ = 0;
Delegate *delegate_ = nullptr;
void posit_paging_flags(int new_flags) {
if(new_flags != paging_flags_) {
paging_flags_ = new_flags;
if(delegate_) delegate_->jasmin_did_change_paging_flags(this);
}
}
void set_motor_on(bool on) final;
bool motor_on_ = false;
Activity::Observer *observer_ = nullptr;
};
};

View File

@ -18,20 +18,10 @@ namespace {
const Cycles::IntType head_load_request_counter_target = 7653333;
}
Microdisc::Microdisc() : WD1770(P1793) {
Microdisc::Microdisc() : DiskController(P1793, 8000000) {
set_control_register(last_control_, 0xff);
}
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
const size_t drive = size_t(d);
if(!drives_[drive]) {
drives_[drive] = std::make_unique<Storage::Disk::Drive>(8000000, 300, 2);
if(drive == selected_drive_) set_drive(drives_[drive]);
drives_[drive]->set_activity_observer(observer_, drive_name(drive), false);
}
drives_[drive]->set_disk(disk);
}
void Microdisc::set_control_register(uint8_t control) {
const uint8_t changes = last_control_ ^ control;
last_control_ = control;
@ -73,8 +63,9 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
// b7: EPROM select (0 = select)
// b1: ROM disable (0 = disable)
if(changes & 0x82) {
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodiscDisable : 0);
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
enable_overlay_ram_ = control & 0x80;
disable_basic_rom_ = !(control & 0x02);
select_paged_item();
}
}
@ -121,23 +112,10 @@ void Microdisc::run_for(const Cycles cycles) {
WD::WD1770::run_for(cycles);
}
bool Microdisc::get_drive_is_ready() {
return true;
}
void Microdisc::set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led("Microdisc");
observer_->set_led_status("Microdisc", head_load_request_);
}
size_t c = 0;
for(auto &drive : drives_) {
if(drive) drive->set_activity_observer(observer, drive_name(c), false);
++c;
}
}
std::string Microdisc::drive_name(size_t index) {
return "Drive " + std::to_string(index);
}

View File

@ -11,16 +11,14 @@
#include "../../Components/1770/1770.hpp"
#include "../../Activity/Observer.hpp"
#include <array>
#include "DiskController.hpp"
namespace Oric {
class Microdisc: public WD::WD1770 {
class Microdisc: public DiskController {
public:
Microdisc();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
void set_control_register(uint8_t control);
uint8_t get_interrupt_request_register();
uint8_t get_data_request_register();
@ -29,42 +27,19 @@ class Microdisc: public WD::WD1770 {
void run_for(const Cycles cycles);
enum PagingFlags {
/// Indicates that the BASIC ROM should be disabled; if this is set then either
/// the Microdisc ROM or overlay RAM will be visible. If it is not set, BASIC
/// should be visible.
BASICDisable = (1 << 0),
/// Indicates that the Microdisc ROM is disabled. If BASIC is disabled and the Microdisc
/// is also disabled, overlay RAM should be visible.
MicrodiscDisable = (1 << 1)
};
class Delegate: public WD1770::Delegate {
public:
virtual void microdisc_did_change_paging_flags(Microdisc *microdisc) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; WD1770::set_delegate(delegate); }
inline int get_paging_flags() { return paging_flags_; }
void set_activity_observer(Activity::Observer *observer);
private:
void set_control_register(uint8_t control, uint8_t changes);
void set_head_load_request(bool head_load) override;
bool get_drive_is_ready();
void set_head_load_request(bool head_load) final;
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
size_t selected_drive_;
void set_control_register(uint8_t control, uint8_t changes);
uint8_t last_control_ = 0;
bool irq_enable_ = false;
int paging_flags_ = BASICDisable;
Cycles::IntType head_load_request_counter_ = -1;
bool head_load_request_ = false;
Delegate *delegate_ = nullptr;
uint8_t last_control_ = 0;
Activity::Observer *observer_ = nullptr;
std::string drive_name(size_t index);
Activity::Observer *observer_ = nullptr;
};
}

View File

@ -8,6 +8,7 @@
#include "Oric.hpp"
#include "BD500.hpp"
#include "Jasmin.hpp"
#include "Keyboard.hpp"
#include "Microdisc.hpp"
@ -223,8 +224,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate,
public Jasmin::Delegate,
public DiskController::Delegate,
public ClockingHint::Observer,
public Activity::Source,
public Machine,
@ -244,7 +244,16 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
speaker_.set_input_rate(1000000.0f);
via_port_handler_.set_interrupt_delegate(this);
tape_player_.set_delegate(this);
// Slight hack here: I'm unclear what RAM should look like at startup.
// Actually, I think completely random might be right since the Microdisc
// sort of assumes it, but also the BD-500 never explicitly sets PAL mode
// so I can't have any switch-to-NTSC bytes in the display area. Hence:
// disallow all atributes.
Memory::Fuzz(ram_, sizeof(ram_));
for(size_t c = 0; c < sizeof(ram_); ++c) {
ram_[c] |= 0x40;
}
if constexpr (disk_interface == DiskInterface::Pravetz) {
diskii_.set_clocking_hint_observer(this);
@ -266,6 +275,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
size_t diskii_state_machine_index = 0;
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
rom_names.emplace_back(machine_name, "the ORIC Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34);
break;
case DiskInterface::Jasmin:
rom_names.emplace_back(machine_name, "the ORIC Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89);
break;
@ -293,13 +305,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(8192);
break;
case DiskInterface::Jasmin:
jasmin_rom_ = std::move(*roms[2]);
jasmin_rom_.resize(2048);
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(2048);
break;
case DiskInterface::Microdisc:
microdisc_rom_ = std::move(*roms[2]);
microdisc_rom_.resize(8192);
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(8192);
break;
case DiskInterface::Pravetz: {
pravetz_rom_ = std::move(*roms[2]);
@ -314,14 +330,15 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
switch(target.disk_interface) {
default: break;
case DiskInterface::Microdisc:
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
case DiskInterface::BD500:
bd500_.set_delegate(this);
break;
case DiskInterface::Jasmin:
jasmin_did_change_paging_flags(&jasmin_);
jasmin_.set_delegate(this);
break;
case DiskInterface::Microdisc:
microdisc_.set_delegate(this);
break;
}
if(!target.loading_command.empty()) {
@ -353,7 +370,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
audio_queue_.flush();
}
void set_key_state(uint16_t key, bool is_pressed) override final {
void set_key_state(uint16_t key, bool is_pressed) final {
if(key == KeyNMI) {
m6502_.set_nmi_line(is_pressed);
} else {
@ -361,7 +378,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
}
void clear_all_keys() override final {
void clear_all_keys() final {
keyboard_.clear_all_keys();
}
@ -380,7 +397,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
return true;
}
bool insert_media(const Analyser::Static::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) final {
bool inserted = false;
if(!media.tapes.empty()) {
@ -390,16 +407,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
if(!media.disks.empty()) {
switch(disk_interface) {
case DiskInterface::Jasmin:
inserted |= insert_disks(media, jasmin_, 4);
break;
case DiskInterface::Microdisc: {
inserted |= insert_disks(media, microdisc_, 4);
} break;
case DiskInterface::Pravetz: {
inserted |= insert_disks(media, diskii_, 2);
} break;
case DiskInterface::BD500: inserted |= insert_disks(media, bd500_, 4); break;
case DiskInterface::Jasmin: inserted |= insert_disks(media, jasmin_, 4); break;
case DiskInterface::Microdisc: inserted |= insert_disks(media, microdisc_, 4); break;
case DiskInterface::Pravetz: inserted |= insert_disks(media, diskii_, 2); break;
default: break;
}
}
@ -434,6 +445,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
} else {
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
if(isReadOperation(operation)) *value = bd500_.read(address);
else bd500_.write(address, *value);
break;
case DiskInterface::Jasmin:
if(address >= 0x3f4) {
if(isReadOperation(operation)) *value = jasmin_.read(address);
@ -497,8 +512,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
tape_player_.run_for(Cycles(1));
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
bd500_.run_for(Cycles(9)); // i.e. effective clock rate of 9Mhz.
break;
case DiskInterface::Jasmin:
jasmin_.run_for(Cycles(8));
jasmin_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
// Jasmin autostart hack: wait for a period, then trigger a reset, having forced
// the Jasmin to page its ROM in first. I assume the latter being what the Jasmin's
@ -511,12 +529,12 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
break;
case DiskInterface::Microdisc:
microdisc_.run_for(Cycles(8));
microdisc_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
break;
case DiskInterface::Pravetz:
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
diskii_.set_data_input(*value);
diskii_.run_for(Cycles(2));
diskii_.run_for(Cycles(2));; // i.e. effective clock rate of 2Mhz.
} else {
cycles_since_diskii_update_ += Cycles(2);
}
@ -534,74 +552,53 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
// to satisfy CRTMachine::Machine
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_output_.set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_output_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override final {
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) override final {
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
}
// to satisfy MOS::MOS6522IRQDelegate::Delegate
void mos6522_did_change_interrupt_status(void *mos6522) override final {
void mos6522_did_change_interrupt_status(void *mos6522) final {
set_interrupt_line();
}
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) final {
// set CB1
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
}
// for Utility::TypeRecipient::Delegate
void type_string(const std::string &string) override final {
void type_string(const std::string &string) final {
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
}
// for Microdisc::Delegate
void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final {
const int flags = microdisc->get_paging_flags();
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
ram_top_ = basic_visible_ram_top_;
paged_rom_ = rom_.data();
} else {
if(flags&Microdisc::PagingFlags::MicrodiscDisable) {
ram_top_ = basic_invisible_ram_top_;
} else {
ram_top_ = 0xdfff;
paged_rom_ = microdisc_rom_.data();
}
}
}
// Jasmin::Delegate
void jasmin_did_change_paging_flags(Jasmin *jasmin) override final {
const int flags = jasmin->get_paging_flags();
switch(flags) {
// BASIC enabled, overlay disabled.
// DiskController::Delegate
void disk_controller_did_change_paged_item(DiskController *controller) final {
switch(controller->get_paged_item()) {
default:
ram_top_ = basic_visible_ram_top_;
paged_rom_ = rom_.data();
break;
// Overlay RAM enabled, with or without BASIC.
case Jasmin::OverlayRAMEnable:
case Jasmin::OverlayRAMEnable | Jasmin::BASICDisable:
case DiskController::PagedItem::RAM:
ram_top_ = basic_invisible_ram_top_;
break;
// BASIC disabled, overlay disabled.
case Jasmin::BASICDisable:
ram_top_ = 0xf7ff;
paged_rom_ = jasmin_rom_.data();
case DiskController::PagedItem::DiskROM:
ram_top_ = uint16_t(0xffff - disk_rom_.size());
paged_rom_ = disk_rom_.data();
break;
}
}
@ -649,6 +646,12 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
void set_activity_observer(Activity::Observer *observer) override {
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
bd500_.set_activity_observer(observer);
break;
case DiskInterface::Jasmin:
jasmin_.set_activity_observer(observer);
break;
case DiskInterface::Microdisc:
microdisc_.set_activity_observer(observer);
break;
@ -669,7 +672,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
// RAM and ROM
std::vector<uint8_t> rom_, microdisc_rom_, jasmin_rom_;
std::vector<uint8_t> rom_, disk_rom_;
uint8_t ram_[65536];
Cycles cycles_since_video_update_;
inline void update_video() {
@ -705,6 +708,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
Jasmin jasmin_;
int jasmin_reset_counter_ = 0;
// the BD-500, if in use.
BD500 bd500_;
// the Pravetz/Disk II, if in use.
Apple::DiskII diskii_;
Cycles cycles_since_diskii_update_;
@ -771,6 +777,7 @@ Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMac
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher);
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher);
case DiskInterface::Jasmin: return new ConcreteMachine<DiskInterface::Jasmin>(*oric_target, rom_fetcher);
case DiskInterface::BD500: return new ConcreteMachine<DiskInterface::BD500>(*oric_target, rom_fetcher);
}
}

View File

@ -369,6 +369,7 @@
4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */; };
4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */; };
4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; };
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
@ -1181,6 +1182,9 @@
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Jasmin.hpp; path = Oric/Jasmin.hpp; sourceTree = "<group>"; };
4B7BA03223C58B1E00B98D9E /* STX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = STX.hpp; sourceTree = "<group>"; };
4B7BA03323C58B1E00B98D9E /* STX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = STX.cpp; sourceTree = "<group>"; };
4B7BA03523CEB86000B98D9E /* BD500.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BD500.cpp; path = Oric/BD500.cpp; sourceTree = "<group>"; };
4B7BA03623CEB86000B98D9E /* BD500.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BD500.hpp; path = Oric/BD500.hpp; sourceTree = "<group>"; };
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DiskController.hpp; path = Oric/DiskController.hpp; sourceTree = "<group>"; };
4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = "<group>"; };
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
@ -3533,16 +3537,19 @@
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
isa = PBXGroup;
children = (
4B7BA03523CEB86000B98D9E /* BD500.cpp */,
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */,
4B54C0BD1F8D8F450050900F /* Keyboard.cpp */,
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */,
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */,
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */,
4B7BA03623CEB86000B98D9E /* BD500.hpp */,
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */,
4B54C0BE1F8D8F450050900F /* Keyboard.hpp */,
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */,
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */,
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */,
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */,
);
name = Oric;
sourceTree = "<group>";
@ -4455,6 +4462,7 @@
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */,
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,

View File

@ -67,7 +67,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES"

View File

@ -50,7 +50,8 @@ typedef NS_ENUM(NSInteger, CSMachineOricDiskInterface) {
CSMachineOricDiskInterfaceNone,
CSMachineOricDiskInterfaceMicrodisc,
CSMachineOricDiskInterfacePravetz,
CSMachineOricDiskInterfaceJasmin
CSMachineOricDiskInterfaceJasmin,
CSMachineOricDiskInterfaceBD500
};
typedef NS_ENUM(NSInteger, CSMachineVic20Region) {

View File

@ -103,6 +103,7 @@
case CSMachineOricDiskInterfaceMicrodisc: target->disk_interface = Target::DiskInterface::Microdisc; break;
case CSMachineOricDiskInterfacePravetz: target->disk_interface = Target::DiskInterface::Pravetz; break;
case CSMachineOricDiskInterfaceJasmin: target->disk_interface = Target::DiskInterface::Jasmin; break;
case CSMachineOricDiskInterfaceBD500: target->disk_interface = Target::DiskInterface::BD500; break;
}
_targets.push_back(std::move(target));
}

View File

@ -331,7 +331,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fYL-p6-wyn">
<rect key="frame" x="111" y="36" width="97" height="25"/>
<rect key="frame" x="111" y="36" width="129" height="25"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@ -341,6 +341,7 @@ Gw
<menuItem title="Microdisc" tag="1" id="1jS-Lz-FRj"/>
<menuItem title="Jasmin" tag="3" id="CGU-gd-xov"/>
<menuItem title="8DOS" tag="2" id="edb-fl-C8Y"/>
<menuItem title="Byte Drive 500" tag="4" id="lkS-Rr-m1D"/>
</items>
</menu>
</popUpButtonCell>

View File

@ -196,6 +196,7 @@ class MachinePicker: NSObject {
case 1: diskInterface = .microdisc
case 2: diskInterface = .pravetz
case 3: diskInterface = .jasmin
case 4: diskInterface = .BD500
default: break;
}

View File

@ -5,6 +5,11 @@ Expected files:
basic10.rom
basic11.rom
colour.rom
microdisc.rom
8dos.rom
pravetz.rom
pravetz.rom
Also potentially required:
* microdisc.rom, for loading Microdisc software;
* 8dos.rom, for loading Pravetz 8-DOS software;
* jasmin.rom, for loading Jasmin disk interface software; and
* bd500.rom, for loading Byte Drive 500 software.

View File

@ -77,7 +77,7 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
for(int byte = 0; byte < 6; byte++) {
last_header[byte] = file_.get8();
encoder->add_byte(last_header[byte]);
track_offset++;
++track_offset;
if(track_offset == 6250) break;
}
break;
@ -85,8 +85,12 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
case 0xfb:
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) {
encoder->add_byte(file_.get8());
track_offset++;
if(track_offset == 6250) break;
++track_offset;
// Special exception: don't interrupt a sector body if it seems to
// be about to run over the end of the track. It seems like BD-500
// disks break the usual 6250-byte rule, pushing out to just less
// than 6400 bytes total.
if(track_offset == 6400) break;
}
break;
}

View File

@ -68,7 +68,7 @@ bool Drive::has_disk() const {
}
ClockingHint::Preference Drive::preferred_clocking() {
return (!motor_is_on_ || !has_disk_) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
return (!has_disk_ || (time_until_motor_transition == Cycles(0) && !disk_is_rotating_)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
}
bool Drive::get_is_track_zero() const {
@ -146,26 +146,33 @@ bool Drive::get_is_ready() const {
}
void Drive::set_motor_on(bool motor_is_on) {
if(motor_is_on_ != motor_is_on) {
motor_is_on_ = motor_is_on;
// Do nothing if the input hasn't changed.
if(motor_input_is_on_ == motor_is_on) return;
motor_input_is_on_ = motor_is_on;
if(observer_) {
observer_->set_drive_motor_status(drive_name_, motor_is_on_);
if(announce_motor_led_) {
observer_->set_led_status(drive_name_, motor_is_on_);
}
}
if(!motor_is_on) {
ready_index_count_ = 0;
if(disk_) disk_->flush_tracks();
}
update_clocking_observer();
// If this now means that the input and the actual state are in harmony,
// cancel any planned change and stop.
if(disk_is_rotating_ == motor_is_on) {
time_until_motor_transition = Cycles(0);
return;
}
// If this is a transition to on, start immediately.
// TODO: spin-up?
// TODO: momentum.
if(motor_is_on) {
set_disk_is_rotating(true);
return;
}
// This is a transition from on to off. Simulate momentum (ha!)
// by delaying the time until complete standstill.
if(time_until_motor_transition == Cycles(0))
time_until_motor_transition = get_input_clock_rate();
}
bool Drive::get_motor_on() const {
return motor_is_on_;
return motor_input_is_on_;
}
bool Drive::get_index_pulse() const {
@ -185,7 +192,16 @@ void Drive::run_for(const Cycles cycles) {
// Assumed: the index pulse pulses even if the drive has stopped spinning.
index_pulse_remaining_ = std::max(index_pulse_remaining_ - cycles, Cycles(0));
if(motor_is_on_) {
if(time_until_motor_transition > Cycles(0)) {
if(time_until_motor_transition > cycles) {
time_until_motor_transition -= cycles;
} else {
time_until_motor_transition = Cycles(0);
set_disk_is_rotating(!disk_is_rotating_);
}
}
if(disk_is_rotating_) {
if(has_disk_) {
Time zero(0);
@ -400,6 +416,23 @@ bool Drive::is_writing() const {
return !is_reading_;
}
void Drive::set_disk_is_rotating(bool is_rotating) {
disk_is_rotating_ = is_rotating;
if(observer_) {
observer_->set_drive_motor_status(drive_name_, motor_input_is_on_);
if(announce_motor_led_) {
observer_->set_led_status(drive_name_, motor_input_is_on_);
}
}
if(!is_rotating) {
ready_index_count_ = 0;
if(disk_) disk_->flush_tracks();
}
update_clocking_observer();
}
void Drive::set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led) {
observer_ = observer;
announce_motor_led_ = add_motor_led;
@ -407,11 +440,11 @@ void Drive::set_activity_observer(Activity::Observer *observer, const std::strin
drive_name_ = name;
observer->register_drive(drive_name_);
observer->set_drive_motor_status(drive_name_, motor_is_on_);
observer->set_drive_motor_status(drive_name_, disk_is_rotating_);
if(add_motor_led) {
observer->register_led(drive_name_);
observer->set_led_status(drive_name_, motor_is_on_);
observer->set_led_status(drive_name_, disk_is_rotating_);
}
}
}

View File

@ -75,7 +75,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
void set_motor_on(bool);
/*!
@returns @c true if the motor is on; @c false otherwise.
@returns @c true if the motor on input is active; @c false otherwise. This does not necessarily indicate whether the drive is spinning, due to momentum.
*/
bool get_motor_on() const;
@ -213,7 +213,10 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
int available_heads_ = 0;
// Motor control state.
bool motor_is_on_ = false;
bool motor_input_is_on_ = false;
bool disk_is_rotating_ = false;
Cycles time_until_motor_transition;
void set_disk_is_rotating(bool);
// Current state of the index pulse output.
Cycles index_pulse_remaining_;