Adding clocking mechanism for serial data transmission

This commit is contained in:
Jeff Parsons 2016-08-17 14:09:02 -07:00
commit 140e1cf094
9 changed files with 526 additions and 400 deletions

View file

@ -13,4 +13,4 @@ The following 8080-based machines are currently available:
* [Space Invaders (1978)](invaders/) (with [Debugger](invaders/debugger/))
* [DEC VT100 Terminal](vt100/) (with [Debugger](vt100/debugger/))
And check out the [Dual VT100 Terminals](vt100/dual/) for a demo of two PCjs machines communicating over a *virtual* serial connection.
Check out the [Dual VT100 Terminals](vt100/dual/) for a demo of two PCjs machines communicating over a *virtual* serial connection.

View file

@ -14,7 +14,8 @@ This is a PCjs work-in-progress emulation of another 8080-based machine: the VT1
Unlike other VT100 emulators, it is not simply an emulation of VT100 protocols. It is a simulation of the original VT100
machine, running the [VT100 Firmware](/devices/pc8080/rom/vt100/) inside the [PC8080](/modules/pc8080/) CPU emulator.
A [Debugger Configuration](/devices/pc8080/machine/vt100/debugger/) is also available.
A [Debugger Configuration](debugger/) is also available, along with a [Dual VT100 Terminals](dual/) demo.
Admittedly, terminals aren't that useful in isolation, since they're designed to be connected to other (host) machines.
But once this PCjs VT100 Terminal simulation is fully operational, you can expect to see it used in conjunction with a variety

View file

@ -20,6 +20,8 @@ This is a test of two [VT100 Terminals](../) whose serial ports are *virtually*
terminal should appear on the screen of the second, and vice versa. Click or tap on the screen of the desired terminal before
you begin typing.
A [Dual Debugger Configuration](debugger/) is also available.
If either terminal becomes LOCKED, press the SET-UP key (mapped to your F9 function key) twice to unlock it.
{% include machine.html id="vt100a" %}

View file

@ -0,0 +1,29 @@
---
layout: page
title: Dual VT100 Terminals with Debuggers
permalink: /devices/pc8080/machine/vt100/dual/debugger/
machines:
- id: vt100a
type: pc8080
debugger: true
autoStart: true
messages: serial|mem|port
config: /devices/pc8080/machine/vt100/debugger/machine.xml
connection: serialPort=vt100b.serialPort
- id: vt100b
type: pc8080
debugger: true
autoStart: true
messages: serial|mem|port
config: /devices/pc8080/machine/vt100/debugger/machine.xml
connection: serialPort=vt100a.serialPort
---
Dual VT100 Terminals with Debuggers
-----------------------------------
[Dual VT100 Terminals](../) with Debuggers attached.
{% include machine.html id="vt100a" %}
{% include machine.html id="vt100b" %}

View file

@ -361,9 +361,13 @@ SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg)
this.dbg = dbg;
var serial = this;
this.timerReceiveData = this.cpu.addTimer(function() {
serial.printMessage("timerReceiveData()");
serial.receiveData()
this.timerReceiveNext = this.cpu.addTimer(function() {
serial.printMessage("timerReceiveNext()");
serial.receiveData();
});
this.timerTransmitNext = this.cpu.addTimer(function() {
serial.printMessage("timerTransmitNext()");
serial.transmitData();
});
this.chipset = /** @type {ChipSet} */ (cmp.getMachineComponent("ChipSet"));
@ -570,6 +574,11 @@ SerialPort.prototype.receiveByte = function(b)
/**
* receiveData()
*
* Helper for clocking received data at the expected RECV_RATE.
*
* Currently, this is only use for test data that we "cram" down the terminal's throat, ensuring that we don't
* cram it too rapidly.
*
* @this {SerialPort}
*/
SerialPort.prototype.receiveData = function()
@ -579,7 +588,7 @@ SerialPort.prototype.receiveData = function()
this.sDataReceived = this.sDataReceived.substr(1);
}
if (this.sDataReceived && this.cpu) {
this.cpu.setTimer(this.timerReceiveData, this.getBaudTimeout(SerialPort.UART8251.BAUDRATES.RECV_RATE));
this.cpu.setTimer(this.timerReceiveNext, this.getBaudTimeout(SerialPort.UART8251.BAUDRATES.RECV_RATE));
}
}
};
@ -602,10 +611,7 @@ SerialPort.prototype.transmitByte = function(b)
}
if (this.controlIOBuffer) {
if (b == 0x0D) {
this.iLogicalCol = 0;
}
else if (b == 0x08) {
if (b == 0x08) {
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
/*
* TODO: Back up the correct number of columns if the character erased was a tab.
@ -613,13 +619,17 @@ SerialPort.prototype.transmitByte = function(b)
if (this.iLogicalCol > 0) this.iLogicalCol--;
}
else {
var s = String.fromCharCode(b);
var nChars = (b >= 0x20? 1 : 0);
var s = str.toASCIICode(b);
var nChars = s.length;
if (b == 0x09) {
var tabSize = this.tabSize || 8;
nChars = tabSize - (this.iLogicalCol % tabSize);
if (this.tabSize) s = str.pad("", nChars);
}
else if (b == 0x0D) {
this.iLogicalCol = nChars = 0;
s = "\n";
}
if (this.charBOL && !this.iLogicalCol && nChars) s = String.fromCharCode(this.charBOL) + s;
this.controlIOBuffer.value += s;
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
@ -641,6 +651,21 @@ SerialPort.prototype.transmitByte = function(b)
return fTransmitted;
};
/**
* transmitData()
*
* Helper for clocking transmitted data at the expected XMIT_RATE.
*
* When timerTransmitNext fires, we have honored the programmed XMIT_RATE period, so we can
* set XMIT_READY (and XMIT_EMPTY), which signals the firmware that another byte can be transmitted.
*
* @this {SerialPort}
*/
SerialPort.prototype.transmitData = function()
{
this.bStatus |= (SerialPort.UART8251.STATUS.XMIT_READY | SerialPort.UART8251.STATUS.XMIT_EMPTY);
};
/**
* isTransmitterReady()
*
@ -698,8 +723,20 @@ SerialPort.prototype.outData = function(port, bOut, addrFrom)
this.printMessageIO(port, bOut, addrFrom, "DATA");
this.bDataOut = bOut;
this.bStatus &= ~(SerialPort.UART8251.STATUS.XMIT_READY | SerialPort.UART8251.STATUS.XMIT_EMPTY);
if (this.transmitByte(bOut)) {
this.bStatus |= (SerialPort.UART8251.STATUS.XMIT_READY | SerialPort.UART8251.STATUS.XMIT_EMPTY);
/*
* If we're transmitting to a virtual device that has no measurable delay, this code may clear XMIT_READY
* too quickly.
*
* if (this.transmitByte(bOut)) {
* this.bStatus |= (SerialPort.UART8251.STATUS.XMIT_READY | SerialPort.UART8251.STATUS.XMIT_EMPTY);
* }
*
* A better solution is to arm a timer based on the XMIT_RATE baud rate, and clear the above bits when that
* timer fires. Consequently, we no longer care what transmitByte() reports.
*/
this.transmitByte(bOut);
if (this.cpu) {
this.cpu.setTimer(this.timerTransmitNext, this.getBaudTimeout(SerialPort.UART8251.BAUDRATES.XMIT_RATE));
}
};

View file

@ -1115,7 +1115,7 @@ Video.prototype.updateVT100 = function(fForced)
/*
* Cell cache logic is complicated by the fact that a line may be single-width one frame and double-width
* the next. So we store the visible line length at the start of each row in the cache, which must match if
* the cache is considered valid for the current line.
* the cache can be considered valid for the current line.
*/
var fLineCacheValid = this.fCellCacheValid && (this.aCellCache[iCell] == nColsVisible);
this.aCellCache[iCell++] = nColsVisible;
@ -1153,6 +1153,9 @@ Video.prototype.updateVT100 = function(fForced)
* cache entry, to guarantee that it's redrawn on the next update.
*/
this.assert(iCellUpdated >= 0);
if (DEBUG && (this.aCellCache[iCellUpdated] & 0x7f) == 0x48) {
console.log("spurious character?");
}
this.aCellCache[iCellUpdated] = -1;
cUpdated = 0;
}

View file

@ -381,7 +381,7 @@ str.replaceArray = function(a, s)
* @param {string} s is a string
* @param {number} cch is desired length
* @param {boolean} [fPadLeft] (default is padding on the right)
* @returns {string} the original string (s) with spaces padding it to the specified length
* @return {string} the original string (s) with spaces padding it to the specified length
*/
str.pad = function(s, cch, fPadLeft)
{
@ -393,7 +393,7 @@ str.pad = function(s, cch, fPadLeft)
* trim(s)
*
* @param {string} s
* @returns {string}
* @return {string}
*/
str.trim = function(s)
{
@ -403,4 +403,56 @@ str.trim = function(s)
return s.replace(/^\s+|\s+$/g, "");
};
str.aASCIICodes = {
0x00: "NUL",
0x01: "SOH", // Start of Heading
0x02: "STX", // Start of Text
0x03: "ETX", // End of Text
0x04: "EOT", // End of Transmission
0x05: "ENQ", // Enquiry
0x06: "ACK", // Acknowledge
0x07: "BEL", // Bell
0x08: "BS", // Backspace
0x09: "TAB", // Horizontal Tab
0x0A: "LF", // Line Feed (New Line)
0x0B: "VT", // Vertical Tab
0x0C: "FF", // Form Feed (New Page)
0x0D: "CR", // Carriage Return
0x0E: "SO", // Shift Out
0x0F: "SI", // Shift In
0x10: "DLE", // Data Link Escape
0x11: "DC1", // Device Control 1
0x12: "DC2", // Device Control 2
0x13: "DC3", // Device Control 3
0x14: "DC4", // Device Control 4
0x15: "NAK", // Negative Acknowledge
0x16: "SYN", // Synchronous Idle
0x17: "ETB", // End of Transmission Block
0x18: "CAN", // Cancel
0x19: "EM", // End of Medium
0x1A: "SUB", // Substitute
0x1B: "ESC", // Escape
0x1C: "FS", // File Separator
0x1D: "GS", // Group Separator
0x1E: "RS", // Record Separator
0x1F: "US" // Unit Separator
};
/**
* toASCIICode(b)
*
* @param {number} b
* @return {string}
*/
str.toASCIICode = function(b)
{
var s = str.aASCIICodes[b];
if (s) {
s = '<' + s + '>';
} else {
s = String.fromCharCode(b);
}
return s;
};
if (NODE) module.exports = str;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long