Skip to content

Instantly share code, notes, and snippets.

@MMcM
Created January 19, 2021 04:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MMcM/198ea44581e2f6009add26a8907b1333 to your computer and use it in GitHub Desktop.
Save MMcM/198ea44581e2f6009add26a8907b1333 to your computer and use it in GitHub Desktop.

ADDS Viewpoint A2

The ASCII keyboard for an ADDS Terminal from 1983.

Pictures

Hardware

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();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment