diff --git a/arduino/kbd/kbd.cc b/arduino/kbd/kbd.cc new file mode 100644 index 0000000..b450bcb --- /dev/null +++ b/arduino/kbd/kbd.cc @@ -0,0 +1,193 @@ +// keyboard wiring diagram: +// https://www.minuszerodegrees.net/5150/misc/5150_keyboard_reset.jpg + +#include "Arduino.h" + +constexpr int kClockPin = PC6; +constexpr int kDataPin = PC4; + +constexpr int kBitDelayMicros = 100; +constexpr int kCodeDelayMicros = 200; +constexpr int kKeyDelayMicros = 100; + +namespace { + +void sendCode(int code) { + // preconditions: clock and data pins are INPUT + + while (digitalRead(kDataPin) == LOW || digitalRead(kClockPin) == LOW) { + // wait. we're not allowed to send + } + + digitalWrite(kDataPin, LOW); + digitalWrite(kClockPin, LOW); + + pinMode(kClockPin, OUTPUT); + delayMicroseconds(kBitDelayMicros); + pinMode(kClockPin, INPUT); + // delayMicroseconds(kDelayMicros); + + for (int i = 0; i < 8; i++) { + if ((code & (1 << i)) == 0) { + // send a 0 + pinMode(kDataPin, OUTPUT); + } // else do nothing, it's already a 1 + delayMicroseconds(kBitDelayMicros); + + pinMode(kClockPin, OUTPUT); + delayMicroseconds(kBitDelayMicros); + pinMode(kDataPin, INPUT); + pinMode(kClockPin, INPUT); + } + delayMicroseconds(kCodeDelayMicros); +} + +enum State { + kReady, + kMaybeReset, +}; + +constexpr int kResetDelay = 19; + +constexpr int kLetterCodes[] = { + 0x1E, // 'a' + 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, + 0x25, 0x26, 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, + 0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2c, +}; + +constexpr int kNumberCodes[] = { + 0x0B, // '0' + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, +}; + +void sendNormalCode(int code) { + sendCode(code); + sendCode(code | 0x80); +} + +void sendShiftCode(int code) { + sendCode(42); // left shift + sendNormalCode(code); + sendCode(42 | 0x80); +} + +void sendCtrlAltDel() { + sendCode(0x1d); // ctrl + sendCode(0x38); // alt + sendCode(0x53); // del + sendCode(0x53 | 0x80); + sendCode(0x38 | 0x80); + sendCode(0x1d | 0x80); +} + +void sendCtrlBreak() { + sendCode(0x1d); // ctrl + sendCode(0x46); // break + sendCode(0x46 | 0x80); + sendCode(0x1d | 0x80); +} + +} // namespace + +void sendAsciiChar(int c) { + if (c >= 'a' && c <= 'z') { + return sendNormalCode(kLetterCodes[c - 'a']); + } else if (c >= '0' && c <= '9') { + return sendNormalCode(kNumberCodes[c - '0']); + } else if (c >= 'A' && c <= 'Z') { + return sendShiftCode(kLetterCodes[c - 'A']); + } + switch (c) { + case '-': + return sendNormalCode(12); + case '=': + return sendNormalCode(13); + case '[': + return sendNormalCode(26); + case ']': + return sendNormalCode(27); + case ';': + return sendNormalCode(39); + case '\'': + return sendNormalCode(40); + case ',': + return sendNormalCode(51); + case '.': + return sendNormalCode(52); + case '/': + return sendNormalCode(53); + case ' ': + return sendNormalCode(57); + case '\n': + return sendNormalCode(28); + + case '!': + return sendShiftCode(2); + case '@': + return sendShiftCode(3); + case '#': + return sendShiftCode(4); + case '$': + return sendShiftCode(5); + case '%': + return sendShiftCode(6); + case '^': + return sendShiftCode(7); + case '&': + return sendShiftCode(8); + case '*': + return sendShiftCode(9); + case '(': + return sendShiftCode(10); + case ')': + return sendShiftCode(11); + case '_': + return sendShiftCode(12); + case '+': + return sendShiftCode(13); + + case '{': + return sendShiftCode(26); + case '}': + return sendShiftCode(27); + case ':': + return sendShiftCode(39); + case '"': + return sendShiftCode(40); + case '<': + return sendShiftCode(51); + case '>': + return sendShiftCode(52); + case '?': + return sendShiftCode(53); + + case 0: + return sendCtrlAltDel(); + case 27: + return sendCtrlBreak(); // escape + } +} + +void setupKbd() { + pinMode(kClockPin, INPUT); + pinMode(kDataPin, INPUT); +} + +void checkKbdReset() { + static State state = kReady; + static int lastclocklow = 0; + + int clockp = digitalRead(kClockPin); + if (state == State::kReady && clockp == LOW) { + state = State::kMaybeReset; + lastclocklow = millis(); + } else if (state == State::kMaybeReset) { + if (clockp == HIGH && millis() - lastclocklow > kResetDelay) { + delay(1); + state = State::kReady; + sendCode(0xaa); + Serial.println("Reset!"); + } + } +} diff --git a/arduino/kbd/kbd.h b/arduino/kbd/kbd.h new file mode 100644 index 0000000..74ebf23 --- /dev/null +++ b/arduino/kbd/kbd.h @@ -0,0 +1,5 @@ +#pragma once + +void sendAsciiChar(int c); +void setupKbd(); +void checkKbdReset(); diff --git a/arduino/kbd/kbd.ino b/arduino/kbd/kbd.ino index 326cf60..d031808 100644 --- a/arduino/kbd/kbd.ino +++ b/arduino/kbd/kbd.ino @@ -1,213 +1,67 @@ -// keyboard wiring diagram: https://www.minuszerodegrees.net/5150/misc/5150_keyboard_reset.jpg +#include "kbd.h" +#include "paracomm.h" +#include "parallel.h" -constexpr int kClockPin = PC6; -constexpr int kDataPin = PC4; - -constexpr int kBitDelayMicros = 100; -constexpr int kCodeDelayMicros = 200; -constexpr int kKeyDelayMicros = 100; +ParaComms pc; void setup() { - // put your setup code here, to run once: - Serial.begin(115200); - pinMode(LED_BUILTIN, OUTPUT); + // put your setup code here, to run once: + Serial.begin(115200); + pinMode(LED_BUILTIN, OUTPUT); - pinMode(kClockPin, INPUT); - pinMode(kDataPin, INPUT); + setupKbd(); + setupParallel(); - Serial.println("kbd 0.1"); -} + pc = ParaComms{ + .mosi_cb = + [](uint8_t *data, uint8_t len) { + for (int i = 0; i < len; i++) { + Serial.printf("%c", data[i]); + pc.SendByte(data[i]); // echo + if (data[i] == '\r') { + pc.SendByte('\n'); + } + } + }, + }; -// 0. reset -// 0.0. clock line will be held low externally for 20 ms -// when back up, keyboard needs to clock 0xAA as a response -// -// 1. operation -// 1.0. just directly clock out scan codes of pressed keys - -void sendCode(int code) { - Serial.printf("sending code 0x%02x\n", code); - // preconditions: clock and data pins are INPUT - - while (digitalRead(kDataPin) == LOW || digitalRead(kClockPin) == LOW) { - // wait. we're not allowed to send - } - - digitalWrite(kDataPin, LOW); - digitalWrite(kClockPin, LOW); - - pinMode(kClockPin, OUTPUT); - delayMicroseconds(kBitDelayMicros); - pinMode(kClockPin, INPUT); - // delayMicroseconds(kDelayMicros); - - for (int i = 0; i < 8; i++) { - if ((code & (1 << i)) == 0) { - // send a 0 - pinMode(kDataPin, OUTPUT); - } // else do nothing, it's already a 1 - delayMicroseconds(kBitDelayMicros); - - pinMode(kClockPin, OUTPUT); - delayMicroseconds(kBitDelayMicros); - pinMode(kDataPin, INPUT); - pinMode(kClockPin, INPUT); - } - delayMicroseconds(kCodeDelayMicros); -} - -enum State { - kReady, - kMaybeReset, -}; - -constexpr int kResetDelay = 19; - -State state = State::kReady; -int lastclocklow = 0; - -constexpr int kLetterCodes[] = { - 0x1E, // 'a' - 0x30, - 0x2E, - 0x20, - 0x12, - 0x21, - 0x22, - 0x23, - 0x17, - 0x24, - 0x25, - 0x26, - 0x32, - 0x31, - 0x18, - 0x19, - 0x10, - 0x13, - 0x1F, - 0x14, - 0x16, - 0x2F, - 0x11, - 0x2D, - 0x15, - 0x2c, -}; - -constexpr int kNumberCodes[] = { - 0x0B, // '0' - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, - 0x09, - 0x0A, -}; - -void sendNormalCode(int code) { - sendCode(code); - sendCode(code | 0x80); -} - -void sendShiftCode(int code) { - sendCode(42); // left shift - sendNormalCode(code); - sendCode(42 | 0x80); -} - -void sendCtrlAltDel() { - sendCode(0x1d); // ctrl - sendCode(0x38); // alt - sendCode(0x53); // del - sendCode(0x53 | 0x80); - sendCode(0x38 | 0x80); - sendCode(0x1d | 0x80); -} - -void sendCtrlBreak() { - sendCode(0x1d); // ctrl - sendCode(0x46); // break - sendCode(0x46 | 0x80); - sendCode(0x1d | 0x80); -} - -void sendAsciiChar(int c) { - if (c >= 'a' && c <= 'z') { - return sendNormalCode(kLetterCodes[c - 'a']); - } else if (c >= '0' && c <= '9') { - return sendNormalCode(kNumberCodes[c - '0']); - } else if (c >= 'A' && c <= 'Z') { - return sendShiftCode(kLetterCodes[c - 'A']); - } - switch (c) { - case '-': return sendNormalCode(12); - case '=': return sendNormalCode(13); - case '[': return sendNormalCode(26); - case ']': return sendNormalCode(27); - case ';': return sendNormalCode(39); - case '\'': return sendNormalCode(40); - case ',': return sendNormalCode(51); - case '.': return sendNormalCode(52); - case '/': return sendNormalCode(53); - case ' ': return sendNormalCode(57); - case '\n': return sendNormalCode(28); - - case '!': return sendShiftCode(2); - case '@': return sendShiftCode(3); - case '#': return sendShiftCode(4); - case '$': return sendShiftCode(5); - case '%': return sendShiftCode(6); - case '^': return sendShiftCode(7); - case '&': return sendShiftCode(8); - case '*': return sendShiftCode(9); - case '(': return sendShiftCode(10); - case ')': return sendShiftCode(11); - case '_': return sendShiftCode(12); - case '+': return sendShiftCode(13); - - case '{': return sendShiftCode(26); - case '}': return sendShiftCode(27); - case ':': return sendShiftCode(39); - case '"': return sendShiftCode(40); - case '<': return sendShiftCode(51); - case '>': return sendShiftCode(52); - case '?': return sendShiftCode(53); - - case 0: return sendCtrlAltDel(); - case 27: return sendCtrlBreak(); // escape - } + Serial.println("kbd 0.1"); } void loop() { - static int led_counter = 0; - static int led = HIGH; - if (led_counter > 400000) { - led_counter = 0; - led = (led == HIGH) ? LOW : HIGH; - digitalWrite(LED_BUILTIN, led); - } - led_counter += 1; + static int led_counter = 0; + static int led = HIGH; - int clockp = digitalRead(kClockPin); - if (state == State::kReady && clockp == LOW) { - state = State::kMaybeReset; - lastclocklow = millis(); - } else if (state == State::kMaybeReset) { - if (clockp == HIGH && millis() - lastclocklow > kResetDelay) { - delay(1); - state = State::kReady; - sendCode(0xaa); - Serial.println("Reset!"); + static int mode = 0; // 0 = keyboard, 1 = parallel + + if (led_counter > 400000) { + led_counter = 0; + led = (led == HIGH) ? LOW : HIGH; + digitalWrite(LED_BUILTIN, led); } - } + led_counter += 1; - if (Serial.available() > 0) { - int c = Serial.read(); - sendAsciiChar(c); - //delayMicroseconds(kKeyDelayMicros); - } + checkKbdReset(); + + uint8_t mosib; + if (strobeOccurred(mosib)) { + pc.FeedMosiData(mosib); + uint8_t misob = pc.NextMisoNibble(); + writeParallel(misob); + } + + if (Serial.available() > 0) { + int c = Serial.read(); + if (c == 2) { + mode = 0; + } else if (c == 3) { + mode = 1; + } else { + if (mode == 0) { + sendAsciiChar(c); + } else { + pc.SendByte(c); + } + } + } } diff --git a/arduino/kbd/paracomm.h b/arduino/kbd/paracomm.h new file mode 100644 index 0000000..bcbdd75 --- /dev/null +++ b/arduino/kbd/paracomm.h @@ -0,0 +1,124 @@ +#pragma once + +#include + +struct ParaComms { + using MessageCallback = void (*)(uint8_t *, uint8_t); + enum State { + kIdle, + kLength, + kLength2, // for miso + kData, + // kCrc, // ? + }; + static constexpr uint8_t kMosiStartByte = 0x42; + static constexpr uint8_t kIdleByte = 0x00; + static constexpr uint8_t kMisoStartNibble = 0xa; + static constexpr uint8_t kMisoIdleNibble = 0x0; + + MessageCallback mosi_cb = nullptr; + uint8_t *miso_buffer = nullptr; + uint8_t miso_size = 0; + uint8_t miso_nibbles_sent = 0; + State miso_state = kIdle; + + // double work buffer. one being sent, one being written to + uint8_t miso_workbuf[2][256]; + uint8_t miso_workbuf_idx = 0; + uint8_t miso_workbuf_size = 0; + + uint8_t mosi_buffer[256]; + uint8_t mosi_size = 0; + uint8_t mosi_received = 0; + State mosi_state = kIdle; + + void SwapBuffers() { + miso_buffer = miso_workbuf[miso_workbuf_idx]; + miso_size = miso_workbuf_size; + + miso_workbuf_idx = (miso_workbuf_idx + 1) % 2; + miso_workbuf_size = 0; + } + + int SendByte(uint8_t b) { + if (miso_workbuf_size == 256) { + return -1; // sorry we're full + } + + uint8_t *buff = miso_workbuf[miso_workbuf_idx]; + buff[miso_workbuf_size] = b; + miso_workbuf_size += 1; + + return 0; + } + + void FeedMosiData(uint8_t b) { + switch (mosi_state) { + case kIdle: + // check start byte + if (b == kMosiStartByte) { + mosi_state = kLength; + } else if (b == kIdleByte) { + // just twiddling our thumb drives + } else { + // Serial.printf("sp: %02x\n", b); + } + break; + case kLength: + // assert(b > 0) + mosi_size = b; + mosi_received = 0; + mosi_state = kData; + break; + case kData: + mosi_buffer[mosi_received] = b; + mosi_received += 1; + + if (mosi_received == mosi_size) { + if (mosi_cb != nullptr) { + mosi_cb(mosi_buffer, mosi_received); + } + mosi_state = kIdle; + } + case kLength2: // error + return; + } + } + + uint8_t NextMisoNibble() { + switch (miso_state) { + case kIdle: + if (miso_size == 0) { + SwapBuffers(); + if (miso_size == 0) { + return kMisoIdleNibble; + } + } + miso_state = kLength; + return kMisoStartNibble; + case kLength: + // assert(miso_size > 0); + miso_state = kLength2; + return miso_size & 0xf; + case kLength2: + miso_nibbles_sent = 0; + miso_state = kData; + return miso_size >> 4; + case kData: { + uint8_t b = miso_buffer[miso_nibbles_sent / 2]; + if (miso_nibbles_sent % 2 == 0) { + b &= 0xf; + } else { + b >>= 4; + } + miso_nibbles_sent += 1; + if (miso_nibbles_sent == miso_size * 2) { + SwapBuffers(); + miso_state = kIdle; + } + return b; + } + } + __builtin_unreachable(); + } +}; diff --git a/arduino/kbd/parallel.cc b/arduino/kbd/parallel.cc new file mode 100644 index 0000000..6a2b1e5 --- /dev/null +++ b/arduino/kbd/parallel.cc @@ -0,0 +1,95 @@ +#include "parallel.h" + +#include + +#include "Arduino.h" + +namespace { + +constexpr int kParallelDataPins[] = { + D9, D8, D7, D6, D5, D4, D3, D2, +}; + +constexpr int kParallelBusyPin = D14; +constexpr int kParallelAckPin = D15; +constexpr int kParallelPaperoutPin = D12; +constexpr int kParallelSelectPin = D11; + +constexpr int kParallelNibblePins[] = { + kParallelSelectPin, + kParallelPaperoutPin, + kParallelAckPin, + kParallelBusyPin, +}; + +constexpr bool kParallelNibbleInverted[] = { + false, + false, + false, + true, +}; + +constexpr int kParallelStrobePin = D10; + +volatile bool strobe_pending = false; +volatile uint8_t strobe_byte = 0; + +void strobeIsr() { + strobe_pending = true; + strobe_byte = readParallel(); +} + +} // namespace + +uint8_t readParallel() { + uint8_t out = 0; + for (int i = 0; i < 8; i++) { + if (digitalReadFast(digitalPinToPinName(kParallelDataPins[i])) == + HIGH) { + out |= (1 << i); + } + } + if (digitalReadFast(digitalPinToPinName(kParallelStrobePin)) == HIGH) { + out |= (1 << 8); + } + return out; +} + +#define BIT(x, n) ((x & (1 << n)) ? HIGH : LOW) + +void writeParallel(uint8_t nibble) { + for (int i = 0; i < 4; i++) { + int lvl = BIT(nibble, i); + if (kParallelNibbleInverted[i]) { + lvl = lvl == HIGH ? LOW : HIGH; + } + digitalWriteFast(digitalPinToPinName(kParallelNibblePins[i]), lvl); + } +} + +bool strobeOccurred(uint8_t &byte) { + bool pending; + { + noInterrupts(); + pending = strobe_pending; + byte = strobe_byte; + strobe_pending = false; + interrupts(); + } + + return pending; +} + +void setupParallel() { + for (int i = 0; i < sizeof(kParallelDataPins) / sizeof(int); i++) { + pinMode(kParallelDataPins[i], INPUT); + } + for (int i = 0; i < sizeof(kParallelNibblePins) / sizeof(int); i++) { + pinMode(kParallelNibblePins[i], OUTPUT); + digitalWrite(kParallelNibblePins[i], HIGH); + } + pinMode(kParallelStrobePin, INPUT); + + attachInterrupt(digitalPinToInterrupt(kParallelStrobePin), strobeIsr, + FALLING); +} diff --git a/arduino/kbd/parallel.h b/arduino/kbd/parallel.h new file mode 100644 index 0000000..45ceae8 --- /dev/null +++ b/arduino/kbd/parallel.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +/** Returns the byte present on D0~D7 */ +uint8_t readParallel(); + +/** Writes a nibble on status pins */ +void writeParallel(uint8_t nibble); + +void setupParallel(); + +/** Returns true and sets the byte if a strobe was received */ +bool strobeOccurred(uint8_t &byte);