diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp index 1001c01ac..4776cb1a8 100644 --- a/Components/6850/6850.cpp +++ b/Components/6850/6850.cpp @@ -57,6 +57,7 @@ void ACIA::write(int address, uint8_t value) { } else { if((value&3) == 3) { transmit.reset_writing(); + transmit.write(true); request_to_send.reset_writing(); } else { switch(value & 3) { diff --git a/Components/SerialPort/SerialPort.cpp b/Components/SerialPort/SerialPort.cpp index c9d9dc506..3d903154d 100644 --- a/Components/SerialPort/SerialPort.cpp +++ b/Components/SerialPort/SerialPort.cpp @@ -19,14 +19,25 @@ void Line::advance_writer(int cycles) { while(!events_.empty()) { if(events_.front().delay < cycles) { cycles -= events_.front().delay; + write_cycles_since_delegate_call_ += events_.front().delay; + const auto old_level = level_; + auto iterator = events_.begin() + 1; while(iterator != events_.end() && iterator->type != Event::Delay) { level_ = iterator->type == Event::SetHigh; ++iterator; } events_.erase(events_.begin(), iterator); + + if(old_level != level_) { + if(read_delegate_) { + read_delegate_->serial_line_did_change_output(this, Storage::Time(write_cycles_since_delegate_call_, clock_rate_), level_); + write_cycles_since_delegate_call_ = 0; + } + } } else { events_.front().delay -= cycles; + write_cycles_since_delegate_call_ += cycles; break; } } @@ -50,6 +61,7 @@ void Line::write(int cycles, int count, int levels) { events_[event].type = Event::Delay; events_[event].delay = cycles; events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow; + levels >>= 1; event += 2; } } @@ -66,10 +78,22 @@ void Line::reset_writing() { void Line::flush_writing() { remaining_delays_ = 0; for(const auto &event : events_) { + bool new_level = level_; switch(event.type) { default: break; - case Event::SetHigh: level_ = true; break; - case Event::SetLow: level_ = false; break; + case Event::SetHigh: new_level = true; break; + case Event::SetLow: new_level = false; break; + case Event::Delay: + write_cycles_since_delegate_call_ += event.delay; + continue; + } + + if(new_level != level_) { + level_ = new_level; + if(read_delegate_) { + read_delegate_->serial_line_did_change_output(this, Storage::Time(write_cycles_since_delegate_call_, clock_rate_), level_); + write_cycles_since_delegate_call_ = 0; + } } } events_.clear(); @@ -78,3 +102,8 @@ void Line::flush_writing() { bool Line::read() { return level_; } + +void Line::set_read_delegate(ReadDelegate *delegate) { + read_delegate_ = delegate; + write_cycles_since_delegate_call_ = 0; +} diff --git a/Components/SerialPort/SerialPort.hpp b/Components/SerialPort/SerialPort.hpp index 82eadc2a3..5f8628588 100644 --- a/Components/SerialPort/SerialPort.hpp +++ b/Components/SerialPort/SerialPort.hpp @@ -10,6 +10,7 @@ #define SerialPort_hpp #include +#include "../../Storage/Storage.hpp" namespace Serial { @@ -53,6 +54,11 @@ class Line { /// @returns The instantaneous level of this line. bool read(); + struct ReadDelegate { + virtual void serial_line_did_change_output(Line *line, Storage::Time time_since_last_change, bool new_level) = 0; + }; + void set_read_delegate(ReadDelegate *delegate); + private: struct Event { enum Type { @@ -64,6 +70,9 @@ class Line { int remaining_delays_ = 0; bool level_ = false; int clock_rate_ = 0; + + ReadDelegate *read_delegate_ = nullptr; + int write_cycles_since_delegate_call_ = 0; }; /*! diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 6277489ff..c13c352bc 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -21,6 +21,7 @@ #include "../../ClockReceiver/ForceInline.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../Outputs/Log.hpp" #include "../Utility/MemoryPacker.hpp" #include "../Utility/MemoryFuzzer.hpp" @@ -30,8 +31,50 @@ namespace ST { const int CLOCK_RATE = 8000000; -using Target = Analyser::Static::Target; +/*! + A receiver for the Atari ST's "intelligent keyboard" commands, which actually cover + keyboard input and output and mouse handling. +*/ +class IntelligentKeyboard: + public Serial::Line::ReadDelegate { + public: + IntelligentKeyboard(Serial::Line &input, Serial::Line &output) : output_line_(output) { + input.set_read_delegate(this); + } + void serial_line_did_change_output(Serial::Line *, Storage::Time time_since_last_change, bool new_level) final { + // Figure out how many bits have passed. TODO: in fixed point? + const float number_of_bits = time_since_last_change.get() * 7812.5f + bit_offset_; + bit_offset_ = fmodf(number_of_bits, 1.0f); + + int bits_remaining = int(number_of_bits); + while(bits_remaining--) { + if(!bit_count_) { + // Check for a potential start bit. + if(!new_level) { + bit_count_ = 10; + command_ = 0; + } + } else { + command_ >>= 1; + command_ |= new_level ? 0 : 0x200; + --bit_count_; + + if(!bit_count_) { + LOG("[IKBD] Should perform " << PADHEX(2) << ((command_ >> 1) & 0xff)); + } + } + } + } + + private: + int bit_count_ = 0; + int command_ = 0; + Serial::Line &output_line_; + float bit_offset_ = 0.0f; +}; + +using Target = Analyser::Static::Target; class ConcreteMachine: public Atari::ST::Machine, public CPU::MC68000::BusHandler, @@ -42,7 +85,8 @@ class ConcreteMachine: keyboard_acia_(Cycles(500000)), midi_acia_(Cycles(500000)), ay_(audio_queue_), - speaker_(ay_) { + speaker_(ay_), + ikbd_(keyboard_acia_->transmit, keyboard_acia_->receive) { set_clock_rate(CLOCK_RATE); speaker_.set_input_rate(CLOCK_RATE / 4); @@ -380,6 +424,8 @@ class ConcreteMachine: Outputs::Speaker::LowpassSpeaker speaker_; HalfCycles cycles_since_audio_update_; + IntelligentKeyboard ikbd_; + std::vector ram_; std::vector rom_;