The Apple III has two different ways ways of accessing beyond the 6502's 64K addressing limit. The first is a fairly conventional bank-switching technique. The Apple III's memory is divided into three areas--a "lower system bank" from $0000 to $1FFF, an "upper system bank" from $A000 to $FFFF, and a bank-switched area in between (from $2000 to $9FFF). The two "system banks" are constant, and the in-between area can be bank-switched between as many as 15 32K banks. The four low-order bits at $FFEF control which bank is currently switched in--any number from $0 to $E can be specified ($F is reserved for some unspecified "special purpose"). Not all fifteen banks may be available--the highest available bank number in a 128K machine is 3, and the highest available bank in a 256K machine is 7. The second method is a bizarre thing called "extended addressing." This depends on a couple of other bizarre facts:
$00 through $FF. The 6502 is separated from memory by a "switch box" which (among other things) notices whenever an address of the form $00xx is referenced, and fetches (or writes to) memory location $nnxx instead, where nn is the current contents of memory location $FFD0. This means zero page can by moved anywhere in memory by storing a new address at $FFD0.$xxxx is accessed, the above-mentioned "switch box" also fetches the byte at "$xxxx XOR $0C00". (This extra byte is used by the video circuitry in 80-column mode.)
Whenever the 6502 uses a (ZP),Y addressing mode, and the zero page register ($FFD0) contains a number from $18 to $1F, the "switch box" uses the above-mentioned extra byte (called the "Xbyte") to perform "extended addressing." If the Xbyte (fetched from page ($FFD0) XOR $0C) is outside the range $80...$8F, then the indirect memory access occurs normally. Otherwise the switch box intervenes again and briefly remaps memory just long enough enough for the indirect memory access. If the low nibble of the Xbyte is n, then the indirect addressing sees a memory map composed of bank n (that's the same bank n from the normal bank-switching method mentioned above) mapped into $0000...$7FFF, and bank n+1 mapped into $8000...$FFFF.
....Well, almost. If the Xbyte is $8F, something special happens. The indirect addressing sees the normal Apple III memory map with bank 0 switched into $2000...$9FFF. As an added bonus, it also sees the RAM that's hiding under the VIA control registers at $FFD0...$FFEF. In fact, this is apparently the ONLY way to get at those 32 bytes of RAM. Since this memory is unavailable by other means, the operating system uses it to store the last valid value of the system clock.
;The procedure PEEK and the function POKE written by John Jeppson
;published in SOFTALK magazine: AUG 82 in the article
;BANK SWITCH RAZZLE DAZZLE: Peeking and Poking the Apple ///
.MACRO POP
PLA
STA %1
PLA
STA %1+1
.ENDM
.MACRO PUSH
LDA %1+1
PHA
LDA %1
PHA
.ENDM
ADDRESS .EQU 0E8 ;zeropage "pseudo" register
BANKSW .EQU 0FFEF
ZEROPG .EQU 0FFD0
ENVRMT .EQU 0FFDF
.FUNC PEEK,2
JMP BEGIN
RETURN .WORD 0
XBYTE .WORD 0
RESULT .WORD 0
OLDXBT .BYTE 0
OLDZPG .BYTE 0
ENV .BYTE 0
BEGIN POP RETURN
PLA ;"dummy" bytes for function
PLA
PLA
PLA
POP XBYTE ;parameters come off in reverse order
POP ADDRESS
LDA ADDRESS+1601 ;save original x-byte value
STA OLDXBT
;which bank is desired
LDA XBYTE
CMP #0FF ;FF = ROM #1, C0-CF = I/O,
BEQ SPECIAL ; "true" 00 and 01 pages
CMP #80
BMI SYSTEM ;80-8F=extended addressing
CMP #90 ; else system bank (ordinary 6502)
BMI EXTEND
;handle system bank
SYSTEM LDY #0
STY ADDRESS+1601 ;xbyte = 0 so get ordinary 6502
LDA @ADDRESS,Y ; indirect indexed addressing
STA RESULT
JMP DONE
;handle extended addressing to a bankpair or $8F
EXTEND STA ADDRESS+1601 ;place extend byte
LDY #0
LDA @ADDRESS,Y ;"extended" addressing to desired
STA RESULT ; bank pair
JMP DONE
;handle artifical bank 'FF'
SPECIAL LDA ADDRESS+1
BEQ TRUEPGS ;true $00, $01 desired?
CMP #1
BEQ TRUEPGS
;ROM#1 --> F000-FFFF, C000-CFFF --> I/O
PHP ;save status, then disable interrupts
SEI ;(an "illegal" move)
LDA ENVRMT ;save environment
STA ENV
LDA #73 ;#% 0111 0011 - new environment reg
STA ENVRMT ;(an "illegal" move)
LDY #0
STY ADDRESS+1601 ;system bank xbyte = 00
LDA @ADDRESS,Y
STA RESULT
LDA ENV ;restore ENVRMT
STA ENVRMT
PLP ;restore status (including interrupts)
JMP DONE
;desired address on true 00 or 01 page
TRUEPGS PHP ;save status, then disable interrupts
SEI ;(an "illegal" move)
LDX ADDRESS ;load BEFORE leaving old z-page
LDY ADDRESS+1
LDA ZEROPG ;save old zpg
STA OLDZPG
LDA #0 ;changes zero-page to 0, stack to 1
STA ZEROPG ;(an "illegal" move)
TYA ;is high byte 00 or 01
BEQ $1
LDA 0100,X ;indexed addressing (x = addr)
JMP $2
$1 LDA 0000,X
$2 STA RESULT
LDA OLDZPG ;restore ZEROPG (and stack page)
STA ZEROPG
PLP ;restore interrupts (status)
DONE LDA OLDXBT ;restore Pascal's xbyte
STA ADDRESS+1601
PUSH RESULT
PUSH RETURN
RTS
.PROC POKE,3
JMP BEGIN
RETURN .WORD 0
XBYTE .WORD 0
VALUE .WORD 0
OLDXBT .BYTE 0
OLDZPG .BYTE 0
OLDENV .BYTE 0
ENV .BYTE 0
BEGIN POP RETURN ;parameters come off in reverse order
POP VALUE
POP XBYTE
POP ADDRESS
LDA ADDRESS+1601 ;save original x-byte value
STA OLDXBT
LDA ENVRMT ;save ENVRMT
STA OLDENV
AND #0F7 ;for POKE, enable write C000 to FFFF
STA ENVRMT
;which bank is desired
LDA XBYTE
CMP #80
BMI $1 ;80-8F=extended addressing
CMP #90 ; else system bank (ordinary 6502)
BMI EXTEND
;disallow certain addresses
$1 LDA ADDRESS+1 ;POKE disallowed at (system bank):
CMP #0FF ; BANKSW = FFEF
BNE $2 ; ENVRMT = FFDF
; ZEROPG = FFD0
LDA ADDRESS
CMP #0D0 ; in this program - suicide certain
BEQ DONE ; in your program - suicide probable
CMP #0DF
BEQ DONE ;if you really want to crash, just star
CMP #0EF ; POKing into SOS (RAM $B800 - FFFF)
BEQ DONE ; soon he will get very sick
;detect artificial bank 'FF'
$2 LDA XBYTE
CMP #0FF ;FF = ROM #1, C0-CF = I/O
BEQ SPECIAL ; "true" 00 and 01 pages
;handle system bank
SYSTEM LDY #0
STY ADDRESS+1601 ;xbyte = 0 so get ordinary 6502
LDA VALUE ; indirect indexed addressing
STA @ADDRESS,Y
JMP DONE
;handle extended addressing to a bankpair or $8F
EXTEND STA ADDRESS+1601 ;place extend byte
LDY #0
LDA VALUE
STA @ADDRESS,Y ;"extended" addressing to desired
; bank pair
JMP DONE
;handle artifical bank 'FF'
SPECIAL LDA ADDRESS+1
BEQ TRUEPGS ;true zp or $01 desired?
CMP #1
BEQ TRUEPGS
;ROM#1 --> F000-FFFF, C000-CFFF --> I/O
PHP ;save status, then disable interrupts
SEI ;(an "illegal" move)
LDA ENVRMT ;save environment
STA ENV
LDA #73 ;#% 0111 0011 - new environment reg
STA ENVRMT ;(an "illegal" move)
LDY #0
STY ADDRESS+1601 ;system bank xbyte = 00
LDA VALUE
STA @ADDRESS,Y
LDA ENV ;restore ENVRMT
STA ENVRMT
PLP ;restore status (including interrupts)
JMP DONE
;desired address on true 00 or 01 page
TRUEPGS PHP ;save status, then disable interrupts
SEI ;(an "illegal" move)
LDX ADDRESS ;load BEFORE leaving old z-page
LDY ADDRESS+1
LDA ZEROPG ;save old zpg
STA OLDZPG
LDA #0 ;changes zero page to 0, stack to 1
STA ZEROPG ;(an "illegal" move)
LDA VALUE
CPY #0 ;is high byte 00 or 01
BEQ $1
STA 0100,X ;indexed addressing (x = addr)
JMP $2
$1 STA 0000,X
$2 LDA OLDZPG ;restore ZEROPG (and stack page)
STA ZEROPG
PLP ;restore interrupts (status)
DONE LDA OLDXBT ;restore Pascal's xbyte
STA ADDRESS+1601
LDA OLDENV ;restore C0-CF read/write status
STA ENVRMT
PUSH RETURN
RTS
.END