OS Workspace — Pages 1, 2, 3

Beyond zero page (zero-page), MOS occupies three more pages of low RAM. Knowing the layout matters when:

  • You want to read MOS state without using OSBYTEs (faster lookups).
  • You want to bypass MOS for a particular function and need to know what state to restore.
  • You’re writing code that lives in low RAM and want to avoid clobbering MOS variables.

Page 1 (&0100-&01FF) — Stack

The 6502 hardware stack. BBC firmware initialises SP to &FF; the stack grows downward from &01FF. SP wraps within page 1 on overflow with no trap, so deep recursion or unbalanced pushes silently corrupt earlier stack frames.

When MOS calls into your code via OSWRCH/OSRDCH/IRQ/etc, the upper portion of page 1 contains the return-address chain. Don’t touch it.

Pages 8-9 (&0800-&09FF) — Buffers

From NAUG §6.6 p114:

RangeUse
&800-&83FSound workspace
&840-&84FSound channel 0
&850-&85FSound channel 1
&860-&86FSound channel 2
&870-&87FSound channel 3
&880-&8BFPrinter buffer (distinct region — NOT shared with sound)
&8C0-&8FFEnvelope storage 1-4 (13 bytes each + 3 spare)
&900-&9BFRS423 output buffer — OR Envelopes 5-16 — OR (overlap) CFS/RFS BPUT sequential output buffer
&9C0-&9FFSpeech buffer — overlapped by CFS/RFS BPUT output buffer when CFS active

Page B (&0B00-&0BFF) — Function keys

RangeUse
&B00-&B10Soft key pointers (one per function key)
&B11-&BFFSoft key definition strings

Page C (&0C00-&0CFF) — User character definitions

8 bytes per character × 32 characters = 256 bytes for ASCII 128-159 (or 224-255 on imploded font).

Page D (&0D00-&0DFF) — NMI + Econet

RangeUse
&D00-&D5FNMI handling code (filing systems install here)
&D60-&D7FEconet workspace
&D80-&D91Unused on most machines
&D92-&D9EReserved for trackerball
&D9F-&DEFExtended vector table (paged-ROM-claimed extra vectors)
&DF0-&DFFROM workspace bytes (per-ROM private workspace pointer)

NMI handlers are page-aligned at &D00. Disc filing systems and Econet share the NMI; claim/release protocol is via paged-ROM service calls (Ch17, pending).

Page 2 (&0200-&02FF) — OS vectors + OS variables

From NAUG §6.6.2:

RangeUse
&200-&235OS vector table (see calls)
&236-&28FOS variables — accessible via OSBYTE &A6-&FF. Direct read is faster than OSBYTE.
&290-&291*TV vertical adjust + interlace
&292-&296TIME — first copy (dual-clock to allow atomic 5-byte read)
&297-&29BTIME — second copy
&29C-&2A0Interval timer (OSWORDs &03/&04)
&2A1-&2B0Paged ROM table (OSBYTE &AA/&AB)
&2B1-onwardsINKEY countdown, OSWORD &00 workspace, ADC LSB/MSB tables, event enable flags, two-key rollover, buffer pointers, CFS state, OSFILE control blocks

Master and Electron differ in the exact addresses of some of these — NAUG p115 has a side-by-side table.

Useful direct reads

The OSBYTE-variable address is &0236 + (osbyte_number - &A6). So &D2 (sound suppression) lives at &0236 + (&D2 - &A6) = &0262. Worked examples:

AddrWhatOSBYTE-var #
&020E-&020FOSWRCH vector
&024AROM active at last BRK&BA (fx186)
&0261Speech suppression status&D1 (fx209)
&0262Sound suppression status&D2 (fx210)
&0277User VIA IRQ mask&E7 (fx231)
&0279System VIA IRQ mask&E9 (fx233)
&027COutput stream destination (FX3)&EC (fx236)
&027DCursor key status (FX4)&ED (fx237)
&028CCurrent language ROM&FC (fx252)
&028DLast BREAK type&FD (fx253)

Exact addresses are stable across BBC/B+/Master — the formula above always holds. Reading directly is faster than calling OSBYTE (saves the dispatch overhead). For full table see allmem-ripley-harston lines 179-287, or query OSBYTE &A6/&A7 to get the base address dynamically (in case a future MOS shifts it).

Page 3 (&0300-&03FF) — VDU workspace + filing buffers

From NAUG §13.2.2 (already in naug-ch13-video):

RangeUse
&300-&30BCurrent graphics window (&300-&307, pixels) + text window (&308-&30B, chars)
&30C-&30FGraphics origin (external coords)
&310-&317Current + old graphics cursor (external coords)
&318-&319Text cursor X / Y
&31ALine within graphics cell of cursor
&31BGraphics workspace / VDU queue control
&31F-&323VDU queue (5 bytes)
&324-&327Current graphics cursor (internal coords)
&328-&32FBitmap read from screen by OSBYTE 135
&330-&349Graphics workspace
&34A-&34BText cursor address for 6845
&34C-&34DText window width in bytes
&34EMSB of HIMEM (ignoring shadow)
&34FBytes per character (current mode)
&350-&351Top-left screen address as given to 6845 — this is the screen-start mirror for hardware scroll
&352-&353Bytes per char row (current mode)
&354Screen mode size in pages
&355Current screen mode (ignoring shadow bit)
&356Screen size code: 0=20K, 1=16K, 2=10K, 3=8K, 4=1K
&35FLast 6845 cursor start register value
&360Logical colours - 1
&361Pixels per byte - 1 (0 for text)
&362-&363Bit masks for left-most and right-most pixel
&366-&36EVDU 23 settings (Master ECF, dot patterns, etc.)
&36F-&37EFont location bytes (MSB per font group)
&37FUnused
&380-onwardsColour palette copy (1 byte per logical colour), BPUT/BGET buffers, filing-system block headers, keyboard input buffer

&350/&351 is the screen-start the OS thinks the 6845 has — keep this in sync if you hardware-scroll directly, so subsequent OSWRCH lands at the right pixel row. Equivalent to OSBYTE &A0, X=&50.

Bypassing MOS — what to save/restore

If you’re going fully native (game / demo style, no return to BASIC):

  • You don’t need to preserve anything. Set SP, set zero page, set IRQ vectors, set MOS-tracked hardware state to whatever you want, run forever (or until BRK/reset).

If you’re returning to BASIC after a routine:

  • Page 0: save &70-&8F and whatever else you touch (see zero-page).
  • OS palette (&FE21 + page 3 colour palette copy at &380+): if you wrote &FE21 directly, also write the same value (XOR 7) into the OS copy, or restore the original.
  • &350/&351 if you moved R12/R13.
  • IRQ vectors &204-&207 if you hooked them.
  • Sideways ROM register &FE30: never set — see paged-rom.

If you’re inside an IRQ handler:

  • A is in &FC (MOS save). Don’t touch.
  • Keep your handler under 2 ms (NAUG §8.5 — interrupts).
  • Don’t call any OS routine.

Master “second 32 KB” workspace map (&3000-&DFFF in LYNNE+ANDY+HAZEL)

The Master’s 128 KB of DRAM splits into two 64 KB regions. The “second 32 KB” (the upper half of the second region, partially overlaid by paged ROMs in the CPU map) contains a mix of shadow display and MOS workspace. Per master-arm Ch 11:

RangeUseNote
&3000-&7FFFLYNNE shadow screenAny portion not needed by the current mode is free; *MOVE will use it
&8000-&83FFSoft-key expansion buffer1 KB — replaces the cramped &0B00-&0BFF allocation on Model B. Not available for any other use
&8400-&88FFVDU workspace (flood-fill scratch)Routines tag &8601 to share; commercial software should avoid
&8900-&8FFFSoft character definitionsMoved from &0E00+ on B/B+ — exploded font no longer steals from user RAM. Big PAGE win
&C000-&DBFFHAZEL (paged-ROM workspace)ROMs claim via service calls; static workspace requires filing-system pattern
&DC00-&DCFFMOS CLI bufferCorrupted by every * command
&DD00-&DEFFTransient utility workspaceUsed by user * commands and *MOVE
&DF00-&DFFFMOS private workspaceOff-limits

The soft-character-definition relocation from &0E00+ to &8900 is one of the Master’s biggest practical wins: on a Model B, defining 32 custom characters cost 256 bytes of OSHWM. On Master those bytes live in the second 32 KB invisibly. Code that explicitly pokes &0E00-area font on a Model B needs a Master code path that targets &8900 instead — see memory-map for the broader map.

Master vector additions (&0D9F-&0DEF)

Per ARM Ch 11, the Master allocates 80 bytes for extended vectors at &0D9F-&0DEF. These let a sideways-ROM intercept any vector with the routine living in the ROM — without the ROM having to be in slot 15 or whatever. Mechanism:

  1. OSBYTE &A8 (168) returns the start of the extended-vector space (&0D9F).
  2. Write the per-vector triple at extended_start + 3 × vector_index: <entry_low> <entry_high> <rom_slot_number>.
  3. Replace the normal vector at &02xx with &FF00 + (vector_addr - &0200) × 3 / 2 — a special address the MOS recognises as “this dispatches via the extended vector triple”.

This is how things like the Master’s ADFS filing system, network filing system, and other complex ROM-resident handlers intercept vectors cleanly without trampling each other. The MOS handles ROM-bank switching on each dispatch via the extended vector.

Vector-number indexing (per ARM Ch 11 vector table): vector_number = (vector_addr - &0200) / 2. So OSWRCH (vector &20E) is vector number 7, OSWORD (&20C) is 6, OSBYTE (&20A) is 5, EVNTV (&220) is 16, etc.

See also

  • zero-page — Page 0 detail.
  • memory-map — Big picture.
  • calls — OS entry-point reference.
  • interrupts — IRQ handler conventions and &FC usage.
  • paged-roms — extended vectors install pattern (the high-level companion to the &0D9F-&0DEF detail above).

This wiki is curated by Claude following the LLM-Wiki methodology — a human curates source documents, the LLM compiles structured cross-linked markdown. Content may contain errors, omissions, or stale claims. For authoritative information refer to the original source documents in the bbc-documents GitHub archive.