TI-99/4A Computer

Page Contents

small arrow Description
small arrow Cortex BASIC
small arrow TI Invaders - Disk Version
small arrow Miscellaneous Programs
small arrow "The Valley" Adventure Game
small arrow EPROM Programmer
small arrow Compact Flash Drive Utility
small arrow Power Supply Consumption
small arrow 32K Memory Expansion in Console
small arrow Mini Memory Module Line-By-Line Assembler Bug
small arrow Cartridge Bank Switching
small arrow Resurrecting a TI Disk System
small arrow Connecting a Serial Mouse
small arrow Internet Web Browser
                   Rob Tempelmans Plat award logo
Honoured to receive the Rob Tempelmans Plat award in 2013


The Texas Instruments TI-99/4A was an early home computer, released in June of 1981. The computer holds the distinction of being the first 16-bit personal computer, having a 16-bit TMS 9900 CPU running at 3.0 MHz.

TI-99/4A photo For a full description of the system, see this comprehensive Wikipedia entry. TI-99/4A start screen

Cortex BASIC

This is a port of the 'Power BASIC' interpreter used with the TMS9995-based Powertran Cortex machine. Features and constraints are as follows:

Code for direct control of the RS-232 and parallel port hardware (bypassing use of the DSRs) is also available, but including this will obviously reduce the amount of RAM available for program storage.

Program Download

Various download options are available, depending on how you intend to run the program. For each option, there is also the choice of whether to download the 40-column or 80-column text version. Download links are in the table below, with a description of the various options below that.

Text Version Disk Image 32K EPROM Image,
Inverted Bank Switching 
32K EPROM Image,
Non-Inverted Bank Switching 
512K EPROM Image,
Non-Inverted Bank Switching 
40-column Cortex BASIC 40 v1_7.zip  Cortex BASIC 40 32K
Inverted v1_7.zip
Cortex BASIC 40 32K
Non-Inverted v1_7.zip
Cortex BASIC 40 512K
Non-Inverted v1_7.zip
(requires F18A)
Cortex BASIC 80 v1_7.zip  Cortex BASIC 80 32K
Inverted v1_7.zip
Cortex BASIC 80 32K
Non-Inverted v1_7.zip
Cortex BASIC 80 512K
Non-Inverted v1_7.zip

Disk Image

The program is on a TiDisk format image and named "CBASIC". This disk image format works with all the simulators listed above (F18A support needed for the 80-column version) and with the CF7+/NanoPEB device on real hardware. To run the program, boot with the Editor Assembler cartridge, select E/A option 3 and type the filename DSKn.CBASIC. The program should load and display the *Ready prompt within a few seconds.

With the disk versions, the size of the code means that only about 5K of RAM is available for your program and variable storage. Programs are stored in a tokenised format so what free memory there is is used quite efficiently.

32K EPROM Image, Inverted Bank Switching

This is a binary image which can be used as follows:

With the bank-switched cartridge versions, some of the code is copied from the cartridge to RAM, and 8K is left running from the cartridge, so just over 14K of RAM is available for your program and variable storage.

32K EPROM Image, Non-Inverted Bank Switching

This is a binary image which can be used as follows:

As with the 32K EPROM image version above, just over 14K of RAM is available for your program and variable storage.

512K EPROM Image, Non-Inverted Bank Switching

This is a binary image which can be used as follows:

As with the 32K EPROM image versions, just over 14K of RAM is available for your program and variable storage.


The Cortex BASIC user guide is available which details the Cortex BASIC implementation. This document was recreated by Jim Fetzner from a poor quality photocopy.

Keyboard Mapping

Cortex Key Console Key Classic99 (PC Keyboard) Key  
Enter ENTER Return
Edit CTRL-T Ctrl-T
Clear CTRL-L Ctrl-L
Escape FCTN-9 (BACK) Esc or Alt-9
Insert FCTN-2 (INSERT Insert or Alt-2
Delete FCTN-1 (DELETE)   Delete or Alt-1
Rubout FCTN-3 (ERASE) Alt-3
Cursor Up FCTN-E Cursor Up
Cursor Down FCTN-X Cursor Down
Cursor Left FCTN-S Cursor Left
Cursor Right   FCTN-D Cursor Right
Home CTRL-H Home

Sample Program Listings

Some sample program listings can be downloaded here. See the @Program Descriptions.txt file within the zip file for program descriptions.

All these programs were typed in or developed on my TM990 system but many will run on the TI-99/4A without modification. Those that interact with the hardware such as the SID chip will require modification to run on the TI-99/4A - I leave these for the reader to experiment with. You will have to work out a way to enter these listings on your system if you don't simply want to type them in (hint: if you're using the Classic99 simulator, copy the text listing to the clipboard then use the Edit > Paste command to 'type' them in).

Most of these programs will require you to use a cartridge version of Cortex BASIC (the 40-column version is probably best to match the original display) - there won't be enough RAM available if you use the disk version.

Changes and Restrictions in the TI Implementation

There are some changes and restrictions in the TI implementation as compared to the implementation described in the Cortex user guide. Note that some commands work differently to their familiar equivalents in TI BASIC. If you think you've found a bug in the interpreter code, download the Cortex emulator (written by David Hunter) and check your program on there. You can report any confirmed bugs to me at ti99(at)stuartconner(dot)me(dot)uk.

1.0 - 29 Jan 2011 - Initial release.
1.1 - 10 Feb 2011 - Added load/save functionality.
1.2 - 17 Feb 2011 - Moved most processor workspaces to 16-bit 256-byte console RAM to increase speed.
1.3 - 01 Apr 2011 - Improved key handling with the KEY() function, which would previously miss some key strokes. To check for keys pressed in a game for example, call NOESC at the start of the program, then A=KEY(0) to check for a key press, and IF A=01BH THEN STOP as part of the key handling routine to enable <Escape>ing out of the program.
1.7 - 05 Dec 2015 - Refactored to enable code to build image for either E/A option 3 file or TI-99/4A bank-switched cartridge.

TI Invaders - Disk Version

This is a disk version of the TI Invaders cartridge, created from an original program listing. The game can be downloaded here (Invaders.zip, V9T9/.tidisk format). The program name is INVADERS - load using Editor/Assembler option 3. The program auto-starts on loading.

The game controls are 'S' and 'D' to move left and right, and '.' to fire.

The program supports the cheat mode, which is accessed when on the title screen by pressing <Shift>838. This prompts for a Y/N to play slow speed then two digits for the number of the screen to start on. None of these inputs will be displayed, and it is not necessary to press <ENTER>.

Miscellaneous Programs

Disk: Miscellaneous.zip (V9T9/.tidisk format)

Programs with names in black are written/generated by me. Programs with names in red are copied from other sources.

CATALOG BASIC disk catalogue program from TI disk drive manual.
Compact Flash Drive Volume Manager.
E/A 5 format.
CONV_BASES Subroutine (for MERGE-ing into other programs) to convert a binary, decimal or hexadecimal number to binary, decimal or hexadecimal. Lines numbered from 10000.
DISASSMBLR TMS 9900 assembly language disassembler, running under TI Extended Basic with 32K RAM.
Source and object files for assembly language utility to print to the screen in text mode (40 characters  24 lines).
DM-1000 (V3.5) Disk Manager (copied from Funnelweb).
E/A 5 format.
Source and object files for assembly language program to save/load the contents of the SuperCart RAM (>6000 - >7FFF) or MiniMem RAM (>7000 - >7FFF) to/from a data file. Also has an option to fill the RAM with zeroes. Load using E/A option 3 or MiniMem option 3, 1. Program auto-starts on loading and detects the cartridge type. Requires 32K RAM expansion to be present.

The following RAM image files can be loaded using the CART_RAM/O program:
SC_START Data file containing the RAM image for the SuperCart startup menu.
MM_LBLA Data file containing the RAM image for the MiniMem Line-By-Line Assembler (LBLA). Program names: OLD, NEW.
MM_LINES Data file containing the RAM image for the MiniMem Lines demonstration program. Program name: LINES.

To load and run the MiniMem LBLA for example:

(1) Download the Miscellaneous.zip file from the link above, unzip it, and make a disk out of the file. (In the case of a CF7+/nanoPEB device, copy the file onto the CF card using the PC and the file transfer utilities provided with the device.)

(2) Insert the MiniMem cartridge, then from the title screen select option 3 then option 1, and specify file DSK1.CART_RAM/O (substitute DSK1 for whichever disk drive you're using). If this gives a Memory Full or Duplicate Definition error, reset the console, select option 3 then option 3 then press the <Proceed> key to reinitialise the MiniMem RAM, and start again.

(3) When the program loads and runs, select option 2 and specify file DSK1.MM_LBLA. When this has loaded, select option 4 to reset the console.

(4) From the title screen, select option 3 then option 2, and specify program name NEW. The LBLA screen should now be displayed ready for input.

RD_REF_TBL BASIC program to display the contents of the REF/DEF table when running with the Extended Basic, Editor/Assembler or MiniMem modules.
SYSTEX XB/assembly language hybridisation program. Version 1.0, 1985, Barry Boone. Allows you to save assembly language programs/utilities as part of XB program files.
XBOPT5 E/A 5 format loader for Extended Basic. Version 3.1, 1987, Barry Boone. To use, make a copy of the program, then edit line 110 to specify the program to load.

"The Valley" Adventure Game

"The Valley" was an adventure game published as a "type in" program listing in the UK Computing Today magazine, April 1982. It was originally developed for the Commodore PET but the "universal BASIC" listing enabled it to be easily adapted to other micros of the time such as the Tandy TRS-80, Oric and Sharp MZ-80K. You can read more about the history of the program here and here.

Choose your character type carefully ... Barbarians recover quickly but their magic doesn't come easily. A Wizard? Slow on the draw and slow to mature ... but live long enough and grow wise enough and your lightning bolts are almost unstoppable ...

The Valley is a real-time game of adventure and survival. You may choose one of five character types to be your personal 'extension of self' to battle and pit your wits against a number of monsters. Find treasure, fight a Thunder-Lizard in the arid deserts of the Valley, conquer a Kraken in the lakes surrounding the dread Temples of Y'Nagioth, or cauterise a Wraith in the Black Tower. In fact, live out the fantasies you've only dared dream about. BUT BEWARE ... more die than live to tell the tale!
Computing Today Magazine Cover

A version of the game to run on the TI-99/4A can be downloaded here (The_Valley.zip, V9T9/.tidisk format). The program requires XB and 32K memory.
To run the program, load and run the file RUN (OLD DSK.THE_VALLEY.RUN). Program developed and tested on a console with 32K  16 bit RAM and CF7+ Compact Flash Drive. Tested on the Win994a simulator.

The Valley - splash screen The Valley - main screen The Valley - Black Tower of Zaexon

EPROM Programmer

This project is an EPROM Programmer to read and program TMS 2708 (1K  8) and TMS 2716 (2K  8) EPROMs Note 1. It was developed to program EPROMs for my TM 990 system. The circuit is based around a TMS 9901 Programmable Systems Interface IC which is used to apply address, data and control signals to the EPROM. A control program, written in assembly language, enables the user to view the data on an EPROM as both hex and ASCII, to verify that an EPROM is blank, to save the data from an EPROM to a data file, to save an EPROM image in memory to a data file, to program an EPROM from a data file, and to compare the data on an EPROM with a data file.

The board is designed to plug into the TI-99/4A console side port via a Y-cable (side port splitter), which allows the PEB or other storage device to also be connected at the same time Note 2. The board requires a +12V supply, which can be provided by fitting a DC power socket to the side of the console, wired directly to the +12V supply from the internal power supply board.

A circuit diagram of the EPROM Programmer is available here. The object code for the control program Note 3 is available here (disk in V9T9 format; load program EPROM/O using Editor/Assembler option 3). Screenshots of the control program in use are shown below - click each for a larger image.
EPROM Programmer PCB

EPROM Programmer - menu EPROM Programmer - verify EPROM is blank EPROM Programmer - view EPROM data EPROM Programmer - programming EPROM

Note 1: Texas Instruments TMS 2716 EPROMs have a different pinout and programming requirements from 2716 EPROMs from other manufacturers. The board is compatible with TMS 2716 EPROMs from Texas Instruments only.

Note 2: The board and software have been developed using a TI-99/4A with a CF7+ Compact Flash Drive for program storage. It has not been tested with a PEB attached, but there is no reason why it should not work. Note also that I found the CF7+ Compact Flash Drive a bit temperamental connected to the Y-cable as supplied. I ended up shortening the 'arm' of the cable that the CF7+ was connected to, and it then worked perfectly. Positioning the two arms of the Y-cable at 90° to each other may also be sufficient for reliable operation.

Note 3: The algorithm to program an EPROM contains some timing loops which control the application of the +26V programming pulses. The object code compiled here is for a TI-99/4A with 32Kbyte RAM on the internal 16-bit data bus. If the program is to be used on a console with external 32Kbyte RAM (on a CF7+ or in the PEB for example), these programming pulses will be extended (as the console is running slower), but an EPROM will probably still program reliably.

Compact Flash Drive Utility

This CF To Disk Transfer Utility helps you copy disk volumes between a CF card and your hard drive. It is in essence a graphical front end to the cf2dsk.exe and dsk2cf.exe programs supplied with the TI-99/4A Compact Flash Drive. Full details are available in this readme file (including how to run the utility and the cf2dsk.exe and dsk2cf.exe programs under Windows 7).

(The files originally provided with the CF7+ drive can be downloaded here or from the designer's website.)
CF To Disk Transfer Utility screenshot

Power Supply Consumption

The console motherboard, fitted with the TI Extended BASIC cartridge, draws the following current from the power supply board:

On the 'standard' power supply board, the +5V regulator is a switched mode design, and the +12V and -5V regulators are of the linear type.

32K Memory Expansion in Console

This modification adds 32K RAM to the console, internally on the 16-bit data bus. The RAM occupies the same address ranges as the 32K RAM expansion card normally fitted in the Peripheral Expansion Unit: >2000 - >3FFF and >A000 - >FFFF. The modification disables the wait state generation circuit when the RAM is accessed, making it faster than the RAM in the external 32K RAM expansion card.

The circuit diagram for the modification is shown below. The circuit diagram shows the original console components and connections in grey, with the modifications shown in black. A photo of the completed modification is shown below the circuit diagram.

The modification uses two 128Kbyte CMOS RAMs (NEC type UPD431000ACZ-70, equivalent parts should work). 24 pins of these 32 pin devices are largely pin-for-pin compatible with the existing console ROMs U610 and U611, which makes it easy to piggy-back them on the ROMs with the non-compatible pins and the extra pins bent out and wired individually. The extra address lines and the /WE signal are taken directly from the processor.

Existing 3-to-8 line decoder U504 provides convenient address decoding for the address space to be occupied by the RAM. A quad AND 74LS08 device is used to combine the chip select lines to drive the RAM /CS and /OE lines. A switch (fitted on the console back panel) and 1K pull up resistor on these lines enables the RAM expansion to be disabled, if required. The 74LS08 device is piggy-backed on another 14 pin LS device on the board for the +5V and GND supplies, with the other pins wired individually.

The existing console ROMs and scratchpad RAMs are fitted on the 16-bit data bus, and the existing circuit which includes NAND gate U606 pins 11/12/13 is used to disable the wait state generation circuit when the ROMs or scratchpad RAMs are accessed (wait states are introduced for all memory accessed through the console 16-to-8 bit data multiplexer). The same circuit is used to disable the wait states for the 32K expansion RAM by ANDing the ROM chip select with the RAM chip select, and feeding this to U606/13. This pin is physically cut to isolate it from the ROM chip select routed through the board (alternatively, the relevant trace on the board could be identified and cut), and the new ROM/RAM chip select wired to it.

32K RAM expansion in console circuit diagram

32K RAM expansion fitted in console
(Ignore the grey cable on the left - that's a reset switch)

Mini Memory Module Line-By-Line Assembler Bug

The Line-By-Line Assembler (LBLA) provided on cassette tape with the TI-99/4A Mini Memory module contains a bug, whereby if assembling instructions with two symbolic addressing operands (for example MOV @L1,@L2), and both operands are unresolved references (that is, L1 and L2 in the previous example have not yet been defined), the first instruction will be assembled correctly, but further instructions will not as the label for the second operand is not added to the symbol table, and hence is not 'filled in' when the label is defined later.

The bug is in a small loop starting at address >7248 that clears memory addresses >7194 - >719F after assembling each instruction. This loop also needs to clear one extra word (address >71A0) as this word is used when assembling an instruction to record whether the second operand is an unresolved symbolic reference or not. The bug can be fixed by simply increasing the loop counter by 1, as follows:

  1. Load the LBLA in the normal way using Easy Bug.
  2. After loading, while still in Easy Bug, type M724F followed by the <Enter> key.
  3. The value 06 should be displayed. Type 07 followed by the <Enter> key.
  4. Press the '.' key to return to the Easy Bug prompt.
  5. Type S7000 followed by the <Enter> key to save the corrected program back to cassette tape.

A .wav file of the LBLA cassette can be downloaded here.

Cartridge Bank Switching


Software cartridges can contain up to 8Kbyte of ROM, which is decoded in the console to appear in the TI-99/4A memory map address range >6000 - >7FFF. A bank-switching technique can be used in a cartridge to 'page' different banks of a larger ROM into the >6000 - >7FFF address space. Such a cartridge requires additional hardware (self-contained in the cartridge) to control the paging. This can then be used in the following ways:

Implementing Bank Switching in a Cartridge

One method of implementing bank switching is to latch one or more low-order address lines when writing to the cartridge, and to apply these latched bits to the ROM high-order address lines to select different 8Kbyte banks. By writing to different specific addresses, different binary codes can be latched from the low-order address lines, and hence different binary codes can be applied to the ROM high-order address lines to select specific 8Kbyte banks. Writing to a ROM obviously has absolutely no effect on the data stored in the ROM. A circuit diagram of such an implementation is shown below, followed by a detailed description of how the circuit works when selecting a ROM bank. The circuit is from Jon Guidry, who has continued the design work of others and has had PCBs manufactured and cartridge kits made up for home assembly.

Bank switching circuit diagram

The circuit uses a 74LS379 device to latch one or more low-order address lines. For a 16Kbyte ROM, one address line needs to be latched, which gives 2 banks of 8Kbyte. For a 32Kbyte ROM, two address lines need to be latched, which gives 4 banks of 8Kbyte, and so on. The latch is clocked by the WE* line and enabled by the ROMG* line so that the device latches the address lines only on write operations to the >6000 - >7FFF address range. The ROM high-order address lines are fed from the latch Qx* outputs; there appears to be no particular reason why the latch's inverted outputs are used - it is possibly for compatibility with earlier designs. Processor address line A15 is not latched because of the word-based nature of the processor - A15 is toggled both high and low during a single memory write operation.

Selecting a ROM Bank

To select a ROM bank, a word of data (any data, it doesn't matter) has to be written to a specific address in the >6000 - >7FFF address range. At circuit level, this is what happens:

  1. The processor sets up the write address on its address bus A0 - A15. (It also sets the data up on the data bus but that is irrelevant at the moment.) It also brings its MEMEN* signal low - this is not shown in the circuit diagram, but that action in the console brings the ROMG* signal low (assuming the write address is an address in the cartridge address space), so the 74LS379 latch is now 'enabled' - meaning it is outputting signals on its Qx and Qx* outputs and will respond to the clock input.
  2. With the address bus now set up, the processor pulses WE* low. This clocks the 74LS379, so whatever binary code the device is seeing on its Dx inputs it presents on it Qx outputs, with inverted signals on the Qx* outputs. Each time the 74LS379 is enabled in the future, this same binary code will be presented on the outputs again until the device is clocked again to latch a (possibly) different binary code.
  3. The processor now brings MEMEN* high, which brings ROMG* high to disable the 74LS379, and performs the next instruction.

When an address in the cartridge ROM is read, the processor sets up the read address on the address bus A0 - A15, and brings *MEMEN low which brings *ROMG low. This enables the 74LS379, so as described before, it presents the address bits last latched (clocked) on the Qx* outputs. The ROM is now seeing 13 address lines from the console (which select an address within the 8Kbyte address space), plus another one or more address lines from the 74LS379 (which select an 8Kbyte bank within the ROM). These address lines are addressing a specific byte in the ROM, and as the ROM CS* line is low (because it is connected to ROMG*) the ROM outputs the data stored at that address to be read by the processor.

To select a ROM bank, a word of data has to be written to a specific address in the >6000 - >7FFF address range. To calculate the address to write to to select a particular bank, the following logic applies:

  1. Consider a 64Kbyte ROM which uses 3 latched address lines, giving 8 banks. Number the banks 0 - 7.
  2. Take the number of the bank required, in binary, and invert it (because the circuit uses the 74LS379 inverted Qx* outputs rather than the Qx outputs). So bank 0 is 111, bank 1 is 110, bank 2 is 101, and so on.
  3. Specify the inverted number of the bank required on processor address lines A12 - A14. Also, address lines A0 - A2 have to be 011 to select the cartridge space >6000. So, address:

<-A0          A15->
011x xxxx xxxx 111x will select bank 0
011x xxxx xxxx 110x will select bank 1
... up to ...
011x xxxx xxxx 001x will select bank 6
011x xxxx xxxx 000x will select bank 7

'x' can be either a 1 or a 0 - it doesn't matter to the latch - but for convention assume they are 0.

So, writing to address:

<-A0          A15->
0110 0000 0000 1110 = >600E will select bank 0
0110 0000 0000 1100 = >600C will select bank 1
... up to ...
0110 0000 0000 0010 = >6002 will select bank 6
0110 0000 0000 0000 = >6000 will select bank 7

As 'x' can be either a 1 or a 0, banks can be selected at other addresses as well ...

<-A0          A15->
0111 1111 1111 1111 = >7FFF will select bank 0
0111 1111 1111 0001 = >7FF1 will select bank 7

... but it makes sense to stick to a convention of having 'x' as 0.

To select a bank in assembly language, any of the following will work (just substitute >600E for the appropriate address of the bank to select):

MOV R0, @>600E (writes the value in R0 to the ROM; the value in R0 is immaterial)
CLR @>600E (writes the value 0 to the ROM)
MOV @>600E,@>600E   (reads from the ROM, then does a write)

Sample Code for Cartridge Header to Copy Cartridge Memory Banks to 32K Memory Expansion

The following code is for a cartridge header which copies code from a 2-bank cartridge to the 32K memory expansion, then branches to an address in the memory expansion to run it.

* JON GUIDRY - MARCH 2009      *
* & COPY/EXECUTE @ >A000       *
* 9900 ASSEMBLY LANGUAGE       *
* FOR THE HELP!                *





       BYTE >01      * VERSION NUMBER
       BYTE >00      * NOT USED
       DATA >0000    * POINTER TO DSR LIST
       DATA >0000
       DATA >0000

       LWPI >8300
       MOV R0,@>6000 * SELECT BANK
       LI R9,>6300   * ADDRESS TO COPY FROM
       LI R10,>A000  * ADDRESS TO COPY TO
       MOV *R9+,*R10+
       MOV *R9+,*R10+
       DEC R4
       JNE LP1

MAIN2  MOV R0,@>6002
       LI R4,>0740
       LI R9,>6300
       LI R10,>BD00
       MOV *R9+,*R10+
       MOV *R9+,*R10+
       DEC R4
       JNE LP2

       B @>A000


Resurrecting a TI Disk System

Most TI disk systems use either a sidecar disk drive controller (PHP1800) or a disk drive controller expansion card (PHP1240) mounted in a peripheral expansion box (PEB). This section covers a third option - a disk drive controller expansion card connected directly to the console using a special ribbon cable. This was purchased off eBay having previously been acquired from a woman whose husband worked for TI. A thread about the system on one of the TI-99 forums suggests that the special ribbon cable may have been made at the TI-99 European research centre at Almelo in the Netherlands. As well as the ribbon cable and disk drive controller, two Siemens FDD200-5 floppy drives were included in the purchase. These are DSDD drives, but the disk controller limits their use to DSSD. The drives are mounted in a rather handy chassis that has been reused from some other piece of equipment. TI disk system
Console extension ribbon cable The ribbon cable has a connector at each end, one to mate with the console side port, and the other with the same pinout as the expansion card connectors in the PEB. The connectors are mounted in paxolin blocks which have been cut, milled and drilled - very tidy work. The 5-pin DIN plug is for connecting an external power supply to power the disk controller card. The whole arrangement is entirely passive apart from five 47Ω pullup resistors (to +5V) on pins 12, 16, 45, 46, 48 at the expansion card end which perform the function of those normally found on the flex cable interface card in the PEB (the flex cable interface card also has pullup resistors on pin 13 and 15, but these pins are not connected through the ribbon cable). Further detailed photos of the ribbon cable can be seen here, here and here. The connections between the two connectors on the ribbon cable are detailed in the table further down this page.

The sliding door has to be removed from the console side port to enable the connector at the console end of the cable to fit, and the connector wiggled a little so it slips past the metal earthing fingers each side of the console connector.

The disk controller card has been modified to disconnect the +5V, +12V and -5V voltage regulators and strap the power supplies straight through onto the card, allowing the use of an external regulated power supply. A few of the TTL ICs are socketed and presumably have been replaced at some point in the past. A picture of a standard card is available here.

Getting the disk system to work proved to be an interesting experience ...

First I had to disassemble the disk controller end of the cable to determine the wiring to the DIN plug used for power to the disk controller. As the disk controller card has been modified to accept regulated supplies directly, it can be conveniently powered from an old PC ATX power supply that can also power the floppy drives themselves, so I wired up a DIN plug to connect with the power plug at the end of the cable.

So with the cable connected to the console, power connected to the cable, but the disk controller card not connected, the console boots fine. Connect the disk controller card however and the console hangs immediately power is applied. So out with the multimeter and check for shorts to ground on each of the disk controller card connector pins. This reveals that the READY signal has a resistance of only 20Ω to ground. It is driven by a 74LS125, so remove the old one, fit a socket and pop a new one in. The console now boots fine with the disk controller connected.

Powering on the console doesn't give the usual brief flash from the disk controller card LED as the DSR power up routine is executed. So out with the MiniMem cartridge and use EasyBug to 'switch on' the card by writing a 1 to CRU address >1100. The disk controller card LED now lights so it looks like the CRU circuit is working. Now looking at memory locations >4000 onward should read the DSR from the disk controller card ROMs, but only 00s are returned. So use the logic probe to start checking the inputs to the PAL U18; nothing obviously wrong here from a quick check. Checking the PAL outputs, the logic probe doesn't give a signal on the line to the 74LS245 data buffer U3 /OE pin. Checking this with a multimeter shows a voltage of ~1.8V, in the indeterminate range between a logic 0 and a logic 1. The 'LS245 is already socketed (and heatsinked via a small block of aluminium stuck to the inside of the clam shell) so has been replaced before – this particular type of IC can be prone to failure. Remove it and the output from the PAL starts registering on the logic probe. Checking the 'LS245 itself, the /OE pin has a resistance of approximately 80Ω to ground, so replace that, and the output from the PAL still registers on the logic probe. Still no joy though reading the contents of the disk controller card DSR ROMs. Looking at the inputs to the PAL a bit closer, there is no activity on the /MEMEN input. Tracing this signal back, the /MEMEN input to the 74LS244 buffer U5 is floating – the signal is not getting down the cable from the console. Take the cable connectors apart again to trace the signal through the cable, and /MEMEN is routed down the outer-most wire on one edge of the ribbon cable. This had been trapped years before when the connectors were assembled – the cable wasn't quite aligned in the shallow cutout between the two halves of the block, and the insulation was completely flattened. I had noticed this before, but hadn't thought that the wire within could also be broken, which it actually was. I managed a neat repair with a short length of wire from a spare piece of ribbon cable. Everything assembled again, power on the console and the disk controller card LED now gives the usual brief flash, and using EasyBug I can now read the contents of the DSR ROMs.

Next stage is to look at the drives. First, on one of the drives the sector mark sensor (photodiode) has broken off its base and this is fixed with a little bit of glue.

So, connect power to the first drive and switch on. Smoke! Resistor R22 on the floppy PCB is glowing gently red! Oh dear. Let's try the second drive. Smoke! Resistor R22 on the second drive is also gently glowing. Checking both boards, one side of R22 is shorted to ground, and the culprit – electrolytic capacitor C3 has shorted on *both* boards. Snipping one end of C3 temporarily allows testing to continue; I'll replace them and the charred resistors later. Experimenting with the drives, which are double-sided, yields the following results: on one drive only the bottom head is working (will only format single-sided), and on the second drive, both heads appear to be working (can format double-sided). The PCB on one of the drives is also faulty – any disk operation gives disk error 16 "no disk or no drive". Looking at the circuit diagram (a comprehensive instruction manual for the Siemens FDD200-5 drive is included in the manual for the Siemens PU 670C Programming Unit), the outputs from the floppy drive to the controller are all open-collector and gated by the drive select signal so that multiple drives can be connected on the same ribbon cable. Checking a few of the outputs with the logic probe while trying to access the drives – nothing. Tracing the drive select signal through the circuit identifies a 74LS14 inverter with a stuck output so remove it, fit a socket and pop a new one in and both drives now working.

Running the Disk Manager comprehensive test on each drive for a while reveals no further problems.

Ribbon Cable Connections

The connections between the two connectors on the ribbon cable are detailed in the table below.

Remember that you will need to provide a power source for the expansion card, either supplies at the same voltages as the PEB if leaving the voltage regulators in the expansion card in place, or regulated 5/12V supplies if bypassing the regulators in the card. Check and check again that the power supplies are correct before connecting!

Signal Console
Side Port
60   +12V regulator supply  
  59 +12V regulator supply  
58   -12V regulator supply  
  57 -12V regulator supply  
56   /MEMEN ----------------------------------- 32
  55 CRUIN ------------------------------------ 33
54   /WE --------------------------------------- 26
  53 --- N/C (GND)  
52   DBIN -------------------------------------- 9
  51 /CRUCLK ---------------------------------- 22
50   /CLKOUT ---------------------------------- 24
  49 --- N/C (GND)  
48   Pullup to +5V (AMC)  
  47 GND -------------------------------------- 23
46   Pullup to +5V (AMA)  
  45 Pullup to +5V (AMB)  
44   A1 ---------------------------------------- 30
  43 A0 ---------------------------------------- 31
42   A3 ---------------------------------------- 10
  41 A2 ---------------------------------------- 20
40   A5 ---------------------------------------- 5
  39 A4 ---------------------------------------- 7
38   A7 ---------------------------------------- 17
  37 A6 ---------------------------------------- 29
36   A9 ---------------------------------------- 18
  35 A8 ---------------------------------------- 14
34   A11 --------------------------------------- 8
  33 A10 --------------------------------------- 6
32   A13 --------------------------------------- 15
  31 A12 --------------------------------------- 11
30   A15 /CRUOUT ---------------------------- 19
  29 A14 --------------------------------------- 16
28   D0 ---------------------------------------- 37
  27 --- N/C (GND)  
26   D2 ---------------------------------------- 39
  25 D1 ---------------------------------------- 40
24   D4 ---------------------------------------- 35
  23 D3 ---------------------------------------- 42
22   D6 ---------------------------------------- 36
  21 D5 ---------------------------------------- 38
20   --- N/C (GND)  
  19 D7 ---------------------------------------- 34
18   --- N/C (/LOAD)  
  17 --- N/C (/INTA)  
16   Pullup to +5V (/SENILB)  
  15 --- N/C (/SENILA)  
14   --- N/C (IAQHA)  
  13 --- N/C (/HOLD))  
12   Pullup to +5V (PCBEN)  
  11 --- N/C (/RDBENA)  
10   --- N/C (AUDIOIN)  
  9 --- N/C (/LCP)  
8   --- N/C (SCLK)  
  7 --- N/C (GND)  
6   /RESET ----------------------------------- 3
  5 --- N/C (GND)  
4   READY ------------------------------------ 12
  3 GND -------------------------------------- 21
2   +5V regulator supply  
  1 +5V regulator supply  

Connecting a Serial Mouse

A standard, old PC serial mouse can be connected directly to the serial port on a TI-99/4A nanoPEB interface.

The listing below is for a mouse-driven menu and a simple sketch program. The listing also provides an example of how to initialise and control the RS-232 port, and how to plot individual pixels with the TMS 9918A VDP in graphics 2 mode. There is a video of the program in use here.

I've tried two serial mice which both work - labelled on the bottom as a Microsoft "Serial Mouse 2.1A", and a Microsoft "Serial - PS/2 Compatible Mouse".
serial mouse 1 serial mouse 2

*Experiment using a PC serial mouse connected to the serial port on a NanoPEB.
*The program first displays a mouse-driven menu, which is configured in
*Graphics 1 mode and uses a sprite defined as the mouse pointer. Selecting the
*first menu option runs a simple sketch application which is configured in
*Graphics 2 mode, giving a resolution of 256 x 192. Instructions for using the
*sketch application are displayed on screen.
*Mouse protocol details reference: www.kryslix.com/nsfaq/Q.12.html
*TMS9918A VDP datasheet:  ftp://ftp.whtech.com/datasheets%20and%20manuals/
*                                              Datasheets%20-%20TI/TMS9918.pdf

        AORG >A000

        DEF  START

        REF  VSBW           VDP single byte write.
        REF  VMBW           VDP multiple byte write.
        REF  GPLLNK         GPL routine link.


*Graphics 1 mode storage in VRAM.

PNTBA1  EQU  >0000          Pattern name table base address.
PGTBA1  EQU  >0800          Pattern generator table base address.
CTBA1   EQU  >0380          Colour table base address.
SATBA1  EQU  >1B00          Sprite attribute table base address.
SGTBA1  EQU  >3800          Sprite generator table base address.

*Graphics 2 (bitmap) mode storage in VRAM.

PNTBA2  EQU  >1800          Pattern name table base address.
PGTBA2  EQU  >0000          Pattern generator table base address.
CTBA2   EQU  >2000          Colour table base address.
SATBA2  EQU  SATBA1         Sprite attribute table base address.
SGTBA2  EQU  SGTBA1         Sprite generator table base address.

*Memory mapped I/O definitions.

VDPREG  EQU  >8C02          VDP VRAM address and register access address.
VRAMW   EQU  >8C00          VDP VRAM data write address.
VRAMR   EQU  >8800          VDP VRAM data read address.

*CRU base address definitions.

BACARD  EQU  >1300          NanoPEB serial interface base address.
BA9902  EQU  >1340          NanoPEB serial port 1 base address.


START   LWPI MYWS           Load workspace.

*Initialise mouse RS-232 port.

*Switch on serial interface.

        LI   R12,BACARD
        SBO  0

*Set port 1 to 1200 Baud, 7 data bits, no parity, 1 stop bit.

        LI   R12,BA9902     CRU base address of 9902 in NanoPEB.
        SBO  31             Reset 9902. This sets /RTS inactive high, so the
*                           RTS output line to -ve voltage.
        LI   R1,>8200       Control register: 1 stop bit, no parity, 7 data
*                           bits (binary 10000010).
        LDCR R1,8           Load control register.
        SBZ  13             Disable loading of interval register.
        LI   R1,>01A0       1200 Baud.
        LDCR R1,12          Load transmit and receive data rate registers.

        SBO  16             Set /RTS active low, so the RTS output line to +ve
*                           voltage to power the mouse.

*Set up Graphics 1 mode.
*(Will already be in Graphics 1 mode after starting the program from E/A,
* but need routine to set up this mode when returning to the menu from the
* mouse drawing program which uses Graphics 2 mode.)

*Load VDP registers.


        BYTE >00,>80        Register 0 - graphics 1 mode, external video off.
        BYTE >E0,>81        Register 1 - 16K, display on, interrupts on,
*                                        graphics 1, size  mag = 0.
        BYTE >00,>82        Register 2 - PNTBA1 = >0000. Pattern name table.
        BYTE >0E,>83        Register 3 - CTBA1 =  >0380. Colour table.
        BYTE >01,>84        Register 4 - PGTBA1 = >0800. Pattern generator
*                                                        table.
        BYTE >36,>85        Register 5 - SATBA1 = >1B00. Sprite attribute
*                                                        table.
        BYTE >07,>86        Register 6 - SGTBA1 = >3800. Sprite generator
*                                                        table.
        BYTE >F5,>87        Register 7 - Backdrop=background colour.
        DATA 0

*Clear the sprite generator table (SGT) and sprite attribute table (SAT).

        BL   @CLRSPR

*Load character set (character codes >20 - >5F) (from E/A manual page 251).

        CLR  R1             Clear GPL STATUS byte.
        MOVB R1,@>837C

        LI   R1,8*>20+PGTBA1  Address in VDP RAM to load character set.
        MOV  R1,@>834A

        BLWP @GPLLNK
        DATA >0018

*Load colour table.

        LI   R8,CTBA1+>4000  Reference CT. Add >4000 for VDP write operations.

        BL   @SENDAD        Send VDP address.

        LI   R11,256/8      Set count.
        LI   R1,>F500       Colour - white on light blue.

CTGRH1  MOVB R1,*R7         Write to colour table.
        DEC  R11            Done all entries?
        JNE  CTGRH1         No, loop around.

*Redefine characters:
* 96 for the menu selection pointer.
* 97 for inverse video 'H'.
* 98 for inverse video 'E'.
* 99 for inverse video 'R'.

        LI   R0,96*8+>0800  Offset of >0800 as PGT starts at >0800.
        LI   R1,MSPDAT
        LI   R2,8*4
        BLWP @VMBW

*Display menu in Graphics 1 mode.

*Clear screen.


*Display menu text.

        CLR  R0
        LI   R1,TXT1
        MOV  @TXT1L,R2
        BLWP @VMBW

        LI   R0,32*3
        LI   R1,TXT2
        MOV  @TXT2L,R2
        BLWP @VMBW

*Define mouse pointer sprite and place at middle of screen.

        BL   @DEFMSE

*Initialise mouse position to the middle of the screen.
*Mouse position range is twice the screen resolution (twice 192 x 256) to get
*an adequate movement of the mouse. The mouse position will be divided by two
*to map it to screen coordinates. (Can't divide mouse movement by two
*otherwise very slow movement will result in no movement.)

        LI   R9,256/2*2     Mouse X position in LSB.
        MOV  R9,@MSXPOS
        LI   R9,192/2*2     Mouse Y position in LSB.
        MOV  R9,@MSYPOS

*Get mouse position and display indicator against menu option if mouse
*Y position is on the same line.

GMPOS   BL   @UPDTMS        Update mouse position.

        MOV  @MSYPOS,R9     Get mouse Y position.
        SRL  R9,4           Divide by 2, then by 8 to convert from pixels
*                           to lines.

        LI   R4,3           Menu start line number.
        LI   R5,6           Menu end line number.
        LI   R0,3*32+2      Menu pointer display position for top line.

MEN01   LI   R1,' '*256     Default to no menu pointer for this line.
        C    R9,R4          Mouse on this line?
        JNE  MEN02          No, jump.
        LI   R1,96*256      Yes, select menu selection pointer character.

MEN02   BLWP @VSBW          Display or remove menu pointer for this line.
        C    R4,R5          Done last line of menu?
        JEQ  CHKBTN         Yes, jump.
        AI   R0,32          No, do next line of menu.
        INC  R4
        JMP  MEN01

*Mouse left button clicked? If yes then jump to menu option.

        COC  @H6000,R1      Check for left mouse button pressed.
        JNE  GMPOS          Left button not pressed, jump.

        CI   R9,3           Button pressed when on first line of menu?
        JEQ  MENU01         Yes, jump to menu.
        CI   R9,4           Button pressed when on second line of menu?
        JEQ  MENU02         Yes, jump to menu.
        CI   R9,5           -- Ditto --
        JEQ  MENU02
        CI   R9,6
        JEQ  MENU02
        JMP  GMPOS          Button not pressed when mouse on menu line.

*Menu option 1 text.

*Clear screen.


*Display instruction text for paint program.

        CLR  R0
        LI   R1,TXT3
        MOV  @TXT3L,R2
        BLWP @VMBW

        LI   R0,32*3
        LI   R1,TXT4
        MOV  @TXT4L,R2
        BLWP @VMBW

        LI   R0,32*23+5
        LI   R1,TXT6
        MOV  @TXT6L,R2
        BLWP @VMBW

*Loop until mouse clicked on 'HERE' on bottom line.

        BL   @WAITHR

*Jump to drawing program.

        JMP  RSTART

*Menu options 2 - 4 text.

*Clear screen.


*Display 'menu option not implemented' text.

        CLR  R0
        LI   R1,TXT5
        MOV  @TXT5L,R2
        BLWP @VMBW

        LI   R0,32*23+5
        LI   R1,TXT6
        MOV  @TXT6L,R2
        BLWP @VMBW

*Loop until mouse clicked on 'HERE' on bottom line.

        BL   @WAITHR

*Return to main menu.

        JMP  MNMENU

*                         MOUSE DRAWING PROGRAM                              *

*Set up Graphics 2 mode.

*Load VDP registers.


        BYTE >02,>80        Register 0 - graphics 2 mode, external video off.
        BYTE >80,>81        Register 1 - 16K, no display, no interrupt,
*                                        graphics 2, size  mag = 0.
        BYTE >06,>82        Register 2 - PNTBA2 = >1800. Pattern name table.
        BYTE >FF,>83        Register 3 - CTBA2 =  >2000. Colour table.
        BYTE >03,>84        Register 4 - PGTBA2 = >0000. Pattern generator
*                                                        table.
        BYTE >36,>85        Register 5 - SATBA2 = >1B00. Sprite attribute
*                                                        table.
        BYTE >07,>86        Register 6 - SGTBA2 = >3800. Sprite generator
*                                                        table.
        BYTE >00,>87        Register 7 - Backdrop=background colour.
        DATA 0

*Clear the sprite generator table (SGT) and sprite attribute table (SAT).

        BL   @CLRSPR

*Set up pattern name table.

        LI   R8,PNTBA2+>4000  Reference PNT. Add >4000 for VDP write
*                             operations.

        BL   @SENDAD        Send VDP address.

        SETO R11            Reset count.

INIPNT  INC  R11            Next pattern.
        SWPB R11            Position LS byte.
        MOVB R11,*R7        Write it.
        SWPB R11            Restore R11.
        CI   R11,3*256      Done all entries?
        JL   INIPNT         No, loop around.

*Set up pattern generator table.

        LI   R8,PGTBA2+>4000  Reference PGT. Add >4000 for VDP write
*                             operations.

        BL   @SENDAD        Send VDP address.

        LI   R11,3*256*8    Set count.

KILPGT  MOVB @B00,*R7       Reset entry.
        DEC  R11            Done all entries?
        JNE  KILPGT         No, loop around.

*Set up colour table.

        LI   R8,CTBA2+>4000  Reference CT. Add >4000 for VDP write operations.

        BL   @SENDAD        Send VDP address.

        CLR  R1             Clear R1 because we'll be shifting left.
        MOVB @MPTSAT+3,R1   Get initial mouse pointer sprite colour.
        SLA  R1,4           Shift to left nibble to align with foreground
*                           colour in CT.
        MOVB R1,@MSCOL      Store initial mouse pointer colour.

        LI   R11,3*256*8    Set count.

INICT   MOVB R1,*R7         Set colour to initial mouse pointer sprite colour.
        DEC  R11            Done all entries?
        JNE  INICT          No, loop around.

*Re-enable display.

        BL   @LOADER

        BYTE >C0,>81
        DATA 0

*Define mouse pointer sprite and place at middle of screen.

        BL   @DEFMSE

        LI   R9,256/2*2     Mouse X position in LSB.
        MOV  R9,@MSXPOS
        LI   R9,192/2*2     Mouse Y position in LSB.
        MOV  R9,@MSYPOS

*Update mouse position.


*Check for left and right buttons pressed together. If pressed, return to
*main menu.

        MOV  @MSBTTN,R1

        COC  @H7000,R1      Check for left and right button indication in 1st
*                           byte received.
        JNE  CLRSCR         Left and right button not both pressed, jump.

        B    @GRAPH1        Return to main menu.

*Check for right button press. If pressed, change mouse pointer colour.

CLRSCR  COC  @H5000,R1      Check for right button indication in 1st byte
*                           received.
        JNE  UPDCL1         Right button not pressed, jump.

        AB   @H10,@MSCOL    Right button pressed. Move onto next colour.
        JNE  UPDCOL         Need to wrap colour index back round? No, jump.
        MOVB @H20,@MSCOL    Reset colour to 2, skipping transparent and black
*                           colours.

UPDCOL  LI   R8,SATBA2+>4000+3  Reference colour byte in SAT. Add >4000 for
*                               VDP write operations.

        BL   @SENDAD        Send VDP address.

        MOVB @MSCOL,R2      Get stored colour.
        SRL  R2,4           Shift to 2nd nibble to align with colour nibble in
*                           SAT.
        MOVB R2,*R7         Write new colour to mouse pointer sprite.

*Check for left button press. If pressed, draw pixel at mouse position.

UPDCL1  COC  @H6000,R1      Check for left button indication in 1st byte
*                           received.
        JNE  DRWEX          Left button not pressed, jump.

*Convert mouse X position to column number and remainder.

        MOV  @MSXPOS,R2     Get X position (which is at twice screen
*                           resolution).
        MOV  R2,R3          Copy it.

        SRL  R2,4           Divide by 2 to convert to screen resolution, then
*                           divide by 8 to get column number.

        SRL  R3,1           Divide by 2 to convert to screen resolution.
        ANDI R3,>0007       Get 3 LS bits which are the remainder
*                           (the pixel 0 to 7 within the 8-pixel column).

*Convert mouse Y position to row number and remainder.

        MOV  @MSYPOS,R8     Get Y position (which is at twice screen
*                           resolution).
        MOV  R8,R9          Copy it.

        SRL  R8,4           Divide by 2 to convert to screen resolution, then
*                           divide by 8 to get row number.

        SRL  R9,1           Divide by 2 to convert to screen resolution.
        ANDI R9,>0007       Get 3 LS bits which are the remainder
*                           (the pixel 0 to 7 within the 8-pixel row).

*Multiply row number by 32, then add column number to get cell number.

        SLA  R8,5           Multiply row number by 32.
        A    R2,R8          Add column number.

*Multiply cell number by 8 to get index into entry in the PGT for this cell
*(8 bytes in PGT per cell, 1 per pixel row).

        SLA  R8,3           Multiply cell number by 8.

*Get current VRAM data from cell number, add new pixel, and write back to

        A    R9,R8          Add Y remainder to get correct byte in PGT for the
*                           pixel row.

        BL   @SENDAD        Send cell address to VDP.

        CLR  R10            Clear pixel mask register. The pixel mask gives
*                           the bit to set in the PGT byte for the pixel
*                           0 to 7 within the 8-pixel column.
        AI   R3,MASKTB      Add address of pixel mask table base to
*                           X remainder.
        MOVB *R3,R10        Fetch pixel mask.
        MOVB @VRAMR,R3      Get current VRAM data.
        SOCB R10,R3         Set pixel bit.

        ORI  R8,>4000       Set VDP write bit in address.

        BL   @SENDAD        Send address to VDP.

        MOVB R3,*R7         Write updated VRAM data.

*Set the colour table entry to the current mouse pointer sprite colour.

        AI   R8,CTBA2-PGTBA2  Get address of PCT entry.

        BL   @SENDAD        Send address to VDP.

        MOVB @MSCOL,*R7     Update colour entry.

*Loop round and do it all again.



*Load the VDP registers from an inline data table.

LOADER  MOVB *R11+,@VDPREG  Write register data.

        C    *R11,*R11      Dummy delay for VDP.
        MOVB *R11+,@VDPREG  Write register number.

        MOV  *R11,*R11      End of data table?
        JNE  LOADER         No, loop.

        INCT R11            Yes, skip data table terminator.

        B    *R11           Return.

*Send the address in R8 to the VDP.

SENDAD  SWPB R8             Position LSB.
        MOVB R8,@VDPREG     Send LSB.

        SWPB R8             Position MSB.
        MOVB R8,@VDPREG     Send MSB.

        B    *R11           Return.

*Clear the sprite generator table (SGT) and sprite attribute table (SAT).

CLRSPR  MOV  R11,@SAVR11    Save return address.

        LI   R7,VRAMW
        LI   R8,SGTBA1+>4000  Reference SGT. Add >4000 for VDP write
*                             operations.

        BL   @SENDAD        Send VDP address.

        LI   R8,256*8       Do 256 8-bit patterns.

KILSGT  MOVB @B00,*R7       Null out the pattern.
        DEC  R8             Count it.
        JNE  KILSGT         Loop till all done.

        LI   R8,SATBA1+>4000  Reference SAT. Add >4000 for VDP write
*                             operations.

        BL   @SENDAD        Send VDP address.

        LI   R8,32*8        Kill off all 32 sprite planes.
        LI   R6,>D000       Fill it full of sprite terminators.

KILSAT  MOVB R6,*R7         Clear SAT.
        DEC  R8             Count it.
        JNE  KILSAT         Loop till all done.

        MOV  @SAVR11,R11    Restore return address.

        B    *R11           Return.

*Clear screen.

CLRSCN  CLR  R0             Start writing at start of screen image table.
        LI   R1,' '*256     Space character.
        LI   R2,32*24-1     Number of characters to do - but do 1 outside
*                           loop.
        BLWP @VSBW          Write one byte.
CLR1    MOVB R1,@VRAMW      Write successive bytes without doing a BLWP -
*                           quicker.
        DEC  R2
        JNE  CLR1

        B    *R11

*Define mouse pointer sprite and place at middle of screen.

*Write character pattern to the sprite generator table.

DEFMSE  MOV  R11,@SAVR11    Save return address.

        LI   R8,SGTBA1+>4000  Reference SGT. Add >4000 for VDP write
*                             operations.

        BL   @SENDAD        Send VDP address.

        LI   R8,8           Do one 8-bit pattern.
        LI   R6,MPTSPG      Pointer to character pattern.

MPTRSG  MOVB *R6+,*R7       Write the pattern.
        DEC  R8             Count it.
        JNE  MPTRSG         Loop till all done.

*Define sprite in the sprite attribute table.

        LI   R8,SATBA1+>4000  Reference SAT. Add >4000 for VDP write
*                             operations.

        BL   @SENDAD        Send VDP address.

        LI   R8,4           4 bytes to write.
        LI   R6,MPTSAT      Pointer to SAT for mouse pointer.

MPTRSA  MOVB *R6+,*R7       Write the SAT.
        DEC  R8             Count it.
        JNE  MPTRSA         Loop till all done.

        MOV  @SAVR11,R11    Restore return address.

        B    *R11           Return.

*Wait for a packet of data from the mouse.
*Determine the mouse movement and update mouse position.
*Move mouse pointer sprite.

*Wait for and store a packet of 3 bytes. The first byte of each packet should
*be a value greater than or equal to >40. Check for this to keep reception in

UPDTMS  LI   R12,BA9902     CRU base address of 9902 in NanoPEB.

        CLR  R1
        CLR  R2
        CLR  R3

WAIT3   TB   21             Receive buffer register full?
        JNE  WAIT3          No, loop until a character is received.
        STCR R1,8           Read character into MSB of R1.
        SBZ  18             Reset receive buffer register.

        CI   R1,>4000       Bit set to indicate first byte in packet?
        JL   WAIT3          No, get another byte.

WAIT4   TB   21             Receive buffer register full?
        JNE  WAIT4          No, loop until a character is received.
        STCR R2,8           Read character into MSB of R2.
        SBZ  18             Reset receive buffer register.

WAIT5   TB   21             Receive buffer register full?
        JNE  WAIT5          No, loop until a character is received.
        STCR R3,8           Read character into MSB of R3.
        SBZ  18             Reset receive buffer register.

        MOV  R1,@MSBTTN     Save mouse buttons pressed data.

*Determine the mouse movement and update mouse position.

*Determine the X movement.

        MOV  R1,R5          Copy byte 1.
        SLA  R5,6           Move MS bits of X movement to top of byte.
        A    R5,R2          Add to rest of X movement in 2nd byte.
        SWPB R2             Move result into LSB.

*Determine the Y movement.

        MOV  R1,R5          Copy byte 1.
        ANDI R5,>0C00       Isolate bits 2 and 3.
        SLA  R5,4           Shift to move MS bits of Y movement to top of
*                           byte.
        A    R5,R3          Add to rest of Y movement in 3rd byte.
        SWPB R3             Move result into LSB.

*Update mouse X position.

        MOV  @MSXPOS,R9     Get stored mouse X position.

        CI   R2,128         Is X movement left (-ve) or right (+ve)?
        JLE  UPDTX1         Jump if to the right.

        AI   R2,>FF00       Movement is to the left. Change to two's
*                           complement word.
        A    R2,R9          Subtract (add two's complement) movement from
*                           mouse X position.
        JGT  UPDTX2         If position now > 0, it's valid.
        JEQ  UPDTX2         If position now 0, it's valid.
        CLR  R9             Result is < 0, so reset position to 0.
        JMP  UPDTX2

UPDTX1  A    R2,R9          Add movement to X position.
        CI   R9,2*255       If X position > 2*255 then set to 2*255.
        JLE  UPDTX2
        LI   R9,2*255

UPDTX2  MOV  R9,@MSXPOS     Store new mouse X position.

*Update mouse Y position.

        MOV  @MSYPOS,R9     Get stored mouse Y position.

        CI   R3,128         Is Y movement up (-ve) or down (+ve)?
        JLE  UPDTY1         Jump if down.

        AI   R3,>FF00       Movement is up. Change to two's complement word.
        A    R3,R9          Subtract (add two's complement) movement from
*                           mouse Y position.
        JGT  UPDTY2         If position now > 0, it's valid.
        JEQ  UPDTY2         If position now 0, it's valid.
        CLR  R9             Result is < 0, so reset position to 0.
        JMP  UPDTY2

UPDTY1  A    R3,R9          Add movement to Y position.
        CI   R9,2*191       If Y position > 2*191 then set to 2*191.
        JLE  UPDTY2
        LI   R9,2*191

UPDTY2  MOV  R9,@MSYPOS     Store new mouse Y position.

*Move mouse pointer sprite.

*First byte in SAT is vertical position, second byte is horizontal position.

MOVMS   MOV  R11,@SAVR11    Save return address.

        LI   R8,SATBA1+>4000  Reference SAT. Add >4000 for VDP write
*                             operations.

        BL   @SENDAD        Send VDP address.

        MOV  @MSYPOS,R2     Get stored mouse Y position (which is at twice
*                           screen resolution).
        DECT R2             Top row of the screen has value -1, so adjust.
        SLA  R2,7           Move to MSB and divide by 2 to convert to screen
*                           resolution.
        MOVB R2,*R7         Write the Y mouse position.

        MOV  @MSXPOS,R2     Get stored mouse X position (which is at twice
*                           screen resolution).
        SLA  R2,7           Move to MSB and divide by 2 to convert to screen
*                           resolution.
        MOVB R2,*R7         Write the X mouse position.

        MOV  @SAVR11,R11    Restore return address.

        B    *R11           Return.

*Loop until mouse clicked on 'HERE' on bottom line.

WAITHR  MOV  R11,@SAVR12    Save return address.

WAHR01  BL   @UPDTMS        Update mouse position.

        MOV  @MSBTTN,R1
        COC  @H6000,R1      Check for left mouse button pressed.
        JNE  WAHR01         Left button not pressed, jump.

        MOV  @MSYPOS,R9     Get mouse Y position.
        SRL  R9,4           Divide by 2, then by 8 to convert from pixels
*                           to lines.
        CI   R9,23          Mouse on bottom line of screen?
        JNE  WAHR01         No, jump.

        MOV  @MSXPOS,R9     Get mouse X position.
        SRL  R9,4           Divide by 2, then by 8 to convert from pixels
*                           to characters.
        CI   R9,11          Mouse over the correct characters?
        JL   WAHR01         No, jump.
        CI   R9,14
        JH   WAHR01         No, jump.

        MOV  @SAVR12,R11    Yes, restore return address.

        B    *R11           Return.

*Workspace, text and data.

MYWS    BSS  32             Workspace.

        TEXT '  ----------------------------'


        TEXT '  ---------------------------'

        TEXT 'COLOUR.                         '
        TEXT '                                '

        TEXT '***       IMPLEMENTED       ***'

        BYTE 97,98,99,98    'HERE' using video inverse characters.


SAVR11  DATA 0              Saved return address.
SAVR12  DATA 0              Second saved return address.

MSXPOS  DATA 0              Mouse X position in LSB.
MSYPOS  DATA 0              Mouse Y position in LSB.
MSBTTN  DATA 0              Mouse buttons pressed.
MSCOL   BYTE 0              Mouse pointer colour.

B00     BYTE 0
B16     BYTE 16
H10     BYTE >10
H20     BYTE >20
H5000   DATA >5000
H6000   DATA >6000
H7000   DATA >7000

*Mouse pointer sprite data.

MPTSPG  BYTE >F8,>F0,>F0,>D0,>88,>04,>02,>01  Arrow pattern.
MPTSAT  BYTE 192/2-1,256/2,>00,>0F  Initial position middle of screen, colour
*                                   white.

*Menu selection pointer character data.

MSPDAT  BYTE >10,>18,>1C,>1E,>1E,>1C,>18,>10

*'HER' in inverse video characters data.

        BYTE >00,>BB,>BB,>BB,>83,>BB,>BB,>BB
        BYTE >00,>83,>BF,>BF,>87,>BF,>BF,>83
        BYTE >00,>87,>BB,>BB,>87,>AF,>B7,>BB

*Pixel bit mask table.

*Pixel------- 0 1   2 3   4 5   6 7
MASKTB  DATA >8040,>2010,>0804,>0201


Internet Web Browser


This is a web browser for the TI-99/4A, where the TI-99 is connected to the Internet through a serial port and a Lantronix UDS-10 serial-to-Ethernet adaptor. The browser will not work with any 'modern' web pages - there are just too many HTML tags and other stuff for the TI to handle, and there's no way you could display much of it anyway with the TI's low resolution screen. Instead, the browser works with a bespoke set of tags which are matched to the display capabilities of the TI-99.

Main Features

Configuring and Connecting the Lantronix Serial-to-Ethernet Adaptor

The Lantronix UDS-10 will need this configuration for Channel 1:

I usually configure the Lantronix over Telnet - the Lantronix User Guide contains details for doing this. I have problems configuring the Lantronix through the built-in web app - the Connect Mode Settings:Response field usually gets reset to the default setting each time I open the web app, rather than showing the current setting.

The Lantronix is wired to the serial port on a NanoPEB as follows:

NanoPEB                 Lantronix
9-way D-type Male       25-way D-type Female
Pin                              Pin
 2 (RX)  ------<<------ (RX out)  3
 3 (TX)  ------>>------ (TX in)   2
 5 (GND) -------------- (GND)     7
 7 (RTS) -->>--+
 8 (CTS) --<<--+ (pins 7 and 8 connected together)

The Lantronix is wired to the 1st serial port on a TI PEB RS-232 card as follows:

RS-232 Card              Lantronix
25-way D-type Female     25-way D-type Female
Pin                              Pin
 2 (RD-1) ------<<----- (RX out)  3
 3 (TX-1) ------>>----- (TX in)   2
 5 (CTS-1) ----->>----- (RTS in)  4
 7 (GND) -------------- (GND)     7
 20 (DTR-1) ----<<----- (CTS out) 5

Loading and Running the Program

Various download options are available, depending on how you intend to run the program. Download links are in the table below, with a description of the various options below that.

Disk Image 32K EPROM Image, Inverted Bank Switching  512K EPROM Image, Non-Inverted Bank Switching 
Internet Browser v8_1.zip  Internet Browser 32K Inverted v8_1.zip Internet Browser 512K Non-Inverted v8_1.zip

Disk Image

The program is on a TiDisk format image and named "INTERNET". To run the program, boot with the Editor/Assembler cartridge, select E/A option 3 and type the filename DSKn.INTERNET. After loading, the program name required depends on the type of RS-232 port you're using:

32K EPROM Image, Inverted Bank Switching

This is a binary image which can be burned into a 32K 27(C)256 EPROM for use on a 64K 'Guidry' bank-switched cartridge board fitted with a 74LS379 (inverted outputs) latch. The option to select from the master selection list depends on the type of RS-232 port you're using:

512K EPROM Image, Non-Inverted Bank Switching

This is a binary image which can be used as follows:

The option to select from the master selection list depends on the type of RS-232 port you're using, as detailed for the 32K EPROM image above.

With the cartridge versions of the program, you still need a disk file DSK.BROWSER.FAVS to use the favourites list, described below.

When the program starts, it displays an instruction page and prompts for the web page to load at the top of the screen. The "www." prefix is provided already. The URLs of some sample web sites are given further down this page. The web page address is limited to 80 characters. Use lower-case for the web page address - ALPHA LOCK up!

Internet web browser startup screen

After entering a web page address, the program shows status messages at the bottom of the screen as it resolves the server IP address then downloads the web page. The Lantronix sometimes experiences a problem connecting to a web server - if the program says it is retrying the connection, it normally succeeds after 30 seconds or so.

Navigation and Control Keys

The navigation and control keys supported are listed below. These can be entered as either upper or lower case.

The keys auto-repeat to make it easier to move the pointer between hyperlinks.

The pointer can also be moved horizontally, vertically and diagonally using a joystick. The joystick Fire button selects any hyperlink that the pointer is over.

Pressing any key when the program is retrying a connection will abort the connection and prompt for a new URL. (Note that it may take a couple of seconds for the program to respond after pressing the key.)

Favourites List

The program supports a list of favourites, which is displayed by pressing the I key. This loads and renders the contents of the file DSK.BROWSER.FAVS. This is written/edited by the user using (for example) the E/A Editor, and saved in 'Variable 80' format. The file contains text and tags for hyperlinks to the user's favourite pages - it is in effect a web page stored on the disk. As well as hyperlinks, the page can contain any graphics or character definitions desired by the user. Long lines of text/tags can be be split across several lines in the file. A sample file is included on the disk.

Sample Web Sites

A set of linked pages demonstrating the capabilities of the program. A screenshot of the first page is shown below. The second page gives a short history of the TI-99/4A. The third page shows the pinout of the TMS 9900 processor.
There is a simple chat application. Use the first URL on the left to view the current messages. To add a message to the page, add to the end of the same URL "?m=your_text" (without the quotes, and with underscores instead of spaces). Remember that the max length of the entire URL is 80 characters.
www.stuartconnerdownloads.me.uk/getclientipaddress.php  Displays the external IP address of your home network.
www.stuartconnerdownloads.me.uk/getdatetime.php Displays the local date and time on the (US) host server.
Example: www.stuartconnerdownloads.me.uk/getweather.php?l=89109
Displays the weather at the location specified by [location]. The location types supported are specified here.
Example: www.stuartconnerdownloads.me.uk/getwebipaddress.php?w=news.bbc.co.uk
Displays the numeric IP address of the domain specified by [domain].
www.stuartconnerdownloads.me.uk/tibrowser/valley.htm Graphics demo page.
www.stuartconnerdownloads.me.uk/tibrowser/elite.htm Graphics demo pages. The program automatically cycles through a sequence of pages.

Supported Tags

The tags supported are listed in the table below. Note that the tags are CASE sensitive. Example usage of the more complex tags is given in later sections.

Tag                                                                                Description
<99ml> Indicates start of page. All text before this tag is ignored.
</99ml> Indicates end of page. All text after this tag is ignored.
<p> Indicates start of a paragraph. All text outside <p></p> tags is treated as a comment and ignored.
</p> Indicates end of a paragraph. The following text is rendered starting on the next line on the screen.
<br> Line break. The following text is rendered starting on the next line on the screen.
<a:(URL)> Specifies a hyperlink for the text following the tag.
</a> Indicates end of hyperlink text.
<arect:AA:BB:CC:DD:(URL)> Defines a rectangular link area defined in pixels. AA, BB, CC and DD are upper-case hex values. If the <Return> key or <Fire> button is pressed, then if the screen pointer X position (in pixels, relative to the top left of the screen) is between the values AA and BB (inclusive) and the screen pointer Y position is between the values CC and DD (inclusive), then the link is invoked, and the specified URL is called with “?x=(pointer X position in hex)&y=(pointer Y position in hex)” appended to the URL (or "&x=..." if the link URL already contains a "?"). (Note that the screen pointer moves in steps of 2 pixels.)
<u> Underlines the text following the tag.
</u> Indicates end of underline text.
<clr:(foreground colour code)(background colour code)> Render the characters following the tag in the specified colours. The colour code definitions are given below.

Characters are 6 pixels wide so are not aligned with the 8-pixel blocks used by the VDP for colour control. Controlling text colour on a paragraph by paragraph basis will work. If trying to change text colour mid-paragraph, the results will depend on whether the start/end characters are exactly aligned with the edge of an 8-pixel block.
</clr> Revert back to normal colours (black on grey).
<cdef:(character code in hex):(hex character def)> Redefine the specified character. Characters in the range >00 to >FF are supported.

The default character definitions are automatically reloaded when browsing to a new page.

Characters have to be defined BEFORE they are used. The same characters can be redefined several times on the same page - define each with the pattern needed before using them.

The default character definitions are:

- Character >00 is a special character used in the keyboard input routine and should not be redefined.
- Character >0A is defined as a Line Feed character.
- Character >0D is defined as a Carriage Return character.
- Characters >0E - >18 are defined with table drawing characters, as follows:
   - Character >0E - ┏ top left corner
   - Character >0F - ━ horizontal line
   - Character >10 - ┳ top join
   - Character >11 - ┓ top right corner
   - Character >12 - ┃ vertical line
   - Character >13 - ┣ left vertical join
   - Character >14 - ╋ centre join
   - Character >15 - ┫ right vertical join
   - Character >16 - ┗ bottom left corner
   - Character >17 - ┻ bottom join
   - Character >18 - ┛ bottom right corner
- Characters >20 - >7E are defined as the normal ASCII character set.
- Character >7F is defined as a solid block.
- (All other characters are defined as blank)
<chr_lt> Displays the '<' character (this 'alias' is needed in order to be able to display tags on the screen as text).
<chr:(character code in hex)> Displays the specified character.
<8pchr:(character code in hex):(character position in hex, 2 bytes)> Render all 8 pixel columns of the specified character at the specified position. The position is based on the screen divided into 8*8 tiles, so positions are numbered from 0 (top left) to 767 (bottom right). This tag is provided for plotting graphics where the character rendering is exactly aligned with the VDP colour table.
<8pclr:(character colour in hex, 8 bytes)> Defines the colours used for characters rendered using the <8pchr> tag. Each byte of the colour code specifies the colour of one row of the 8x8 character. The first nibble of each byte specifies the foreground colour, and the second byte specifies the background colour. The colour code definitions are given below.

The default foreground and background colours are both transparent.

Note that the colour information is loaded to VDP RAM as the tags are processed, so colour changes to the currently displayed page might be seen as a new page is rendered.
<ptrclr:(sprite colour code)> Changes the colour of the sprite pointer. The sprite colour code is two hex characters, the first of which must be 0 and the second the required sprite colour according to the colour code definitions given below.
<noscroll> Disables the keyboard scroll keys on a web page. This is intended for web pages designed for display as a single page and using the <8pchr> and/or <arect> tags which don't work as intended if the page is scrolled down.
<noclearscreen> Disables clearing the screen when rendering a new web page. This enables one web page to update the screen rendered by the previous web page (so for example one web page could draw half a complex graphic on the screen and a second web page draw the other half).
<loadpage:(URL)> Displays the screen rendered so far then loads the specified web page.
<updatescreen> Displays the screen rendered by the tags processed so far, then continues processing tags on the page.

Colour Codes

The colour codes used by the <clr>, <8pclr> and <ptrclr> tags are as follows:

0 - Transparent 8 - Medium Red
1 - Black 9 - Light Red
2 - Medium Green     A - Dark Yellow
3 - Light Green B - Light Yellow
4 - Dark Blue C - Dark Green
5 - Light Blue D - Magenta
6 - Dark Red E - Grey
7 - Cyan F - White                   (Note: A-F must be upper-case)

Text Character Definitions

The diagram below shows some example text character patterns. Each character is 6 pixels wide - the right-most 2 columns in the 8-pixel character block are not used. To define these patterns for characters >80 and >81, use the following <cdef> tags:


Internet web browser example character definitions

These characters can then be used in text displayed using the <p></p> tags. The <chr> tag can be used to specify a particular character code to display. The <clr> tag enables the text colour to be specified - each character has a single foreground colour and a single background colour. See the web page listing below for examples.

Displaying Graphics

Although 'graphics' could be displayed by defining and displaying text characters (as shown by the TI logo in the example web page listing and screenshot below), using the <8pchr> and <8pclr> tags provides more flexibility and colour control.

Using the <8pchr> tag, all 8 pixel columns of a character definition are displayed. To define the patterns below for characters >A0 and >A1, use the following <cdef> tags:


Internet web browser example character definitions

When using the <8pchr> tag, each character is displayed at a specified position. The position is based on the screen divided into 8*8 tiles, so positions are numbered from 0 (top left) to 767 (bottom right) - 24 rows of 32 tiles. To display the two characters above as the first two characters on row 3 of the screen:


Note that the character code must be exactly two upper-case hex digits and the character position must be exactly four upper-case hex digits.

To set the colour for a character, use the <8pclr> tag, where the colour is specified by 8 bytes. Each byte of the colour code specifies the colour of one row of the 8x8 character. The first nibble of each byte specifies the foreground colour, and the second byte specifies the background colour. The colour specified by an <8pclr> tag is stored for use by all following <8pchr> tags until changed by another <8pclr> tag. The default foreground and background colours for displaying characters using the <8pchr> tag are both transparent. The following example displays the two characters above, the first character as dark red on light yellow, and the second character as a rainbow effect on a black background.


Internet web browser example character definitions

Characters displayed using <8pchr> tags can be mixed with text displayed using the <p></p> tags. If displayed in the same position on the screen, whichever tags occur later in the web page source overwrite those that occur earlier in the page source.

As characters displayed using the <8pchr> tag are displayed at fixed positions on the screen, they cannot be scrolled. Consider including the <noscroll> tag on a page where <8pchr> tags are used to disable the keyboard scroll keys.

If wanting to display a very complex graphic, the 12,000 byte web page size limitation might be an issue. This can be got round by drawing part of the screen on one page, using the <loadpage> tag at the end of that page to automatically load a second page, including the <noclearscreen> tag at the top of the second page and continuing to draw the graphic. Obviously though there will be a delay while the second page loads.  The same technique can be used for simple animation by drawing a main graphic then automatically loading a further sequence of pages to draw updates over that graphic. An example <loadpage> tag is:


Hyperlinks Over Graphics

The <a></a> tags enable hyperlinks over text displayed with the <p></p> tags. To place hyperlinks over graphics, use the <arect> tag, which has the format <arect:AA:BB:CC:DD:(URL)>. AA, BB, CC and DD are upper-case hex values which define a rectangular area of pixels relative to the top left of the screen. If the <Return> key or <Fire> button is pressed, then if the screen pointer X position (in pixels, relative to the top left of the screen) is between the values AA and BB (inclusive) and the screen pointer Y position is between the values CC and DD (inclusive), then the link is invoked, and the specified URL is called with “?x=(pointer X position in hex)&y=(pointer Y position in hex)” appended to the URL (or "&x=..." if the link URL already contains a "?"). (Note that the screen pointer moves in steps of 2 pixels.)

Looking at the part of the page below, to place a hyperlink over the black cross, AA has the value 8, BB has the value 16, CC is 16 and DD is 24. With these values in hex, this gives the following <arect> tag:


Internet web browser example character definitions

Many <arect> tags can be used on a page (the limit is a total of around 25 <a> and <arect> hyperlinks per page). If any of the hyperlink areas overlap, clicking on an overlapping area selects the first of the overlapping hyperlinks defined in the web page source.

As the pointer position is appended to the hyperlink URL when the hyperlink is selected, this information can be further processed if required by a script on the linked page. For example, if hyperlinks are required over a chess board graphic, rather than defining one hyperlink per chess board square (which you can't do anyway as there are too many ...), define one hyperlink over the complete board then process the pointer information returned in the hyperlink URL to determine which square the pointer was over.

User-Agent Parameter

When making HTTP GET calls, the browser identifies itself with a User-Agent parameter value of "TI", enabling a web page script to see it is talking to the TI browser and format a page accordingly.

Example Web Page Listing

The following listing displays the example web page below which uses many of the supported tags. Space characters in the listing have been replaced with a small dot to aid legibility.

Internet web browser example page












back to home page