// // Keyboard.hpp // Clock Signal // // Created by Thomas Harte on 20/03/2024. // Copyright © 2024 Thomas Harte. All rights reserved. // #pragma once #include "HalfDuplexSerial.hpp" #include "../../../Outputs/Log.hpp" #include "../../../Inputs/Mouse.hpp" #include namespace Archimedes { namespace { constexpr uint16_t map(int row, int column) { return static_cast((row << 4) | column); } constexpr uint8_t row(uint16_t key) { return static_cast(key >> 4); } constexpr uint8_t column(uint16_t key) { return static_cast(key & 0xf); } } struct Key { /// Named key codes that the machine wlll accept directly. enum Value: uint16_t { Escape = map(0, 0), F1 = map(0, 1), F2 = map(0, 2), F3 = map(0, 3), F4 = map(0, 4), F5 = map(0, 5), F6 = map(0, 6), F7 = map(0, 7), F8 = map(0, 8), F9 = map(0, 9), F10 = map(0, 10), F11 = map(0, 11), F12 = map(0, 12), Print = map(0, 13), Scroll = map(0, 14), Break = map(0, 15), Tilde = map(1, 0), k1 = map(1, 1), k2 = map(1, 2), k3 = map(1, 3), k4 = map(1, 4), k5 = map(1, 5), k6 = map(1, 6), k7 = map(1, 7), k8 = map(1, 8), k9 = map(1, 9), k0 = map(1, 10), Hyphen = map(1, 11), Equals = map(1, 12), GBPound = map(1, 13), Backspace = map(1, 14), Insert = map(1, 15), Home = map(2, 0), PageUp = map(2, 1), NumLock = map(2, 2), KeypadSlash = map(2, 3), KeypadAsterisk = map(2, 4), KeypadHash = map(2, 5), Tab = map(2, 6), Q = map(2, 7), W = map(2, 8), E = map(2, 9), R = map(2, 10), T = map(2, 11), Y = map(2, 12), U = map(2, 13), I = map(2, 14), O = map(2, 15), P = map(3, 0), OpenSquareBracket = map(3, 1), CloseSquareBracket = map(3, 2), Backslash = map(3, 3), Delete = map(3, 4), Copy = map(3, 5), PageDown = map(3, 6), Keypad7 = map(3, 7), Keypad8 = map(3, 8), Keypad9 = map(3, 9), KeypadMinus = map(3, 10), LeftControl = map(3, 11), A = map(3, 12), S = map(3, 13), D = map(3, 14), F = map(3, 15), G = map(4, 0), H = map(4, 1), J = map(4, 2), K = map(4, 3), L = map(4, 4), Semicolon = map(4, 5), Quote = map(4, 6), Return = map(4, 7), Keypad4 = map(4, 8), Keypad5 = map(4, 9), Keypad6 = map(4, 10), KeypadPlus = map(4, 11), LeftShift = map(4, 12), /* unused */ Z = map(4, 14), X = map(4, 15), C = map(5, 0), V = map(5, 1), B = map(5, 2), N = map(5, 3), M = map(5, 4), Comma = map(5, 5), FullStop = map(5, 6), ForwardSlash = map(5, 7), RightShift = map(5, 8), Up = map(5, 9), Keypad1 = map(5, 10), Keypad2 = map(5, 11), Keypad3 = map(5, 12), CapsLock = map(5, 13), LeftAlt = map(5, 14), Space = map(5, 15), RightAlt = map(6, 0), RightControl = map(6, 1), Left = map(6, 2), Down = map(6, 3), Right = map(6, 4), Keypad0 = map(6, 5), KeypadDecimalPoint = map(6, 6), KeypadEnter = map(6, 7), Max = KeypadEnter, }; }; // Resource for the keyboard protocol: https://github.com/tmk/tmk_keyboard/wiki/ACORN-ARCHIMEDES-Keyboard struct Keyboard { Keyboard(HalfDuplexSerial &serial) : serial_(serial), mouse_(*this) {} void set_key_state(uint16_t key, bool is_pressed) { states_[key] = is_pressed; if(!scan_keyboard_) { logger_.info().append("Ignored key event as key scanning disabled"); return; } // Don't waste bandwidth on repeating facts. if(posted_states_[key] == is_pressed) return; // Post new key event. enqueue_key_event(key, is_pressed); consider_dequeue(); } void set_mouse_button(uint8_t button, bool is_pressed) { if(!scan_mouse_) { return; } // Post new key event. enqueue_key_event(7, button, is_pressed); consider_dequeue(); } void update() { if(serial_.events(KeyboardParty) & HalfDuplexSerial::Receive) { const auto reset = [&]() { serial_.output(KeyboardParty, HRST); state_ = State::Idle; }; const uint8_t input = serial_.input(KeyboardParty); // A reset command is always accepted, usurping any other state. if(input == HRST) { logger_.info().append("HRST; resetting"); state_ = State::ExpectingRAK1; event_queue_.clear(); serial_.output(KeyboardParty, HRST); return; } switch(state_) { case State::ExpectingACK: if(input != NACK && input != SMAK && input != MACK && input != SACK) { logger_.error().append("No ack; requesting reset"); reset(); break; } state_ = State::Idle; [[fallthrough]]; case State::Idle: switch(input) { case RQID: // Post keyboard ID. serial_.output(KeyboardParty, 0x81); // Declare this to be a UK keyboard. logger_.info().append("RQID; responded with 0x81"); break; case PRST: // "1-byte command, does nothing." logger_.info().append("PRST; ignored"); break; case RQMP: logger_.error().append("RQMP; TODO: respond something other than 0, 0"); enqueue(0, 0); break; case NACK: case SMAK: case MACK: case SACK: { const bool was_scanning_keyboard = input & 1; scan_keyboard_ = input & 1; if(!scan_keyboard_) { posted_states_.reset(); } else if(!was_scanning_keyboard) { needs_state_check_ = true; } scan_mouse_ = input & 2; logger_.info().append("ACK; keyboard:%d mouse:%d", scan_keyboard_, scan_mouse_); } break; default: if((input & 0b1111'0000) == 0b0100'0000) { // RQPD; request to echo the low nibble. serial_.output(KeyboardParty, 0b1110'0000 | (input & 0b1111)); logger_.info().append("RQPD; echoing %x", input & 0b1111); } else if(!(input & 0b1111'1000)) { // LEDS: should set LED outputs. logger_.error().append("TODO: set LEDs %d%d%d", static_cast(input&4), static_cast(input&2), static_cast(input&1)); } else { logger_.info().append("Ignoring unrecognised command %02x received in idle state", input); } break; } break; case State::ExpectingRAK1: if(input != RAK1) { logger_.info().append("Didn't get RAK1; resetting"); reset(); break; } logger_.info().append("Got RAK1; echoing"); serial_.output(KeyboardParty, input); state_ = State::ExpectingRAK2; break; case State::ExpectingRAK2: if(input != RAK2) { logger_.info().append("Didn't get RAK2; resetting"); reset(); break; } logger_.info().append("Got RAK2; echoing"); serial_.output(KeyboardParty, input); state_ = State::ExpectingACK; break; case State::ExpectingBACK: if(input != BACK) { logger_.info().append("Didn't get BACK; resetting"); reset(); break; } logger_.info().append("Got BACK; posting next byte"); dequeue_next(); state_ = State::ExpectingACK; break; } } consider_dequeue(); } void consider_dequeue() { if(state_ == State::Idle) { // If the key event queue is empty but keyboard scanning is enabled, check for // any disparity between posted keys states and actuals. if(needs_state_check_) { needs_state_check_ = false; if(states_ != posted_states_) { for(size_t key = 0; key < Key::Max; key++) { if(states_[key] != posted_states_[key]) { enqueue_key_event(static_cast(key), states_[key]); } } } } // If the key event queue is _still_ empty, grab as much mouse motion // as available. if(event_queue_.empty()) { const int x = std::clamp(mouse_x_, -0x3f, 0x3f); const int y = std::clamp(mouse_y_, -0x3f, 0x3f); mouse_x_ -= x; mouse_y_ -= y; if(x || y) { enqueue(static_cast(x) & 0x7f, static_cast(-y) & 0x7f); } } if(dequeue_next()) { state_ = State::ExpectingBACK; } } } Inputs::Mouse &mouse() { return mouse_; } private: HalfDuplexSerial &serial_; Log::Logger logger_; std::bitset states_; std::bitset posted_states_; bool needs_state_check_ = false; bool scan_keyboard_ = false; bool scan_mouse_ = false; enum class State { ExpectingRAK1, // Post a RAK1 and proceed to ExpectingRAK2 if RAK1 is received; otherwise request a reset. ExpectingRAK2, // Post a RAK2 and proceed to ExpectingACK if RAK2 is received; otherwise request a reset. ExpectingACK, // Process NACK, SACK, MACK or SMAK if received; otherwise request a reset. Idle, // Process any of: NACK, SACK, MACK, SMAK, RQID, RQMP, RQPD or LEDS if received; also // unilaterally begin post a byte pair enqueued but not yet sent if any are waiting. ExpectingBACK, // Dequeue and post one further byte if BACK is received; otherwise request a reset. } state_ = State::Idle; std::vector event_queue_; void enqueue(uint8_t first, uint8_t second) { event_queue_.push_back(first); event_queue_.push_back(second); } bool dequeue_next() { // To consider: a cheaper approach to the queue than this; in practice events // are 'rare' so it's not high priority. if(event_queue_.empty()) return false; serial_.output(KeyboardParty, event_queue_[0]); event_queue_.erase(event_queue_.begin()); return true; } void enqueue_key_event(uint16_t key, bool is_pressed) { posted_states_[key] = is_pressed; enqueue_key_event(row(key), column(key), is_pressed); } void enqueue_key_event(uint8_t row, uint8_t column, bool is_pressed) { logger_.info().append("Posting row %d, column %d is now %s", row, column, is_pressed ? "pressed" : "released"); const uint8_t prefix = is_pressed ? 0b1100'0000 : 0b1101'0000; enqueue(static_cast(prefix | row), static_cast(prefix | column)); } static constexpr uint8_t HRST = 0b1111'1111; // Keyboard reset. static constexpr uint8_t RAK1 = 0b1111'1110; // Reset response #1. static constexpr uint8_t RAK2 = 0b1111'1101; // Reset response #2. static constexpr uint8_t RQID = 0b0010'0000; // Request for keyboard ID. static constexpr uint8_t RQMP = 0b0010'0010; // Request for mouse data. static constexpr uint8_t BACK = 0b0011'1111; // Acknowledge for first keyboard data byte pair. static constexpr uint8_t NACK = 0b0011'0000; // Acknowledge for last keyboard data byte pair, disables both scanning and mouse. static constexpr uint8_t SACK = 0b0011'0001; // Last data byte acknowledge, enabling scanning but disabling mouse. static constexpr uint8_t MACK = 0b0011'0010; // Last data byte acknowledge, disabling scanning but enabling mouse. static constexpr uint8_t SMAK = 0b0011'0011; // Last data byte acknowledge, enabling scanning and mouse. static constexpr uint8_t PRST = 0b0010'0001; // Does nothing. struct Mouse: public Inputs::Mouse { Mouse(Keyboard &keyboard): keyboard_(keyboard) {} void move(int x, int y) override { keyboard_.mouse_x_ += x; keyboard_.mouse_y_ += y; } int get_number_of_buttons() override { return 3; } virtual void set_button_pressed(int index, bool is_pressed) override { keyboard_.set_mouse_button(static_cast(index), is_pressed); } private: Keyboard &keyboard_; }; Mouse mouse_; int mouse_x_ = 0; int mouse_y_ = 0; }; }