Added LED support to VT100 keyboard
This commit is contained in:
parent
b25a9268a1
commit
999828da4e
4 changed files with 640 additions and 409 deletions
|
|
@ -14,10 +14,15 @@ VT100 Terminal
|
|||
This is where we'll be testing another 8080-based machine: the VT100 Terminal. Unlike other VT100 emulations,
|
||||
this will simulate a VT100 by running the terminal's original firmware inside the [PC8080](/modules/pc8080/) CPU emulator.
|
||||
|
||||
{% include machine.html id="vt100" %}
|
||||
|
||||
VT100 Memory Usage
|
||||
------------------
|
||||
|
||||
As described in the [Technical Manual (July 1982)](http://bitsavers.informatik.uni-stuttgart.de/pdf/dec/terminal/vt100/EK-VT100-TM-003_VT100_Technical_Manual_Jul82.pdf),
|
||||
p. 4-15, 8Kb (0x2000) of ROM is located at 0x0000, and 3Kb (0x0C00) of RAM immediately follows it at 0x2000. The ROM at
|
||||
0x0000 contains all the VT100's 8080 code. The VT100 also contains a 2Kb character generator ROM, but it is not
|
||||
mapped into the 8080's address space.
|
||||
0x0000 contains all the VT100's 8080 code. The VT100 also contains a 2Kb character generator ROM, but that ROM is not
|
||||
addressable by the CPU; it is used directly by the Video Processor.
|
||||
|
||||
[vt100romhax](http://vt100romhax.tumblr.com/post/90697428973/the-vt100-memory-map-and-8080-disassembly)
|
||||
(aka [phooky](https://github.com/phooky)) further explains VT100 memory usage:
|
||||
|
|
@ -29,18 +34,47 @@ mapped into the 8080's address space.
|
|||
0x204f 0x22d0 641B Scratch Pad/Setup Area(?)
|
||||
0x22d0 0x2c00 2352B Screen RAM
|
||||
|
||||
VT100 I/O Ports
|
||||
---------------
|
||||
|
||||
From p. 4-17 of the Technical Manual:
|
||||
|
||||
READ OR WRITE
|
||||
00H PUSART data bus
|
||||
01H PUSART command port
|
||||
|
||||
WRITE ONLY (Decoded with I/O WR L)
|
||||
02H Baud rate generator
|
||||
42H Brightness D/A latch
|
||||
62H NVR latch
|
||||
82H Keyboard UART data input
|
||||
A2H Video processor DC012
|
||||
C2H Video processor DC011
|
||||
E2H Graphics port
|
||||
|
||||
READ ONLY (Decoded with I/O RD L)
|
||||
22H Modem buffer
|
||||
42H Flags buffer
|
||||
82H Keyboard UART data output
|
||||
|
||||
The PC8080 ChipSet component deals with the ER1400's Non-volatile RAM (NVR) ports, while the Keyboard component
|
||||
deals with the Keyboard UART ports.
|
||||
|
||||
VT100 Video Processor
|
||||
---------------------
|
||||
|
||||
Normally, the PC8080 Video component allocates its own video buffer, based on the specified buffer address
|
||||
(*bufferAddr*) and other dimensions (eg, *bufferCols* and *bufferRows*), but the VT100 is a little unusual:
|
||||
it has a custom Video Processor that uses DMA to request character data from any region of RAM, one line at a time.
|
||||
It always defaults to address 0x2000 for the first line of character data, but each line terminates with 3 bytes
|
||||
containing line attributes and the address of the next line, so the location of subsequent lines will vary,
|
||||
depending on the following line attributes:
|
||||
containing the attributes and address of the next line, so the location of subsequent lines will vary, depending
|
||||
on the type of line:
|
||||
|
||||
- Single-width characters (80 or 132 columns)
|
||||
- Double-width characters (40 or 66 columns)
|
||||
- Single-wide characters (80 or 132 columns)
|
||||
- Double-wide characters (40 or 66 columns)
|
||||
|
||||
In addition to single-width vs. double-width, line attributes can also specify double-height, along with whether the
|
||||
top or bottom halves of the double-high characters should be displayed. Also, double-high always implies double-wide
|
||||
In addition to single-wide vs. double-wide, line attributes can also specify double-high, along with whether the
|
||||
top or bottom halves of the double-high characters should be displayed, because double-high always implies double-wide
|
||||
(ie, there is no support for double-high, single-wide characters).
|
||||
|
||||
Conssequently, a VT100 [machine XML file](machine.xml) must set the Video component's *bufferRAM* property
|
||||
|
|
@ -50,6 +84,9 @@ enabling support for the VT100's line data format; eg:
|
|||
<ram id="ram" addr="0x2000" size="0x0C00"/>
|
||||
<video id="video" screenWidth="1600" screenHeight="960" bufferAddr="0x2000" bufferRAM="true" bufferFormat="vt100" bufferCols="80" bufferRows="24" ...>
|
||||
|
||||
VT100 Screen and Character Dimensions
|
||||
-------------------------------------
|
||||
|
||||
Ordinarily, the VT100 screen displays 800 dots per horizontal scan, and a total of 240 horizontal scans, and by default,
|
||||
it uses a 10x10 character cell, for a total of 80 columns and 24 rows of characters. However, in 132-column mode, it
|
||||
uses a 9x10 character cell instead, implying a total of 1188 dots displayed per horizontal scan. This means we will have
|
||||
|
|
@ -64,9 +101,7 @@ measure 2.0mm x 3.35mm (in 132-column mode, they measure 1.3mm x 3.35mm), which
|
|||
roughly 160mm x 80mm, implying a screen aspect ratio of 2.0. However, after visually comparing the Technical Manual's SET-UP
|
||||
screenshots to our test screens, 1.67 appears to be closer to reality than 2.0. I'll revisit this issue at a later date.
|
||||
|
||||
{% include machine.html id="vt100" %}
|
||||
|
||||
VT100 Resources
|
||||
---------------
|
||||
Additional VT100 Resources
|
||||
--------------------------
|
||||
|
||||
[VT100 Publications](/pubs/dec/vt100/)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,20 @@ function Keyboard(parmsKbd)
|
|||
|
||||
Component.subclass(Keyboard);
|
||||
|
||||
Keyboard.VT100 = {
|
||||
STATUS: {
|
||||
PORT: 0x82, // write-only
|
||||
LED4: 0x01,
|
||||
LED3: 0x02,
|
||||
LED2: 0x04,
|
||||
LED1: 0x08,
|
||||
LOCKED: 0x10,
|
||||
ONLINE: 0x20,
|
||||
START: 0x40,
|
||||
CLICK: 0x80
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Alphanumeric and other common (printable) ASCII codes.
|
||||
*
|
||||
|
|
@ -234,6 +248,8 @@ Keyboard.SOFTCODES = {
|
|||
'fire': Keyboard.KEYCODE.SPACE
|
||||
};
|
||||
|
||||
Keyboard.MINPRESSTIME = 100; // 100ms
|
||||
|
||||
/**
|
||||
* Alternate keyCode mappings (to support the popular WASD directional mappings)
|
||||
*
|
||||
|
|
@ -246,44 +262,13 @@ Keyboard.ALTCODES[Keyboard.ASCII.D] = Keyboard.KEYCODE.RIGHT;
|
|||
Keyboard.ALTCODES[Keyboard.ASCII.L] = Keyboard.KEYCODE.SPACE;
|
||||
|
||||
Keyboard.LEDSTATES = {
|
||||
'l1': 0x01,
|
||||
'l2': 0x02,
|
||||
'l3': 0x04,
|
||||
'l4': 0x08,
|
||||
'locked': 0x10,
|
||||
'local': 0x20,
|
||||
'online': 0x40
|
||||
};
|
||||
|
||||
/**
|
||||
* getSoftCode(keyCode)
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @return {string|null}
|
||||
*/
|
||||
Keyboard.prototype.getSoftCode = function(keyCode)
|
||||
{
|
||||
keyCode = Keyboard.ALTCODES[keyCode] || keyCode;
|
||||
for (var sSoftCode in Keyboard.SOFTCODES) {
|
||||
if (Keyboard.SOFTCODES[sSoftCode] === keyCode) {
|
||||
return sSoftCode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {Keyboard}
|
||||
*/
|
||||
Keyboard.prototype.reset = function()
|
||||
{
|
||||
/*
|
||||
* As SOFTCODE keyDown events are encountered, a corresponding property is set to true in
|
||||
* keysPressed, and as SOFTCODE keyUp events are encountered, the property is set to false.
|
||||
*/
|
||||
this.keysPressed = {};
|
||||
'l4': Keyboard.VT100.STATUS.LED4,
|
||||
'l3': Keyboard.VT100.STATUS.LED3,
|
||||
'l2': Keyboard.VT100.STATUS.LED2,
|
||||
'l1': Keyboard.VT100.STATUS.LED1,
|
||||
'locked': Keyboard.VT100.STATUS.LOCKED,
|
||||
'online': Keyboard.VT100.STATUS.ONLINE,
|
||||
'local': ~Keyboard.VT100.STATUS.ONLINE
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -372,6 +357,138 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {CPUState} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.dbg = dbg; // NOTE: The "dbg" property must be set for the message functions to work
|
||||
this.chipset = cmp.getMachineComponent("ChipSet");
|
||||
this.model = this.chipset.model;
|
||||
switch(this.model) {
|
||||
case ChipSet.VT100.MODEL:
|
||||
this.config = Keyboard.VT100;
|
||||
break;
|
||||
}
|
||||
if (this.config) {
|
||||
bus.addPortInputTable(this, this.config.portsInput);
|
||||
bus.addPortOutputTable(this, this.config.portsOutput);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Keyboard.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
if (!data) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
Keyboard.prototype.powerDown = function(fSave, fShutdown)
|
||||
{
|
||||
return fSave? this.save() : true;
|
||||
};
|
||||
|
||||
Keyboard.VT100.init = [
|
||||
[
|
||||
0
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {Keyboard}
|
||||
*/
|
||||
Keyboard.prototype.reset = function()
|
||||
{
|
||||
/*
|
||||
* As keyDown events are encountered, a corresponding "softcode" property in keysPressed
|
||||
* is set to the timestamp of the keyDown event. When the corresponding keyUp event occurs,
|
||||
* we look at the elapsed time: if it is less than MINPRESSTIME, then we move the key
|
||||
* to keysToRelease and set a timeout handler to release the key later; otherwise, we process
|
||||
* the keyUp event immediately.
|
||||
*/
|
||||
this.keysPressed = {};
|
||||
this.keysToRelease = {};
|
||||
|
||||
if (this.config && !this.restore(this.config.init)) {
|
||||
this.notice("reset error");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the Keyboard component.
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @return {Object}
|
||||
*/
|
||||
Keyboard.prototype.save = function()
|
||||
{
|
||||
var state = new State(this);
|
||||
switch(this.model) {
|
||||
case ChipSet.SI1978.MODEL:
|
||||
break;
|
||||
case ChipSet.VT100.MODEL:
|
||||
state.set(0, [this.bLEDs]);
|
||||
break;
|
||||
}
|
||||
return state.data();
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the Keyboard component.
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Keyboard.prototype.restore = function(data)
|
||||
{
|
||||
var a;
|
||||
if (data && (a = data[0]) && a.length) {
|
||||
switch(this.model) {
|
||||
case ChipSet.SI1978.MODEL:
|
||||
return true;
|
||||
case ChipSet.VT100.MODEL:
|
||||
this.bLEDs = a[0];
|
||||
this.updateLEDs();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* setLED(control, f)
|
||||
*
|
||||
|
|
@ -388,36 +505,41 @@ Keyboard.prototype.setLED = function(control, f)
|
|||
};
|
||||
|
||||
/**
|
||||
* updateLEDs(bitState)
|
||||
* updateLEDs()
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {number} [bitState] is the bit in bitsStateSim that may have changed, if known; undefined if not
|
||||
*/
|
||||
Keyboard.prototype.updateLEDs = function(bitState)
|
||||
Keyboard.prototype.updateLEDs = function()
|
||||
{
|
||||
var control;
|
||||
for (var sBinding in Keyboard.LEDSTATES) {
|
||||
var id = "led-" + sBinding;
|
||||
var control = this.bindings[id];
|
||||
if (control) {
|
||||
var bitLED = Keyboard.LEDSTATES[sBinding];
|
||||
if ((!bitState || bitState == bitLED) && (control = this.bindings[id])) {
|
||||
this.setLED(control, !!bitLED); // !!(this.bKeyboardState & bitLED)
|
||||
var fOn = !!(this.bLEDs & bitLED);
|
||||
if (bitLED & (bitLED-1)) {
|
||||
fOn = !(this.bLEDs & ~bitLED);
|
||||
}
|
||||
this.setLED(control, fOn);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
* getSoftCode(keyCode)
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {CPUState} cpu
|
||||
* @param {Debugger} dbg
|
||||
* @return {string|null}
|
||||
*/
|
||||
Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
Keyboard.prototype.getSoftCode = function(keyCode)
|
||||
{
|
||||
this.dbg = dbg; // NOTE: The "dbg" property must be set for the message functions to work
|
||||
this.chipset = cmp.getMachineComponent("ChipSet");
|
||||
keyCode = Keyboard.ALTCODES[keyCode] || keyCode;
|
||||
for (var sSoftCode in Keyboard.SOFTCODES) {
|
||||
if (Keyboard.SOFTCODES[sSoftCode] === keyCode) {
|
||||
return sSoftCode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -425,7 +547,7 @@ Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
*
|
||||
* @this {Keyboard}
|
||||
* @param {Object} event
|
||||
* @param {boolean} fDown is true for a keyDown event, false for a keyUp event
|
||||
* @param {boolean} fDown is true for a keyDown event, false for up
|
||||
* @return {boolean} true to pass the event along, false to consume it
|
||||
*/
|
||||
Keyboard.prototype.onKeyDown = function(event, fDown)
|
||||
|
|
@ -454,12 +576,27 @@ Keyboard.prototype.onKeyDown = function(event, fDown)
|
|||
*
|
||||
* @this {Keyboard}
|
||||
* @param {string} sSoftCode
|
||||
* @param {boolean} fDown is true for a down event, false for an up event
|
||||
* @param {boolean} fDown is true for a down event, false for up
|
||||
* @return {boolean} true to pass the event along, false to consume it
|
||||
*/
|
||||
Keyboard.prototype.onSoftKeyDown = function(sSoftCode, fDown)
|
||||
{
|
||||
this.keysPressed[sSoftCode] = fDown;
|
||||
if (fDown) {
|
||||
this.keysPressed[sSoftCode] = Date.now();
|
||||
delete this.keysToRelease[sSoftCode];
|
||||
} else {
|
||||
var msDown = this.keysPressed[sSoftCode];
|
||||
this.assert(msDown);
|
||||
if (msDown) {
|
||||
var msElapsed = Date.now() - msDown;
|
||||
if (msElapsed < Keyboard.MINPRESSTIME) {
|
||||
this.keysToRelease[sSoftCode] = msDown;
|
||||
this.checkSoftKeysToRelease();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
delete this.keysPressed[sSoftCode];
|
||||
}
|
||||
|
||||
if (this.chipset) {
|
||||
switch(sSoftCode) {
|
||||
|
|
@ -488,7 +625,60 @@ Keyboard.prototype.onSoftKeyDown = function(sSoftCode, fDown)
|
|||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkSoftKeysToRelease()
|
||||
*
|
||||
* @this {Keyboard}
|
||||
*/
|
||||
Keyboard.prototype.checkSoftKeysToRelease = function()
|
||||
{
|
||||
var msDelayMin = -1;
|
||||
var asSoftCodes = Object.keys(this.keysToRelease);
|
||||
for (var sSoftCode in asSoftCodes) {
|
||||
var msDown = this.keysToRelease[sSoftCode];
|
||||
var msElapsed = Date.now() - msDown;
|
||||
var msDelay = Keyboard.MINPRESSTIME - msElapsed;
|
||||
if (msDelay > 0) {
|
||||
if (msDelayMin < 0 || msDelayMin > msDelay) {
|
||||
msDelayMin = msDelay;
|
||||
}
|
||||
} else {
|
||||
delete this.keysToRelease[sSoftCode];
|
||||
this.onSoftKeyDown(sSoftCode, false);
|
||||
}
|
||||
}
|
||||
if (msDelayMin >= 0) {
|
||||
var kbd = this;
|
||||
setTimeout(function() { kbd.checkSoftKeysToRelease(); }, msDelayMin);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* outVT100UARTStatus(port, b, addrFrom)
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {number} port (0x82)
|
||||
* @param {number} b
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
|
||||
*/
|
||||
Keyboard.prototype.outVT100UARTStatus = function(port, b, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, b, addrFrom, "KBDUART.STATUS", null, true);
|
||||
this.bLEDs = b;
|
||||
this.updateLEDs();
|
||||
};
|
||||
|
||||
/*
|
||||
* Port notification tables
|
||||
*/
|
||||
Keyboard.VT100.portsInput = {
|
||||
};
|
||||
|
||||
Keyboard.VT100.portsOutput = {
|
||||
0x82: Keyboard.prototype.outVT100UARTStatus
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue