Miscellaneous #2
Apple II Family Identification Routines 2.2

Revised by Jim Luther (May 1991)
Revised by Matt Deatherage & Keith Rollin (November 1988)
Revisec by Pete McDonald (January 1986)

This Technical Note presents a new version of the Apple II Family Identification Routine, a sample piece of code which shows how to identify various Apple II computers and their memory configurations.

Changes since November 1988: Converted the identification routine from Apple II Assembler/Editor (EDASM) source code to Apple IIgs Programmer's Workshop (APW) Assembler source code. Added the Apple IIe Card for the Macintosh LC to the identification routine's lookup table and memory check routine. Made minor corrections to text.

Note: Yes, there are three "revised" by-lines and no "written" by-line for this note. This is how I found it when I got it off of the archives online. -- AH

Why Identification Routines?

Although we present the Apple II family identification bytes in Apple II Miscellaneous Technical Note #7, many people would prefer a routine they can simply plug into their own program and call. In addition, this routine serves as a small piece of sample code, and there is no reason for you to reinvent the wheel.

Most of the interesting part of the routine consists of identifying the memory configuration of the machine. On an Apple IIe, the routine moves code into the zero page to test for the presence of auxiliary memory. (A IIe with a non-extended 80-column card is a configuration still found in many schools throughout the country.)

The actual identification is done by a table-lookup method.

What the Routine Returns

This version (2.2) of the identification routine returns several things:

In addition to these features, most of the addressing done in the routine is by label. If you wish things to be stored in different places, simply changing the labels will often do it.

Limitations and Improvements

As sample code, you might have already guessed that this is not the most compact, efficient way of identifying these machines. Some improvements you might incorporate if using these routines include:

How It Works

The identification routine does the following things:

           keep ID2.2

           list on

           org $2000

           longa off
           longi off

*                                           *
*  Apple II Family Identification Program   *
*                                           *
*               Version 2.2                 *
*                                           *
*               March, 1990                 *
*                                           *
*  Includes support for the Apple IIe Card  *
*  for the Macintosh LC.                    *
*                                           *

;  First, some global equates for the routine:

PROGRAM    start

IIplain    equ $01          ;Apple II
IIplus     equ $02          ;Apple II+
IIIem      equ $03          ;Apple /// in emulation mode
IIe        equ $04          ;Apple IIe
IIc        equ $05          ;Apple IIc
IIeCard    equ $06          ;Apple IIe Card for the Macintosh LC

safe       equ $0001        ;start of code relocated to zp
location   equ $06          ;zero page location to use

test1      equ $AA          ;test byte #1
test2      equ $55          ;lsr of test1
test3      equ $88          ;test byte #3
test4      equ $EE          ;test byte #4

begpage1   equ $400         ;beginning of text page 1
begpage2   equ $800         ;beginning of text page 2
begsprse   equ $C00         ;byte after text page 2

clr80col   equ $C000        ;disable 80-column store
set80col   equ $C001        ;enable 80-column store
rdmainram  equ $C002        ;read main ram
rdcardram  equ $C003        ;read aux ram
wrmainram  equ $C004        ;write main ram
wrcardram  equ $C005        ;write aux ram
rdramrd    equ $C013        ;are we reading aux ram?
rdaltzp    equ $C016        ;are we reading aux zero page?
rd80col    equ $C018        ;are we using 80-columns?
rdtext     equ $C01A        ;read if text is displayed
rdpage2    equ $C01C        ;read if page 2 is displayed
txtclr     equ $C050        ;switch in graphics
txtset     equ $C051        ;switch in text
txtpage1   equ $C054        ;switch in page 1
txtpage2   equ $C055        ;switch in page 2
ramin      equ $C080        ;read LC bank 2, write protected
romin      equ $C081        ;read ROM, 2 reads write enable LC
lcbank1    equ $C08B        ;LC bank 1 enable

lc1        equ $E000        ;bytes to save for LC
lc2        equ $D000        ;save/restore routine
lc3        equ $D400
lc4        equ $D800

idroutine  equ $FE1F        ;IIgs id routine

;  Start by saving the state of the language card banks and
;  by switching in main ROM.

strt       php              ;save the processor state
           sei              ;before disabling interrupts
           lda lc1          ;save four bytes from
           sta save         ;ROM/RAM area for later
           lda lc2          ;restoring of RAM/ROM
           sta save+1       ;to original condition
           lda lc3
           sta save+2
           lda lc4
           sta save+3
           lda $C081        ;read ROM
           lda $C081
           lda #0           ;start by assuming unknown machine
           sta machine
           sta romlevel

IdStart    lda location     ;save zero page locations
           sta save+4       ;for later restoration
           lda location+1
           sta save+5
           lda #$FB         ;all ID bytes are in page $FB
           sta location+1   ;save in zero page as high byte
           ldx #0           ;init pointer to start of ID table
loop       lda IDTable,x    ;get the machine we are testing for
           sta machine      ;and save it
           lda IDTable+1,x  ;get the ROM level we are testing for
           sta romlevel     ;and save it
           ora machine      ;are both zero?
           beq matched      ;yes - at end of list - leave

loop2      inx              ;bump index to loc/byte pair to test
           lda IDTable,x    ;get the byte that should be in ROM
           beq matched      ;if zero, we're at end of list
           sta location     ;save in zero page

           ldy #0           ;init index for indirect addressing
           lda IDTable+1,x  ;get the byte that should be in ROM
           cmp (Location),y ;is it there?
           beq loop2        ;yes, so keep on looping

loop3      inx              ;we didn't match. Scoot to the end of the
           inx              ;line in the ID table so we can start
           lda IDTable,x    ;checking for another machine
           bne loop3
           inx              ;point to start of next line
           bne loop         ;should always be taken

matched    anop

;  Here we check the 16-bit ID routine at idroutine ($FE1F).  If it
;  returns with carry clear, we call it again in 16-bit
;  mode to provide more information on the machine.

idIIgs     sec              ;set the carry bit
           jsr idroutine    ;Apple IIgs ID Routine
           bcc idIIgs2      ;it's a IIgs or equivalent
           jmp IIgsOut      ;nope, go check memory
idIIgs2    lda machine      ;get the value for machine
           ora #$80         ;and set the high bit
           sta machine      ;put it back
           clc              ;get ready to switch into native mode
           php              ;save the processor status
           rep #$30         ;sets 16-bit registers
           longa on
           longi on
           jsr idroutine    ;call the ID routine again
           sta IIgsA        ;16-bit store!
           stx IIgsX        ;16-bit store!
           sty IIgsY        ;16-bit store!
           plp              ;restores 8-bit registers
           xce              ;switches back to whatever it was before
           longa off
           longi off

           ldy IIgsY        ;get the ROM vers number (starts at 0)
           cpy #$02         ;is it ROM 01 or 00?
           bcs idIIgs3      ;if not, don't increment
           iny              ;bump it up for romlevel
idIIgs3    sty romlevel     ;and put it there
           cpy #$01         ;is it the first ROM?
           bne IIgsOut      ;no, go on with things
           lda IIgsY+1      ;check the other byte too
           bne IIgsOut      ;nope, it's a IIgs successor
           lda #$7F         ;fix faulty ROM 00 on the IIgs
           sta IIgsA
IIgsOut    anop

* This part of the code checks for the   *
* memory configuration of the machine.   *
* If it's a IIgs, we've already stored   *
* the total memory from above.  If it's  *
* a IIc or a IIe Card, we know it's      *
* 128K; if it's a ][+, we know it's at   *
* least 48K and maybe 64K.  We won't     *
* check for less than 48K, since that's  *
* a really rare circumstance.            *

exit       lda machine      ;get the machine kind
           bmi exit128      ;it's a 16-bit machine (has 128K)
           cmp #IIc         ;is it a IIc?
           beq exit128      ;yup, it's got 128K
           cmp #IIeCard     ;is it a IIe Card?
           beq exit128      ;yes, it's got 128K
           cmp #IIe         ;is it a IIe?
           bne contexit     ;yes, go muck with aux memory
           jmp muckaux
contexit   cmp #IIIem       ;is it a /// in emulation?
           bne exitII       ;nope, it's a ][ or ][+
           lda #48          ;/// emulation has 48K
           jmp exita
exit128    lda #128         ;128K
exita      sta memory
exit1      lda lc1          ;time to restore the LC
           cmp save         ;if all 4 bytes are the same
           bne exit2        ;then LC was never on so
           lda lc2          ;do nothing
           cmp save+1
           bne exit2
           lda lc3
           cmp save+2
           bne exit2
           lda lc4
           cmp save+3
           beq exit6
exit2      lda $C088        ;no match! so turn first LC
           lda lc1          ;bank on and check
           cmp save
           beq exit3
           lda $C080
           jmp exit6
exit3      lda lc2
           cmp save+1       ;if all locations check
           beq exit4        ;then do more more else
           lda $C080        ;turn on bank 2
           jmp exit6
exit4      lda lc3          ;check second byte in bank 1
           cmp save+2
           beq exit5
           lda $C080        ;select bank 2
           jmp exit6
exit5      lda lc4          ;check third byte in bank 1
           cmp save+3
           beq exit6
           lda $C080        ;select bank 2
exit6      plp              ;restore interrupt status
           lda save+4       ;put zero page back
           sta location
           lda save+5       ;like we found it
           sta location+1
           rts              ;and go home.

exitII     lda lcbank1      ;force in language card
           lda lcbank1      ;bank 1
           ldx lc2          ;save the byte there
           lda #test1       ;use this as a test byte
           sta lc2
           eor lc2          ;if the same, should return zero
           bne noLC
           lsr lc2          ;check twice just to be sure
           lda #test2       ;this is the shifted value
           eor lc2          ;here's the second check
           bne noLC
           stx lc2          ;put it back!
           lda #64          ;there's 64K here
           jmp exita
noLC       lda #48          ;no restore - no LC!
           jmp exita        ;and get out of here

muckaux    ldx rdtext       ;remember graphics in X
           lda rdpage2      ;remember current video display
           asl A            ;in the carry bit
           lda #test3       ;another test character
           bit rd80col      ;remember video mode in N
           sta set80col     ;enable 80-column store
           php              ;save N and C flags
           sta txtpage2     ;set page two
           sta txtset       ;set text
           ldy begpage1     ;save first character
           sta begpage1     ;and replace it with test character
           lda begpage1     ;get it back
           sty begpage1     ;and put back what was there
           bcs muck2        ;stay in page 2
           sta txtpage1     ;restore page 1
muck1      bmi muck2        ;stay in 80-columns
           sta $c000        ;turn off 80-columns
muck2      tay              ;save returned character
           txa              ;get graphics/text setting
           bmi muck3
           sta txtclr       ;turn graphics back on
muck3      cpy #test3       ;finally compare it
           bne nocard       ;no 80-column card!
           lda rdramrd      ;is aux memory being read?
           bmi muck128      ;yup, there's 128K!
           lda rdaltzp      ;is aux zero page used?
           bmi muck128      ;yup!
           ldy #done-start
move       ldx start-1,y    ;swap section of zero page
           lda |safe-1,y    ;code needing safe location during
           stx safe-1,y     ;reading of aux mem
           sta start-1,Y
           bne move
           jmp |safe        ;jump to safe ground
back       php              ;save status
           ldy #done-start  ;move zero page back
move2      lda start-1,y
           sta |safe-1,y
           bne move2
           bcs noaux
isaux      jmp muck128      ;there is 128K

*  You can put your own routine at "noaux" if you wish to
*  distinguish between 64K without an 80-column card and
*  64K with an 80-column card.

noaux      anop
nocard     lda #64          ;only 64K
           jmp exita
muck128    jmp exit128      ;there's 128K

*  This is the routine run in the safe area not affected
*  by bank-switching the main and aux RAM.

start      lda #test4       ;yet another test byte
           sta wrcardram    ;write to aux while on main zero page
           sta rdcardram    ;read aux ram as well
           sta begpage2     ;check for sparse memory mapping
           lda begsprse     ;if sparse, these will be the same
           cmp #test4       ;value since they're 1K apart
           bne auxmem       ;yup, there's 128K!
           asl begsprse     ;may have been lucky so we'll
           lda begpage2     ;change the value and see what happens
           cmp begsprse
           bne auxmem
           sec              ;oops, no auxiliary memory
           bcs goback
auxmem     clc
goback     sta wrmainram    ;write main RAM
           sta rdmainram    ;read main RAM
           jmp back         ;continue with program in main mem
done       nop              ;end of relocated program marker

*  The storage locations for the returned machine ID:

machine    ds  1            ;the type of Apple II
romlevel   ds  1            ;which revision of the machine
memory     ds  1            ;how much memory (up to 128K)
IIgsA      ds  2            ;16-bit field
IIgsX      ds  2            ;16-bit field
IIgsY      ds  2            ;16-bit field
save       ds  6            ;six bytes for saved data

IDTable    dc  I1'1,1'      ;Apple ][
           dc  H'B3 38 00'

           dc  I1'2,1'      ;Apple ][+
           dc  H'B3 EA 1E AD 00'

           dc  I1'3,1'      ;Apple /// (emulation)
           dc  H'B3 EA 1E 8A 00'

           dc  I1'4,1'      ;Apple IIe (original)
           dc  H'B3 06 C0 EA 00'

;  Note: You must check for the Apple IIe Card BEFORE you
;  check for the enhanced Apple IIe since the first
;  two identification bytes are the same.

           dc  I1'6,1'      ;Apple IIe Card for the Macintosh LC (1st 
           dc  H'B3 06 C0 E0 DD 02 BE 00 00'

           dc  I1'4,2'      ;Apple IIe (enhanced)
           dc  H'B3 06 C0 E0 00'

           dc  I1'5,1'      ;Apple IIc (original)
           dc  H'B3 06 C0 00 BF FF 00'

           dc  I1'5,2'      ;Apple IIc (3.5 ROM)
           dc  H'B3 06 C0 00 BF 00 00'

           dc  I1'5,3'      ;Apple IIc (Mem. Exp)
           dc  H'B3 06 C0 00 BF 03 00'

           dc  I1'5,4'      ;Apple IIc (Rev. Mem. Exp.)
           dc  H'B3 06 C0 00 BF 04 00'

           dc  I1'5,5'      ;Apple IIc Plus
           dc  H'B3 06 C0 00 BF 05 00'

           dc  I1'0,0'      ;end of table


Further Reference

This and all of the other Apple II Technical Notes have been converted to HTML by Aaron Heiss as a public service to the Apple II community, with permission by Apple Computer, Inc. Any and all trademarks, registered and otherwise, are properties of their owners.