VOLUME C -- HARDWARE REFERENCE
SC-3000 BASIC Level III-B — part of the SE3K documentation library, also readable inside the emulator.
============================================================================ SC-3000 BASIC LEVEL III-B VOLUME C -- HARDWARE REFERENCE ============================================================================ Level : intermediate/advanced -- basic Z80 knowledge helpful References : Vol. D (ROM routines) * Vol. E (disassembly) TABLE OF CONTENTS ---------------------------------------------------------------------------- Chapter 1 SC-3000 Architecture ...................................... C-2 Chapter 2 VDP -- TMS9918A (video processor) .......................... C-6 2.10 All display modes -- mode-bit table and register summary 2.11 Mode 0: Graphics I (32x24 chars, 8-colour groups) 2.12 Mode 2: Multicolour (64x48 fat-pixel grid) 2.13 VRAM access timing (CPU constraints) 2.14 Hardware interface (TMS9918A pins, SC-3000 connections) 2.15 ML practical examples 2.15.1 Switch to Mode 0 from ML / return to SCREEN 2 2.15.2 Redefine a character pattern (Modes 0 & 1) 2.15.3 Define and place an 8x8 sprite (Screen 2) 2.15.4 Define a 16x16 sprite 2.16 Undocumented mode combinations (experimental) Chapter 3 PSG -- SN76489AN (sound processor) ......................... C-22 3.1-3.5 Basic operation, frequency, noise, BASIC access 3.6 Write protocol -- detailed behaviour (Maxim, 2003) 3.7 Noise generator -- LFSR implementation (Maxim, 2003) 3.8 Initial state and SC-3000 boot behaviour (Maxim, 2003) 3.9 Signal imperfection -- output voltage decay (Maxim, 2003) 3.10 Sample playback techniques (Maxim, 2003) Chapter 4 PPI -- Intel 8255 (peripheral interface) ................... C-32 Chapter 5 Joystick .................................................. C-48 Chapter 6 SC-3000H Hardware Notes (C. MacDonald, 2002-2004) ......... C-56 6.2.3b RGB connector (8-pin DIN, PAL/RGB models) 6.3.5 Cartridge bus timing (service manual Fig. 2-1 -- 2-3) Chapter 7 Serial Printer Interface (practical reference) ............. C-72 7.1 Overview -- signals and BASIC commands 7.2 Connector pinout -- dual source comparison 7.3 BASIC interface -- how LPRINT works internally (PUTADR) 7.4 Checking printer status from ML 7.5 Sending a character via the ROM handler ($7008) 7.6 Physical wire protocol -- bit encoding and timing 7.7 Timing diagram -- "Hello World\r" (logic analyzer view) 7.8 Emulator interception -- capturing LPRINT output in software
CHAPTER 1 -- SC-3000 ARCHITECTURE
CHAPTER 1 -- SC-3000 ARCHITECTURE ============================================================================
1.1 CPU
1.1 CPU ---------------------------------------------------------------------------- Zilog Z80A Clock : 3.579545 MHz (NTSC colorburst / 1) Bus : 8-bit data, 16-bit address Refresh: automatic DRAM refresh via /RFSH pin Power : DC 9 V +0.5 V / 850 mA (minimum ripple voltage >= 7.5 V)
1.2 Memory map ($0000-$FFFF)
1.2 Memory map ($0000-$FFFF) ---------------------------------------------------------------------------- $0000-$7FFF ROM (32 KB -- BASIC Level III-B cartridge) $8000-$87FF RAM (2 KB -- present on all SC-3000 models) $8000-$BFFF RAM (16 KB -- with 16KB RAM expansion) $8000-$FFFF RAM (32 KB -- with SF-7000 or 32KB expansion) Notes: * POKE to $0000-$7FFF is ignored by BASIC (ROM is not writable) * BASIC uses $8000-$97FF as its working area (Vol. D ch. 10) * BASIC programs start at $9800 * With only 2 KB, the RAM available for programs is minimal
1.3 I/O map ($00-$FF)
1.3 I/O map ($00-$FF) ---------------------------------------------------------------------------- Port Chip Dir Function ------ ------------- ---- ---------------------------------------- $7F SN76489AN PSG W PSG data write (write-only) $BE TMS9918A VDP R/W VRAM data (read or write) $BF TMS9918A VDP R/W Write VDP register; read status $DC 8255 PPI R Port A -- keyboard / joystick P1 data $DD 8255 PPI R Port B -- keyboard / joystick P2 data $DE 8255 PPI R/W Port C -- control signals $DF 8255 PPI W PPI control register
1.4 Interrupts
1.4 Interrupts ---------------------------------------------------------------------------- /INT Generated by the VDP at the end of every frame. Frequency: 60 Hz (NTSC) / 50 Hz (PAL) Z80 vector: $0038 -> JP $6806 -> BASIC interrupt handler Uses: incrementing the TIME$ timer (TIMEIV/$948D), keyboard auto-repeat, updating BRKFLG and SPCFLG. /NMI Not used by standard SC-3000 hardware. Z80 vector: $0066 -> JP $6803
1.5 Block diagram
1.5 Block diagram ---------------------------------------------------------------------------- +-------------+ Z80 Bus +-------------+ | Z80A CPU |<-------------->| ROM 32 KB | $0000-$7FFF | 3.58 MHz | +-------------+ +------+------+ | Z80 Bus +-------------+ +-------------------->| RAM 2-32KB | $8000-$FFFF | +-------------+ | I/O $BE/$BF +-------------+ +----------+ +-------------------->| TMS9918A |<-->| VRAM 16K | | | VDP | +----------+ | | Video ----+--> TV/RGB signal | | /INT -----+--> Z80 /INT | +-------------+ | I/O $7F +-------------+ +-------------------->| SN76489AN |--> Audio (mono) | | PSG | | +-------------+ | I/O $DC-$DF +-------------+ +-------------------->| Intel 8255 |--> Keyboard matrix | PPI |--> Cassette (CMT) +-------------+--> Serial printer --> Joystick P1/P2
CHAPTER 2 -- VDP -- TMS9918A (VIDEO PROCESSOR)
CHAPTER 2 -- VDP -- TMS9918A (VIDEO PROCESSOR) ============================================================================
2.1 Features
2.1 Features ---------------------------------------------------------------------------- Chip : Texas Instruments TMS9918A (NTSC) / TMS9928A (PAL) VRAM : 16 KB separate from CPU RAM (not visible to the Z80) Ports : $BE = data, $BF = control/status Output : composite / RGB (depends on hardware version) /INT : signal to Z80 at every VBlank (end of frame) Display modes supported by SC-3000 BASIC: SCREEN 1 -> Text 40x25 characters (6x8 pixels/char) SCREEN 2 -> Graphics 256x192 pixels (2 colours per 8 pixels)
2.2 I/O port access
2.2 I/O port access ---------------------------------------------------------------------------- Port $BF -- write VDP register ---------------------------------------------------------------------------- Writing a VDP register requires TWO consecutive bytes: 1st byte: value to write into the register 2nd byte: $80 | register_number (bit 7 = 1 -> register write) Example -- write $C2 into register R1: LD A, $C2 OUT ($BF), A ; value first LD A, $80 | 1 ; = $81 OUT ($BF), A ; register number second Port $BF -- read status register ---------------------------------------------------------------------------- A read from $BF returns the status register and clears the interrupt flag: IN A, ($BF) ; bit 7 (F) : 1 = VBlank interrupt pending ; bit 6 (5S) : 1 = 5th sprite on a scanline (overflow) ; bit 5 (C) : 1 = sprite coincidence (overlap) ; bits 4-0 : number of the 5th out-of-range sprite Port $BE -- VRAM access ---------------------------------------------------------------------------- Before reading or writing VRAM, set the address via $BF (see section 2.7). Each access to $BE automatically advances the VRAM address by 1. IMPORTANT: insert at least 1 us between two consecutive VRAM accesses (2 NOP cycles at 3.58 MHz). The ROM uses 4-5 guard NOPs (see VRAMWR/VRAMRD in Vol. D ch. 5).
2.3 VDP registers R0-R7
2.3 VDP registers R0-R7 ---------------------------------------------------------------------------- All registers are write-only (except status read from $BF). +----+-------------------------------------------------------------+ | R0 | Bit 1 (M3): Graphics II mode selection | | | Bit 0 (EXT): external video (normally 0) | | | Bits 7-2: 0 | | | Screen 1: $00 Screen 2: $02 | +----+-------------------------------------------------------------+ | R1 | Bit 7 (4/16K): VRAM size -> always 1 (16K) | | | Bit 6 (BL): display active -> 1=on, 0=blank | | | Bit 5 (IE): interrupt enable -> 1=enable VBlank /INT | | | Bit 4 (M2): mode bit 2 | | | Bit 3 (M1): mode bit 1 | | | Bit 2: 0 | | | Bit 1 (SI): sprite size 0=8x8, 1=16x16 | | | Bit 0 (MAG): sprite zoom 0=1x, 1=2x | | | | | | Mode selection (M3 in R0, M2 and M1 in R1): | | | M3=0 M2=0 M1=0 -> Mode 0: Graphics I (32x24 chars) | | | M3=0 M2=0 M1=1 -> Mode 1: Text (40x24 chars) | | | M3=0 M2=1 M1=0 -> Mode 2: Multicolour (not used) | | | M3=1 M2=0 M1=0 -> Mode 3: Graphics II (256x192 px) | | | | | | Screen 1 (text, IE enabled): $D0 (10110000) | | | Screen 2 (gfx, IE enabled): $C2 (11000010) | +----+-------------------------------------------------------------+ | R2 | Name table base (bits 3-0): addr = value x $0400 | | | Screen 1: $00 -> name table at $0000 | | | Screen 2: $06 -> name table at $1800 | +----+-------------------------------------------------------------+ | R3 | Colour table base (bits 7-0): addr = value x $0040 | | | Graphics I and Graphics II only (M3=1: addr = R3 x $0040) | | | Screen 2 (Graphics II): $FF -> colour table at $3FC0 | | | (In M3=1 mode, R3 and R4 use all bits to address a | | | 6144-byte colour table at $2000) | +----+-------------------------------------------------------------+ | R4 | Pattern generator table base (bits 2-0): addr = val x $800| | | Screen 1: $03 -> pattern table at $1800 | | | Screen 2: $03 -> pattern table at $0000 | | | (In Graphics II mode the field has 2 usable bits) | +----+-------------------------------------------------------------+ | R5 | Sprite attribute table base (bits 6-0): addr = val x $80 | | | Screen 2: $36 -> sprite attr at $1B00 | +----+-------------------------------------------------------------+ | R6 | Sprite pattern table base (bits 2-0): addr = val x $800 | | | Screen 2: $07 -> sprite pattern at $3800 | +----+-------------------------------------------------------------+ | R7 | Backdrop and text colour (hi nibble=fg, lo nibble=bg) | | | Used in text mode for character colour and as backdrop | | | colour in all modes. | +----+-------------------------------------------------------------+ IMPORTANT -- the R2/R6 values above ($06/$07) are the conventional textbook TMS9918 example, valid ONLY if you program the registers yourself via raw OUT $BF sequences (as in the ML examples further below in this chapter, which do set R2=$06/R6=$07 explicitly and are self-consistent). If instead you enter Screen 2 the normal way -- the BASIC "SCREEN 2,2" statement, or calling the ROM's SCRINIT ($39DF) + DSPGRP ($39E2) -- the actual registers written by THIS ROM (verified by disassembly) are DIFFERENT: R2 = $0E -> name table at $3800 (not $1800) R6 = $03 -> sprite pattern table at $1800 (not $3800) R5 = $76 -> sprite attribute table at $3B00 (not $1B00) Any BASIC/ML code that relies on SCRINIT/DSPGRP but assumes the conventional $1800/$3800 mapping will write to the wrong VRAM area (typically an invisible/blank screen). This was the root cause of a real bug in GRAPHICS_MASTER.txt and the game tutorial chapters -- see manual_v2/VOL_G_TAPE_MASTERING.txt section 7 point 5. | | BASIC default: $F5 (white on grey) or similar | +----+-------------------------------------------------------------+
2.4 SCREEN 1 -- Text mode (40x25 characters)
2.4 SCREEN 1 -- Text mode (40x25 characters) ---------------------------------------------------------------------------- Features: * 40 columns x 25 rows (rows 0-24 in BASIC) * Each character: 6 pixels x 8 pixels * 1 fg/bg colour pair for the whole screen (from R7) * No direct access to individual pixels VRAM map -- Screen 1: +-----------------+------------------------------------------------+ | $0000-$03E7 | Name table (1000 bytes = 40x25) | | | Address = row x 40 + column | | | Byte = ASCII code of the character | +-----------------+------------------------------------------------+ | $0800-$0FFF | (unused in Screen 1) | +-----------------+------------------------------------------------+ | $1800-$1FFF | Pattern table (256 chars x 8 bytes = 2048 B) | | | Each char: 8 bytes, 1 byte per row | | | Bit 1=visible pixel, Bit 0=hidden pixel | | | This block is VRAMSV (swapped with RAM) | +-----------------+------------------------------------------------+ Calculating the VRAM address of a screen character: addr = $0000 + (row x 40) + column Example: row 12, column 20 -> $0000 + 480 + 20 = $01F4 Modifying a character pattern (via direct VRAM): addr_pattern = $1800 + (ASCII_code x 8) + inner_row Example: character 'A' (ASCII 65), row 0: $1800 + 520 = $1A08
2.5 SCREEN 2 -- Graphics mode (256x192 pixels)
2.5 SCREEN 2 -- Graphics mode (256x192 pixels) ---------------------------------------------------------------------------- Features: * 256 columns x 192 rows of pixels * 2 colours per group of 8 horizontal pixels (1 byte) * A bit=1 -> "bit-1 colour"; a bit=0 -> "bit-0 colour" * The screen is divided into 3 vertical "thirds" of 64 rows each VRAM map -- Screen 2 (conventional TMS9918 layout; see the IMPORTANT note in section 2.3/2.4's register table -- if you entered this mode via SCRINIT+DSPGRP or BASIC's "SCREEN 2,2", the name table and sprite pattern table addresses below are SWAPPED on this ROM: name table is actually at $3800 and sprite pattern at $1800): +-----------------+------------------------------------------------+ | $0000-$17FF | Pattern table (3 x 256 chars x 8 bytes) | | | Third 0: $0000-$07FF (pixel rows 0-63) | | | Third 1: $0800-$0FFF (pixel rows 64-127) | | | Third 2: $1000-$17FF (pixel rows 128-191) | | | Each byte: 8 pixels (bit7=left, bit0=right) | +-----------------+------------------------------------------------+ | $1800-$1AFF | Name table (768 bytes = 3 x 256) | | | Third 0: $1800-$18FF (sequence 0,1,2...255) | | | Third 1: $1900-$19FF (same sequence) | | | Third 2: $1A00-$1AFF (same sequence) | | | Normally preloaded $00,$01,...,$FF to map | | | patterns 1:1 to pixels | +-----------------+------------------------------------------------+ | $1B00-$1B7F | Sprite attribute table (32 sprites x 4 bytes) | +-----------------+------------------------------------------------+ | $2000-$37FF | Colour table (3 x 256 chars x 8 bytes = 6144 B)| | | Same structure as the pattern table | | | Each byte: hi nibble=fg, lo nibble=bg | | | Corresponds to 8 pixels of the same row | +-----------------+------------------------------------------------+ | $3800-$3FFF | Sprite pattern table (256 patterns x 8 bytes) | | | For 16x16 sprites: 4 blocks of 8 bytes | +-----------------+------------------------------------------------+ Finding the address of a pixel (x, y): third = y / 64 (0, 1 or 2) char_row = y MOD 8 (0-7 = inner row within the byte) col_byte = x / 8 (0-31 = which byte in the row) Correct formula for the pattern table: idx = (y MOD 64) / 8 x 32 + (x / 8) ; block index addr_patt = $0000 + third x $0800 + idx x 8 + (y MOD 8) bit_mask = $80 >> (x MOD 8) ; bit to set Formula for the colour table (same index): addr_col = $2000 + third x $0800 + idx x 8 + (y MOD 8) byte = (fg_nibble << 4) | bg_nibble Note: the PLOTPT and PLOTLN routines (Vol. D ch. 6) perform these calculations automatically. Using them is recommended.
2.6 Sprite system
2.6 Sprite system ---------------------------------------------------------------------------- Hardware limits: * Maximum 32 sprites simultaneously * Maximum 4 sprites visible on the same scanline * The 5th sprite on a scanline is not displayed (bit 6 of the status register is set) Sprite attribute table ($1B00-$1B7F, 4 bytes per sprite): +--------+-------------------------------------------------------+ | Byte 0 | Y -- vertical coordinate (0-191; 208=$D0 = hidden) | | | Display starts from row Y+1 | +--------+-------------------------------------------------------+ | Byte 1 | X -- horizontal coordinate (0-255) | +--------+-------------------------------------------------------+ | Byte 2 | Pattern number (0-255 for 8x8; multiple of 4 for 16x16)| +--------+-------------------------------------------------------+ | Byte 3 | Bit 7 (EC): early clock -> shift sprite by -32 pixels | | | Bits 3-0: colour (1-15; 0=transparent) | +--------+-------------------------------------------------------+ Display priority: sprite with lower number is in front. Sizes: MAG=0->8x8 px, MAG=1->16x16 px, MAG=2->8x8 zoomx2, MAG=3->16x16 zoomx2.
2.7 Direct VRAM access
2.7 Direct VRAM access ---------------------------------------------------------------------------- VRAM write sequence: 1. Write the low byte of the address to $BF 2. Write the high byte to $BF with bit 6=0 (write mode) 3. Write data bytes to $BE (auto-increment after each write) ; Example: write $AB to VRAM address $1800 LD A, $00 ; low byte of $1800 OUT ($BF), A LD A, $18 | $40 ; high byte $18, bit6=0 -> write = $58 OUT ($BF), A LD A, $AB ; data OUT ($BE), A VRAM read sequence: 1. Write the low byte to $BF 2. Write the high byte to $BF with bit 6=1 (read mode) 3. Read bytes from $BE after a short delay ; Example: read from $1800 LD A, $00 OUT ($BF), A LD A, $18 | $00 ; bit6=0 -> read = $18 (do not use $40) OUT ($BF), A NOP : NOP ; minimum guard (~0.5 us) IN A, ($BE) ; data read IMPORTANT: do not access VRAM during active display without disabling interrupts (DI/EI). The ROM routines VRAMADRW, VRAMADRR, VRAMWR, VRAMRD (Vol. D ch. 5) handle timing correctly.
2.8 TMS9918A 16-colour palette
2.8 TMS9918A 16-colour palette ---------------------------------------------------------------------------- The palette is fixed (not programmable). Approximate RGB values: Index BASIC name R G B Notes ------ ------------- --- --- --- -------------------------- 0 Transparent 0 0 0 transparent background 1 Black 0 0 0 2 Medium green 36 139 36 3 Light green 94 198 94 4 Dark blue 85 85 183 5 Light blue 128 128 212 6 Dark red 183 79 63 7 Cyan 79 193 193 8 Medium red 212 85 79 9 Light red 255 113 100 10 Dark yellow 212 193 79 11 Light yellow 217 210 112 12 Dark green 36 116 36 13 Magenta 183 79 173 14 Grey 183 183 183 15 White 255 255 255 Colour 0 is transparent: it shows the backdrop (R7 low nibble). Colour 1 (black) and 0 (transparent) look identical but behave differently with sprites: colour 0 in sprites is always transparent.
2.9 VBlank interrupt
2.9 VBlank interrupt ---------------------------------------------------------------------------- The VDP generates a /INT signal to the Z80 at the end of every frame. To enable it: bit 5 (IE) of R1 = 1. The BASIC interrupt handler (at $6806) performs: * Incrementing the software timer (TIMEA/$948B, TIMEIV/$948D) * Updating TIMES/$948E, TIMEM/$948F, TIMEH/$9490 * Reading the keyboard matrix (KEYREAD -> updates KEYIM0-8) * Keyboard auto-repeat * Updating BRKFLG and SPCFLG flags To read the interrupt flag (and clear it): IN A, ($BF) ; reads VDP status, clears the flag BIT 7, A ; bit7=1 if VBlank was pending
2.10 All display modes -- mode-bit table and register summary
2.10 All display modes -- mode-bit table and register summary ---------------------------------------------------------------------------- The TMS9918A has three mode bits: M3 (bit 1 of R0), M2 (bit 4 of R1), M1 (bit 3 of R1). Their combination selects one of four documented display modes. M3 M2 M1 Name Resolution Sprites BASIC cmd -- -- -- ------------ ------------------ ------- --------- 0 0 0 Mode 0 (G1) 32x24 chars (8x8) yes -- 0 0 1 Mode 1 (Text) 40x24 chars (6x8) no SCREEN 1 0 1 0 Mode 2 (MC) 64x48 fat pixels yes -- 1 0 0 Mode 3 (G2) 256x192 bitmap yes SCREEN 2 Register values for all four modes (16 KB VRAM, BL=1, IE=1): Reg Mode 0 (G1) Mode 1 (Text) Mode 2 (MC) Mode 3 (G2) * --- ----------- ------------- ----------- ----------- R0 $00 $00 $00 $02 R1 $E0 $D0 $F0 $C2 R2 $05 ($1400) $00 ($0000) $05 ($1400) $0E ($3800) * R3 $80 ($2000) -- (unused) -- (unused) $FF ($3FC0) R4 $03 ($1800) $03 ($1800) $00 ($0000) $03 ($0000) R5 $36 ($1B00) -- (no sprite) $36 ($1B00) $76 ($3B00) * R6 $07 ($3800) -- (no sprite) $07 ($3800) $03 ($1800) * R7 $01 (border) $F4 (wh/gr) $01 (border) $01 (border) * CORRECTED from the generic TMS9918 textbook values (R2=$06/$1800, R5=$36/$1B00, R6=$07/$3800) after disassembling this ROM's actual SCRINIT ($39DF) and DSPGRP ($39E2) -- the routines behind BASIC's "SCREEN 2,2". This ROM swaps name table and sprite pattern table relative to the conventional layout: name table is at $3800 and sprite pattern table at $1800. See the IMPORTANT note in section 2.3/2.4 for detail; any code assuming the textbook values will write tile/graphics data to the wrong VRAM area (blank screen). Notes: * Mode 1 (Text): R4 address field uses only bits 2-0, addr = val x $800. BASIC uses R4=$03 -> $1800 (same as Mode 0 above, reusing the ROM character set already loaded there). * Mode 3 (G2): R3 and R4 together address a 6144-byte colour and pattern table; R3=$FF means the full upper half of VRAM. * R7 in Text mode: hi nibble=foreground colour, lo nibble=background colour for all characters. BASIC default: $F4 (white on grey).
2.11 Mode 0: Graphics I (32x24 chars, 8-colour groups)
2.11 Mode 0: Graphics I (32x24 chars, 8-colour groups) ---------------------------------------------------------------------------- Graphics I is the "native" tile mode of the TMS9918A. It is not accessible via a BASIC SCREEN command on the SC-3000 but can be entered directly from ML (see section 2.15.1). Features: * 32 columns x 24 rows = 768 character cells * Each cell: 8x8 pixels, character code 0-255 * 256 user-definable patterns (pattern generator table) * 16 colours available; colour assigned per group of 8 character codes * Sprites: 32 sprites, max 4 per scanline (same as Mode 3) VRAM map -- Mode 0 (using the register values in section 2.10): Address range Size Description --------------- ----- -------------------------------------------------- $0000-$02FF 768B Name table (32x24 char codes; R2=$00) $1800-$1FFF 2048B Pattern generator (256 chars x 8 bytes; R4=$03) $2000-$201F 32B Colour table (32 groups x 1 byte; R3=$80) $1B00-$1B7F 128B Sprite attribute table (32 sprites; R5=$36) $3800-$3FFF 2048B Sprite pattern table (R6=$07) Name table: each byte is the character code (0-255) to display at that cell. Row-major order: cell (col, row) is at offset row*32 + col. Colour table: 32 bytes, one per group of 8 character codes. Byte N applies to char codes N*8 through N*8+7. Bit layout of each colour byte: bits 7-4 : foreground colour (1-15; 0=transparent) bits 3-0 : background colour (0-15) Example: $F1 = white (15) foreground on black (1) background. Pattern generator table: one entry per character, 8 bytes each. byte[0] = pixel row 0 (topmost), byte[7] = pixel row 7. Bit 7 = leftmost pixel, bit 0 = rightmost pixel. A '1' bit renders in the foreground colour; '0' in background colour. Compared to Mode 3 (Graphics II): * Mode 0 uses ONE pattern generator shared by the whole screen; Mode 3 has THREE independent pattern generators (one per vertical third). * Mode 0 colour granularity: one colour pair per 8 char codes. In Mode 3, every 8-pixel row of every character has its own colour byte. * Mode 0 needs only 32 colour bytes; Mode 3 needs 6144.
2.12 Mode 2: Multicolour (64x48 fat-pixel grid)
2.12 Mode 2: Multicolour (64x48 fat-pixel grid) ---------------------------------------------------------------------------- Multicolour mode divides the 256x192 screen into a 64x48 grid of "fat pixels", each 4x4 real pixels in size. Every fat pixel can be assigned an independent colour from the 16-colour palette. Features: * 64 columns x 48 rows of independently-coloured fat pixels * Each fat pixel: 4x4 real pixels, one of 16 colours * No per-pixel resolution; no colour-pairs constraint * Sprites: supported (same attribute/pattern tables as Modes 0 and 3) Organisation: the 64x48 fat pixel grid maps to 32x24 character cells (same name table as Mode 0). Each character cell contains a 2x2 sub- grid of fat pixels (2 wide x 2 tall = 4 fat pixels per character). VRAM map -- Mode 2: Address range Size Description --------------- ----- -------------------------------------------------- $0000-$07FF 2048B Pattern/colour table (32x24 x 8 bytes; R4=$00) $1400-$16FF 768B Name table (32x24 char codes; R2=$05) $1B00-$1B7F 128B Sprite attribute table (R5=$36) $3800-$3FFF 2048B Sprite pattern table (R6=$07) R3 (colour table): not used in Mode 2. Pattern/colour table format (8 bytes per character entry): Byte 0: colours for scan rows 0-3 (top fat-pixel row of the cell) bits 7-4 = LEFT fat pixel colour bits 3-0 = RIGHT fat pixel colour Byte 2: colours for scan rows 4-7 (bottom fat-pixel row of the cell) bits 7-4 = LEFT fat pixel colour bits 3-0 = RIGHT fat pixel colour Bytes 1, 3, 4, 5, 6, 7: not read by the VDP in this mode. Addressing a fat pixel at grid position (fx, fy) where fx: 0-63, fy: 0-47: char_col = fx / 2 ; which character column (0-31) char_row = fy / 2 ; which character row (0-23) cell_code = name_table[char_row * 32 + char_col] ; typically 0-255 byte_ofs = (fy MOD 2) * 2 ; 0 = top row, 2 = bottom row nibble = (fx MOD 2) ; 0 = left (hi nibble), 1 = right (lo nibble) addr = $0000 + cell_code * 8 + byte_ofs colour = (VRAM[addr] >> (4 - nibble*4)) AND $0F Practical note: for a fixed 1:1 mapping (each fat pixel independently addressable), preload the name table with cell codes 0-255 in row-major order (cells 0-31 in row 0, 32-63 = 0-31 repeated in row 1, etc., since there are only 256 codes for 768 cells). A common approach uses a tiled arrangement where each tile block shares a pattern entry but has its fat pixel colours set individually via the colour bytes.
2.13 VRAM access timing (CPU constraints)
2.13 VRAM access timing (CPU constraints) ---------------------------------------------------------------------------- The TMS9918A has a dedicated 16-bit VRAM bus (separate from the Z80 data bus). The CPU accesses VRAM only via the I/O ports $BE/$BF. Internal clock: the VDP runs at 10.7386 MHz (= 3 x NTSC colour burst). The Z80 runs at 3.5795 MHz (= 1/3 of VDP clock). Critical timing rules (from TI TMS9918A Application Manual): 1. Two-byte register/address write (port $BF): Write byte 1 to $BF. Wait AT LEAST 1 Z80 machine cycle (NOPx1, ~279 ns) before the second write. The ROM typically waits 0 cycles (back-to-back OUT instructions are separated by ~1.5 µs of opcode fetch, which is sufficient). 2. Address setup to first VRAM data access (port $BE): After the second $BF write, wait AT LEAST 2 µs before the first $BE access. At 3.58 MHz, 2 µs = ~7 Z80 T-states. One NOP = 4 T-states. The ROM guard (VRAMADRW/VRAMADRR) uses 2 NOP instructions (~2.2 µs). Safe minimum: 2 x NOP. 3. Consecutive VRAM data reads (port $BE, read mode): The VDP needs time to increment its address counter and prepare the next byte. Wait AT LEAST 8 µs between consecutive reads. At 3.58 MHz: 8 µs = ~29 T-states = ~7 NOP instructions. The ROM's VRAMRD uses 5 guard NOPs and an additional LD operation (~6.5 µs total), which is marginally safe. For reliable operation use at least 5 NOPs between reads. 4. Consecutive VRAM data writes (port $BE, write mode): Minimum: 8 µs between writes (~29 T-states). The ROM's VRAMWR guard: 4-5 NOPs + surrounding instructions. For maximum write speed from ML: 5 NOPs minimum. For burst writes (large block copy), a DJNZ loop with 4 NOPs per iteration gives ~9 µs/byte at 3.58 MHz (safe). 5. Status register read (port $BF, read): Can be performed at any time without timing restriction relative to VRAM operations. Clears the VBlank interrupt flag (bit 7). 6. Active display vs blanking: VRAM access during active display does NOT corrupt the display (the VDP has internal arbitration) but timing rules still apply. The safest approach is to access VRAM during VBlank only (inside the interrupt handler after reading the status register, or while BL=0 via R1 bit 6). Recommended ML idiom for safe VRAM write: ; Set VRAM address for write (example: $1800) LD A, $00 ; low byte OUT ($BF), A LD A, $18 | $40 ; high byte | write bit ($40) OUT ($BF), A NOP : NOP ; >=2 µs setup (2 NOPs = ~2.2 µs) ; Write loop (5 NOPs between writes = ~8 µs) LD B, count LD HL, source WRLOOP: LD A, (HL) OUT ($BE), A INC HL NOP : NOP : NOP : NOP : NOP DJNZ WRLOOP
2.14 Hardware interface (TMS9918A pins, SC-3000 connections)
2.14 Hardware interface (TMS9918A pins, SC-3000 connections) ---------------------------------------------------------------------------- Pin group Pin name(s) Dir SC-3000 connection ------------ ----------- --- --------------------------------------- CPU data bus CD0-CD7 I/O Z80 data bus D0-D7 Chip select /CSR IN /IORQ AND /RD AND (port addr $BE or $BF) /CSW IN /IORQ AND /WR AND (port addr $BE or $BF) Mode/addr MODE IN A0 of Z80 address bus 0 ($BE) = VRAM data port 1 ($BF) = control/status port Interrupt /INT OUT Z80 /INT pin (active low, VBlank) Video out COMVID OUT Composite video signal (TMS9918A NTSC) Y, R-Y, B-Y OUT Component video (TMS9928A PAL only) CSYNC OUT Composite sync (TMS9928A) Crystal XTAL1/XTAL2 -- 10.7386 MHz crystal (NTSC) (PAL: TMS9929A uses 17.734 MHz) VRAM bus AD0-AD13 OUT 14-bit VRAM address bus (not CPU-visible) D0-D7 (VRAM) I/O VRAM data bus (not CPU-visible) Supply VCC/GND -- +5V / 0V Notes: * GROMCLK and CPUCLK outputs of the TMS9918A are not used on SC-3000. * EXTVDP (external video mix) is tied high (inactive) on SC-3000. * The TMS9918A (NTSC models) generates composite video internally. The TMS9928A (PAL/RGB models used in SC-3000H PAL versions) outputs separate colour-difference signals; a separate encoder generates PAL. * Port decoding: address bits A7-A1 select $BE ($10111110) or $BF. Bit A0 connects to the VDP MODE pin to switch between data and control.
2.15 ML practical examples
2.15 ML practical examples ----------------------------------------------------------------------------
2.15.1 Switch to Mode 0 (Graphics I) and back to Screen 2
2.15.1 Switch to Mode 0 (Graphics I) and back to Screen 2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ------------------------------------------------------------------ ; SWITCH_G1 -- activate Mode 0 (Graphics I, 32x24) ; Using: name table at $0000, patterns at $1800 (BASIC char set), ; colour table at $2000, sprites from $1B00/$3800. ; Destroys: A ; ------------------------------------------------------------------ SWITCH_G1: LD A, $00 : OUT ($BF), A : LD A, $80 : OUT ($BF), A ; R0=$00 (M3=0) LD A, $E0 : OUT ($BF), A : LD A, $81 : OUT ($BF), A ; R1=$E0 (G1,IE,BL) LD A, $00 : OUT ($BF), A : LD A, $82 : OUT ($BF), A ; R2=$00 -> $0000 LD A, $80 : OUT ($BF), A : LD A, $83 : OUT ($BF), A ; R3=$80 -> $2000 LD A, $03 : OUT ($BF), A : LD A, $84 : OUT ($BF), A ; R4=$03 -> $1800 LD A, $36 : OUT ($BF), A : LD A, $85 : OUT ($BF), A ; R5=$36 -> $1B00 LD A, $07 : OUT ($BF), A : LD A, $86 : OUT ($BF), A ; R6=$07 -> $3800 LD A, $01 : OUT ($BF), A : LD A, $87 : OUT ($BF), A ; R7=$01 (blk bdr) RET After SWITCH_G1 the screen shows 32 columns x 24 rows using BASIC's built-in character set (patterns at $1800). The name table at $0000 must be preloaded before calling this routine, otherwise random VRAM content is displayed. Fill the name table with spaces (ASCII $20 = 32): FILL_NAME: LD A, $00 : OUT ($BF), A ; address low = $00 LD A, $40 : OUT ($BF), A ; address high = $00 | $40 (write) NOP : NOP LD BC, 768 ; 32 x 24 = 768 cells LD A, $20 ; space character FNLOOP: OUT ($BE), A NOP : NOP : NOP : NOP : NOP DEC BC LD A, B OR C JR NZ, FNLOOP RET Set all 32 colour groups to white-on-black ($F1): FILL_COL: LD A, $00 : OUT ($BF), A LD A, $60 : OUT ($BF), A ; $2000 = $80 * $40 -> high=$20, wr=$40 -> $60 NOP : NOP LD B, 32 LD A, $F1 FCLOOP: OUT ($BE), A NOP : NOP : NOP : NOP : NOP DJNZ FCLOOP RET Return to Screen 2 (Mode 3, Graphics II) after use: BACK_TO_G2: LD A, $02 : OUT ($BF), A : LD A, $80 : OUT ($BF), A ; R0=$02 (M3=1) LD A, $C2 : OUT ($BF), A : LD A, $81 : OUT ($BF), A ; R1=$C2 (G2,IE) LD A, $06 : OUT ($BF), A : LD A, $82 : OUT ($BF), A ; R2=$06 -> $1800 LD A, $FF : OUT ($BF), A : LD A, $83 : OUT ($BF), A ; R3=$FF LD A, $03 : OUT ($BF), A : LD A, $84 : OUT ($BF), A ; R4=$03 -> $0000 LD A, $36 : OUT ($BF), A : LD A, $85 : OUT ($BF), A ; R5=$36 -> $1B00 LD A, $07 : OUT ($BF), A : LD A, $86 : OUT ($BF), A ; R6=$07 -> $3800 LD A, $F5 : OUT ($BF), A : LD A, $87 : OUT ($BF), A ; R7=$F5 (BASIC dflt) RET
2.15.2 Redefine a character pattern (Modes 0 and 1)
2.15.2 Redefine a character pattern (Modes 0 and 1) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In Mode 0 (Graphics I) and Mode 1 (Text), character patterns are stored at $1800 on the SC-3000. Each character uses 8 consecutive bytes. Address of pattern for ASCII code C: $1800 + C * 8. Example: redefine ASCII '$' (code 36, $24) as a smiley face. New pixel data (8 rows x 8 bits): Row Binary Hex Visual (. = 0, X = 1) 0 00111100 $3C ..XXXX.. 1 01000010 $42 .X....X. 2 10100101 $A5 X.X..X.X 3 10000001 $81 X......X 4 10100101 $A5 X.X..X.X 5 10011001 $99 X..XX..X 6 01000010 $42 .X....X. 7 00111100 $3C ..XXXX.. ; Redefine pattern for '$' ($24 = 36) ; Pattern address = $1800 + 36*8 = $1800 + $120 = $1920 REDEF_DOLLAR: LD A, $20 ; low byte of $1920 OUT ($BF), A LD A, $19 | $40 ; = $59 (high byte | write flag) OUT ($BF), A NOP : NOP LD HL, SMILEY_DATA LD B, 8 RDLOOP: LD A, (HL) OUT ($BE), A INC HL NOP : NOP : NOP : NOP : NOP DJNZ RDLOOP RET SMILEY_DATA: DB $3C,$42,$A5,$81,$A5,$99,$42,$3C After calling REDEF_DOLLAR, every '$' character on screen immediately shows the new smiley shape (VRAM writes take effect on the next scan line). Revert by restoring the original pattern from ROM (copy 8 bytes from ROM address $1920 to VRAM $1920 using the VRAMWR routine).
2.15.3 Define and place an 8x8 sprite (Screen 2 / Mode 3)
2.15.3 Define and place an 8x8 sprite (Screen 2 / Mode 3) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sprites in Screen 2 (Mode 3) use two VRAM areas: Sprite pattern table $3800 (R6=$07) -- pixel patterns Sprite attribute table $1B00 (R5=$36) -- position, number, colour Each sprite pattern: 8 bytes (one per row, bit7=leftmost pixel). Each attribute entry: 4 bytes per sprite (sprite 0 at $1B00, etc.): Byte 0: Y coordinate (0-191; $D0 = off-screen/hidden) Byte 1: X coordinate (0-255) Byte 2: pattern number (0-255 for 8x8; must be multiple of 4 for 16x16) Byte 3: bits 7=EC (early clock, shifts -32 px), bits 3-0=colour (1-15) Example: define sprite 0 as a small rocket and place it at X=120, Y=80. Rocket shape (8x8, points upward): Row Binary Hex 0 00011000 $18 ...XX... 1 00111100 $3C ..XXXX.. 2 01111110 $7E .XXXXXX. 3 11111111 $FF XXXXXXXX 4 01111110 $7E .XXXXXX. 5 00100100 $24 ..X..X.. 6 00100100 $24 ..X..X.. 7 01100110 $66 .XX..XX. ; Step 1: write sprite pattern 0 to $3800 DEF_ROCKET: LD A, $00 : OUT ($BF), A LD A, $38 | $40 : OUT ($BF), A ; $78 = write to $3800 NOP : NOP LD HL, ROCKET_PAT LD B, 8 RKTLOOP: LD A, (HL) OUT ($BE), A INC HL NOP : NOP : NOP : NOP : NOP DJNZ RKTLOOP RET ROCKET_PAT: DB $18,$3C,$7E,$FF,$7E,$24,$24,$66 ; Step 2: set sprite 0 attribute (Y=80, X=120, pattern=0, colour=15 white) ; Note: display starts at Y+1, so write Y=79 ($4F) to show at row 80. SET_ROCKET: LD A, $00 : OUT ($BF), A LD A, $1B | $40 : OUT ($BF), A ; $5B = write to $1B00 NOP : NOP LD A, 79 ; Y = 79 -> displayed at row 80 OUT ($BE), A NOP : NOP : NOP : NOP : NOP LD A, 120 ; X = 120 OUT ($BE), A NOP : NOP : NOP : NOP : NOP LD A, 0 ; pattern number 0 OUT ($BE), A NOP : NOP : NOP : NOP : NOP LD A, 15 ; colour 15 = white (no EC bit) OUT ($BE), A RET To hide all 32 sprites (e.g. during initialisation), write $D0 to the Y byte of sprite 0: HIDE_SPRITES: LD A, $00 : OUT ($BF), A LD A, $5B : OUT ($BF), A ; write to $1B00 NOP : NOP LD A, $D0 ; $D0 = off-screen sentinel OUT ($BE), A RET The TMS9918A stops processing the sprite list when it encounters $D0 in a Y byte. Writing $D0 to sprite 0's Y is sufficient to hide them all.
2.15.4 Define a 16x16 sprite
2.15.4 Define a 16x16 sprite ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Enable 16x16 sprites by setting bit 1 (SI) of R1: For Screen 2 with 16x16 sprites: R1 = $C2 | $02 = $C6. A 16x16 sprite uses 4 consecutive 8-byte pattern blocks (N, N+1, N+2, N+3) where N must be a multiple of 4. Layout of the 4 blocks: Block N+0: rows 0-7, columns 0-7 (top-left quadrant) Block N+1: rows 0-7, columns 8-15 (top-right quadrant) Block N+2: rows 8-15, columns 0-7 (bottom-left quadrant) Block N+3: rows 8-15, columns 8-15 (bottom-right quadrant) The attribute entry uses the base pattern number N (e.g. N=0, 4, 8...). Example: define a 16x16 smiley face starting at pattern slot 0. The four 8-byte blocks together form a 16x16 pixel image. ; Enable 16x16 sprite mode (modify R1) SET_16X16: LD A, $C6 : OUT ($BF), A : LD A, $81 : OUT ($BF), A ; SI=1 RET ; Write all four 8x8 quadrants to $3800-$381F (patterns 0-3) DEF_SMILEY16: LD A, $00 : OUT ($BF), A LD A, $78 : OUT ($BF), A ; write to $3800 NOP : NOP LD HL, SMILEY16_DATA LD B, 32 ; 4 x 8 bytes SM16LOOP: LD A, (HL) OUT ($BE), A INC HL NOP : NOP : NOP : NOP : NOP DJNZ SM16LOOP RET ; Sprite 0 attribute: Y=80, X=100, pattern=0, colour=14 (grey) SET_SP16: LD A, $00 : OUT ($BF), A LD A, $5B : OUT ($BF), A NOP : NOP LD A, 79 : OUT ($BE), A ; Y NOP : NOP : NOP : NOP : NOP LD A, 100 : OUT ($BE), A ; X NOP : NOP : NOP : NOP : NOP LD A, 0 : OUT ($BE), A ; pattern 0 (base of 4-block set) NOP : NOP : NOP : NOP : NOP LD A, 14 : OUT ($BE), A ; colour 14 (grey) RET SMILEY16_DATA: ; Top-left (block 0, cols 0-7, rows 0-7) DB $07,$1F,$3F,$7F,$7F,$FF,$FF,$FF ; Top-right (block 1, cols 8-15, rows 0-7) DB $E0,$F8,$FC,$FE,$FE,$FF,$FF,$FF ; Bottom-left (block 2, cols 0-7, rows 8-15) DB $FF,$FF,$FF,$7F,$7F,$3F,$1F,$07 ; Bottom-right (block 3, cols 8-15, rows 8-15) DB $FF,$FF,$FF,$FE,$FE,$FC,$F8,$E0
2.16 Undocumented mode combinations (experimental)
2.16 Undocumented mode combinations (experimental) ---------------------------------------------------------------------------- The TMS9918A decodes only 4 of the 8 possible M3/M2/M1 combinations. The remaining 4 produce undefined behaviour that varies between chip revisions and individual units. The table below summarises observations from the MSX and SC-3000 hobbyist community (not guaranteed on all chips): M3 M2 M1 Typical observed behaviour -- -- -- ------------------------------------------------------- 0 1 1 Text mode wins; M2 is ignored. Screen shows 40x24 text. Border colour from R7 may render incorrectly on some chips. 1 1 0 Graphics II pattern-table access but colour table addressing follows the Multicolour formula. Colour data looks corrupted. Some chips show a stripe-distorted Graphics I image instead. 1 0 1 Text mode wins; M1 overrides M3. Sprites disabled. Pattern addressing may use the Graphics II tables, giving garbled text characters on some revisions. 1 1 1 Text mode wins (M1 highest priority). Screen is blank or shows text with randomised pattern data. General rule: when M1=1, it tends to assert Text mode regardless of the other bits. When M1=0 and M2=M3=1, the behaviour is most chip-dependent. EXPERIMENTAL TECHNIQUE: mid-frame mode switching ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Changing R0 (M3) mid-frame by synchronising to the scanline counter (counted in the VBlank interrupt handler) allows different regions of the screen to use different modes. Classic example: Graphics II for the game area + Text mode for a status bar. Skeleton (synchronise mode switch to a target scan line): ; In the VBlank handler -- count down to target line LD A, (SCANLINE_CTR) DEC A LD (SCANLINE_CTR), A JR NZ, VBLANK_DONE ; Target line reached: switch to Text for status bar (top 24 rows) LD A, $00 : OUT ($BF), A : LD A, $80 : OUT ($BF), A ; R0 M3=0 LD A, $D0 : OUT ($BF), A : LD A, $81 : OUT ($BF), A ; R1 Text ; ... render status bar into text-mode VRAM ... ; At next VBlank, switch back to Graphics II VBLANK_DONE: RET Caveats: * Scan-line synchronisation requires tight interrupt timing; BASIC's interrupt handler adds jitter. Use DI/EI to stabilise if needed. * The VDP latches the mode bits at the START of each frame. On some chip revisions, mid-frame R1 writes only take effect on the NEXT frame. Test on real hardware before relying on this behaviour. * On the SC-3000 emulator (web), mid-frame mode switching works only if the emulator processes VDP register writes between rendered scan lines.
CHAPTER 3 -- PSG -- SN76489AN (SOUND PROCESSOR)
CHAPTER 3 -- PSG -- SN76489AN (SOUND PROCESSOR) ============================================================================
3.1 Features
3.1 Features ---------------------------------------------------------------------------- Chip : Texas Instruments SN76489AN Port : $7F (write-only -- no reading possible) Clock : 3.579545 MHz (same clock as the Z80) Channels: 3 tone channels (square wave) + 1 noise channel Volume : 4 bits per channel (attenuation 0=maximum, 15=silent) Output : mono, summed across channels The PSG is a write-only device: it is not possible to read the current register state via I/O. To track current values, keep them in RAM variables.
3.2 Command format
3.2 Command format ---------------------------------------------------------------------------- Each operation requires 1 or 2 bytes written to $7F. BYTE TYPE 1 -- LATCH (bit7=1): selects register and provides the low 4 bits of the data: +---+---+---+---+---+---+---+---+ | 1 | r2| r1| r0| t |d3 |d2 |d1 |d0| +---+---+---+---+---+---+---+---+ Bit 7 : 1 = latch byte Bits 6-5 : channel select (r2-r1-r0 = 3 bits -> see table below) Bit 4 : type (0=frequency, 1=volume) Bits 3-0 : low 4 bits of data BYTE TYPE 2 -- DATA (bit7=0): provides the high 6 bits of the frequency (used only for tone frequency registers, not for volume): +---+---+---+---+---+---+---+---+ | 0 | x |d9 |d8 |d7 |d6 |d5 |d4 | +---+---+---+---+---+---+---+---+ Bit 7 : 0 = data byte Bits 5-0 : high 6 bits of frequency divisor (d9-d4) Register map (bits 6-4 of the latch byte): +----------+----------------------------------------------------+ | 000 (0) | Tone 1 -- frequency (10 bits: latch d3-d0, data d9-d4)| | 001 (1) | Tone 1 -- volume (4 bits: d3-d0; 0=max, 15=off) | | 010 (2) | Tone 2 -- frequency | | 011 (3) | Tone 2 -- volume | | 100 (4) | Tone 3 -- frequency | | 101 (5) | Tone 3 -- volume | | 110 (6) | Noise -- control (see section 3.4) | | 111 (7) | Noise -- volume (4 bits; 0=max, 15=off) | +----------+----------------------------------------------------+ Example -- write volume=0 (maximum) on Tone 1 (register 1): LD A, %10010000 ; bit7=1 latch, bits6-5=00 ch1, bit4=1 vol, d3-d0=0000 OUT ($7F), A ; -> $90 Example -- silence all channels: LD A, $9F ; Tone 1 vol = 15 (off) OUT ($7F), A LD A, $BF ; Tone 2 vol = 15 OUT ($7F), A LD A, $DF ; Tone 3 vol = 15 OUT ($7F), A LD A, $FF ; Noise vol = 15 OUT ($7F), A
3.3 Tone channels -- frequency calculation
3.3 Tone channels -- frequency calculation ---------------------------------------------------------------------------- Formula: f_out = f_clock / (32 x N) N = divisor (10 bits, range 1-1023; N=0 -> maximum frequency) f_clock = 3.579545 MHz Deriving N from a note: N = round( f_clock / (32 x f_note) ) N = round( 3.579545e6 / (32 x f_note) ) Practical examples: A4 (440 Hz) -> N = 3579545 / (32x440) = 254.25 -> N=254 C5 (523 Hz) -> N = 3579545 / (32x523) = 213.8 -> N=214 G4 (392 Hz) -> N = 3579545 / (32x392) = 285.4 -> N=285 Divisor N table for notes (octaves 3-6): +-------+------+------+------+------+ | Note | Oct3 | Oct4 | Oct5 | Oct6 | +-------+------+------+------+------+ | C | 853 | 426 | 213 | 107 | | C# | 805 | 402 | 201 | 101 | | D | 759 | 379 | 190 | 95 | | D# | 716 | 358 | 179 | 90 | | E | 676 | 338 | 169 | 85 | | F | 638 | 319 | 160 | 80 | | F# | 602 | 301 | 151 | 75 | | G | 568 | 284 | 142 | 71 | | G# | 536 | 268 | 134 | 67 | | A | 506 | 253 | 127 | 63 | | A# | 477 | 239 | 119 | 60 | | B | 451 | 225 | 113 | 56 | +-------+------+------+------+------+ Setting the frequency (2 bytes to $7F): ; Tone 1, frequency C4 (N=426 = $1AA) ; N bits: 9 8 7 6 5 4 | 3 2 1 0 ; : 0 1 1 0 1 0 | 1 0 1 0 LD A, %10000000 | ((426 & $F) ) ; = $8A (latch, ch0, freq, lo4) OUT ($7F), A LD A, (426 >> 4) & $3F ; = $1A (data, hi6) OUT ($7F), A Note: the BASIC SOUND c, f, v command uses the value f as frequency in Hz. The ROM internally converts Hz -> divisor N before writing to the PSG. The frequency table in Vol. B lists values in Hz (262=C4, 440=A4).
3.4 Noise channel
3.4 Noise channel ---------------------------------------------------------------------------- Register 6 (noise control) has this format in the latch byte: +-----------------------------------------------------------------+ | Bits 3-2: NOT used (always 0) | | Bit 1 : NF noise type 0=periodic (tone), 1=white (random) | | Bits 1-0: FB shift rate: | | 00 = N/512 (~7 kHz) | | 01 = N/1024 (~3.5 kHz) | | 10 = N/2048 (~1.75 kHz) | | 11 = Tone 3 frequency | +-----------------------------------------------------------------+ Correspondence with BASIC SOUND c, f, v: c=4 -> white noise (NF=1); f selects shift rate (0-2) or Tone3 c=5 -> periodic noise (NF=0); f selects shift rate Latch byte for noise control: $E0 | (NF << 2) | shift_rate SOUND 4,0 -> white noise, shift 0 -> $E4 SOUND 4,1 -> white noise, shift 1 -> $E5 SOUND 4,2 -> white noise, shift 2 -> $E6 SOUND 4,3 -> white noise + Tone3 -> $E7 SOUND 5,0 -> periodic, shift 0 -> $E0 SOUND 5,1 -> periodic, shift 1 -> $E1
3.5 Direct access from BASIC with OUT
3.5 Direct access from BASIC with OUT ---------------------------------------------------------------------------- Using OUT to access the PSG bypasses the ROM: BASIC does not track values set manually. This is acceptable when managing audio entirely from ML or when using CALL. Example: tone A4 (440 Hz) on channel 1, maximum volume ; N = round(3579545 / (32x440)) = 254 = $FE -> lo4=$E, hi6=$0F 10 OUT &H7F, &H8E : REM Tone1 freq lo (ch1, freq, lo4=$E) 20 OUT &H7F, &H0F : REM Tone1 freq hi (hi6=$0F) 30 OUT &H7F, &H90 : REM Tone1 volume = 0 (maximum) Example: silence all channels from BASIC 10 OUT &H7F,&H9F : OUT &H7F,&HBF : OUT &H7F,&HDF : OUT &H7F,&HFF Note: after SOUND or BEEP, PSG registers contain the values set by the ROM. OUT overwrites only the specified registers.
3.6 Write protocol -- detailed behaviour (Maxim, 2003)
3.6 Write protocol -- detailed behaviour (Maxim, 2003) ---------------------------------------------------------------------------- The SN76489 contains 8 internal registers: Register Type Width Description -------- ------- ------ -------------------------------------- 0 Tone 1 10 bit Frequency divisor, channel 1 1 Vol 1 4 bit Attenuation, channel 1 (0=max, F=off) 2 Tone 2 10 bit Frequency divisor, channel 2 3 Vol 2 4 bit Attenuation, channel 2 4 Tone 3 10 bit Frequency divisor, channel 3 5 Vol 3 4 bit Attenuation, channel 3 6 Noise 4 bit Control byte (see section 3.4) 7 Vol N 4 bit Attenuation, noise channel Two write formats exist. The currently-latched register is retained between writes; a DATA byte always affects the last register selected by a LATCH byte. LATCH byte %1 cc t dddd bit 7 : must be 1 bits 6-5 : channel select (cc: 00=ch1, 01=ch2, 10=ch3, 11=noise) bit 4 : type (t: 0=frequency, 1=volume/control) bits 3-0 : low 4 data bits (dddd) DATA byte %0 x dddddd bit 7 : must be 0 bit 6 : ignored (x) bits 5-0 : high 6 data bits (for tone frequency registers only) Assembly of a 10-bit tone divisor (N): LATCH byte carries N bits 3-0 (LSB side) DATA byte carries N bits 9-4 (MSB side) -> full N = (data_byte & $3F) << 4 | (latch_byte & $0F) IMPORTANT -- common emulator misconception: DATA bytes are NOT silently ignored when the latched register is a volume or noise register. The chip still latches the DATA byte and applies its bits 5-0 to the register (which, being only 4 bits wide, takes bits 3-0 of the data byte). Emulators that discard DATA bytes for non-tone registers will produce correct audio only by accident. Example -- set Tone 1 to N=300 ($12C): ; N bits: 9-4 = $0C = 001100 bits 3-0 = $C = 1100 LD A, %10001100 ; LATCH: ch1 freq, lo4=$C -> $8C OUT ($7F), A LD A, %00001100 ; DATA: hi6=%001100 -> $0C OUT ($7F), A Example -- set Tone 2 volume to 7 (half attenuation): LD A, %10110111 ; LATCH: ch2 vol, value=7 -> $B7 OUT ($7F), A ; No DATA byte needed for a volume register
3.7 Noise generator -- LFSR implementation (Maxim, 2003)
3.7 Noise generator -- LFSR implementation (Maxim, 2003) ---------------------------------------------------------------------------- The noise channel uses a 16-bit linear feedback shift register (LFSR). On every clock tick (divided by the selected shift rate) the register is shifted right by one bit. The bit that falls off becomes the output sample. A new bit is fed into the MSB. PERIODIC noise (NF=0): The feedback bit is simply the bit that fell off the LSB end (i.e. the LFSR wraps around). This produces a repeating pattern. WHITE noise (NF=1): The feedback bit is the XOR (parity) of selected "tapped" bit positions within the register. This produces a pseudo-random sequence. SC-3000H tapped bits: 1 and 2 (verified from MacDonald's hardware analysis) LFSR initial value: $8000 (on hardware reset) LFSR width : 16 bits Correct pseudocode for one noise clock step (white noise, SC-3000H): feedback = parity( lfsr & $0006 ) ; bits 1 and 2 lfsr = (lfsr >> 1) | (feedback << 15) output = lfsr & 1 ; before shift: lfsr & 1 where parity(x) = x ^ (x >> 8) ^ (x >> 4) ^ (x >> 2) ^ (x >> 1), reduced to a single bit (LSB of result). WRONG approach (produces incorrect output): feedback = lfsr & 1 (then XOR with a fixed constant) This common error generates a different sequence and must be avoided in emulators targeting SC-3000H compatibility.
3.8 Initial state and SC-3000 boot behaviour (Maxim, 2003)
3.8 Initial state and SC-3000 boot behaviour (Maxim, 2003) ---------------------------------------------------------------------------- On hardware reset the SN76489 initialises as follows: All volume registers : $F (fully attenuated -- all channels silent) All tone registers : $000 (divisor 0) Noise register : $00 SC-3000 note: the ROM does not explicitly write to all PSG registers at startup, so the PSG may be in an undefined state if the system was previously running a cartridge. Code that relies on a clean PSG state should always silence all channels and reset all frequency registers explicitly: ; Initialise PSG: silence all channels, clear Tone 1 LD A, $9F : OUT ($7F), A ; Vol 1 = $F (off) LD A, $BF : OUT ($7F), A ; Vol 2 = $F (off) LD A, $DF : OUT ($7F), A ; Vol 3 = $F (off) LD A, $FF : OUT ($7F), A ; Vol N = $F (off) LD A, $80 : OUT ($7F), A ; Tone 1 freq latch = 0 LD A, $00 : OUT ($7F), A ; Tone 1 freq data = 0 LD A, $A0 : OUT ($7F), A ; Tone 2 freq latch = 0 LD A, $00 : OUT ($7F), A ; Tone 2 freq data = 0 LD A, $C0 : OUT ($7F), A ; Tone 3 freq latch = 0 LD A, $00 : OUT ($7F), A ; Tone 3 freq data = 0
3.9 Signal imperfection -- output voltage decay (Maxim, 2003)
3.9 Signal imperfection -- output voltage decay (Maxim, 2003) ---------------------------------------------------------------------------- The SN76489 does not generate a perfect square wave. The output voltage of each channel decays toward 0 V between transitions. This means that: * Very low-frequency tones sound slightly distorted (the waveform is not symmetric). * The effective DC level of a playing channel is not 0. In practice this is inaudible at normal music and SFX frequencies (above ~50 Hz) and rarely affects SC-3000 software.
3.10 Sample playback techniques (Maxim, 2003)
3.10 Sample playback techniques (Maxim, 2003) ---------------------------------------------------------------------------- The SN76489 can approximate digital audio despite being a programmable sound generator. Two techniques are used in practice: PWM -- Pulse Width Modulation (1-bit resolution) 1. Set all three tone channels to frequency divisor N=0 (maximum frequency: ~112 kHz, above audible range). 2. Alternate volume between 0 (maximum) and $F (silence) on a tone channel at the desired sample rate. The rapidly-toggled channel acts as a 1-bit DAC; low-pass filtering (inherent in the speaker) produces the audio signal. ; Toggle Tone 1 between max and silent (called at sample rate): LD A, $90 : OUT ($7F), A ; Vol 1 = 0 (on) ; ... wait sample period ... LD A, $9F : OUT ($7F), A ; Vol 1 = $F (off) PCM -- 4-bit DAC via volume register Use a single channel's volume register as a 4-bit output value. Update it at the sample rate with successive 4-bit samples. The volume register steps are not perfectly linear in amplitude (they are approximately 2 dB apart), but the result is intelligible for voice or simple waveforms. Effective sample rate is limited by the CPU time available to continuously write to the PSG. At 3.579 MHz, a practical upper limit for BASIC is ~5-8 kHz; assembly routines can reach ~20 kHz.
3.11 Credits -- SN76489 extended notes
3.11 Credits -- SN76489 extended notes ---------------------------------------------------------------------------- Sections 3.6-3.10 are based on: "SN76489 notes" by Maxim (maxim@mwos.cjb.net) Last updated: 21 April 2003 Used for educational and documentation purposes. Original document: http://www.smspower.org/Development/SN76489
CHAPTER 4 -- PPI -- INTEL 8255 (PERIPHERAL INTERFACE)
CHAPTER 4 -- PPI -- INTEL 8255 (PERIPHERAL INTERFACE) ============================================================================
4.1 Features
4.1 Features ---------------------------------------------------------------------------- Chip : Intel 8255A (or compatible) Ports : $DC (Port A), $DD (Port B), $DE (Port C), $DF (Control) Function : interface between the Z80 CPU and physical peripherals: keyboard matrix, cassette CMT, serial printer, joystick Port configuration (input/output) is set by the ROM at initialisation via the Control Register ($DF).
4.2 Port configuration in the SC-3000
4.2 Port configuration in the SC-3000 ---------------------------------------------------------------------------- Based on ROM disassembly analysis (KEYREAD $4918, keyboard scan $48A3): Port A ($DC) INPUT Reads keyboard data (matrix rows, columns 0-5) Port B ($DD) INPUT Reads keyboard data (matrix rows, columns 4-7), joystick, and CMT data read (bit 7) Port C ($DE) OUTPUT Bits 2-0: keyboard column select Bit 3: CMT motor relay Bit 4: CMT data write Bits 7-5: serial printer (data/reset/feed) Note: the exact Port C configuration (which nibble is input/output) depends on the value written to the Control Register at startup.
4.3 Port C ($DE) -- bit map
4.3 Port C ($DE) -- bit map ---------------------------------------------------------------------------- Bit Dir Signal Description --- ---- -------------- ----------------------------------------- 0 OUT KEY_COL0 Keyboard column select (bit 0) 1 OUT KEY_COL1 Keyboard column select (bit 1) 2 OUT KEY_COL2 Keyboard column select (bit 2) 3 OUT CMT_MOTOR Cassette motor relay (1=on, 0=off) 4 OUT CMT_WRITE Cassette data write (FSK serial bit) 5 OUT PRINTER_DATA Serial printer data (DIN pin 3) 6 OUT PRINTER_RESET Serial printer reset (DIN pin 4) 7 OUT PRINTER_FEED Serial printer feed (DIN pin 5) (CMT data READ is on Port B bit 7 / $DD, not on Port C.) From KEYREAD disassembly ($4918): IN A,($DE) ; read current Port C AND $F8 ; preserve bits 7-3, clear bits 2-0 (column=0) LD C, A ; save state (with column=0) LD B, 6 ; 6 iterations (columns 0-5) loop: OUT ($DE), C ; set current column CALL $4964 ; read row data (IN A,($DC)) -> A LD (HL), A ; save in KEYIM[i] INC C ; next column (bits 2-0 increment 0->1->2...->5) INC HL DJNZ loop
4.4 Keyboard matrix
4.4 Keyboard matrix ---------------------------------------------------------------------------- Structure: 8 columns x 8 rows = 64 key positions Column select: bits 2-0 of Port C (values 0-7) Row read: Port A ($DC) for columns 0-5, Port B ($DD) for 5-7 Logic: bit=0 -> key pressed (inverted logic -- pull-up resistors) RAM image (updated by the interrupt handler every frame): KEYIM0 ($9460) -- column 0 data KEYIM1 ($9461) -- column 1 data KEYIM2 ($9462) -- column 2 data KEYIM3 ($9463) -- column 3 data KEYIM4 ($9464) -- column 4 data KEYIM5 ($9465) -- column 5 data KEYIM6 ($9466) -- column 6 data (joystick P1 directions) KEYIM7 ($9467) -- column 7 data (joystick P2 directions) KEYIMC ($9468) -- control byte (joystick P1/P2 buttons, BREAK) Keyboard matrix layout (from ROM analysis -- SC-3000H/SF-7000 full keyboard): +-----+------------------------------------------------------------+ | Col | Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 | +-----+------------------------------------------------------------+ | 0 | I K , Kana Z A Q 1 | | 1 | O L . SPC X S W 2 | | 2 | P ; / CLR C D E 3 | | 3 | @ : RO DEL V F R 4 | | 4 | [ ] v -- B G T 5 | | 5 | -- CR <- -- N H Y 6 | | 6 | -- ^ -> -- M J U 7 | | 7 | 8 9 0 - ^ \ GRP BREAK | +-----+------------------------------------------------------------+ Bit=1 after CPL (in KEYREAD) = key PRESSED. NOTE: this layout is for the SC-3000H/SF-7000 keyboard. On the base SC-3000 (reduced chicklet keyboard) many positions correspond to different keys or to connected joysticks. Columns 6-7 in particular share bits with joystick P1/P2 direction inputs (on the directional arrow pins). Reading a specific key from ML: ; Example: read key '1' (column 0, bit 1) CALL $4A00 ; KEYREAD -- update KEYIM0-8 LD A, ($9460) ; KEYIM0 -- column 0 BIT 1, A ; bit 1 = key '1' JR Z, pressed ; Z=1 if bit=0 -> key pressed Or simply use INKEYS (CALL $4A36) or KEYIN1 ($4A51).
4.5 BRKFLG and SPCFLG -- special keys
4.5 BRKFLG and SPCFLG -- special keys ---------------------------------------------------------------------------- BRKFLG ($9469): BREAK key flag 0 = not pressed 1 = pressed (set by the KEYREAD routine / interrupt handler) Check with: CALL $4A03 (BRKCHK) -> Cf=1 if pressed SPCFLG ($946A): SPACE key flag Similar to BRKFLG. Check with: CALL $4A06 (SPCCHK) -> Cf=1 if pressed
4.6 Cassette interface (CMT -- Cassette Magnetic Tape)
4.6 Cassette interface (CMT -- Cassette Magnetic Tape) ---------------------------------------------------------------------------- The physical signal is FSK audio (Frequency Shift Keying): bit '1' -> 2400 Hz (H tone) bit '0' -> 1200 Hz (L tone) Baud rate : 1200 baud The analogue comparator converts the audio signal to the digital bit read by CMT_READ (Port B bit 7, I/O $DD). Electrical levels (3.5 mm 2P mini-jack): Output : 20 mV P-P ±5 mV (to cassette recorder MIC input) Input : >= 1 V P-P (from cassette recorder EAR output) Signals involved: CMT_MOTOR (Port C bit 3): 1=motor on, 0=motor off CMT_WRITE (Port C bit 4): write data bit (FSK-modulated) CMT_READ (Port B bit 7): read data bit (from comparator) Data structure on tape: +------------------------------------------------------------------+ | Leader : 3600 x bit '1' (approx. 1.5 sec -- read sync) | | Sync byte : $16 (or similar) | | Header : filename (16 ASCII bytes) + type + length | | Leader 2 : second sequence of '1's | | Data : tokenised program bytes | | Checksum : verification byte | +------------------------------------------------------------------+ Motor control from BASIC: MOTOR 0 motor OFF (Port C bit 3 = 0) MOTOR 1 motor ON (Port C bit 3 = 1) ; any non-zero value MOTOR toggle Direct ML motor control (as used by the ROM, $7EC8/$7ED1): IN A, ($DE) OR $08 ; set bit 3 -> motor ON OUT ($DE), A ; --- or --- IN A, ($DE) AND $F7 ; clear bit 3 -> motor OFF OUT ($DE), A ROM routines for CMT (Vol. D ch. 6): BYTERD ($3A06) -- read 1 byte from tape -> A BYTEWD ($3A12) -- write 1 byte A to tape LEADSR ($3A00) -- wait for leader signal (256 x '1') LEADWR ($3A0F) -- write leader signal (3600 x '1') WVSCAN ($39FD) -- FSK waveform scan CDELAY ($3A03) -- calibrated delay: 279 ns x C
4.7 Serial printer interface
4.7 Serial printer interface ---------------------------------------------------------------------------- The protocol used is synchronous serial with strobe. Signals involved: PRINTER_STROBE (Port C bit 7): handshake pulse Data: sent via Port A or Port B (verify with LPRINT handler disassembly) BASIC redirects LPRINT by setting PUTADR ($82A0) = $7008. The routine at $7008 manages the physical handshake with the printer. Checking printer busy: see 7.4 -- BUSY is Port B bit 6 (PB6), read with IN A,($DD) / BIT 6,A. (An earlier draft attributed this to a "BRKCHK" routine at $48A3. That was wrong twice over: $48A3 is NOT BRKCHK -- the real break-check is $5933, reached via SYS $4A03 -- and $48A3 does not read printer status at all. $48A3 is a keyboard key-scanner: it selects keyboard columns 5 and 6 in turn and tests Port B bit 3 (key-row data): IN A, ($DE) AND $F8 OR $05 ; select keyboard column 5 OUT ($DE), A CALL $4972 ; settle delay IN A, ($DD) ; Port B: bit 3 = key-row data, NOT printer busy BIT 3, A )
4.8 Port C bit 3 -- cassette motor (NO PSG "sound gate")
4.8 Port C bit 3 -- cassette motor (NO PSG "sound gate") ---------------------------------------------------------------------------- Earlier drafts labelled Port C bit 3 a PSG "sound gate". ROM analysis disproves this: an exhaustive scan shows the ONLY routines that touch bit 3 are the cassette-motor primitives $7EC8/$7ED1/$7EDA (see 4.6). The SN76489 PSG has no hardware gate on the PPI -- audio is silenced by writing the volume/attenuation registers on port $7F (see Chapter 3). So bit 3 is the cassette motor relay: IN A, ($DE) OR $08 ; set bit 3 -> cassette motor ON OUT ($DE), A
CHAPTER 5 -- JOYSTICK
CHAPTER 5 -- JOYSTICK ============================================================================
5.1 D-Sub 9-pin connector
5.1 D-Sub 9-pin connector ---------------------------------------------------------------------------- Standard Atari/Sega pinout (compatible with Mega Drive): Active-low signals (contact with GND = pressed). Pin Signal RAM image Description --- -------- ------------- ------------------------- 1 UP KEYIM6 bit 6 P1 up (same bit as ^ on SC-3000H) 2 DOWN KEYIM4 bit 5 P1 down (same bit as v on SC-3000H) 3 LEFT KEYIM5 bit 5 P1 left (same bit as <- on SC-3000H) 4 RIGHT KEYIM6 bit 5 P1 right (same bit as -> on SC-3000H) 5 (VCC) -- +5V power supply 6 TRG1 KEYIMC bit 0 P1 button 1 (KANA position on SC-3000H) 7 (n.c.) -- 8 GND -- Ground 9 TRG2 KEYIMC bit 2 P1 button 2 (CTRL position on SC-3000H) Joystick P2: directions in KEYIM7 (bit6=UP, bit5=RIGHT etc.); buttons TRG1 -> KEYIMC bit 1 (FUNC), TRG2 -> KEYIMC bit 3 (SHIFT). KEYIMC ($9468) -- modifier keys / fire buttons byte: +------+--------------------------------------------------------+ | Bit | SC-3000H keyboard | SC-3000 base (joystick connected) | +------+--------------------+-----------------------------------+ | 0 | KANA | TRG1 P1 | | 1 | FUNC | TRG1 P2 | | 2 | CTRL | TRG2 P1 | | 3 | SHIFT | TRG2 P2 | | 7 | CTRL (dup.) | -- | +------+--------------------+-----------------------------------+ Bit=1 = key pressed (active-high logic, after CPL in KEYREAD). Built by KEYREAD: bit0=~PortA_col0_bit4, bit1=~PortB_col5_bit0, bit2=~PortB_col6_bit2, bit3=~PortB_col6_bit3. NOTE: the TRG1/TRG2 correspondence requires confirmation on physical hardware.
5.2 STICK() and STRIG() -- returned values
5.2 STICK() and STRIG() -- returned values ---------------------------------------------------------------------------- STICK(f) -- f=1 joystick P1, f=2 joystick P2 +---+---------------+ +---+----------------------+ | 0 | no direction | | 5 | down | | 1 | up | | 6 | down-left | | 2 | up-right | | 7 | left | | 3 | right | | 8 | up-left | | 4 | down-right | +---+----------------------+ +---+---------------+ STRIG(f) -- f=1 joystick P1, f=2 joystick P2 0 = no button 1 = button 1 (TRG1) 2 = button 2 (TRG2) 3 = both buttons
5.3 Direct read with INP()
5.3 Direct read with INP() ---------------------------------------------------------------------------- Joystick directions are in bytes KEYIM6 ($9466) and KEYIM7 ($9467), updated by the interrupt handler. Reading from BASIC: 10 J = PEEK(&H9466) : REM P1 directions (active bits=0) 20 IF (J AND 1)=0 THEN PRINT "UP" 30 IF (J AND 2)=0 THEN PRINT "DOWN" 40 IF (J AND 4)=0 THEN PRINT "LEFT" 50 IF (J AND 8)=0 THEN PRINT "RIGHT" Safe alternative: STICK(1) and STRIG(1) use the already-updated RAM image and return processed values. For the most responsive read (bypasses the RAM image): CALL $4A00 ; KEYREAD -- immediately update KEYIM0-8 LET J = PEEK(&H9466) ; then read KEYIM6
CHAPTER 6 -- SC-3000H HARDWARE NOTES
CHAPTER 6 -- SC-3000H HARDWARE NOTES ============================================================================ Source: Charles MacDonald, 2002-2004. Original title: "Sega SC-3000H hardware notes". Reproduced with attribution (see #6.9). Last revision: 2004-07-29. This chapter consolidates hardware-level information about the SC-3000H that complements the other chapters of this volume. It covers the physical components, connector pinouts, cartridge memory maps, port decoding quirks, the complete keyboard matrix, the MITEC-2 custom chip, and the cassette interface chip.
6.1 Component list
6.1 Component list ---------------------------------------------------------------------------- Manufacturer Part Description ----------------- ------------------ ---------------------------------- Fujitsu MB82128-15 2Kx8 SRAM (Z80 work RAM) Fujitsu MB74LS145 1-of-10 decoder (keyboard row sel.) Mitsubishi MN74HC04 Hex inverter (clock divider) Motorola MCM4517P15 (x8) 16Kx1 Page Mode DRAM (VDP VRAM) NEC uPD9255AC-2 Programmable peripheral interface SEGA MITEC-2 Custom chip (Z80 address decoding) Sharp LH0080A CPU (Z80-A compatible) Texas Instruments SN76489AN Programmable sound generator Texas Instruments TMS9929ANL Video display processor (PAL) Both SN76489A and TMS9929A are the 'A' revision: * TMS9929 (no suffix) lacks Graphics II mode -- added in TMS9929A. * SN76489 (no suffix) has a different noise generator than SN76489A. Clock: Master clock: 10.738635 MHz -- used directly by TMS9929A. The hex inverter (MN74HC04) plus analog components divides the master by 3 to produce the Z80 and SN76489A clock of 3.579545 MHz.
6.2 Physical connectors
6.2 Physical connectors ----------------------------------------------------------------------------
6.2.1 Joystick connectors (male DB9 on back panel)
6.2.1 Joystick connectors (male DB9 on back panel) Pin diagram: 1 2 3 4 5 6 7 8 9 Pin Signal --- ------------------------------------------------------------------ 1 Joystick up 2 Joystick down 3 Joystick left 4 Joystick right 5 Unused (not connected) 6 Trigger left 7 Unused (not connected) 8 Common (active-LOW when row 7 of keyboard matrix selected) 9 Trigger right Pin 8 is the common side of all joystick switches. It is driven LOW by output line 7 of the 74LS145 when row 7 is selected (enabling joystick input) and HIGH otherwise. Caution: this differs from SMS/Genesis which use pin 8 as GND and pin 5 as +5V. Original SMS joypads work, but autofire, programmable, or Genesis/MegaDrive pads are not guaranteed to be compatible.
6.2.2 Printer connector (female 7-pin DIN on back panel)
6.2.2 Printer connector (female 7-pin DIN on back panel) Pin diagram: 1 7 2 6 3 5 4 Pin Signal --- ------------------------------------------------------------------ 1 Unused (not connected) 2 PPI PC5 (DATA output) 3 PPI PC7 (/FEED output) 4 PPI PB6 (BUSY input) 5 PPI PC6 (/RESET output) 6 PPI PB5 (FAULT input) 7 GND
6.2.3 Audio/Video connector (female 5-pin DIN on back panel)
6.2.3 Audio/Video connector (female 5-pin DIN on back panel) NTSC and PAL composite models: Pin diagram: 1 5 2 4 3 Pin Signal --- ------------------------------------------------------------------ 1 Audio out 2 Ground 3 Composite video out 4 Ground 5 Ground Signal levels (from SC-3000 service manual): Audio : load >= 10 kOhm; level 1 V max +0.2 V Composite : recommended load 75 Ohm; level 0.9 Vp-p +0.1 V Note: MacDonald (2002) lists pin 1 as composite and a "pin 6" as audio. The 5-pin DIN has no pin 6; the Sega service manual assignment above is from the primary source and is considered authoritative. Note: a US SMS 2 power supply and standard Genesis/MegaDrive composite A/V cable (composite + mono audio) are compatible with the SC-3000H. 6.2.3b RGB connector (female 8-pin DIN -- PAL/RGB models only) The SC-3000 PAL and RGB variants use an 8-pin DIN for RGB output. This connector is NOT present on the standard NTSC composite SC-3000. Pin Signal --- ------------------------------------------------------------------ 1 Audio 2 Ground 3 Composite sync (SYNC) 4 Horizontal sync (HSYNC) 5 Vertical sync (VSYNC) 6 Red (R) 7 Green (G) 8 Blue (B)
6.2.4 Internal keyboard ribbon cable (CN7 and CN8)
6.2.4 Internal keyboard ribbon cable (CN7 and CN8) Pin numbering left (1) to right (11) for both connectors. CN7 Pin Signal --- ------------------------------------------------------------------ 1 74LS145 output line 0 (keyboard row select, output) 2 74LS145 output line 1 3 74LS145 output line 2 4 74LS145 output line 3 5 74LS145 output line 4 6 74LS145 output line 5 7 74LS145 output line 6 8 PPI PA0 (keyboard data, input) 9 PPI PA1 10 PPI PA2 11 PPI PA3 CN8 Pin Signal --- ------------------------------------------------------------------ 1 PPI PA4 2 PPI PA5 3 PPI PA6 4 PPI PA7 5 PPI PB0 6 PPI PB1 7 PPI PB2 8 PPI PB3 9 Ground 10 Not used by keyboard (connected to power switch) 11 RESET key -> MITEC-2 pin 3 (NMI-IN) CN7 pins 1-7: outputs to keyboard row select (74LS145). CN7 pins 8-11 and CN8 pins 1-8: inputs with pull-up resistors for key data.
6.2.5 Cartridge connector (44-pin)
6.2.5 Cartridge connector (44-pin) Solder side (A-row) ---------------------------------------------------------------------------- A01-A14 : Z80 address bus A0-A13 A15-A22 : Z80 data bus D0-D7 Components side (B-row) Pin Signal ---- ---------------------------------------------------------------- B01 +5V B02 +5V B03 Work RAM /CS (pull-up; tied HIGH disables work RAM at $C000-$FFFF) B04 /EXM1 (chip select for $8000-$BFFF) B05 MEMORY /RD (/MREQ AND /RD) B06 MEMORY /WR (/MREQ AND /WR) B07 I/O PORT /RD (/IORQ AND /RD) B08 I/O PORT /WR (/IORQ AND /WR) B09 Unused B10 Z80 /MREQ B11 /CONT (PPI PB4 via pull-up) B12 /RAS0 (DRAM row address strobe, bank 0) B13 /CAS0 (DRAM column address strobe, bank 0) B14 CA7 (DRAM column address bit 7) B15 /RAS1 (DRAM row address strobe, bank 1) B16 /CAS1 (DRAM column address strobe, bank 1) B17 /RCSEL (row/column select: 0=row, 1=column) B18 Z80 A14 B19 Z80 A15 B20 Unused B21 GND B22 GND
6.3 Cartridge memory maps
6.3 Cartridge memory maps ---------------------------------------------------------------------------- The MITEC-2 provides /CS for $8000-$BFFF (/EXM1) and $C000-$FFFF (work RAM). The rest of the decoding is up to the cartridge. Unused address ranges return the high byte of the address bus (e.g. reading $C1AB returns $C1).
6.3.1 Safari Hunting
6.3.1 Safari Hunting 16K ROM ($0000-$3FFF, mirrored at $4000-$7FFF) 2K RAM ($8000-$BFFF, mirrored every 2K) 2K Work RAM ($C000-$FFFF, mirrored every 2K) ROM /OE = Z80 A15 (ROM enabled when A15=0), ROM /CS = /MREQ. RAM /CS = /EXM1 (B04).
6.3.2 BASIC Level III A
6.3.2 BASIC Level III A 32K ROM ($0000-$7FFF) 16K DRAM ($8000-$BFFF, 2x TMS4416) 2K Work RAM ($C000-$FFFF, mirrored every 2K) -- or with work RAM disabled and remaining DRAM populated: 32K DRAM ($C000-$FFFF, latter 16K) ROM /OE = B05 (MEMORY /RD), ROM /CS = 74LS32 gate (enabled when Z80 A15=0 and /MREQ active).
6.3.3 Other cartridge types (from external sources)
6.3.3 Other cartridge types (from external sources) Terebi Oekaki $0000-$1FFF : ROM $6000 : Graphics tablet axis select $8000 : Pen pressure / axis conversion busy flag $A000 : Axis data SF-7000 $0000-$3FFF : ROM (8K, mirrored) $0000-$FFFF : RAM (48K from $4000, or 64K from $0000 after ROM swap) On power-up, $0000-$3FFF is ROM; the SF-7000's PPI can swap it out.
6.3.4 DRAM interface (MITEC-2 output signals)
6.3.4 DRAM interface (MITEC-2 output signals) MITEC pin Cartridge pin Signal Description --------- ------------- ------- ---------------------------------- 19 B14 CA7 DRAM column address bit 7 20 B17 /RCSEL Row/column select (0=row, 1=column) 21 B12 /RAS0 Row address strobe, bank 0 22 B15 /RAS1 Row address strobe, bank 1 23 B13 /CAS0 Column address strobe, bank 0 24 B16 /CAS1 Column address strobe, bank 1 Address multiplexing for TMS4416 16Kx4 DRAMs: DRAM pin Row (/RCSEL=0) Column (/RCSEL=1) -------- -------------- ----------------- A0 GND Z80 A0 A1 Z80 A8 Z80 A1 A2 Z80 A9 Z80 A2 A3 Z80 A10 Z80 A3 A4 Z80 A11 Z80 A4 A5 Z80 A12 Z80 A5 A6 Z80 A13 Z80 A6 A7 GND CA7 Bits 0 and 7 of the row are unused (GND) to allow expansion to 64Kx4 DRAMs, which use those bits for address bits 14 and 15.
6.3.5 Cartridge bus timing (from SC-3000 service manual, Fig. 2-1 -- 2-3)
6.3.5 Cartridge bus timing (from SC-3000 service manual, Fig. 2-1 -- 2-3) These are the timing constraints that cartridge hardware must satisfy. All values in nanoseconds; scale 10 ns/mm in the original timing charts. M1 cycle (opcode fetch): ROM / SRAM /OE assertion to data valid : 270 ns max ROM access time from /MREQ : 375 ns max DRAM tRAC : 290 ns max DRAM tCAC : 140 ns max Memory read/write cycle: ROM / SRAM /OE assertion to data valid : 370 ns max SRAM tWP (write pulse width) : 290 ns max ROM / SRAM access time from /MREQ : 520 ns max SRAM from /WE to data valid : 670 ns max DRAM tRAC : (not specified separately) DRAM tCAC : 255 ns max I/O read/write cycle: I/O access time (device must respond) : 545 ns max
6.4 I/O port decoding
6.4 I/O port decoding ---------------------------------------------------------------------------- The PPI, VDP, and PSG are enabled according to the port address range: Range PPI VDP PSG Data returned on read ------- --- --- --- ---------------------------------------------- $00-1F Y Y Y PPI + VDP (some bits corrupted -- bus conflict) $20-3F N Y Y VDP $40-5F Y N Y PPI $60-7F N N Y Last value from memory refresh cycle (see #6.5) $80-9F Y Y N PPI + VDP (bus conflict) $A0-BF N Y N VDP ($BE = data, $BF = status/register) $C0-DF Y N N PPI ($DC/$DD/$DE/$DF -- see Ch. 4) $E0-FF N N N Last value from memory refresh cycle (see #6.5) For write operations, data goes to all enabled devices simultaneously. The PSG has no readable registers; its presence does not affect read data. When PPI and VDP are both enabled, the PPI drives the bus but some bits are corrupted by the VDP attempting to drive simultaneously.
6.5 Z80 memory refresh behavior
6.5 Z80 memory refresh behavior ---------------------------------------------------------------------------- During every opcode fetch the Z80 performs an automatic DRAM refresh cycle (T3/T4 states): /RFSH goes low, the IR register pair drives the address bus, and /MREQ pulses low. This looks like a memory read but uses /RFSH instead of /RD. Most SC-3000 cartridges wire ROM /OE = /MREQ and ROM /CS = A15, so the ROM responds to refresh cycles in $0000-$7FFF and places data on the bus. The MITEC-2 does not enable work RAM during refresh, so reads to $C000-$FFFF during refresh return the last value left on the data bus. This directly affects ports $60-$7F and $E0-$FF: no device drives the bus for those reads, so the value returned is whatever was left on the bus from the preceding refresh cycle. Example (port $E0 read, IR=$2A01, ROM data at $2A00 = $AB, $CD, $EF): Opcode fetch #1 ($ED): refresh reads $CD from $2A01 Opcode fetch #2 ($78): refresh reads $EF from $2A02 I/O read from $E0: no device drives bus -> returns $EF PSG write address ($7F) falls in the $60-$7F range. The PSG is write- only; reading $7F returns the memory refresh value, not PSG data.
6.6 Keyboard matrix and PPI ports
6.6 Keyboard matrix and PPI ports ----------------------------------------------------------------------------
6.6.1 PPI port assignments (see Ch. 4 for register-level detail)
6.6.1 PPI port assignments (see Ch. 4 for register-level detail) Port A (input) -- PA7-PA0 with pull-up resistors, active-low All 8 bits: keyboard/gamepad row data Port B (input) -- PB6-PB0 pull-up active-low; PB7 active-high D7 : Cassette-IN signal (active-high) D6 : BUSY from printer port D5 : FAULT from printer port D4 : /CONT from cartridge connector pin B11 D3-D0 : keyboard/gamepad row data Port C (output) D7 : /FEED output -> printer pin 3 D6 : /RESET output -> printer pin 5 D5 : DATA output -> printer pin 2 D4 : Unused D3 : Cassette-OUT signal D2-D0 : Keyboard row select -> 74LS145 (selects 1 of 8 rows) Default initialisation: write $92 to PPI control register (ports A and B as inputs, port C as output), write $07 to port C (select row 7 = gamepad row). Reading port C returns the last written value. The PPI control register always returns $FF when read.
6.6.2 Key matrix (8 rows x 12 columns)
6.6.2 Key matrix (8 rows x 12 columns) Data is returned active-low (0 = key pressed, 1 = released). PPI Port A PPI Port B Row D0 D1 D2 D3 D4 D5 D6 D7 D0 D1 D2 D3 --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- 0 '1' 'Q' 'A' 'Z' ED ',' 'K' 'I' '8' --- --- --- 1 '2' 'W' 'S' 'X' SPC '.' 'L' 'O' '9' --- --- --- 2 '3' 'E' 'D' 'C' HC '/' ';' 'P' '0' --- --- --- 3 '4' 'R' 'F' 'V' ID PI ':' '@' '-' --- --- --- 4 '5' 'T' 'G' 'B' --- DA ']' '[' '^' --- --- --- 5 '6' 'Y' 'H' 'N' --- LA CR --- YEN --- --- FNC 6 '7' 'U' 'J' 'M' --- RA UA --- BRK GRP CTL SHF 7 1U 1D 1L 1R 1TL 1TR 2U 2D 2L 2R 2TL 2TR Key abbreviations: ED = ENG DIER'S SPC = Spacebar HC = HOME CLR ID = INS DEL PI = pi symbol DA = Down arrow LA = Left arrow RA = Right arrow UA = Up arrow CR = Enter (CR) YEN = Yen symbol BRK = BREAK GRP = GRAPH CTL = CTRL FNC = FUNC SHF = SHIFT --- = Unused (always 1) 1U/1D/1L/1R = Joystick 1 Up/Down/Left/Right 1TL/1TR = Joystick 1 Trigger Left/Right 2U/2D/2L/2R = Joystick 2 Up/Down/Left/Right 2TL/2TR = Joystick 2 Trigger Left/Right
6.6.3 Key ghosting
6.6.3 Key ghosting The keyboard uses a passive matrix with no diodes. Two ghosting rules: Rule 1 (row ghost): if two or more keys in the same row are held, any key in another row that shares a column will cause all other keys in those columns to appear pressed. Example: holding 1, Q, A, Z and pressing 2 also reports W, S, X. Rule 2 (column ghost): if two or more keys in the same column are held, any key in another column that shares a row will cause all other keys in those rows to appear pressed. Example: holding 1, 2, 3, 4 and pressing Q also reports W, E, R. The joystick ports share rows/columns and are subject to the same rules.
6.7 TMS9929A notes
6.7 TMS9929A notes ---------------------------------------------------------------------------- * Register write: bits 6-3 of the second byte written to $BF are ignored by the VDP. * Data written to the data port ($BE) is also copied to the VRAM read buffer (pre-loads the read latch). * Bits 7-3 of register #0 and bit 2 of register #1 have no function. * Bit 0 of register #0 does not blank the screen; instead it distorts display synchronisation. It was intended for external video input, which is unused on the SC-3000H. * Bit 7 of register #1 selects 4K vs 8K/16K VRAM addressing mode. The SC-3000H uses 16K DRAMs (MCM4517P15); always use 8/16K mode. VRAM address multiplexing (bit 7 reg #1): VDP bus 4K mode (bit7=0) 8/16K mode (bit7=1) ------- ---------------- ---------------------- Row AD0 VA6 VA7 Row AD1 VA7 VA8 Row AD2 VA8 VA9 Row AD3 VA9 VA10 Row AD4 VA10 VA11 Row AD5 VA11 VA12 Row AD6 VA13 VA13 Col AD0 VA0 VA0 Col AD1 VA1 VA1 Col AD2 VA2 VA2 Col AD3 VA3 VA3 Col AD4 VA4 VA4 Col AD5 VA5 VA5 Col AD6 VA12 VA6 In 4K mode all 16K of VRAM are still accessible, but at different physical addresses.
6.8 Custom chip pinouts
6.8 Custom chip pinouts ----------------------------------------------------------------------------
6.8.1 Cassette interface chip (11-pin SIP, epoxy-covered PCB)
6.8.1 Cassette interface chip (11-pin SIP, epoxy-covered PCB) Pin Signal --- ------------------------------------------------------------------ 1 From CASSETTE-IN miniplug jack (analog input) 2 Input from PPI PC3 (tape data out from BASIC) 3 To CASSETTE-OUT miniplug jack (analog output) 4 Ground 5 Output to PPI PB7 (digitised tape data in) 6 Unused 7 Unused 8 Unused 9 Unknown (connected to a transistor and analog components) 10 Unused 11 +5V This chip digitises incoming tape signals for PPI PB7 and converts PPI PC3 output to analog for tape recording.
6.8.2 SEGA MITEC-2 (28-pin DIP)
6.8.2 SEGA MITEC-2 (28-pin DIP) +----v----+ Z80 /NMI |01 o x 28| VCC Z80 /MREQ |02 i o 27| TMS9929A /CSW NMI-IN |03 i o 26| TMS9929A /CSR Z80 /RD |04 i o 25| SN76489A /CE Z80 /WR |05 i o 24| /CAS1 Z80 /IORQ |06 i o 23| /CAS0 Z80 /RFSH |07 i o 22| /RAS1 MEMORY /RD |08 o o 21| /RAS0 MEMORY /WR |09 o o 20| /RCSEL I/O PORT /RD|10 o o 19| CA7 I/O PORT /WR|11 o i 18| Z80 A7 Z80 A14 |12 i i 17| Z80 A6 Z80 A15 |13 i o 16| /EXM1 GND |14 x o 15| Work RAM /CS +---------+ NMI-IN (pin 3): connected to keyboard RESET key. MEMORY /RD, /WR (pins 8-9): drive 2Kx8 work RAM and cartridge B05/B06. I/O PORT /RD, /WR (pins 10-11): drive 8255 PPI and cartridge B07/B08. TMS9929A /CSR, /CSW (pins 26-27): VDP has no chip select, so MITEC-2 generates separate read/write enables. SN76489A /CE (pin 25): PSG /WE and /READY are both wired to Z80 /WAIT. MITEC-2 asserts /CE when the Z80 writes to the PSG port; PSG /READY then holds /WAIT low while the PSG latches the data byte.
6.9 Miscellaneous notes
6.9 Miscellaneous notes ---------------------------------------------------------------------------- * The SN76489A is not reset on power-up. After a power cycle it replays whatever sounds were last written. From a cold boot it emits a medium- volume tone. Always silence all channels at program start (see Ch. 3). * The Z80 interrupt mode: when an interrupt occurs the value on the data bus is unpredictable (similar to reading $60-$7F; see #6.5). Therefore interrupt modes 0 and 2 cannot be used reliably. The SC-3000 BASIC ROM uses interrupt mode 1 ($0038). To use IM 2, one technique is to fill 257 bytes starting at the I register value with the same byte so that the vector address is deterministic regardless of bus noise.
6.10 Credits and disclaimer
6.10 Credits and disclaimer ---------------------------------------------------------------------------- Source document: "Sega SC-3000H hardware notes" Author: Charles MacDonald Website: http://cgfm2.emuviews.com Copyright 2002-2004 Charles MacDonald (unpublished work) Credits from original document: Omar Cornut -- MEKA emulator, cartridge/AV pinouts, Terebi Oekaki info Marc Le Dourain -- SF-7000 webpage (PPI and keyboard information) Sean Young -- TMS9918 documentation Disclaimer: information from this chapter is used with attribution as requested by the author. For the original document visit the author's website. This chapter may not be redistributed separately without the author's consent.
CHAPTER 7 -- SERIAL PRINTER INTERFACE (PRACTICAL REFERENCE)
CHAPTER 7 -- SERIAL PRINTER INTERFACE (PRACTICAL REFERENCE) ============================================================================ This chapter consolidates the printer-related information scattered across Chapters 4 and 6, adds connector pinout notes from the Sega service manual, and explains the ROM hooks used by LPRINT.
7.1 Overview
7.1 Overview ---------------------------------------------------------------------------- The SC-3000 exposes a serial printer interface via the 7-pin DIN connector on the back panel. Five active signals are routed through the 8255A PPI: Signal PPI pin Dir Description -------- ------- --- ------------------------------------------ DATA PC5 OUT Serial data bit sent to printer /FEED PC7 OUT Line feed request to printer /RESET PC6 OUT Reset printer BUSY PB6 IN Printer busy (1 = not ready) FAULT PB5 IN Printer fault (1 = error condition) Electrical level: TTL, fan-out 1 TTL (Sega service manual). BASIC commands that use the printer: LPRINT -- formatted output to printer (same syntax as PRINT) LLIST -- program listing to printer (same syntax as LIST) HCOPY -- copy current text screen to printer Syntax details: Vol. A1 ch. 4.10, 4.11 and ch. 1 (LLIST).
7.2 Connector pinout -- dual source comparison
7.2 Connector pinout -- dual source comparison ---------------------------------------------------------------------------- Female 7-pin DIN on the back panel. Two conflicting pinouts exist in the available documentation. Both are reproduced; the Sega service manual version is the primary source. SERVICE MANUAL (Sega, primary source): Pin PPI signal Dir Function --- ---------- --- ---------------------------------------- 1 PB5 IN FAULT (printer fault/paper-out) 2 PB6 IN BUSY (printer not ready) 3 PC5 OUT DATA (serial data to printer) 4 PC6 OUT RESET (reset printer) 5 PC7 OUT FEED (line feed) 6 GND -- Ground 7 NC -- Not connected MacDONALD (hardware analysis, 2002): Pin PPI signal Dir Function --- ---------- --- ---------------------------------------- 1 -- -- Unused (not connected) 2 PC5 OUT DATA output 3 PC7 OUT /FEED output 4 PB6 IN BUSY input 5 PC6 OUT /RESET output 6 PB5 IN FAULT input 7 GND -- Ground The two tables differ only in which physical pin carries each signal (possibly a front-vs-back pin numbering convention, or a hardware revision). The PPI bit assignments are consistent between both sources.
7.3 BASIC interface -- how LPRINT works internally
7.3 BASIC interface -- how LPRINT works internally ---------------------------------------------------------------------------- BASIC routes all character output through a RAM pointer: PUTADR $82A0 (2 bytes) -- address of the active character output routine; normally points to the screen write handler When LPRINT or LLIST is executing, BASIC temporarily sets: PUTADR = $7008 The routine at $7008 manages the physical printer handshake: it polls BUSY, serialises the character byte on PC5 (DATA), and pulses the strobe. When the statement ends, PUTADR is restored to the screen handler. Redirecting LPRINT to a custom handler from ML: ; Save original PUTADR first LD HL, ($82A0) LD (saved_putadr), HL ; Install custom handler LD HL, my_printer_handler LD ($82A0), HL ; ... execute LPRINT statements ... ; Restore LD HL, (saved_putadr) LD ($82A0), HL
7.4 Checking printer status from ML
7.4 Checking printer status from ML ---------------------------------------------------------------------------- BUSY is PB6 (bit 6 of Port B, $DD). FAULT is PB5 (bit 5): IN A, ($DD) BIT 6, A ; test BUSY (PB6) JR NZ, printer_not_ready IN A, ($DD) BIT 5, A ; test FAULT (PB5) JR NZ, printer_fault Note: the routine at $48A3 is sometimes mistaken for printer-status reading -- it switches Port C to keyboard column 5 and reads Port B -- but it is a keyboard key-scanner (tests PB3 in columns 5-6), NOT a printer-status read and NOT BRKCHK (the real break-check is $5933, via SYS $4A03). For direct printer ML access the simple IN ($DD) / BIT 6 approach above is sufficient.
7.5 Sending a character via the ROM handler
7.5 Sending a character via the ROM handler ---------------------------------------------------------------------------- The safest way to output a character to the printer from ML is to dispatch through PUTADR after setting it to the ROM printer handler: ; Redirect PUTADR to ROM printer handler LD HL, $7008 LD ($82A0), HL ; Output one character (ASCII code in A) LD A, 'A' ; example: print 'A' LD HL, ($82A0) CALL_HL: PUSH HL RET ; equivalent to JP (HL) Simpler idiom (the ROM call entry point accepts character in A): LD A, charcode CALL $7008 ; call printer handler directly The PUTADR vector is also the target of the LPRINT bytecode handler, so any character sent through $7008 is subject to the same BUSY wait and handshake that LPRINT uses.
7.6 Physical wire protocol -- bit encoding and timing
7.6 Physical wire protocol -- bit encoding and timing ---------------------------------------------------------------------------- Source: ROM disassembly of $7008-$7099 in BASIC.rom (Level III-B cart). SIGNAL LINE: DATA = PC5 of 8255 PPI, controlled via port $DF in BSR mode. BSR commands: OUT ($DF), $0B -> SET PC5 = 1 (DATA HIGH) OUT ($DF), $0A -> RESET PC5 = 0 (DATA LOW) IDLE STATE: DATA = HIGH (1). PER-CHARACTER SEQUENCE: 1. BUSY WAIT Poll PB6 (Port $DD bit 6). Spin until BUSY = 0 (printer ready). Interrupts remain ENABLED during polling. 2. CRITICAL SECTION START DI (disable interrupts for the duration of the bit stream) 3. PRE-STROBE (~12.8 µs) SET DATA HIGH (OUT $DF,$0B). CDELAY(C = $0E = 14) => 279 ns × 14 = 3.9 µs + loop overhead Actual measured gap from ROM timing: ~12.8 µs. 4. BIT STREAM (8 bits, LSB first, INVERTED logic) For each bit b0..b7 of the character byte: If bit = 1: SET DATA LOW (OUT $DF,$0A) -- char 1 -> wire LOW If bit = 0: SET DATA HIGH (OUT $DF,$0B) -- char 0 -> wire HIGH Each bit period: CDELAY(C = $2E = 46) => 279 ns × 46 = 12.8 µs + Z80 loop overhead (~23 cycles = 6.4 µs) = ~19.2 µs per half-step Total per bit: ~36 µs => ~27.8 kbps effective bit rate. 5. END-OF-BYTE STROBE (~27.9 µs) RESET DATA LOW (OUT $DF,$0A). CDELAY(C = $64 = 100) => 279 ns × 100 = 27.9 µs. 6. CRITICAL SECTION END EI (re-enable interrupts) PRINTABLE RANGE: Characters with ASCII code < $20 (space) or > $7E are replaced with space (0x20) by the ROM at $702A before the bit stream is sent. Exception: $0D (CR) is handled by the caller (LPRINT/LLIST) and triggers a line feed; it is not passed to the character sender. INTER-CHARACTER GAP: After EOB the printer asserts BUSY (PB6 = 1) while it processes the character. The next character's BUSY-WAIT loop (step 1) blocks until the printer clears BUSY. Duration is printer-dependent. TIMING SUMMARY (per character, excluding BUSY wait): Phase Duration DATA state ----------- ---------- ---------------------------------------- Idle -- HIGH PRE ~12.8 µs HIGH (held, giving printer start signal) bit 0 (LSB) ~36 µs HIGH if char_bit=0 / LOW if char_bit=1 bit 1 ~36 µs (same rule) ... bit 7 (MSB) ~36 µs (same rule) EOB ~27.9 µs LOW (end-of-byte strobe, always LOW) Inter-char printer-dep. (BUSY line HIGH, then LOW when ready) TOTAL WIRE TIME per character (no BUSY wait): 12.8 + (8 × 36) + 27.9 = ~328.7 µs (~3 045 chars/s theoretical max)
7.7 Timing diagram -- "Hello World\r" (logic analyzer view)
7.7 Timing diagram -- "Hello World\r" (logic analyzer view) ---------------------------------------------------------------------------- Scale: each cell [ ] = 1 bit period (~36 µs). PRE and EOB are shown as half-width markers (·). BUSY is shown per character as a block between EOB and the next PRE; actual BUSY duration is printer-dependent. Wire encoding rule: char bit 1 -> DATA LOW (L); char bit 0 -> DATA HIGH (H). SINGLE CHARACTER DETAIL (character 'H' = 0x48): Bit name: IDLE PRE b0 b1 b2 b3 b4 b5 b6 b7 EOB BUSY DATA: H H H H H L H H L H L ~ Time (µs): ... 12.8 36 36 36 36 36 36 36 36 27.9 (var) |<-------- 288 µs bit stream -------->| 'H' = 0x48 = 0100 1000 binary. LSB first: b0=0 b1=0 b2=0 b3=1 b4=0 b5=0 b6=1 b7=0 Inverted: b0=H b1=H b2=H b3=L b4=H b5=H b6=L b7=H MULTI-SIGNAL WAVEFORM (character 'A' = 0x41 = 0100 0001 b) Format: - = HIGH, _ = LOW, | = signal edge (transition). Bit values (LSB first): b0=1 b1=0 b2=0 b3=0 b4=0 b5=0 b6=1 b7=0 Wire (0->H, 1->L): b0=L b1=H b2=H b3=H b4=H b5=H b6=L b7=H Phase: IDLE+PRE b0 b1 b2 b3 b4 b5 b6 b7 EOB BUSY us: 0 12.8 48.8 84.8 121 157 193 229 265 301 329+ DATA (PC5) --------| |-------------------------| |-----| |_____| |_____| |____________ BUSY (PB6) |------- _________________________________________________________| L (printer ready throughout character frame) H (busy) ^ DI (Z80 IRQ off) ^ EI (Z80 interrupts disabled for entire bit-stream; re-enabled after EOB) FULL STRING "Hello World\r" -- DATA line only: Char Hex b0 b1 b2 b3 b4 b5 b6 b7 Wire stream (· = PRE/EOB boundary) ---- ---- -- -- -- -- -- -- -- -- ------------------------------------- 'H' 0x48 0 0 0 1 0 0 1 0 H · H H H L H H L H · L 'e' 0x65 1 0 1 0 0 1 1 0 H · L H L H H L L H · L 'l' 0x6C 0 0 1 1 0 1 1 0 H · H H L L H L L H · L 'l' 0x6C 0 0 1 1 0 1 1 0 H · H H L L H L L H · L 'o' 0x6F 1 1 1 1 0 1 1 0 H · L L L L H L L H · L ' ' 0x20 0 0 0 0 0 1 0 0 H · H H H H H L H H · L 'W' 0x57 1 1 1 0 1 0 1 0 H · L L L H L H L H · L 'o' 0x6F 1 1 1 1 0 1 1 0 H · L L L L H L L H · L 'r' 0x72 0 1 0 0 1 1 1 0 H · H L H H L L L H · L 'l' 0x6C 0 0 1 1 0 1 1 0 H · H H L L H L L H · L 'd' 0x64 0 0 1 0 0 1 1 0 H · H H L H H L L H · L '\r' 0x0D 1 0 1 1 0 0 0 0 H · L H L L H H H H · L Legend: H = DATA HIGH (wire voltage ~+5 V TTL) L = DATA LOW (wire voltage ~0 V TTL) · = PRE (DATA HIGH, ~12.8 µs) before b0 / EOB (DATA LOW, ~27.9 µs) after b7 BUSY signal (not shown above) goes HIGH after each · L and returns LOW before the next character's · H can begin. NOTE on '\r': the ASCII carriage return (0x0D) is sent to the physical wire by LPRINT/LLIST as an actual byte. Some printers interpret 0x0D as carriage return; others require 0x0A (LF) or 0x0D+0x0A (CRLF). The ROM does not append 0x0A automatically.
7.8 Emulator interception -- capturing LPRINT output in software
7.8 Emulator interception -- capturing LPRINT output in software
----------------------------------------------------------------------------
In a JavaScript SC-3000 emulator the serial printer protocol never
produces actual wire transitions. Instead, the PPI port writes to
$DF are silently discarded. To capture LPRINT output as text,
intercept the call BEFORE the bit-serialisation loop runs.
RECOMMENDED HOOK POINT: the PUTCHAR dispatcher.
PUTCHAR lives at ROM address $2400 (or similar; check your ROM build).
Every character sent to any output device -- screen or printer --
passes through this point. At the hook:
* cpu.PC === PUTCHAR_ADDR (e.g. 0x2400)
* cpu.A === character code (0x20-0x7E for printable ASCII)
* ram[0x82A0] | (ram[0x82A1] << 8) === 0x7008 --> printer active
ALTERNATIVE HOOK POINT: the ROM printer handler entry.
Hook directly at 0x7008. Every time the emulator is about to enter
the printer bit-stream loop:
* cpu.PC === 0x7008
* cpu.A === character code
This is simpler; no PUTADR check needed. Any call to $7008 is a
printer output call.
JAVASCRIPT EXAMPLE (hook at $7008):
const PRINTER_ENTRY = 0x7008;
let printerBuffer = '';
cpu.addBreakpoint(PRINTER_ENTRY, () => {
const ch = cpu.A & 0xFF;
if (ch >= 0x20 && ch <= 0x7E) {
printerBuffer += String.fromCharCode(ch);
} else if (ch === 0x0D) {
printerBuffer += '\n';
}
// Skip the ROM printer routine -- advance PC past it
// Option A: set PC to the return address already on the stack
cpu.PC = cpu.popWord(); // equivalent to RET
return true; // signal: hook consumed this call
});
// To copy to clipboard when printer is done:
function flushPrinter() {
navigator.clipboard.writeText(printerBuffer);
printerBuffer = '';
}
NOTES:
* The hook must fire BEFORE the DI / bit-loop so interrupts are
not left disabled if the loop is skipped.
* Non-printable characters (< 0x20 or > 0x7E) are already replaced
with space by ROM code at $702A before reaching $7008; the hook
at $7008 will receive 0x20 for those characters.
* HCOPY sends characters in screen order (left-right, top-bottom),
including trailing spaces on each line. Post-process with
trimEnd() on each line if compact output is preferred.
* LLIST adds CR (0x0D) at the end of each BASIC line.
============================================================================