2024-03-20 18:25:20 +00:00
|
|
|
//
|
|
|
|
// Keyboard.hpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 20/03/2024.
|
|
|
|
// Copyright © 2024 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "HalfDuplexSerial.hpp"
|
|
|
|
|
|
|
|
namespace Archimedes {
|
|
|
|
|
|
|
|
// Resource for the keyboard protocol: https://github.com/tmk/tmk_keyboard/wiki/ACORN-ARCHIMEDES-Keyboard
|
|
|
|
struct Keyboard {
|
|
|
|
Keyboard(HalfDuplexSerial &serial) : serial_(serial) {}
|
|
|
|
|
2024-03-23 19:58:48 +00:00
|
|
|
void set_key_state(int row, int column, bool is_pressed) {
|
2024-03-24 01:02:35 +00:00
|
|
|
if(!scan_keyboard_) return;
|
2024-03-23 21:08:03 +00:00
|
|
|
|
2024-03-24 01:02:35 +00:00
|
|
|
// Don't waste bandwidth on repeating facts.
|
|
|
|
if(states_[row][column] == is_pressed) return;
|
|
|
|
states_[row][column] = is_pressed;
|
|
|
|
|
|
|
|
// Post new key event.
|
2024-03-23 19:58:48 +00:00
|
|
|
const uint8_t prefix = is_pressed ? 0b1100'0000 : 0b1101'0000;
|
2024-03-23 21:08:03 +00:00
|
|
|
enqueue(static_cast<uint8_t>(prefix | row), static_cast<uint8_t>(prefix | column));
|
|
|
|
consider_dequeue();
|
2024-03-23 19:43:04 +00:00
|
|
|
}
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
void update() {
|
|
|
|
if(serial_.events(KeyboardParty) & HalfDuplexSerial::Receive) {
|
2024-03-23 21:08:03 +00:00
|
|
|
const auto reset = [&]() {
|
|
|
|
serial_.output(KeyboardParty, HRST);
|
2024-03-24 01:12:01 +00:00
|
|
|
state_ = State::Idle;
|
2024-03-23 21:08:03 +00:00
|
|
|
};
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
const uint8_t input = serial_.input(KeyboardParty);
|
2024-03-23 21:08:03 +00:00
|
|
|
|
|
|
|
// A reset command is always accepted, usurping any other state.
|
|
|
|
if(input == HRST) {
|
2024-03-24 01:12:01 +00:00
|
|
|
state_ = State::ExpectingRAK1;
|
2024-03-23 21:08:03 +00:00
|
|
|
event_queue_.clear();
|
|
|
|
serial_.output(KeyboardParty, HRST);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-24 01:12:01 +00:00
|
|
|
switch(state_) {
|
|
|
|
case State::ExpectingACK:
|
2024-03-24 01:02:35 +00:00
|
|
|
if(input != NACK && input != SMAK && input != MACK && input != SACK) {
|
|
|
|
reset();
|
|
|
|
break;
|
|
|
|
}
|
2024-03-24 01:12:01 +00:00
|
|
|
state_ = State::Idle;
|
2024-03-24 01:02:35 +00:00
|
|
|
[[fallthrough]];
|
|
|
|
|
2024-03-24 01:12:01 +00:00
|
|
|
case State::Idle:
|
2024-03-23 21:08:03 +00:00
|
|
|
switch(input) {
|
|
|
|
case RQID: // Post keyboard ID.
|
|
|
|
serial_.output(KeyboardParty, 0x81); // Declare this to be a UK keyboard.
|
2024-03-24 01:12:01 +00:00
|
|
|
state_ = State::Idle;
|
2024-03-23 21:08:03 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case PRST: // "1-byte command, does nothing."
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RQMP:
|
|
|
|
// TODO: real mouse data.
|
|
|
|
enqueue(0, 0);
|
|
|
|
break;
|
|
|
|
|
2024-03-24 01:02:35 +00:00
|
|
|
case NACK:
|
|
|
|
scan_keyboard_ = scan_mouse_ = false;
|
|
|
|
break;
|
|
|
|
case SMAK:
|
|
|
|
scan_keyboard_ = scan_mouse_ = true;
|
|
|
|
break;
|
|
|
|
case MACK:
|
|
|
|
scan_keyboard_ = false;
|
|
|
|
scan_mouse_ = true;
|
|
|
|
break;
|
|
|
|
case SACK:
|
|
|
|
scan_keyboard_ = true;
|
|
|
|
scan_mouse_ = false;
|
|
|
|
break;
|
|
|
|
|
2024-03-23 21:08:03 +00:00
|
|
|
default:
|
|
|
|
if((input & 0b1111'0000) == 0b0100'0000) {
|
|
|
|
// RQPD; request to echo the low nibble.
|
|
|
|
serial_.output(KeyboardParty, 0b1110'0000 | (input & 0b1111));
|
2024-03-24 01:02:35 +00:00
|
|
|
} else if(!(input & 0b1111'1000)) {
|
2024-03-23 21:08:03 +00:00
|
|
|
// LEDS: should set LEd outputs.
|
2024-03-24 01:02:35 +00:00
|
|
|
} else {
|
|
|
|
reset();
|
2024-03-23 21:08:03 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2024-03-24 01:12:01 +00:00
|
|
|
case State::ExpectingRAK1:
|
2024-03-23 21:08:03 +00:00
|
|
|
if(input != RAK1) {
|
|
|
|
reset();
|
|
|
|
break;
|
|
|
|
}
|
2024-03-20 18:25:20 +00:00
|
|
|
serial_.output(KeyboardParty, input);
|
2024-03-24 01:12:01 +00:00
|
|
|
state_ = State::ExpectingRAK2;
|
2024-03-20 18:25:20 +00:00
|
|
|
break;
|
|
|
|
|
2024-03-24 01:12:01 +00:00
|
|
|
case State::ExpectingRAK2:
|
2024-03-23 21:08:03 +00:00
|
|
|
if(input != RAK2) {
|
|
|
|
reset();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
serial_.output(KeyboardParty, input);
|
2024-03-24 01:12:01 +00:00
|
|
|
state_ = State::ExpectingACK;
|
2024-03-20 18:25:20 +00:00
|
|
|
break;
|
|
|
|
|
2024-03-24 01:12:01 +00:00
|
|
|
case State::ExpectingBACK:
|
2024-03-23 21:08:03 +00:00
|
|
|
if(input != BACK) {
|
|
|
|
reset();
|
|
|
|
break;
|
|
|
|
}
|
2024-03-24 01:02:35 +00:00
|
|
|
dequeue_next();
|
2024-03-24 01:12:01 +00:00
|
|
|
state_ = State::ExpectingACK;
|
2024-03-23 19:58:48 +00:00
|
|
|
break;
|
2024-03-20 18:25:20 +00:00
|
|
|
}
|
2024-03-23 21:08:03 +00:00
|
|
|
|
|
|
|
consider_dequeue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void consider_dequeue() {
|
2024-03-24 01:12:01 +00:00
|
|
|
if(state_ == State::Idle && dequeue_next()) {
|
|
|
|
state_ = State::ExpectingBACK;
|
2024-03-20 18:25:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
HalfDuplexSerial &serial_;
|
|
|
|
|
2024-03-24 01:02:35 +00:00
|
|
|
bool states_[16][16]{};
|
|
|
|
|
2024-03-23 21:08:03 +00:00
|
|
|
bool scan_keyboard_ = false;
|
|
|
|
bool scan_mouse_ = false;
|
2024-03-24 01:12:01 +00:00
|
|
|
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;
|
2024-03-23 21:08:03 +00:00
|
|
|
|
|
|
|
std::vector<uint8_t> 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;
|
2024-03-23 19:58:48 +00:00
|
|
|
}
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
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.
|
2024-03-24 01:12:01 +00:00
|
|
|
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.
|
2024-03-20 18:25:20 +00:00
|
|
|
static constexpr uint8_t PRST = 0b0010'0001; // Does nothing.
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|