diff --git a/Machines/Apple/Macintosh/Keyboard.hpp b/Machines/Apple/Macintosh/Keyboard.hpp index 96b111e22..ff7e0708c 100644 --- a/Machines/Apple/Macintosh/Keyboard.hpp +++ b/Machines/Apple/Macintosh/Keyboard.hpp @@ -15,10 +15,36 @@ namespace Macintosh { class Keyboard { public: void set_input(bool data) { - printf(""); + } + + bool get_clock() { + return false; + } + + bool get_data() { + return false; + } + + /*! + The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz. + */ + void run_for(HalfCycles cycle) { + // TODO: something. } }; +/* + "When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high) + on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each + clock cycle and maintains it for 330 µS. The VIA in the compu(er latches the data bit into its Shift register on the + rising edge of the Keyboard Clock signal." + + "When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low, + 220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places + a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge + of the Keyboard Clock signal." +*/ + } } diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 64f78b345..c42bd8457 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -78,6 +78,16 @@ class ConcreteMachine: via_clock_ += cycle.length; via_.run_for(via_clock_.divide(HalfCycles(10))); + // The keyboard also has a clock, albeit a very slow one. + // Its clock and data lines are connected to the VIA. + keyboard_clock_ += cycle.length; + auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000)); + if(keyboard_ticks > HalfCycles(0)) { + keyboard_.run_for(keyboard_ticks); + via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data()); + via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock()); + } + // TODO: SCC is a divide-by-two. // Consider updating the real-time clock. @@ -307,9 +317,10 @@ class ConcreteMachine: Apple::IWM iwm_; HalfCycles via_clock_; + HalfCycles real_time_clock_; + HalfCycles keyboard_clock_; HalfCycles time_since_video_update_; HalfCycles time_since_iwm_update_; - HalfCycles real_time_clock_; bool ROM_is_overlay_ = true; }; diff --git a/Machines/Apple/Macintosh/RealTimeClock.hpp b/Machines/Apple/Macintosh/RealTimeClock.hpp new file mode 100644 index 000000000..40899114e --- /dev/null +++ b/Machines/Apple/Macintosh/RealTimeClock.hpp @@ -0,0 +1,158 @@ +// +// RealTimeClock.hpp +// Clock Signal +// +// Created by Thomas Harte on 07/05/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef RealTimeClock_hpp +#define RealTimeClock_hpp + +namespace Apple { +namespace Macintosh { + +/*! + Models the storage component of Apple's real-time clock. + + Since tracking of time is pushed to this class, it is assumed + that whomever is translating real time into emulated time + will notify the VIA of a potential interrupt. +*/ +class RealTimeClock { + public: + /*! + Advances the clock by 1 second. + + The caller should also notify the VIA. + */ + void update() { + for(int c = 0; c < 4; ++c) { + ++seconds_[c]; + if(seconds_[c]) break; + } + } + + /*! + Sets the current clock and data inputs to the clock. + */ + void set_input(bool clock, bool data) { + /* + Documented commands: + + z0000001 Seconds register 0 (lowest order byte) + z0000101 Seconds register 1 + z0001001 Seconds register 2 + z0001101 Seconds register 3 + 00110001 Test register (write only) + 00110101 Write-protect register (write only) + z010aa01 RAM addresses 0x10 - 0x13 + z1aaaa01 RAM addresses 0x00 – 0x0f + + z = 1 => a read; z = 0 => a write. + + The top bit of the write-protect register enables (0) or disables (1) + writes to other locations. + + All the documentation says about the test register is to set the top + two bits to 0 for normal operation. Abnormal operation is undefined. + + The data line is valid when the clock transitions to level 0. + */ + + if(clock && !previous_clock_) { + // Shift into the command_ register, no matter what. + command_ = uint16_t((command_ << 1) | (data ? 1 : 0)); + result_ <<= 1; + + // Increment phase. + ++phase_; + + // When phase hits 8, inspect the command. + // If it's a read, prepare a result. + if(phase_ == 8) { + if(command_ & 0x80) { + // A read. + const auto address = (command_ >> 2) & 0x1f; + + // Begin pessimistically. + result_ = 0xff; + + if(address < 4) { + result_ = seconds_[address]; + } else if(address >= 0x10) { + result_ = data_[address & 0xf]; + } else if(address >= 0x8 && address < 0xb) { + result_ = data_[0x10 + (address & 0x3)]; + } + } + } + + // If phase hits 16 and this was a read command, + // just stop. If it was a write command, do the + // actual write. + if(phase_ == 16) { + if(!(command_ & 0x8000)) { + // A write. + + const auto address = (command_ >> 10) & 0x1f; + const uint8_t value = uint8_t(command_ & 0xff); + + // First test: is this to the write-protect register? + if(address == 0xd) { + write_protect_ = value; + } + + // No other writing is permitted if the write protect + // register won't allow it. + if(!(write_protect_ & 0x80)) { + if(address < 4) { + seconds_[address] = value; + } else if(address >= 0x10) { + data_[address & 0xf] = value; + } else if(address >= 0x8 && address < 0xb) { + data_[0x10 + (address & 0x3)] = value; + } + } + } + + // A phase of 16 always ends the command, so reset here. + abort(); + } + } + + previous_clock_ = clock; + } + + /*! + Reads the current data output level from the clock. + */ + bool get_data() { + return !!(result_ & 0x80); + } + + /*! + Announces that a serial command has been aborted. + */ + void abort() { + result_ = 0; + phase_ = 0; + command_ = 0; + } + + private: + uint8_t data_[0x14]; + uint8_t seconds_[4]; + uint8_t write_protect_; + + int phase_ = 0; + uint16_t command_; + uint8_t result_ = 0; + + bool previous_clock_ = false; +}; + +} +} + +#endif /* RealTimeClock_hpp */