This card features an 8-bit parallel port and two serial ports. The parallel port does not follow the Centronics standards, but the serial ports are compatible with the RS232C standard, also known as V.24 in Europe.
Here is a commented picture of the card, including instructions for modification of its CRU address (to install two cards in your PE-box).
The parallel port
Pinout
Theory of operation
Circuitery
Sample programs
The serial ports
Pinout
Theory of operation
__Xon/Xoff
__RTS/CTS
__DST/DTR
__DCD, RI
__Simplex. duplex, half-duplex
__Null-modem connections
The TMS9902
Pinout
Operating the TMS9902
_Control register
_Interval register
_Rate registers
_Interrupts
_Error detection
_Transmission monitoring
Sample program
Electrical characteristics
Timing diagrams
Circuitery
CRU interface
CRU map
Card ROMs
The power-up routine
The DSRs
The ISR
This port, known as PIO (Parallel Input/Output) on the TI-99/4A card, features an 8-bit wide bidirectional parallel connection. In addition there are two extra output lines (SPAREOUT and HANDSHAKEOUT) and two extra input lines (SPAREIN and HANDSHAKEIN).
Pin | I/O | Function | Access |
---|---|---|---|
1 | O | HANDSHAKEOUT | CRU bit 2 |
2 | I/O | D7 (lsb) | Byte >5000 |
3 | I/O | D6 | |
4 | I/O | D5 | |
5 | I/O | D4 | |
6 | I/O | D3 | |
7 | I/O | D2 | |
8 | I/O | D1 | |
9 | I/O | D0 (msb) | |
10 | I | HANDSHAKEIN | CRU bit 2 |
11 | - | Ground | - |
12 | O | +5V via 10 Ohm | - |
13 | I | SPAREIN | CRU bit 3 |
14 | O | SPAREOUT | CRU bit 3 |
15 | O | +5V via 1 KOhm | - |
16 | - | Ground | - |
Nothing simpler than a parallel port: the sender just puts a byte of data on the port and it is transfered to the receiver over 8 parallel wires, one per bit. The only problem is that we need some way to tell the receiver when a new byte is sent (since we may want to send the same byte several times, the receiver cannot just monitor the port for changes). We'll have to use an extra line, on which a pulse (strobe) will be sent to indicate a new byte. The PIO port has no dedicated strobe line, but we could use either HANDSHAKEOUT or SPAREOUT for this purpose. Let's say HANDSHAKEOUT since the name suggests something like that anyway.
The other control lines can serve several purposes. For instance, the receiver may need more time to process the incoming data. The HANDSHAKEIN line may thus be used to provide the sender with an acknowledgment signal: "OK, I got that. Keep sending data".
The peripheral may also need to signal the sender when an error condition occured, such a "printer out of paper". The SPAREIN line may be used for that purpose.
Finally, since the PIO port is bidirectional, we may want to establish a duplex communication, in which the data lines are used by either device to send data to the other (this is known as half-duplex, see below). The SPAREOUT line may be used to decide whose turn it is to send data.
Once again, these are only suggestions: there is no norm for PIO port connections. The extra lines may well be used to operate special features of the peripheral controlled via the parallel port.
This is the internal wiring for the PIO port in a typical interface card. For help with the TTL chips, refer to my TTL page.
PE-Box bus 74LS259 74LS251 |
The PIO port can be accessed at address >5000 once the RS232 card is active (this is done by setting CRU bit number 0 to 1). Note however that the card can either send a byte or receive one, but not both together. Therefore we must first set the direction of transmission with CRU bit 1, this bit directly controls the DIR pin of the 74LS245 bus transceiver that buffers the PR-box bus. To read a byte, first set CRU bit 1 to '1', then read address >5000. To send a byte, set CRU bit 1 to '0', then write a byte to >5000.
As you can see from the above schematic, data to/from the PIO port is latched by two 74LS373 D-type latches, one for input and one for output.The signals controlling these latches are generated by a custom control chip, with the exception of PIOOUT which comes directly from CRU bit 1. The custom chip also generates the CRU_I* and CRU_O* that control the CRU interface chips. One of the input of this control chip is connected to a jumper that allows to select either >1300 or >1500 as a CRU base address for the card.
CRU bit 2 controls two of the spares lines: HANDSHAKEIN and HANDSHAKEOUT. Reading CRU bit 2 returns the current status of the HANDSHAKEIN line, writting to this bit sets the status of the HANDSHAKEOUT line.
The SPAREIN and SPAREOUT lines are controled by CRU bit 3, in the same manner.
Finally, CRU bit 4 is reflected onto itself. Whatever you write to it should be read back from it. This is a way to determine whether a RS232 card is installed.
CRU bits 5 and 6 control the CTS pins of the RS232 serial port, they'll be discussed later.
CRU bit 7 turns the card light on in the PE-box, to signal the user that the card is active. That's an unusual design since most of the time the light is controlled by CRU bit 0, together with the ROMs. May be TI did this so that the light can indicate an ongoing transmission, rather than just telling when the card is on.
Here are simple exemples on how to send and receive bytes through the PIO port, using the HANDSHAKEIN and HANDSHAKEOUT lines for synchronisation. This is an adaptation of the routines in the card ROM. The original routines check whether to use PIO or RS232, but I'm not including the RS232 part here. Also R11 is saved in the scratch-pad, not in R10.
* This routine sends 1 byte through the PIO port. The Byte is in R0. PIOUT MOV R11,R10 Save return point SK1 MOVB R0,@>5000 Send the byte SK2 SBO 2 Reset HANDSHAKEOUT as high |
The other computer would run the corresponding routine:
* This routine receives 1 byte through PIO PIOIN TB 2 Wait for HANDSHAKEIN to be high SK0 SBO 1 Set PIO port as input SK1 CLR R0 The emmiter signaled a byte was sent |
Of course, these are pretty crude routines. In particular, there is no way to control the flow, nor to verify the integrity of the tranmitted data. Furthermore, they require that you first send the number of bytes to follow:
* This routine sends a bunch of bytes NBYTES DATA >0123 Number of bytes to send *This routine receives a bunch of bytes LI R1,BUFFER Ptr to reception buffer SIZE DATA 0 Number of bytes that will be received |
The transmission protocol is thus the following:
Sender Receiver
Check it bit 2 is high (sender ready)
Wait for bit 2 low Set bit 2 low: "ready to receive"
Send data Wait for bit 2 low
Set bit 2 low: "sent" Get the data
Wait for bit 2 high Set bit 2 high: "I got it"
Set bit 2 high "ready"
Note that in this protocol the sender speaks first. I mean that to send something we have to wait for the receiver to signal it's ready. Another scheme would be for the sender to activate HANSHAKEOUT when it wants to send something and to wait for the receiver to acknowledge the change. With the first scheme, the sender constantly checks whether the receiver is ready, with the second scheme the receiver constantly checks whether the sender requests a transmission.
Sender Receiver
Check if bit 2 low (sender calling)
Set bit 2 low: "hello?"
Wait for bit 2 low Set bit 2 low: "ready to receive"
Send data Wait for bit 2 high
Set bit 2 high: "sent" Get the data
Wait for bit 2 high Set bit 2 high: "I got it"
TheTI interface card comprises two RS232C serial ports combined in a single connector.
Pin # | I/O | Function | Access |
---|---|---|---|
1 | - | Ground | - |
2 | I | RD-1, data input | CRU bits 0-7, base >1340 |
3 | O | TX-1, data output | CRU bits 0-7, base >1340 |
5 | O | CTS-1, clear to send | CRU bit 5, base >1300 |
6 | O | DSR, data set ready | Always 1 (+12V via 1.8 KOhm) |
7 | - | Ground | - |
8 | O | DCD1, data carrier detected | CRU bits 16, base >1340 |
12 | O | DCD2, data carrier detected | CRU bits 16, base >1380 |
13 | O | CTS2, clear to send | CRU bit 6, base >1300 |
14 | I | RD2, data input | CRU bits 0-7, base >1380 |
16 | O | TX2, data output | CRU bits 0-7, base >1380 |
19 (or 18) | I | DTR-2 (to DSR* and CTS*) | CRU bits 27 or 28, base >1380 |
20 (or 11) | I | DTR-1 (to DSR* and CTS*) | CRU bits 27 or 28, base >1340 |
4, 9, 15,17 21, 21, 24, 25 |
- | Not connected | - |
NB Some cards have jumpers to direct the DTR lines to either pin 19 or 18 and 20 or 11.
Contrarily to a parallel port, the serial ports send a byte one bit at a time. This is of course much slower, but it has one advantage: it requires only one physical connection (two with the ground reference, three if you want bidirectional communications). This is much easier to isolate than a 8-bit bus and as a result serial transmission is much more reliable and works at longer distances than parallel transmission. Also think of an infrared connector: for parallel transmission we would need 8 infrared LEDs of different wavewelength in the emmiter and 8 photo-transistors, each with a filter discriminating for only one LED. Not impossible, but quite tricky. By contrast, a serial transmission requires only one LED in the emmiter and one phototransistor in the receiver. The price to pay is that we need some kind of hardware to serialize a byte in the sender and pack the received bits back in a byte in the receiver. Fortunately, there are dedicated chips for that purpose: UART (Universal Assynchronous Receiver/Transmitter) and USRT (Universal Synchronous Receiver/Transmitter). There are also USART, i.e. chips that can operate either synchronously or asynchronously. Synchronous serial transmission uses one more line to send a clock signal used to synchronized the sender and the receiver. In a way, it is already a first step toward parallelism...
Texas Instruments created their own UART chip, the TMS9902, and their USRT, the TMS9903, for use with the TMS9900 line of products. The interface card has two TMS9902 each in charge of a serial port.
Here is a typical signal send over an asynchronous serial line:
______ _________________________________________________ Logic 1
| st | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | Pa | Sp |____ Logic 0
As you can see, 8 data bits were transmitted. This is by no mean an absolute requirement:, we could have transmitted any number of data bits: 5, 6, 7, 8, 9, 10, 11, etc. The only requirement is that the sender and the receiver agree in advance on the number of bits that will be sent as a "character". Generally it will be 7 or 8. This is because a byte is 8 bits on a computer. However, the ASCII code is limited to characters 32-127, thus only 7 bits are needed to transmit pure text and setting serial transmission as 7-bit words will gain some time (about 10% when compared to 8-bit).. The TMS9902 can handle between 5 and 8 bits. Note that data bits are transmitted starting with the least significant one, bit 7 according to TI numbering convention.
You will have noted that the data bits are enclosed between extra bits: a start bit (st) and a stop bit (Sp). One says that the transmitted character is "framed" by these control bits. The start bit is necessary so that the receiver knows where the data starts: as there are only two voltage levels, there is no way to differentiate an idle line from a "space" (logic 1 in our exemple). For instance, imagine that we send the following char 11001010, what the receiver sees is 00 0 0 and how shall it know whether we meant 00101011, 10010101 or 11001010? In other words, we must find a way to mark the leading ones. We could use an extra connection that would provide a clock signal each time a bit is sent: this is called synchronous transmission and is dealt with by specialized chips: USRTs. Asynchronous transmissions use a different trick: a start bit is placed in front of the others: 011001010. Now the receiver sees 0 00 0 0, knows that the first 0 is the start bit and interprets the next two spaces as 1s: 11001010. Elegant, isn't it?
Similarly, a stop bit is appended at the end of the byte (Sp in the scheme). A stop bit is merely the absence of a start bit: it just provides a pause to let the receiver resynchronize with the sender. Detection of a low signal during that time generally indicates that sender and receiver are using different transmission speeds and results in an error condition (framing error). In the above example the stop bit has the same size than a regular bit, but it is possible for the TMS9902 to send a stop bit equal to 1.5 or 2 regular bits.
The start bit for the next char should come right after the stop bit. If this does not happen whithin a short time, the receiver interprets it as a "break" signal.
The parity bit (Pa in the scheme) is used to check for the integrity of the transmission. The hardware counts the bits set as "1" (not including start and stop) and uses the parity bit to tell the receiver whether the total of "1"s is odd or even. The receiver performs the same check: if a bit was mangled during transmission the parity will be wrong. If more than one bit were affected, there is one chance out of two that the parity bit will be incorrect. The receiver can then issue an error and ask the sender to transmit that byte again.
There are 5 possibly parity options:
The first two types of parities are rarely used and the TMS9902 does not support them. The next two are very common. With odd parity the number of "1" bits in the char plus the parity bit itslef will always be odd (the harware adjusts the parity bit so that this is always true). With even parity, the number of "1" bits in the char plus the parity bit itself is always even. Here also, sender and receiver must agree on what type of parity check will be used.
The last issue is speed. This is not appearant from the diagram above, but the same sequence of bits could be sent at different speeds. Serial transmission speeds are measured in bps (bits per second). Transmissions over a phone line are measured in bauds (the name comes from the french mathematician J.M.E. Baudot). For low values bauds equal bps, however there is an upper limit to the number of bauds for a phone line. Todays high-speed modems manage to overcome that limit by using sophisticated compression techniques, so you can have 28000 bps or even 56000 bps modems, but strictly speaking these aren't 28000 or 56000 bauds! Once more, sender and receiver must agree on the transmission speed prior to any transmission.
In summary the sender and the receiver must aggree on: transmission speed, number of bits per char, parity type, number of stop bits. All this is often expressed in cryptic expressions like: 9600 8N1. This simply means: 9600 bps, 8 bits per word, No parity, 1 stop bit.
Now we have a way to reliably transfer one byte over a serial line (or on a parallel connection), but what if we want to transfer more than one. How does the sender tell the receiver where the byte stream starts or ends?
One way is to reserve special characters to serve as start-of-message and end-of-message marks. This is called the Xon/Xoff protocol and is widely used over so-called "null modems" (i.e.using no other connections than the data lines). The major pain in the butt, is that the special characters cannot be part of the transmitted message. That's fine if we are transmitting text (since ASCII characters only use values from 32 to 127), but we'll be in trouble if we want to send binary data.
Here is a list of all special characters reserved by the ASCII convention. Those were usefull for teletypes: terminals that could only send/receive characters. The "keyboard" column indicates the key combination that will generate that code on the TI-99/4A (keyboard type 4).
Hex | Code | Meaning | Keyboard |
---|---|---|---|
01 | SOH | Start of heading | Ctrl-A |
02 | STX | Start of text | Ctrl-B |
03 | ETX | End of text | Ctrl-C |
04 | EOT | End of tasnmission | Ctrl-D |
05 | ENQ | Enquiry | Ctrl-E |
06 | ACQ | Acknowledge | Ctrl-F |
07 | BEL | Generate a beep | Ctrl-G |
08 | BS | Backspace | Ctrl-H |
09 | HT | Horizontal tab | Ctrl-I |
0a | LF | Line feed (next line) | Ctrl-J |
0b | VT | Vertical tab | Ctrl-K |
0c | FF | Form feed (next page) | Ctrl-L |
0d | CR | Carriage return (to first column) | Ctrl-M |
0e | SO | Shift out | Ctrl-N |
0f | SI | Shift in | Ctrl-O |
10 | DLE | Data link escape | Ctrl-P |
11 | DC1 | Device Control 1 | Ctrl-Q |
12 | DC2 | Device Control 2 | Ctrl-R |
13 | DC3 | Device Control 3 | Ctrl-S |
14 | DC4 | Device Control 4 | Ctrl-T |
15 | NAK | Negative acknowledge | Ctrl-U |
16 | SYN | Synchronous idle | Ctrl-V |
17 | ETB | End of transmission block | Ctrl-W |
18 | CAN | Cancel | Ctrl-X |
19 | EM | End of medium | Ctrl-Y |
1a | SUB | Substitute | Ctrl-Z |
1b | ESC | Escape | Ctrl . |
1c | FS | File separator | Ctrl : |
1d | GS | Group separator | Ctrl = |
1e | RS | Record separator | Ctrl-9 |
1f | US | Unit separator | Ctrl-8 |
The "Old" and "Save" DSR routines in the TI interface card make use of SYN, ACQ and NAK (see below).
A way to overcome the necessity for special characters is to have extra connections that serve as handshake lines. This introduces some more parallelism into our serial connection, but it has the advantage of speeding the transmission a little. The RS232C standard defines two such pins: RTS send for "Request To Send" ("Can I send you data"), and CTS stands for "Clear To Send" ("OK, send it").
The sender initiates the transmission by activating the RTS line, waits for the receiver to answer by activating the CTS line. The sender then sends data and wait for the receiver to ackowledge it by reseting the CTS line.
Sender Receiver
Wait for RTS
Activate RTS
Wait for CTS Activate CTS
Send data Receive data
Reset RTS
Wait for CTS Reset CTS
These two extra lines provide a different type of handshake: they are meant to insure that both sender and receiver are online and ready to operate. DSR stands for "Data Set Ready" and is used by the receiver to indicates that it's ready to operate. DTR stands for "Data Terminal Ready" and is used by the sender to indicate that it's operational. In other words, DSR and DTR are used by two devices to establish a connection. Once this is done, they can begins sending/receiving data and use the RTS/CTS protocol to control the flow of data.
Sender Receiver
Set DTR active
Check DSR
Wait for DTR
Set DSR active
Those lines are meant for use with a modem. DCD stands for "Data Carrier Detected" and is used by the modem to indicate that it is receiving a carrier, i.e. a sound wave modulated to carry data bits. DCD should remain active as long as the connection is established.
RI stands for "Ring Indicator" and is used by the modem to indicate that the phone is ringing. It thus signals that an external device would like to send data to the computer.
DSRD stands for "Data Signal Rate Detector". It is not always present on RS232C plugs (it is on PCs 25-pins sockets, but not on 9-pin sockets). This signal is used by either device to signal a change in transmission rate.
In the TI card, DCD is internally connected to DTR. RI and DSRD are not implemented.
Two devices can communicate either in simplex mode, half-duplex or dull-duplex mode.
In simplex mode, one device is a dedicated sender and the other is a dedicated receiver. By definition there will be only one data line (8 for parallel ports), and possibly some synchronisation lines. Duplex means that both devices can either send or receive data. With half-duplex there is still only one data line shared by both devices. This implies using extra control lines so that the devices can agree on who is sending and who is receiveing. By contrast, with full-duplex there are two data lines: one is used by device A to send data to B, the other is used by B to send data to A. Additional control lines may be used, but are optional.
This is a typical wiring for simplex connections: for instance a printer hooked to computer. The printer does not need to send data, at the most a control line is used so that the printer can tell the computer when data is arriving too fast.
Sender Receiver
Sends data TX----------------------------->RD Receive data
RD<-- nc nc---TX
Always active RTS---.....(may be nc).......-->RTS
CTS<--.....(may be nc).......---CTS Always active
DCD<----------------------------DCD Always inactive
Active when ready DTR---------------------------->DTR
DSR<----------------------------DSR Always active (or active when called)
Half-duplex connections are much more tricky:
Sender Receiver
Sends data TX---+----------------------+-->RD Receives data
Receives data RD<--' '---TX Sends data
Active to send RTS---------------------------->RTS
CTS<----------------------------CTS Active to receive
DCD<----------------------------DCD Active when needs to send
Active when ready DTR---------------------------->DTR
DSR<----------------------------DSR Always active (or active when called)
Finally, here is an example of a full-duplex connection.
Sender Receiver
Sends data TX----------------------------->RD Receives data
Receives data RD<-----------------------------TX Sends data
Always active RTS---.....(may be nc).......--->RTS
CTS<--.....(may be nc).......---CTS Always active
DCD<----------------------------DCD
Active when ready DTR---------------------------->DTR
DSR<----------------------------DSR Always active (or active when ready)
Here is an exemple of a duplex transmission between a computer and a modem:
Sender (computer) Receiver (modem)
Standby: (DTR, RTS inactive) Standby: RI, DCD inactive. (DSR, CTS inactive)
Incoming call: activate RI
Acknowledge: Active DTR Activate DSR
Remote connection established: Activate DCD
Receive data on RD Sends data on RD
Wants to send: Activate RTS Acknowledge: Activate CTS
Sends data over TX Receives data on TX
Break connection: inactivate DTR Or hangup: inactivate DCD or DSR
A so-called null-modem designs a simple connection in which no control lines have been implemented. Concretely, the lines are reflected on the machine they came from, so that it looks like the remote device is always answering immediately. Here is an example of a null-modem connection between a computer and a printer. Since the printer won't send any data to the computer, the RD connection is not necessary. On the other hand, the printer may need to tell the computer to hold the flow of data until it had caught up with printing it. To this end, the BUSY pin on the printer may be connected to DSR on the computer.
Sender (computer) Receiver (printer)
Sends data TX----------------------------->RD Receives data
RD<--------(optional)-----------TX
RTS---, ,---RTS
CTS<--' '-->CTS
DCD<--, nc ,---DCD
DTR---+ nc +-->DTR
DSR---' '---DSR
Gnd-----------------------------Gnd
( Optionally DSR<----------------------------BUSY Hold transmission while printing)
Here is a more complicated exemple: a null-modem connection between two computers. In this case, the problem is that RTS in an output pin on both machines (as opposed to a dedicater receiver, like a printer, where RTS is an input pin). Conversely DCD is an input pin on both devices (whereas it is an ouput pin on a modem), etc. In other words, there is a sender at both end of the connection! We therefore need some creativity in our wiring if we want to respect the RS232C standards. If we don't care about that, the previous wiring will do just fine (without the BUSY line of course).
Sender (computer A) Sender (Computer B)
Sends data TX----------------------------->RD Receives data
Receives data RD<-----------------------------TX Sends data
RTS---+------------------------>DCD
CTS<--'
DCD<-----------------------+----RTS
'--->CTS
DTR------------------------+--->DSR
'--->RI
DSR<--+-------------------------DTR
RI<---'
Gnd-----------------------------Gnd
This UART is meant to be used with a TMS9900 or equivalent CPU. All the CPU access is performed via the CRU. There are 64 CRU bits available, some are read-only, some are write-only and some have different meanings whether read or written to.
The chip contains several registers: a read-only Receive buffer (8 bits) and a write-only Emit buffer (8 bits) are used to store data before/after serialisation. Two Rate registers (11 bits) are used to specify the transmission speed, one for emission, one for reception. Generally both rates will be equal, but that's not always the case: the french Minitel system for instance has a high reception speed, but a slow emission speed. The TMS9902 also comprises an Interval register (8 bits) that can be used as a countdown timer and a Control register (8 bits) used to control several functions of the chip.
The TMS9902 can generate interrupts on its INT* pin upon several conditions: when a bit arrives, when the buffer is full, when the timer fires, etc. These interrupts are fed to the console via the EXTINT line and processed by the TMS9901 in the console as peripheral interrupts. The main interrupt service routine (ISR) in the console then scans every peripheral card for ISR and branches to it. The ISR in the TI interface card only deals with reception interrupts (see below).
+----+--+----+
INT* |1 o 18| Vcc
XOUT |2 T 17| CE*
RIN |3 M 16| PHI*
CRUIN |4 S 15| CRUCLK
RTS* |5 14| S0
CTS* |6 9 13| S1
DSR* |7 9 12| S2
CRUOUT |8 0 11| S3
GND |9 2 10| S4
+------------+
Power supply
Vcc: +5V
GND : ground
CPU interface
CE*: Chip enable. This pin is active (low) when the CRU
address
corresponds to the base address of the chip (>134x or >138x).
S0-S4: These 5 input pins serve to select the proper CRU
bit to be accessed.
CRUIN: This output line is used by the CPU to read CRU
bits
from the TMS9902
CRUOUT: This input line is used by the CPU to write to a
CRU bit inside the TMS9902.
CRUCLK:. CRU clock: this input line is activated by the CPU upon
CRU write operations, so that the TMS9902 can distinguish them from
regular
memory access.
INT*: This output line is used by the TMS9902 to send an
interrupt
signal to the CPU.
PHI*: This pin is used to input the main clock signal.
Serial interface
RIN: Serial data input pin.
XOUT: Serial data output pin.
RTS*: This output pin is used by the TMS9902 to carry a
request-to-send
signal to the peripheral.
CTS*: This input pin is used by the peripheral to tell the
TMS9902
it is clear to send data.
DSR*: This input pin is used by the peripheral to tell the
TMS9902
that it is active (data set ready).
Note: CTS* and DSR* are connected together in the TI card! They get
their
input from the DTR (data terminal ready) pin of the RS232 connector.
All four write-only register map to the same CRU bits: the first 11 bits in the CRU address space of the chip (although some registers use less than 11 bits). To write a value in a register, you must first tell the TMS9902 which register you want to access. This is done with CRU bits 11 to 14:
This register is used to set various transmission parameters.
Bit # | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Name | SBS1* | SBS2 | PENA | PODD | CLK4M | RCL2 | RCL1 | RCL0 |
Meaning | 00: 1.5 stop bit 01: 2 stop bits 1x: 1 stop bit |
0: No parity 1: Parity enabled |
0: Even parity 1: Odd parity |
0: 1/3 freq 1: 1/4 freq |
Record lenght: # of bits per char 000=5, 001=6, 010=7, 011=8, |
SBS1-2: Number of stop bits: 00=1.5, 01=2, 10=1.
PENA: Parity enable: 0=no parity, 1=use parity.
PODD: Parity odd/even: 0=even, 1=odd.
RCL0-2: Number of bits per word.000=5, 001=6, 010=7, 011=8, 100=9 (?) .
CLK4M: Console clock frequency divider. 0= divide PHI* by 3 to get the
main frequency, 1= divide PHI* by 4.
*: Loading SBS1 cause the next load operation to access the interval
register.
Note: I've arranged bits in descending order because the LCDR instruction always starts loading with the least significant bit of the source argument. The table thus shows the bits as they need to be in the source of a LCDR instruction, which makes it easier to use.
These registers are used to specify the speed of transmission for reception:
Bit # | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|
Name | RDV8 * | RDR9 | RDR8 | RDR7 | RDR6 | RDR5 | RDR4 | RDR3 | RDR2 | RDR1 | RDR0 |
Meaning | Div by 8 | Frequency divider |
And for emission:
Bit # | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|
Name | XDV8 | XDR9 | XDR8 | XDR7 | XDR6 | XDR5 | XDR4 | XDR3 | XDR2 | XDR1 | XDR0 |
Meaning | Div by 8 | Frequency divider |
RDR0-9: Divide the main frequency by this number to get the
reception
frequency.
RDV8: Further divide the reception frequency by 8.
XDR0-9: Divide the main frequency by this number to get the emission
frequency.
XDV8: Further divide the emission frequency by 8.
*: Loading RDV8 causes the next load operation to access the emission
rate
register.
In summary, the transmission frequency can be calculated as:
Ftrans = Fmain
2 * (8**RDV8) * RDR
Fmain = Phi3 or Phi3 depending on CLK4M
3 4
Phi3 = 3 Mhz or 2.5 Mhz depending on the console
Here are the values to program in the rate register for the most common transmission rates (as used in the interface card ROM):
Clock | 2.5 MHz | 3.0 MHz | ||
---|---|---|---|---|
Rate (bps) | CLK4M | Rate register | CLK4M | Rate register |
110 | 1 | >563 | 1 | >5AA |
300 | 1 | >482 | 1 | >49C |
600 | 1 | >209 | 1 | >271 |
1200 | 0 | >15B | 0 | >1A1 |
2400 | 1 | >082 | 1 | >09C |
4800 | 1 | >041 | 1 | >04E |
9600 | 0 | >02B | 1 | >027 |
This register can be used as a countdown timer.
Bit # | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Name | TMR7 * | TMR6 | TMR5 | TMR4 | TMR3 | TMR2 | TMR1 | TMR0 |
Meaning | Timer: # of cycles to countdown (1 for 64 clock ticks) |
TMR0-7: countdown value.
*: Loading TMR7 causes the next load operation to access the reception
rate register.
The counter will be decremented every 64 tick of the main clock (i.e Phi/3 or Phi/4). When it reaches 0, CRU bit 25 is set to 1. If timer interrupts were enabled (by writing 1 to CRU bit 20), an interrupt is sent and CRU bit 19 is set to 1 (as well as CRU bit 31, which is set by any interrupt). The countdown then resumes from the original value. If it reaches 0 again and the previous countdown was not acknowledged (.e. CRU bit 25 was not reset by writing to CRU bit 20), then an error occurs and CRU bit 24 is set to 1. This way the user can know that the timer has cycled more than once.
In summary:
Writing to CRU bit 20 enables/disables timer interrupts.
Loading the Interval register starts the timer (or stops it if 0 is
loaded).
CRU bit 25 is set to 1 when the time has elapsed.
CRU bit 24 is set to 1 when the time has elapsed and CRU bit 25 was
already
1.
CRU bit 19 is set to 1 when the time has elapsed and timer interrupts
were
enabled.
CRU bit 31 is set to 1 if an interrupt occured.
CRU bits 19, 24 and 25 can be reset by writing to CRU bit 20.
For a 3 MHz console, the minimum interval on the timer is 64 micoseconds and the maximum is 16.32 milliseconds, with a resolution of 64 microseconds. By setting the CLK4M bit in the control register, these values become 48 microseconds to 12.24 milliseconds with a resolution of 48 microseconds. I'll leave it to you to do the calculations for a 2.5 MHz console.
There are many conditions that may cause the TMS9902 to issue an interrupt . Each type of interrupt can be enabled/disabled by writing to a specific CRU bit. Also, each type of interrupt will set a specific CRU bit, which allows the user to determine the cause of the interrupt. Most of the time, reading the culprit bit will clear the interrupt condition and reset that bit.
Interrupt issued when | Set with | Signaled by | Signal cleared by |
---|---|---|---|
Reception buffer is loaded | CRU bit 18 | CRU bit 16 | CRU bit 18 |
Emission buffer is empty | CRU bit 19 | CRU bit 17 | Loading emission register |
Timer reached 0 | CRU bit 20 | CRU bit 19 | CRU bit 20 |
CTS or DSR changed | CRU bit 21 | CRU bit 20 | CRU bit 21 |
Any of the above occured | Always set | CRU bit 31 | Reseting that interrupt |
The TMS9902 can detect a variety of error conditions and several CRU bits are used to indicate which error occured.
Error condition | Signaled by | Signal cleared by |
---|---|---|
Reception error (any of the next 3) | CRU bit 9 | Error condition is cleared |
Parity error | CRU bit 10 | Receiving a char with good parity |
Overflow (new bit received when buffer full) | CRU bit 11 | Reception buffer is read |
Frame error (0 received instead of stop bit) | CRU bit 12 | Receiving a char with correct stop bit |
Timer error (time elapsed twice) | CRU bit 24 | Writing to CRU bit 20 |
If you need more sophisticated control over the transmission than the automated function provided by the UART, more CRU bits can be used for this purpose.
CRU bit | I/O | Function |
---|---|---|
13 | I | 0: first data bit of a char has arrived 1: all bits have arrived |
14 | I | 1: first data bit has arrived |
15 | I | Logic level of the RIN pin |
21 | I | 1: Receive buffer contains a char (Reset by writing to bit 18) |
22 | I | 1: The emission buffer is empty 0: A char has been loaded in the buffer |
23 | I | 0: Char emission in progress 1: Nothing is emitted (XOUT is high) |
26 | I | Logic level of the RTS pin (inverted) |
27 | I | Logic level of the DSR pin (inverted) |
28 | I | Logic level of the CTS pin (inverted) |
29 | I | 1: DSR or CTS has changed and is stable for 2 clock cycles (Reset by writing to bit 21) |
30 | I | 1: A register is being loaded or the break bit (17) is 1. |
15 | O | 1: Test mode RTS-->CTS, XOUT-->RIN, DSR=low, interval timer decremented every 2nd cycle (32x faster) |
16 | O | 1: Activate RTS (low) 0: Inactivate RTS once emission is completed (and break bit is 0) |
17 | O | 1: Break on. XOUT=low, emission buffer cannot be loaded |
31 | O | 1: Reset. Set bits 11-14 to 1. Set bits 17-20 to 0 (no
interrupts) Empty emission and reception buffers, clear timer. No break, RTS=high No operation allowed for 11 clock cycles. |
Here is an example on how to set transmission parameters. For routines that send and receive a byte, see below. For examples dealing with the interval register, see the interrupt routine.
* This routine sets the transmission conditions for the first TMS9902 on card 1 |
Supply voltage: -0.3 to +10V
Input/output voltages: -0.3 to +10V
Power dissipation: 0.55W
Free air temperature: 0 to 70 `C
Storage temperature: -65 to 150 `C
Parameter | Min | Nom | Max | Unit |
---|---|---|---|---|
Supply voltage Vcc | 4.5 | 5 | 5.5 | V |
Supply voltage Vss | . | 0 | . | V |
High-level input voltage | 2 | . | Vcc | V |
Low-level input voltage | Vss-0.3 | . | 0.8 | V |
Free air temp | 0 | . | 70 | 'C |
Parameter | Test conditions | Min | Typ | Max | Unit |
---|---|---|---|---|---|
High-level output voltage | I= -100 uA I= -200 uA |
2.4 2.2 |
. | Vcc Vcc |
V |
Low-level output voltage | I=3.2 mA | Vss | . | 0.4 | V |
Input current (any input) | 0 - Vcc | . | . | 10 | uA |
Average Vcc supply | tc=330ns, TA=70'C | . | . | 100 | mA |
Small signal input capacitance | f = 1 MHz | . | . | 15 | pF |
|tc=300-500 |_____ 12-30 12-30 _____
____/ a \ b / |\____|/ |\_____/ \________ PHI*
__ __ |25|
X| bit address n | bit address n+1 |XXXX CRU address
| >180 ns |
\ >150 ns | CE*
___ ___
_________________/ c \___________________/ \_________ CRUCLK
__ __
X| bit data out n | bit data out n+1 |XXXXX A15/CRUOUT
| >180 ns |
a) 0.45 - 0.55 Tc
b) 0.45 - 0.55 Tc
c) >0.37 Tc
____/ \ / \_____/ \_____/ \________ PHI*
__ __
X| bit address n | bit address n+1 |XXXX CRU address
| <260 ns |
\ <240 ns | CE*
|
| CRUCLK
__ | __
XXXXXXXXXXXXXXX| bit n |XXXXXXXXXXXXXXXX|bit n+1|XXXXXX CRUIN
What I did not mention above, is that a serial port uses different voltage values than a parallel port. Typically, for a parallel port a logic 1 is +5V and a logic 0 is 0V (gnd). However, for serial input connections a logical 1 is any voltage between +3 to +15V and a logical 0 is an voltage between -3V and -15V. For output connections, a logical 1 is +5V to +15V and a logical 0 is -5V to -15V. These values are known as EIA signal level and allows for better transmission over longer distances. However, it necessitates special chips to handle the interface, since standard TTL chips only operate in the range 0 to +5V. The PE-Box power supply provides peripheral cards with +5V, +12V and -12V, therefore the serial ports on the card will generate +12V and -12V signals. They should however be able to input higher voltage values.
The interface chips used for output are two operational amplifiers, TL082 and TL084, powered by +12V and -12V instead of +5V and 0V. For input, the card uses a 75189 Schmidt-trigger inverter. Other possibilities for output would be: SN75150, SN75188, or MC1488. And for input: SN75152, SN75154, SN75189, SN75189A, MC1489, or MC1489A. All these guaranty transfer rates upto 20 kB over a 50-foot connection.
PE-Box bus TMS9902 RS232 plug |
Notes
This circuit exists in duplicate: one per TMS9902 chip (except for
DSR: there is only one).
The SEL* signals are provided by the custom control IC that decodes the
CRU addresses.
Let's summarize the above in a map of the CRU bits used to access the interface card. Remember that the card base address is >1300. It can be set as >1500 if you have two cards in a PE-Box, but for this you need to physically modify the card. Nothing too difficult, just locate the resistor labelled R5 and move it to the place labelled PTH1 (it should be just below it). Here is a picture of a card bearing such a modification.
The second card will respond to DSRs RS232/3, RS232/4 and PIO/2, but only if the first card is also installed, otherwise it will respond to RS232, RS232/2 and PIO, just as if it were not modified.
Bits 0 through 7 control the card directly. The two TSM9902 are installed at >1340 and >1380 respectively. In the table below, I chose to renumber the bits from 0 in each chip, since this makes things easier to understrand. Furthermore, this is generally what the programmer will do: set R12 as >1340 instead of >1300 so that LCDR and STCR instructions can be used to load and read registers.
Bit | R12 address | Meaning when read | Effect when written |
---|---|---|---|
0 | >1300 | Always 0 | 1: Turn card ROMs on |
1 | >1302 | PIO direction 0=output 1=input | 0: Set PIO as output 1: Set PIO as input |
2 | >1304 | Status of HANSHAKEIN pin (PIO) | Set HANSHAKEOUT pin ( PIO) |
3 | >1306 | Status of SPAREIN pin (PIO) | Set SPAREOUT pin (PIO) |
4 | >1308 | Read itself | Writes to itself |
5 | >130A | Status of CTS1 pin | Set CTS1 pin |
6 | >130C | Status of CTS2 pin | Set CTS2 pin |
7 | >130E | Lamp status | 1: Lamp on |
0-7 | >1340-134E | Content of Receive buffer (8 bits) | Value to load in selected register (11 bits) |
8 | >1350 | - | |
9 | >1352 | 1: Reception error (bit 10/11/12 =1) | |
10 | >1354 | 1: Parity error | |
11 | >1356 | 1: Overflow (bit arrived when buffer full) | 1: Load Emission-Rate register |
12 | >1358 | 1: Frame error (0 received as stop bit) | 1: Load Reception-Rate register (reset upon loading) |
13 | >135A | 1: First bit has arrived | 1: Load Interval register (reset upon loading) |
14 | >135C | 1: Receiving byte (for test purposes) | 1: Load Control register (reset upon loading) |
15 | >135E | 1: Status of RIN pin | 1: Test mode (RTS->CTS, XOUT->RIN, timer*32) |
16 | >1360 | 1: Reception interrupt occured (reset by writing to bit 18) |
Set RTS pin (RTS=1 only if input bits 22+23=0) |
17 | >1362 | 1: Emission interrupt occured (reset by loading emission register) |
1: Abort transmission (XOUT=0 if bits 22+23=0) |
18 | >1364 | - | 1: Enable reception interrupts |
19 | >1366 | 1: Timer interrupt occured (reset by writing to bit 20) |
1: Enable emission interrupts |
20 | >1368 | 1: CTS or RTS interrupt occured (reset by writing to bit 21) |
1: Enable timer interrupts |
21 | >136A | 1: Receive register full (reset by writing to bit 18) |
1: Enable interrupts when CTS or DSR change |
22 | >136C | 1: Emission register empty (reset by loading emission register) |
- |
23 | >136E | 1: No data currently sent (shift register is empty) |
- |
24 | >1370 | 1: Timer error (time elapsed twice) (reset by writning to bit 20) |
- |
25 | >1372 | 1: Time elapsed (reset by writning to bit 20) |
- |
26 | >1374 | Status of RTS pin (inverted) | - |
27 | >1376 | Status of DSR pin (inverted) | - |
28 | >1378 | Status of CTS pin (inverted) | - |
29 | >137A | 1: Change of DSR or CTS detected (reset by wrinting to bit 21) |
- |
30 | >137C | 1: Register being loaded |
- |
31 | >137E | 1: An interrupt occured | 1: Reset. Output bits 11-14=1, bits 17-20=0 Input bits 22,23=1, bits 13,21,25=0 |
0-31 | >1380-13BE | Same at >1340-137E for 2nd chip | Ditto for second chip (RS232/2) |
The TI interface card comprises 4096 bytes of ROM which provides the software to use with the card. The ROM contains an interrupt routine and several DSR (device service routines) for file access. There is also a power-up routine, but no subprograms. You'll find a commented disassembly listing of the whole ROM on my download page.
This is an extremely simple routine. All it does is:
The card ROM contains the following DSRs:
PIO, PIO/1: Access to parallel port
PIO/2: Ditto, on second card
RS232, RS232/1: Access to first serial port on first card
RS232/2: Access to second serial port on first card
RS232/3: Access to first serial port on second card
RS232/4: Access to second serial port on second card
As you can see, the DSRs are written to take into account the possibility of a second interface card installed in the PE-Box. A resistor on the second card is used to change its CRU address from >1300 to >1500 (although the software will work with any pair of CRU addresses). When only one card is installed, it answers to the first set of DSRs, no matter what its CRU is. When two cards are present, the one with the highest CRU address answers to the second set of DSRs.
Changing the CRU address involves a small hardware modification: transplanting a resistor. It is described in here.
When the console scanning routine calls a DSR, it has some key values stored at well defined places:
Several modifiers can be appended to the DSR name, using decimal points as separators:
These parameters are stored in the scratch-pad RAM at the following addresses:
>834A-8353: Copy of PAB
>8354-5: Length of DSR name
>8356-7: Point after DSR name in the PAB (in VDP memory)
>8358: Echo off (.EC)
>8359: No CR nor LF (.CR)
>835A: No LF (.LF)
>835B: Check for parity (.CH)
>835C: Add null chars (.NU)
>835D: Use interrupts (opcode >80)
>835E-F: Current rec #
>8360-1: Current rec size
>8364-D: Five buffers to save R11, used by different routines.
>83DA-B: Copy of the Control register contents
>83DE-F: Copy of the Interval register contents
The DSR executes the following file operations: Open, Close, Read, Write, Load, and Save.The other opcodes (Rewind, Delete, Scratch and Status) generate an I/O error 3. In addition, the DSR accepts a special "Open" opcode, >80, that turns interrupts on.
If you are not familiar with PABs and their opcodes, read this first.
Exit:
Save is meant to transfer a whole file (in program format) to another TI-99/4A that will receive it with Load. Load sends the SYN character to signal the beginning of a transmission however since "program" files can contain any character, none can be reserved as an end-of-file mark. Therefore, Save must always send first the number of bytes that will follow (sent as a 2-byte number).
To ensure that transmission worked well, Save and Load use a CRC (cyclic redundency check) checking mechanism. Load sends either the ACK or the NAK character to indicate whether thr CRC matches or not.
Correspondence between Load and Save
Save Bytes Load
Emit <SYN> ---1---> Wait for <SYN>
Emit size <------------+ ---2---> Receive size <-------------------------+
Emit CRC | ---2---> Receive CRC |
| If CRC wrong: emit <NAK> and retry---------+
Receive 1 char | <--1--- else: emit <ACK>
If not <ACK>, resend----+
Emit 1 chunk <----------+ --256--> Receive 1 chunk <--------------------------+
Emit CRC | ---2---> Receive CRC |
| If CRC wrong: emit <NAK> and receive same --+
Receive 1 char | <--1--- else: send <ACK> |
If not <ACK> send same--+ |
Next chunk -------------+ Next chunk ---------------------------------+
Close Close
CRC calculation routine
* This routine updates the current CRC value. DOCRC MOV R6,R1 |
If I'm not mistaken, this should correspond to a polynomial of x15+x11+x7+1. See the disk controller page for a discussion of CRCs.
The following is an adaptation of the reception routine in the card ROM. The original routine checks whether to branch to use PIO or RS232, but I'm only including the RS232 part. Also the two tests between LP1 and SK1 are in a separate subroutine. Finally, R11 is saved in the scratch-pad, not in R10.
* This routine receives 1 byte from the RS232 port REC1BY MOV R11,10 Save return point SK2 CLR R6 A byte was received |
The following is an adaptation of the reception routine in the card ROM. The original routine checks whether to branch to use PIO or RS232, but I'm only including the RS232 part. Also R11 is saved in the scratch-pad, not in R10.
* This routine sends 1 byte to the RS232 port. Byte is in R6. EMI1BY MOV R11,R10 Save return point SK6 LDCR R6,8 Load next byte in emission buffer |
The expected connections are as follow:
Sender Receiver
TMS9902 Connector Connector TMS9902
XOUT----->TX ----------------------->RD------>RIN
RTS*----->DCD----------------------->DTR--+-->DSR*
DSR*<--, '-->CTS*
CTS*<--+--DTR<-----------------------DCD<-----RTS*
RIN<------RD<------ optional ------TX<------XOUT
and the transmission protocol is the following:
Sender Receiver
Check if DTR is low
Set DCD low
Wait for DTR to be low
Wait until done with previous Wait for byte to fully arrive
Send next byte Read byte
Check for transmission errors
Will set DCD high once done
To send a whole bunch of bytes, there are two options: 1) First send the number of bytes to be sent, then send them or 2) send all bytes then generate a break signal by keeping the line inactive for a time.
The interrupt service routine in the peripheral card is fairly primitive, some may even call it buggy...
As you can see, this routine is buggy: if the interrupt did not come from the card, the second TMS9902 in the card will start generating interrupts whether this was intended or not!
Apart from this bug, the main trouble with this routine is that, once more, the TI engineers decided that we won't be allowed to use other features than the ones they have planed us to use. That's what I call the MacIntosh philosophy: "The average user is a complete moron. Let's make sure he/she won't be allowed to fool around with our wonderfull machine". In the present case, it means that we cannot use the interval timer to generate an interrupt. We could set the timer, but when the interrupt occurs nothing will happen: the chip will just be reset.
There is a way around that for those of us that have an Horizon Ramdisk: the guys who wrote the DSRs for that disk did an incredibly good job and they notably allow the user to install a custom ISR on disk. If we set the CRU address of the Ramdisk below that of the RS232 card, its ISR will be called before that of the RS232. It could thus check whether the interrupt was issued by the timer on the interface card. The routine should install itself in the memory expansion since the custom control chip may require interface card to be turned on (with CRU bit 0) for proper operation, which forces us to first turn off the Ramdisk.
Assuming all the above, here is what we would do:
* Set the timer * ISR installed in Ramdisk memory * This will run in the low memory expansion NOTME LI R12,>1300 CRU address of the card |