From the article "Bank Switch Razzle-Dazzle -- Peeking and Poking the Apple III" by John Jeppson, Softalk, August 1982, p. 38:

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:

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 code below formed another part of the article: it is a peek/poke invokable module.

;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

Back