The ASCII keyboard for an ADDS Terminal from 1983.
The keyboard communicates using a special serial protocol. The keyboard is powered with 12VDC.
RJ | Color | Signal | AVR |
---|---|---|---|
1 | Black | +12V | |
2 | Red | Data | PD0 |
3 | Green | Ground | GND |
4 | Yellow | PE |
The ASCII keyboard for an ADDS Terminal from 1983.
The keyboard communicates using a special serial protocol. The keyboard is powered with 12VDC.
RJ | Color | Signal | AVR |
---|---|---|---|
1 | Black | +12V | |
2 | Red | Data | PD0 |
3 | Green | Ground | GND |
4 | Yellow | PE |
import sigrokdecode as srd | |
from collections import namedtuple | |
class Ann: | |
SHORT, LONG, MASKED, CODE, ASCII = range(5) | |
Pulse = namedtuple('Pulse', 'ss es is_long') | |
class Decoder(srd.Decoder): | |
api_version = 3 | |
id = 'adds_viewpoint' | |
name = 'VPT' | |
longname = 'ADDS Viewpoint' | |
desc = 'ADDS Viewpoint keyboard interface.' | |
license = 'mit' | |
inputs = ['logic'] | |
outputs = [] | |
tags = ['PC'] | |
channels = ( | |
{'id': 'data', 'name': 'Data', 'desc': 'Data line'}, | |
{'id': 'mask', 'name': 'Mask', 'desc': 'Mask line for own strobes'}, | |
) | |
annotations = ( | |
('short', 'Short'), | |
('long', 'Long'), | |
('masked', 'Masked'), | |
('code', 'Code'), | |
('ascii', 'ASCII'), | |
) | |
annotation_rows = ( | |
('bits', 'Bits', (0, 1, 2)), | |
('fields', 'Fields', (3,)), | |
('ascii', 'ASCII', (4,)), | |
) | |
def __init__(self): | |
self.reset() | |
def metadata(self, key, value): | |
if key == srd.SRD_CONF_SAMPLERATE: | |
self.samplerate = value | |
def reset(self): | |
self.bits = [] | |
self.bitcount = 0 | |
def start(self): | |
self.out_ann = self.register(srd.OUTPUT_ANN) | |
def next_bit(self, ss, es, masked): | |
if masked: | |
self.put(ss, es, self.out_ann, [Ann.MASKED, ['Masked', 'M']]) | |
self.reset() | |
return | |
length = (es - ss) / self.samplerate | |
is_long = length > 100e-6 | |
self.bits.append(Pulse(ss, es, is_long)) | |
self.bitcount += 1 | |
if self.bitcount < 8: | |
return | |
for i in range(self.bitcount): | |
bit = self.bits[i] | |
ann = [Ann.LONG, ['Long', 'L']] if bit.is_long else [Ann.SHORT, ['Short', 'S']] | |
self.put(bit.ss, bit.es, self.out_ann, ann) | |
word = 0 | |
for i in range(8): | |
if self.bits[i].is_long: | |
word |= (1 << i) | |
code = [Ann.CODE, ['Code: %02X' % word, 'C: %02X' % word, '%02X' % word]] | |
self.put(self.bits[0].ss, self.bits[7].es, self.out_ann, code) | |
if word >= 32 and word < 127: | |
ascii = [Ann.ASCII, ['ASCII: %c' % word, 'A: %c' % word, '%c' % word]] | |
self.put(self.bits[0].ss, self.bits[7].es, self.out_ann, ascii) | |
self.reset() | |
def decode(self): | |
while True: | |
masked = self.wait({0: 'f'})[1] | |
ss = self.samplenum | |
self.wait({0: 'r'}) | |
es = self.samplenum | |
self.next_bit(ss, es, masked) |
#define wait_us delayMicroseconds | |
#define timer_read32 millis | |
// Bidirectional data line on PD0. | |
#define DATA_DDR DDRD | |
#define DATA_PIN PIND | |
#define DATA_PORT PORTD | |
#define DATA_MASK (1 << 0) | |
#define LED_DDR DDRC | |
#define LED_PORT PORTC | |
#define LED_MASK (1 << 7) | |
// Arduino's micros() requires interrupts. So Timer3 is set up to count once every 16us. | |
#define SHORT_MIN_WIDTH 1 | |
#define LONG_MIN_WIDTH 6 | |
#define LONG_MAX_WIDTH 12 | |
#define XMIT_WAIT_US 26 | |
#define POLL_INTERVAL_MS 10 | |
#define QUEUE_SIZE 16 | |
static uint16_t char_queue[QUEUE_SIZE]; | |
static uint8_t char_queue_in, char_queue_out; | |
static inline void queue_clear(void) { | |
char_queue_in = char_queue_out = 0; | |
} | |
static inline bool queue_is_empty(void) { | |
return (char_queue_in == char_queue_out); | |
} | |
static inline bool queue_is_full(void) { | |
// One entry wasted to be able to check this easily. | |
return (((char_queue_in + 1) % QUEUE_SIZE) == char_queue_out); | |
} | |
static inline uint16_t queue_remove(void) { | |
uint16_t frame = char_queue[char_queue_out]; | |
char_queue_out = (char_queue_out + 1) % QUEUE_SIZE; | |
return frame; | |
} | |
static inline void queue_add(uint16_t frame) { | |
char_queue[char_queue_in] = frame; | |
char_queue_in = (char_queue_in + 1) % QUEUE_SIZE; | |
} | |
ISR(INT0_vect) { | |
uint16_t time = TCNT3; | |
bool state = (DATA_PIN & DATA_MASK) != 0; | |
static uint16_t pulse_start_time; | |
if (state) { | |
// Going high, end of a bit pulse. | |
uint16_t delta = time - pulse_start_time; | |
static uint8_t bit_count = 0; | |
static uint8_t bits = 0; | |
if (delta < SHORT_MIN_WIDTH || delta > LONG_MAX_WIDTH) { | |
bit_count = 0; | |
bits = 0; | |
return; | |
} | |
if (delta >= LONG_MIN_WIDTH) { | |
bits |= (1 << bit_count); | |
} | |
bit_count++; | |
if (bit_count >= 8) { | |
if (!queue_is_full()) { | |
queue_add(bits); | |
} | |
bit_count = 0; | |
bits = 0; | |
LED_PORT &= ~LED_MASK; | |
} | |
} else { | |
// Going low, start of bit pulse. | |
pulse_start_time = time; | |
LED_PORT |= LED_MASK; | |
} | |
} | |
// I am not sure why only these 16 keys send what seem to be raw codes with shift bits. | |
static const uint8_t PROGMEM shifted[3][16] = { | |
{ '2', '3', '4', '6', 'q', 'w', 'y', 'a', 'z', 'm', '`', '[', ']', '\\', ';', '\'' }, | |
{ '@', '#', '$', '^', 'Q', 'W', 'Y', 'A', 'Z', 'M', '~', '{', '}', '|', ':', '"' }, | |
{ 0x12, 0x13, 0x14, 0x16, 0x11, 0x17, 0x19, 0x01, 0x1A, 0x0D, 0x00, 0x1B, 0x1D, 0x1C, 0x1B, 0x07 } | |
}; | |
static void output_char(uint8_t ch) { | |
static bool caps_lock = false; | |
if (ch == 0xC4) { | |
caps_lock = !caps_lock; | |
return; | |
} | |
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { | |
if (!caps_lock) { | |
ch ^= 0x20; | |
} | |
} else if (ch >= 0x80 && ch <= 0xAF) { | |
uint8_t shift = (ch >> 4) - 0x08; | |
if (caps_lock && (ch < 0xA0)) { | |
shift ^= 1; | |
} | |
const uint8_t *pch = shifted[shift] + (ch & 0x0F); | |
ch = pgm_read_byte(pch); | |
} | |
Serial.write(ch); | |
} | |
void setup() { | |
Serial.begin(115200); | |
while (!Serial) { | |
} | |
// Input pull-up to start. | |
DATA_DDR &= ~DATA_MASK; | |
DATA_PORT |= DATA_MASK; | |
#if F_CPU != 16000000 | |
#error Wrong prescalar for clock rate | |
#endif | |
// Timer3 count at 16us. | |
TCCR3A = 0; // Normal | |
TCCR3B = _BV(CS32); // Prescalar = 256 | |
// Interrupt 0 on any edge. | |
EIMSK |= (1 << INT0); | |
EICRA |= (1 << ISC00); | |
// Turn on LED while receiving. | |
LED_DDR |= LED_MASK; | |
LED_PORT &= ~LED_MASK; | |
} | |
void loop() { | |
static uint8_t last_ch; | |
static bool ch_received = false; | |
static bool key_down = false; | |
static uint32_t last_poll_time = 0; | |
uint32_t now = timer_read32(); | |
if (!queue_is_empty()) { | |
uint8_t ch = queue_remove(); | |
ch_received = true; | |
if (!key_down || last_ch != ch) { | |
output_char(ch); | |
last_ch = ch; | |
key_down = true; | |
} | |
} | |
if (now - last_poll_time > POLL_INTERVAL_MS) { | |
if (!ch_received) { | |
key_down = false; | |
} | |
ch_received = false; | |
last_poll_time = now; | |
cli(); | |
DATA_DDR |= DATA_MASK; | |
DATA_PORT &= ~DATA_MASK; | |
wait_us(XMIT_WAIT_US); | |
DATA_DDR &= ~DATA_MASK; | |
DATA_PORT |= DATA_MASK; | |
sei(); | |
} | |
} |