TI-99/4A

ARCHITECTURE & HARDWARE REFERENCE MANUAL
CPU • MEMORY MAP • RAM • ROM • GROM • VDP • I/O • BUS

00 System Overview

+

Introduction

The Texas Instruments TI-99/4A (1981–1984) was one of the first 16-bit home computers ever sold. Designed around the TMS9900 CPU — a chip derived from TI's minicomputer lineage — the machine predated the IBM PC and offered a genuinely advanced architecture for its era, albeit with several unusual design choices that both empowered and constrained developers.

The system sold approximately 2.8 million units before TI exited the home computer market in 1983. It remains beloved by hobbyists for its quirky architecture, powerful sprite hardware, and the active modern community still writing software for it today.

Key Specifications at a Glance

CPU

TMS9900 @ 3.0 MHz
16-bit registers & ALU
8-bit external data bus

RAM

256 bytes scratchpad
+ 16 KB (32KB w/exp.)
VDP: 16 KB VRAM

ROM

8 KB Console ROM
26 KB+ GROM
Cartridge up to 8 KB

Video

TMS9918A VDP
256×192 px, 15 colors
32 sprites, 3 modes

Sound

TMS9919 / SN76489
3 tone channels
1 noise channel

Storage

Cassette (CS1/CS2)
PEB: floppy/HDD
Solid-state cartridges

Architecture Philosophy

The 16-bit paradox: The TMS9900 is a genuine 16-bit CPU with 16-bit internal registers and ALU — but communicates with the outside world through an 8-bit multiplexed address/data bus. Every 16-bit memory access takes two bus cycles. This made the TI-99/4A both ahead of its time in internal architecture and hamstrung in real-world throughput.
Memory bottleneck: The CPU cannot directly address the system's 16 KB RAM expansion. That RAM lives on the expansion bus and is accessible only via a memory-mapped I/O mechanism. The CPU must use the 256-byte scratchpad RAM as its primary fast workspace, DMA-ing data from expansion RAM as needed — a significant constraint compared to contemporary 8-bit machines like the Apple II or Commodore 64.

01 CPU — TMS9900

+

Overview

The TMS9900 (Texas Instruments Metal-oxide Semiconductor 9900) is a 16-bit NMOS microprocessor introduced in 1976. It was TI's answer to the minicomputer market, shrunk onto a single chip. The TI-99/4A runs it at 3.0 MHz (3,000,000 cycles per second), though effective throughput is considerably lower due to the memory wait states imposed by the system design.

Internal Architecture

Data Width

16 bits internal
8 bits external
Multiplexed A/D bus

Address Space

64 KB linear
0x0000–0xFFFF
16-bit address bus

Clock

3.0 MHz crystal
CLKOUT = CLKIN/1
Φ1 and Φ2 phases

Technology

NMOS process
40-pin DIP package
+5V single supply

Register Architecture — Workspace Registers

Unique Design: The TMS9900 has no on-chip general-purpose registers. Instead, it uses a Workspace Pointer (WP) — a 16-bit register pointing to a block of 16 consecutive 16-bit words in RAM. These 16 words act as the working registers (R0–R15). Switching "register sets" is as fast as changing WP, enabling extremely rapid context switching for interrupts and subroutines.
WP — Workspace Pointer
16-bit
Points to 16-word register block in RAM. The heart of the TMS9900 model.
PC — Program Counter
16-bit
Always points to next instruction. Word-aligned (bit 0 always 0).
ST — Status Register
16-bit
Condition codes + interrupt mask level (bits 12–15).
R0–R15
16-bit each
Workspace registers in RAM. R0 = accumulator role. R11 = return address (BL). R13/R14/R15 saved on BLWP.
Status BitPosMeaning
L> Logical >15Set if result is logically greater than
A> Arithmetic >14Set if signed result is arithmetically greater
EQ Equal13Set if result equals zero / comparison equal
C Carry12Carry out from MSB
OV Overflow11Signed arithmetic overflow
OP Odd Parity10Odd number of 1-bits in byte result
XOP9Extended operation (XOP instruction used)
IM Int Mask3–0Interrupt priority mask (0=all enabled, F=all masked)

Instruction Set

The TMS9900 has a CISC-style instruction set with 72 distinct opcodes operating on bytes, words, and bits. Instructions are 16 bits wide (one or two words). Most instructions support multiple addressing modes.

Addressing Modes

ModeNotationExampleDescription
RegisterRnMOV R1,R2Operand is workspace register
Register Indirect*RnMOV *R1,R2Rn contains memory address of operand
Symbolic (Direct)@ADDRMOV @1000H,R1Absolute memory address follows opcode
Indexed@ADDR(Rn)MOV @TABLE(R3),R1Base address + register offset
Auto-Increment*Rn+MOV *R1+,R2Indirect; Rn incremented by 1 (byte) or 2 (word) after use
Immediate#nnLI R0,#1234HLiteral value in following word

Key Instructions

InstructionOpcodeCyclesDescription
MOV0x020014+Move word (16-bit copy)
MOVB0x0A0014+Move byte (8-bit copy)
A / AB0x060014+Add word / byte
S / SB0x070014+Subtract word / byte
MPY0x03C052Unsigned multiply (32-bit result in Rn:Rn+1)
DIV0x03D092+Unsigned divide
BL0x068012Branch and Link (return addr → R11)
BLWP0x040026Branch/Link with Workspace switch (full context save)
RTWP0x038014Return with Workspace Pointer (restore context)
LI0x020012Load Immediate (word into register)
SBO / SBZCRU ops12Set CRU bit one / zero (I/O bus)
TBCRU ops12Test CRU bit (I/O input)
LDCR / STCRCRU ops20+Load/Store n bits to CRU (serial I/O port)

Interrupt System

The TMS9900 supports 16 priority interrupt levels (0–15) via the 4-bit interrupt mask in the Status Register. Level 0 = highest priority (NMI-like). Level 4 is the normal VDP interrupt. The RESET vector is at 0x0000 and loads WP and PC from the first two words.

LevelVector AddressSource
0 (RESET)0x0000–0x0003Hardware reset
10x0004–0x0007External INT1 (VDP VBLANK on TI-99/4A)
20x0008–0x000BExternal INT2 (VDP HBLANK line — rarely used)
30x000C–0x000FExternal INT3 (Tape/keyboard)
40x0010–0x0013External INT4 (Speech synthesizer)
150x003C–0x003FXOP (software trap via XOP instruction)
Each interrupt vector is two words: the new WP address (word 0) and new PC address (word 1). Old WP, PC, and ST are automatically saved into R13, R14, R15 of the new workspace on entry.

Bus Timing & Wait States

The TMS9900's external bus runs at 3 MHz but every memory cycle requires multiple clock phases. The system inserts wait states for DRAM refresh and ROM access:

Scratchpad RAM
0 wait states
Console ROM
4 wait states
Cartridge ROM
4 wait states
16K RAM Exp.
4+ wait states
VDP (VRAM)
~6 wait states
Effective throughput: Due to wait states and the 8-bit external bus (requiring two cycles per 16-bit word), the practical instruction throughput averages roughly 0.2–0.4 MIPS despite the 3 MHz clock — slower than a 1 MHz 6502 in many real-world benchmarks.

02 Memory Map — Full 64KB Address Space

+

Address Map (0x0000 – 0xFFFF)

All addresses are in hexadecimal. The TMS9900 uses big-endian byte ordering (MSB at lower address). The address space is 64KB; only portions are populated.
0x0000–0x00FF
▓ Interrupt Vectors (64 words × 2 = 128 bytes, vectors for INT0–INT15)
0x0000–0x1FFF
▓ Console ROM (8 KB) — BIOS, GPLR interpreter, boot code
0x2000–0x3FFF
░ Low Expansion RAM (8 KB) — optional, rarely populated
0x4000–0x5FFF
▓ VDP Port (2 addresses: read/write data + read/write register)
0x6000–0x7FFF
▓ Cartridge ROM / GROM port (8 KB cartridge window)
0x8000–0x83FF
▓ Scratchpad RAM (256 bytes at 0x8300–0x83FF; mirrors fill block)
0x8400–0x9FFF
▓ Memory-mapped I/O: Sound (0x8400), Speech (0x9000), GROM (0x9800)
0xA000–0xFFFF
▓ 16 KB RAM Expansion (0xA000–0xDFFF) + upper ROM/expansion space

Detailed Region Breakdown

RangeSizeTypeDescription
0x0000–0x001F32BROM (vectors)INT0–INT15 vector pairs (WP,PC)
0x0000–0x1FFF8 KBConsole ROMBIOS, GPL interpreter, keyboard handler, VDP init, BASIC entry points
0x2000–0x3FFF8 KBLow RAM (opt.)Rarely installed; some mini-memory modules use this range
0x4000–0x400F16BVDP Read portMemory-mapped read from VDP data (0x4000) and VDP status (0x4002)
0x4010–0x5FFFVDP Write portWrite data to VDP (0x4000), write register/address (0x4002)
0x6000–0x7FFF8 KBCartridge ROMModule port — cartridge solid-state ROM mapped here
0x8000–0x82FF768BRAM (mirrors)Mirrors of the 256-byte scratchpad (3× mirror)
0x8300–0x83FF256BScratchpad RAMFast on-board SRAM; CPU workspace registers live here
0x8400–0x87FF1 KBSound chip I/OWrite-only port to TMS9919/SN76489 sound chip
0x8800–0x8BFF1 KBVDP status readRead VDP status register (address 0x8800)
0x8C00–0x8FFF1 KBVDP writeWrite to VDP data/register (0x8C00 data, 0x8C02 ctrl)
0x9000–0x93FF1 KBSpeech I/OSpeech synthesizer read/write ports
0x9800–0x9BFF1 KBGROM readRead from GROM data/address
0x9C00–0x9FFF1 KBGROM writeWrite to GROM address register
0xA000–0xDFFF16 KB32K RAM Exp.Upper portion of 32 KB RAM expansion (via PEB or side port)
0xE000–0xFFFF8 KBROM/ExpansionAdditional ROM space; second bank in 32K expansion

Critical Addresses

; RESET vectors (first thing the CPU reads on powerup) 0x0000: WP (Workspace Pointer for INT0/RESET) = 0x83E0 0x0002: PC (Program Counter for INT0/RESET) = 0x0000 (start of Console ROM) ; VDP Ports (Memory-mapped) 0x8800: VDP Status Register READ 0x8802: (mirror) 0x8C00: VDP Data Register WRITE 0x8C02: VDP Address/Register WRITE ; Scratchpad RAM layout (typical system use) 0x8300: Low scratchpad (GPL workspace, system variables) 0x83C0: Default CPU Workspace (R0–R15, 32 bytes) 0x83E0: RESET workspace pointer target 0x83F0: Stack area (grows downward) ; Sound chip 0x8400: Write byte → TMS9919 command register (write-only)

03 ROM — Console & Cartridge

+

Console ROM (8 KB)

The Console ROM at 0x0000–0x1FFF contains the fundamental firmware of the TI-99/4A. It is a mask-programmed, read-only memory — the only way to update it would be to replace the chip itself. TI released several console ROM versions during the machine's production life.

Interrupt Vectors

0x0000–0x001F: 16 vectors (WP+PC pairs) for all interrupt levels. RESET vector at 0x0000.

32 bytes
GPL Interpreter

Interpretive engine for GROM-based programs (GPL = Graphics Programming Language). The console ROM contains the entire GPL runtime.

~3 KB
VDP Driver

Low-level VDP initialization, screen clear, and character output routines. Called by BASIC and GPL programs.

~1 KB
Keyboard Driver

Scans the keyboard matrix via CRU bits. Returns keycode in workspace register. Handles repeat and shift states.

~1 KB
Boot Sequence

Initializes scratchpad RAM, checks for cartridge, presents the master title screen, launches GROM-based menu.

~1 KB
Math/String Lib

Floating-point arithmetic, BCD routines, and string manipulation called by TI BASIC ROM (in GROM).

~2 KB

Boot Sequence (Power-On Reset)

POWER ON → CPU reads vector at 0x0000 → Loads WP = 0x83E0 (scratchpad workspace) → Loads PC = address in Console ROM Console ROM starts: 1. Initialize scratchpad RAM (zero-fill 0x8300–0x83FF) 2. Set up VDP: 40-col text mode, screen on 3. Check CRU bit for cartridge present at 0x6000 YES → Read cartridge header (GPL header at 0x6000) Launch cartridge GPL program NO → Load GROM address 0x0000 (system GROM) Run GPL interpreter → Master Title Screen 4. User selects: TI BASIC, cartridge, or peripheral

Cartridge ROM (0x6000–0x7FFF)

The cartridge slot maps 8 KB of ROM into the CPU address space at 0x6000–0x7FFF. Cartridge ROMs are solid-state (no moving parts) — a major selling point of the system. The cartridge also contains GROM chips (up to 40 KB of additional GPL program data) accessed via the GROM port, not directly by the CPU.

Cartridge header structure (at 0x6000): The first 8 bytes are a standard header. Byte 0–1: GPL header magic (>AA 01). Byte 2–3: Pointer to program name string. Byte 4–5: Pointer to GPL program start. Byte 6–7: Link to next header (for multi-program cartridges). The console ROM reads this to display the module's name in the menu.
ByteContentNotes
0x60000xAAGPL magic byte 1
0x60010x01GPL magic byte 2 (ROM program flag)
0x6002–3Name ptrPoints to null-terminated program name string
0x6004–5Entry ptrGPL or assembly entry point (BLWP or B)
0x6006–7Next ptr0x0000 if single program, else next header

04 RAM — Scratchpad & Expansion

+

Scratchpad RAM — 256 Bytes (0x8300–0x83FF)

The scratchpad RAM is the only RAM built into the TI-99/4A console itself. It is a fast static RAM (SRAM) chip — zero wait states, accessible at full bus speed. Despite its tiny size, it is absolutely critical: it holds the CPU workspace registers, the GPL interpreter's working variables, and the top of the system stack.

Only 256 bytes! By comparison, the Apple II had 4 KB standard RAM and the Commodore 64 had 64 KB. The TI-99/4A's on-board RAM is smaller than a single sector on a floppy disk. Every byte must be carefully managed when writing assembly language programs.
Scratchpad RAM layout (0x8300–0x83FF): 0x8300–0x833F GPL system workspace (variables, stack frame) 0x8340–0x835F BASIC/GPL user variable area 0x8360–0x837F VDP shadow registers, screen state 0x8380–0x839F Keyboard scan buffer, joystick state 0x83A0–0x83BF Cassette I/O buffer, misc system flags 0x83C0–0x83DF Default CPU workspace (R0–R15 = 32 bytes) 0x83E0–0x83EF RESET workspace (R0–R7, 16 bytes) 0x83F0–0x83FF System stack (grows DOWN from 0x83FF)

The 256-byte block is mirrored at 0x8000, 0x8100, and 0x8200 due to incomplete address decoding — a common technique in 1980s hardware design to simplify chip-select logic.

16 KB RAM Expansion (0xA000–0xDFFF)

The standard memory expansion for the TI-99/4A adds 32 KB of DRAM in two 16 KB banks. The first bank (0x2000–0x3FFF) is rarely used. The second bank (0xA000–0xDFFF) is the standard "expansion RAM" used by TI Extended BASIC, assembly programs, and most third-party software.

Technology

Dynamic RAM (DRAM)
Requires refresh cycles
4+ wait states

16 KB bank
Location

Peripheral Expansion Box (PEB) or
Side-port memory module

External
Access

CPU can address directly BUT must go through expansion bus — adds latency vs scratchpad

8-bit bus
Extended BASIC

TI Extended BASIC requires 32 KB expansion. Uses both banks (0x2000 and 0xA000).

Required
The RAM bottleneck in detail: When the CPU at 3 MHz accesses the expansion RAM, it must: (1) output the address on the multiplexed bus, (2) wait for the DRAM RAS cycle, (3) wait for the CAS cycle, (4) transfer the byte, then (5) repeat for the second byte of a 16-bit word. Total: 8–12 clock cycles per 16-bit access vs 4–6 for scratchpad. Programs should keep hot code and critical variables in scratchpad.

VRAM — Video Display Processor RAM (16 KB)

The TMS9918A VDP has its own dedicated 16 KB of VRAM, completely separate from the CPU's address space. The CPU cannot directly address VRAM — it must communicate through the VDP's two I/O ports. This is a crucial architectural distinction.

VRAM RegionTypical Use (Graphics 2 mode)
0x0000–0x17FFPattern Name Table (768 bytes — 32×24 screen)
0x1800–0x1AFFSprite Attribute Table (128 bytes)
0x2000–0x27FFPattern Generator Table (2 KB — character bitmaps)
0x3800–0x3BFFSprite Pattern Table (2 KB — sprite bitmaps)
0x2000–0x3FFFColor Table (6 KB in Graphics 2 mode)
Accessing VRAM: Write address to VDP port 0x8C02 (with bit 14 set for write, clear for read), then read/write data bytes sequentially via port 0x8C00. The VDP auto-increments its internal address pointer — allowing burst transfers of screen data.

05 GROM — Graphics ROM & GPL

+

What is GROM?

GROM (Graphics ROM) is a proprietary TI chip that stores programs in a special byte-code language called GPL (GROM Programming Language, also called Graphics Programming Language). GROMs are accessed through a dedicated port — the CPU does not address them directly in its memory map. Instead, the CPU writes an address to the GROM port and reads back bytes of GPL code, which the GPL interpreter in Console ROM then executes.

Why GROMs? GROMs allowed TI to pack up to 40 KB of program data into a cartridge while only exposing one 8 KB window to the CPU address space. The GROM chips daisy-chain on a shared bus. Each GROM contains 6 KB of code (not a full 8 KB — the chip design uses a 13-bit internal counter). Up to 8 GROMs can exist per device: GROM 0–7.

GROM Address Space (24-bit effective)

GROM #Byte RangeContent
GROM 00x0000–0x17FFConsole system GROM: Master title screen, system menu, font data
GROM 10x2000–0x37FFConsole system GROM 1: TI BASIC language core (part 1)
GROM 20x4000–0x57FFConsole system GROM 2: TI BASIC language core (part 2)
GROM 3–70x6000–0xFFFFCartridge GROMs (module-specific programs)

GROMs 0, 1, and 2 are physically inside the TI-99/4A console. GROMs 3–7 are in cartridges. The 3 console GROMs contain approximately 18 KB of system software.

GPL — Graphics Programming Language

GPL is a compact, stack-based byte-code language interpreted by the Console ROM's GPL engine. It is higher-level than machine code but runs much slower (roughly 1/10 the speed of native TMS9900 assembly). Most TI-written cartridges use GPL for their main logic and call assembly routines for speed-critical sections.

GPL instruction examples (all single-byte opcodes): 0x00 NOP No operation 0x02 nn BR addr Branch to GPL address 0x04 nn BST addr Branch to subroutine 0x06 RT Return from subroutine 0x24 nn MOVE Move data in GROM space 0x46 nn CALL nn Call assembly routine (BLWP) 0x60 nn RAND nn Generate random number 0x88 nn LOAD reg Load GPL register 0xCA VWTR Write to VDP register 0xCB VWRD Write data to VRAM

GROM Port Access

; How to read from GROM (CPU assembly): ; Set GROM address (two writes to 0x9C02): LI R0, GROM_ADDR ; address to read MOVB @R0, @0x9C02 ; write high byte (address MSB) MOVB @R0+1, @0x9C02 ; write low byte (address LSB) ; Read GROM data byte: MOVB @0x9800, R1 ; R1 = byte at GROM address ; GROM auto-increments address

06 VDP — TMS9918A Video Display Processor

+

Overview

The TMS9918A is a dedicated video processor with its own 16 KB of VRAM, completely independent of the CPU. It generates a composite NTSC (or PAL for European models) video signal. The CPU communicates via two I/O ports. The VDP generates a VBLANK interrupt (INT1, level 1) at the end of each frame (~60 Hz NTSC).

Resolution

256 × 192 active pixels
Tile-based rendering
8×8 pixel characters

Colors

15 colors + transparent
2 colors per 8×1 pixel row
16-color palette (fixed)

Sprites

32 sprites maximum
Up to 4 per scanline
8×8 or 16×16 size
2× magnification option

Video Output

Composite NTSC/PAL
RF modulated (built-in)
No direct RGB output

Screen Modes

ModeResolutionColorsUse Case
Text Mode (M1)40×24 chars2 (fg+bg only)TI BASIC default. 240 chars on screen. No sprites. Monochrome.
Graphics 1 (M0)32×24 chars2 per group of 8 charsGeneral use. 256 tile patterns. 32 sprites.
Graphics 2 (bitmap)256×192 px2 per 8px rowTrue bitmap mode. 6 KB color table. Used for games.
Multicolor64×48 blocks16 per blockEach 4×4 block has one color. Limited resolution, many colors.

VDP Registers (R0–R7)

RegisterPurposeKey Bits
R0Mode control 1M3 (bitmap), external video, sprite size, sprite magnify
R1Mode control 2M1/M2 (text/gfx), VBLANK enable, display enable, 16×16 sprite
R2Name Table BaseVRAM address >> 10 (which 1KB block holds the screen map)
R3Color Table BaseVRAM address >> 6
R4Pattern Gen. BaseVRAM address >> 11 (character/tile bitmap data)
R5Sprite Attr. BaseVRAM address >> 7 (sprite Y,X,name,color table)
R6Sprite Pat. BaseVRAM address >> 11 (sprite bitmap data)
R7Text/Border ColorHigh nibble = fg color, low nibble = bg/border color

Color Palette (16 Fixed Colors)

#ColorRGBNotes
0TransparentShows backdrop/border color
1Black000
2Medium Green3320066Default screen color
3Light Green94220120
4Dark Blue8485237
5Light Blue125118252
6Dark Red2128277
7Cyan66235245
8Medium Red2528584
9Light Red255121120
10Dark Yellow21219384
11Light Yellow230206128
12Dark Green3317659
13Magenta20191186
14Gray204204204
15White255255255

Sprite Architecture

The VDP supports 32 sprites with hardware collision detection. The Sprite Attribute Table in VRAM defines each sprite:

Sprite Attribute Table entry (4 bytes per sprite): Byte 0: Y position (0–191, 0xD0 = end of sprite list) Byte 1: X position (0–255) Byte 2: Pattern number (0–255, or 0–63 for 16×16) Byte 3: Color (bits 3–0) | Early Clock (bit 7, shifts sprite left 32px)
Sprite limit: Only 4 sprites per horizontal scanline. The 5th sprite on any line is not drawn, and the VDP sets a coincidence flag in the status register. This is the "sprite flicker" seen in many TI-99/4A games — programmers rotate sprite priorities each frame to make all sprites appear.

07 I/O — CRU Bus & Peripherals

+

The CRU — Communications Register Unit

The TMS9900's CRU (Communications Register Unit) is a serial bit-addressable I/O bus — unique to TI's architecture. Instead of traditional parallel I/O ports, the CRU allows the CPU to read or write individual bits or groups of up to 16 bits at a time. The CRU address space is 4096 bits (512 bytes equivalent) mapped to a separate address region.

The CRU uses dedicated instructions: SBO (Set Bit One), SBZ (Set Bit Zero), TB (Test Bit), LDCR (Load CRU — write n bits), STCR (Store CRU — read n bits). The CRU base address register is the upper byte of workspace register R12 — meaning each software module can have its own CRU base.
CRU Base AddrPeripheralBits Used
0x0000–0x001EKeyboard columns 0–78 columns select (output)
0x0000–0x000EKeyboard row scan8 rows read (input)
0x0010–0x001EJoystick inputFire, up, down, left, right
0x0100–0x010ECassette output (CS1)1 bit write (motor + data)
0x0110–0x011ECassette input (CS2)1 bit read (data)
0x0200–0x020EAlpha Lock key1 bit (key state)
0x1000–0x17FEPEB expansion busPeripheral cards use these

Keyboard Interface

The TI-99/4A keyboard is a 8×8 matrix (8 columns × 8 rows = 64 key positions, though not all are populated). The CPU scans it by:

  1. Writing a column number (0–7) to CRU bits via LDCR
  2. Reading the 8 row bits via STCR
  3. Checking which rows have a key pressed (active low)
; Keyboard scan example (TMS9900 assembly): ; R12 = CRU base for keyboard = 0x0000 LI R12, 0x0000 ; CRU base = keyboard LI R0, 0x0300 ; select column 3, right-shifted LDCR R0, 3 ; output 3 bits → selects column STCR R1, 8 ; read 8 row bits into R1 ; R1 now has bitmask of pressed keys in column 3

Cassette Interface (CS1 / CS2)

Two cassette ports (CS1 primary, CS2 secondary) use frequency-shift keying (FSK) at 1200 baud. The Console ROM contains the cassette read/write routines. Data is stored as audio tones: ~2400 Hz for a '1' bit and ~1200 Hz for a '0' bit. The motor relay is controlled via CRU bit.

Joystick Ports

Two 9-pin Atari-compatible joystick ports. Directions and fire button are read via CRU bits. The joystick inputs share CRU addresses with the keyboard rows when specific columns are selected. The Console ROM's joystick subroutines handle the multiplexing.

08 System Bus & Expansion

+

The TI-99/4A Expansion Bus

The right side of the TI-99/4A console has a 44-pin edge connector exposing the full system bus: address lines, data lines, control signals, CRU lines, and power. This connects to the Peripheral Expansion Box (PEB) via a flat cable, or to direct-connect peripherals like the Speech Synthesizer.

Address Bus

A0–A14 (15 bits) + MEMEN signal. A15 not bused out. Limits peripherals to 32 KB address window each.

15-bit
Data Bus

D0–D7 (8 bits). Only 8 bits external despite 16-bit CPU. Two cycles per word.

8-bit
CRU Bus

CRUIN, CRUOUT, CRUCLK lines allow serial I/O to peripheral cards. Each card decodes a unique CRU base address.

serial
Control Signals

MEMEN (memory access), WE (write enable), DBIN (data bus input), READY (wait state insert), IAQ (instruction acquisition)

control

Peripheral Expansion Box (PEB)

The PEB is a large external chassis housing up to 8 peripheral cards on an internal bus. Each card plugs into the PEB backplane and is connected to the console via a proprietary ribbon cable. Available cards include:

CardDescription
32 KB RAM ExpansionProvides the full 32 KB expansion RAM at 0x2000–0x3FFF and 0xA000–0xDFFF
Disk Controller (DSDD)Controls up to 3 floppy drives (90K–360K each). Uses DSK1/DSK2/DSK3 device names.
RS-232 CardTwo serial ports (RS-232C, up to 9600 baud) + one parallel port. Uses CRU base 0x1300.
80-Column CardAdds 80×24 text mode via a second video chip. Non-standard, few programs support it.
UCSD Pascal CardZ80 coprocessor card running UCSD Pascal. Completely takes over the system.
Myarc 128K/512K RAMThird-party expanded RAM beyond the standard 32 KB, with bank switching.
Horizon RAMdiskUp to 2 MB battery-backed RAM acting as a solid-state disk drive.

09 Sound — TMS9919 / SN76489

+

Sound Chip Architecture

The TI-99/4A uses the TMS9919 (functionally equivalent to the SN76489) programmable sound generator. It is a write-only device — the CPU can only send commands, never read its state. Accessed at I/O address 0x8400.

Tone Channels

3 independent tone generators
Frequency: ~109 Hz – 111 KHz
10-bit frequency divider each

Noise Channel

1 noise generator
White or periodic noise
3 frequency steps + tone 3 sync

Volume

4-bit attenuation each channel
0 = max volume, 15 = silence
No hardware envelope

Output

Single mixed mono output
Digital (square wave)
No FM, no PCM

Register Map & Command Bytes

All commands are single or two-byte writes to port 0x8400:

Command byte format: Bit 7: 1 = LATCH byte (first byte), 0 = DATA byte (second byte) Bit 5-4: Channel select (00=tone1, 01=tone2, 10=tone3, 11=noise) Bit 3: Type (0=frequency/noise, 1=volume/attenuation) Tone frequency (2 bytes): Byte 1: 1 CC T DDDD (latch, channel, type=0, data low 4 bits) Byte 2: 0 0 DDDDDD (data high 6 bits) Frequency = 3,579,545 / (32 × N) where N = 10-bit value Volume command (1 byte): Byte: 1 CC 1 AAAA (latch, channel, type=1, attenuation 0-15) Noise command (1 byte): Byte: 1 11 0 0 W FF (latch, noise channel, type=0, white, feedback) FF: 00=low, 01=medium, 10=high, 11=use tone3 frequency Example: Set tone channel 1 to 440 Hz (A4): N = 3579545 / (32 × 440) = 254.2 → N = 254 (0x0FE) Byte 1: 1 00 0 1110 = 0x8E (low 4 bits of 0x0FE = 0xE) Byte 2: 0 0 001111 = 0x0F (high 6 bits of 0x0FE = 0x0F) Volume: 1 00 1 0000 = 0x90 (channel 1, volume max)

10 Expansion — Speech & Other Chips

+

Speech Synthesizer (TMS5200 / TMS5220)

The TI-99/4A Speech Synthesizer is an external module plugging into the side expansion port. It contains a TMS5200 (or later TMS5220) Linear Predictive Coding (LPC) speech processor, 128 KB of speech ROM (vocabulary), and connects to the console's I/O bus.

Technology

LPC (Linear Predictive Coding) synthesis
10th-order filter
8 KHz sample rate

Vocabulary

~350 words in built-in ROM
Expandable via module ROMs
Human-quality for 1980

CPU Access

Memory-mapped at 0x9000
Read: 0x9000 (status)
Write: 0x9400 (data)

Interrupt

Can trigger INT4 (level 4) when speech data buffer empty or ready

; Speaking a word via TMS5220 (simplified): MOVB #0x70, @0x9400 ; Send SPEAK command MOVB #0x00, @0x9400 ; Address high byte of word MOVB #0x28, @0x9400 ; Address low byte ("hello" word) ; Poll for completion: WAIT: MOVB @0x9000, R1 ; Read status byte ANDI R1, #0x8000 ; Check TALK bit JNE WAIT ; Still talking?

Other Notable Chips in the Console

ChipFunctionNotes
TMS9901Programmable Systems InterfaceCRU-addressed I/O expander. Handles keyboard, joystick, cassette via its internal registers. Bridges CRU to parallel I/O.
TMS9904Clock generator / oscillatorGenerates the 3 MHz system clock from a crystal. Provides PHI1/PHI2 clock phases to TMS9900.
74LS1383-to-8 decoder (address decode)Decodes upper address bits to generate chip select signals for ROM, RAM, VDP, GROM, and I/O.
SN74LS245Bus transceiverBidirectional buffer on the data bus. Handles direction control (DBIN/WE) between CPU and peripherals.
4116 / 4164DRAM (16K × 1 bit)16 chips × 1 bit each = 16 KB VRAM for VDP. Also used in 32 KB expansion modules.

11 Bus Timing & Signal Diagrams

+

Memory Read Cycle (with wait states)

Clock: _____|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|___ Φ1 Φ2 Φ1 Φ2 W1 W2 W3 W4 (4 wait states shown) ADDR: -----XXXXXXXXXXXXXXXXXXXXXXXXXXX---- [Address valid from T1 through T-end] MEMEN: ‾‾‾‾|___________________________________|‾‾ [Low = memory access in progress] DBIN: ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾ [High = CPU reading from bus] DATA: -------zzzzzzzzzzzzXXXXXXXXXX------ [Data must be valid before DBIN rises] READY: ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾ [Peripheral pulls READY low to insert waits] [Releases READY → CPU proceeds]

VDP VBLANK Interrupt Timing

NTSC Frame: 16.67 ms (60 Hz) VDP raster: Active display: 0–191 scanlines (192 × 63.5 µs = ~12.2 ms) VBLANK: 192–261 lines (~4.4 ms available for CPU) INT assertion: After line 192 (INT1 pin goes LOW) CPU response: Current instruction completes CPU checks interrupt mask (ST bits 3-0 ≤ 1 for INT1) BLWP to vector at 0x0004: Save WP→R13, PC→R14, ST→R15 Load new WP and PC from 0x0004–0x0007 ISR executes (~4 ms budget before next VBLANK) RTWP restores WP, PC, ST VDP status register (read 0x8800): Bit 7: INT flag (1 = interrupt pending; READ CLEARS IT) Bit 6: Sprite overflow (5th sprite on line) Bit 5: Sprite collision Bits 4-0: 5th sprite number (when overflow)

System Architecture Summary

┌─────────────────────────────────────────────┐ │ TI-99/4A SYSTEM BUS │ │ │ │ ┌─────────┐ 8-bit data bus ┌────────┐ │ │ │ TMS9900 │◄──────────────────►│Console │ │ │ │ CPU │ 15-bit addr bus │ ROM │ │ │ │ 3.0 MHz │◄──────────────────►│ 8 KB │ │ │ └────┬────┘ └────────┘ │ │ │CRU serial bus │ │ ┌────▼────┐ ┌─────────┐ ┌────────────┐ │ │ │ TMS9901 │ │Scratchpad│ │ TMS9918A │ │ │ │ PSI │ │RAM 256B │ │ VDP+16KVRAM│ │ │ └─────────┘ └─────────┘ └────────────┘ │ │ │ │ │ Keyboard Joystick Cassette │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ Expansion Bus (44-pin edge) │ │ │ │ ┌────────┐ ┌───────┐ ┌─────────┐ │ │ │ │ │32KB RAM│ │Floppy │ │ Speech │ │ │ │ │ │Exp. │ │Disks │ │TMS5220 │ │ │ │ │ └────────┘ └───────┘ └─────────┘ │ │ │ └─────────────────────────────────────┘ │ │ │ │ GROM bus (serial): │ │ ┌────────┐ ┌────────┐ ┌────────────────┐ │ │ │GROM 0 │ │GROM 1 │ │Cartridge GROM │ │ │ │6KB │ │6KB │ │(up to 40KB) │ │ │ └────────┘ └────────┘ └────────────────┘ │ │ │ │ Sound: TMS9919 @ 0x8400 (write-only) │ │ 3 tone channels + 1 noise, 4-bit volume │ └─────────────────────────────────────────────┘

Programming Tips — Assembly

Speed tips for TMS9900 programmers:
1. Keep your workspace (R0–R15) in scratchpad RAM (0x83C0–0x83DF) — zero wait states.
2. Use MOV (word) instead of two MOVB (byte) instructions where possible.
3. Minimize access to the 16 KB expansion RAM — reads cost 3× more than scratchpad.
4. Do heavy lifting during VBLANK (lines 192–261) to avoid screen tears.
5. Use the auto-increment mode *Rn+ for bulk memory copies — fewer instruction fetches.
Memory model summary:
Code: Cartridge ROM (0x6000) or expansion RAM (0xA000+)
Fast data: Scratchpad RAM (0x8300–0x83FF) — 256 bytes only!
Bulk data: 16 KB expansion RAM (0xA000–0xDFFF)
Screen data: VDP VRAM (via port writes to 0x8C00) — separate 16 KB
Programs: GROM (via port reads from 0x9800) — up to 40 KB GPL bytecode

12 TI BASIC — Complete Quick Reference

+

About TI BASIC

TI BASIC is built into the three console GROMs (18 KB total). Every TI-99/4A has it — no cartridge needed. Programs use line numbers 1–32767. The interpreter is GPL-based and runs slowly but is always available. Line numbers govern execution order; RUN starts from the lowest. Variables are global, untyped (numeric or string). Strings end with $.

▶ Program Flow & Control
REM
REM remark text
Comment — ignored by interpreter. Documents code. Cannot appear after a statement on the same line.
100 REM *** MAIN LOOP *** 110 REM WRITTEN BY: JOE
END
END
Terminates program execution and returns to BASIC prompt. Optional if program naturally runs out of lines.
900 PRINT "DONE" 910 END
STOP
STOP
Halts execution with "PROGRAM STOPPED" message. Can be resumed with CONTINUE (CON). Useful for debugging.
200 IF X>10 THEN STOP 210 PRINT "X IS OK"
GOTO
GOTO linenum
Unconditional branch to specified line number. Line must exist or error 0002.
100 PRINT "HELLO" 110 GOTO 100
GOSUB
GOSUB linenum
Branch to subroutine at linenum. Return address is saved on the RETURN stack. RETURN comes back here.
100 GOSUB 500 110 PRINT "BACK" 500 PRINT "SUB" 510 RETURN
RETURN
RETURN
Returns execution to the line after the most recent GOSUB. Error if no GOSUB was executed (stack empty).
500 PRINT "IN SUB" 510 RETURN
IF / THEN / ELSE
IF cond THEN line [ELSE line]
Conditional branch. THEN and ELSE targets can be line numbers or statements. ELSE is optional.
100 IF X=5 THEN 200 ELSE 300 200 PRINT "FIVE" :: GOTO 400 300 PRINT "NOT FIVE"
FOR / NEXT
FOR var=start TO end [STEP n]
NEXT var
Counting loop. STEP defaults to 1. Negative STEP counts down. Loop body between FOR and NEXT.
100 FOR I=1 TO 10 110 PRINT I 120 NEXT I
WHILE / WEND
WHILE condition
WEND
Loops while condition is true. Condition tested at top — may never execute. Nesting allowed.
100 X=1 110 WHILE X<=5 120 PRINT X :: X=X+1 130 WEND
ON GOTO
ON expr GOTO line,line,...
Computed GOTO. Jumps to the Nth line in list where N=INT(expr). N must be 1 or more.
100 X=2 110 ON X GOTO 200,300,400 200 PRINT "ONE" :: GOTO 500 300 PRINT "TWO" :: GOTO 500 400 PRINT "THREE"
ON GOSUB
ON expr GOSUB line,line,...
Computed GOSUB. Calls Nth subroutine in list. Returns to next statement after ON GOSUB.
100 M=2 110 ON M GOSUB 500,600 500 PRINT "SUB1" :: RETURN 600 PRINT "SUB2" :: RETURN
ON ERROR
ON ERROR GOTO line
Traps runtime errors. When an error occurs, jumps to linenum instead of stopping. ERR and ERRLN hold error code/line.
100 ON ERROR GOTO 900 110 X=1/0 900 PRINT "ERR ";ERR;" AT ";ERRLN
▶ Output & Display
PRINT
PRINT [expr][,][;][expr]...
Outputs to screen. Comma tabs to next 14-char zone. Semicolon suppresses space/newline. PRINT alone prints blank line.
100 PRINT "HELLO","WORLD" 110 PRINT "X=";X;" Y=";Y 120 PRINT
PRINT AT
PRINT AT pos [,size]: expr
Prints at a specific screen position (0-767 in 40-col mode: row*40+col). Optional size for field width.
100 PRINT AT 320:"MIDDLE" 110 PRINT AT 0,40:"TOP ROW"
DISPLAY
DISPLAY [AT pos] [,ERASE line]: expr
Like PRINT AT but uses DISPLAY mode conventions. ERASE erases N lines before printing. Preferred for full-screen output.
100 DISPLAY AT(1,1):"NAME:" 110 DISPLAY AT(1,7)ERASE 1:N$
TAB
PRINT TAB(n); expr
Moves print position to column N (1-based) before printing. Only moves forward; ignored if already past N.
100 PRINT TAB(10);"HI" 110 PRINT "A";TAB(20);"B"
▶ Input
INPUT
INPUT [prompt:] var[,var...]
Displays prompt (or ? if none), waits for user entry, stores result. Multiple variables separated by commas on entry.
100 INPUT "NAME: ":N$ 110 INPUT "X,Y: ":X,Y 120 PRINT "HI ";N$
ACCEPT
ACCEPT [AT pos] [,BEEP] [,VALIDATE(...)]: var
Advanced input at screen position. BEEP alerts on invalid entry. VALIDATE limits accepted characters or range.
100 ACCEPT AT(24,1),BEEP:X$ 110 ACCEPT AT(1,1),VALIDATE(DIGIT):N
INPUT (file)
INPUT #filenumber: var[,var]
Reads from an open sequential file. Each call reads the next record. File must be opened in INPUT mode first.
100 OPEN #1:"DSK1.DATA",INPUT 110 INPUT #1:A$,B 120 CLOSE #1
▶ Variables & Assignment
LET (assignment)
LET var = expr (or just: var = expr)
Assigns value to variable. LET keyword optional. Numeric vars: undecorated name. String vars: name with $.
100 LET X=42 110 Y=X*2 120 N$="TEXAS"
DIM
DIM var(dim[,dim]) [,var...]
Declares array dimensions. 1D or 2D numeric/string arrays. Index starts at 0. Default max is 10 if not DIM'd.
100 DIM A(20),B(5,5) 110 DIM N$(10) 120 A(3)=99 :: PRINT A(3)
▶ Math Functions
ABS
ABS(n)
Returns absolute (positive) value of n.
100 PRINT ABS(-15) REM OUTPUT: 15
INT
INT(n)
Returns greatest integer less than or equal to n (floor). Negative numbers truncate toward negative infinity.
100 PRINT INT(3.9) REM OUTPUT: 3 110 PRINT INT(-3.1) REM OUTPUT: -4
SQR
SQR(n)
Square root of n. Error if n is negative.
100 PRINT SQR(144) REM OUTPUT: 12
SGN
SGN(n)
Returns sign: -1 if n<0, 0 if n=0, 1 if n>0.
100 PRINT SGN(-99);" ";SGN(0);" ";SGN(7) REM OUTPUT: -1 0 1
RND
RND
Returns a pseudo-random number 0 <= RND < 1. Use RANDOMIZE (or RANDOMIZE TIMER in XB) to seed.
100 RANDOMIZE 110 PRINT INT(RND*6)+1 REM OUTPUT: 1–6 (dice roll)
SIN / COS / TAN
SIN(n) / COS(n) / TAN(n)
Trigonometric functions. Argument in radians. Results in floating-point.
100 PI=3.14159265 110 PRINT SIN(PI/2) REM OUTPUT: 1
ATN
ATN(n)
Arc tangent of n in radians. Derive other inverse trig via standard identities.
100 PI=4*ATN(1) 110 PRINT PI REM OUTPUT: 3.14159265
EXP
EXP(n)
e raised to the power n (natural exponential). e ≈ 2.71828.
100 PRINT EXP(1) REM OUTPUT: 2.71828183
LOG
LOG(n)
Natural logarithm (base e) of n. For log base 10: LOG(n)/LOG(10).
100 PRINT LOG(EXP(1)) REM OUTPUT: 1 110 PRINT LOG(100)/LOG(10) REM OUTPUT: 2
MAX / MIN
MAX(a,b[,...]) / MIN(a,b[,...])
Returns largest/smallest of two or more values. Any number of arguments.
100 PRINT MAX(3,7,2,9,1) REM OUTPUT: 9 110 PRINT MIN(3,7,2) REM OUTPUT: 2
MOD (operator)
a MOD b (or: a-INT(a/b)*b)
Modulo (remainder after division). Built-in as MOD in Extended BASIC; in TI BASIC use the formula.
100 PRINT 17 MOD 5 REM OUTPUT: 2 110 PRINT 17-INT(17/5)*5 REM OUTPUT: 2 (TI BASIC)
▶ String Functions
LEN
LEN(str$)
Returns number of characters in string. Empty string returns 0.
100 A$="HELLO" 110 PRINT LEN(A$) REM OUTPUT: 5
SEG$
SEG$(str$, start, length)
Returns a substring. Start is 1-based. Returns up to length characters. TI's equivalent of MID$.
100 A$="TEXAS" 110 PRINT SEG$(A$,2,3) REM OUTPUT: EXA
POS
POS(str$, search$, start)
Finds first occurrence of search$ in str$ starting at position start. Returns 0 if not found.
100 PRINT POS("HELLO","LL",1) REM OUTPUT: 3
STR$
STR$(n)
Converts numeric value to its string representation.
100 N=42 110 A$=STR$(N) 120 PRINT "VALUE IS "&A$ REM OUTPUT: VALUE IS 42
VAL
VAL(str$)
Converts a string containing a number to its numeric value. Returns 0 if string is non-numeric.
100 A$="3.14" 110 PRINT VAL(A$)*2 REM OUTPUT: 6.28
CHR$
CHR$(n)
Returns the character whose ASCII code is n (0–127). Useful for special characters and control codes.
100 PRINT CHR$(65) REM OUTPUT: A 110 PRINT CHR$(7) REM (BEEP sound)
ASC
ASC(str$)
Returns the ASCII code (integer) of the first character of str$.
100 PRINT ASC("A") REM OUTPUT: 65 110 PRINT ASC("Z")-ASC("A") REM OUTPUT: 25
STRING$ (& concat)
A$&B$ (concatenation)
The & operator joins two strings. TI BASIC has no STRING$() function — repeat with a loop instead.
100 A$="GOOD "&"MORNING" 110 PRINT A$ REM OUTPUT: GOOD MORNING
▶ Screen & Graphics
CALL CLEAR
CALL CLEAR
Clears the screen (fills with spaces) and homes the cursor to top-left.
100 CALL CLEAR 110 PRINT "FRESH START"
CALL SCREEN
CALL SCREEN(color)
Sets the screen background/border color. Colors are 1–16 (TMS9918A palette numbers).
100 CALL SCREEN(2) REM Sets background to medium green
CALL COLOR
CALL COLOR(charset,fg,bg)
Sets foreground and background colors for a character set (1–8, groups of 32 chars each). Colors 1–16.
100 CALL COLOR(1,16,1) REM Charset 1: white on black 110 PRINT "WHITE TEXT"
CALL HCHAR
CALL HCHAR(row,col,charcode[,repeat])
Places character at row,col (1-based). Optional repeat fills horizontally. Fast — writes directly to VRAM.
100 CALL HCHAR(12,15,42,10) REM Prints 10 asterisks (*=42) at row 12
CALL VCHAR
CALL VCHAR(row,col,charcode[,repeat])
Like HCHAR but fills vertically downward. Wraps to next column when reaching bottom.
100 CALL VCHAR(1,20,124,24) REM Vertical bar (|=124) down col 20
CALL CHAR
CALL CHAR(charcode, pattern$)
Redefines a character's bitmap. Pattern$ is 16 hex digits (8 bytes = 8×8 pixel bitmap). Codes 32–143 user-definable.
100 CALL CHAR(96,"3C7EFFFFFFFF7E3C") REM Defines char 96 as a filled circle 110 CALL HCHAR(12,16,96)
CALL GCHAR
CALL GCHAR(row,col,charvar)
Reads the character code at a given screen position into a numeric variable. Useful for collision detection.
100 CALL GCHAR(12,16,C) 110 IF C=96 THEN PRINT "HIT!"
CALL LOCATE
CALL LOCATE(sprite,row,col)
Moves a sprite to specified row and column (pixel-based: row 1–192, col 1–256). Smooth positioning.
100 CALL SPRITE(#1,64,16,1,1) 110 CALL LOCATE(#1,96,128) REM Move sprite #1 to screen center
▶ Sprites
CALL SPRITE
CALL SPRITE(#n, char, color, row, col)
Creates or repositions sprite #n using char as the bitmap (must be CALL CHAR defined). Color 1–16. Row/col in pixels.
100 CALL CHAR(128,"003C7EFFFEFE7E3C") 110 CALL SPRITE(#1,128,16,96,120) REM White filled blob at screen center
CALL DELSPRITE
CALL DELSPRITE(#n[,#m,...])
Removes (hides) one or more sprites. Use #ALL to remove all sprites at once.
100 CALL DELSPRITE(#1,#2) 110 CALL DELSPRITE(#ALL)
CALL MOTION
CALL MOTION(#n, vrow, vcol)
Sets sprite automatic motion (velocity). vrow/vcol are pixels-per-second (+/- direction). Hardware-animated each VBLANK.
100 CALL MOTION(#1,-2,3) REM Sprite moves up 2, right 3 per frame
CALL COINC
CALL COINC(#n,#m,tolerance,result)
Checks if two sprites are within tolerance pixels of each other. Result is -1 (true) or 0 (false).
100 CALL COINC(#1,#2,4,C) 110 IF C THEN PRINT "COLLISION!"
CALL POSITION
CALL POSITION(#n, rowvar, colvar)
Returns current pixel row and column of sprite #n into two numeric variables.
100 CALL POSITION(#1,R,C) 110 PRINT "SPRITE AT";R;C
CALL PATTERN
CALL PATTERN(#n, charcode)
Changes which character bitmap the sprite uses (for animation). Char must already be defined with CALL CHAR.
100 FOR F=128 TO 131 110 CALL PATTERN(#1,F) 120 FOR D=1 TO 50 :: NEXT D 130 NEXT F
▶ Sound
CALL SOUND
CALL SOUND(dur, freq1, vol1[, freq2, vol2, ...])
Plays up to 3 tones plus noise simultaneously. Duration in ms (negative=async). Freq 110-44733 Hz or -1 to -8 for noise. Volume 0 (loud) to 30 (quiet).
100 CALL SOUND(500,440,5) REM 440 Hz (A4) for 500ms, vol 5 110 CALL SOUND(300,262,0,330,0,392,0) REM C-E-G chord (C major)
CALL SOUND (noise)
CALL SOUND(dur, -noise, vol)
Noise values -1 to -8: -1/-2/-3 periodic noise (low/med/hi), -4 white noise using tone3, -5/-6/-7/-8 white noise.
100 CALL SOUND(200,-6,5) REM White noise burst (explosion FX) 110 CALL SOUND(500,-2,4) REM Low periodic tone (engine hum)
▶ Keyboard & Joystick
CALL KEY
CALL KEY(unit, keyvar, statusvar)
Reads keyboard (unit=0) or joystick fire (unit=1,2). keyvar gets key code, statusvar: -1=pressed, 1=released, 0=no change.
100 CALL KEY(0,K,S) 110 IF S=-1 THEN PRINT CHR$(K) 120 GOTO 100
CALL JOYST
CALL JOYST(unit, xvar, yvar)
Reads joystick direction. unit=1 or 2. xvar: -4=left, 4=right, 0=center. yvar: -4=down, 4=up, 0=center.
100 CALL JOYST(1,X,Y) 110 PRINT "X=";X;" Y=";Y 120 GOTO 100
▶ File I/O
OPEN
OPEN #n:"device.filename" [,mode] [,org] [,type]
Opens file for I/O. Mode: INPUT, OUTPUT, UPDATE, APPEND. Org: SEQUENTIAL, RELATIVE. Type: INTERNAL, DISPLAY.
100 OPEN #1:"DSK1.SCORES",OUTPUT 110 OPEN #2:"DSK1.DATA",INPUT,RELATIVE
CLOSE
CLOSE #n [DELETE]
Closes open file. DELETE option removes the file from disk after closing. Always CLOSE before ending.
100 CLOSE #1 110 CLOSE #2 DELETE
PRINT (file)
PRINT #n: expr[,expr...]
Writes to open sequential file #n. Separate multiple values with commas (stored as separate fields).
100 OPEN #1:"DSK1.HI",OUTPUT 110 PRINT #1:NAME$,SCORE 120 CLOSE #1
EOF
EOF(n)
Returns -1 (true) if file #n is at end-of-file, 0 otherwise. Use to stop reading loops cleanly.
100 OPEN #1:"DSK1.DATA",INPUT 110 IF EOF(1) THEN 200 120 INPUT #1:A$ :: PRINT A$ :: GOTO 110 200 CLOSE #1
▶ Miscellaneous & System
RANDOMIZE
RANDOMIZE [n]
Seeds the random number generator. Without argument, uses same seed (reproducible). In XB: RANDOMIZE TIMER uses clock.
100 RANDOMIZE 110 FOR I=1 TO 5 120 PRINT INT(RND*100)+1 130 NEXT I
DATA / READ / RESTORE
DATA val,val,...
READ var[,var...]
RESTORE [line]
DATA stores constants in the program. READ fetches the next value from DATA lines. RESTORE resets the DATA pointer.
100 READ N$,S 110 PRINT N$;" SCORED ";S 120 DATA "ALICE",9500 130 DATA "BOB",7200 200 RESTORE 120
DEF (user function)
DEF FN name(param)=expr
Defines a single-line user function. Called as FN name(arg). Cannot be recursive. One parameter only.
100 DEF FNC(X)=INT(X*100+.5)/100 110 PRINT FNC(3.14159) REM OUTPUT: 3.14
SIZE
SIZE
Prints the number of bytes of free program space remaining. Useful to know how close you are to running out of memory.
SIZE REM OUTPUT: 11986 BYTES FREE
RUN
RUN [linenum]
Starts (or restarts) program execution. Optional line number starts from that point. Clears all variables first.
RUN REM Start from first line RUN 200 REM Start from line 200
LIST
LIST [line[-line]]
Lists program lines to screen. Optional range e.g. LIST 100-200. LIST ALL lists everything.
LIST LIST 100-200 LIST 500
NEW
NEW
Erases the current program and all variables from memory. Cannot be undone. Start fresh.
NEW REM (Program and vars cleared)
CALL SAY
CALL SAY("text")
Speaks text through the Speech Synthesizer module (if attached). Uses built-in phoneme encoding.
100 CALL SAY("HELLO COMPUTER") 110 CALL SAY("TEXAS INSTRUMENTS")

13 TI Extended BASIC — Additional Instructions

+

About TI Extended BASIC

TI Extended BASIC (XB) is a cartridge module that expands TI BASIC with ~60 additional commands, structured programming features, sprite control, assembly call support, and more. Requires the 32 KB RAM expansion. XB programs are source-compatible with TI BASIC programs (all TI BASIC commands work in XB) but XB programs will not run on bare TI BASIC.

All commands on this page are XB-only — they do not exist in standard TI BASIC. XB tags mark them in the reference below.
▶ Program Structure & Control
SUB / SUBEND XB
SUB name[(params)]
...
SUBEND
Defines a named subroutine with local parameters. Called by CALL name(args). Parameters are local to the SUB — they do not affect global vars of the same name.
100 CALL GREET("ALICE") 110 END 120 SUB GREET(N$) 130 PRINT "HELLO ";N$ 140 SUBEND
CALL (user SUB) XB
CALL subname[(args)]
Invokes a user-defined SUB. Arguments are passed by value (numbers) or reference (strings & arrays). Return via SUBEND or SUBEXIT.
100 CALL DOUBLE(X) 110 PRINT X 120 END 130 SUB DOUBLE(N) 140 N=N*2 150 SUBEND
SUBEXIT XB
SUBEXIT
Exits the current SUB early (like a RETURN in a GOSUB). Execution resumes after the CALL that invoked this SUB.
130 SUB CHECKIT(X) 140 IF X<0 THEN SUBEXIT 150 PRINT "POSITIVE: ";X 160 SUBEND
EXIT FOR / EXIT SUB XB
EXIT FOR
EXIT SUB
EXIT FOR breaks out of the innermost FOR loop immediately. EXIT SUB is an alias for SUBEXIT — exits the current named subroutine.
100 FOR I=1 TO 100 110 IF I=5 THEN EXIT FOR 120 NEXT I 130 PRINT "STOPPED AT ";I
NUMERIC / STRING param XB
SUB name(NUMERIC n, STRING s$)
Explicitly typed parameters in SUB definitions. NUMERIC and STRING keywords declare the type of each parameter. Improves clarity and error detection.
200 SUB SHOW(NUMERIC N, STRING S$) 210 PRINT S$;" = ";N 220 SUBEND
▶ Enhanced I/O & Display
DISPLAY AT (XB enhanced) XB
DISPLAY AT(row,col)[,ERASE n][,SIZE n]: expr
XB's primary output command. Row 1–24, Col 1–28. ERASE clears lines before print. SIZE pads/truncates field.
100 DISPLAY AT(1,1):"SCORE:" 110 DISPLAY AT(1,8),ERASE 1:STR$(SCORE) 120 DISPLAY AT(12,5),SIZE(18):"CENTERED LINE"
ACCEPT AT (XB enhanced) XB
ACCEPT AT(row,col)[,BEEP][,VALIDATE(set)][,SIZE(n)]: var
Full-featured input at cursor position. VALIDATE can be UALPHA, DIGIT, ALPHA, or a custom character string.
100 DISPLAY AT(10,1):"ENTER NAME:" 110 ACCEPT AT(10,13),BEEP,SIZE(10):N$ 120 ACCEPT AT(11,1),VALIDATE(DIGIT):AGE
INPUT AT XB
INPUT AT(row,col): var
Places cursor at row,col before accepting input. Shorter than ACCEPT AT when validation isn't needed.
100 DISPLAY AT(5,1):"CITY? " 110 INPUT AT(5,7):CITY$
▶ Enhanced Graphics
CALL MAGNIFY XB
CALL MAGNIFY(size)
Sets sprite magnification. Size 1=normal (8×8 px), 2=double (16×16 px), 3=32×32, 4=64×64. Applies globally to all sprites.
100 CALL MAGNIFY(2) 110 CALL SPRITE(#1,128,16,80,120) REM Sprite drawn at double size
CALL DISTANCE XB
CALL DISTANCE(#n,#m,distvar)
Returns the pixel distance between two sprites' centers as a numeric result. Useful for smoother collision detection than COINC.
100 CALL DISTANCE(#1,#2,D) 110 IF D<16 THEN PRINT "TOO CLOSE!"
CALL BORDERS XB
CALL BORDERS(#n, top,bot,left,right, var)
Checks if sprite #n has hit a rectangular boundary. Result -1=hit, 0=inside. Bounces or triggers events at edges.
100 CALL BORDERS(#1,1,191,1,255,H) 110 IF H THEN CALL MOTION(#1,VR*-1,VC)
CALL LINK XB
CALL LINK("ASMNAME"[,arg,...])
Calls a TMS9900 assembly language subroutine by name. The assembly routine must be loaded via CALL LOAD first. Bridge between BASIC and machine code.
100 CALL LOAD("DSK1.MYASM") 110 CALL LINK("MYROUTINE",X,Y,R$)
CALL LOAD XB
CALL LOAD("file") or CALL LOAD(addr,byte,...)
Loads an assembly object file from disk into memory, making its routines available to CALL LINK. Can also poke bytes directly at an address.
100 CALL LOAD("DSK1.UTILS") 110 CALL LINK("INIT") 200 CALL LOAD(-31868,9,37) REM Poke bytes at address >8304
CALL PEEK XB
CALL PEEK(addr, var[,var,...])
Reads one or more bytes from memory starting at addr into variables. Equivalent to PEEK in other BASICs. Addresses use TI sign convention (>8000 = -32768+).
100 CALL PEEK(8192,A,B,C) REM Read 3 bytes from address >2000 110 PRINT A,B,C
CALL POKEV XB
CALL POKEV(vaddr, byte[,byte,...])
Writes bytes directly into VDP VRAM at address vaddr. Allows raw VRAM manipulation — useful for tile maps and sprite data beyond CALL CHAR/HCHAR.
100 CALL POKEV(0,65,66,67) REM Write "ABC" chars to VRAM addr 0
CALL PEEKV XB
CALL PEEKV(vaddr, var[,var,...])
Reads bytes from VDP VRAM at vaddr into variables. Allows reading back tile/sprite data or screen state from video memory.
100 CALL PEEKV(0,A,B,C) 110 PRINT A,B,C REM Reads first 3 bytes of VRAM
▶ Enhanced Math & String
PI XB
PI
Built-in constant = 3.14159265358979. No need to define it manually as in TI BASIC.
100 PRINT PI REM OUTPUT: 3.14159265 110 CIRC=2*PI*RADIUS
RANDOMIZE TIMER XB
RANDOMIZE TIMER
Seeds the RNG from the system real-time clock. Produces different random sequences every run. Not available in TI BASIC (no timer access).
100 RANDOMIZE TIMER 110 PRINT INT(RND*1000)
TIME XB
TIME
Returns current value of the system timer (counts up from 0 at power-on, in 1/100 second units). Useful for timing loops and speed measurement.
100 T=TIME 110 FOR I=1 TO 1000 :: NEXT I 120 PRINT "ELAPSED:";TIME-T;"cs"
RPT$ XB
RPT$(str$, n)
Returns str$ repeated n times. Equivalent to STRING$() in other BASICs.
100 PRINT RPT$("=-",14) REM OUTPUT: =-=-=-=-=-=-=-=-=-=-=-=-=-=- 110 B$=RPT$("*",20)
NUMERIC / STRING test XB
NUMERIC var, STRING var
As standalone statements inside a SUB, explicitly declares variables as numeric or string type in that scope. Prevents type errors.
200 SUB CALC() 210 NUMERIC X,Y,RESULT 220 STRING MSG$ 230 X=10 :: Y=20 :: RESULT=X+Y 240 MSG$=STR$(RESULT) 250 SUBEND
USING (format) XB
PRINT USING "fmt": expr
Formatted output. Format codes: # = digit, . = decimal, + = sign, * = fill with *, $ = dollar sign. Pads to field width.
100 PRINT USING "###.##":3.14159 REM OUTPUT: 3.14 110 PRINT USING "$###,###.##":1234.5 REM OUTPUT: $ 1,234.50
▶ Sprite Enhancements
CALL SPRITEC XB
CALL SPRITEC(#n, color)
Changes the color of an existing sprite without repositioning it. More efficient than re-issuing CALL SPRITE.
100 CALL SPRITE(#1,128,2,80,120) 110 FOR C=2 TO 15 120 CALL SPRITEC(#1,C) 130 FOR D=1 TO 30 :: NEXT D 140 NEXT C
CALL MOVE XB
CALL MOVE(srcaddr, dstaddr, bytes)
Fast memory block copy from source to destination in CPU RAM. Far faster than a FOR loop. Essential for moving large data buffers.
100 CALL MOVE(SOURCE,DEST,512) REM Copy 512 bytes from SOURCE to DEST
CALL INIT XB
CALL INIT
Initializes the Extended BASIC assembly support library. Must be called before CALL LOAD or CALL LINK. Usually the first line of XB programs using assembly.
100 CALL INIT 110 CALL LOAD("DSK1.MYLIB") 120 CALL LINK("SETUP")
▶ Program Management
SAVE (XB) XB
SAVE "device.filename" [,PROTECTED]
Saves current program to disk or cassette. PROTECTED flag prevents LISTing after loading (copy protection). XB uses SAVE; TI BASIC uses SAVE as well.
SAVE "DSK1.MYGAME" SAVE "DSK1.FINAL",PROTECTED
OLD XB
OLD "device.filename"
Loads a saved program from disk/cassette into memory, replacing the current program. XB equivalent of LOAD in other BASICs.
OLD "DSK1.MYGAME" OLD "CS1" REM Load from cassette
MERGE XB
MERGE "device.filename"
Loads a program from disk and MERGES it with the current program in memory. Lines from disk overwrite existing lines with the same number.
100 REM MAIN PROGRAM 200 CALL INIT MERGE "DSK1.SUBROUTINES" REM Adds lines from file to program
RUN (file) XB
RUN "device.filename"
Loads and immediately runs a program from disk. Equivalent to OLD + RUN as a single command. Useful for program chaining.
900 REM CHAIN TO NEXT LEVEL 910 RUN "DSK1.LEVEL2"
▶ Complete XB Quick Reference Summary

XB vs TI BASIC — Key Differences

FeatureTI BASICExtended BASIC
Named subroutinesGOSUB/RETURN onlySUB/SUBEND with local vars
Structured loopsFOR, WHILE+ EXIT FOR, EXIT SUB
Screen outputPRINT, PRINT ATDISPLAY AT (row,col) format
InputINPUTACCEPT AT with VALIDATE
VRAM accessNone (only via CALL HCHAR etc.)CALL PEEKV / CALL POKEV
RAM accessNoneCALL PEEK / CALL LOAD
Assembly callsNoneCALL INIT, LOAD, LINK
PI constant4*ATN(1)PI built-in
TimerNot availableTIME function
String repeatLoop onlyRPT$(str$,n)
Formatted outputManual with STR$/TABPRINT USING "format"
Sprite distanceCOINC onlyCALL DISTANCE(#a,#b,var)
Sprite sizeFixed 8×8 or 16×16CALL MAGNIFY(1-4)
Program chainingNot supportedRUN "filename", MERGE
Number formatManual STR$/printPRINT USING
Memory requiredNone (ROM-based)32 KB RAM expansion + cartridge
Line number tip: Number lines in steps of 10 (100, 110, 120…) so you can insert lines later. Use :: to put multiple statements on one line — saves program memory and speeds the interpreter since each line has overhead. Example: 100 X=1 :: Y=2 :: Z=X+Y
Memory tip: Every variable name takes memory. Use short names (A, B, X$) in tight programs. DELETE unused lines with just their line number and ENTER. Remove REM lines to free space — use LIST to disk first as a backup.

14 CALL SPRITE — Complete Parameter Reference

+

What is CALL SPRITE?

CALL SPRITE is the primary sprite creation and positioning command in both TI BASIC and TI Extended BASIC. It instructs the TMS9918A Video Display Processor to display a hardware sprite on screen — a moving graphical object rendered entirely by the VDP chip, completely independent of the character background, with no CPU intervention required once set in motion.

The TI-99/4A VDP supports up to 32 hardware sprites simultaneously (#1 through #32). Each sprite is an 8×8 pixel (or 16×16 with magnification) bitmap that can be freely positioned anywhere on the 256×192 pixel screen, colored independently, and given automatic motion. Hardware collision detection between sprites is built into the VDP.

Hardware advantage: Sprites are managed entirely by the TMS9918A. Once CALL MOTION is set, the VDP moves sprites automatically every VBLANK (60 times/sec) without any CPU cycles — freeing the TMS9900 for game logic, input, and sound.

Full Syntax

CALL SPRITE( #spritenum, charcode, color, row, column ) #spritenum — Sprite number: #1 through #32 charcode — Character pattern to use as bitmap: 32–143 (user-defined via CALL CHAR) color — Sprite color: 1–16 (TMS9918A palette) row — Vertical pixel position: 1–256 (values >192 move sprite off bottom) column — Horizontal pixel position: 1–256 (values >255 wrap or go off-screen) Note: Row and column are pixel-based, NOT character cell positions. The screen is 256 pixels wide × 192 pixels tall. Character cells are 8×8 pixels: row 1 = pixel row 1, cell row 1 row 9 = pixel row 9, cell row 2

Parameter 1 — #spritenum (Sprite Number)

Sprites are identified by a number prefixed with #, from #1 to #32. Each sprite number is an independent hardware object. You can have up to 32 on screen at once, but the VDP enforces a maximum of 4 sprites per horizontal scanline — the 5th sprite on any given scanline is silently skipped (not drawn). This causes the classic "sprite flicker" in busy games.

ValueMeaningNotes
#1Sprite slot 1 (highest priority)Drawn on top of all other sprites. Player character typically uses #1.
#2–#31Sprite slots 2–31Drawn in order; lower numbers appear on top of higher numbers.
#32Sprite slot 32 (lowest priority)Drawn beneath all other sprites. Backgrounds / decorative elements.
#ALLAll spritesValid only in CALL DELSPRITE — removes all 32 sprites at once.
Priority rule: When two sprites overlap, the one with the lower sprite number appears on top visually. Sprite #1 is always the topmost. This is the opposite of what many newcomers expect — lower number = higher priority = drawn last (on top).
100 CALL SPRITE(#1, 96, 16, 96, 120) :: REM Player — highest priority 110 CALL SPRITE(#2, 104, 8, 50, 60) :: REM Enemy 1 120 CALL SPRITE(#3, 104, 9, 80, 180) :: REM Enemy 2 130 CALL SPRITE(#4, 112, 14, 140, 100) :: REM Powerup REM If #1 and #2 overlap, #1 is drawn ON TOP of #2

Parameter 2 — charcode (Bitmap Pattern)

The charcode tells the VDP which 8×8 pixel bitmap to use as the sprite's visual shape. The sprite uses the same pattern table as screen characters — so you define a sprite's appearance with CALL CHAR, then reference it by its character code number.

Any character code from 32 to 143 can be redefined with CALL CHAR and used as a sprite pattern. Codes 32–95 are the standard ASCII printable characters (space, letters, numbers, symbols) — you can redefine these freely. Codes 96–143 are entirely user-definable.

Understanding the 16-hex-digit CALL CHAR pattern

Each character is an 8×8 pixel grid = 8 bytes = 16 hex digits. Each hex digit pair is one row of 8 pixels. A 1 bit = pixel ON (sprite color), a 0 bit = pixel OFF (transparent).

CALL CHAR( charcode, "HHHHHHHHHHHHHHHHH" ) ↑↑ ↑↑ ↑↑ ↑↑ ↑↑ ↑↑ ↑↑ ↑↑ Row1 Row2 Row3 Row4 Row5 Row6 Row7 Row8 (each pair = 1 byte = 8 pixels in that row) Pixel mapping for one byte: Hex "FF" = 11111111 = ████████ (full row, all 8 pixels lit) Hex "81" = 10000001 = █______█ (only leftmost and rightmost pixels) Hex "3C" = 00111100 = __████__ (middle 4 pixels) Hex "00" = 00000000 = ________ (empty row, all transparent) Example — solid 8×8 diamond: CALL CHAR(96, "183C7EFFFF7E3C18") Row 1: 18 = 00011000 = ___██___ Row 2: 3C = 00111100 = __████__ Row 3: 7E = 01111110 = _██████_ Row 4: FF = 11111111 = ████████ Row 5: FF = 11111111 = ████████ Row 6: 7E = 01111110 = _██████_ Row 7: 3C = 00111100 = __████__ Row 8: 18 = 00011000 = ___██___

Common sprite pattern recipes

ShapeCodePattern StringVisual
Solid square96"FFFFFFFFFFFFFFFF"████████ (all 8 rows full)
Hollow square97"FF818181818181FF"Border only, hollow center
Filled circle98"3C7EFFFFFFFF7E3C"Rounded blob shape
Small dot99"0000183C3C180000"4×4 dot in center
Cross / plus100"1818FF18181818FF"+ shape
Arrow right101"0810204040201008"> pointing right
Arrow left102"1008040202040810"< pointing left
Space invader103"24DBE7E7E7240000"Classic alien shape
Heart104"006699FFFF7E3C18"Heart outline shape
Spaceship105"183C7EFFDB7E1818"Diamond with details
Bullet (horiz)106"000000FF00000000"Thin horizontal bar
Bullet (vert)107"1010101010101010"Thin vertical line
Explosion 1108"A554A554A554A554"Checkerboard burst
Explosion 2109"5AA55AA55AA55AA5"Alternate checkerboard

16×16 sprites (Extended BASIC / magnification)

With CALL MAGNIFY(2) in XB, each sprite is doubled to 16×16 pixels but still uses an 8×8 bitmap (just scaled up). For true 16×16 sprites with full resolution, use two consecutive even charcode numbers: the VDP combines chars N and N+2 (and N+1 and N+3 for the bottom half) into a 16×16 composite. The charcode must be an even number and divisible by 4 for 16×16 mode (hardware behavior of TMS9918A).

REM True 16x16 sprite using 4 characters (32 bytes, 64 hex digits each pair): REM In 16x16 mode the VDP reads 4 consecutive chars automatically. REM Enable with VDP register R1 bit 1 — in BASIC, no direct control, REM but CALL MAGNIFY(1) with a 16x16 char block gives effective 16x16. 100 CALL CHAR(128,"003C7EFFFFFFFF7E") :: REM Top-left 8x8 of sprite 110 CALL CHAR(129,"3C7EFFFFFFFF7E3C") :: REM Top-right 8x8 120 CALL CHAR(130,"7EFFFFFFFF7E3C00") :: REM Bottom-left 8x8 140 CALL CHAR(131,"FFFFFFFF7E3C0000") :: REM Bottom-right 8x8 REM Use char 128 as the sprite — VDP reads 128,129,130,131 together
REM Simple complete example — define and display a spaceship sprite: 100 CALL CLEAR 110 CALL SCREEN(1) 120 CALL COLOR(1,16,1) 130 CALL CHAR(128,"183C7EFFDB7E1818") :: REM Spaceship bitmap 140 CALL SPRITE(#1, 128, 16, 96, 120) :: REM White sprite at screen center 150 CALL SOUND(200,440,5) :: REM Startup beep 160 GOTO 160 :: REM Hold screen

Parameter 3 — color (Sprite Color)

The color parameter sets the sprite's foreground color — the color of every 1-bit pixel in the sprite bitmap. 0-bit pixels are always transparent (the background shows through). Each sprite has exactly one color; multi-color sprites require overlapping multiple sprites.

Colors are specified as integers 1 through 16, matching the TMS9918A's fixed 16-color palette. Color 1 (transparent) makes the sprite completely invisible — useful for "hit detection only" sprites.

ValueColor NameHex (approx.)Use Case
1TransparentInvisible sprite (collision only)
2Medium Green#21C842Grass, aliens, foliage
3Light Green#5EDC78Highlights, plants
4Dark Blue#5455EDPlayer ship, water
5Light Blue#7D76FCSky objects, ice
6Dark Red#D4524DEnemies, danger
7Cyan#42EBF5Bullets, energy beams
8Medium Red#FC5554Explosions, fire
9Light Red#FF7978Hearts, health
10Dark Yellow#D4C154Gold, stars
11Light Yellow#E6CE80Highlights, coins
12Dark Green#21B03BGround, trees
13Magenta#C95BB5Power-ups, portals
14Gray#CCCCCCRocks, metal objects
15Black#000000Outlines, shadows
16White#FFFFFFPlayer, bullets, UI

Multi-color sprites using sprite stacking

Since each sprite has only one color, multi-color objects require layering two or more sprites on top of each other at the same position, each with a different bitmap (showing different portions) and a different color. Move them together with synchronized CALL MOTION or CALL LOCATE.

REM Two-color spaceship: white body (#1) + red cockpit overlay (#2) 100 CALL CHAR(128,"183C7EFF7E3C1818") :: REM Full ship body 110 CALL CHAR(129,"0000003C3C000000") :: REM Cockpit only (center pixels) 120 CALL SPRITE(#1, 128, 16, 96, 120) :: REM White body 130 CALL SPRITE(#2, 129, 8, 96, 120) :: REM Red cockpit at same position 140 REM Move both together to keep them aligned: 150 CALL MOTION(#1, -1, 0) :: CALL MOTION(#2, -1, 0)

Parameters 4 & 5 — row and column (Pixel Position)

Row and column set the sprite's top-left corner position in pixel coordinates. The TI-99/4A screen is 256 pixels wide and 192 pixels tall. Unlike CALL HCHAR which uses character cell coordinates (1–24 rows, 1–32 columns), sprite positions are raw pixels starting at 1 (top-left).

CoordinateRangeNotes
row (vertical)1 – 2561 = top of screen. 192 = last visible row for 8×8 sprite. Values 193–256 place sprite partially or fully below screen. Value 209 (0xD0) is the sentinel that hides sprite when scanned by VDP.
column (horizontal)1 – 2561 = leftmost pixel. 249 = last visible column for 8×8 sprite. Values beyond 256 wrap (TMS9918A uses 8-bit column register).

Coordinate system diagram

Screen pixel coordinate system (256 × 192): col→ 1 128 256 row↓ ┌─────────────────────────────────────────────────────────────┐ 1 │(1,1) (1,128) (1,256)│ │ │ │ │ 96 │ Screen Center │ │ (96,128) │ │ │ 192 │(192,1) (192,128) (192,256)│ └─────────────────────────────────────────────────────────────┘ ↑ Pixel row 193+ = off-screen bottom (sprite partially hidden) ↑ Column 249+ = sprite partially/fully off right edge Character cell mapping (each cell = 8×8 pixels): Pixel row 1 = Character row 1 Pixel row 9 = Character row 2 Pixel row 17 = Character row 3 ... Pixel row = (char_row - 1) * 8 + 1 Pixel col = (char_col - 1) * 8 + 1 Center an 8×8 sprite on screen: row = 96 (192/2 - 8/2 = 92, use 96 for visual center) column = 124 (256/2 - 8/2 = 124)

Position reference table — character cells to pixel positions

Char RowPixel RowChar ColPixel Col
1111
2929
317425
641857
128916121
139717129
1813724185
2418532249
Off-screen trick: Setting row to 193 or higher hides a sprite below the visible area — a common way to "park" a sprite without deleting it (preserving its MOTION settings). Setting row to 1 and column to 249+ hides it off the right edge. Use CALL LOCATE(#n, 200, 1) to park a sprite invisibly below the screen.
REM Centering formulas: REM Center horizontally: col = INT(256/2) - INT(spritewidth/2) + 1 = 125 REM Center vertically: row = INT(192/2) - INT(spriteheight/2) + 1 = 93 100 SPRW = 8 :: REM Sprite width in pixels (8 for normal, 16 for 16x16) 110 SPRH = 8 :: REM Sprite height in pixels 120 CR = INT((192-SPRH)/2)+1 :: REM Centered row 130 CC = INT((256-SPRW)/2)+1 :: REM Centered column 140 CALL SPRITE(#1, 128, 16, CR, CC) 150 PRINT "CENTERED AT ROW ";CR;" COL ";CC

CALL MOTION — Automatic Sprite Movement

CALL MOTION(#n, vrow, vcol) sets a sprite's velocity. The VDP automatically updates the sprite's position every VBLANK (~60 times per second NTSC, ~50 PAL) by adding vrow to the row and vcol to the column. Velocity is in 1/60th-pixel units per frame — or equivalently, pixels per second when vrow/vcol represent pixel-per-second values that the VDP divides over frames.

vrow valueEffect
0No vertical movement
-n (negative)Moves UP n units per second
+n (positive)Moves DOWN n units per second
vcol valueEffect
0No horizontal movement
-n (negative)Moves LEFT n units per second
+n (positive)Moves RIGHT n units per second
REM Motion examples: CALL MOTION(#1, 0, 0) :: REM Stopped CALL MOTION(#1, -4, 0) :: REM Move up slowly CALL MOTION(#1, 8, 0) :: REM Move down faster CALL MOTION(#1, 0, 6) :: REM Move right CALL MOTION(#1, 0, -6) :: REM Move left CALL MOTION(#1, -4, 4) :: REM Diagonal: up-right CALL MOTION(#1, 4, -4) :: REM Diagonal: down-left REM Bouncing ball example: 100 CALL CHAR(128,"3C7EFFFFFFFF7E3C") 110 CALL SPRITE(#1, 128, 16, 96, 124) 120 CALL MOTION(#1, 3, 4) 130 VR=3 :: VC=4 140 CALL POSITION(#1, R, C) 150 IF R<2 OR R>185 THEN VR=VR*-1 :: CALL MOTION(#1,VR,VC) 160 IF C<2 OR C>249 THEN VC=VC*-1 :: CALL MOTION(#1,VR,VC) 170 GOTO 140

CALL LOCATE — Direct Position Override

CALL LOCATE(#n, row, col) instantly moves a sprite to the given pixel coordinates, overriding any MOTION velocity without stopping it. The sprite continues moving from the new position at the same velocity. Use for teleportation, wrapping, and respawning.

REM Wrap sprite at right edge back to left edge: 100 CALL POSITION(#1, R, C) 110 IF C > 250 THEN CALL LOCATE(#1, R, 1) 120 GOTO 100 REM Teleport player to center on death: 200 CALL LOCATE(#1, 96, 124) 210 CALL MOTION(#1, 0, 0) :: REM Stop motion after warp

CALL POSITION — Read Sprite Location

CALL POSITION(#n, rowvar, colvar) reads the current pixel row and column of sprite #n into two numeric variables. Essential for collision logic, boundary detection, and AI movement.

REM Read player position and print it: 100 CALL POSITION(#1, PR, PC) 110 PRINT AT 1: "ROW:";PR;" COL:";PC REM Keep sprite inside screen bounds manually: 200 CALL POSITION(#1, R, C) 210 IF R < 1 THEN CALL LOCATE(#1, 1, C) :: CALL MOTION(#1, ABS(VR), VC) 220 IF R > 185 THEN CALL LOCATE(#1, 185, C) :: CALL MOTION(#1, -ABS(VR), VC) 230 IF C < 1 THEN CALL LOCATE(#1, R, 1) :: CALL MOTION(#1, VR, ABS(VC)) 240 IF C > 249 THEN CALL LOCATE(#1, R, 249) :: CALL MOTION(#1, VR, -ABS(VC))

CALL COINC — Sprite Collision Detection

CALL COINC(#a, #b, tolerance, resultvar) checks whether sprites #a and #b are within tolerance pixels of each other. The result is -1 (true) if they overlap within tolerance, or 0 (false) if not. The tolerance is the maximum allowed distance between their top-left corners.

ToleranceMeaning
1–4Very precise — sprites must nearly perfectly overlap. Good for bullets vs targets.
5–8Normal collision — good for most 8×8 sprite game objects.
9–16Loose collision — good for larger objects or forgiving hit boxes.
17+Wide proximity detection — use for "nearby" triggers, not true collision.
REM Bullet vs. enemy collision: 100 CALL COINC(#1, #2, 6, HIT) 110 IF HIT THEN GOSUB 500 :: REM Handle hit REM REM Check all enemies vs player (sprite #1 = player, #2..#6 = enemies): 200 FOR E=2 TO 6 210 CALL COINC(#1, #E, 5, C) 220 IF C THEN PRINT "HIT ENEMY ";E :: CALL DELSPRITE(#E) 230 NEXT E
XB CALL DISTANCE alternative: In Extended BASIC, CALL DISTANCE(#a, #b, var) returns the actual pixel distance between sprite centers as a number, allowing circular collision detection with any threshold. More flexible but slightly slower than COINC.

CALL PATTERN — Change Sprite Bitmap (Animation)

CALL PATTERN(#n, charcode) changes which character bitmap sprite #n displays without altering its position, color, or motion. This is the primary animation mechanism — cycling through a sequence of pre-defined character patterns creates the illusion of movement.

REM Walking character animation (4-frame cycle): 100 CALL CHAR(128,"183C7EFF181818FF") :: REM Frame 1: legs together 110 CALL CHAR(129,"183C7EFF18182424") :: REM Frame 2: left foot forward 120 CALL CHAR(130,"183C7EFF181818FF") :: REM Frame 3: legs together (repeat) 130 CALL CHAR(131,"183C7EFF18182418") :: REM Frame 4: right foot forward 140 CALL SPRITE(#1, 128, 16, 150, 30) 150 CALL MOTION(#1, 0, 4) :: REM Walk right 160 F=128 170 CALL PATTERN(#1, F) 180 F=F+1 :: IF F>131 THEN F=128 :: REM Loop frames 190 FOR D=1 TO 8 :: NEXT D :: REM Frame delay 200 GOTO 170

CALL DELSPRITE — Remove Sprites

CALL DELSPRITE(#n[, #m, ...]) removes one or more sprites from the screen. The sprite's slot is freed — position, motion, and pattern are all cleared. Use #ALL to remove all 32 sprites instantly.

CALL DELSPRITE(#1) :: REM Remove sprite 1 only CALL DELSPRITE(#1, #2, #3) :: REM Remove sprites 1, 2, and 3 CALL DELSPRITE(#ALL) :: REM Remove ALL 32 sprites at once REM Explode and remove enemy: 500 CALL SOUND(150, -6, 3) :: REM Explosion sound 510 CALL PATTERN(#2, 108) :: REM Switch to explosion bitmap 520 FOR D=1 TO 60 :: NEXT D :: REM Brief pause 530 CALL DELSPRITE(#2) :: REM Remove enemy sprite 540 RETURN

CALL MAGNIFY — Sprite Size (Extended BASIC only)

CALL MAGNIFY(size) sets the global sprite magnification level for ALL sprites. It writes directly to VDP register R1 bits 0–1. Cannot be set per-sprite — it affects all sprites simultaneously.

Size ValueSprite Pixel SizeBitmap ResolutionNotes
18 × 8 px8 × 8 (1:1)Default. Normal size. Sharpest.
216 × 16 px8 × 8 scaled ×2Double size, blocky. Each pixel becomes 2×2.
332 × 32 px8 × 8 scaled ×4Very large, very blocky. Bosses/large objects.
464 × 64 px8 × 8 scaled ×8Huge. Nearly full screen. Background elements.
100 CALL MAGNIFY(2) 110 CALL CHAR(128,"3C7EFFFFFFFF7E3C") :: REM Circle bitmap 120 CALL SPRITE(#1, 128, 16, 80, 112) :: REM Appears as 16x16 circle 130 REM Adjust centering for larger sprite: 140 REM At MAGNIFY(2), sprite occupies 16px: center = (192-16)/2=88 row, (256-16)/2=120 col
Centering at larger sizes: When using MAGNIFY, remember the sprite occupies more pixels. Adjust row/col accordingly: center_row = (192 - sprite_height) / 2, center_col = (256 - sprite_width) / 2. For MAGNIFY(2) with 8×8 bitmap: effective size = 16×16, so center_row = (192-16)/2 = 88, center_col = (256-16)/2 = 120.

Complete Worked Example — Full Sprite Game Loop

This example demonstrates every CALL SPRITE parameter and related function in a complete mini-game skeleton: player-controlled ship, enemy with motion, bullet firing, collision detection, animation, and boundary wrapping.

100 REM *** SPRITE DEMO — TI-99/4A *** 110 REM Demonstrates: SPRITE, MOTION, LOCATE, POSITION, 120 REM COINC, PATTERN, DELSPRITE, KEY, JOYST 130 REM ───────────────────────────────────────────────── 140 CALL CLEAR 150 CALL SCREEN(1) :: REM Black background 160 REM --- DEFINE BITMAPS --- 170 CALL CHAR(128,"183C7EFFDB7E1818") :: REM #128 Player ship 180 CALL CHAR(129,"FF818181818181FF") :: REM #129 Enemy (hollow square) 190 CALL CHAR(130,"00001818181800") :: REM #130 Bullet (vertical) 200 CALL CHAR(131,"3C7EFFFFFFFF7E3C") :: REM #131 Player frame 2 (thrust) 210 CALL CHAR(132,"A554A554A554A554") :: REM #132 Explosion frame 1 220 CALL CHAR(133,"5AA55AA55AA55AA5") :: REM #133 Explosion frame 2 230 REM --- CREATE SPRITES --- 240 CALL SPRITE(#1, 128, 16, 180, 124) :: REM Player: white ship at bottom 250 CALL SPRITE(#2, 129, 8, 20, 60) :: REM Enemy 1: red square top-left 260 CALL SPRITE(#3, 129, 9, 20, 180) :: REM Enemy 2: light-red top-right 270 REM --- SET ENEMY MOTION --- 280 CALL MOTION(#2, 2, 3) :: REM Enemy 1: drift down-right 290 CALL MOTION(#3, 2, -3) :: REM Enemy 2: drift down-left 300 REM --- INIT VARS --- 310 BX=0 :: BACTIVE=0 :: SCORE=0 :: ALIVE=1 320 FRAME=0 330 REM ════════════════════════════════ 340 REM MAIN GAME LOOP 350 REM ════════════════════════════════ 360 CALL JOYST(1, JX, JY) :: REM Read joystick 370 CALL POSITION(#1, PR, PC) :: REM Get player position 380 REM --- PLAYER MOVEMENT --- 390 PC = PC + JX :: REM Move left/right with joystick 400 IF PC < 1 THEN PC = 1 :: REM Left boundary 410 IF PC > 249 THEN PC = 249 :: REM Right boundary 420 CALL LOCATE(#1, PR, PC) :: REM Apply new position 430 REM --- ANIMATE PLAYER (thrust flicker) --- 440 FRAME=FRAME+1 450 IF FRAME MOD 4 < 2 THEN CALL PATTERN(#1,128) ELSE CALL PATTERN(#1,131) 460 REM --- FIRE BULLET (joystick button) --- 470 CALL KEY(0, K, S) 480 IF S=-1 AND K=32 AND BACTIVE=0 THEN GOSUB 700 490 REM --- MOVE BULLET IF ACTIVE --- 500 IF BACTIVE=0 THEN GOTO 560 510 CALL POSITION(#4, BR, BC) 520 IF BR < 1 THEN CALL DELSPRITE(#4) :: BACTIVE=0 :: GOTO 560 530 REM Check bullet vs enemies: 540 CALL COINC(#4, #2, 6, C2) :: IF C2 THEN GOSUB 800 :: SCORE=SCORE+100 550 CALL COINC(#4, #3, 6, C3) :: IF C3 THEN GOSUB 810 :: SCORE=SCORE+100 560 REM --- CHECK ENEMY BOUNDARIES (bounce) --- 570 CALL POSITION(#2, E2R, E2C) 580 IF E2R>185 OR E2R<1 THEN VR2=VR2*-1 :: CALL MOTION(#2,VR2, VC2) 590 IF E2C>249 OR E2C<1 THEN VC2=VC2*-1 :: CALL MOTION(#2,VR2, VC2) 600 REM --- CHECK PLAYER COLLISION WITH ENEMIES --- 610 CALL COINC(#1, #2, 8, HIT) 620 IF HIT THEN ALIVE=0 :: GOTO 900 630 CALL COINC(#1, #3, 8, HIT) 640 IF HIT THEN ALIVE=0 :: GOTO 900 650 REM --- SCORE DISPLAY --- 660 PRINT AT 1,SIZE(28):"SCORE: "&STR$(SCORE) 670 GOTO 360 :: REM Loop 680 REM ════════════════ 690 REM SUBROUTINES 700 REM FIRE BULLET 710 CALL POSITION(#1, PR, PC) 720 CALL SPRITE(#4, 130, 7, PR-8, PC+3) :: REM Cyan bullet above player 730 CALL MOTION(#4, -8, 0) :: REM Bullet moves up fast 740 BACTIVE=1 750 CALL SOUND(-50, 880, 8) :: REM Shoot sound (async) 760 RETURN 800 REM DESTROY ENEMY 2 810 CALL PATTERN(#2, 132) :: REM Explosion frame 1 820 CALL SOUND(200, -6, 3) :: REM Explosion noise 830 FOR D=1 TO 30 :: NEXT D 840 CALL PATTERN(#2, 133) :: REM Explosion frame 2 850 FOR D=1 TO 30 :: NEXT D 860 CALL LOCATE(#2, 200, 1) :: REM Park off screen 870 CALL MOTION(#2, 0, 0) 880 CALL DELSPRITE(#4) :: BACTIVE=0 :: REM Remove bullet too 890 RETURN 900 REM GAME OVER 910 CALL DELSPRITE(#ALL) 920 CALL CLEAR 930 PRINT AT 200:"*** GAME OVER ***" 940 PRINT AT 240:"FINAL SCORE: "&STR$(SCORE) 950 END

CALL SPRITE — One-Page Parameter Cheat Sheet

┌────────────────────────────────────────────────────────────────────┐ │ CALL SPRITE( #n, charcode, color, row, column ) │ │ │ │ │ │ │ │ │ │ │ │ │ └── Pixel X: 1–256 │ │ │ │ │ └────────── Pixel Y: 1–192 │ │ │ │ └───────────────── TMS color: 1–16 │ │ │ └─────────────────────────── CALL CHAR code │ │ └─────────────────────────────────── Sprite #1–#32 │ ├────────────────────────────────────────────────────────────────────┤ │ Related commands: │ │ CALL MOTION(#n, vrow, vcol) — set velocity (pixels/sec) │ │ CALL LOCATE(#n, row, col) — instant position override │ │ CALL POSITION(#n, R, C) — read current pixel position │ │ CALL COINC(#a, #b, tol, res) — collision detection │ │ CALL PATTERN(#n, charcode) — change bitmap (animation) │ │ CALL DELSPRITE(#n) / (#ALL) — remove sprite(s) │ │ CALL SPRITEC(#n, color) — change color only [XB] │ │ CALL DISTANCE(#a, #b, var) — pixel distance [XB] │ │ CALL MAGNIFY(1-4) — global sprite scale [XB] │ │ CALL BORDERS(#n,...,res) — boundary hit detection [XB] │ ├────────────────────────────────────────────────────────────────────┤ │ SCREEN: 256px wide × 192px tall │ Up to 32 sprites │ │ Max 4 sprites per scanline (VDP hardware limit) │ │ Lower #spritenum = higher draw priority (drawn on top) │ │ Sprite bitmap: defined with CALL CHAR — 16 hex digits = 8×8px │ │ Color 1 = transparent (invisible) — use for hit-box-only sprites │ │ Motion unit = pixels per second (VDP updates at 60 Hz NTSC) │ └────────────────────────────────────────────────────────────────────┘
►   ►   ►   END OF REFERENCE   ◄   ◄   ◄
Further Reference: The TI-99/4A Internal Hardware Specification (Texas Instruments, 1982), the TMS9900 Microprocessor Data Manual, the TMS9918A Video Display Processors Data Manual, and the TI Extended BASIC Reference Manual (MP-1253) are the primary technical sources. The modern TI-99/4A community at atariage.com/ti99 and ti99iuc.it maintains active documentation and new software development.
TI-99/4A Architecture & BASIC Reference • Texas Instruments Home Computer (1981–1984)
TMS9900 CPU • TMS9918A VDP • TMS9919 Sound • TMS5220 Speech • TI BASIC • Extended BASIC
Compatible with browsers from 2014 onward • No external dependencies • Pure HTML/CSS/JS