Skip to content

Instantly share code, notes, and snippets.

@MMcM
Created August 22, 2021 01:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MMcM/36fed5189a22cd381af2c6e26cd922a6 to your computer and use it in GitHub Desktop.
Save MMcM/36fed5189a22cd381af2c6e26cd922a6 to your computer and use it in GitHub Desktop.
Coleco Adam Keyboard as ASCII virtual serial port using Teensy 3.2
/*
* Wiring for Teensy3.2:
* Teensy 1(TX1) -- RJ 1
* Teensy 2 -- RJ 2 (optional)
* Teensy GND -- RJ 3,4,5
* Teensy Vin -- RJ 6
*
* Note that a real Coleco keyboard cable is reversed.
* (E.g., https://raw.githubusercontent.com/Kalidomra/AdamNet-Drive-Emulator/master/Connection%20Diagram.jpg)
* It is best to check that 3,4,5 are connected to one another on the Teensy end.
*/
// See https://console5.com/techwiki/images/b/b5/Coleco_ADAM_Technical_Reference_Manual.pdf, Chapter 3.3.
// The high nibble is the message type and the low nibble is the address.
// Since we only send to device 1, the keyboard, we bake that in.
const uint8_t MN_RESET = 0x01; // command.control (reset)
const uint8_t MN_STATUS = 0x11; // command.control (status)
const uint8_t MN_ACK = 0x21; // command.control (ack)
const uint8_t MN_CLR = 0x31; // command.control (clr)
const uint8_t MN_RECEIVE = 0x41; // command.control (receive)
const uint8_t MN_CANCEL = 0x51; // command.control (cancel)
const uint8_t MN_SEND = 0x61; // command.data (send)
const uint8_t MN_NACK = 0x71; // command.control (nack)
const uint8_t MN_READY = 0xD1; // command.control (ready)
const uint8_t NM_STATUS = 0x81; // response.control (status)
const uint8_t NM_ACK = 0x91; // response.control (ack)
const uint8_t NM_CANCEL = 0xA1; // response.control (cancel)
const uint8_t NM_SEND = 0xB1; // response.data (send)
const uint8_t NM_NACK = 0xC1; // response.control (nack)
#define DEBUG_ADAMNET 0
void send_command(uint8_t command) {
#if DEBUG_ADAMNET
Serial.print("S: ");
Serial.println(command, HEX);
#endif
UART0_C3 |= (1 << 5); // Switch Transmit pin direction to output.
Serial1.write(command);
Serial1.flush(); // Wait for transmit complete.
UART0_C3 &= ~(1 << 5); // Switch Transmit pin direction back to input.
}
bool receive_response(uint8_t *buffer, uint8_t expected_length) {
uint8_t len = Serial1.readBytes(buffer, expected_length);
#if DEBUG_ADAMNET
Serial.print("R:");
for (uint8_t i = 0; i < len; i++) {
Serial.print(" ");
Serial.print(buffer[i], HEX);
}
Serial.println();
#endif
return len == expected_length;
}
void setup() {
Serial.begin(115200);
while (!Serial) {
}
Serial1.begin(62500, SERIAL_8N1_RXINV_TXINV);
UART0_C1 |= (1 << 7) | (1 << 5); // LOOPS RSRC, enabling single-wire mode.
Serial1.setTimeout(100);
// Hard RESET.
pinMode(2, OUTPUT);
digitalWrite(2, HIGH);
delay(100);
// Soft RESET. Mostly turns off LOCK.
send_command(MN_RESET);
}
const uint32_t RECEIVE_INTERVAL = 10;
void loop() {
static uint32_t last_receive_time = 0;
if (millis() - last_receive_time > RECEIVE_INTERVAL) {
uint8_t buf[8];
send_command(MN_RECEIVE);
if (receive_response(buf, 1)) {
if (buf[0] == NM_ACK) {
send_command(MN_CLR); // Clear to Send
if (receive_response(buf, 5) && buf[0] == NM_SEND) {
if (buf[1] == 0 && buf[2] == 1 && buf[3] == buf[4]) {
// Length 1, checksum okay.
send_command(MN_ACK);
#if DEBUG_ADAMNET
char ch = buf[3];
Serial.print("A: ");
Serial.print(ch, HEX);
if (ch > 0x20 && ch < 0x7F) {
Serial.print(" ");
Serial.write(ch);
}
Serial.println();
#else
Serial.write(buf[3]);
#endif
last_receive_time = millis();
} else {
send_command(MN_NACK);
// Try again right away.
}
}
} else if (buf[0] == NM_NACK) {
// No input available, wait a bit.
last_receive_time = millis();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment