Commits

Trammell Hudson committed 5e70bc3 Draft

split into separate files, improved underline/inverse video

  • Participants
  • Parent commits 3bc7b07

Comments (0)

Files changed (10)

 
 
 # Target file name (without extension).
-TARGET = lcd
+TARGET = model100
 
 
 # List C source files here. (C dependencies are automatically generated.)
-SRC =	$(TARGET).c \
+SRC =	\
+	main.c \
+	lcd.c \
+	vt100.c \
+	font.c \
+	keyboard.c \
 	bits.c \
 	usb_serial.c \
 
+/** \file
+ * Bitmap font drawing.
+ *
+ * 5x7 font is stored in program memory in 8-bit columns with the
+ * LSB at the top of the column, MSB at the bottom.
+ */
 #include <avr/io.h>
 #include <avr/pgmspace.h>
+#include "font.h"
+#include "lcd.h"
  
-const char font[][6] PROGMEM =
+static const char font[][6] PROGMEM =
 {
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
-	{0x00,0x00,0x00,0x00,0x00,0x00},
+	[' '] = {0x00,0x00,0x00,0x00,0x00,0x00},	// SPACE
  
-	{0x00,0x00,0x00,0x00,0x00,0x00},	// SPACE
+	['!'] = {0x00,0x00,0x00,0x4F,0x00,0x00},	// !
+	['"'] = {0x00,0x00,0x07,0x00,0x07,0x00},	// "
+	['#'] = {0x00,0x14,0x7F,0x14,0x7F,0x14},	// #
+	['$'] = {0x00,0x24,0x2A,0x7F,0x2A,0x12},	// $
+	['%'] = {0x00,0x23,0x13,0x08,0x64,0x62},	// %
+	['&'] = {0x00,0x36,0x49,0x55,0x22,0x50},	// &
+	['\''] = {0x00,0x00,0x05,0x03,0x00,0x00},	// '
+	['('] = {0x00,0x00,0x1C,0x22,0x41,0x00},	// (
+	[')'] = {0x00,0x00,0x41,0x22,0x1C,0x00},	// )
+	['*'] = {0x00,0x14,0x08,0x3E,0x08,0x14},	// *
+	['+'] = {0x00,0x08,0x08,0x3E,0x08,0x08},	// +
+	[','] = {0x00,0x00,0x50,0x30,0x00,0x00},	// ,
+	['-'] = {0x00,0x08,0x08,0x08,0x08,0x08},	// -
+	['.'] = {0x00,0x00,0x60,0x60,0x00,0x00},	// .
+	['/'] = {0x00,0x20,0x10,0x08,0x04,0x02},	// /
  
-	{0x00,0x00,0x00,0x4F,0x00,0x00},	// !
-	{0x00,0x00,0x07,0x00,0x07,0x00},	// "
-	{0x00,0x14,0x7F,0x14,0x7F,0x14},	// #
-	{0x00,0x24,0x2A,0x7F,0x2A,0x12},	// $
-	{0x00,0x23,0x13,0x08,0x64,0x62},	// %
-	{0x00,0x36,0x49,0x55,0x22,0x50},	// &
-	{0x00,0x00,0x05,0x03,0x00,0x00},	// '
-	{0x00,0x00,0x1C,0x22,0x41,0x00},	// (
-	{0x00,0x00,0x41,0x22,0x1C,0x00},	// )
-	{0x00,0x14,0x08,0x3E,0x08,0x14},	// *
-	{0x00,0x08,0x08,0x3E,0x08,0x08},	// +
-	{0x00,0x00,0x50,0x30,0x00,0x00},	// ,
-	{0x00,0x08,0x08,0x08,0x08,0x08},	// -
-	{0x00,0x00,0x60,0x60,0x00,0x00},	// .
-	{0x00,0x20,0x10,0x08,0x04,0x02},	// /
+	['0'] = {0x00,0x3E,0x51,0x49,0x45,0x3E},	// 0
+	['1'] = {0x00,0x00,0x42,0x7F,0x40,0x00},	// 1
+	['2'] = {0x00,0x42,0x61,0x51,0x49,0x46},	// 2
+	['3'] = {0x00,0x21,0x41,0x45,0x4B,0x31},	// 3
+	['4'] = {0x00,0x18,0x14,0x12,0x7F,0x10},	// 4
+	['5'] = {0x00,0x27,0x45,0x45,0x45,0x39},	// 5
+	['6'] = {0x00,0x3C,0x4A,0x49,0x49,0x30},	// 6
+	['7'] = {0x00,0x01,0x71,0x09,0x05,0x03},	// 7
+	['8'] = {0x00,0x36,0x49,0x49,0x49,0x36},	// 8
+	['9'] = {0x00,0x06,0x49,0x49,0x29,0x1E},	// 9
  
-	{0x00,0x3E,0x51,0x49,0x45,0x3E},	// 0
-	{0x00,0x00,0x42,0x7F,0x40,0x00},	// 1
-	{0x00,0x42,0x61,0x51,0x49,0x46},	// 2
-	{0x00,0x21,0x41,0x45,0x4B,0x31},	// 3
-	{0x00,0x18,0x14,0x12,0x7F,0x10},	// 4
-	{0x00,0x27,0x45,0x45,0x45,0x39},	// 5
-	{0x00,0x3C,0x4A,0x49,0x49,0x30},	// 6
-	{0x00,0x01,0x71,0x09,0x05,0x03},	// 7
-	{0x00,0x36,0x49,0x49,0x49,0x36},	// 8
-	{0x00,0x06,0x49,0x49,0x29,0x1E},	// 9
+	[':'] = {0x00,0x36,0x36,0x00,0x00,0x00},	// :
+	[';'] = {0x00,0x56,0x36,0x00,0x00,0x00},	// ;
+	['<'] = {0x00,0x08,0x14,0x22,0x41,0x00},	// <
+	['='] = {0x00,0x14,0x14,0x14,0x14,0x14},	// =
+	['>'] = {0x00,0x00,0x41,0x22,0x14,0x08},	// >
+	['?'] = {0x00,0x02,0x01,0x51,0x09,0x06},	// ?
+	['@'] = {0x00,0x30,0x49,0x79,0x41,0x3E},	// @
  
-	{0x00,0x36,0x36,0x00,0x00,0x00},	// :
-	{0x00,0x56,0x36,0x00,0x00,0x00},	// ;
-	{0x00,0x08,0x14,0x22,0x41,0x00},	// <
-	{0x00,0x14,0x14,0x14,0x14,0x14},	// =
-	{0x00,0x00,0x41,0x22,0x14,0x08},	// >
-	{0x00,0x02,0x01,0x51,0x09,0x06},	// ?
-	{0x00,0x30,0x49,0x79,0x41,0x3E},	// @
+	['A'] = {0x00,0x7E,0x11,0x11,0x11,0x7E},	// A
+	['B'] = {0x00,0x7F,0x49,0x49,0x49,0x36},	// B
+	['C'] = {0x00,0x3E,0x41,0x41,0x41,0x22},	// C
+	['D'] = {0x00,0x7F,0x41,0x41,0x22,0x1C},	// D
+	['E'] = {0x00,0x7F,0x49,0x49,0x49,0x41},	// E
+	['F'] = {0x00,0x7F,0x09,0x09,0x09,0x01},	// F
+	['G'] = {0x00,0x3E,0x41,0x49,0x49,0x7A},	// G
+	['H'] = {0x00,0x7F,0x08,0x08,0x08,0x7F},	// H
+	['I'] = {0x00,0x00,0x41,0x7F,0x41,0x00},	// I
+	['J'] = {0x00,0x20,0x40,0x41,0x3F,0x01},	// J
+	['K'] = {0x00,0x7F,0x08,0x14,0x22,0x41},	// K
+	['L'] = {0x00,0x7F,0x40,0x40,0x40,0x40},	// L
+	['M'] = {0x00,0x7F,0x02,0x0C,0x02,0x7F},	// M
+	['N'] = {0x00,0x7F,0x04,0x08,0x10,0x7F},	// N
+	['O'] = {0x00,0x3E,0x41,0x41,0x41,0x3E},	// O
+	['P'] = {0x00,0x7F,0x09,0x09,0x09,0x06},	// P
+	['Q'] = {0x00,0x3E,0x41,0x51,0x21,0x5E},	// Q
+	['R'] = {0x00,0x7F,0x09,0x19,0x29,0x46},	// R
+	['S'] = {0x00,0x46,0x49,0x49,0x49,0x31},	// S
+	['T'] = {0x00,0x01,0x01,0x7F,0x01,0x01},	// T
+	['U'] = {0x00,0x3F,0x40,0x40,0x40,0x3F},	// U
+	['V'] = {0x00,0x1F,0x20,0x40,0x20,0x1F},	// V
+	['W'] = {0x00,0x3F,0x40,0x30,0x40,0x3F},	// W
+	['X'] = {0x00,0x63,0x14,0x08,0x14,0x63},	// X
+	['Y'] = {0x00,0x07,0x08,0x70,0x08,0x07},	// Y
+	['Z'] = {0x00,0x61,0x51,0x49,0x45,0x43},	// Z
  
-	{0x00,0x7E,0x11,0x11,0x11,0x7E},	// A
-	{0x00,0x7F,0x49,0x49,0x49,0x36},	// B
-	{0x00,0x3E,0x41,0x41,0x41,0x22},	// C
-	{0x00,0x7F,0x41,0x41,0x22,0x1C},	// D
-	{0x00,0x7F,0x49,0x49,0x49,0x41},	// E
-	{0x00,0x7F,0x09,0x09,0x09,0x01},	// F
-	{0x00,0x3E,0x41,0x49,0x49,0x7A},	// G
-	{0x00,0x7F,0x08,0x08,0x08,0x7F},	// H
-	{0x00,0x00,0x41,0x7F,0x41,0x00},	// I
-	{0x00,0x20,0x40,0x41,0x3F,0x01},	// J
-	{0x00,0x7F,0x08,0x14,0x22,0x41},	// K
-	{0x00,0x7F,0x40,0x40,0x40,0x40},	// L
-	{0x00,0x7F,0x02,0x0C,0x02,0x7F},	// M
-	{0x00,0x7F,0x04,0x08,0x10,0x7F},	// N
-	{0x00,0x3E,0x41,0x41,0x41,0x3E},	// O
-	{0x00,0x7F,0x09,0x09,0x09,0x06},	// P
-	{0x00,0x3E,0x41,0x51,0x21,0x5E},	// Q
-	{0x00,0x7F,0x09,0x19,0x29,0x46},	// R
-	{0x00,0x46,0x49,0x49,0x49,0x31},	// S
-	{0x00,0x01,0x01,0x7F,0x01,0x01},	// T
-	{0x00,0x3F,0x40,0x40,0x40,0x3F},	// U
-	{0x00,0x1F,0x20,0x40,0x20,0x1F},	// V
-	{0x00,0x3F,0x40,0x30,0x40,0x3F},	// W
-	{0x00,0x63,0x14,0x08,0x14,0x63},	// X
-	{0x00,0x07,0x08,0x70,0x08,0x07},	// Y
-	{0x00,0x61,0x51,0x49,0x45,0x43},	// Z
+	['['] = {0x00,0x00,0x7F,0x41,0x41,0x00},	// [
+	['\\'] = {0x00,0x02,0x04,0x08,0x10,0x20},	// backslash
+	[']'] = {0x00,0x00,0x41,0x41,0x7F,0x00},	// ]
+	['^'] = {0x00,0x04,0x02,0x01,0x02,0x04},	// ^
+	['_'] = {0x00,0x40,0x40,0x40,0x40,0x40},	// _
+	['`'] = {0x00,0x00,0x01,0x02,0x04,0x00},	// `
  
-	{0x00,0x00,0x7F,0x41,0x41,0x00},	// [
-	{0x00,0x02,0x04,0x08,0x10,0x20},	// backslash
-	{0x00,0x00,0x41,0x41,0x7F,0x00},	// ]
-	{0x00,0x04,0x02,0x01,0x02,0x04},	// ^
-	{0x00,0x40,0x40,0x40,0x40,0x40},	// _
-	{0x00,0x00,0x01,0x02,0x04,0x00},	// `
+	['a'] = {0x00,0x20,0x54,0x54,0x54,0x78},	// a
+	['b'] = {0x00,0x7F,0x50,0x48,0x48,0x30},	// b
+	['c'] = {0x00,0x38,0x44,0x44,0x44,0x20},	// c
+	['d'] = {0x00,0x38,0x44,0x44,0x48,0x7F},	// d
+	['e'] = {0x00,0x38,0x54,0x54,0x54,0x18},	// e
+	['f'] = {0x00,0x08,0x7E,0x09,0x01,0x02},	// f
+	['g'] = {0x00,0x0C,0x52,0x52,0x52,0x3E},	// g
+	['h'] = {0x00,0x7F,0x08,0x04,0x04,0x78},	// h
+	['i'] = {0x00,0x00,0x44,0x7D,0x40,0x00},	// i
+	['j'] = {0x00,0x20,0x40,0x44,0x3D,0x00},	// j
+	['k'] = {0x00,0x7F,0x10,0x28,0x44,0x00},	// k
+	['l'] = {0x00,0x00,0x41,0x7F,0x40,0x00},	// l
+	['m'] = {0x00,0x78,0x04,0x18,0x04,0x78},	// m
+	['n'] = {0x00,0x7C,0x08,0x04,0x04,0x78},	// n
+	['o'] = {0x00,0x38,0x44,0x44,0x44,0x38},	// o
+	['p'] = {0x00,0x7C,0x14,0x14,0x14,0x08},	// p
+	['q'] = {0x00,0x08,0x14,0x14,0x18,0x7C},	// q
+	['r'] = {0x00,0x7C,0x08,0x04,0x04,0x08},	// r
+	['s'] = {0x00,0x48,0x54,0x54,0x54,0x20},	// s
+	['t'] = {0x00,0x04,0x3F,0x44,0x40,0x20},	// t
+	['u'] = {0x00,0x3C,0x40,0x40,0x20,0x7C},	// u
+	['v'] = {0x00,0x1C,0x20,0x40,0x20,0x1C},	// v
+	['w'] = {0x00,0x3C,0x40,0x30,0x40,0x3C},	// w
+	['x'] = {0x00,0x44,0x28,0x10,0x28,0x44},	// x
+	['y'] = {0x00,0x0C,0x50,0x50,0x50,0x3C},	// y
+	['z'] = {0x00,0x44,0x64,0x54,0x4C,0x44},	// z
  
-	{0x00,0x20,0x54,0x54,0x54,0x78},	// a
-	{0x00,0x7F,0x50,0x48,0x48,0x30},	// b
-	{0x00,0x38,0x44,0x44,0x44,0x20},	// c
-	{0x00,0x38,0x44,0x44,0x48,0x7F},	// d
-	{0x00,0x38,0x54,0x54,0x54,0x18},	// e
-	{0x00,0x08,0x7E,0x09,0x01,0x02},	// f
-	{0x00,0x0C,0x52,0x52,0x52,0x3E},	// g
-	{0x00,0x7F,0x08,0x04,0x04,0x78},	// h
-	{0x00,0x00,0x44,0x7D,0x40,0x00},	// i
-	{0x00,0x20,0x40,0x44,0x3D,0x00},	// j
-	{0x00,0x7F,0x10,0x28,0x44,0x00},	// k
-	{0x00,0x00,0x41,0x7F,0x40,0x00},	// l
-	{0x00,0x78,0x04,0x18,0x04,0x78},	// m
-	{0x00,0x7C,0x08,0x04,0x04,0x78},	// n
-	{0x00,0x38,0x44,0x44,0x44,0x38},	// o
-	{0x00,0x7C,0x14,0x14,0x14,0x08},	// p
-	{0x00,0x08,0x14,0x14,0x18,0x7C},	// q
-	{0x00,0x7C,0x08,0x04,0x04,0x08},	// r
-	{0x00,0x48,0x54,0x54,0x54,0x20},	// s
-	{0x00,0x04,0x3F,0x44,0x40,0x20},	// t
-	{0x00,0x3C,0x40,0x40,0x20,0x7C},	// u
-	{0x00,0x1C,0x20,0x40,0x20,0x1C},	// v
-	{0x00,0x3C,0x40,0x30,0x40,0x3C},	// w
-	{0x00,0x44,0x28,0x10,0x28,0x44},	// x
-	{0x00,0x0C,0x50,0x50,0x50,0x3C},	// y
-	{0x00,0x44,0x64,0x54,0x4C,0x44},	// z
- 
-	{0x00,0x00,0x08,0x36,0x41,0x00},	// {
-	{0x00,0x00,0x00,0x7F,0x00,0x00},	// |
-	{0x00,0x00,0x41,0x36,0x08,0x00},	// }
-	{0x00,0x0C,0x02,0x0C,0x10,0x0C},	// ~
- 
-	{0x00,0x00,0x00,0x00,0x00,0x00}
+	['{'] = {0x00,0x00,0x08,0x36,0x41,0x00},	// {
+	['|'] = {0x00,0x00,0x00,0x7F,0x00,0x00},	// |
+	['}'] = {0x00,0x00,0x41,0x36,0x08,0x00},	// }
+	['~'] = {0x00,0x0C,0x02,0x0C,0x10,0x0C},	// ~
 };
 
 
 
 
 void
-lcd_char(
+font_draw(
 	uint8_t col,
 	uint8_t row,
-	uint8_t c
+	uint8_t c,
+	uint8_t mod
 )
 {
 	uint8_t x = col * 6;
 	uint8_t y = row * 8;
-	uint8_t inverse = 0;
-
-	if (c >= 0x80)
-	{
-		c -= 0x80;
-		inverse = 1;
-	}
 
 	const char * f = font[c];
 
 		// functions, but has lots of special cases for crossing
 		// display boundaries.
 		uint8_t bits = pgm_read_byte(&f[i]);
-		if (inverse)
+		if (mod & FONT_UNDERLINE)
+			bits |= 0x80;
+		if (mod & FONT_INVERSE)
 			bits = ~bits;
+
 		lcd_display(x, y, bits);
 	}
 }
+/** \file
+ * Bitmap font driver.
+ */
+#ifndef _model100_font_h_
+#define _model100_font_h_
+
+#include <avr/io.h>
+#include <stdint.h>
+
+#define FONT_NORMAL	0x00
+#define FONT_INVERSE	0x01
+#define FONT_UNDERLINE	0x02
+
+
+extern void
+font_draw(
+	uint8_t col,
+	uint8_t row,
+	uint8_t c,
+	uint8_t mod
+);
+
+
+#endif
+/**
+ * \file Model 100 keyboard matrix reader
+ *
+ * Keyboard needs:
+ *	9 columns, shared with the 10 LCD chipselect lines
+ *	8 rows, must not be shared.
+ */
+
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+#include <avr/eeprom.h>
+#include <stdint.h>
+#include <string.h>
+#include <util/delay.h>
+#include "usb_serial.h"
+#include "bits.h"
+#include "keyboard.h"
+
+// Can not be shared with the LCD
+#define KEY_ROWS_PIN	PINA
+#define KEY_ROWS_DDR	DDRA
+#define KEY_ROWS_PORT	PORTA
+
+// Shared with LCD chip select lines
+#define KEY_COLS_PIN	PINF
+#define KEY_COLS_DDR	DDRF
+#define KEY_COLS_PORT	PORTF
+#define KEY_COLS_MOD	0xE6 // shared with LCD_CS8
+
+// The bits on the modifier column
+#define KEY_MOD_SHIFT	0x01
+#define KEY_MOD_CONTROL	0x02
+#define KEY_MOD_GRAPH	0x04
+#define KEY_MOD_CODE 	0x08
+#define KEY_MOD_NUMLOCK	0x10
+#define KEY_MOD_CAPS	0x20
+#define KEY_MOD_NC	0x40
+#define KEY_MOD_BREAK	0x80
+
+
+/** Layout of the rows and columns in normal mode */
+static const uint8_t key_codes[8][8] PROGMEM =
+{
+	[0] = "\x81\x82\x83\x84\x85\x86\x87\x88", // function keys
+	[1] = "zxcvbnml",
+	[2] = "asdfghjk",
+	[3] = "qwertyui",
+	[4] = "op[;',./",
+	[5] = "12345678",
+	[6] = "90-=\x92\x93\x90\x91", // need to handle arrows
+	[7] = " \x8\t\eLC0\n", // need to handle weird keys
+};
+
+
+/** Layout of the rows and columns when shifted */
+static const uint8_t shift_codes[8][8] PROGMEM =
+{
+	[0] = "\x81\x82\x83\x84\x85\x86\x87\x88", // function keys
+	[1] = "ZXCVBNML",
+	[2] = "ASDFGHJK",
+	[3] = "QWERTYUI",
+	[4] = "OP]:\"<>?",
+	[5] = "!@#$%^&*",
+	[6] = "()_+\x92\x93\x90\x91", // need to handle arrows
+	[7] = " \x8\t\eLC0\n", // need to handle weird keys
+};
+
+
+/** Initialize the keyboard for a scan.
+ *
+ * This is called before each read of the keyboard, unlike the
+ * LCD that is initialized at boot time.
+ */
+static void
+keyboard_init(void)
+{
+	// KEY_Cx configuration is handled in lcd_init() sincej
+	// they are shared with the chip select lines of the LCD 
+	KEY_ROWS_DDR = 0x00; // all input
+	KEY_ROWS_PORT = 0xFF; // all pull ups enabled
+
+	KEY_COLS_DDR = 0xFF; // all output
+	KEY_COLS_PORT = 0xFF; // all high
+
+	// Pull the function key line high, too
+	ddr(KEY_COLS_MOD, 1);
+	out(KEY_COLS_MOD, 1);
+}
+
+
+/** Return the keyboard to the normal state (LCD writing).
+ *
+ */
+static void
+keyboard_reset(void)
+{
+	KEY_ROWS_DDR = 0x00; // all inputs
+	KEY_ROWS_PORT = 0x00; // all tri-state
+
+	KEY_COLS_DDR = 0xFF; // leave as all output
+	KEY_COLS_PORT = 0x00; // all low
+
+	// Reset the function key modifier to pull down, too
+	ddr(KEY_COLS_MOD, 1);
+	out(KEY_COLS_MOD, 0);
+}
+
+
+
+static uint8_t
+keyboard_scancode_convert(
+	uint8_t col,
+	uint8_t rows,
+	uint8_t mods
+)
+{
+	for (uint8_t row = 0, mask = 1 ; row < 8 ; row++, mask <<= 1)
+	{
+		if ((rows & mask) == 0)
+			continue;
+
+		char c = pgm_read_byte(&(
+			(mods & KEY_MOD_SHIFT) && !(mods & KEY_MOD_CONTROL)
+			? shift_codes
+			: key_codes
+		)[col][row]);
+
+		// If control is held, only allow a-z
+		// Send nothing, otherwise
+		if (mods & KEY_MOD_CONTROL)
+		{
+			if ('a' <= c && c <= 'z')
+				return 0x1 + c - 'a';
+			return 0;
+		}
+
+		// If we are caps locked, switch lower and upper
+		if (mods & KEY_MOD_CAPS)
+		{
+			if ('a' <= c && c <= 'z')
+				c -= 32;
+			else
+			if ('A' <= c && c <= 'Z')
+				c += 32;
+		}
+
+		return c;
+	}
+
+	// Scan code converts to nothing...
+	return 0;
+}
+
+
+/** Check to see if there are any keys held down.
+ *
+ * \todo Scans only one column per call?
+ *
+ * \todo Multiple keys held?
+ *
+ * \return 0 if no keys are held down
+ */
+uint8_t
+keyboard_scan(void)
+{
+	keyboard_init();
+
+
+	// Scan the modifier column first, which is on the separate pin
+	out(KEY_COLS_MOD, 0);
+	_delay_us(50);
+	const uint8_t mods = ~KEY_ROWS_PIN;
+	out(KEY_COLS_MOD, 1);
+
+	uint8_t mask = 1;
+	for (uint8_t col = 0 ; col < 8 ; col++, mask <<= 1)
+	{
+		KEY_COLS_PORT = ~mask; // pull one down
+		_delay_us(50); // wait for things to stabilize
+		uint8_t rows = ~KEY_ROWS_PIN;
+		KEY_COLS_PORT = 0xFF; // bring them all back up
+
+		if (!rows)
+			continue;
+
+		keyboard_reset();
+
+		return keyboard_scancode_convert(col, rows, mods);
+	}
+
+	keyboard_reset();
+	return 0;
+}
+/** \file
+ * Model 100 keyboard matrix.
+ */
+#ifndef _model100_keyboard_h_
+#define _model100_keyboard_h_
+
+#include <avr/io.h>
+#include <stdint.h>
+
+
+/** Scan the row/col matrix for any keys held down.
+ *
+ * This will return the lowest key; multiple keys are not currently
+ * supported.
+ *
+ * \return ASCII code for key, or 0 for no key.
+ */
+extern uint8_t
+keyboard_scan(void);
+
+
+#endif
  * i2c: PD0, PD1
  * RS232: PD2, PD3
  * SPI: PB3, PB2, PB1, PB0
- *
- * Keyboard needs:
- *	9 column, 8 row: 17
- * LCD needs:
- *	10 select, (could be shared with keyboard?)
- *	8 data
- * 	CS1 
- *	EN, DI, RW: 3
- *	V2: 
  */
 
 #include <avr/io.h>
 #include <stdint.h>
 #include <string.h>
 #include <util/delay.h>
-#include "usb_serial.h"
 #include "bits.h"
-
-
-#define LED		0xD6
+#include "lcd.h"
 
 #define LCD_V2		0xB7 // 4, Analog voltage to generate negative voltage
 #define LCD_VO		0xB6 // Analog voltage to control contrast
 #define LCD_CS28	0xE6
 #define LCD_CS29	0xE7
 
-// Can not be shared with the LCD
-#define KEY_ROWS_PIN	PINA
-#define KEY_ROWS_DDR	DDRA
-#define KEY_ROWS_PORT	PORTA
-
-// Shared with LCD chip select lines
-#define KEY_COLS_PIN	PINF
-#define KEY_COLS_DDR	DDRF
-#define KEY_COLS_PORT	PORTF
-#define KEY_COLS_MOD	0xE6 // shared with LCD_CS8
-
-// The bits on the modifier column
-#define KEY_MOD_SHIFT	0x01
-#define KEY_MOD_CONTROL	0x02
-#define KEY_MOD_GRAPH	0x04
-#define KEY_MOD_CODE 	0x08
-#define KEY_MOD_NUMLOCK	0x10
-#define KEY_MOD_CAPS	0x20
-#define KEY_MOD_NC	0x40
-#define KEY_MOD_BREAK	0x80
-
-void send_str(const char *s);
-uint8_t recv_str(char *buf, uint8_t size);
-void parse_and_execute_command(const char *buf, uint8_t num);
-
-static inline uint8_t
-hexdigit(
-	uint8_t x
-)
-{
-	x &= 0xF;
-	if (x < 0xA)
-		return x + '0' - 0x0;
-	else
-		return x + 'A' - 0xA;
-}
-
-
-
-// Send a string to the USB serial port.  The string must be in
-// flash memory, using PSTR
-//
-void send_str(const char *s)
-{
-	char c;
-	while (1) {
-		c = pgm_read_byte(s++);
-		if (!c) break;
-		usb_serial_putchar(c);
-	}
-}
-
 
 static uint8_t
 lcd_command(
 }
 
 
-static void
+void
 lcd_init(void)
 {
 	LCD_DATA_PORT = 0x00;
  * x is ranged 0 to 240, for each pixel
  * y is ranged 0 to 64, rounded to 8
  */
-static void
+void
 lcd_display(
 	uint8_t x,
 	uint8_t y,
 
 	out(LCD_CS1, 0);
 }
-
-#include "font.c"
-
-
-static void
-keyboard_init(void)
-{
-	// KEY_Cx configuration is handled in lcd_init() sincej
-	// they are shared with the chip select lines of the LCD 
-	KEY_ROWS_DDR = 0x00; // all input
-	KEY_ROWS_PORT = 0xFF; // all pull ups enabled
-
-	KEY_COLS_DDR = 0xFF; // all output
-	KEY_COLS_PORT = 0xFF; // all high
-
-	// Pull the function key line high, too
-	ddr(KEY_COLS_MOD, 1);
-	out(KEY_COLS_MOD, 1);
-}
-
-
-static const uint8_t key_codes[8][8] PROGMEM =
-{
-	[0] = "\x81\x82\x83\x84\x85\x86\x87\x88", // function keys
-	[1] = "zxcvbnml",
-	[2] = "asdfghjk",
-	[3] = "qwertyui",
-	[4] = "op[;',./",
-	[5] = "12345678",
-	[6] = "90-=\x92\x93\x90\x91", // need to handle arrows
-	[7] = " \x8\t\eLC0\n", // need to handle weird keys
-};
-
-
-static const uint8_t shift_codes[8][8] PROGMEM =
-{
-	[0] = "\x81\x82\x83\x84\x85\x86\x87\x88", // function keys
-	[1] = "ZXCVBNML",
-	[2] = "ASDFGHJK",
-	[3] = "QWERTYUI",
-	[4] = "OP]:\"<>?",
-	[5] = "!@#$%^&*",
-	[6] = "()_+\x92\x93\x90\x91", // need to handle arrows
-	[7] = " \x8\t\eLC0\n", // need to handle weird keys
-};
-
-
-static void
-keyboard_reset(void)
-{
-	KEY_ROWS_DDR = 0x00; // all inputs
-	KEY_ROWS_PORT = 0x00; // all tri-state
-
-	KEY_COLS_DDR = 0xFF; // leave as all output
-	KEY_COLS_PORT = 0x00; // all low
-
-	// Reset the function key modifier to pull down, too
-	ddr(KEY_COLS_MOD, 1);
-	out(KEY_COLS_MOD, 0);
-}
-
-
-/** Check to see if there are any keys held down.
- *
- * \todo Scans only one column per call?
- *
- * \todo Multiple keys held?
- *
- * \return 0 if no keys are held down
- */
-static uint8_t
-keyboard_scan(void)
-{
-	keyboard_init();
-
-
-	// Scan the modifier column first, which is on the separate pin
-	out(KEY_COLS_MOD, 0);
-	_delay_us(50);
-	const uint8_t mods = ~KEY_ROWS_PIN;
-	out(KEY_COLS_MOD, 1);
-
-	uint8_t mask = 1;
-	for (uint8_t col = 0 ; col < 8 ; col++, mask <<= 1)
-	{
-		KEY_COLS_PORT = ~mask; // pull one down
-		_delay_us(50);
-		uint8_t rows = ~KEY_ROWS_PIN;
-		KEY_COLS_PORT = 0xFF;; // bring them all back up
-
-		if (!rows)
-			continue;
-
-		keyboard_reset();
-		mask = 1;
-
-		for (uint8_t row = 0 ; row < 8 ; row++, mask <<= 1)
-		{
-			if ((rows & mask) == 0)
-				continue;
-
-			char c = pgm_read_byte(&(
-				(mods & KEY_MOD_SHIFT) && !(mods & KEY_MOD_CONTROL)
-				? shift_codes
-				: key_codes
-			)[col][row]);
-
-			// If control is held, only allow a-z
-			// Send nothing, otherwise
-			if (mods & KEY_MOD_CONTROL)
-			{
-				if ('a' <= c && c <= 'z')
-					return 0x1 + c - 'a';
-				return 0;
-			}
-
-			// If we are caps locked, switch lower and upper
-			if (mods & KEY_MOD_CAPS)
-			{
-				if ('a' <= c && c <= 'z')
-					c -= 32;
-				else
-				if ('A' <= c && c <= 'Z')
-					c += 32;
-			}
-
-
-			return c;
-		}
-	}
-
-	keyboard_reset();
-	return 0;
-}
-
-
-// These might change if we use a smaller font.
-#define MAX_COLS 40
-#define MAX_ROWS 8
-
-static uint8_t cur_col;
-static uint8_t cur_row;
-static uint8_t vt100_state;
-static uint8_t vt100_inverse;
-
-static void
-lcd_clear(void)
-{
-	for (uint8_t j = 0 ; j < 8 ; j++)
-		for (uint8_t i = 0 ; i < 40 ; i++)
-			lcd_char(i, j, ' ');
-}
-
-
-static void
-buzzer(void)
-{
-	// todo: use PWM on the buzzer
-}
-
-
-/** VT100ish emulation.
- *
- * A simplistic approach to VT100 emulation.  Only a few commands
- * are supported:
- *   Erase
- *   Home
- *   Cursor up
- *   Cursor down
- */
-
-static void
-vt100_process(
-	char c
-)
-{
-	static uint8_t arg1;
-	static uint8_t arg2;
-	static uint8_t vt100_query;
-
-	if (vt100_state == 1)
-	{
-		arg1 = arg2 = 0;
-		vt100_query = 0;
-
-		if (c == 'c')
-		{
-			lcd_clear();
-			cur_row = cur_col = 0;
-		} else
-		if (c == '[')
-		{
-			vt100_state = 2;
-			return;
-		} else
-		if (c == '(' || c == ')')
-		{
-			// We mostly ignore these
-			vt100_state = 10;
-			return;
-		}
-	} else
-	if (vt100_state == 2)
-	{
-		if (c == ';')
-		{
-			vt100_state = 3;
-			return;
-		} else
-		if (c == '?')
-		{
-			vt100_query = 1;
-			return;
-		} else
-		if ('0' <= c && c <= '9')
-		{
-			arg1 = (arg1 * 10) + c - '0';
-			return;
-		} else
-		if (c == 'H')
-		{
-			// <ESC>[H == home cursor
-			cur_row = cur_col = 0;
-		} else
-		if (c == 'm')
-		{
-			// <ESC>[{arg}m == set attributes
-			// 0 == clear
-			if (arg1 == 0)
-				vt100_inverse = 0;
-		} else
-		if (c == 'A')
-		{
-			// <ESC>[{arg}A == move N lines up
-			if (cur_row < arg1)
-				cur_row = 0;
-			else
-				cur_row -= arg1;
-		} else
-		if (c == 'B')
-		{
-			// <ESC>[{arg}B == move N lines down
-			if (cur_row + arg1 >= MAX_ROWS)
-				cur_row = MAX_ROWS-1;
-			else
-				cur_row += arg1;
-		} else
-		if (c == 'D')
-		{
-			// <ESC>[{arg}D == move N lines to the left
-			if (cur_col < arg1)
-				cur_col = 0;
-			else
-				cur_col -= arg1;
-		} else
-		if (c == 'C')
-		{
-			// <ESC>[{arg}C == move N lines to the right
-			if (cur_col + arg1 >= MAX_COLS)
-				cur_col = MAX_COLS-1;
-			else
-				cur_col += arg1;
-		} else
-		if (c == 'J')
-		{
-			// <ESC>[{arg}J == clear the screen
-			// 0 == to the bottom
-			// 1 == to the top
-			// 2 == entire screen, and move home
-			// we just do a full clear
-			lcd_clear();
-			cur_row = cur_col = 0;
-		} else
-		if (c == 'K')
-		{
-			// <ESC>[K == erase to end of line
-			for (uint8_t x = cur_col ; x < MAX_COLS ; x++)
-				lcd_char(x, cur_row, ' ');
-		}
-	} else
-	if (vt100_state == 3)
-	{
-		if ('0' <= c && c <= '9')
-		{
-			arg2 = (arg2 * 10) + c - '0';
-			return;
-		} else
-		if (c == 'H')
-		{
-			// <ESC>[{row};{col}H == goto position row,col
-			// vt100 is 1 indexed, we are 0 indexed.
-			cur_row = arg1 > 0 ? arg1 - 1 : 0;
-			cur_col = arg2 > 0 ? arg2 - 1 : 0;
-			if (cur_row >= MAX_ROWS)
-				cur_row = MAX_ROWS - 1;
-			if (cur_col >= MAX_COLS)
-				cur_col = MAX_COLS - 1;
-		} else
-		if (c == 'm')
-		{
-			// <ESC>[{arg};{arg}m == set attributes
-			// 0 == clear
-			// no others are supported
-			if (arg2 == 7)
-				vt100_inverse = 0x80;
-			else // if (arg1 == 0)
-				vt100_inverse = 0;
-		}
-	} else
-	if (vt100_state == 10)
-	{
-		// We really don't care.
-	}
-
-	// If we have fallen through to here, we are done and should
-	// exit vt100 mode.
-	vt100_state = 0;
-	return;
-}
-
-
-static void
-lcd_putc(
-	char c
-)
-{
-	if (c == '\e')
-	{
-		vt100_state = 1;
-		return;
-	} else
-	if (vt100_state)
-	{
-		vt100_process(c);
-		return;
-	}
-
-	if (c == '\r')
-	{
-		cur_col = 0;
-	} else
-	if (c == '\n')
-	{
-		goto new_row;
-	} else
-	if (c == '\x7')
-	{
-		// Bell!
-		buzzer();
-	} else
-	if (c == '\xF')
-	{
-		// ^O or ASCII SHIFT-IN (SI) switches sets?
-/*
-		if (vt100_inverse)
-			vt100_inverse = 0;
-		else
-			vt100_inverse = 0x80;
-*/
-	} else
- 	if (c == '\xE')
-	{
-		// We do not support alternate char sets for now.
-		// ignore.
-		// ^P or ASCII SHIFT-OUT (SO) switches sets, too
-	} else
-	if (c == '\x8')
-	{
-		// erase the old char and backup
-		lcd_char(cur_col, cur_row, ' ');
-		if (cur_col > 0)
-		{
-			cur_col--;
-		} else {
-			cur_col = 40;
-			cur_row = (cur_row - 1 + MAX_ROWS) % MAX_ROWS;
-		}
-	} else {
-		lcd_char(cur_col, cur_row, c | vt100_inverse);
-		if (++cur_col == 40)
-			goto new_row;
-	}
-
-	return;
-
-new_row:
-	cur_row = (cur_row + 1) % 8;
-	cur_col = 0;
-}
-
-
-static void
-redraw(void)
-{
-	static uint8_t val;
-
-#if 1
-	for (uint8_t j = 0 ; j < 8 ; j++)
-	{
-		for (uint8_t i = 0 ; i < 40 ; i++)
-		{
-			val = (val + 1) & 0x3F;
-			lcd_char(i, j, val + '0');
-		}
-	}
-#else
-	for (uint8_t y = 0 ; y < 64 ; y += 8)
-	{
-		for (uint8_t x = 0 ; x < 240 ; x++)
-		{
-			lcd_display(x, y, val++);
-		}
-	}
-#endif
-
-	val++;
-}
-
-
-static void
-key_special(
-	const uint8_t key
-)
-{
-	if (key == 0x81)
-	{
-		// f1 == redraw everything
-		lcd_clear();
-		cur_row = cur_col = vt100_state = 0;
-		return;
-	}
-
-	if (0x90 <= key && key <= 0x93)
-	{
-		uint8_t buf[2];
-		buf[0] = '\e';
-		buf[1] = 'A' + key - 0x90;
-		usb_serial_write(buf, 2);
-		return;
-	}
-}
-
-
-int
-main(void)
-{
-	// set for 16 MHz clock
-#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))
-	CPU_PRESCALE(0);
-
-	// Disable the ADC
-	ADMUX = 0;
-
-	// initialize the USB, and then wait for the host
-	// to set configuration.  If the Teensy is powered
-	// without a PC connected to the USB port, this 
-	// will wait forever.
-	usb_init();
-
-	// LED is an output; will be pulled down once connected
-	ddr(LED, 1);
-	out(LED, 1);
-
-	lcd_init();
-	//keyboard_init();
-
-        // Timer 0 is used for a 64 Hz control loop timer.
-        // Clk/256 == 62.5 KHz, count up to 125 == 500 Hz
-        // Clk/1024 == 15.625 KHz, count up to 125 == 125 Hz
-        // CTC mode resets the counter when it hits the top
-        TCCR0A = 0
-                | 1 << WGM01 // select CTC
-                | 0 << WGM00
-                ;
-
-        TCCR0B = 0
-                | 0 << WGM02
-                | 1 << CS02 // select Clk/256
-                | 0 << CS01
-                | 1 << CS00
-                ;
-
-        OCR0A = 125;
-        sbi(TIFR0, OCF0A); // reset the overflow bit
-
-	while (!usb_configured())
-		;
-
-	_delay_ms(1000);
-
-	// wait for the user to run their terminal emulator program
-	// which sets DTR to indicate it is ready to receive.
-	while (!(usb_serial_get_control() & USB_SERIAL_DTR))
-		;
-
-	// discard anything that was received prior.  Sometimes the
-	// operating system or other software will send a modem
-	// "AT command", which can still be buffered.
-	usb_serial_flush_input();
-
-	send_str(PSTR("lcd model100\r\n"));
-	redraw();
-
-	uint8_t last_key = 0;
-
-	while (1)
-	{
-		int c = usb_serial_getchar();
-		if (c != -1)
-		{
-			lcd_putc(c);
-/*
-			usb_serial_putchar(c);
-			if (c == '+')
-			{
-				OCR1C += 8;
-				off = 0;
-				buf[off++] = hexdigit(OCR1C >> 8);
-				buf[off++] = hexdigit(OCR1C >> 4);
-				buf[off++] = hexdigit(OCR1C >> 0);
-				buf[off++] = '\r';
-				buf[off++] = '\n';
-				usb_serial_write(buf, off);
-			}
-			if (c == '-')
-			{
-				OCR1B += 8;
-				off = 0;
-				buf[off++] = hexdigit(OCR1B >> 8);
-				buf[off++] = hexdigit(OCR1B >> 4);
-				buf[off++] = hexdigit(OCR1B >> 0);
-				buf[off++] = '\r';
-				buf[off++] = '\n';
-				usb_serial_write(buf, off);
-			}
-*/
-		}
-
-		uint8_t key = keyboard_scan();
-		if (key == 0)
-		{
-			last_key = 0;
-		} else
-		if (key != last_key)
-		{
-			last_key = key;
-			if (key >= 0x80)
-			{
-				// Special char!
-				key_special(key);
-			} else {
-				// Normal, send it.
-				usb_serial_putchar(key);
-			}
-		}
-
-		if (bit_is_clear(TIFR0, OCF0A))
-			continue;
-
-		sbi(TIFR0, OCF0A); // reset the bit
-	}
-}
+/** \file
+ * HD44102 LCD driver.
+ */
+#ifndef _model100_lcd_h_
+#define _model100_lcd_h_
+
+#include <avr/io.h>
+#include <stdint.h>
+
+
+/** Bring up the LCD interface.
+ *
+ * Call once after boot to bring up the interface.
+ */
+extern void
+lcd_init(void);
+
+
+/** Display val at position x,y.
+ *
+ * x is ranged 0 to 240, for each pixel
+ * y is ranged 0 to 64, rounded to 8
+ * MSB of val is at the top, LSB is at the bottom.
+ */
+extern void
+lcd_display(
+	uint8_t x,
+	uint8_t y,
+	uint8_t val
+);
+
+
+#endif
+/**
+ * \file Model 100 motherboard.
+ */
+
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+#include <avr/eeprom.h>
+#include <stdint.h>
+#include <string.h>
+#include <util/delay.h>
+#include "usb_serial.h"
+#include "bits.h"
+#include "vt100.h"
+#include "lcd.h"
+#include "font.h"
+#include "keyboard.h"
+
+
+#define LED		0xD6
+
+void send_str(const char *s);
+uint8_t recv_str(char *buf, uint8_t size);
+void parse_and_execute_command(const char *buf, uint8_t num);
+
+static inline uint8_t
+hexdigit(
+	uint8_t x
+)
+{
+	x &= 0xF;
+	if (x < 0xA)
+		return x + '0' - 0x0;
+	else
+		return x + 'A' - 0xA;
+}
+
+
+
+// Send a string to the USB serial port.  The string must be in
+// flash memory, using PSTR
+//
+void send_str(const char *s)
+{
+	char c;
+	while (1) {
+		c = pgm_read_byte(s++);
+		if (!c) break;
+		usb_serial_putchar(c);
+	}
+}
+
+
+static void
+fill_screen(void)
+{
+	static uint8_t val;
+
+#if 1
+	for (uint8_t j = 0 ; j < 8 ; j++)
+	{
+		for (uint8_t i = 0 ; i < 40 ; i++)
+		{
+			val = (val + 1) & 0x3F;
+			font_draw(i, j, val + '0', FONT_NORMAL);
+		}
+	}
+#else
+	for (uint8_t y = 0 ; y < 64 ; y += 8)
+	{
+		for (uint8_t x = 0 ; x < 240 ; x++)
+		{
+			lcd_display(x, y, val++);
+		}
+	}
+#endif
+
+	val++;
+}
+
+
+static void
+key_special(
+	const uint8_t key
+)
+{
+	if (key == 0x81)
+	{
+		// f1 == redraw everything
+		vt100_clear();
+		vt100_goto(0, 0);
+		return;
+	}
+
+	if (0x90 <= key && key <= 0x93)
+	{
+		uint8_t buf[2];
+		buf[0] = '\e';
+		buf[1] = 'A' + key - 0x90;
+		usb_serial_write(buf, 2);
+		return;
+	}
+}
+
+
+int
+main(void)
+{
+	// set for 16 MHz clock
+#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))
+	CPU_PRESCALE(0);
+
+	// Disable the ADC
+	ADMUX = 0;
+
+	// initialize the USB, and then wait for the host
+	// to set configuration.  If the Teensy is powered
+	// without a PC connected to the USB port, this 
+	// will wait forever.
+	usb_init();
+
+	// LED is an output; will be pulled down once connected
+	ddr(LED, 1);
+	out(LED, 1);
+
+	lcd_init();
+
+        // Timer 0 is used for a 64 Hz control loop timer.
+        // Clk/256 == 62.5 KHz, count up to 125 == 500 Hz
+        // Clk/1024 == 15.625 KHz, count up to 125 == 125 Hz
+        // CTC mode resets the counter when it hits the top
+        TCCR0A = 0
+                | 1 << WGM01 // select CTC
+                | 0 << WGM00
+                ;
+
+        TCCR0B = 0
+                | 0 << WGM02
+                | 1 << CS02 // select Clk/256
+                | 0 << CS01
+                | 1 << CS00
+                ;
+
+        OCR0A = 125;
+        sbi(TIFR0, OCF0A); // reset the overflow bit
+
+	while (!usb_configured())
+		;
+
+	_delay_ms(1000);
+
+	// wait for the user to run their terminal emulator program
+	// which sets DTR to indicate it is ready to receive.
+	while (!(usb_serial_get_control() & USB_SERIAL_DTR))
+		;
+
+	// discard anything that was received prior.  Sometimes the
+	// operating system or other software will send a modem
+	// "AT command", which can still be buffered.
+	usb_serial_flush_input();
+
+	send_str(PSTR("lcd model100\r\n"));
+	fill_screen();
+
+	uint8_t last_key = 0;
+
+	while (1)
+	{
+		int c = usb_serial_getchar();
+		if (c != -1)
+		{
+			vt100_putc(c);
+		}
+
+		uint8_t key = keyboard_scan();
+		if (key == 0)
+		{
+			last_key = 0;
+		} else
+		if (key != last_key)
+		{
+			last_key = key;
+			if (key >= 0x80)
+			{
+				// Special char!
+				key_special(key);
+			} else {
+				// Normal, send it.
+				usb_serial_putchar(key);
+			}
+		}
+
+		if (bit_is_clear(TIFR0, OCF0A))
+			continue;
+
+		sbi(TIFR0, OCF0A); // reset the bit
+	}
+}
+/**
+ * \file VT100-like emulation.
+ *
+ * This implements a very simplistic VT100 emulator.
+ * Not very many of the functions are implemented; just enough to
+ * run vi and lynx, etc.
+ */
+
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+#include <avr/eeprom.h>
+#include <stdint.h>
+#include <string.h>
+#include <util/delay.h>
+#include "vt100.h"
+#include "lcd.h"
+#include "font.h"
+
+
+// These might change if we use a smaller font.
+#define MAX_COLS 40
+#define MAX_ROWS 8
+
+static uint8_t cur_col;
+static uint8_t cur_row;
+static uint8_t vt100_state;
+static uint8_t font_mod;
+
+void
+vt100_clear(void)
+{
+	for (uint8_t j = 0 ; j < 8 ; j++)
+		for (uint8_t i = 0 ; i < 40 ; i++)
+			font_draw(i, j, ' ', FONT_NORMAL);
+}
+
+
+void
+vt100_goto(
+	uint8_t new_row,
+	uint8_t new_col
+)
+{
+	cur_row = new_row > 0 ? new_row - 1 : 0;
+	cur_col = new_col > 0 ? new_col - 1 : 0;
+
+	if (cur_row >= MAX_ROWS)
+		cur_row = MAX_ROWS - 1;
+	if (cur_col >= MAX_COLS)
+		cur_col = MAX_COLS - 1;
+}
+
+
+static void
+buzzer(void)
+{
+	// todo: use PWM on the buzzer
+}
+
+
+static void
+vt100_process(
+	char c
+)
+{
+	static uint8_t arg1;
+	static uint8_t arg2;
+	static uint8_t vt100_query;
+
+	if (vt100_state == 1)
+	{
+		arg1 = arg2 = 0;
+		vt100_query = 0;
+
+		if (c == 'c')
+		{
+			vt100_clear();
+			cur_row = cur_col = 0;
+		} else
+		if (c == '[')
+		{
+			vt100_state = 2;
+			return;
+		} else
+		if (c == '(' || c == ')')
+		{
+			// We mostly ignore these
+			vt100_state = 10;
+			return;
+		}
+	} else
+	if (vt100_state == 2)
+	{
+		if (c == ';')
+		{
+			vt100_state = 3;
+			return;
+		} else
+		if (c == '?')
+		{
+			vt100_query = 1;
+			return;
+		} else
+		if ('0' <= c && c <= '9')
+		{
+			arg1 = (arg1 * 10) + c - '0';
+			return;
+		} else
+		if (c == 'H')
+		{
+			// <ESC>[H == home cursor
+			cur_row = cur_col = 0;
+		} else
+		if (c == 'm')
+		{
+			// <ESC>[{arg}m == set attributes
+			// 0 == clear
+			if (arg1 == 0)
+				font_mod = FONT_NORMAL;
+		} else
+		if (c == 'A')
+		{
+			// <ESC>[{arg}A == move N lines up
+			if (cur_row < arg1)
+				cur_row = 0;
+			else
+				cur_row -= arg1;
+		} else
+		if (c == 'B')
+		{
+			// <ESC>[{arg}B == move N lines down
+			if (cur_row + arg1 >= MAX_ROWS)
+				cur_row = MAX_ROWS-1;
+			else
+				cur_row += arg1;
+		} else
+		if (c == 'D')
+		{
+			// <ESC>[{arg}D == move N lines to the left
+			if (cur_col < arg1)
+				cur_col = 0;
+			else
+				cur_col -= arg1;
+		} else
+		if (c == 'C')
+		{
+			// <ESC>[{arg}C == move N lines to the right
+			if (cur_col + arg1 >= MAX_COLS)
+				cur_col = MAX_COLS-1;
+			else
+				cur_col += arg1;
+		} else
+		if (c == 'J')
+		{
+			// <ESC>[{arg}J == clear the screen
+			// 0 == to the bottom
+			// 1 == to the top
+			// 2 == entire screen, and move home
+			// we just do a full clear
+			vt100_clear();
+			cur_row = cur_col = 0;
+		} else
+		if (c == 'K')
+		{
+			// <ESC>[K == erase to end of line
+			for (uint8_t x = cur_col ; x < MAX_COLS ; x++)
+				font_draw(x, cur_row, ' ', FONT_NORMAL);
+		}
+	} else
+	if (vt100_state == 3)
+	{
+		if ('0' <= c && c <= '9')
+		{
+			arg2 = (arg2 * 10) + c - '0';
+			return;
+		} else
+		if (c == 'H')
+		{
+			// <ESC>[{row};{col}H == goto position row,col
+			// vt100 is 1 indexed, we are 0 indexed.
+			vt100_goto(arg1, arg2);
+		} else
+		if (c == 'm')
+		{
+			// <ESC>[{arg};{arg}m == set attributes
+			// 0 == clear
+			// no others are supported
+			if (arg1 == 0)
+				font_mod = FONT_NORMAL;
+
+			if (arg2 == 1)
+				font_mod |= FONT_UNDERLINE;
+			else
+			if (arg2 == 7)
+				font_mod |= FONT_INVERSE;
+		}
+	} else
+	if (vt100_state == 10)
+	{
+		// We really don't care.
+	}
+
+	// If we have fallen through to here, we are done and should
+	// exit vt100 mode.
+	vt100_state = 0;
+	return;
+}
+
+
+void
+vt100_putc(
+	char c
+)
+{
+	if (c == '\e')
+	{
+		vt100_state = 1;
+		return;
+	} else
+	if (vt100_state)
+	{
+		vt100_process(c);
+		return;
+	}
+
+	if (c == '\r')
+	{
+		cur_col = 0;
+	} else
+	if (c == '\n')
+	{
+		goto new_row;
+	} else
+	if (c == '\x7')
+	{
+		// Bell!
+		buzzer();
+	} else
+	if (c == '\xF')
+	{
+		// ^O or ASCII SHIFT-IN (SI) switches sets?
+	} else
+ 	if (c == '\xE')
+	{
+		// ^P or ASCII SHIFT-OUT (SO) switches sets, too
+		// We do not support alternate char sets for now.
+		// ignore.
+	} else
+	if (c == '\x8')
+	{
+		// erase the old char and backup
+		font_draw(cur_col, cur_row, ' ', FONT_NORMAL);
+		if (cur_col > 0)
+		{
+			cur_col--;
+		} else {
+			cur_col = 40;
+			cur_row = (cur_row - 1 + MAX_ROWS) % MAX_ROWS;
+		}
+	} else {
+		font_draw(
+			cur_col,
+			cur_row,
+			c,
+			font_mod
+		);
+
+		if (++cur_col == 40)
+			goto new_row;
+	}
+
+	return;
+
+new_row:
+	cur_row = (cur_row + 1) % 8;
+	cur_col = 0;
+}
+/** \file
+ * VT100 terminal emulation.
+ */
+#ifndef _vt100_
+#define _vt100_
+
+#include <avr/io.h>
+#include <stdint.h>
+
+
+extern void
+vt100_clear(void);
+
+
+extern void
+vt100_goto(
+	uint8_t new_row,
+	uint8_t new_col
+);
+
+
+extern void
+vt100_putc(
+	char c
+);
+
+
+#endif