The RS232C/PIO interface card

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
Theory of operation
Sample programs

The serial ports
Theory of operation
__Simplex. duplex, half-duplex
__Null-modem connections

The TMS9902
Operating the TMS9902
_Control register
_Interval register
_Rate registers
_Error detection
_Transmission monitoring
Sample program
Electrical characteristics
Timing diagrams

CRU interface
CRU map

Card ROMs
The power-up routine
The DSRs

Parallel port

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
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)
11 - Ground -
12 O +5V via 10 Ohm -
13 I SPAREIN CRU bit 3
15 O +5V via 1 KOhm -
16 - Ground -

Theory of operation

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
+----------+ +----------+ PE-Box bus
A12>----------| S0 Q0 |----->ROMSEL nc-| A0 S0 |---------<A14
A13>----------| S1 Q1 |-----+PIOOUT-----------| A1 S1 |---------<A13
A14>----------| S2 Q2 |---+ | +---------| A2 S2 |---------<A12
| Q3 |---|+| |+--------| A3 |
A15>----------| D Q4 |---|||-------||--------| A4 |
| Q5 |---|||-CTS1--||--------| A5 |
CRU_O*>-| EN* Q6 |---|||-CTS2--||--------| A6 |
| Q7 |---|||-LED+--||--------| A7 Y |---------->CRUIN
RESET*>-------| RST* | ||| | || | |
| | ||| | || CRU_I*-| EN* |
+----------+ ||| | || +----------+
||| | || PIO Port
LED 2N3904 +||----|--||------------------------>HANDSHAKEOUT
+5V --->|----\| +|----|--||------------------------>SPAREOUT
|-------------|----' +|---+---------UUU--------<HANDSHAKEIN
/| | | | 10 nF
| | | +---||--- Gnd
Gnd | |
| +---+---------UUU--------<SPAREIN
| | 10 nF
| +---||--- Gnd
PE-Box bus 74LS245 | 74LS373
+----------+ | +----------+
DBIN--------| DIR | +-------| OE* |
| | LATCHOUT---| CK* | PIO Port
D0----------| A0 B0 |---+-----------| D0 Q0 |----------+---PIO0
D1----------| A1 B1 |---|+----------| D1 Q1 |---------+|---PIO1
D2----------| A2 B2 |---||+---------| D2 Q2 |--------+||---PIO2
D3----------| A3 B3 |---|||+--------| D3 Q3 |-------+|||---PIO3
D4----------| A4 B4 |---||||+-------| D4 Q4 |------+||||---PIO4
D5----------| A5 B5 |---|||||+------| D5 Q5 |-----+|||||---PIO5
D6----------| A6 B6 |---||||||+-----| D6 Q6 |----+||||||---PIO6
D7----------| A7 B7 |---|||||||+----| D7 Q7 |---+|||||||---PIO7
+----------+ |||||||| +----------+ ||||||||
|||||||| 74LS373 ||||||||
|||||||| +----------+ ||||||||
+-----------| Q0 D0 |----------+
+----------| Q1 D1 |---------+
+---------| Q2 D2 |--------+
+--------| Q3 D3 |-------+
+-------| Q4 D4 |------+
+------| Q5 D5 |-----+
+-----| Q6 D6 |----+
+----| Q7 D7 |---+
PIOIN-------| EN* |
LATCHIN-----| CK* |

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.

Sample programs

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. 
* It assumes the card CRU is in R12 (>1300).
PIOUT  MOV  R11,R10        Save return point
SBZ 1 Set PIO port as output
LP1 TB 2 See if receiver is ready (HANDSHAKEIN low)
JNE SK1 Yes, it is
BL @CLEAR Test <Clear> key, abort if pressed
JMP LP1 Keep waiting
SK1    MOVB R0,@>5000      Send the byte 
SBZ 2 Set HANDSHAKEOUT low to signal a byte was sent
LP2 TB 2 Wait till acknowledged (HANDSHAKEIN high)
BL @CLEAR Test <Clear> key, abort if pressed
JMP LP2 Keep waiting
SK2    SBO  2              Reset HANDSHAKEOUT as high 
B *R10

The other computer would run the corresponding routine:

* This routine receives 1 byte through PIO
* The byte will be placed in R0
* It assumes the card CRU is in R12 (>1300)
PIOIN  TB   2              Wait for HANDSHAKEIN to be high      
JEQ SK0 OK: sender is ready
BL @CLEAR Not yet: test <Clear> key, abort if pressed
JMP PIOIN Keep waiting
SK0    SBO  1              Set PIO port as input
SBZ 2 Set HANDSHAKEOUT low: we're ready to receive
LP1 TB 2 Did HANDSHAKEIN become low?
JNE SK1 Yes, it did
BL @CLEAR Test <Clear> key, abort if pressed
JMP LP1 Keep waiting
SK1    CLR  R0             The emmiter signaled a byte was sent 
MOV @>5000,R0 Get byte
SBO 2 Acknowledge reception
B *R10 Return

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
SENDER LI R1,NBYTES Ptr to string of bytes to send
MOV *R1+,R2 First word is the # of bytes
BL @PIOUT Send the first half of it
BL @PIOUT Send the second half

LP1 MOV *R1+,R0 Get a byte
BL @PIOUT Send it
JNE LP1 Next byte
... Done
NBYTES DATA >0123         Number of bytes to send 
BYTE ..... Bytes to be sent
*This routine receives a bunch of bytes
RECVER BL @PIOIN Receive # of bytes
MOV R0,R2 (passed as 2 bytes)
BL @PIOIN Get the second half
SWPB R2 Make it a word
* CI R2,512 (Optional) check if we have room enough
* JH ERROR4 If not issue "memory full" error
MOV R2,@SIZE Save size
       LI   R1,BUFFER     Ptr to reception buffer
LP2 BL @PIOIN Receive a byte
MOVB R0,*R1+ Save it
JNE LP2 Next byte
... Done
SIZE   DATA 0             Number of bytes that will be received 
BUFFER BSS 512 Reception buffer

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"

Serial ports

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.

Theory of operation

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.

Signal encoding

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

Data bits

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.

Start bit

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?

Stop bit

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.

Parity bit

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.

Transmission rate

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.

Transmission protocols

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.

Simple vs duplex

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
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

Null-modem connections

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
RTS---, ,---RTS
CTS<--' '-->CTS
DCD<--, nc ,---DCD
DTR---+ nc +-->DTR
DSR---' '---DSR
    ( 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

The TMS9902

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*
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.

Operating the TMS9902

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:

CRU bit 14: Set as 1 to load the Control register. Then load 8 bits, starting with CRU bit 0.
CRU bit 13: Set as 1 to load the Interval register. Then load 8 bits starting, with CRU bit 0.
CRU bit 12: Set as 1 to load the Reception Rate register. Then load 11 bits, starting with CRU bit 0.
CRU bit 11: Set as 1 to load the Emission Rate register. Then load 11 bits, starting with CRU bit 0
When bits 11-14 are all 0, the emit register will be accessed: load upto 8 bits, starting with CRU bit 0.
Each time you load the last bit of a register, the address changes so that the next loading operation will access the next register. Thus, if we first set bit 14 we can load all registers at a turn, with five successive LDCR instructions: first the control register, then the intervall register, then the two rate registers, and finally the emission register. (Note: You can load both rate registers with a single LDCR operation, by setting both bit 12 and 11 to 1. The ROM routines in the TI card make use of this feature).

Control register

This register is used to set various transmission parameters.

Bit # 7 6 5 4 3 2 1 0
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.

Emission/Reception rate registers

These registers are used to specify the speed of transmission for reception:

Bit # 10 9 8 7 6 5 4 3 2 1 0
Meaning Div by 8 Frequency divider

And for emission:

Bit # 10 9 8 7 6 5 4 3 2 1 0
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.

Frequency calculation

  • The TMS9902 uses the PHI3 clock signal from the console as its main frequency. The big drag is that there were two types of TI-99 consoles: some have 3 mHz clocks, others have 2.5 mHz clocks. The latter are pretty rare, but still we cannot assume that the clock will always be 3 MHz, we must check for that. Fortunately, the clock speed is specified in the console ROM, at byte >000C: if this byte is >30 the console has a 3 MHz clock, if it's >28 the clock is 2.5 MHz.
  • The TMS9902 divides this signal by 2 to derive its main frequency.
  • If the CLK4M bit is set in the control register, the main frequency is further divided by 4, otherwise it is divided by 3. This comes handy to produce some odd rates that are not powers of two.
  • If the RDV8 (or XDV8) bit is set in the rate register, the frequency is further divided by 8.
  • Finally, the frequency is divided by the number specified in the rate register, to get the transmission speed.
  • 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

    Interval register

    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

    Error detection

    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

    Transmission monitoring

    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.

    Sample programs

    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
    LI R12,>1300 CRU address of first interface card (use >1500 for the second card, if any)
    SBO 0 Turn card on
    AI R12,>0040 Address of first TMS9902 (use >0080 for the second chip)
    SBO 14 We want to load the control register
    LI R0,>0800 Divide Phi3 by 4
    ORI R0,>3000 Odd parity
    ORI R0,>0300 8 bits per char
    ORI R0,>8000 1 stop bit
    LDCR R0,8 Load the control register
    SBO 12 Let's skip the interval register
    LI R0,>009C This boils down to 2400 bps
    LDCR R0,11 Load the reception rate register
    LDCR R0,11 Load same value in the emission rate register
    B *R11

    Electrical characteristics

    Absolute maximum ratings

    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

    Recommended operating conditions

    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

    Characteristics under recommended conditions

    Parameter Test conditions Min Typ Max Unit
    High-level output voltage I= -100 uA
    I= -200 uA
    . Vcc
    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

    Timing diagrams

    CRU output

        |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
    | >180 ns |
    a) 0.45 - 0.55 Tc
    b) 0.45 - 0.55 Tc
    c) >0.37 Tc

    CRU input

    ____/ \ / \_____/ \_____/ \________ PHI*
    __ __
    X| bit address n | bit address n+1 |XXXX
    CRU address
    | <260 ns |
    \ <240 ns | CE*
    | CRUCLK
    __ | __


    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
    +------------+ 75189 330 Ohm 4.7K
    A10>----------|S0 RIN |-------o<|----www------------------------------<RD-1
    A11>----------|S1 CTS*|---, 75189 330 Ohm ,------www-----+5V
    A12>----------|S2 DSR*|---+---o<|----www------+-----------------------<DTR-1
    A13>----------|S3 | '-------||-----Gnd
    A14>----------|S4 | ,--www--+5V 36K 10 nF
    A15/CRUOUT>---|CRUOUT | | 1.8K ,--www---,
    CRUCLK>-------|CRUCLK | | 10K | |\ | 150 Ohm 150 Ohm
    CRUIN<--------|CRUIN XOUT|--+----www------+---|-\__+---www---+---www----->TX-1
    | | ,----|+/ '---||--Gnd
    SEL*---------|CE* | | |/TL084 10 nF
    | | ,--www--+5V | 36K
    PHI3*>--------|PHI* | | 1.8K | ,-www---,
    +--|INT* | | 10K | | |\ | 150 Ohm 150 Ohm
    | | RTS*|--+----www-----|-+--|-\__+---www---+---www----->DCD-1
    | +------------+ +----|+/ '---||--Gnd
    | | |/TL084 10 nF
    INT* |______74LS21 |
    on other +5V===|)--+ 200 Ohm | 200 Ohm
    TMS9902-------' | Gnd----www----+--------www---+5V
    | |
    EXTINT*<--------------<|---Gnd | |\TL082
    74LS125 10K '----|+\__+---www---+---www----->CTS-1
    Cru bit 5-------------------+----www------+---|-/ | 150 Ohm | 150 Ohm
    (6 for CTS-2) | | |/ | '---||--Gnd
    | 1.8K '--www---' 10 nF
    '--www--+5V 36K 1.8K

    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.

    CRU map

    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 card ROM

    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.

    The power-up routine

    This is an extremely simple routine. All it does is:

    The DSRs

    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:

    >8356 points to the first character after the DSR name in the VDP memory. This often will be a decimal point.
    >8354 contains the size of the DSR name, not counting any decimal pont.
    R12 (>83F8) contains the CRU of the card being called. This information is also saved in word >83D0
    R2 (>83E4) contains the address of next link in the chain of DSR routines. This information is also saved in word >83D2
    R9 (>83F4) contains the address of the routine being called.
    R11 (>83F6) contains the return address to the scanning routine
    R1(>83E2) contains the number of time a routine with that name was called. Normally it is 1.

    Several modifiers can be appended to the DSR name, using decimal points as separators:

    .BA=xxx Where xxx is the transmission speed. Legal values are: 110, 300, 600, 1200, 2400, 4800 and 9600.
    .DA=n Where n is the number of bits per word. Accepted values are 7 and 8.
    .TW Causes two stop bits to be used. Otherwise only 1 is issued. No provision is made to use 1.5 stop bit, although the TMS9902 would allow for it.
    .CH Check for parity upon reception.
    .PA=c Parity type. Where c is N: no parity, E:even parity or O: odd parity.
    .CR Prevents a character 0d (CR) from being appended to each record sent.
    .LF Prevents a character 0a (LF) and 0d (CR) from being appended to each record sent.
    .EC Echo off. When echo is on each character received will be sent back to the sender.
    .NU Append 6 null characters (>00) to the end of Dis/Var records.

    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.

    Interrupt (>80)

    Open (>00)

    Close (>01)

    Read (>02)

    Write (>03)

    Save (>06)

    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.

    Load (>05)

    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.
    * R6 contains the byte received/emmited.
    * The CRC value is in R9, it is initialised as >FFFF (SETO R9)
    DOCRC  MOV  R6,R1         
    ANDI R1,>FF00 Mask irrelevant bits
    XOR R1,R9 XOR with CRC value
    MOV R9,R1
    SRL R1,4 Shift by 4
    XOR R9,R1 XOR with new CRC value
    ANDI R1,>FF00 Mask irrelevant bits
    SRL R1,4 Shift by 4
    XOR R1,R9 XOR with same CRC value
    SRC R1,7 Shift by 7 (circular)
    XOR R1,R9 XOR with new CRC value
    SWPB R9 Shift by 8 (circular)
    B *R11

    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.

    Serial reception routine

    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
    * It sssumes the port was properly set, and R12 contains the CRU base
    * of the appropriate TMS9902 (i.e. >1340 or >1380)
    REC1BY MOV  R11,10           Save return point
    LP1 TB 27 Test DSR pin (i.e. DTR on the connector)
    JNE SK1 Line is high
    TB 21 Line is low: test receive buffer
    JEQ SK2 A byte is ready
    SK1 BL @CLEAR Check <Clear> key, abort if pressed
    JMP LP1 Else keep waiting
    SK2    CLR  R6               A byte was received
    STCR R6,8 Get it
    SBZ 18 Reset buffer bit 21 (use SBO 18 to allow interrupts)
    TB 11 Was there an overflow?
    JEQ ERROR6 Yes: I/O error #6
    TB 12 Was there a frame error (stop bit read as 0)?
    JEQ ERROR6 Yes
    MOVB @PARCHK,R11 Flag: shall we check parity?
    JEQ SK3 No
    TB 10 Yes: was there a parity error?
    JEQ ERROR6 Yes
    SK3 B *R10 Return

    Serial emission routine

    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.
    * It sssumes the port was properly set, and R12 contains the CRU base
    * of the appropriate TMS9902 (i.e. >1340 or >1380)
    EMI1BY MOV  R11,R10          Save return point
    LP2 SBO 16 Set RTS pin low (DCD on the connector)
    TB 27 Test DSR pin (DTR on the connector)
    JNE SK5 It's high: no answer yet
    TB 22 It's low: is emission buffer empty yet?
    JEQ SK6 Yes: send previous byte now
    SK5 BL @CLEAR Test <Clear> key, abort if pressed
    JMP LP2 Keep waiting
    SK6    LDCR R6,8             Load next byte in emission buffer
    SBZ 16 Set RTS/DCD high (effective once buffer is empty)
    B *R10 Return

    The expected connections are as follow:

          Sender                           Receiver
    TMS9902 Connector Connector TMS9902
    XOUT----->TX ----------------------->RD------>RIN
    DSR*<--, '-->CTS*
    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 ISR

    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
    SETIMR LI R12,>1340 Address of first TMS9902 on card 1
    SBO 31 (Optional) Reset it
    LI R0,>xxxx Delay = >xxxx * 64 * 333 ns (for a 3 MHz console)
    SBO 13 Next load will be in interval timer
    LCDR R0,11 Load value
    SBO 20 Enable timer interrupts
    B *R11
    * ISR installed in Ramdisk memory
    MYISR LI R3,TIMISR Points to timer ISR
    LI R4,>2000 Where to put it, in memory expansion
    LI R5,ENDISR-TIMISR Size in words
    LP0 MOV *R3+,*R4+ Copy ISR to low memory expansion
    DECT R5
    JNE LP0
    B @>2000 Branch to it
    * This will run in the low memory expansion
    TIMISR SBZ 0 Turn Ramdisk off
    MOV R12,R10 Save for return
    LI R12,>1300 CRU address of the card
    SBO 0 Turn card on
    LI R12,>1340 CRU address of the first TMS9902
    TB 31 Does the interrupt come from this chip?
    JNE NOTME Nope
    TB 19 (Optional) Was it a timer interrupt?
    JNE NOTME Let the RS232 card ISR deal with reception interrupts
    TB 24 Was there an overflow (missed interrupt)?
    JEQ ERROR2 Do something if more time than expected has elapsed
    ... Do what needs to be done when timer fires
    SBO 20 Reset flag (by enabling timer interrupts again)
    * C   *R11+,*R11+ Do not scan other cards (must do SBZ 0 ourselves)
    NOTME  LI   R12,>1300          CRU address of the card
    SBZ 0 Turn interface card off
    MOV R10,R12 Restore old CRU base
    SBO 0 Turn ramdisk back on
    B *R11 Leave ISR

    Preliminary version. 3/8/99. Not for release
    Revision 1. 5/31/99 Checked serial subroutines, ok to release
    Revision 2. 6/16/00 Got the TMS9902A manual! Added timing + electical characteristics, corrected pinout.
    Revision 3. 8/28/00 Added a note on the ISR bug.
    Revision 4. 8/26/01 Added links to pictures page.
    Revision 5. 5/2/03 Corrected a typo: bit PIOOUT should be '0' for output.

    Back to the TI-99/4A Tech Pages