diff --git a/Components/I2C/I2C.cpp b/Components/I2C/I2C.cpp index f4d8fe6f5..5a43156fb 100644 --- a/Components/I2C/I2C.cpp +++ b/Components/I2C/I2C.cpp @@ -24,7 +24,7 @@ void Bus::set_data(bool pulled) { bool Bus::data() { bool result = data_; if(peripheral_bits_) { - result |= !(peripheral_response_ & 0x80); + result |= !(peripheral_response_ & 0x200); } return result; } @@ -41,35 +41,33 @@ void Bus::set_clock_data(bool clock_pulled, bool data_pulled) { return; } - auto info = logger.info(); - info.append("C:%d D:%d; ", clock_, data_); - const bool prior_data = data_; clock_ = clock_pulled; data_ = data_pulled; if(clock_) { - info.append("nothing"); return; } if(prior_data != data_) { if(data_) { - info.append("start"); + logger.info().append("S"); signal(Event::Start); } else { - info.append("stop"); + logger.info().append("P"); signal(Event::Stop); } } else { - --peripheral_bits_; - peripheral_response_ <<= 1; + if(peripheral_bits_) { + --peripheral_bits_; + peripheral_response_ <<= 1; + } if(data_) { - info.append("zero"); + logger.info().append("0"); signal(Event::Zero); } else { - info.append("one"); + logger.info().append("1"); signal(Event::One); } } @@ -77,75 +75,95 @@ void Bus::set_clock_data(bool clock_pulled, bool data_pulled) { void Bus::signal(Event event) { const auto capture_bit = [&]() { - if(event == Event::Zero || event == Event::One) { - input_ = uint16_t((input_ << 1) | (event == Event::Zero ? 0 : 1)); - ++input_count_; + input_ = uint16_t((input_ << 1) | (event == Event::Zero ? 0 : 1)); + ++input_count_; + }; + + const auto acknowledge = [&]() { + // Post an acknowledgement bit. + peripheral_response_ = 0; + peripheral_bits_ = 2; + }; + + const auto set_state = [&](State state) { + state_ = state; + input_count_ = 0; + input_ = 0; + }; + + const auto enqueue = [&](std::optional next) { + if(next) { + peripheral_response_ = *next; + peripheral_bits_ = 9; + set_state(State::PostingByte); + } else { + acknowledge(); + set_state(State::AwaitingAddress); } }; - const auto await_byte = [&] { - peripheral_response_ = 0; - peripheral_bits_ = 2; - phase_ = Phase::AwaitingByte; - logger.info().append("Waiting for byte"); - }; - - // Check for stop condition at any time. - // "A LOW-to-HIGH transition of the data line - // while the clock is HIGH is defined as the STOP condition". - if(event == Event::Stop) { - logger.info().append("Stopped"); - phase_ = Phase::AwaitingStart; + // Allow start and stop conditions at any time. + if(event == Event::Start) { + set_state(State::CollectingAddress); + active_peripheral_ = nullptr; + return; } - switch(phase_) { - case Phase::AwaitingStart: - // "A HIGH-to-LOW transition of the data line while - // the clock is HIGH is defined as the START condition" - if(event == Event::Start) { - phase_ = Phase::CollectingAddress; - input_count_ = 0; - input_ = 0; - logger.info().append("Waiting for [remainder of] address"); - } - break; + if(event == Event::Stop) { + set_state(State::AwaitingAddress); + if(active_peripheral_) { + active_peripheral_->stop(); + } + active_peripheral_ = nullptr; + return; + } - case Phase::CollectingAddress: + switch(state_) { + case State::AwaitingAddress: break; + + case State::CollectingAddress: capture_bit(); - if(input_count_ == 8) { - logger.info().append("Addressed %02x with %s?", uint8_t(input_) & 0xfe, input_ & 1 ? "read" : "write"); + if(input_count_ == 8) { auto pair = peripherals_.find(uint8_t(input_) & 0xfe); if(pair != peripherals_.end()) { active_peripheral_ = pair->second; + active_peripheral_->start(input_ & 1); - await_byte(); + if(input_&1) { + enqueue(active_peripheral_->read()); + } else { + acknowledge(); + set_state(State::ReceivingByte); + } } else { - phase_ = Phase::AwaitingStart; - logger.info().append("No device; not acknowledging"); + state_ = State::AwaitingAddress; } } break; - case Phase::AwaitingByte: + case State::ReceivingByte: // Run down the clock on the acknowledge bit. if(peripheral_bits_) { return; } - logger.info().append("Beginning byte"); - phase_ = Phase::CollectingByte; - input_count_ = 0; - [[fallthrough]]; - - case Phase::CollectingByte: capture_bit(); if(input_count_ == 8) { - logger.info().append("Got byte %02x?", uint8_t(input_)); - - await_byte(); + active_peripheral_->write(static_cast(input_)); + acknowledge(); + set_state(State::ReceivingByte); } break; + + case State::PostingByte: + // Finish whatever is enqueued. + if(peripheral_bits_) { + return; + } + + enqueue(active_peripheral_->read()); + break; } } diff --git a/Components/I2C/I2C.hpp b/Components/I2C/I2C.hpp index a030f3ede..1f276e07a 100644 --- a/Components/I2C/I2C.hpp +++ b/Components/I2C/I2C.hpp @@ -9,14 +9,18 @@ #pragma once #include +#include #include namespace I2C { /// Provides the virtual interface for an I2C peripheral; attaching this to a bus /// provides automatic protocol handling. -class Peripheral { - +struct Peripheral { + virtual void start([[maybe_unused]] bool is_read) {} + virtual void stop() {} + virtual std::optional read() { return std::nullopt; } + virtual void write(uint8_t) {} }; class Bus { @@ -48,12 +52,13 @@ private: }; void signal(Event); - enum class Phase { - AwaitingStart, + enum class State { + AwaitingAddress, CollectingAddress, - AwaitingByte, - CollectingByte, - } phase_ = Phase::AwaitingStart; + + PostingByte, + ReceivingByte, + } state_ = State::AwaitingAddress; }; } diff --git a/Machines/Acorn/Archimedes/CMOSRAM.hpp b/Machines/Acorn/Archimedes/CMOSRAM.hpp index 4b2ff6102..ea36bae92 100644 --- a/Machines/Acorn/Archimedes/CMOSRAM.hpp +++ b/Machines/Acorn/Archimedes/CMOSRAM.hpp @@ -13,7 +13,25 @@ namespace Archimedes { struct CMOSRAM: public I2C::Peripheral { - // All TODO. + void start(bool is_read) override { + expecting_address_ = !is_read; + } + + std::optional read() override { + return 0; + } + + void write(uint8_t value) override { + if(expecting_address_) { + address_ = value; + } else { + // TODO: write to RAM. + } + } + +private: + bool expecting_address_ = false; + uint8_t address_; }; }