This repository has been archived by the owner on Aug 11, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathtypewriter.pde
600 lines (505 loc) · 15.2 KB
/
typewriter.pde
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
/*
* Copyright © 2009 Scott Perry
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associapted documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* preprocessor macros
*/
#define DUEMILANOVE
// disables use of Serial in the reading functions
//#define DEBUG_WRITES
/*
* disables flooding the typewriter's lines to prevent passing keys when there
* is nothing being actively written.
*/
//#define KEYBOARD_PASSTHROUGH
/*
* TODO: though I'm tempted to throw the metapass idea out, it would still be
* interesting to see why it's broken. it doesn't make a lot of sense.
*/
//#define METAPASS
// TODO: do a final review of plswrite, maybe even improve it
/*
* system status (LED)
* in the future this can be used for flow control (with STS).
*/
#define LED 13
// measured in scans, more or less arbitrarily chosen.
#define KEYPRESS_DURATION 3
#define KEYPRESS_GAP 2
// measured in ms
#define SCANLINE_INTERVAL 6
// measured in… baud
#define BAUD 115200
// helper functions
#define LONIBBLE(x) ((x) & 0x0F)
#define HINIBBLE(x) (((x) >> 4) & 0x0F)
#define LOHINIBBLE(x) (((x) >> 8) & 0x0F)
#define BIT(x) (0x01 << (x))
#define MASK(x) (~(0x01 << (x)) & 0xFF)
/*
* includes
*/
// functions for interacting with the scan/signal lines. no logic.
#include "signals.h"
// tables of key codes for reading and writing
#include "keycodes.h"
// debouncer for tracking scanline state
#include "debounce.h"
/*
* global state
*/
// loop-local persistant state
struct {
// debouncer for catching scanline changes
debounceState debouncer;
// the time of the last scanline transition, in ms
uint32_t scanedge;
} loopstate;
// input signal state
uint8_t signal_tracker[] = {
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF
};
// bitfield of width 3. what a waste
uint8_t meta_key_state;
#ifdef PLSWRITE
/*
* key code the read side would like to be written by the write side
*/
uint8_t plswrite;
#endif
// write-local persistant state
struct {
// counts how long the current write cycle has been, or 0 when not writing
uint8_t writing;
// the character being written, 0 when not writing
uint8_t character;
// meta key(s) associated with character. stale when character is 0
uint8_t meta:4;
// scan line associated with character. stale when character is 0
uint8_t scan:3;
// signal line associated with character. stale when character is 0
uint8_t signal:3;
} writestate;
/*
* read_keydown --
* actions to be taken when a key is pressed. includes:
* * meta key state tracking
* * serial output of appropriate codes based on previous state
*
* args:
* keycode - the keycode for the key held down
*/
void read_keydown(uint8_t keycode) {
// meta key states
if(HINIBBLE(keycode) == 0x01) {
if(keycode == SHIFT_LOCK) {
keycode = SHIFT;
}
#ifdef METAPASS
#ifdef PLSWRITE
// update the typewriter's display to reflect the shift key being held
if(keycode == SHIFT) {
plswrite = SHIFT_LOCK;
}
#endif
#endif
meta_key_state |= LONIBBLE(keycode);
}
#ifndef DEBUG_WRITES
Serial.print(keycode);
#endif
}
/*
* read_keyheld --
* actions to be taken while a key is held.
*
* args:
* keycode - the keycode for the key held down
*/
void read_keyheld(uint8_t keycode) {
return;
}
/*
* read_keyup --
* actions to be taken when a key is released. currently:
* * meta key state tracking
*
* args:
* keycode - the keycode for the key held down
*/
void read_keyup(uint8_t keycode) {
// meta key states
if(HINIBBLE(keycode) == 0x01 && keycode != SHIFT_LOCK)
meta_key_state &= 0x0F ^ LONIBBLE(keycode);
#ifdef PLSWRITE
#ifdef METAPASS
// reset the typewriter's display to reflect shift not held
if(keycode == SHIFT)
plswrite = SHIFT;
#endif
switch(keycode) {
case RESET:
plswrite = RESET;
break;
}
#endif
#ifndef DEBUG_WRITES
Serial.print(keycode);
#endif
return;
}
/*
* read_getsignals --
* delivers the intentional signal state. this is ensured by detecting if
* the possibility of a current loop exists between 4 or more keycodes.
*
* args:
* scannum: the number of the scanline that the typewriter is polling
*
* returns:
* if multiple scanlines are down and multiple signal lines on the current
* scan are down, a cached value is used as the signal, otherwise the
* actual signal is used.
*/
uint8_t read_getsignals(uint8_t scannum) {
uint8_t scans = read_scans();
uint8_t signs = read_signs();
int8_t numscans;
uint8_t i;
// if only one scanline is down, we don't have to do anything fancy.
if((~scans & (~scans - 1) & 0xFF) == 0)
return signs;
/*
* if scanline 7 is one of the lines down, don't worry—only shift and
* shift lock live on it.
*
* NB: this is done in advance because the counting loop clobbers scans
*/
if(~scans & BIT(7))
numscans--;
// count the number of scanlines that are lowered
for(i = ~scans, numscans = 0; i > 0; i >>= 1)
if(i & 0x01)
numscans++;
/*
* phantom keys only appear when multiple signals are down as well as
* multiple scans.
*/
if(numscans > 1 && (~signs & (~signs - 1) & 0xFF)) {
signs = signal_tracker[scannum];
}
return signs;
/*
* BUT WAIT!
*
* there is even more we can do here.
*
* RESET >
* x--------------x
* | |
* x--------------x
* < NC
*
* in the case above, where RESET, >, and < are held (with the CODE meta
* key also held) it is clear that all three are intentional keypresses
* because the phantom press is an NC (Not Connected) keycode.
*
* searching all paths to ensure that every rectangle of sunk lines
* contains one NC key solves the multiple-scanline problem, but is only
* necessary when the meta keys ALT and CODE are used—in regular typing
* the only NC keys are on the same scanline as SHIFT and SHIFT_LOCK, so
* the more efficient approach of not counting scanline 7 when counting
* how many scanlines are down is effective in the vast majority of cases.
*/
}
/*
* do_read --
* performs a scan of the active (down) scanlines and correspondingly active
* signallines and reports (over Serial) the changes.
*
* args:
* scans - the current state of the scanlines, to prevent an unnecessary poll
*/
void do_read(uint8_t scans) {
uint8_t scannum, signs;
// avoid getting called out of range
if(scans == 0xFF) return;
// get the active scanline
for(scannum = 0; (BIT(scannum) & ~scans & 0xFF) == 0; scannum++);
signs = read_getsignals(scannum);
for(uint8_t j = 1, signum = 0; j > 0; j <<= 1, signum++) {
uint8_t keycode;
// common case: no key down.
if((j & signs) && (j & signal_tracker[scannum]))
continue;
// do the keycode lookup here since all execution paths need it
keycode = pgm_read_word_near((uint8_t *)keycodes + ((meta_key_state << 6) | (scannum * 8 + signum)));
// next most common: key held down
if(!(j & signs) && !(j & signal_tracker[scannum]))
read_keyheld(keycode);
// key pressed and key raised are equally uncommon
else if(!(j & signs) && (j & signal_tracker[scannum]))
read_keydown(keycode);
else
read_keyup(keycode);
}
// remember the new state
signal_tracker[scannum] = signs;
return;
}
/*
* write_getcodes --
* looks up the writestate character's meta key requirements, signal, and
* scan. if the character is not supported by the typewriter, ? is used.
*
* this is the only function that writes to the writestate's meta, signal,
* and scan members.
*/
void write_getcodes() {
uint16_t *bank;
uint16_t entry;
uint8_t index;
/*
* not convinced this switch is necessary. we should just drop all
* instructions to press meta keys.
*/
switch(writestate.character) {
case SHIFT:
writestate.meta = 0;
writestate.signal = 0;
writestate.scan = 7;
return;
case SHIFT_LOCK:
writestate.meta = 0;
writestate.signal = 1;
writestate.scan = 7;
return;
case CODE:
case ALT:
writestate.character = 0;
writestate.writing = 0;
writestate.meta = 0;
return;
}
// get the lookup table and index for the char in question
if(writestate.character < 0x80) {
bank = (uint16_t *)ascii;
index = writestate.character;
}
else if(writestate.character <= HALF && writestate.character >= CENT) {
bank = (uint16_t *)extended;
index = writestate.character - CENT;
}
else if(writestate.character <= OPERATE && writestate.character >= EXPR) {
bank = (uint16_t *)control;
index = writestate.character - EXPR;
}
// if not found, use ? instead!
if (!bank || pgm_read_word_near(bank + index) == NC16) {
writestate.character = '?';
write_getcodes();
return;
}
entry = pgm_read_word_near(bank + index);
// for more detail on the lookup table's organization, see keycodes.h
writestate.meta = LOHINIBBLE(entry);
writestate.signal = HINIBBLE(entry);
writestate.scan = LONIBBLE(entry);
return;
}
/*
* write_consume --
* consume input from the reading code or Serial, if available.
* set up state for writing—meta keys to hold, scan/signal lines,
* and write status.
*
* write_consume DOES NOT BLOCK
*/
void write_consume(void) {
#ifdef PLSWRITE
if(plswrite) {
writestate.character = plswrite;
plswrite = 0;
}
else
#endif
if(Serial.available()) {
writestate.character = Serial.read();
}
if(writestate.character) {
// populate the writestate
writestate.writing = 1;
write_getcodes();
// sanity check. if this condition is true there is a serious problem.
if(writestate.meta > 0x07) {
writestate.character = 0;
writestate.writing = 0;
Serial.print(ERR);
}
}
return;
}
/*
* write_metagen --
* based on the desired meta key state and the current scanline state,
* return the signal state needed to hold the meta keys down.
*
* args:
* meta - the meta key state, either for an character we are printing, or
* the meta_key_state from the read logic.
* scans - the current state of the scanlines
*/
uint8_t write_metagen(uint8_t meta, uint8_t scans) {
uint8_t signals = 0xFF;
// only the lower three bits are valid
if(meta & 0xF8) return signals;
// SHIFT
if(meta & BIT(0) && scans == MASK(7))
signals &= MASK(0);
// CODE
if(meta & BIT(1) && scans == MASK(6))
signals &= MASK(7);
// ALT
if(meta & BIT(2) && scans == MASK(6))
signals &= MASK(4);
return signals;
}
/*
* do_write --
* based upon the state of the scanlines, pulls down the appropriate
* signallines in order to type text read from Serial. this is called every
* time the scanlines change and settle into a new state.
*
* args:
* scans - the current state of the scanlines
*/
void do_write(uint8_t scans) {
uint8_t signals = 0xFF;
#ifdef KEYBOARD_PASSTHROUGH
if(!writestate.writing)
init_read();
#endif
/*
* the best time to consume input is when all the scanlines are high.
* it happens often enough—every 16ms—and nothing much is happening on
* our end: it's 2ms of totally free time in which to do our extra work.
*/
if(scans == 0xFF) {
if(!writestate.writing)
write_consume();
else
writestate.writing++;
return;
}
if(writestate.character) {
// hold down the appropriate meta key(s)
if(writestate.meta)
signals &= write_metagen(writestate.meta, scans);
// after the meta key(s) (if applicable) have been held long enough, type the character
if(writestate.writing > KEYPRESS_DURATION * !!writestate.meta)
if(scans == MASK(writestate.scan))
signals &= MASK(writestate.signal);
// stop holding the keys, but continue blocking writes
if(writestate.writing == KEYPRESS_DURATION * (1 + !!writestate.meta)) {
writestate.character = 0;
}
}
#ifdef KEYBOARD_PASSTHROUGH
if(writestate.writing)
#endif
write_signs(signals);
/*
* wait long enough that the next keystroke, if the same, won't be
* interpreted by the typewriter as a held key
*/
if(writestate.writing == KEYPRESS_DURATION * (1 + !!writestate.meta) + KEYPRESS_GAP)
writestate.writing = 0;
return;
}
/*
* setup --
* standard Arduino function. initialize pins, ports, Serial.
*/
void setup(void) {
// pin 2 is used for timing instrumentation
pinMode(2, OUTPUT);
Serial.begin(BAUD);
/*
* the LED pin is not connected to the typewriter's signal or scan lines
* because it is used as output during boot, which can cause phantom keystrokes
* on the typewriter. that's ok, since we're using the LED pin to emit state.
*/
digitalWrite(LED, HIGH);
init_scans();
init_read();
write_signs(0xFF);
digitalWrite(LED, LOW);
}
/*
* loop --
* standard Arduino function.
*/
void loop(void) {
uint8_t scans = read_scans();
uint8_t changes = debounce(scans, &loopstate.debouncer);
if(changes) {
loopstate.scanedge = millis();
/*
* ensure that only one (or zero!) scanline is down
*/
if(~loopstate.debouncer.state & (~loopstate.debouncer.state - 1) & 0xFF)
return;
digitalWrite(LED, HIGH);
init_read();
write_signs(0xFF);
do_read(scans);
init_write();
do_write(scans);
digitalWrite(LED, LOW);
}
/*
* in normal operation, the scanlines change every 2ms, with a range reliably
* between 1950 and 2050 µs. if we've waited much longer than that for a
* change, it is safe to assume that the typewriter is resetting and has
* pulled down the scanline for the limit switch so it can respond as
* quickly as possible when the carriage hits it.
*
* when this is happening we cannot be in write mode. driving the signal
* lines will drown out the switch when it does trigger, causing the
* typewriter to grind the gearing on the carriage motor.
*/
if(millis() - loopstate.scanedge >= SCANLINE_INTERVAL) {
digitalWrite(LED, HIGH);
init_read();
write_signs(0xFF);
while(loopstate.debouncer.state == read_scans());
/*
* sometimes there is a little bit of noise that could cause us to
* mistakenly start polling in read/write mode. this 1ms delay should
* be plenty to avoid the blips of noise that occur pretty rarely.
*/
delay(1);
}
}